From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Thibaud Michaud Date: Fri, 21 Jun 2024 16:31:15 +0200 Subject: Fix scanning of wasm-to-js params Wasm-to-js wrappers are sometimes compiled as on-heap Code objects, for example when tiering-up from a WasmFuncRef call origin. The frames of these functions are mapped to a subclass of TypedFrame, however TypedFrame::Iterate() only supports iterating the generic wasm-to-js wrapper. Add support for iterating the tagged parameters of optimized wasm-to-js wrappers in TypedFrame::Iterate. For this we also add two 16-bit fields in the Code object to encode the incoming tagged parameter region, which we would normally find in the WasmCode data. R=jkummerow@chromium.org Fixed: 346597059 Change-Id: I425619fca86c38f91f1ca9cbeb70e7b5a7b2d6c1 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/5639725 Reviewed-by: Jakob Kummerow Commit-Queue: Thibaud Michaud Cr-Commit-Position: refs/heads/main@{#94589} diff --git a/src/compiler/pipeline.cc b/src/compiler/pipeline.cc index 0181588337df73bfa97220d895733c40b92bd40b..033469e626ffb35846ae5114632f3dc000400935 100644 --- a/src/compiler/pipeline.cc +++ b/src/compiler/pipeline.cc @@ -2295,6 +2295,14 @@ CompilationJob::Status FinalizeWrapperCompilation( Handle::cast(code), info->GetDebugName().get())); } + // Set the wasm-to-js specific code fields needed to scan the incoming stack + // parameters. + if (code->kind() == CodeKind::WASM_TO_JS_FUNCTION) { + code->set_wasm_js_tagged_parameter_count( + call_descriptor->GetTaggedParameterSlots() & 0xffff); + code->set_wasm_js_first_tagged_parameter( + call_descriptor->GetTaggedParameterSlots() >> 16); + } return CompilationJob::SUCCEEDED; } return CompilationJob::FAILED; diff --git a/src/diagnostics/objects-printer.cc b/src/diagnostics/objects-printer.cc index 7677022ce31c1b8ead0cc2eb37fb505b750639be..12bd5b8fd27f67c73938550acc4af1857eace59a 100644 --- a/src/diagnostics/objects-printer.cc +++ b/src/diagnostics/objects-printer.cc @@ -2102,7 +2102,14 @@ void Code::CodePrint(std::ostream& os, const char* name, Address current_pc) { os << "\n - instruction_size: " << instruction_size(); os << "\n - metadata_size: " << metadata_size(); - os << "\n - inlined_bytecode_size: " << inlined_bytecode_size(); + if (kind() != CodeKind::WASM_TO_JS_FUNCTION) { + os << "\n - inlined_bytecode_size: " << inlined_bytecode_size(); + } else { + os << "\n - wasm_js_tagged_parameter_count: " + << wasm_js_tagged_parameter_count(); + os << "\n - wasm_js_first_tagged_parameter: " + << wasm_js_first_tagged_parameter(); + } os << "\n - osr_offset: " << osr_offset(); os << "\n - handler_table_offset: " << handler_table_offset(); os << "\n - unwinding_info_offset: " << unwinding_info_offset(); diff --git a/src/execution/frames.cc b/src/execution/frames.cc index 92dff2f7e8c8f72e38eef4feb5b10ace9fe2535c..2693fb2a859dc7489ef802c3064beace88608415 100644 --- a/src/execution/frames.cc +++ b/src/execution/frames.cc @@ -1562,7 +1562,7 @@ void WasmFrame::Iterate(RootVisitor* v) const { frame_header_limit); } -void TypedFrame::IterateParamsOfWasmToJSWrapper(RootVisitor* v) const { +void TypedFrame::IterateParamsOfGenericWasmToJSWrapper(RootVisitor* v) const { Tagged maybe_signature = Tagged( Memory
(fp() + WasmToJSWrapperConstants::kSignatureOffset)); if (IsSmi(maybe_signature)) { @@ -1678,6 +1678,18 @@ void TypedFrame::IterateParamsOfWasmToJSWrapper(RootVisitor* v) const { } } } + +void TypedFrame::IterateParamsOfOptimizedWasmToJSWrapper(RootVisitor* v) const { + Tagged code = GcSafeLookupCode(); + if (code->wasm_js_tagged_parameter_count() > 0) { + FullObjectSlot tagged_parameter_base(&Memory
(caller_sp())); + tagged_parameter_base += code->wasm_js_first_tagged_parameter(); + FullObjectSlot tagged_parameter_limit = + tagged_parameter_base + code->wasm_js_tagged_parameter_count(); + v->VisitRootPointers(Root::kStackRoots, nullptr, tagged_parameter_base, + tagged_parameter_limit); + } +} #endif // V8_ENABLE_WEBASSEMBLY void TypedFrame::Iterate(RootVisitor* v) const { @@ -1709,10 +1721,13 @@ void TypedFrame::Iterate(RootVisitor* v) const { CHECK(entry->code.has_value()); Tagged code = entry->code.value(); #if V8_ENABLE_WEBASSEMBLY - bool is_wasm_to_js = + bool is_generic_wasm_to_js = code->is_builtin() && code->builtin_id() == Builtin::kWasmToJsWrapperCSA; - if (is_wasm_to_js) { - IterateParamsOfWasmToJSWrapper(v); + bool is_optimized_wasm_to_js = this->type() == WASM_TO_JS_FUNCTION; + if (is_generic_wasm_to_js) { + IterateParamsOfGenericWasmToJSWrapper(v); + } else if (is_optimized_wasm_to_js) { + IterateParamsOfOptimizedWasmToJSWrapper(v); } #endif // V8_ENABLE_WEBASSEMBLY DCHECK(code->is_turbofanned()); @@ -1745,10 +1760,14 @@ void TypedFrame::Iterate(RootVisitor* v) const { // wrapper switched to before pushing the outgoing stack parameters and // calling the target. It marks the limit of the stack param area, and is // distinct from the beginning of the spill area. - Address central_stack_sp = - Memory
(fp() + WasmToJSWrapperConstants::kCentralStackSPOffset); + int central_stack_sp_offset = + is_generic_wasm_to_js + ? WasmToJSWrapperConstants::kCentralStackSPOffset + : WasmImportWrapperFrameConstants::kCentralStackSPOffset; + Address central_stack_sp = Memory
(fp() + central_stack_sp_offset); FullObjectSlot parameters_limit( - is_wasm_to_js && central_stack_sp != kNullAddress + (is_generic_wasm_to_js || is_optimized_wasm_to_js) && + central_stack_sp != kNullAddress ? central_stack_sp : frame_header_base.address() - spill_slots_size); #else diff --git a/src/execution/frames.h b/src/execution/frames.h index 081c74bf124ccfa57e4b40f75cbe42b00c771b2e..908239b4161235aeda04fe5526ddea8d56b09425 100644 --- a/src/execution/frames.h +++ b/src/execution/frames.h @@ -634,7 +634,8 @@ class TypedFrame : public CommonFrame { Tagged unchecked_code() const override { return {}; } void Iterate(RootVisitor* v) const override; - void IterateParamsOfWasmToJSWrapper(RootVisitor* v) const; + void IterateParamsOfGenericWasmToJSWrapper(RootVisitor* v) const; + void IterateParamsOfOptimizedWasmToJSWrapper(RootVisitor* v) const; protected: inline explicit TypedFrame(StackFrameIteratorBase* iterator); diff --git a/src/objects/code-inl.h b/src/objects/code-inl.h index baaced29afdbc87eae1c34b8df779e17e41410c4..1e1d66a87ee633cbdb49265444f53b5db790d9dd 100644 --- a/src/objects/code-inl.h +++ b/src/objects/code-inl.h @@ -48,6 +48,8 @@ GCSAFE_CODE_FWD_ACCESSOR(bool, has_tagged_outgoing_params) GCSAFE_CODE_FWD_ACCESSOR(bool, marked_for_deoptimization) GCSAFE_CODE_FWD_ACCESSOR(Tagged, raw_instruction_stream) GCSAFE_CODE_FWD_ACCESSOR(int, stack_slots) +GCSAFE_CODE_FWD_ACCESSOR(uint16_t, wasm_js_tagged_parameter_count) +GCSAFE_CODE_FWD_ACCESSOR(uint16_t, wasm_js_first_tagged_parameter) GCSAFE_CODE_FWD_ACCESSOR(Address, constant_pool) GCSAFE_CODE_FWD_ACCESSOR(Address, safepoint_table_address) #undef GCSAFE_CODE_FWD_ACCESSOR @@ -428,6 +430,31 @@ void Code::set_inlined_bytecode_size(unsigned size) { RELAXED_WRITE_UINT_FIELD(*this, kInlinedBytecodeSizeOffset, size); } +// For optimized on-heap wasm-js wrappers, we repurpose the (otherwise unused) +// 32-bit InlinedBytecodeSize field to encode two 16 values needed for scanning +// the frame: the count and starting offset of incoming tagged parameters. +// TODO(wasm): Eventually the wrappers should be managed off-heap by the wasm +// engine. Remove these accessors when that is the case. +void Code::set_wasm_js_tagged_parameter_count(uint16_t count) { + DCHECK_EQ(kind(), CodeKind::WASM_TO_JS_FUNCTION); + RELAXED_WRITE_UINT16_FIELD(*this, kInlinedBytecodeSizeOffset, count); +} + +uint16_t Code::wasm_js_tagged_parameter_count() const { + DCHECK_EQ(kind(), CodeKind::WASM_TO_JS_FUNCTION); + return RELAXED_READ_UINT16_FIELD(*this, kInlinedBytecodeSizeOffset); +} + +void Code::set_wasm_js_first_tagged_parameter(uint16_t count) { + DCHECK_EQ(kind(), CodeKind::WASM_TO_JS_FUNCTION); + RELAXED_WRITE_UINT16_FIELD(*this, kInlinedBytecodeSizeOffset + 2, count); +} + +uint16_t Code::wasm_js_first_tagged_parameter() const { + DCHECK_EQ(kind(), CodeKind::WASM_TO_JS_FUNCTION); + return RELAXED_READ_UINT16_FIELD(*this, kInlinedBytecodeSizeOffset + 2); +} + BytecodeOffset Code::osr_offset() const { return BytecodeOffset(RELAXED_READ_INT32_FIELD(*this, kOsrOffsetOffset)); } diff --git a/src/objects/code.h b/src/objects/code.h index 9a079a94ba0126b24532362a0ce233477f42c221..1da011899807125d6dc9ffb6d56622f5f15ad465 100644 --- a/src/objects/code.h +++ b/src/objects/code.h @@ -124,6 +124,15 @@ class Code : public ExposedTrustedObject { // [deoptimization_data]: Array containing data for deopt for non-baseline // code. DECL_ACCESSORS(deoptimization_data, Tagged) + // [parameter_count]: The number of formal parameters, including the + // receiver. Currently only available for optimized functions. + // TODO(saelo): make this always available. This is just a matter of figuring + // out how to obtain the parameter count during code generation when no + // BytecodeArray is available from which it can be copied. + DECL_PRIMITIVE_ACCESSORS(parameter_count, uint16_t) + inline uint16_t parameter_count_without_receiver() const; + DECL_PRIMITIVE_ACCESSORS(wasm_js_tagged_parameter_count, uint16_t) + DECL_PRIMITIVE_ACCESSORS(wasm_js_first_tagged_parameter, uint16_t) // Whether this type of Code uses deoptimization data, in which case the // deoptimization_data field will be populated. @@ -503,6 +512,10 @@ class GcSafeCode : public HeapObject { inline bool CanDeoptAt(Isolate* isolate, Address pc) const; inline Tagged raw_instruction_stream( PtrComprCageBase code_cage_base) const; + // The two following accessors repurpose the InlinedBytecodeSize field, see + // comment in code-inl.h. + inline uint16_t wasm_js_tagged_parameter_count() const; + inline uint16_t wasm_js_first_tagged_parameter() const; private: OBJECT_CONSTRUCTORS(GcSafeCode, HeapObject);