[wasm] Allow externref parameters in the generic wrapper

With this CL, externref parameters are supported by the generic wrapper.
Externref parameters get handled in a separate loop which runs after the
loop which converts primitive type parameters from JavaScript values to
WebAssembly values. Externref parameters get handled separately because
the conversion of primitive type parameters may cause a GC, and it would
be hard for the GC to identify stack slots which contain reference
parameters which have already been processed.

As an optimization we remember in the first loop if we have seen a
reference parameter. For functions without a reference parameter we
would not iterate the parameters for a second time.

R=thibaudm@chromium.org

Bug: v8:12565
Change-Id: Ib36bee9d8e6b1606250fcd5f2e9cdbbdfed96356
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3412079
Reviewed-by: Thibaud Michaud <thibaudm@chromium.org>
Commit-Queue: Andreas Haas <ahaas@chromium.org>
Cr-Commit-Position: refs/heads/main@{#78814}
This commit is contained in:
Andreas Haas 2022-01-27 14:30:03 +01:00 committed by V8 LUCI CQ
parent 9566a6e3f4
commit 125740ab4c
3 changed files with 93 additions and 6 deletions

View File

@ -2927,12 +2927,16 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
constexpr int kReturnCountOffset = kParamCountOffset - kSystemPointerSize; constexpr int kReturnCountOffset = kParamCountOffset - kSystemPointerSize;
constexpr int kValueTypesArrayStartOffset = constexpr int kValueTypesArrayStartOffset =
kReturnCountOffset - kSystemPointerSize; kReturnCountOffset - kSystemPointerSize;
// A boolean flag to check if one of the parameters is a reference. If so, we
// iterate over the parameters two times, first for all value types, and then
// for all references.
constexpr int kHasRefTypesOffset =
kValueTypesArrayStartOffset - kSystemPointerSize;
// We set and use this slot only when moving parameters into the parameter // We set and use this slot only when moving parameters into the parameter
// registers (so no GC scan is needed). // registers (so no GC scan is needed).
constexpr int kFunctionDataOffset = constexpr int kFunctionDataOffset = kHasRefTypesOffset - kSystemPointerSize;
kValueTypesArrayStartOffset - kSystemPointerSize;
constexpr int kLastSpillOffset = kFunctionDataOffset; constexpr int kLastSpillOffset = kFunctionDataOffset;
constexpr int kNumSpillSlots = 6; constexpr int kNumSpillSlots = 7;
__ subq(rsp, Immediate(kNumSpillSlots * kSystemPointerSize)); __ subq(rsp, Immediate(kNumSpillSlots * kSystemPointerSize));
// Put the in_parameter count on the stack, we only need it at the very end // Put the in_parameter count on the stack, we only need it at the very end
// when we pop the parameters off the stack. // when we pop the parameters off the stack.
@ -2941,6 +2945,9 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
__ movq(MemOperand(rbp, kInParamCountOffset), in_param_count); __ movq(MemOperand(rbp, kInParamCountOffset), in_param_count);
in_param_count = no_reg; in_param_count = no_reg;
// Initialize the {HasRefTypes} slot.
__ movq(MemOperand(rbp, kHasRefTypesOffset), Immediate(0));
// ------------------------------------------- // -------------------------------------------
// Load the Wasm exported function data and the Wasm instance. // Load the Wasm exported function data and the Wasm instance.
// ------------------------------------------- // -------------------------------------------
@ -3172,6 +3179,64 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
__ cmpq(current_param, param_limit); __ cmpq(current_param, param_limit);
__ j(not_equal, &loop_through_params); __ j(not_equal, &loop_through_params);
// -------------------------------------------
// Second loop to handle references.
// -------------------------------------------
// In this loop we iterate over all parameters for a second time and copy all
// reference parameters at the end of the integer parameters section.
Label ref_params_done;
// We check if we have seen a reference in the first parameter loop.
__ cmpq(MemOperand(rbp, kHasRefTypesOffset), Immediate(0));
__ j(equal, &ref_params_done);
// We re-calculate the beginning of the value-types array and the beginning of
// the parameters ({valuetypes_array_ptr} and {current_param}).
__ movq(valuetypes_array_ptr, MemOperand(rbp, kValueTypesArrayStartOffset));
return_count = current_param;
current_param = no_reg;
__ movq(return_count, MemOperand(rbp, kReturnCountOffset));
returns_size = return_count;
return_count = no_reg;
__ shlq(returns_size, Immediate(kValueTypeSizeLog2));
__ addq(valuetypes_array_ptr, returns_size);
current_param = returns_size;
returns_size = no_reg;
__ Move(current_param,
kFPOnStackSize + kPCOnStackSize + kReceiverOnStackSize);
Label ref_loop_through_params;
Label ref_loop_end;
// Start of the loop.
__ bind(&ref_loop_through_params);
// Load the current parameter with type.
__ movq(param, MemOperand(rbp, current_param, times_1, 0));
__ movl(valuetype,
Operand(valuetypes_array_ptr, wasm::ValueType::bit_field_offset()));
// Extract the ValueKind of the type, to check for kRef and kOptRef.
__ andl(valuetype, Immediate(wasm::kWasmValueKindBitsMask));
Label move_ref_to_slot;
__ cmpq(valuetype, Immediate(wasm::ValueKind::kOptRef));
__ j(equal, &move_ref_to_slot);
__ cmpq(valuetype, Immediate(wasm::ValueKind::kRef));
__ j(equal, &move_ref_to_slot);
__ jmp(&ref_loop_end);
// Place the param into the proper slot in Integer section.
__ bind(&move_ref_to_slot);
__ movq(MemOperand(current_int_param_slot, 0), param);
__ subq(current_int_param_slot, Immediate(kSystemPointerSize));
// Move to the next parameter.
__ bind(&ref_loop_end);
__ addq(current_param, Immediate(increment));
__ addq(valuetypes_array_ptr, Immediate(kValueTypeSize));
// Check if we finished all parameters.
__ cmpq(current_param, param_limit);
__ j(not_equal, &ref_loop_through_params);
__ bind(&ref_params_done);
// ------------------------------------------- // -------------------------------------------
// Move the parameters into the proper param registers. // Move the parameters into the proper param registers.
// ------------------------------------------- // -------------------------------------------
@ -3283,7 +3348,8 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
__ cmpq(valuetype, Immediate(wasm::kWasmF64.raw_bit_field())); __ cmpq(valuetype, Immediate(wasm::kWasmF64.raw_bit_field()));
__ j(equal, &place_float_param); __ j(equal, &place_float_param);
__ int3(); // All other types are reference types. We can just fall through to place them
// in the integer section.
__ bind(&place_integer_param); __ bind(&place_integer_param);
__ cmpq(start_int_section, current_int_param_slot); __ cmpq(start_int_section, current_int_param_slot);
@ -3446,6 +3512,21 @@ void Builtins::Generate_GenericJSToWasmWrapper(MacroAssembler* masm) {
__ cmpq(valuetype, Immediate(wasm::kWasmF64.raw_bit_field())); __ cmpq(valuetype, Immediate(wasm::kWasmF64.raw_bit_field()));
__ j(equal, &param_kWasmF64); __ j(equal, &param_kWasmF64);
// The parameter is a reference. We do not convert the parameter immediately.
// Instead we will later loop over all parameters again to handle reference
// parameters. The reason is that later value type parameters may trigger a
// GC, and we cannot keep reference parameters alive then. Instead we leave
// reference parameters at their initial place on the stack and only copy them
// once no GC can happen anymore.
// As an optimization we set a flag here that indicates that we have seen a
// reference so far. If there was no reference parameter, we would not iterate
// over the parameters for a second time.
__ movq(MemOperand(rbp, kHasRefTypesOffset), Immediate(1));
RestoreAfterBuiltinCall(masm, function_data, wasm_instance,
valuetypes_array_ptr, current_float_param_slot,
current_int_param_slot, param_limit, current_param);
__ jmp(&param_conversion_done);
__ int3(); __ int3();
__ bind(&param_kWasmI32_not_smi); __ bind(&param_kWasmI32_not_smi);

View File

@ -187,7 +187,9 @@ bool UseGenericWrapper(const FunctionSig* sig) {
} }
for (ValueType type : sig->parameters()) { for (ValueType type : sig->parameters()) {
if (type.kind() != kI32 && type.kind() != kI64 && type.kind() != kF32 && if (type.kind() != kI32 && type.kind() != kI64 && type.kind() != kF32 &&
type.kind() != kF64) { type.kind() != kF64 &&
!(type.is_reference() &&
type.heap_representation() == wasm::HeapType::kExtern)) {
return false; return false;
} }
} }

View File

@ -549,12 +549,13 @@ class ValueType {
return buf.str(); return buf.str();
} }
private:
// We only use 31 bits so ValueType fits in a Smi. This can be changed if // We only use 31 bits so ValueType fits in a Smi. This can be changed if
// needed. // needed.
static constexpr int kKindBits = 5; static constexpr int kKindBits = 5;
static constexpr int kHeapTypeBits = 20; static constexpr int kHeapTypeBits = 20;
static constexpr int kDepthBits = 6; static constexpr int kDepthBits = 6;
private:
STATIC_ASSERT(kV8MaxWasmTypes < (1u << kHeapTypeBits)); STATIC_ASSERT(kV8MaxWasmTypes < (1u << kHeapTypeBits));
// Note: we currently conservatively allow only 5 bits, but have room to // Note: we currently conservatively allow only 5 bits, but have room to
// store 6, so we can raise the limit if needed. // store 6, so we can raise the limit if needed.
@ -611,6 +612,9 @@ constexpr ValueType kWasmArrayRef =
ValueType::Ref(HeapType::kArray, kNonNullable); ValueType::Ref(HeapType::kArray, kNonNullable);
constexpr ValueType kWasmAnyRef = ValueType::Ref(HeapType::kAny, kNullable); constexpr ValueType kWasmAnyRef = ValueType::Ref(HeapType::kAny, kNullable);
// Constants used by the generic js-to-wasm wrapper.
constexpr int kWasmValueKindBitsMask = (1u << ValueType::kKindBits) - 1;
// This is used in wasm.tq. // This is used in wasm.tq.
constexpr ValueType kWasmExternNonNullableRef = constexpr ValueType kWasmExternNonNullableRef =
ValueType::Ref(HeapType::kExtern, kNonNullable); ValueType::Ref(HeapType::kExtern, kNonNullable);