speculative-inlining.js 8.32 KB
Newer Older
1 2 3 4 5
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --wasm-speculative-inlining --experimental-wasm-return-call
6
// Flags: --experimental-wasm-typed-funcref --experimental-wasm-type-reflection
7
// Flags: --no-wasm-tier-up --wasm-dynamic-tiering --wasm-tiering-budget=100
8
// Flags: --allow-natives-syntax
9 10 11 12 13

// These tests check if functions are speculatively inlined as expected. We do
// not check automatically which functions are inlined. To get more insight, run
// with --trace-wasm-speculative-inlining, --trace-turbo, --trace-wasm and (for
// the last test only) --trace.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34

d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js");

(function CallRefSpecSucceededTest() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();

  // f(x) = x - 1
  let callee = builder.addFunction("callee", kSig_i_i)
    .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);

  let global = builder.addGlobal(wasmRefType(0), false,
                                 WasmInitExpr.RefFunc(callee.index));

  // g(x) = f(5) + x
  builder.addFunction("main", kSig_i_i)
    .addBody([kExprI32Const, 5, kExprGlobalGet, global.index, kExprCallRef,
              kExprLocalGet, 0, kExprI32Add])
    .exportAs("main");

  let instance = builder.instantiate();
35 36 37 38 39 40
  // Run 'main' until it is tiered-up.
  while (%IsLiftoffFunction(instance.exports.main)) {
    assertEquals(14, instance.exports.main(10));
  }
  // The tiered-up function should have {callee} speculatively inlined.
  assertEquals(14, instance.exports.main(10));
41 42 43 44 45 46
})();

(function CallRefSpecFailedTest() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();

47 48
  let sig_index = builder.addType(kSig_i_i);

49
  // h(x) = x - 1
50
  let callee0 = builder.addFunction("callee0", sig_index)
51 52 53
    .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);

  // f(x) = x - 2
54
  let callee1 = builder.addFunction("callee1", sig_index)
55 56
    .addBody([kExprLocalGet, 0, kExprI32Const, 2, kExprI32Sub]);

57
  let global0 = builder.addGlobal(wasmRefType(sig_index), false,
58
                                  WasmInitExpr.RefFunc(callee0.index));
59
  let global1 = builder.addGlobal(wasmRefType(sig_index), false,
60
                                  WasmInitExpr.RefFunc(callee1.index));
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75

  // g(x, y) = if (y) { h(5) + x } else { f(7) + x }
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprLocalGet, 1,
      kExprIf, kWasmI32,
        kExprI32Const, 5, kExprGlobalGet, global0.index, kExprCallRef,
        kExprLocalGet, 0, kExprI32Add,
      kExprElse,
        kExprI32Const, 7, kExprGlobalGet, global1.index, kExprCallRef,
        kExprLocalGet, 0, kExprI32Add,
      kExprEnd])
    .exportAs("main");

  let instance = builder.instantiate();
76 77 78

  // Run 'main' until it is tiered-up.
  while (%IsLiftoffFunction(instance.exports.main)) {
79 80 81
    assertEquals(14, instance.exports.main(10, 1));
  }
  // Tier-up is done, and {callee0} should be inlined in the trace.
82 83
  assertEquals(14, instance.exports.main(10, 1))

84 85
  // Now, run main with {callee1} instead. The correct reference should still be
  // called after inlining.
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
  assertEquals(15, instance.exports.main(10, 0));
})();

(function CallReturnRefSpecSucceededTest() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();

  // f(x) = x - 1
  let callee = builder.addFunction("callee", kSig_i_i)
    .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);

  let global = builder.addGlobal(wasmRefType(0), false,
                                 WasmInitExpr.RefFunc(callee.index));

  // g(x) = f(5 + x)
  builder.addFunction("main", kSig_i_i)
    .addBody([kExprI32Const, 5, kExprLocalGet, 0, kExprI32Add,
              kExprGlobalGet, global.index, kExprReturnCallRef])
    .exportAs("main");

  let instance = builder.instantiate();
107 108 109 110 111 112
  // Run 'main' until it is tiered-up.
  while (%IsLiftoffFunction(instance.exports.main)) {
    assertEquals(14, instance.exports.main(10));
  }
  // After tier-up, the tail call should be speculatively inlined.
  assertEquals(14, instance.exports.main(10));
113 114 115 116 117 118
})();

(function CallReturnRefSpecFailedTest() {
  print(arguments.callee.name);
  let builder = new WasmModuleBuilder();

119 120
  let sig_index = builder.addType(kSig_i_i);

121
  // h(x) = x - 1
122
  let callee0 = builder.addFunction("callee0", sig_index)
123 124 125
    .addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Sub]);

  // f(x) = x - 2
