Commit 999a791f authored by Thibaud Michaud's avatar Thibaud Michaud Committed by V8 LUCI CQ

[wasm] Suspend wasm continuation

Save the PC in the jump buffer and implement the suspend builtin.

R=ahaas@chromium.org
CC=​fgm@chromium.org

Bug: v8:12191
Change-Id: I1a6d965d7864dce0a572f6c8d7102046dad190fd
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3345006Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Reviewed-by: 's avatarAndreas Haas <ahaas@chromium.org>
Commit-Queue: Thibaud Michaud <thibaudm@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78715}
parent 7f26cbd2
...@@ -3616,10 +3616,23 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) { ...@@ -3616,10 +3616,23 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
} }
namespace { namespace {
// Helper function for WasmReturnPromiseOnSuspend. // Helper function for wasm stack switching.
void LoadJumpBuffer(MacroAssembler* masm, Register jmpbuf) { void FillJumpBuffer(MacroAssembler* masm, Register jmpbuf, Label* pc) {
__ movq(MemOperand(jmpbuf, wasm::kJmpBufSpOffset), rsp);
__ movq(MemOperand(jmpbuf, wasm::kJmpBufFpOffset), rbp);
__ movq(kScratchRegister,
__ StackLimitAsOperand(StackLimitKind::kRealStackLimit));
__ movq(MemOperand(jmpbuf, wasm::kJmpBufStackLimitOffset), kScratchRegister);
__ leaq(kScratchRegister, MemOperand(pc, 0));
__ movq(MemOperand(jmpbuf, wasm::kJmpBufPcOffset), kScratchRegister);
}
void LoadJumpBuffer(MacroAssembler* masm, Register jmpbuf, bool load_pc) {
__ movq(rsp, MemOperand(jmpbuf, wasm::kJmpBufSpOffset)); __ movq(rsp, MemOperand(jmpbuf, wasm::kJmpBufSpOffset));
__ movq(rbp, MemOperand(jmpbuf, wasm::kJmpBufFpOffset)); __ movq(rbp, MemOperand(jmpbuf, wasm::kJmpBufFpOffset));
if (load_pc) {
__ jmp(MemOperand(jmpbuf, wasm::kJmpBufPcOffset));
}
// The stack limit is set separately under the ExecutionAccess lock. // The stack limit is set separately under the ExecutionAccess lock.
// TODO(thibaudm): Reload live registers. // TODO(thibaudm): Reload live registers.
} }
...@@ -3674,19 +3687,9 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) { ...@@ -3674,19 +3687,9 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) {
__ LoadExternalPointerField( __ LoadExternalPointerField(
jmpbuf, FieldOperand(foreign_jmpbuf, Foreign::kForeignAddressOffset), jmpbuf, FieldOperand(foreign_jmpbuf, Foreign::kForeignAddressOffset),
kForeignForeignAddressTag, r8); kForeignForeignAddressTag, r8);
__ movq(MemOperand(jmpbuf, wasm::kJmpBufSpOffset), rsp); Label suspend;
__ movq(MemOperand(jmpbuf, wasm::kJmpBufFpOffset), rbp); FillJumpBuffer(masm, jmpbuf, &suspend);
Register stack_limit_address = rcx;
__ movq(stack_limit_address,
FieldOperand(wasm_instance,
WasmInstanceObject::kRealStackLimitAddressOffset));
Register stack_limit = rdx;
__ movq(stack_limit, MemOperand(stack_limit_address, 0));
__ movq(MemOperand(jmpbuf, wasm::kJmpBufStackLimitOffset), stack_limit);
// TODO(thibaudm): Save live registers.
foreign_jmpbuf = no_reg; foreign_jmpbuf = no_reg;
stack_limit = no_reg;
stack_limit_address = no_reg;
// live: [rsi, rdi, rax] // live: [rsi, rdi, rax]
// ------------------------------------------- // -------------------------------------------
...@@ -3725,7 +3728,7 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) { ...@@ -3725,7 +3728,7 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) {
kForeignForeignAddressTag, r8); kForeignForeignAddressTag, r8);
__ Move(GCScanSlotPlace, 0); __ Move(GCScanSlotPlace, 0);
// Switch stack! // Switch stack!
LoadJumpBuffer(masm, target_jmpbuf); LoadJumpBuffer(masm, target_jmpbuf, false);
foreign_jmpbuf = no_reg; foreign_jmpbuf = no_reg;
target_jmpbuf = no_reg; target_jmpbuf = no_reg;
// live: [rsi, rdi] // live: [rsi, rdi]
...@@ -3787,7 +3790,7 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) { ...@@ -3787,7 +3790,7 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) {
jmpbuf, FieldOperand(foreign_jmpbuf, Foreign::kForeignAddressOffset), jmpbuf, FieldOperand(foreign_jmpbuf, Foreign::kForeignAddressOffset),
kForeignForeignAddressTag, r8); kForeignForeignAddressTag, r8);
// Switch stack! // Switch stack!
LoadJumpBuffer(masm, jmpbuf); LoadJumpBuffer(masm, jmpbuf, false);
__ Move(GCScanSlotPlace, 1); __ Move(GCScanSlotPlace, 1);
__ Push(wasm_instance); // Spill. __ Push(wasm_instance); // Spill.
__ Move(kContextRegister, Smi::zero()); __ Move(kContextRegister, Smi::zero());
...@@ -3812,17 +3815,22 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) { ...@@ -3812,17 +3815,22 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) {
#ifdef DEBUG #ifdef DEBUG
// Check that the parent suspender is inactive. // Check that the parent suspender is inactive.
Label parent_inactive; Label parent_inactive;
__ cmpq(FieldOperand(suspender, WasmSuspenderObject::kStateOffset), Register state = rbx;
Immediate(WasmSuspenderObject::Inactive)); __ LoadTaggedSignedField(
state, FieldOperand(suspender, WasmSuspenderObject::kStateOffset));
__ SmiCompare(state, Smi::FromInt(WasmSuspenderObject::Inactive));
__ j(equal, &parent_inactive, Label::kNear); __ j(equal, &parent_inactive, Label::kNear);
__ Trap(); __ Trap();
__ bind(&parent_inactive); __ bind(&parent_inactive);
#endif #endif
__ movq(FieldOperand(suspender, WasmSuspenderObject::kStateOffset), __ StoreTaggedSignedField(
Immediate(WasmSuspenderObject::State::Active)); FieldOperand(suspender, WasmSuspenderObject::kStateOffset),
Smi::FromInt(WasmSuspenderObject::State::Active));
__ bind(&undefined); __ bind(&undefined);
__ movq(masm->RootAsOperand(RootIndex::kActiveSuspender), suspender); __ movq(masm->RootAsOperand(RootIndex::kActiveSuspender), suspender);
__ bind(&suspend);
// ------------------------------------------- // -------------------------------------------
// Epilogue. // Epilogue.
// ------------------------------------------- // -------------------------------------------
...@@ -3838,6 +3846,97 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) { ...@@ -3838,6 +3846,97 @@ void Builtins::Generate_WasmReturnPromiseOnSuspend(MacroAssembler* masm) {
void Builtins::Generate_WasmSuspend(MacroAssembler* masm) { void Builtins::Generate_WasmSuspend(MacroAssembler* masm) {
// Set up the stackframe. // Set up the stackframe.
__ EnterFrame(StackFrame::STACK_SWITCH); __ EnterFrame(StackFrame::STACK_SWITCH);
Register promise = rax;
Register suspender = rbx;
__ subq(rsp, Immediate(-(BuiltinWasmWrapperConstants::kGCScanSlotCountOffset -
TypedFrameConstants::kFixedFrameSizeFromFp)));
// TODO(thibaudm): Throw if any of the following holds:
// - caller is null
// - ActiveSuspender is undefined
// - 'suspender' is not the active suspender
// -------------------------------------------
// Save current state in active jump buffer.
// -------------------------------------------
Label resume;
Register continuation = rcx;
__ LoadRoot(continuation, RootIndex::kActiveContinuation);
Register jmpbuf = rdx;
__ LoadAnyTaggedField(
jmpbuf,
FieldOperand(continuation, WasmContinuationObject::kJmpbufOffset));
__ LoadExternalPointerField(
jmpbuf, FieldOperand(jmpbuf, Foreign::kForeignAddressOffset),
kForeignForeignAddressTag, r8);
FillJumpBuffer(masm, jmpbuf, &resume);
__ StoreTaggedSignedField(
FieldOperand(suspender, WasmSuspenderObject::kStateOffset),
Smi::FromInt(WasmSuspenderObject::Suspended));
jmpbuf = no_reg;
// live: [rax, rbx, rcx]
#ifdef DEBUG
// -------------------------------------------
// Check that the suspender's continuation is the active continuation.
// -------------------------------------------
// TODO(thibaudm): Once we add core stack-switching instructions, this check
// will not hold anymore: it's possible that the active continuation changed
// (due to an internal switch), so we have to update the suspender.
Register suspender_continuation = rdx;
__ LoadAnyTaggedField(
suspender_continuation,
FieldOperand(suspender, WasmSuspenderObject::kContinuationOffset));
__ cmpq(suspender_continuation, continuation);
Label ok;
__ j(equal, &ok);
__ Trap();
__ bind(&ok);
#endif
// -------------------------------------------
// Update roots.
// -------------------------------------------
Register caller = rcx;
__ LoadAnyTaggedField(
caller,
FieldOperand(suspender, WasmSuspenderObject::kContinuationOffset));
__ LoadAnyTaggedField(
caller, FieldOperand(caller, WasmContinuationObject::kParentOffset));
__ movq(masm->RootAsOperand(RootIndex::kActiveContinuation), caller);
Register parent = rdx;
__ LoadAnyTaggedField(
parent, FieldOperand(suspender, WasmSuspenderObject::kParentOffset));
__ movq(masm->RootAsOperand(RootIndex::kActiveSuspender), parent);
parent = no_reg;
// live: [rax, rcx]
// -------------------------------------------
// Load jump buffer.
// -------------------------------------------
MemOperand GCScanSlotPlace =
MemOperand(rbp, BuiltinWasmWrapperConstants::kGCScanSlotCountOffset);
__ Move(GCScanSlotPlace, 2);
__ Push(promise);
__ Push(caller);
__ Move(kContextRegister, Smi::zero());
__ CallRuntime(Runtime::kWasmSyncStackLimit);
__ Pop(caller);
__ Pop(promise);
jmpbuf = caller;
__ LoadAnyTaggedField(
jmpbuf, FieldOperand(caller, WasmContinuationObject::kJmpbufOffset));
caller = no_reg;
__ LoadExternalPointerField(
jmpbuf, FieldOperand(jmpbuf, Foreign::kForeignAddressOffset),
kForeignForeignAddressTag, r8);
__ movq(kReturnRegister0, promise);
__ Move(GCScanSlotPlace, 0);
LoadJumpBuffer(masm, jmpbuf, true);
__ Trap();
__ bind(&resume);
__ LeaveFrame(StackFrame::STACK_SWITCH); __ LeaveFrame(StackFrame::STACK_SWITCH);
__ ret(0); __ ret(0);
} }
......
...@@ -1791,8 +1791,9 @@ class WasmFloat64ToNumberDescriptor final ...@@ -1791,8 +1791,9 @@ class WasmFloat64ToNumberDescriptor final
class WasmSuspendDescriptor final class WasmSuspendDescriptor final
: public StaticCallInterfaceDescriptor<WasmSuspendDescriptor> { : public StaticCallInterfaceDescriptor<WasmSuspendDescriptor> {
public: public:
DEFINE_RESULT_AND_PARAMETERS_NO_CONTEXT(0, kArg) DEFINE_RESULT_AND_PARAMETERS_NO_CONTEXT(0, kArg0, kArg1)
DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::AnyTagged()) // value DEFINE_RESULT_AND_PARAMETER_TYPES(MachineType::AnyTagged(), // value
MachineType::AnyTagged()) // value
DECLARE_DESCRIPTOR(WasmSuspendDescriptor) DECLARE_DESCRIPTOR(WasmSuspendDescriptor)
}; };
......
...@@ -7056,8 +7056,12 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder { ...@@ -7056,8 +7056,12 @@ class WasmWrapperGraphBuilder : public WasmGraphBuilder {
Builtin::kWasmSuspend, zone_, StubCallMode::kCallWasmRuntimeStub); Builtin::kWasmSuspend, zone_, StubCallMode::kCallWasmRuntimeStub);
Node* call_target = mcgraph()->RelocatableIntPtrConstant( Node* call_target = mcgraph()->RelocatableIntPtrConstant(
wasm::WasmCode::kWasmSuspend, RelocInfo::WASM_STUB_CALL); wasm::WasmCode::kWasmSuspend, RelocInfo::WASM_STUB_CALL);
Node* suspender =
gasm_->Call(call_descriptor, call_target, call); gasm_->Load(MachineType::TaggedPointer(), Param(0),
wasm::ObjectAccess::ToTagged(
WasmApiFunctionRef::kSuspenderOffset));
// TODO(thibaudm): Create and pass the chained promise.
gasm_->Call(call_descriptor, call_target, call, suspender);
} }
break; break;
} }
......
...@@ -1884,6 +1884,8 @@ void WasmContinuationObject::WasmContinuationObjectPrint(std::ostream& os) { ...@@ -1884,6 +1884,8 @@ void WasmContinuationObject::WasmContinuationObjectPrint(std::ostream& os) {
void WasmSuspenderObject::WasmSuspenderObjectPrint(std::ostream& os) { void WasmSuspenderObject::WasmSuspenderObjectPrint(std::ostream& os) {
PrintHeader(os, "WasmSuspenderObject"); PrintHeader(os, "WasmSuspenderObject");
os << "\n - continuation: " << continuation(); os << "\n - continuation: " << continuation();
os << "\n - parent: " << parent();
os << "\n - state: " << state();
os << "\n"; os << "\n";
} }
......
...@@ -741,6 +741,7 @@ RUNTIME_FUNCTION(Runtime_WasmAllocateContinuation) { ...@@ -741,6 +741,7 @@ RUNTIME_FUNCTION(Runtime_WasmAllocateContinuation) {
.set_state(WasmSuspenderObject::Inactive); .set_state(WasmSuspenderObject::Inactive);
} }
suspender->set_state(WasmSuspenderObject::State::Active); suspender->set_state(WasmSuspenderObject::State::Active);
suspender->set_continuation(*target);
active_suspender_slot.store(*suspender); active_suspender_slot.store(*suspender);
SyncStackLimit(isolate); SyncStackLimit(isolate);
......
...@@ -21,12 +21,14 @@ namespace wasm { ...@@ -21,12 +21,14 @@ namespace wasm {
struct JumpBuffer { struct JumpBuffer {
Address sp; Address sp;
Address fp; Address fp;
Address pc;
void* stack_limit; void* stack_limit;
// TODO(thibaudm/fgm): Add general-purpose registers. // TODO(thibaudm/fgm): Add general-purpose registers.
}; };
constexpr int kJmpBufSpOffset = offsetof(JumpBuffer, sp); constexpr int kJmpBufSpOffset = offsetof(JumpBuffer, sp);
constexpr int kJmpBufFpOffset = offsetof(JumpBuffer, fp); constexpr int kJmpBufFpOffset = offsetof(JumpBuffer, fp);
constexpr int kJmpBufPcOffset = offsetof(JumpBuffer, pc);
constexpr int kJmpBufStackLimitOffset = offsetof(JumpBuffer, stack_limit); constexpr int kJmpBufStackLimitOffset = offsetof(JumpBuffer, stack_limit);
class StackMemory { class StackMemory {
......
...@@ -41,16 +41,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js"); ...@@ -41,16 +41,14 @@ load("test/mjsunit/wasm/wasm-module-builder.js");
let suspender = new WebAssembly.Suspender(); let suspender = new WebAssembly.Suspender();
function js_import() { function js_import() {
// TODO(thibaudm): Return the value as a promise. // TODO(thibaudm): Return the value as a promise.
return 42; return new Promise((resolve) => { resolve(42); });
}; };
let wasm_js_import = new WebAssembly.Function({parameters: [], results: ['i32']}, js_import); let wasm_js_import = new WebAssembly.Function({parameters: [], results: ['i32']}, js_import);
let suspending_wasm_js_import = suspender.suspendOnReturnedPromise(wasm_js_import); let suspending_wasm_js_import = suspender.suspendOnReturnedPromise(wasm_js_import);
let instance = builder.instantiate({m: {import: suspending_wasm_js_import}}); let instance = builder.instantiate({m: {import: suspending_wasm_js_import}});
let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test); let wrapped_export = suspender.returnPromiseOnSuspend(instance.exports.test);
let combined_promise = wrapped_export(); let combined_promise = wrapped_export();
// TODO(thibaudm): Once we generate the actual wrapper, we expect 0 here assertEquals(0, instance.exports.g.value);
// The global will only be set after the promise resolves.
assertEquals(42, instance.exports.g.value);
})(); })();
(function TestStackSwitchGC() { (function TestStackSwitchGC() {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment