// Copyright 2017 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: --allow-natives-syntax load('test/mjsunit/wasm/wasm-module-builder.js'); // ============================================================================= // Tests in this file test the interaction between the wasm interpreter and // compiled code. // ============================================================================= // The stack trace contains file path, replace it by "file". let stripPath = s => s.replace(/[^ (]*interpreter-mixed\.js/g, 'file'); function checkStack(stack, expected_lines) { print('stack: ' + stack); let lines = stack.split('\n'); assertEquals(expected_lines.length, lines.length); for (let i = 0; i < lines.length; ++i) { let test = typeof expected_lines[i] == 'string' ? assertEquals : assertMatches; test(expected_lines[i], lines[i], 'line ' + i); } } (function testMemoryGrowBetweenInterpretedAndCompiled() { // grow_memory can be called from interpreted or compiled code, and changes // should be reflected in either execution. var builder = new WasmModuleBuilder(); var grow_body = [kExprLocalGet, 0, kExprMemoryGrow, kMemoryZero]; var load_body = [kExprLocalGet, 0, kExprI32LoadMem, 0, 0]; var store_body = [kExprLocalGet, 0, kExprLocalGet, 1, kExprI32StoreMem, 0, 0]; builder.addFunction('grow_memory', kSig_i_i).addBody(grow_body).exportFunc(); builder.addFunction('load', kSig_i_i).addBody(load_body).exportFunc(); builder.addFunction('store', kSig_v_ii).addBody(store_body).exportFunc(); var grow_interp_function = builder.addFunction('grow_memory_interpreted', kSig_i_i) .addBody(grow_body) .exportFunc(); var load_interp_function = builder.addFunction('load_interpreted', kSig_i_i) .addBody(load_body) .exportFunc(); var kNumPages = 2; var kMaxPages = 10; builder.addMemory(kNumPages, kMaxPages, false); var instance = builder.instantiate(); var exp = instance.exports; %RedirectToWasmInterpreter(instance, grow_interp_function.index); %RedirectToWasmInterpreter(instance, load_interp_function.index); // Initially, we can load from offset 12, but not OOB. var oob_index = kNumPages * kPageSize; var initial_interpreted = %WasmNumInterpretedCalls(instance); assertEquals(0, exp.load(12)); assertEquals(0, exp.load_interpreted(12)); assertTraps(kTrapMemOutOfBounds, () => exp.load(oob_index)); assertTraps( kTrapMemOutOfBounds, () => exp.load_interpreted(oob_index)); // Grow by 2 pages from compiled code, and ensure that this is reflected in // the interpreter. assertEquals(kNumPages, exp.grow_memory(2)); kNumPages += 2; assertEquals(kNumPages, exp.grow_memory_interpreted(0)); assertEquals(kNumPages, exp.grow_memory(0)); // Now we can load from the previous OOB index. assertEquals(0, exp.load(oob_index)); assertEquals(0, exp.load_interpreted(oob_index)); // Set new OOB index and ensure that it traps. oob_index = kNumPages * kPageSize; assertTraps(kTrapMemOutOfBounds, () => exp.load(oob_index)); assertTraps( kTrapMemOutOfBounds, () => exp.load_interpreted(oob_index)); // Grow by another page in the interpreter, and ensure that this is reflected // in compiled code. assertEquals(kNumPages, exp.grow_memory_interpreted(1)); kNumPages += 1; assertEquals(kNumPages, exp.grow_memory_interpreted(0)); assertEquals(kNumPages, exp.grow_memory(0)); // Now we can store to the previous OOB index and read it back in both // environments. exp.store(oob_index, 47); assertEquals(47, exp.load(oob_index)); assertEquals(47, exp.load_interpreted(oob_index)); // We cannot grow beyong kMaxPages. assertEquals(-1, exp.grow_memory(kMaxPages - kNumPages + 1)); assertEquals(-1, exp.grow_memory_interpreted(kMaxPages - kNumPages + 1)); // Overall, we executed 9 functions in the interpreter. assertEquals(initial_interpreted + 9, %WasmNumInterpretedCalls(instance)); })(); function createTwoInstancesCallingEachOther(inner_throws = false) { let builder1 = new WasmModuleBuilder(); let id_imp = builder1.addImport('q', 'id', kSig_i_i); let plus_one = builder1.addFunction('plus_one', kSig_i_i) .addBody([ kExprLocalGet, 0, // - kExprI32Const, 1, // - kExprI32Add, // - kExprCallFunction, id_imp ]) .exportFunc(); function imp(i) { if (inner_throws) throw new Error('i=' + i); return i; } let instance1 = builder1.instantiate({q: {id: imp}}); let builder2 = new WasmModuleBuilder(); let plus_one_imp = builder2.addImport('q', 'plus_one', kSig_i_i); let plus_two = builder2.addFunction('plus_two', kSig_i_i) .addBody([ // Call import, add one more. kExprLocalGet, 0, // - kExprCallFunction, plus_one_imp, // - kExprI32Const, 1, // - kExprI32Add ]) .exportFunc(); let instance2 = builder2.instantiate({q: {plus_one: instance1.exports.plus_one}}); return [instance1, instance2]; } function redirectToInterpreter( instance1, instance2, redirect_plus_one, redirect_plus_two) { // Redirect functions to the interpreter. if (redirect_plus_one) { %RedirectToWasmInterpreter(instance1, parseInt(instance1.exports.plus_one.name)); } if (redirect_plus_two) { %RedirectToWasmInterpreter(instance2, parseInt(instance2.exports.plus_two.name)); } } (function testImportFromOtherInstance() { print("testImportFromOtherInstance"); // Three runs: Break in instance 1, break in instance 2, or both. for (let run = 0; run < 3; ++run) { print(" - run " + run); let [instance1, instance2] = createTwoInstancesCallingEachOther(); let interpreted_before_1 = %WasmNumInterpretedCalls(instance1); let interpreted_before_2 = %WasmNumInterpretedCalls(instance2); // Call plus_two, which calls plus_one. assertEquals(9, instance2.exports.plus_two(7)); // Nothing interpreted: assertEquals(interpreted_before_1, %WasmNumInterpretedCalls(instance1)); assertEquals(interpreted_before_2, %WasmNumInterpretedCalls(instance2)); // Now redirect functions to the interpreter. redirectToInterpreter(instance1, instance2, run != 1, run != 0); // Call plus_two, which calls plus_one. assertEquals(9, instance2.exports.plus_two(7)); // TODO(6668): Fix patching of instances which imported others' code. //assertEquals(interpreted_before_1 + (run == 1 ? 0 : 1), // %WasmNumInterpretedCalls(instance1)); assertEquals(interpreted_before_2 + (run == 0 ? 0 : 1), %WasmNumInterpretedCalls(instance2)); } })(); (function testStackTraceThroughCWasmEntry() { print("testStackTraceThroughCWasmEntry"); for (let run = 0; run < 3; ++run) { print(" - run " + run); let [instance1, instance2] = createTwoInstancesCallingEachOther(true); redirectToInterpreter(instance1, instance2, run != 1, run != 0); try { // Call plus_two, which calls plus_one. instance2.exports.plus_two(7); assertUnreachable('should trap because of unreachable instruction'); } catch (e) { checkStack(stripPath(e.stack), [ 'Error: i=8', // - /^ at imp \(file:\d+:29\)$/, // - ' at plus_one (wasm-function[1]:0x3b)', // - ' at plus_two (wasm-function[1]:0x3e)', // - /^ at testStackTraceThroughCWasmEntry \(file:\d+:25\)$/, // - /^ at file:\d+:3$/ ]); } } })(); (function testInterpreterPreservedOnTierUp() { print(arguments.callee.name); var builder = new WasmModuleBuilder(); var fun_body = [kExprI32Const, 23]; var fun = builder.addFunction('fun', kSig_i_v).addBody(fun_body).exportFunc(); var instance = builder.instantiate(); var exp = instance.exports; // Initially the interpreter is not being called. var initial_interpreted = %WasmNumInterpretedCalls(instance); assertEquals(23, exp.fun()); assertEquals(initial_interpreted + 0, %WasmNumInterpretedCalls(instance)); // Redirection will cause the interpreter to be called. %RedirectToWasmInterpreter(instance, fun.index); assertEquals(23, exp.fun()); assertEquals(initial_interpreted + 1, %WasmNumInterpretedCalls(instance)); // Requesting a tier-up still ensure the interpreter is being called. %WasmTierUpFunction(instance, fun.index); assertEquals(23, exp.fun()); assertEquals(initial_interpreted + 2, %WasmNumInterpretedCalls(instance)); })();