Commit 4c9c6c58 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.

This a reland of https://chromium-review.googlesource.com/c/v8/v8/+/1467343

Bug: v8:7431
Change-Id: Iac9392a6a81995e30009dac74035e4d728ac3dbb
Cq-Include-Trybots: luci.v8.try:v8_linux64_ubsan_rel_ng
Reviewed-on: https://chromium-review.googlesource.com/c/1484905
Commit-Queue: Francis McCabe <fgm@chromium.org>
Reviewed-by: 's avatarClemens Hammacher <clemensh@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/master@{#59925}
parent a25279df
......@@ -1354,17 +1354,24 @@ class ThreadImpl {
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.
void PushFrame(InterpreterCode* code) {
DCHECK_NOT_NULL(code);
DCHECK_NOT_NULL(code->side_table);
EnsureStackSpace(code->side_table->max_stack_height_ +
code->locals.type_list.size());
EnsureStackSpaceForCall(code);
++num_interpreted_calls_;
size_t arity = code->function->sig->parameter_count();
// The parameters will overlap the arguments already on the stack.
DCHECK_GE(StackHeight(), arity);
frames_.push_back({code, 0, StackHeight() - arity});
frames_.back().pc = InitLocals(code);
TRACE(" => PushFrame #%zu (#%u @%zu)\n", frames_.size() - 1,
......@@ -1484,6 +1491,41 @@ class ThreadImpl {
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},
// dropping the values in-between.
void DoStackTransfer(WasmValue* dest, size_t arity) {
......@@ -2540,8 +2582,7 @@ class ThreadImpl {
switch (result.type) {
case ExternalCallResult::INTERNAL:
// The import is a function of this instance. Call it directly.
target = result.interpreter_code;
DCHECK(!target->function->imported);
DCHECK(!result.interpreter_code->function->imported);
break;
case ExternalCallResult::INVALID_FUNC:
case ExternalCallResult::SIGNATURE_MISMATCH:
......@@ -2565,6 +2606,7 @@ class ThreadImpl {
PAUSE_IF_BREAK_FLAG(AfterCall);
continue; // Do not bump pc.
} break;
case kExprCallIndirect: {
CallIndirectImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc));
......@@ -2597,6 +2639,91 @@ class ThreadImpl {
continue; // Do not bump pc.
}
} break;
case kExprReturnCall: {
CallFunctionImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc));
InterpreterCode* target = codemap()->GetCode(imm.index);
if (!target->function->imported) {
// Enter internal found function.
if (!DoReturnCall(&decoder, target, &pc, &limit)) return;
code = target;
PAUSE_IF_BREAK_FLAG(AfterCall);
continue; // Do not bump pc.
}
// Function is imported.
CommitPc(pc);
ExternalCallResult result =
CallImportedFunction(target->function->func_index);
switch (result.type) {
case ExternalCallResult::INTERNAL:
// Cannot import internal functions.
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;
} 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;
DCHECK(!target->function->imported);
// The function belongs to this instance. Enter it directly.
if (!DoReturnCall(&decoder, target, &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: {
GlobalIndexImmediate<Decoder::kNoValidate> imm(&decoder,
code->at(pc));
......
......@@ -143,6 +143,109 @@ TEST(Run_Wasm_nested_ifs_i) {
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<uint32_t, int32_t> r(ExecutionTier::kInterpreter);
WasmFunctionCompiler& fact_aux_fn =
r.NewFunction<int32_t, int32_t, int32_t>("fact_aux");
BUILD(r, 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.
uint32_t test_values[] = {1, 2, 5, 10, 20, 20000};
for (uint32_t v : test_values) {
uint32_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_aux_fn =
r.NewFunction<int64_t, int32_t, int64_t>("fact_aux");
BUILD(r, 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_returnCallIndirectFactorial) {
EXPERIMENTAL_FLAG_SCOPE(return_call);
TestSignatures sigs;
WasmRunner<uint32_t, uint32_t> r(ExecutionTier::kInterpreter);
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_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)))));
uint32_t test_values[] = {1, 2, 5, 10, 20};
for (uint32_t v : test_values) {
CHECK_EQ(factorial(v), r.Call(v));
}
}
// Make tests more robust by not hard-coding offsets of various operations.
// The {Find} method finds the offsets for the given bytecodes, returning
// the offsets in an array.
......
......@@ -204,6 +204,8 @@ let kExprBrTable = 0x0e;
let kExprReturn = 0x0f;
let kExprCallFunction = 0x10;
let kExprCallIndirect = 0x11;
let kExprReturnCall = 0x12;
let kExprReturnCallIndirect = 0x13;
let kExprDrop = 0x1a;
let kExprSelect = 0x1b;
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