[wasm-c-api] Faster C-to-Wasm entry

powered by a new function Execution::CallWasm and a corresponding,
Turbofan-generated CWasmEntry stub. This entirely sidesteps the
traditional Execution::Invoke -> JSEntryStub path.

Change-Id: If2b97825cca4ce927eecbddc248c64782d903287
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1660618
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Michael Starzinger <mstarzinger@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62424}
This commit is contained in:
Jakob Kummerow 2019-06-27 14:46:03 +02:00 committed by Commit Bot
parent 907f3a64b7
commit e304d80e09
32 changed files with 568 additions and 299 deletions

View File

@ -2891,6 +2891,7 @@ v8_source_set("v8_base_without_compiler") {
"src/wasm/streaming-decoder.cc",
"src/wasm/streaming-decoder.h",
"src/wasm/value-type.h",
"src/wasm/wasm-arguments.h",
"src/wasm/wasm-code-manager.cc",
"src/wasm/wasm-code-manager.h",
"src/wasm/wasm-constants.h",

View File

@ -899,6 +899,11 @@ extern class WasmExportedFunctionData extends Struct {
instance: WasmInstanceObject;
jump_table_offset: Smi;
function_index: Smi;
// The remaining fields are for fast calling from C++. The contract is
// that they are lazily populated, and either all will be present or none.
c_wrapper_code: Object;
wasm_call_target: Smi; // Pseudo-smi: one-bit shift on all platforms.
packed_args_size: Smi;
}
extern class WasmJSFunctionData extends Struct {

View File

@ -183,6 +183,8 @@ StackFrame::Type OptimizedCompilationInfo::GetOutputStackFrameType() const {
return StackFrame::WASM_TO_JS;
case Code::WASM_INTERPRETER_ENTRY:
return StackFrame::WASM_INTERPRETER_ENTRY;
case Code::C_WASM_ENTRY:
return StackFrame::C_WASM_ENTRY;
default:
UNIMPLEMENTED();
return StackFrame::NONE;

View File

@ -2994,8 +2994,14 @@ void CodeGenerator::AssembleConstructFrame() {
auto call_descriptor = linkage()->GetIncomingDescriptor();
if (frame_access_state()->has_frame()) {
if (call_descriptor->IsCFunctionCall()) {
__ Push(lr, fp);
__ mov(fp, sp);
if (info()->GetOutputStackFrameType() == StackFrame::C_WASM_ENTRY) {
__ StubPrologue(StackFrame::C_WASM_ENTRY);
// Reserve stack space for saving the c_entry_fp later.
__ AllocateStackSpace(kSystemPointerSize);
} else {
__ Push(lr, fp);
__ mov(fp, sp);
}
} else if (call_descriptor->IsJSFunctionCall()) {
__ Prologue();
if (call_descriptor->PushArgumentCount()) {
@ -3026,8 +3032,8 @@ void CodeGenerator::AssembleConstructFrame() {
unwinding_info_writer_.MarkFrameConstructed(__ pc_offset());
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.

View File

@ -2440,8 +2440,8 @@ void CodeGenerator::AssembleConstructFrame() {
// The frame has been previously padded in CodeGenerator::FinishFrame().
DCHECK_EQ(frame()->GetTotalFrameSlotCount() % 2, 0);
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
CPURegList saves = CPURegList(CPURegister::kRegister, kXRegSizeInBits,
call_descriptor->CalleeSavedRegisters());
@ -2580,7 +2580,17 @@ void CodeGenerator::AssembleConstructFrame() {
MemOperand(fp, WasmCompiledFrameConstants::kWasmInstanceOffset));
} break;
case CallDescriptor::kCallAddress:
if (info()->GetOutputStackFrameType() == StackFrame::C_WASM_ENTRY) {
required_slots += 2; // marker + saved c_entry_fp.
}
__ Claim(required_slots);
if (info()->GetOutputStackFrameType() == StackFrame::C_WASM_ENTRY) {
UseScratchRegisterScope temps(tasm());
Register scratch = temps.AcquireX();
__ Mov(scratch, StackFrame::TypeToMarker(StackFrame::C_WASM_ENTRY));
__ Str(scratch,
MemOperand(fp, TypedFrameConstants::kFrameTypeOffset));
}
break;
default:
UNREACHABLE();

View File

@ -4226,6 +4226,11 @@ void CodeGenerator::AssembleConstructFrame() {
if (call_descriptor->IsCFunctionCall()) {
__ push(ebp);
__ mov(ebp, esp);
if (info()->GetOutputStackFrameType() == StackFrame::C_WASM_ENTRY) {
__ Push(Immediate(StackFrame::TypeToMarker(StackFrame::C_WASM_ENTRY)));
// Reserve stack space for saving the c_entry_fp later.
__ AllocateStackSpace(kSystemPointerSize);
}
} else if (call_descriptor->IsJSFunctionCall()) {
__ Prologue();
if (call_descriptor->PushArgumentCount()) {
@ -4256,8 +4261,8 @@ void CodeGenerator::AssembleConstructFrame() {
}
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.

View File

@ -3416,8 +3416,8 @@ void CodeGenerator::AssembleConstructFrame() {
}
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.

View File

@ -3575,8 +3575,8 @@ void CodeGenerator::AssembleConstructFrame() {
}
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.

View File

@ -2366,8 +2366,8 @@ void CodeGenerator::AssembleConstructFrame() {
}
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.
__ Abort(AbortReason::kShouldNotDirectlyEnterOsrFunction);

View File

@ -3057,8 +3057,8 @@ void CodeGenerator::AssembleConstructFrame() {
}
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.
__ Abort(AbortReason::kShouldNotDirectlyEnterOsrFunction);

View File

@ -3734,6 +3734,11 @@ void CodeGenerator::AssembleConstructFrame() {
if (call_descriptor->IsCFunctionCall()) {
__ pushq(rbp);
__ movq(rbp, rsp);
if (info()->GetOutputStackFrameType() == StackFrame::C_WASM_ENTRY) {
__ Push(Immediate(StackFrame::TypeToMarker(StackFrame::C_WASM_ENTRY)));
// Reserve stack space for saving the c_entry_fp later.
__ AllocateStackSpace(kSystemPointerSize);
}
} else if (call_descriptor->IsJSFunctionCall()) {
__ Prologue();
if (call_descriptor->PushArgumentCount()) {
@ -3765,8 +3770,8 @@ void CodeGenerator::AssembleConstructFrame() {
unwinding_info_writer_.MarkFrameConstructed(pc_base);
}
int required_slots = frame()->GetTotalFrameSlotCount() -
call_descriptor->CalculateFixedFrameSize();
int required_slots =
frame()->GetTotalFrameSlotCount() - frame()->GetFixedSlotCount();
if (info()->is_osr()) {
// TurboFan OSR-compiled functions cannot be entered directly.

View File

@ -137,13 +137,19 @@ bool CallDescriptor::CanTailCall(const Node* node) const {
return HasSameReturnLocationsAs(CallDescriptorOf(node->op()));
}
int CallDescriptor::CalculateFixedFrameSize() const {
// TODO(jkummerow, sigurds): Arguably frame size calculation should be
// keyed on code/frame type, not on CallDescriptor kind. Think about a
// good way to organize this logic.
int CallDescriptor::CalculateFixedFrameSize(Code::Kind code_kind) const {
switch (kind_) {
case kCallJSFunction:
return PushArgumentCount()
? OptimizedBuiltinFrameConstants::kFixedSlotCount
: StandardFrameConstants::kFixedSlotCount;
case kCallAddress:
if (code_kind == Code::C_WASM_ENTRY) {
return CWasmEntryFrameConstants::kFixedSlotCount;
}
return CommonFrameConstants::kFixedSlotCountAboveFp +
CommonFrameConstants::kCPSlotCount;
case kCallCodeObject:

View File

@ -325,7 +325,7 @@ class V8_EXPORT_PRIVATE CallDescriptor final
bool CanTailCall(const Node* call) const;
int CalculateFixedFrameSize() const;
int CalculateFixedFrameSize(Code::Kind code_kind) const;
RegList AllocatableRegisters() const { return allocatable_registers_; }

View File

@ -399,7 +399,8 @@ class PipelineData {
DCHECK_NULL(frame_);
int fixed_frame_size = 0;
if (call_descriptor != nullptr) {
fixed_frame_size = call_descriptor->CalculateFixedFrameSize();
fixed_frame_size =
call_descriptor->CalculateFixedFrameSize(info()->code_kind());
}
frame_ = new (codegen_zone()) Frame(fixed_frame_size);
}
@ -2961,8 +2962,9 @@ bool PipelineImpl::SelectInstructionsAndAssemble(
}
MaybeHandle<Code> PipelineImpl::GenerateCode(CallDescriptor* call_descriptor) {
if (!SelectInstructionsAndAssemble(call_descriptor))
if (!SelectInstructionsAndAssemble(call_descriptor)) {
return MaybeHandle<Code>();
}
return FinalizeCode();
}

View File

@ -5790,22 +5790,26 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
}
void BuildCWasmEntry() {
// Build the start and the JS parameter nodes.
SetEffect(SetControl(Start(CWasmEntryParameters::kNumParameters + 5)));
// +1 offset for first parameter index being -1.
SetEffect(SetControl(Start(CWasmEntryParameters::kNumParameters + 1)));
// Create parameter nodes (offset by 1 for the receiver parameter).
Node* code_entry = Param(CWasmEntryParameters::kCodeEntry + 1);
Node* object_ref_node = Param(CWasmEntryParameters::kObjectRef + 1);
Node* arg_buffer = Param(CWasmEntryParameters::kArgumentsBuffer + 1);
Node* code_entry = Param(CWasmEntryParameters::kCodeEntry);
Node* object_ref = Param(CWasmEntryParameters::kObjectRef);
Node* arg_buffer = Param(CWasmEntryParameters::kArgumentsBuffer);
Node* c_entry_fp = Param(CWasmEntryParameters::kCEntryFp);
Node* fp_value = graph()->NewNode(mcgraph()->machine()->LoadFramePointer());
STORE_RAW(fp_value, TypedFrameConstants::kFirstPushedFrameValueOffset,
c_entry_fp, MachineType::PointerRepresentation(),
kNoWriteBarrier);
int wasm_arg_count = static_cast<int>(sig_->parameter_count());
int arg_count =
wasm_arg_count + 4; // code, object_ref_node, control, effect
int arg_count = wasm_arg_count + 4; // code, object_ref, control, effect
Node** args = Buffer(arg_count);
int pos = 0;
args[pos++] = code_entry;
args[pos++] = object_ref_node;
args[pos++] = object_ref;
int offset = 0;
for (wasm::ValueType type : sig_->parameters()) {
@ -5826,26 +5830,43 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
Node* call = SetEffect(graph()->NewNode(
mcgraph()->common()->Call(call_descriptor), arg_count, args));
// Store the return value.
DCHECK_GE(1, sig_->return_count());
if (sig_->return_count() == 1) {
Node* if_success = graph()->NewNode(mcgraph()->common()->IfSuccess(), call);
Node* if_exception =
graph()->NewNode(mcgraph()->common()->IfException(), call, call);
// Handle exception: return it.
SetControl(if_exception);
Return(if_exception);
// Handle success: store the return value(s).
SetControl(if_success);
pos = 0;
offset = 0;
for (wasm::ValueType type : sig_->returns()) {
StoreRepresentation store_rep(
wasm::ValueTypes::MachineRepresentationFor(sig_->GetReturn()),
kNoWriteBarrier);
wasm::ValueTypes::MachineRepresentationFor(type), kNoWriteBarrier);
Node* value =
sig_->return_count() == 1
? call
: graph()->NewNode(mcgraph()->common()->Projection(pos), call);
SetEffect(graph()->NewNode(mcgraph()->machine()->Store(store_rep),
arg_buffer, Int32Constant(0), call, Effect(),
Control()));
arg_buffer, Int32Constant(offset), value,
Effect(), Control()));
offset += wasm::ValueTypes::ElementSizeInBytes(type);
pos++;
}
Return(jsgraph()->SmiConstant(0));
if (mcgraph()->machine()->Is32() && ContainsInt64(sig_)) {
MachineRepresentation sig_reps[] = {
MachineRepresentation::kWord32, // return value
MachineRepresentation::kTagged, // receiver
MachineRepresentation::kTagged, // arg0 (code)
MachineRepresentation::kTagged // arg1 (buffer)
MachineType::PointerRepresentation(), // return value
MachineType::PointerRepresentation(), // target
MachineRepresentation::kTagged, // object_ref
MachineType::PointerRepresentation(), // argv
MachineType::PointerRepresentation() // c_entry_fp
};
Signature<MachineRepresentation> c_entry_sig(1, 2, sig_reps);
Signature<MachineRepresentation> c_entry_sig(1, 4, sig_reps);
Int64Lowering r(mcgraph()->graph(), mcgraph()->machine(),
mcgraph()->common(), mcgraph()->zone(), &c_entry_sig);
r.LowerGraph();
@ -6308,9 +6329,17 @@ MaybeHandle<Code> CompileCWasmEntry(Isolate* isolate, wasm::FunctionSig* sig) {
builder.BuildCWasmEntry();
// Schedule and compile to machine code.
CallDescriptor* incoming = Linkage::GetJSCallDescriptor(
zone.get(), false, CWasmEntryParameters::kNumParameters + 1,
CallDescriptor::kNoFlags);
MachineType sig_types[] = {MachineType::Pointer(), // return
MachineType::Pointer(), // target
MachineType::AnyTagged(), // object_ref
MachineType::Pointer(), // argv
MachineType::Pointer()}; // c_entry_fp
MachineSignature incoming_sig(1, 4, sig_types);
// Traps need the root register, for TailCallRuntimeWithCEntry to call
// Runtime::kThrowWasmError.
bool initialize_root_flag = true;
CallDescriptor* incoming = Linkage::GetSimplifiedCDescriptor(
zone.get(), &incoming_sig, initialize_root_flag);
// Build a name in the form "c-wasm-entry:<params>:<returns>".
static constexpr size_t kMaxNameLen = 128;

View File

@ -138,13 +138,13 @@ enum CWasmEntryParameters {
kCodeEntry,
kObjectRef,
kArgumentsBuffer,
kCEntryFp,
// marker:
kNumParameters
};
// Compiles a stub with JS linkage, taking parameters as described by
// {CWasmEntryParameters}. It loads the wasm parameters from the argument
// buffer and calls the wasm function given as first parameter.
// Compiles a stub with C++ linkage, to be called from Execution::CallWasm,
// which knows how to feed it its parameters.
MaybeHandle<Code> CompileCWasmEntry(Isolate* isolate, wasm::FunctionSig* sig);
// Values from the instance object are cached between WASM-level function calls.

View File

@ -5,6 +5,7 @@
#include "src/execution/execution.h"
#include "src/api/api-inl.h"
#include "src/compiler/wasm-compiler.h" // Only for static asserts.
#include "src/execution/isolate-inl.h"
#include "src/execution/vm-state-inl.h"
#include "src/logging/counters.h"
@ -393,5 +394,69 @@ MaybeHandle<Object> Execution::TryRunMicrotasks(
exception_out));
}
struct StackHandlerMarker {
Address next;
Address padding;
};
STATIC_ASSERT(offsetof(StackHandlerMarker, next) ==
StackHandlerConstants::kNextOffset);
STATIC_ASSERT(offsetof(StackHandlerMarker, padding) ==
StackHandlerConstants::kPaddingOffset);
STATIC_ASSERT(sizeof(StackHandlerMarker) == StackHandlerConstants::kSize);
void Execution::CallWasm(Isolate* isolate, Handle<Code> wrapper_code,
Address wasm_call_target, Handle<Object> object_ref,
Address packed_args) {
using WasmEntryStub = GeneratedCode<Address(
Address target, Address object_ref, Address argv, Address c_entry_fp)>;
WasmEntryStub stub_entry =
WasmEntryStub::FromAddress(isolate, wrapper_code->InstructionStart());
// Save and restore context around invocation and block the
// allocation of handles without explicit handle scopes.
SaveContext save(isolate);
SealHandleScope shs(isolate);
Address saved_c_entry_fp = *isolate->c_entry_fp_address();
Address saved_js_entry_sp = *isolate->js_entry_sp_address();
if (saved_js_entry_sp == kNullAddress) {
*isolate->js_entry_sp_address() = GetCurrentStackPosition();
}
StackHandlerMarker stack_handler;
stack_handler.next = isolate->thread_local_top()->handler_;
#ifdef V8_USE_ADDRESS_SANITIZER
stack_handler.padding = GetCurrentStackPosition();
#else
stack_handler.padding = 0;
#endif
isolate->thread_local_top()->handler_ =
reinterpret_cast<Address>(&stack_handler);
trap_handler::SetThreadInWasm();
{
RuntimeCallTimerScope timer(isolate, RuntimeCallCounterId::kJS_Execution);
STATIC_ASSERT(compiler::CWasmEntryParameters::kCodeEntry == 0);
STATIC_ASSERT(compiler::CWasmEntryParameters::kObjectRef == 1);
STATIC_ASSERT(compiler::CWasmEntryParameters::kArgumentsBuffer == 2);
STATIC_ASSERT(compiler::CWasmEntryParameters::kCEntryFp == 3);
Address result = stub_entry.Call(wasm_call_target, object_ref->ptr(),
packed_args, saved_c_entry_fp);
if (result != kNullAddress) {
isolate->set_pending_exception(Object(result));
}
}
// If there was an exception, then the thread-in-wasm flag is cleared
// already.
if (trap_handler::IsThreadInWasm()) {
trap_handler::ClearThreadInWasm();
}
isolate->thread_local_top()->handler_ = stack_handler.next;
if (saved_js_entry_sp == kNullAddress) {
*isolate->js_entry_sp_address() = saved_js_entry_sp;
}
*isolate->c_entry_fp_address() = saved_c_entry_fp;
}
} // namespace internal
} // namespace v8

View File

@ -55,6 +55,16 @@ class Execution final : public AllStatic {
static MaybeHandle<Object> TryRunMicrotasks(
Isolate* isolate, MicrotaskQueue* microtask_queue,
MaybeHandle<Object>* exception_out);
// Call a Wasm function identified by {wasm_call_target} through the
// provided {wrapper_code}, which must match the function's signature.
// Upon return, either isolate->has_pending_exception() is true, or
// the function's return values are in {packed_args}.
V8_EXPORT_PRIVATE static void CallWasm(Isolate* isolate,
Handle<Code> wrapper_code,
Address wasm_call_target,
Handle<Object> object_ref,
Address packed_args);
};
} // namespace internal

View File

@ -249,6 +249,13 @@ class ConstructFrameConstants : public TypedFrameConstants {
DEFINE_TYPED_FRAME_SIZES(5);
};
class CWasmEntryFrameConstants : public TypedFrameConstants {
public:
// FP-relative:
static constexpr int kCEntryFPOffset = TYPED_FRAME_PUSHED_VALUE_OFFSET(0);
DEFINE_TYPED_FRAME_SIZES(1);
};
class WasmCompiledFrameConstants : public TypedFrameConstants {
public:
// FP-relative.

View File

@ -33,6 +33,23 @@ namespace internal {
ReturnAddressLocationResolver StackFrame::return_address_location_resolver_ =
nullptr;
namespace {
Address AddressOf(const StackHandler* handler) {
Address raw = handler->address();
#ifdef V8_USE_ADDRESS_SANITIZER
// ASan puts C++-allocated StackHandler markers onto its fake stack.
// We work around that by storing the real stack address in the "padding"
// field. StackHandlers allocated from generated code have 0 as padding.
Address padding =
base::Memory<Address>(raw + StackHandlerConstants::kPaddingOffset);
if (padding != 0) return padding;
#endif
return raw;
}
} // namespace
// Iterator that supports traversing the stack handlers of a
// particular frame. Needs to know the top of the handler chain.
class StackHandlerIterator {
@ -40,12 +57,18 @@ class StackHandlerIterator {
StackHandlerIterator(const StackFrame* frame, StackHandler* handler)
: limit_(frame->fp()), handler_(handler) {
// Make sure the handler has already been unwound to this frame.
DCHECK(frame->sp() <= handler->address());
DCHECK(frame->sp() <= AddressOf(handler));
// For CWasmEntry frames, the handler was registered by the last C++
// frame (Execution::CallWasm), so even though its address is already
// beyond the limit, we know we always want to unwind one handler.
if (frame->type() == StackFrame::C_WASM_ENTRY) {
handler_ = handler_->next();
}
}
StackHandler* handler() const { return handler_; }
bool done() { return handler_ == nullptr || handler_->address() > limit_; }
bool done() { return handler_ == nullptr || AddressOf(handler_) > limit_; }
void Advance() {
DCHECK(!done());
handler_ = handler_->next();
@ -637,6 +660,12 @@ StackFrame::Type EntryFrame::GetCallerState(State* state) const {
return ExitFrame::GetStateForFramePointer(fp, state);
}
StackFrame::Type CWasmEntryFrame::GetCallerState(State* state) const {
const int offset = CWasmEntryFrameConstants::kCEntryFPOffset;
Address fp = Memory<Address>(this->fp() + offset);
return ExitFrame::GetStateForFramePointer(fp, state);
}
Code ConstructEntryFrame::unchecked_code() const {
return isolate()->heap()->builtin(Builtins::kJSConstructEntry);
}

View File

@ -1049,6 +1049,7 @@ class CWasmEntryFrame : public StubFrame {
private:
friend class StackFrameIteratorBase;
Type GetCallerState(State* state) const override;
};
class WasmCompileLazyFrame : public StandardFrame {

View File

@ -1622,7 +1622,12 @@ Object Isolate::UnwindAndFindHandler() {
thread_local_top()->pending_handler_fp_ = handler_fp;
thread_local_top()->pending_handler_sp_ = handler_sp;
// Return and clear pending exception.
// Return and clear pending exception. The contract is that:
// (1) the pending exception is stored in one place (no duplication), and
// (2) within generated-code land, that one place is the return register.
// If/when we unwind back into C++ (returning to the JSEntry stub,
// or to Execution::CallWasm), the returned exception will be sent
// back to isolate->set_pending_exception(...).
clear_pending_exception();
return exception;
};
@ -1657,6 +1662,19 @@ Object Isolate::UnwindAndFindHandler() {
0);
}
case StackFrame::C_WASM_ENTRY: {
StackHandler* handler = frame->top_handler();
thread_local_top()->handler_ = handler->next_address();
Code code = frame->LookupCode();
HandlerTable table(code);
Address instruction_start = code.InstructionStart();
int return_offset = static_cast<int>(frame->pc() - instruction_start);
int handler_offset = table.LookupReturn(return_offset);
DCHECK_NE(-1, handler_offset);
return FoundHandler(Context(), instruction_start, handler_offset,
code.constant_pool(), frame->sp(), frame->fp());
}
case StackFrame::WASM_COMPILED: {
if (trap_handler::IsThreadInWasm()) {
trap_handler::ClearThreadInWasm();

View File

@ -28,8 +28,10 @@
#include "include/libplatform/libplatform.h"
#include "src/api/api-inl.h"
#include "src/compiler/wasm-compiler.h"
#include "src/wasm/leb-helper.h"
#include "src/wasm/module-instantiate.h"
#include "src/wasm/wasm-arguments.h"
#include "src/wasm/wasm-constants.h"
#include "src/wasm/wasm-objects.h"
#include "src/wasm/wasm-result.h"
@ -1103,85 +1105,6 @@ auto ExportType::type() const -> const ExternType* {
return impl(this)->type.get();
}
///////////////////////////////////////////////////////////////////////////////
// Conversions of values from and to V8 objects
auto val_to_v8(StoreImpl* store, const Val& v) -> v8::Local<v8::Value> {
auto isolate = store->isolate();
switch (v.kind()) {
case I32:
return v8::Integer::NewFromUnsigned(isolate, v.i32());
case I64:
return v8::BigInt::New(isolate, v.i64());
case F32:
return v8::Number::New(isolate, v.f32());
case F64:
return v8::Number::New(isolate, v.f64());
case ANYREF:
case FUNCREF: {
if (v.ref() == nullptr) {
return v8::Null(isolate);
} else {
WASM_UNIMPLEMENTED("ref value");
}
}
default:
UNREACHABLE();
}
}
own<Val> v8_to_val(i::Isolate* isolate, i::Handle<i::Object> value,
ValKind kind) {
switch (kind) {
case I32:
do {
if (value->IsSmi()) return Val(i::Smi::ToInt(*value));
if (value->IsHeapNumber()) {
return Val(i::DoubleToInt32(i::HeapNumber::cast(*value).value()));
}
value = i::Object::ToInt32(isolate, value).ToHandleChecked();
// This will loop back at most once.
} while (true);
UNREACHABLE();
case I64:
if (value->IsBigInt()) return Val(i::BigInt::cast(*value).AsInt64());
return Val(
i::BigInt::FromObject(isolate, value).ToHandleChecked()->AsInt64());
case F32:
do {
if (value->IsSmi()) {
return Val(static_cast<float32_t>(i::Smi::ToInt(*value)));
}
if (value->IsHeapNumber()) {
return Val(i::DoubleToFloat32(i::HeapNumber::cast(*value).value()));
}
value = i::Object::ToNumber(isolate, value).ToHandleChecked();
// This will loop back at most once.
} while (true);
UNREACHABLE();
case F64:
do {
if (value->IsSmi()) {
return Val(static_cast<float64_t>(i::Smi::ToInt(*value)));
}
if (value->IsHeapNumber()) {
return Val(i::HeapNumber::cast(*value).value());
}
value = i::Object::ToNumber(isolate, value).ToHandleChecked();
// This will loop back at most once.
} while (true);
UNREACHABLE();
case ANYREF:
case FUNCREF: {
if (value->IsNull(isolate)) {
return Val(nullptr);
} else {
WASM_UNIMPLEMENTED("ref value");
}
}
}
}
i::Handle<i::String> VecToString(i::Isolate* isolate,
const vec<byte_t>& chars) {
return isolate->factory()
@ -1728,74 +1651,153 @@ auto Func::result_arity() const -> size_t {
return sig->return_count();
}
namespace {
void PrepareFunctionData(i::Isolate* isolate,
i::Handle<i::WasmExportedFunctionData> function_data,
i::wasm::FunctionSig* sig) {
// If the data is already populated, return immediately.
if (!function_data->c_wrapper_code().IsSmi()) return;
// Compile wrapper code.
i::Handle<i::Code> wrapper_code =
i::compiler::CompileCWasmEntry(isolate, sig).ToHandleChecked();
function_data->set_c_wrapper_code(*wrapper_code);
// Compute packed args size.
function_data->set_packed_args_size(
i::wasm::CWasmArgumentsPacker::TotalSize(sig));
// Get call target (function table offset). This is an Address, we store
// it as a pseudo-Smi by shifting it by one bit, so the GC leaves it alone.
i::Address call_target =
function_data->instance().GetCallTarget(function_data->function_index());
i::Smi smi_target((call_target << i::kSmiTagSize) | i::kSmiTag);
function_data->set_wasm_call_target(smi_target);
}
void PushArgs(i::wasm::FunctionSig* sig, const Val args[],
i::wasm::CWasmArgumentsPacker* packer) {
for (size_t i = 0; i < sig->parameter_count(); i++) {
i::wasm::ValueType type = sig->GetParam(i);
switch (type) {
case i::wasm::kWasmI32:
packer->Push(args[i].i32());
break;
case i::wasm::kWasmI64:
packer->Push(args[i].i64());
break;
case i::wasm::kWasmF32:
packer->Push(args[i].f32());
break;
case i::wasm::kWasmF64:
packer->Push(args[i].f64());
break;
case i::wasm::kWasmAnyRef:
case i::wasm::kWasmAnyFunc:
case i::wasm::kWasmExceptRef:
// TODO(jkummerow): Implement these.
UNIMPLEMENTED();
break;
default:
UNIMPLEMENTED();
}
}
}
void PopArgs(i::wasm::FunctionSig* sig, Val results[],
i::wasm::CWasmArgumentsPacker* packer) {
packer->Reset();
for (size_t i = 0; i < sig->return_count(); i++) {
i::wasm::ValueType type = sig->GetReturn(i);
switch (type) {
case i::wasm::kWasmI32:
results[i] = Val(packer->Pop<int32_t>());
break;
case i::wasm::kWasmI64:
results[i] = Val(packer->Pop<int64_t>());
break;
case i::wasm::kWasmF32:
results[i] = Val(packer->Pop<float>());
break;
case i::wasm::kWasmF64:
results[i] = Val(packer->Pop<double>());
break;
case i::wasm::kWasmAnyRef:
case i::wasm::kWasmAnyFunc:
case i::wasm::kWasmExceptRef:
// TODO(jkummerow): Implement these.
UNIMPLEMENTED();
break;
default:
UNIMPLEMENTED();
}
}
}
} // namespace
auto Func::call(const Val args[], Val results[]) const -> own<Trap*> {
auto func = impl(this);
auto store = func->store();
auto isolate = store->isolate();
auto i_isolate = store->i_isolate();
v8::HandleScope handle_scope(isolate);
auto isolate = store->i_isolate();
i::HandleScope handle_scope(isolate);
i::Object raw_function_data = func->v8_object()->shared().function_data();
int num_params;
int num_results;
ValKind result_kind;
i::Handle<i::JSFunction> v8_func = func->v8_object();
if (i::WasmExportedFunction::IsWasmExportedFunction(*v8_func)) {
i::WasmExportedFunction wef = i::WasmExportedFunction::cast(*v8_func);
i::wasm::FunctionSig* sig =
wef.instance().module()->functions[wef.function_index()].sig;
num_params = static_cast<int>(sig->parameter_count());
num_results = static_cast<int>(sig->return_count());
if (num_results > 0) {
result_kind = v8::wasm::v8_valtype_to_wasm(sig->GetReturn(0));
// WasmCapiFunctions can be called directly.
if (raw_function_data.IsWasmCapiFunctionData()) {
i::WasmCapiFunctionData data =
i::WasmCapiFunctionData::cast(raw_function_data);
FuncData* func_data = reinterpret_cast<FuncData*>(data.embedder_data());
if (func_data->kind == FuncData::kCallback) {
return (func_data->callback)(args, results);
}
#if DEBUG
for (int i = 0; i < num_params; i++) {
DCHECK_EQ(args[i].kind(), v8::wasm::v8_valtype_to_wasm(sig->GetParam(i)));
}
#endif
} else {
DCHECK(i::WasmCapiFunction::IsWasmCapiFunction(*v8_func));
UNIMPLEMENTED();
}
// TODO(rossberg): cache v8_args array per thread.
auto v8_args = std::unique_ptr<i::Handle<i::Object>[]>(
new (std::nothrow) i::Handle<i::Object>[num_params]);
for (int i = 0; i < num_params; ++i) {
v8_args[i] = v8::Utils::OpenHandle(*val_to_v8(store, args[i]));
DCHECK(func_data->kind == FuncData::kCallbackWithEnv);
return (func_data->callback_with_env)(func_data->env, args, results);
}
// TODO(jkummerow): Use Execution::TryCall instead of manual TryCatch.
v8::TryCatch handler(isolate);
i::MaybeHandle<i::Object> maybe_val = i::Execution::Call(
i_isolate, func->v8_object(), i_isolate->factory()->undefined_value(),
num_params, v8_args.get());
DCHECK(raw_function_data.IsWasmExportedFunctionData());
i::Handle<i::WasmExportedFunctionData> function_data(
i::WasmExportedFunctionData::cast(raw_function_data), isolate);
i::Handle<i::WasmInstanceObject> instance(function_data->instance(), isolate);
// Caching {sig} would give a ~10% reduction in overhead.
i::wasm::FunctionSig* sig =
instance->module()->functions[function_data->function_index()].sig;
PrepareFunctionData(isolate, function_data, sig);
i::Handle<i::Code> wrapper_code = i::Handle<i::Code>(
i::Code::cast(function_data->c_wrapper_code()), isolate);
i::Address call_target =
function_data->wasm_call_target().ptr() >> i::kSmiTagSize;
if (handler.HasCaught()) {
i_isolate->OptionalRescheduleException(true);
i::Handle<i::Object> exception =
v8::Utils::OpenHandle(*handler.Exception());
i::wasm::CWasmArgumentsPacker packer(function_data->packed_args_size());
PushArgs(sig, args, &packer);
// Imported and then re-exported functions requiring wrappers are not
// supported yet.
// TODO(jkummerow): When we have C-API + JavaScript, we'll need to get
// the appropriate {instance, function} tuple -- or possibly call the
// original JavaScript function directly?
DCHECK(function_data->function_index() >=
static_cast<int>(instance->module()->num_imported_functions));
i::Handle<i::Object> object_ref = instance;
i::Execution::CallWasm(isolate, wrapper_code, call_target, object_ref,
packer.argv());
if (isolate->has_pending_exception()) {
i::Handle<i::Object> exception(isolate->pending_exception(), isolate);
isolate->clear_pending_exception();
if (!exception->IsJSReceiver()) {
i::MaybeHandle<i::String> maybe_string =
i::Object::ToString(i_isolate, exception);
i::Object::ToString(isolate, exception);
i::Handle<i::String> string = maybe_string.is_null()
? i_isolate->factory()->empty_string()
? isolate->factory()->empty_string()
: maybe_string.ToHandleChecked();
exception =
i_isolate->factory()->NewError(i_isolate->error_function(), string);
isolate->factory()->NewError(isolate->error_function(), string);
}
return implement<Trap>::type::make(
store, i::Handle<i::JSReceiver>::cast(exception));
}
auto val = maybe_val.ToHandleChecked();
if (num_results == 0) {
assert(val->IsUndefined(i_isolate));
} else if (num_results == 1) {
assert(!val->IsUndefined(i_isolate));
new (&results[0]) Val(v8_to_val(i_isolate, val, result_kind));
} else {
WASM_UNIMPLEMENTED("multiple results");
}
PopArgs(sig, results, &packer);
return nullptr;
}

73
src/wasm/wasm-arguments.h Normal file
View File

@ -0,0 +1,73 @@
// Copyright 2019 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.
#ifndef V8_WASM_WASM_ARGUMENTS_H_
#define V8_WASM_WASM_ARGUMENTS_H_
#include <stdint.h>
#include <vector>
#include "src/base/memory.h"
#include "src/codegen/signature.h"
#include "src/common/globals.h"
#include "src/wasm/value-type.h"
namespace v8 {
namespace internal {
namespace wasm {
// Helper class for {Push}ing Wasm value arguments onto the stack in the format
// that the CWasmEntryStub expects, as well as for {Pop}ping return values.
// {Reset} must be called if a packer instance used for pushing is then
// reused for popping: it resets the internal pointer to the beginning of
// the stack region.
class CWasmArgumentsPacker {
public:
explicit CWasmArgumentsPacker(size_t buffer_size)
: heap_buffer_(buffer_size <= kMaxOnStackBuffer ? 0 : buffer_size),
buffer_((buffer_size <= kMaxOnStackBuffer) ? on_stack_buffer_
: heap_buffer_.data()) {}
i::Address argv() const { return reinterpret_cast<i::Address>(buffer_); }
void Reset() { offset_ = 0; }
template <typename T>
void Push(T val) {
Address address = reinterpret_cast<Address>(buffer_ + offset_);
offset_ += sizeof(val);
base::WriteUnalignedValue(address, val);
}
template <typename T>
T Pop() {
Address address = reinterpret_cast<Address>(buffer_ + offset_);
offset_ += sizeof(T);
return base::ReadUnalignedValue<T>(address);
}
static int TotalSize(FunctionSig* sig) {
int return_size = 0;
for (ValueType t : sig->returns()) {
return_size += ValueTypes::ElementSizeInBytes(t);
}
int param_size = 0;
for (ValueType t : sig->parameters()) {
param_size += ValueTypes::ElementSizeInBytes(t);
}
return std::max(return_size, param_size);
}
private:
static const size_t kMaxOnStackBuffer = 10 * i::kSystemPointerSize;
uint8_t on_stack_buffer_[kMaxOnStackBuffer];
std::vector<uint8_t> heap_buffer_;
uint8_t* buffer_;
size_t offset_ = 0;
};
} // namespace wasm
} // namespace internal
} // namespace v8
#endif // V8_WASM_WASM_ARGUMENTS_H_

View File

@ -617,8 +617,8 @@ Handle<JSObject> WasmDebugInfo::GetLocalScopeObject(
}
// static
Handle<JSFunction> WasmDebugInfo::GetCWasmEntry(
Handle<WasmDebugInfo> debug_info, wasm::FunctionSig* sig) {
Handle<Code> WasmDebugInfo::GetCWasmEntry(Handle<WasmDebugInfo> debug_info,
wasm::FunctionSig* sig) {
Isolate* isolate = debug_info->GetIsolate();
DCHECK_EQ(debug_info->has_c_wasm_entries(),
debug_info->has_c_wasm_entry_map());
@ -642,24 +642,9 @@ Handle<JSFunction> WasmDebugInfo::GetCWasmEntry(
DCHECK(entries->get(index).IsUndefined(isolate));
Handle<Code> new_entry_code =
compiler::CompileCWasmEntry(isolate, sig).ToHandleChecked();
Handle<WasmExportedFunctionData> function_data =
Handle<WasmExportedFunctionData>::cast(isolate->factory()->NewStruct(
WASM_EXPORTED_FUNCTION_DATA_TYPE, AllocationType::kOld));
function_data->set_wrapper_code(*new_entry_code);
function_data->set_instance(debug_info->wasm_instance());
function_data->set_jump_table_offset(-1);
function_data->set_function_index(-1);
Handle<String> name =
isolate->factory()->InternalizeString(StaticCharVector("c-wasm-entry"));
NewFunctionArgs args = NewFunctionArgs::ForWasm(
name, function_data, isolate->sloppy_function_map());
Handle<JSFunction> new_entry = isolate->factory()->NewFunction(args);
new_entry->set_context(debug_info->wasm_instance().native_context());
new_entry->shared().set_internal_formal_parameter_count(
compiler::CWasmEntryParameters::kNumParameters);
entries->set(index, *new_entry);
entries->set(index, *new_entry_code);
}
return handle(JSFunction::cast(entries->get(index)), isolate);
return handle(Code::cast(entries->get(index)), isolate);
}
} // namespace internal

View File

@ -12,7 +12,6 @@
#include "src/compiler/wasm-compiler.h"
#include "src/numbers/conversions.h"
#include "src/objects/objects-inl.h"
#include "src/trap-handler/trap-handler.h"
#include "src/utils/boxed-float.h"
#include "src/utils/identity-map.h"
#include "src/utils/utils.h"
@ -21,6 +20,7 @@
#include "src/wasm/function-body-decoder.h"
#include "src/wasm/memory-tracing.h"
#include "src/wasm/module-compiler.h"
#include "src/wasm/wasm-arguments.h"
#include "src/wasm/wasm-engine.h"
#include "src/wasm/wasm-external-refs.h"
#include "src/wasm/wasm-limits.h"
@ -3552,118 +3552,71 @@ class ThreadImpl {
}
Handle<WasmDebugInfo> debug_info(instance_object_->debug_info(), isolate);
Handle<JSFunction> wasm_entry =
WasmDebugInfo::GetCWasmEntry(debug_info, sig);
Handle<Code> wasm_entry = WasmDebugInfo::GetCWasmEntry(debug_info, sig);
TRACE(" => Calling external wasm function\n");
// Copy the arguments to one buffer.
// TODO(clemensh): Introduce a helper for all argument buffer
// con-/destruction.
std::vector<uint8_t> arg_buffer(num_args * 8);
size_t offset = 0;
CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig));
sp_t base_index = StackHeight() - num_args;
for (int i = 0; i < num_args; ++i) {
int param_size = ValueTypes::ElementSizeInBytes(sig->GetParam(i));
if (arg_buffer.size() < offset + param_size) {
arg_buffer.resize(std::max(2 * arg_buffer.size(), offset + param_size));
}
Address address = reinterpret_cast<Address>(arg_buffer.data()) + offset;
WasmValue arg = GetStackValue(base_index + i);
switch (sig->GetParam(i)) {
case kWasmI32:
WriteUnalignedValue(address, arg.to<uint32_t>());
packer.Push(arg.to<uint32_t>());
break;
case kWasmI64:
WriteUnalignedValue(address, arg.to<uint64_t>());
packer.Push(arg.to<uint64_t>());
break;
case kWasmF32:
WriteUnalignedValue(address, arg.to<float>());
packer.Push(arg.to<float>());
break;
case kWasmF64:
WriteUnalignedValue(address, arg.to<double>());
packer.Push(arg.to<double>());
break;
case kWasmAnyRef:
case kWasmAnyFunc:
case kWasmExceptRef:
DCHECK_EQ(kSystemPointerSize, param_size);
WriteUnalignedValue<Object>(address, *arg.to_anyref());
packer.Push(arg.to_anyref()->ptr());
break;
default:
UNIMPLEMENTED();
}
offset += param_size;
}
// Ensure that there is enough space in the arg_buffer to hold the return
// value(s).
size_t return_size = 0;
for (ValueType t : sig->returns()) {
return_size += ValueTypes::ElementSizeInBytes(t);
}
if (arg_buffer.size() < return_size) {
arg_buffer.resize(return_size);
}
// Wrap the arg_buffer and the code target data pointers in handles. As
// these are aligned pointers, to the GC it will look like Smis.
Handle<Object> arg_buffer_obj(
Object(reinterpret_cast<Address>(arg_buffer.data())), isolate);
DCHECK(!arg_buffer_obj->IsHeapObject());
Handle<Object> code_entry_obj(Object(code->instruction_start()), isolate);
DCHECK(!code_entry_obj->IsHeapObject());
static_assert(compiler::CWasmEntryParameters::kNumParameters == 3,
"code below needs adaption");
Handle<Object> args[compiler::CWasmEntryParameters::kNumParameters];
args[compiler::CWasmEntryParameters::kCodeEntry] = code_entry_obj;
args[compiler::CWasmEntryParameters::kObjectRef] = object_ref;
args[compiler::CWasmEntryParameters::kArgumentsBuffer] = arg_buffer_obj;
Handle<Object> receiver = isolate->factory()->undefined_value();
trap_handler::SetThreadInWasm();
MaybeHandle<Object> maybe_retval =
Execution::Call(isolate, wasm_entry, receiver, arraysize(args), args);
Address call_target = code->instruction_start();
Execution::CallWasm(isolate, wasm_entry, call_target, object_ref,
packer.argv());
TRACE(" => External wasm function returned%s\n",
maybe_retval.is_null() ? " with exception" : "");
isolate->has_pending_exception() ? " with exception" : "");
// Pop arguments off the stack.
Drop(num_args);
if (maybe_retval.is_null()) {
// JSEntry may throw a stack overflow before we actually get to wasm code
// or back to the interpreter, meaning the thread-in-wasm flag won't be
// cleared.
if (trap_handler::IsThreadInWasm()) {
trap_handler::ClearThreadInWasm();
}
if (isolate->has_pending_exception()) {
return TryHandleException(isolate);
}
trap_handler::ClearThreadInWasm();
// Push return values.
if (sig->return_count() > 0) {
// TODO(wasm): Handle multiple returns.
DCHECK_EQ(1, sig->return_count());
Address address = reinterpret_cast<Address>(arg_buffer.data());
switch (sig->GetReturn()) {
packer.Reset();
for (size_t i = 0; i < sig->return_count(); i++) {
switch (sig->GetReturn(i)) {
case kWasmI32:
Push(WasmValue(ReadUnalignedValue<uint32_t>(address)));
Push(WasmValue(packer.Pop<uint32_t>()));
break;
case kWasmI64:
Push(WasmValue(ReadUnalignedValue<uint64_t>(address)));
Push(WasmValue(packer.Pop<uint64_t>()));
break;
case kWasmF32:
Push(WasmValue(ReadUnalignedValue<float>(address)));
Push(WasmValue(packer.Pop<float>()));
break;
case kWasmF64:
Push(WasmValue(ReadUnalignedValue<double>(address)));
Push(WasmValue(packer.Pop<double>()));
break;
case kWasmAnyRef:
case kWasmAnyFunc:
case kWasmExceptRef: {
Handle<Object> ref(ReadUnalignedValue<Object>(address), isolate);
Handle<Object> ref(Object(packer.Pop<Address>()), isolate);
Push(WasmValue(ref));
break;
}

View File

@ -307,6 +307,10 @@ ACCESSORS(WasmExportedFunctionData, instance, WasmInstanceObject,
SMI_ACCESSORS(WasmExportedFunctionData, jump_table_offset,
kJumpTableOffsetOffset)
SMI_ACCESSORS(WasmExportedFunctionData, function_index, kFunctionIndexOffset)
ACCESSORS(WasmExportedFunctionData, c_wrapper_code, Object, kCWrapperCodeOffset)
ACCESSORS(WasmExportedFunctionData, wasm_call_target, Smi,
kWasmCallTargetOffset)
SMI_ACCESSORS(WasmExportedFunctionData, packed_args_size, kPackedArgsSizeOffset)
// WasmJSFunction
WasmJSFunction::WasmJSFunction(Address ptr) : JSFunction(ptr) {

View File

@ -2179,6 +2179,9 @@ Handle<WasmExportedFunction> WasmExportedFunction::New(
function_data->set_instance(*instance);
function_data->set_jump_table_offset(jump_table_offset);
function_data->set_function_index(func_index);
function_data->set_c_wrapper_code(Smi::zero(), SKIP_WRITE_BARRIER);
function_data->set_wasm_call_target(Smi::zero(), SKIP_WRITE_BARRIER);
function_data->set_packed_args_size(0);
MaybeHandle<String> maybe_name;
if (instance->module()->origin == wasm::kAsmJsOrigin) {

View File

@ -751,6 +751,9 @@ class WasmExportedFunctionData : public Struct {
DECL_ACCESSORS(instance, WasmInstanceObject)
DECL_INT_ACCESSORS(jump_table_offset)
DECL_INT_ACCESSORS(function_index)
DECL_ACCESSORS(c_wrapper_code, Object)
DECL_ACCESSORS(wasm_call_target, Smi)
DECL_INT_ACCESSORS(packed_args_size)
DECL_CAST(WasmExportedFunctionData)
@ -868,8 +871,8 @@ class WasmDebugInfo : public Struct {
Address frame_pointer,
int frame_index);
V8_EXPORT_PRIVATE static Handle<JSFunction> GetCWasmEntry(
Handle<WasmDebugInfo>, wasm::FunctionSig*);
V8_EXPORT_PRIVATE static Handle<Code> GetCWasmEntry(Handle<WasmDebugInfo>,
wasm::FunctionSig*);
OBJECT_CONSTRUCTORS(WasmDebugInfo, Struct);
};

View File

@ -962,7 +962,8 @@ class CodeGeneratorTester {
: zone_(environment->main_zone()),
info_(ArrayVector("test"), environment->main_zone(), Code::STUB),
linkage_(environment->test_descriptor()),
frame_(environment->test_descriptor()->CalculateFixedFrameSize()) {
frame_(environment->test_descriptor()->CalculateFixedFrameSize(
Code::STUB)) {
// Pick half of the stack parameters at random and move them into spill
// slots, separated by `extra_stack_space` bytes.
// When testing a move with stack slots using CheckAssembleMove or

View File

@ -7,6 +7,7 @@
#include "src/base/overflowing-math.h"
#include "src/codegen/assembler-inl.h"
#include "src/objects/objects-inl.h"
#include "src/wasm/wasm-arguments.h"
#include "src/wasm/wasm-objects.h"
#include "test/cctest/cctest.h"
#include "test/cctest/compiler/value-helper.h"
@ -41,46 +42,33 @@ class CWasmEntryArgTester {
Handle<WasmInstanceObject> instance(runner_.builder().instance_object());
Handle<WasmDebugInfo> debug_info =
WasmInstanceObject::GetOrCreateDebugInfo(instance);
c_wasm_entry_fn_ = WasmDebugInfo::GetCWasmEntry(debug_info, sig_);
c_wasm_entry_ = WasmDebugInfo::GetCWasmEntry(debug_info, sig_);
}
template <typename... Rest>
void WriteToBuffer(Address buf, Rest... rest) {
void WriteToBuffer(CWasmArgumentsPacker* packer, Rest... rest) {
static_assert(sizeof...(rest) == 0, "this is the base case");
}
template <typename First, typename... Rest>
void WriteToBuffer(Address buf, First first, Rest... rest) {
WriteUnalignedValue(buf, first);
WriteToBuffer(buf + sizeof(first), rest...);
void WriteToBuffer(CWasmArgumentsPacker* packer, First first, Rest... rest) {
packer->Push(first);
WriteToBuffer(packer, rest...);
}
void CheckCall(Args... args) {
std::vector<uint8_t> arg_buffer(sizeof...(args) * 8);
WriteToBuffer(reinterpret_cast<Address>(arg_buffer.data()), args...);
Handle<Object> receiver = isolate_->factory()->undefined_value();
Handle<Object> buffer_obj(
Object(reinterpret_cast<Address>(arg_buffer.data())), isolate_);
CHECK(!buffer_obj->IsHeapObject());
Handle<Object> code_entry_obj(Object(wasm_code_->instruction_start()),
isolate_);
CHECK(!code_entry_obj->IsHeapObject());
Handle<Object> call_args[]{code_entry_obj,
runner_.builder().instance_object(), buffer_obj};
static_assert(
arraysize(call_args) == compiler::CWasmEntryParameters::kNumParameters,
"adapt this test");
CWasmArgumentsPacker packer(CWasmArgumentsPacker::TotalSize(sig_));
WriteToBuffer(&packer, args...);
Address wasm_call_target = wasm_code_->instruction_start();
Handle<Object> object_ref = runner_.builder().instance_object();
wasm_code_->native_module()->SetExecutable(true);
MaybeHandle<Object> return_obj = Execution::Call(
isolate_, c_wasm_entry_fn_, receiver, arraysize(call_args), call_args);
CHECK(!return_obj.is_null());
CHECK(return_obj.ToHandleChecked()->IsSmi());
CHECK_EQ(0, Smi::ToInt(*return_obj.ToHandleChecked()));
Execution::CallWasm(isolate_, c_wasm_entry_, wasm_call_target, object_ref,
packer.argv());
CHECK(!isolate_->has_pending_exception());
packer.Reset();
// Check the result.
ReturnType result = ReadUnalignedValue<ReturnType>(
reinterpret_cast<Address>(arg_buffer.data()));
ReturnType result = packer.Pop<ReturnType>();
ReturnType expected = expected_fn_(args...);
if (std::is_floating_point<ReturnType>::value) {
CHECK_DOUBLE_EQ(expected, result);
@ -94,7 +82,7 @@ class CWasmEntryArgTester {
Isolate* isolate_;
std::function<ReturnType(Args...)> expected_fn_;
FunctionSig* sig_;
Handle<JSFunction> c_wasm_entry_fn_;
Handle<Code> c_wasm_entry_;
WasmCode* wasm_code_;
};

View File

@ -151,6 +151,29 @@ own<Trap*> Stage4_GC(void* env, const Val args[], Val results[]) {
return nullptr;
}
own<Trap*> FibonacciC(void* env, const Val args[], Val results[]) {
int32_t x = args[0].i32();
if (x == 0 || x == 1) {
results[0] = Val::i32(x);
return nullptr;
}
WasmCapiTest* self = reinterpret_cast<WasmCapiTest*>(env);
Func* fibo_wasm = self->GetExportedFunction(1);
// Aggressively re-use existing arrays. That's maybe not great coding
// style, but this test intentionally ensures that it works if someone
// insists on doing it.
Val recursive_args[] = {Val::i32(x - 1)};
own<Trap*> result = fibo_wasm->call(recursive_args, results);
DCHECK_NULL(result);
int32_t x1 = results[0].i32();
recursive_args[0] = Val::i32(x - 2);
result = fibo_wasm->call(recursive_args, results);
DCHECK_NULL(result);
int32_t x2 = results[0].i32();
results[0] = Val::i32(x1 + x2);
return nullptr;
}
} // namespace
TEST_F(WasmCapiTest, Trap) {
@ -190,6 +213,39 @@ TEST_F(WasmCapiTest, GC) {
EXPECT_EQ(43, results[0].i32());
}
TEST_F(WasmCapiTest, Recursion) {
// Build the following function:
// int32 fibonacci_wasm(int32 arg0) {
// if (arg0 == 0) return 0;
// if (arg0 == 1) return 1;
// return fibonacci_c(arg0 - 1) + fibonacci_c(arg0 - 2);
// }
uint32_t fibo_c_index =
builder()->AddImport(ArrayVector("fibonacci_c"), wasm_sig());
byte code_fibo[] = {
WASM_IF(WASM_I32_EQ(WASM_GET_LOCAL(0), WASM_ZERO),
WASM_RETURN1(WASM_ZERO)),
WASM_IF(WASM_I32_EQ(WASM_GET_LOCAL(0), WASM_ONE), WASM_RETURN1(WASM_ONE)),
// Muck with the parameter to ensure callers don't depend on its value.
WASM_SET_LOCAL(0, WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_ONE)),
WASM_RETURN1(WASM_I32_ADD(
WASM_CALL_FUNCTION(fibo_c_index, WASM_GET_LOCAL(0)),
WASM_CALL_FUNCTION(fibo_c_index,
WASM_I32_SUB(WASM_GET_LOCAL(0), WASM_ONE))))};
AddExportedFunction(CStrVector("fibonacci_wasm"), code_fibo,
sizeof(code_fibo));
Compile();
own<Func*> fibonacci = Func::make(store(), cpp_sig(), FibonacciC, this);
Extern* imports[] = {stage2(), fibonacci.get()};
// Enough iterations to make it interesting, few enough to keep it fast.
Val args[] = {Val::i32(15)};
Val results[1];
own<Trap*> result = Run(imports, args, results);
EXPECT_EQ(result, nullptr);
EXPECT_EQ(610, results[0].i32());
}
} // namespace wasm
} // namespace internal
} // namespace v8