[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:
parent
907f3a64b7
commit
e304d80e09
1
BUILD.gn
1
BUILD.gn
@ -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",
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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.
|
||||
|
@ -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:
|
||||
|
@ -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_; }
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -1049,6 +1049,7 @@ class CWasmEntryFrame : public StubFrame {
|
||||
|
||||
private:
|
||||
friend class StackFrameIteratorBase;
|
||||
Type GetCallerState(State* state) const override;
|
||||
};
|
||||
|
||||
class WasmCompileLazyFrame : public StandardFrame {
|
||||
|
@ -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();
|
||||
|
@ -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
73
src/wasm/wasm-arguments.h
Normal 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_
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
};
|
||||
|
@ -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
|
||||
|
@ -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_;
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user