summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/gc/br-on-cast.js
blob: 14108a24aae8a0633cd30d123c01c157301b1f7d (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
// |jit-test| skip-if: !wasmGcEnabled()

function typingModule(types, castToTypeIndex, brParams, blockResults) {
  return `(module
    ${types}
    (func
      (param ${brParams.join(' ')})
      (result ${blockResults.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 0 ${castToTypeIndex}
      unreachable
    )
  )`;
}

function validTyping(types, castToTypeIndex, brParams, blockResults) {
  wasmValidateText(typingModule(types, castToTypeIndex, brParams, blockResults));
}

function invalidTyping(types, castToTypeIndex, brParams, blockResults, error) {
  wasmFailValidateText(typingModule(types, castToTypeIndex, brParams, blockResults), error);
}

// valid: input eqref, output non-nullable struct
validTyping('(type $a (struct))', '$a', ['eqref'], ['(ref $a)']);
// valid: input eqref, output nullable struct
validTyping('(type $a (struct))', '$a', ['eqref'], ['(ref null $a)']);
// valid: input non-nullable struct, output non-nullable struct
validTyping('(type $a (struct)) (type $b (struct))', '$b', ['(ref $a)'], ['(ref $b)']);
// valid: input nullable struct, output non-nullable struct
validTyping('(type $a (struct)) (type $b (struct))', '$b', ['(ref null $a)'], ['(ref $b)']);
// valid: input nullable struct, output nullable struct
validTyping('(type $a (struct)) (type $b (struct))', '$b', ['(ref null $a)'], ['(ref null $b)']);
// valid: input with an extra i32
validTyping('(type $a (struct))', '$a', ['i32', 'eqref'], ['i32', '(ref $a)']);
// valid: input with an extra i32 and f32
validTyping('(type $a (struct))', '$a', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)']);

// invalid: block result type must have slot for casted-to type
invalidTyping('(type $a (struct))', '$a', ['eqref'], [], /type mismatch/);
// invalid: block result type must be subtype of casted-to type
invalidTyping('(type $a (struct)) (type $b (struct (field i32)))', '$a', ['eqref'], ['(ref $b)'], /type mismatch/);
// invalid: input is missing extra i32 from the branch target type
invalidTyping('(type $a (struct))', '$a', ['f32', 'eqref'], ['i32', 'f32', '(ref $a)'], /popping value/);
// invalid: input is has extra [i32, f32] swapped from the branch target type
invalidTyping('(type $a (struct))', '$a', ['i32', 'f32', 'eqref'], ['f32', 'i32', '(ref $a)'], /type mismatch/);

// Simple runtime test of casting
{
  let { makeA, makeB, isA, isB } = wasmEvalText(`(module
    (type $a (struct))
    (sub $a (type $b (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 $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 $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 $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 $a
         unreachable
       )
     )`;
    let { onCast } = wasmEvalText(tOnCast).exports;
}