Commit 4e329f8c authored by Andreas Haas's avatar Andreas Haas Committed by V8 LUCI CQ

Reland: [wasm] WasmCompileLazyFrame scanning

The original CL did not handle the case where a GC gets triggered by
the allocation of the error object when compilation fails.

Orignal message:

Feedback vector allocation can trigger a GC, and thereby make the
WasmCompileLazyFrame visible for the GC. This CL add stack scanning
for the WasmCompileLazyFrame.

Design doc: http://doc/1peovM6N6C4nSEdC77l4uxU1L0njA0RTaOjy5F12r2CQ

Change-Id: I9be66c696e27f9ecf8228daf40ad6258f0e963d1

Bug: v8:12852
Fix: v8:13133
Change-Id: I9be66c696e27f9ecf8228daf40ad6258f0e963d1
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3804599Reviewed-by: 's avatarJakob Kummerow <jkummerow@chromium.org>
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/main@{#82138}
parent 0669c5bf
......@@ -2688,7 +2688,7 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
CHECK_EQ(highest_fp_reg.code() - lowest_fp_reg.code() + 1,
arraysize(wasm::kFpParamRegisters));
CHECK_EQ(gp_regs.Count(),
WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs);
WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs + 1);
CHECK_EQ(highest_fp_reg.code() - lowest_fp_reg.code() + 1,
WasmCompileLazyFrameConstants::kNumberOfSavedFpParamRegs);
......@@ -2699,10 +2699,13 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ push(kWasmInstanceRegister);
// Push the function index as second argument.
__ push(kWasmCompileLazyFuncIndexRegister);
// Allocate a stack slot for the NativeModule, the pushed value does not
// matter.
__ push(r8);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ Move(cp, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
__ CallRuntime(Runtime::kWasmCompileLazy, 3);
// The runtime function returns the jump table slot offset as a Smi. Use
// that to compute the jump target in r8.
__ mov(r8, Operand::SmiUntag(kReturnRegister0));
......
......@@ -3075,7 +3075,8 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
CHECK_EQ(0, saved_gp_regs.Count() % 2);
// The Wasm instance must be part of the saved registers.
CHECK(saved_gp_regs.has(kWasmInstanceRegister));
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs,
// + instance + alignment
CHECK_EQ(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs + 2,
saved_gp_regs.Count());
return saved_gp_regs;
})();
......@@ -3102,13 +3103,14 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ PushXRegList(kSavedGpRegs);
__ PushQRegList(kSavedFpRegs);
// Pass instance and function index as explicit arguments to the runtime
// function.
__ Push(kWasmInstanceRegister, kWasmCompileLazyFuncIndexRegister);
// Pass instance, function index, and an additional stack slot for the
// native module, as explicit arguments to the runtime function. The first
// pushed register is for alignment. {x0} and {x1} are picked arbitrarily.
__ Push(x0, kWasmInstanceRegister, kWasmCompileLazyFuncIndexRegister, x1);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ Mov(cp, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
__ CallRuntime(Runtime::kWasmCompileLazy, 3);
// Untag the returned Smi into into x17 (ip1), for later use.
static_assert(!kSavedGpRegs.has(x17));
......
......@@ -2915,9 +2915,10 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.h). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
static_assert(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs ==
arraysize(wasm::kGpParamRegisters),
"frame size mismatch");
static_assert(
WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs + 1 ==
arraysize(wasm::kGpParamRegisters),
"frame size mismatch");
for (Register reg : wasm::kGpParamRegisters) {
__ Push(reg);
}
......@@ -2935,10 +2936,13 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Push(kWasmInstanceRegister);
// Push the function index as second argument.
__ Push(kWasmCompileLazyFuncIndexRegister);
// Allocate a stack slot, where the runtime function can spill a pointer to
// the the NativeModule.
__ Push(esp);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ Move(kContextRegister, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
__ CallRuntime(Runtime::kWasmCompileLazy, 3);
// The runtime function returns the jump table slot offset as a Smi. Use
// that to compute the jump target in edi.
__ SmiUntag(kReturnRegister0);
......
......@@ -2838,9 +2838,10 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
// Save all parameter registers (see wasm-linkage.h). They might be
// overwritten in the runtime call below. We don't have any callee-saved
// registers in wasm, so no need to store anything else.
static_assert(WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs ==
arraysize(wasm::kGpParamRegisters),
"frame size mismatch");
static_assert(
WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs + 1 ==
arraysize(wasm::kGpParamRegisters),
"frame size mismatch");
for (Register reg : wasm::kGpParamRegisters) {
__ Push(reg);
}
......@@ -2858,10 +2859,14 @@ void Builtins::Generate_WasmCompileLazy(MacroAssembler* masm) {
__ Push(kWasmInstanceRegister);
// Push the function index as second argument.
__ Push(r15);
// Allocate a stack slot, where the runtime function can spill a pointer to
// the the NativeModule.
__ Push(rsp);
// Initialize the JavaScript context with 0. CEntry will use it to
// set the current context on the isolate.
__ Move(kContextRegister, Smi::zero());
__ CallRuntime(Runtime::kWasmCompileLazy, 2);
__ CallRuntime(Runtime::kWasmCompileLazy, 3);
// The runtime function returns the jump table slot offset as a Smi. Use
// that to compute the jump target in r15.
__ SmiUntag(kReturnRegister0);
......
......@@ -57,18 +57,26 @@ class EntryFrameConstants : public AllStatic {
class WasmCompileLazyFrameConstants : public TypedFrameConstants {
public:
static constexpr int kNumberOfSavedGpParamRegs = 4;
// Number of gp parameters, without the instance.
static constexpr int kNumberOfSavedGpParamRegs = 3;
static constexpr int kNumberOfSavedFpParamRegs = 8;
// FP-relative.
// The instance is pushed as part of the saved registers. Being in {r3}, it is
// at position 1 in the list [r0, r2, r3, r6] (kGpParamRegisters sorted by
// number and indexed zero-based from the back).
static constexpr int kWasmInstanceOffset = TYPED_FRAME_PUSHED_VALUE_OFFSET(1);
static constexpr int kFixedFrameSizeFromFp =
TypedFrameConstants::kFixedFrameSizeFromFp +
kNumberOfSavedGpParamRegs * kPointerSize +
kNumberOfSavedFpParamRegs * kDoubleSize;
// On arm, spilled registers are implicitly sorted backwards by number.
// We spill:
// r3: param0 = instance
// r0, r2, r6: param1, param2, param3
// in the following FP-relative order: [r6, r3, r2, r0].
static constexpr int kInstanceSpillOffset =
TYPED_FRAME_PUSHED_VALUE_OFFSET(1);
static constexpr int kParameterSpillsOffset[] = {
TYPED_FRAME_PUSHED_VALUE_OFFSET(3), TYPED_FRAME_PUSHED_VALUE_OFFSET(2),
TYPED_FRAME_PUSHED_VALUE_OFFSET(0)};
// SP-relative.
static constexpr int kWasmInstanceOffset = 2 * kSystemPointerSize;
static constexpr int kFunctionIndexOffset = 1 * kSystemPointerSize;
static constexpr int kNativeModuleOffset = 0;
};
// Frame constructed by the {WasmDebugBreak} builtin.
......
......@@ -74,20 +74,29 @@ class EntryFrameConstants : public AllStatic {
class WasmCompileLazyFrameConstants : public TypedFrameConstants {
public:
static constexpr int kNumberOfSavedGpParamRegs = 8;
// Number of gp parameters, without the instance.
static constexpr int kNumberOfSavedGpParamRegs = 6;
static constexpr int kNumberOfSavedFpParamRegs = 8;
// FP-relative.
// The instance is pushed as part of the saved registers. Being in {r7}, it is
// the first register pushed (highest register code in
// {wasm::kGpParamRegisters}). Because of padding of the frame header, it is
// actually one word further down the stack though (thus at position {1}).
static constexpr int kWasmInstanceOffset = TYPED_FRAME_PUSHED_VALUE_OFFSET(1);
static constexpr int kFixedFrameSizeFromFp =
// Header is padded to 16 byte (see {MacroAssembler::EnterFrame}).
RoundUp<16>(TypedFrameConstants::kFixedFrameSizeFromFp) +
kNumberOfSavedGpParamRegs * kSystemPointerSize +
kNumberOfSavedFpParamRegs * kSimd128Size;
// On arm, spilled registers are implicitly sorted backwards by number.
// We spill:
// x7: param0 = instance
// x0, x2, x3, x4, x5, x6: param1, param2, ..., param6
// x1: for alignment
// in the following FP-relative order: [x7, x6, x5, x4, x3, x2, x1, x0].
// For frame alignment, the first spill slot is at position '1', not at '0'.
static constexpr int kInstanceSpillOffset =
TYPED_FRAME_PUSHED_VALUE_OFFSET(1);
static constexpr int kParameterSpillsOffset[] = {
TYPED_FRAME_PUSHED_VALUE_OFFSET(8), TYPED_FRAME_PUSHED_VALUE_OFFSET(6),
TYPED_FRAME_PUSHED_VALUE_OFFSET(5), TYPED_FRAME_PUSHED_VALUE_OFFSET(4),
TYPED_FRAME_PUSHED_VALUE_OFFSET(3), TYPED_FRAME_PUSHED_VALUE_OFFSET(2)};
// SP-relative.
static constexpr int kWasmInstanceOffset = 2 * kSystemPointerSize;
static constexpr int kFunctionIndexOffset = 1 * kSystemPointerSize;
static constexpr int kNativeModuleOffset = 0;
};
// Frame constructed by the {WasmDebugBreak} builtin.
......
......@@ -2577,21 +2577,96 @@ void StackSwitchFrame::GetStateForJumpBuffer(wasm::JumpBuffer* jmpbuf,
DCHECK_NE(*state->pc_address, kNullAddress);
}
WasmInstanceObject WasmCompileLazyFrame::wasm_instance() const {
return WasmInstanceObject::cast(*wasm_instance_slot());
int WasmCompileLazyFrame::GetFunctionIndex() const {
Object func_index(Memory<Address>(
sp() + WasmCompileLazyFrameConstants::kFunctionIndexOffset));
return Smi::ToInt(func_index);
}
wasm::NativeModule* WasmCompileLazyFrame::GetNativeModule() const {
return *reinterpret_cast<wasm::NativeModule**>(
sp() + WasmCompileLazyFrameConstants::kNativeModuleOffset);
}
FullObjectSlot WasmCompileLazyFrame::wasm_instance_slot() const {
const int offset = WasmCompileLazyFrameConstants::kWasmInstanceOffset;
return FullObjectSlot(&Memory<Address>(fp() + offset));
return FullObjectSlot(&Memory<Address>(
sp() + WasmCompileLazyFrameConstants::kWasmInstanceOffset));
}
void WasmCompileLazyFrame::Iterate(RootVisitor* v) const {
const int header_size = WasmCompileLazyFrameConstants::kFixedFrameSizeFromFp;
FullObjectSlot base(&Memory<Address>(sp()));
FullObjectSlot limit(&Memory<Address>(fp() - header_size));
v->VisitRootPointers(Root::kStackRoots, nullptr, base, limit);
v->VisitRootPointer(Root::kStackRoots, nullptr, wasm_instance_slot());
FullObjectSlot spilled_instance_slot(&Memory<Address>(
fp() + WasmCompileLazyFrameConstants::kInstanceSpillOffset));
v->VisitRootPointer(Root::kStackRoots, "spilled wasm instance",
spilled_instance_slot);
v->VisitRootPointer(Root::kStackRoots, "wasm instance parameter",
wasm_instance_slot());
int func_index = GetFunctionIndex();
wasm::NativeModule* native_module = GetNativeModule();
if (!native_module) {
// This GC was triggered by lazy compilation, because otherwise this frame
// would not be on the stack. The native module gets set on the stack after
// a successful compilation. The native module being nullptr means that
// compilation failed, and we don't have to preserve any references because
// the stack will get unwound immediately after the GC.
return;
}
// Scan the spill slots of the parameter registers. Parameters in WebAssembly
// get reordered such that first all value parameters get put into registers.
// If there are more registers than value parameters, the remaining registers
// are used for reference parameters. Therefore we can determine which
// registers get used for which parameters by counting the number of value
// parameters and the number of reference parameters.
int num_int_params = 0;
int num_ref_params = 0;
const wasm::FunctionSig* sig =
native_module->module()->functions[func_index].sig;
for (auto param : sig->parameters()) {
if (param == wasm::kWasmI32) {
num_int_params++;
} else if (param == wasm::kWasmI64) {
num_int_params += kSystemPointerSize == 8 ? 1 : 2;
} else if (param.is_reference()) {
num_ref_params++;
}
}
// There are no reference parameters, there is nothing to scan.
if (num_ref_params == 0) return;
int num_int_params_in_registers = std::min(
num_int_params, WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs);
int num_ref_params_in_registers = std::min(
num_ref_params, WasmCompileLazyFrameConstants::kNumberOfSavedGpParamRegs -
num_int_params_in_registers);
for (int i = 0; i < num_ref_params_in_registers; ++i) {
FullObjectSlot spill_slot(
fp() + WasmCompileLazyFrameConstants::kParameterSpillsOffset
[num_int_params_in_registers + i]);
v->VisitRootPointer(Root::kStackRoots, "register parameter", spill_slot);
}
// Next we scan the slots of stack parameters.
wasm::WasmCode* wasm_code = native_module->GetCode(func_index);
uint32_t first_tagged_stack_slot = wasm_code->first_tagged_parameter_slot();
uint32_t num_tagged_stack_slots = wasm_code->num_tagged_parameter_slots();
// Visit tagged parameters that have been passed to the function of this
// frame. Conceptionally these parameters belong to the parent frame. However,
// the exact count is only known by this frame (in the presence of tail calls,
// this information cannot be derived from the call site).
if (num_tagged_stack_slots > 0) {
FullObjectSlot tagged_parameter_base(&Memory<Address>(caller_sp()));
tagged_parameter_base += first_tagged_stack_slot;
FullObjectSlot tagged_parameter_limit =
tagged_parameter_base + num_tagged_stack_slots;
v->VisitRootPointers(Root::kStackRoots, "stack parameter",
tagged_parameter_base, tagged_parameter_limit);
}
}
#endif // V8_ENABLE_WEBASSEMBLY
......
......@@ -1113,9 +1113,12 @@ class WasmCompileLazyFrame : public TypedFrame {
public:
Type type() const override { return WASM_COMPILE_LAZY; }
WasmInstanceObject wasm_instance() const;
FullObjectSlot wasm_instance_slot() const;
int GetFunctionIndex() const;
wasm::NativeModule* GetNativeModule() const;
// Garbage collection support.
void Iterate(RootVisitor* v) const override;
......
......@@ -36,15 +36,21 @@ class EntryFrameConstants : public AllStatic {
class WasmCompileLazyFrameConstants : public TypedFrameConstants {
public:
static constexpr int kNumberOfSavedGpParamRegs = 4;
// Number of gp parameters, without the instance.
static constexpr int kNumberOfSavedGpParamRegs = 3;
static constexpr int kNumberOfSavedFpParamRegs = 6;
// FP-relative.
static constexpr int kWasmInstanceOffset = TYPED_FRAME_PUSHED_VALUE_OFFSET(0);
static constexpr int kFixedFrameSizeFromFp =
TypedFrameConstants::kFixedFrameSizeFromFp +
kNumberOfSavedGpParamRegs * kSystemPointerSize +
kNumberOfSavedFpParamRegs * kSimd128Size;
static constexpr int kInstanceSpillOffset =
TYPED_FRAME_PUSHED_VALUE_OFFSET(0);
static constexpr int kParameterSpillsOffset[] = {
TYPED_FRAME_PUSHED_VALUE_OFFSET(1), TYPED_FRAME_PUSHED_VALUE_OFFSET(2),
TYPED_FRAME_PUSHED_VALUE_OFFSET(3)};
// SP-relative.
static constexpr int kWasmInstanceOffset = 2 * kSystemPointerSize;
static constexpr int kFunctionIndexOffset = 1 * kSystemPointerSize;
static constexpr int kNativeModuleOffset = 0;
};
// Frame constructed by the {WasmDebugBreak} builtin.
......
......@@ -45,15 +45,22 @@ class EntryFrameConstants : public AllStatic {
class WasmCompileLazyFrameConstants : public TypedFrameConstants {
public:
static constexpr int kNumberOfSavedGpParamRegs = 6;
// Number of gp parameters, without the instance.
static constexpr int kNumberOfSavedGpParamRegs = 5;
static constexpr int kNumberOfSavedFpParamRegs = 6;
// FP-relative.
static constexpr int kWasmInstanceOffset = TYPED_FRAME_PUSHED_VALUE_OFFSET(0);
static constexpr int kFixedFrameSizeFromFp =
TypedFrameConstants::kFixedFrameSizeFromFp +
kNumberOfSavedGpParamRegs * kSystemPointerSize +
kNumberOfSavedFpParamRegs * kSimd128Size;
static constexpr int kInstanceSpillOffset =
TYPED_FRAME_PUSHED_VALUE_OFFSET(0);
static constexpr int kParameterSpillsOffset[] = {
TYPED_FRAME_PUSHED_VALUE_OFFSET(1), TYPED_FRAME_PUSHED_VALUE_OFFSET(2),
TYPED_FRAME_PUSHED_VALUE_OFFSET(3), TYPED_FRAME_PUSHED_VALUE_OFFSET(4),
TYPED_FRAME_PUSHED_VALUE_OFFSET(5)};
// SP-relative.
static constexpr int kWasmInstanceOffset = 2 * kSystemPointerSize;
static constexpr int kFunctionIndexOffset = 1 * kSystemPointerSize;
static constexpr int kNativeModuleOffset = 0;
};
// Frame constructed by the {WasmDebugBreak} builtin.
......
......@@ -219,35 +219,22 @@ RUNTIME_FUNCTION(Runtime_WasmStackGuard) {
}
RUNTIME_FUNCTION(Runtime_WasmCompileLazy) {
// The parameters of the called function we are going to compile have been
// spilled on the stack. Some of these parameters may be references. As we
// don't know which parameters are references, we have to make sure that no GC
// is triggered during the compilation of the function.
base::Optional<DisallowGarbageCollection> no_gc(base::in_place);
ClearThreadInWasmScope wasm_flag(isolate);
HandleScope scope(isolate);
DCHECK_EQ(2, args.length());
// TODO(jkummerow): This can be an unhandlified object once
// {wasm::CompileLazy()} no longer allocates even when speculative inlining
// is enabled.
DCHECK_EQ(3, args.length());
Handle<WasmInstanceObject> instance(WasmInstanceObject::cast(args[0]),
isolate);
int func_index = args.smi_value_at(1);
#ifdef DEBUG
FrameFinder<WasmCompileLazyFrame> frame_finder(isolate);
DCHECK_EQ(*instance, frame_finder.frame()->wasm_instance());
#endif
wasm::NativeModule** native_module_stack_slot =
reinterpret_cast<wasm::NativeModule**>(args.address_of_arg_at(2));
*native_module_stack_slot = nullptr;
DCHECK(isolate->context().is_null());
isolate->set_context(instance->native_context());
bool success = wasm::CompileLazy(isolate, instance, func_index);
bool success = wasm::CompileLazy(isolate, instance, func_index,
native_module_stack_slot);
if (!success) {
{
// Compilation of function failed. We have to allocate the exception
// object. This allocation may trigger a GC, but that's okay, because the
// parameters on the stack will not be used anymore anyways.
no_gc.reset();
wasm::ThrowLazyCompilationError(
isolate, instance->module_object().native_module(), func_index);
}
......
......@@ -603,7 +603,7 @@ namespace internal {
F(WasmTableGrow, 3, 1) \
F(WasmTableFill, 5, 1) \
F(WasmIsValidRefValue, 3, 1) \
F(WasmCompileLazy, 2, 1) \
F(WasmCompileLazy, 3, 1) \
F(WasmCompileWrapper, 2, 1) \
F(WasmTriggerTierUp, 1, 1) \
F(WasmDebugBreak, 0, 1) \
......
......@@ -1135,7 +1135,7 @@ bool IsLazyModule(const WasmModule* module) {
} // namespace
bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
int func_index) {
int func_index, NativeModule** out_native_module) {
Handle<WasmModuleObject> module_object(instance->module_object(), isolate);
NativeModule* native_module = module_object->native_module();
const WasmModule* module = native_module->module();
......@@ -1189,15 +1189,6 @@ bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
return false;
}
// Allocate feedback vector if needed.
if (result.feedback_vector_slots > 0) {
DCHECK(FLAG_wasm_speculative_inlining);
Handle<FixedArray> vector = isolate->factory()->NewFixedArrayWithZeroes(
result.feedback_vector_slots);
instance->feedback_vectors().set(
declared_function_index(module, func_index), *vector);
}
WasmCodeRefScope code_ref_scope;
WasmCode* code;
{
......@@ -1226,6 +1217,17 @@ bool CompileLazy(Isolate* isolate, Handle<WasmInstanceObject> instance,
compilation_state->CommitTopTierCompilationUnit(tiering_unit);
}
// Allocate feedback vector if needed.
if (result.feedback_vector_slots > 0) {
DCHECK(FLAG_wasm_speculative_inlining);
// We have to save the native_module on the stack, in case the allocation
// triggers a GC and we need the module to scan WasmCompileLazy stack frame.
*out_native_module = native_module;
Handle<FixedArray> vector = isolate->factory()->NewFixedArrayWithZeroes(
result.feedback_vector_slots);
instance->feedback_vectors().set(
declared_function_index(module, func_index), *vector);
}
return true;
}
......
......@@ -80,7 +80,8 @@ WasmCode* CompileImportWrapper(
// Triggered by the WasmCompileLazy builtin. The return value indicates whether
// compilation was successful. Lazy compilation can fail only if validation is
// also lazy.
bool CompileLazy(Isolate*, Handle<WasmInstanceObject>, int func_index);
bool CompileLazy(Isolate*, Handle<WasmInstanceObject>, int func_index,
NativeModule** out_native_module);
// Throws the compilation error after failed lazy compilation.
void ThrowLazyCompilationError(Isolate* isolate,
......
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