summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/gc/br-on-cast.js
blob: cf83f1ba007f3ee9b99e52ec7ee2035c5f19d2f3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// |jit-test| skip-if: !wasmGcEnabled()

function typingModule(types, from, to, brParams, branchResults, fallthroughResults) {
  return `(module
    ${types}
    (func
      (param ${brParams.join(' ')})
      (result ${branchResults.join(' ')})

      (block (result ${fallthroughResults.join(' ')})
        (; push params onto the stack in the same order as they appear, leaving
          the last param at the top of the stack. ;)
        ${brParams.map((_, i) => `local.get ${i}`).join('\n')}
        br_on_cast 1 ${from} ${to}
      )
      unreachable
    )
  )`;
}

function validTyping(types, from, to, brParams, branchResults, fallthroughResults) {
  wasmValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults));
}

function invalidTyping(types, from, to, brParams, branchResults, fallthroughResults, error) {
  wasmFailValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults), error);
}

// valid: eqref -> struct
validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['eqref']);
// valid: eqref -> struct (and looser types on results)
validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref null $a)'], ['anyref']);
// valid: eqref -> nullable struct (note that fallthrough becomes non-nullable)
validTyping('(type $a (struct))', 'eqref', '(ref null $a)', ['eqref'], ['(ref null $a)'], ['(ref eq)']);
// valid: struct -> struct (from anyref)
validTyping('(type $a (struct))', 'anyref', '(ref $a)', ['(ref $a)'], ['(ref $a)'], ['anyref']);
// valid: struct -> struct (canonicalized)
validTyping('(type $a (struct)) (type $b (struct))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $b)']);
// valid: nullable struct -> non-nullable struct (canonicalized)
validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref $b)', ['(ref null $a)'], ['(ref $b)'], ['(ref null $a)']);
// valid: nullable struct -> nullable struct (canonicalized)
validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref null $b)', ['(ref null $a)'], ['(ref null $a)'], ['(ref $a)']);
// valid: eqref -> struct with extra arg
validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'eqref'], ['i32', '(ref $a)'], ['i32', 'eqref']);
// valid: eqref -> struct with two extra args
validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref']);

// invalid: block result type must have slot for casted-to type
invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], [], ['eqref'], /type mismatch/);
// invalid: block result type must be supertype of casted-to type
invalidTyping('(type $a (struct)) (type $b (struct (field i32)))', 'eqref', '(ref $a)', ['eqref'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);
// invalid: input is missing extra i32 from the branch target type
invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref'], /popping value/);
// invalid: input has extra [i32, f32] swapped from the branch target type
invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['f32', 'i32', '(ref $a)'], ['i32', 'f32', 'eqref'], /type mismatch/);
// invalid: input has extra [i32, f32] swapped from the branch fallthrough type
invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['f32', 'i32', 'eqref'], /type mismatch/);
// invalid: casting to non-nullable but fallthrough not nullable
invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['(ref eq)'], /type mismatch/);
// invalid: struct -> struct (same recursion group)
invalidTyping('(rec (type $a (struct)) (type $b (struct)))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);

// Simple runtime test of casting
{
  let { makeA, makeB, isA, isB } = wasmEvalText(`(module
    (type $a (struct))
    (type $b (sub $a (struct (field i32))))

    (func (export "makeA") (result eqref)
      struct.new_default $a
    )

    (func (export "makeB") (result eqref)
      struct.new_default $b
    )

    (func (export "isA") (param eqref) (result i32)
      (block (result (ref $a))
        local.get 0
        br_on_cast 0 anyref (ref $a)

        i32.const 0
        br 1
      )
      drop
      i32.const 1
    )

    (func (export "isB") (param eqref) (result i32)
      (block (result (ref $a))
        local.get 0
        br_on_cast 0 anyref (ref $b)

        i32.const 0
        br 1
      )
      drop
      i32.const 1
    )
  )`).exports;

  let a = makeA();
  let b = makeB();

  assertEq(isA(a), 1);
  assertEq(isA(b), 1);
  assertEq(isB(a), 0);
  assertEq(isB(b), 1);
}

// Runtime test of casting with extra values
{
  function assertEqResults(a, b) {
    if (!(a instanceof Array)) {
      a = [a];
    }
    if (!(b instanceof Array)) {
      b = [b];
    }
    if (a.length !== b.length) {
      assertEq(a.length, b.length);
    }
    for (let i = 0; i < a.length; i++) {
      let x = a[i];
      let y = b[i];
      // intentionally use loose equality to allow bigint to compare equally
      // to number, as can happen with how we use the JS-API here.
      assertEq(x == y, true);
    }
  }

  function testExtra(values) {
    let { makeT, makeF, select } = wasmEvalText(`(module
      (type $t (struct))
      (type $f (struct (field i32)))

      (func (export "makeT") (result eqref)
        struct.new_default $t
      )
      (func (export "makeF") (result eqref)
        struct.new_default $f
      )

      (func (export "select") (param eqref) (result ${values.map((type) => type).join(" ")})
        (block (result (ref $t))
          local.get 0
          br_on_cast 0 anyref (ref $t)

          ${values.map((type, i) => `${type}.const ${values.length + i}`).join("\n")}
          br 1
        )
        drop
        ${values.map((type, i) => `${type}.const ${i}`).join("\n")}
      )
    )`).exports;

    let t = makeT();
    let f = makeF();

    let trueValues = values.map((type, i) => i);
    let falseValues = values.map((type, i) => values.length + i);

    assertEqResults(select(t), trueValues);
    assertEqResults(select(f), falseValues);
  }

  // multiples of primitive valtypes
  for (let valtype of ['i32', 'i64', 'f32', 'f64']) {
    testExtra([valtype]);
    testExtra([valtype, valtype]);
    testExtra([valtype, valtype, valtype]);
    testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]);
  }

  // random sundry of valtypes
  testExtra(['i32', 'f32', 'i64', 'f64']);
  testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']);
}

// This test causes the `values` vector returned by
// `OpIter<Policy>::readBrOnCast` to contain three entries, the last of which
// is the argument, hence is reftyped.  This is used to verify an assertion to
// that effect in FunctionCompiler::brOnCastCommon.
{
    let tOnCast =
    `(module
       (type $a (struct))
       (func (export "onCast") (param f32 i32 eqref) (result f32 i32 (ref $a))
         local.get 0
         local.get 1
         local.get 2
         br_on_cast 0 anyref (ref $a)
         unreachable
       )
     )`;
    let { onCast } = wasmEvalText(tOnCast).exports;
}