Commit 6a88f0b1 authored by Francis McCabe's avatar Francis McCabe Committed by Commit Bot

[wasm] ReturnCall Implementation (interpreter)

Implement the ReturnCall functionality for the interpreter.

Note that some tests have had to be deferred to the implementation
of ReturnCall for TurboFan.

Bug: v8:7431
Change-Id: I091528e72f9113ddf1929bd1a5650b490bc8cc0c
Reviewed-on: https://chromium-review.googlesource.com/c/1467343Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Commit-Queue: Francis McCabe <fgm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59803}
parent 05de6ac1
...@@ -1354,17 +1354,24 @@ class ThreadImpl { ...@@ -1354,17 +1354,24 @@ class ThreadImpl {
CommitPc(pc); CommitPc(pc);
} }
// Check if there is room for a function's activation.
void EnsureStackSpaceForCall(InterpreterCode* code) {
EnsureStackSpace(code->side_table->max_stack_height_ +
code->locals.type_list.size());
DCHECK_GE(StackHeight(), code->function->sig->parameter_count());
}
// Push a frame with arguments already on the stack. // Push a frame with arguments already on the stack.
void PushFrame(InterpreterCode* code) { void PushFrame(InterpreterCode* code) {
DCHECK_NOT_NULL(code); DCHECK_NOT_NULL(code);
DCHECK_NOT_NULL(code->side_table); DCHECK_NOT_NULL(code->side_table);
EnsureStackSpace(code->side_table->max_stack_height_ + EnsureStackSpaceForCall(code);
code->locals.type_list.size());
++num_interpreted_calls_; ++num_interpreted_calls_;
size_t arity = code->function->sig->parameter_count(); size_t arity = code->function->sig->parameter_count();
// The parameters will overlap the arguments already on the stack. // The parameters will overlap the arguments already on the stack.
DCHECK_GE(StackHeight(), arity); DCHECK_GE(StackHeight(), arity);
frames_.push_back({code, 0, StackHeight() - arity}); frames_.push_back({code, 0, StackHeight() - arity});
frames_.back().pc = InitLocals(code); frames_.back().pc = InitLocals(code);
TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1, TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1,
...@@ -1484,6 +1491,41 @@ class ThreadImpl { ...@@ -1484,6 +1491,41 @@ class ThreadImpl {
return true; return true;
} }
// Returns true if the tail call was successful, false if the stack check
// failed.
bool DoReturnCall(Decoder* decoder, InterpreterCode* target, pc_t* pc,
pc_t* limit) V8_WARN_UNUSED_RESULT {
DCHECK_NOT_NULL(target);
DCHECK_NOT_NULL(target->side_table);
EnsureStackSpaceForCall(target);
++num_interpreted_calls_;
Frame* top = &frames_.back();
// Drop everything except current parameters.
WasmValue* sp_dest = stack_.get() + top->sp;
size_t arity = target->function->sig->parameter_count();
DoStackTransfer(sp_dest, arity);
*limit = target->end - target->start;
decoder->Reset(target->start, target->end);
// Rebuild current frame to look like a call to callee.
top->code = target;
top->pc = 0;
top->sp = StackHeight() - arity;
top->pc = InitLocals(target);
*pc = top->pc;
TRACE(" => ReturnCall #%zu (#%u @%zu)\n", frames_.size() - 1,
target->function->func_index, top->pc);
return true;
}
// Copies {arity} values on the top of the stack down the stack to {dest}, // Copies {arity} values on the top of the stack down the stack to {dest},
// dropping the values in-between. // dropping the values in-between.
void DoStackTransfer(WasmValue* dest, size_t arity) { void DoStackTransfer(WasmValue* dest, size_t arity) {
...@@ -2540,8 +2582,7 @@ class ThreadImpl { ...@@ -2540,8 +2582,7 @@ class ThreadImpl {
switch (result.type) { switch (result.type) {
case ExternalCallResult::INTERNAL: case ExternalCallResult::INTERNAL:
// The import is a function of this instance. Call it directly. // The import is a function of this instance. Call it directly.
target = result.interpreter_code; DCHECK(!result.interpreter_code->function->imported);
DCHECK(!target->function->imported);
break; break;
case ExternalCallResult::INVALID_FUNC: case ExternalCallResult::INVALID_FUNC:
case ExternalCallResult::SIGNATURE_MISMATCH: case ExternalCallResult::SIGNATURE_MISMATCH:
...@@ -2565,6 +2606,7 @@ class ThreadImpl { ...@@ -2565,6 +2606,7 @@ class ThreadImpl {
PAUSE_IF_BREAK_FLAG(AfterCall); PAUSE_IF_BREAK_FLAG(AfterCall);
continue; // Do not bump pc. continue; // Do not bump pc.
} break; } break;
case kExprCallIndirect: { case kExprCallIndirect: {
CallIndirectImmediate<Decoder::kNoValidate> imm(&decoder, CallIndirectImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc)); code->at(pc));
...@@ -2597,6 +2639,117 @@ class ThreadImpl { ...@@ -2597,6 +2639,117 @@ class ThreadImpl {
continue; // Do not bump pc. continue; // Do not bump pc.
} }
} break; } break;
case kExprReturnCall: {
CallFunctionImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc));
InterpreterCode* target = codemap()->GetCode(imm.index);
if (target->function->imported) {
CommitPc(pc);
ExternalCallResult result =
CallImportedFunction(target->function->func_index);
switch (result.type) {
case ExternalCallResult::INTERNAL:
// Cannot import internal functions
UNREACHABLE();
case ExternalCallResult::INVALID_FUNC:
case ExternalCallResult::SIGNATURE_MISMATCH:
// Direct calls are checked statically.
UNREACHABLE();
case ExternalCallResult::EXTERNAL_RETURNED:
len = 1 + imm.length;
break;
case ExternalCallResult::EXTERNAL_UNWOUND:
return;
case ExternalCallResult::EXTERNAL_CAUGHT:
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
continue;
}
if (result.type != ExternalCallResult::INTERNAL) {
size_t arity = code->function->sig->return_count();
if (!DoReturn(&decoder, &code, &pc, &limit, arity)) return;
PAUSE_IF_BREAK_FLAG(AfterReturn);
continue;
}
}
// Execute an internal return call with found function.
if (!DoReturnCall(&decoder, target, &pc, &limit)) return;
code = target;
PAUSE_IF_BREAK_FLAG(AfterCall);
continue; // do not bump pc.
} break;
case kExprReturnCallIndirect: {
CallIndirectImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc));
uint32_t entry_index = Pop().to<uint32_t>();
// Assume only one table for now.
DCHECK_LE(module()->tables.size(), 1u);
CommitPc(pc); // TODO(wasm): Be more disciplined about committing PC.
// TODO(wasm): Calling functions needs some refactoring to avoid
// multi-exit code like this.
ExternalCallResult result =
CallIndirectFunction(0, entry_index, imm.sig_index);
switch (result.type) {
case ExternalCallResult::INTERNAL: {
InterpreterCode* target = result.interpreter_code;
if (target->function->imported) {
CommitPc(pc);
ExternalCallResult result =
CallImportedFunction(target->function->func_index);
switch (result.type) {
case ExternalCallResult::INTERNAL:
// Cannot import internal functions
UNREACHABLE();
case ExternalCallResult::INVALID_FUNC:
case ExternalCallResult::SIGNATURE_MISMATCH:
// Direct calls are checked statically.
UNREACHABLE();
case ExternalCallResult::EXTERNAL_RETURNED:
len = 1 + imm.length;
break;
case ExternalCallResult::EXTERNAL_UNWOUND:
return;
case ExternalCallResult::EXTERNAL_CAUGHT:
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
continue;
}
size_t arity = code->function->sig->return_count();
if (!DoReturn(&decoder, &code, &pc, &limit, arity)) return;
PAUSE_IF_BREAK_FLAG(AfterReturn);
continue;
}
// The import is a function of this instance. Call it directly.
if (!DoReturnCall(&decoder, result.interpreter_code, &pc, &limit))
return;
code = result.interpreter_code;
PAUSE_IF_BREAK_FLAG(AfterCall);
continue; // do not bump pc.
}
case ExternalCallResult::INVALID_FUNC:
return DoTrap(kTrapFuncInvalid, pc);
case ExternalCallResult::SIGNATURE_MISMATCH:
return DoTrap(kTrapFuncSigMismatch, pc);
case ExternalCallResult::EXTERNAL_RETURNED: {
len = 1 + imm.length;
size_t arity = code->function->sig->return_count();
if (!DoReturn(&decoder, &code, &pc, &limit, arity)) return;
PAUSE_IF_BREAK_FLAG(AfterCall);
break;
}
case ExternalCallResult::EXTERNAL_UNWOUND:
return;
case ExternalCallResult::EXTERNAL_CAUGHT:
ReloadFromFrameOnException(&decoder, &code, &pc, &limit);
break;
}
} break;
case kExprGetGlobal: { case kExprGetGlobal: {
GlobalIndexImmediate<Decoder::kNoValidate> imm(&decoder, GlobalIndexImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc)); code->at(pc));
......
...@@ -143,6 +143,118 @@ TEST(Run_Wasm_nested_ifs_i) { ...@@ -143,6 +143,118 @@ TEST(Run_Wasm_nested_ifs_i) {
CHECK_EQ(14, r.Call(0, 0)); CHECK_EQ(14, r.Call(0, 0));
} }
// Repeated from test-run-wasm.cc to avoid poluting header files.
template <typename T>
static T factorial(T v) {
T expected = 1;
for (T i = v; i > 1; i--) {
expected *= i;
}
return expected;
}
// Basic test of return call in interpreter. Good old factorial.
TEST(Run_Wasm_returnCallFactorial) {
EXPERIMENTAL_FLAG_SCOPE(return_call);
// Run in bounded amount of stack - 8kb.
FlagScope<int32_t> stack_size(&v8::internal::FLAG_stack_size, 8);
WasmRunner<int32_t, int32_t> r(ExecutionTier::kInterpreter);
WasmFunctionCompiler& fact_fn = r.NewFunction<int32_t, int32_t>("fact");
WasmFunctionCompiler& fact_aux_fn =
r.NewFunction<int32_t, int32_t, int32_t>("fact_aux");
BUILD(r, WASM_CALL_FUNCTION(fact_fn.function_index(), WASM_GET_LOCAL(0)));
BUILD(fact_fn, WASM_RETURN_CALL_FUNCTION(fact_aux_fn.function_index(),
WASM_GET_LOCAL(0), WASM_I32V(1)));
BUILD(fact_aux_fn,
WASM_IF_ELSE_I(
WASM_I32_EQ(WASM_I32V(1), WASM_GET_LOCAL(0)), WASM_GET_LOCAL(1),
WASM_RETURN_CALL_FUNCTION(
fact_aux_fn.function_index(),
WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_I32V(1)),
WASM_I32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)))));
// Runs out of stack space without using return call.
int test_values[] = {1, 2, 5, 10, 20, 20000};
for (int v : test_values) {
int32_t found = r.Call(v);
CHECK_EQ(factorial(v), found);
}
}
TEST(Run_Wasm_returnCallFactorial64) {
EXPERIMENTAL_FLAG_SCOPE(return_call);
int32_t test_values[] = {1, 2, 5, 10, 20};
WasmRunner<int64_t, int32_t> r(ExecutionTier::kInterpreter);
WasmFunctionCompiler& fact_fn = r.NewFunction<int64_t, int32_t>("fact");
WasmFunctionCompiler& fact_aux_fn =
r.NewFunction<int64_t, int32_t, int64_t>("fact_aux");
BUILD(r, WASM_CALL_FUNCTION(fact_fn.function_index(), WASM_GET_LOCAL(0)));
BUILD(fact_fn, WASM_RETURN_CALL_FUNCTION(fact_aux_fn.function_index(),
WASM_GET_LOCAL(0), WASM_I64V(1)));
BUILD(fact_aux_fn,
WASM_IF_ELSE_L(
WASM_I32_EQ(WASM_I32V(1), WASM_GET_LOCAL(0)), WASM_GET_LOCAL(1),
WASM_RETURN_CALL_FUNCTION(
fact_aux_fn.function_index(),
WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_I32V(1)),
WASM_I64_MUL(WASM_I64_SCONVERT_I32(WASM_GET_LOCAL(0)),
WASM_GET_LOCAL(1)))));
for (int32_t v : test_values) {
CHECK_EQ(factorial<int64_t>(v), r.Call(v));
}
}
TEST(Run_Wasm_tailCallIndirectFactorial) {
EXPERIMENTAL_FLAG_SCOPE(return_call);
TestSignatures sigs;
WasmRunner<int32_t, int32_t> r(ExecutionTier::kInterpreter);
WasmFunctionCompiler& fact_fn = r.NewFunction(sigs.i_i(), "fact");
WasmFunctionCompiler& fact_aux_fn = r.NewFunction(sigs.i_ii(), "fact_aux");
fact_aux_fn.SetSigIndex(0);
r.builder().AddSignature(sigs.i_ii());
// Function table.
uint16_t indirect_function_table[] = {
static_cast<uint16_t>(fact_aux_fn.function_index())};
r.builder().AddIndirectFunctionTable(indirect_function_table,
arraysize(indirect_function_table));
r.builder().PopulateIndirectFunctionTable();
BUILD(r, WASM_CALL_FUNCTION(fact_fn.function_index(), WASM_GET_LOCAL(0)));
BUILD(fact_fn, WASM_RETURN_CALL_INDIRECT(0, WASM_I32V(0), WASM_GET_LOCAL(0),
WASM_I32V(1)));
BUILD(fact_aux_fn,
WASM_IF_ELSE_I(
WASM_I32_EQ(WASM_I32V(1), WASM_GET_LOCAL(0)), WASM_GET_LOCAL(1),
WASM_RETURN_CALL_INDIRECT(
0, WASM_I32V(0), WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_I32V(1)),
WASM_I32_MUL(WASM_GET_LOCAL(0), WASM_GET_LOCAL(1)))));
int32_t test_values[] = {1, 2, 5, 10, 20};
for (int32_t v : test_values) {
CHECK_EQ(factorial(v), r.Call(v));
}
}
// Make tests more robust by not hard-coding offsets of various operations. // Make tests more robust by not hard-coding offsets of various operations.
// The {Find} method finds the offsets for the given bytecodes, returning // The {Find} method finds the offsets for the given bytecodes, returning
// the offsets in an array. // the offsets in an array.
......
...@@ -204,6 +204,8 @@ let kExprBrTable = 0x0e; ...@@ -204,6 +204,8 @@ let kExprBrTable = 0x0e;
let kExprReturn = 0x0f; let kExprReturn = 0x0f;
let kExprCallFunction = 0x10; let kExprCallFunction = 0x10;
let kExprCallIndirect = 0x11; let kExprCallIndirect = 0x11;
let kExprReturnCall = 0x12;
let kExprReturnCallIndirect = 0x13;
let kExprDrop = 0x1a; let kExprDrop = 0x1a;
let kExprSelect = 0x1b; let kExprSelect = 0x1b;
let kExprGetLocal = 0x20; let kExprGetLocal = 0x20;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment