summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/function-references/nnl-test.js
blob: 9436b970d521d1b61c8256442180d2e6faddb5f2 (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
// |jit-test| skip-if: !wasmFunctionReferencesEnabled()

// Generates combinations of different block types and operations for
// non-defaultable locals (local.set / .tee / .get).
// See the function references specification on the updated algorithm
// for validating non-null references in locals.

const KINDS = [
  "block",
  "loop",
  "try",
  "catch",
  "delegate",
  "if",
  "else",
]
const INITIALIZED = [
  "nowhere",
  "outer",
  "inner",
  "outer-tee",
  "inner-tee",
];
const USED = [
  "outer",
  "inner",
  "after-inner",
  "after-outer",
];

function generateBlock(kind, contents) {
  switch (kind) {
    case "block": {
      return `block\n${contents}end\n`
    }
    case "loop": {
      return `loop\n${contents}end\n`
    }
    case "try": {
      return `try\n${contents}end\n`
    }
    case "catch": {
      return `try\ncatch_all\n${contents}end\n`
    }
    case "delegate": {
      return `try\n${contents}\ndelegate 0\n`
    }
    case "if": {
      return `i32.const 0\nif\n${contents}end\n`
    }
    case "else": {
      return `i32.const 0\nif\nelse\n${contents}end\n`
    }
  }
}

// Generate a variation of the module below:
//
// (func
//  (block
//    $outer
//    (block
//      $inner
//    )
//    $after-inner
//  )
//  $after-outer
// )
//
// Where a local is used and initialized at different points depending on the
// parameters. The block kinds of the inner and outer block may also be
// customized.
function generateModule(outerBlockKind, innerBlockKind, initializedWhere, usedWhere) {
  const INITIALIZE_STMT = '(local.set 0 ref.func 0)\n';
  const INITIALIZE_STMT2 = '(drop (local.tee 0 ref.func 0))\n';
  const USE_STMT = '(drop local.get 0)\n';

  // inner block
  let innerBlockContents = '';
  if (initializedWhere === 'inner') {
    innerBlockContents += INITIALIZE_STMT;
  } else if (initializedWhere === 'inner-tee') {
    innerBlockContents += INITIALIZE_STMT2;
  }
  if (usedWhere === 'inner') {
    innerBlockContents += USE_STMT;
  }
  let innerBlock = generateBlock(innerBlockKind, innerBlockContents);

  // outer block
  let outerBlockContents = '';
  if (initializedWhere === 'outer') {
    outerBlockContents += INITIALIZE_STMT;
  } else if (initializedWhere === 'outer-tee') {
    outerBlockContents += INITIALIZE_STMT2;
  }
  if (usedWhere === 'outer') {
    outerBlockContents += USE_STMT;
  }
  outerBlockContents += innerBlock;
  if (usedWhere === 'after-inner') {
    outerBlockContents += USE_STMT;
  }
  let outerBlock = generateBlock(outerBlockKind, outerBlockContents);

  // after outer block
  let afterOuterBlock = '';
  if (usedWhere === 'after-outer') {
    afterOuterBlock += USE_STMT;
  }

  return `(module
  (type $t (func))
  (func (export "test")
    (local (ref $t))
${outerBlock}${afterOuterBlock}  )
)`;
}

const LOGGING = false;

for (let outer of KINDS) {
  for (let inner of KINDS) {
    for (let initialized of INITIALIZED) {
      for (let used of USED) {
        let text = generateModule(outer, inner, initialized, used);

        let expectPass;
        switch (initialized) {
          case "outer":
          case "outer-tee": {
            // Defining the local in the outer block makes it valid
            // in the outer block, the inner block, and after the
            // inner block
            expectPass = used !== "after-outer";
            break;
          }
          case "inner":
          case "inner-tee": {
            // Defining the local in the inner block makes it valid
            // in the inner block
            //
            // NOTE: an extension to typing could make this valid
            // after the inner block in some cases
            expectPass = used === "inner";
            break;
          }
          case "nowhere": {
            // Not defining the local makes it always invalid to
            // use
            expectPass = false;
            break;
          }
        }

        if (LOGGING) {
          console.log();
          console.log(`TEST: outer=${outer}, inner=${inner}, initialized=${initialized}, used=${used}`);
          console.log(expectPass ? "EXPECT PASS" : "EXPECT FAIL");
          console.log(text);
        }

        let binary = wasmTextToBinary(text);
        assertEq(WebAssembly.validate(binary), expectPass);
        if (!expectPass) {
          // Check if the error message is right.
          try {
            new WebAssembly.Module(binary);
          } catch (ex) {
            assertEq(true, /local\.get read from unset local/.test(ex.message));
          }
        }
      }
    }
  }
}