126
  let callee1 = builder.addFunction("callee1", sig_index)
127 128
    .addBody([kExprLocalGet, 0, kExprI32Const, 2, kExprI32Sub]);

129
  let global0 = builder.addGlobal(wasmRefType(sig_index), false,
130
                                 WasmInitExpr.RefFunc(callee0.index));
131
  let global1 = builder.addGlobal(wasmRefType(sig_index), false,
132 133 134 135 136 137 138 139 140 141 142 143 144 145
                                 WasmInitExpr.RefFunc(callee1.index));

  // g(x, y) = if (y) { h(x) } else { f(x) }
  builder.addFunction("main", kSig_i_ii)
    .addBody([
      kExprLocalGet, 1,
      kExprIf, kWasmI32,
        kExprLocalGet, 0, kExprGlobalGet, global0.index, kExprReturnCallRef,
      kExprElse,
        kExprLocalGet, 0, kExprGlobalGet, global1.index, kExprReturnCallRef,
      kExprEnd])
    .exportAs("main");

  let instance = builder.instantiate();
146 147 148 149 150
  // Run 'main' until it is tiered-up.
  while (%IsLiftoffFunction(instance.exports.main)) {
    assertEquals(9, instance.exports.main(10, 1));
  }
  // After tier-up, {callee0} should be inlined in the trace.
151 152
  assertEquals(9, instance.exports.main(10, 1))

153 154
  // Now, run main with {callee1} instead. The correct reference should still be
  // called.
155 156
  assertEquals(8, instance.exports.main(10, 0));
})();
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

(function CallRefImportedFunction() {
  print(arguments.callee.name);

  let instance1 = function() {
    let builder = new WasmModuleBuilder();

    let f1 = builder.addImport("m", "i_f1", kSig_i_i);
    let f2 = builder.addImport("m", "i_f2", kSig_i_i);

    builder.addExport("f1", f1);
    builder.addExport("f2", f2);

    return builder.instantiate({m : { i_f1 : x => x + 1, i_f2 : x => x + 2}});
  }();

  let instance2 = function() {
    let builder = new WasmModuleBuilder();

    let sig1 = builder.addType(kSig_i_i);
    let sig2 = builder.addType(kSig_i_ii);

    builder.addFunction("callee", sig2)
      .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprI32Add])
      .exportFunc();

    builder.addFunction("main", makeSig([kWasmI32,
                                         wasmRefType(sig1)], [kWasmI32]))
      .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprCallRef])
      .exportFunc();

    return builder.instantiate({});
  }();

  // Run 'main' until it is tiered-up.
  while (%IsLiftoffFunction(instance2.exports.main)) {
193
    assertEquals(1, instance2.exports.main(0, instance1.exports.f1));
194 195
  }
  // The function f1 defined in another module should not be inlined.
196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
  assertEquals(1, instance2.exports.main(0, instance1.exports.f1));
})();

// Check that we handle WasmJSFunctions properly and do not inline them, both
// in the monomorphic and polymorphic case.
(function CallRefWasmJsFunction() {
  print(arguments.callee.name);

  let f1 = new WebAssembly.Function({parameters: ["i32"], results: ["i32"]},
                                    x => x + 1);
  let f2 = new WebAssembly.Function({parameters: ["i32"], results: ["i32"]},
                                    x => x * 2);

  let instance2 = function() {
    let builder = new WasmModuleBuilder();

    let sig = builder.addType(kSig_i_i);

    builder.addFunction("main", makeSig(
        [kWasmI32, wasmRefType(sig), wasmRefType(sig)], [kWasmI32]))
      .addBody([kExprLocalGet, 0, kExprLocalGet, 1, kExprCallRef,
                kExprLocalGet, 0, kExprLocalGet, 2, kExprCallRef,
                kExprI32Add])
      .exportFunc();

    return builder.instantiate({});
  }();

  var i = 0;
  // Run 'main' until it is tiered-up. The first argument should try to be
  // spec-inlined monomorphically. We pass f2 to the second argument 80% of the
  // time, so it should try to be spec-inlined polymorphically.
  while (%IsLiftoffFunction(instance2.exports.main)) {
    if (i % 5 == 0) {
      assertEquals(12, instance2.exports.main(5, f1, f1));
    } else {
      assertEquals(16, instance2.exports.main(5, f1, f2));
    }
    i++;
  }
  // WebAssembly.Function objects should not be inlined.
  assertEquals(16, instance2.exports.main(5, f1, f2));
  assertEquals(12, instance2.exports.main(5, f1, f1));
239
})();