From 54b5ec94e067650f72a680ea5bf6a84da36ea096 Mon Sep 17 00:00:00 2001 From: "Node.js GitHub Bot" Date: Wed, 9 Oct 2024 09:32:15 +0200 Subject: [PATCH] deps: patch V8 to 12.9.202.26 Refs: https://github.com/v8/v8/compare/12.9.202.19...12.9.202.26 PR-URL: https://github.com/nodejs/node/pull/55161 Reviewed-By: Luigi Pinca Reviewed-By: Jiawen Geng Reviewed-By: Rafael Gonzaga --- deps/v8/include/v8-version.h | 2 +- deps/v8/src/flags/flag-definitions.h | 7 +-- deps/v8/src/maglev/maglev-graph-builder.cc | 27 +++++++++--- deps/v8/src/parsing/parser-base.h | 41 ++++++++++++------ deps/v8/src/parsing/parser.cc | 7 +++ deps/v8/src/parsing/parser.h | 1 + deps/v8/src/parsing/preparser.h | 2 + deps/v8/src/wasm/graph-builder-interface.cc | 3 +- deps/v8/src/wasm/streaming-decoder.cc | 16 +++++++ deps/v8/src/wasm/wasm-engine.cc | 7 +-- deps/v8/src/wasm/wasm-js.cc | 22 ++++++---- .../test/mjsunit/maglev/regress-369630648.js | 18 ++++++++ .../test/mjsunit/regress/regress-363538434.js | 9 ++++ .../test/mjsunit/regress/regress-366323452.js | 7 +++ .../mjsunit/regress/wasm/regress-368241691.js | 43 +++++++++++++++++++ 15 files changed, 176 insertions(+), 36 deletions(-) create mode 100644 deps/v8/test/mjsunit/maglev/regress-369630648.js create mode 100644 deps/v8/test/mjsunit/regress/regress-363538434.js create mode 100644 deps/v8/test/mjsunit/regress/regress-366323452.js create mode 100644 deps/v8/test/mjsunit/regress/wasm/regress-368241691.js diff --git a/deps/v8/include/v8-version.h b/deps/v8/include/v8-version.h index c5cefa77702e8a..f1c9dae4b6a61e 100644 --- a/deps/v8/include/v8-version.h +++ b/deps/v8/include/v8-version.h @@ -11,7 +11,7 @@ #define V8_MAJOR_VERSION 12 #define V8_MINOR_VERSION 9 #define V8_BUILD_NUMBER 202 -#define V8_PATCH_LEVEL 19 +#define V8_PATCH_LEVEL 26 // Use 1 for candidates and 0 otherwise. // (Boolean macro values are not supported by all preprocessors.) diff --git a/deps/v8/src/flags/flag-definitions.h b/deps/v8/src/flags/flag-definitions.h index 5bbc31893eeb1c..3d663c81ca7b0f 100644 --- a/deps/v8/src/flags/flag-definitions.h +++ b/deps/v8/src/flags/flag-definitions.h @@ -566,8 +566,8 @@ DEFINE_BOOL(maglev_inlining, true, "enable inlining in the maglev optimizing compiler") DEFINE_BOOL(maglev_loop_peeling, true, "enable loop peeling in the maglev optimizing compiler") -DEFINE_BOOL(maglev_optimistic_peeled_loops, true, - "enable aggressive optimizations for loops (loop SPeeling) in the " +DEFINE_BOOL(maglev_optimistic_peeled_loops, false, + "enable speculation on loop state using peeling as fallback in the " "maglev optimizing compiler") DEFINE_INT(maglev_loop_peeling_max_size, 200, "max loop size for loop peeling in the maglev optimizing compiler") @@ -582,6 +582,7 @@ DEFINE_BOOL(maglev_destroy_on_background, true, DEFINE_BOOL(maglev_inline_api_calls, false, "Inline CallApiCallback builtin into generated code") DEFINE_EXPERIMENTAL_FEATURE(maglev_licm, "loop invariant code motion") +DEFINE_WEAK_IMPLICATION(maglev_future, maglev_optimistic_peeled_loops) DEFINE_WEAK_IMPLICATION(maglev_future, maglev_speculative_hoist_phi_untagging) DEFINE_WEAK_IMPLICATION(maglev_future, maglev_inline_api_calls) DEFINE_WEAK_IMPLICATION(maglev_future, maglev_escape_analysis) @@ -2499,7 +2500,7 @@ DEFINE_BOOL_READONLY(fast_map_update, false, DEFINE_INT(max_valid_polymorphic_map_count, 4, "maximum number of valid maps to track in POLYMORPHIC state") DEFINE_BOOL( - clone_object_sidestep_transitions, true, + clone_object_sidestep_transitions, false, "support sidestep transitions for dependency tracking object clone maps") DEFINE_WEAK_IMPLICATION(future, clone_object_sidestep_transitions) diff --git a/deps/v8/src/maglev/maglev-graph-builder.cc b/deps/v8/src/maglev/maglev-graph-builder.cc index b3ee4e0fb760ee..0ce37b22e61f5a 100644 --- a/deps/v8/src/maglev/maglev-graph-builder.cc +++ b/deps/v8/src/maglev/maglev-graph-builder.cc @@ -1370,7 +1370,14 @@ DeoptFrame MaglevGraphBuilder::GetDeoptFrameForLazyDeoptHelper( current_source_position_, GetParentDeoptFrame()); ret.frame_state()->ForEachValue( *compilation_unit_, [this](ValueNode* node, interpreter::Register reg) { - AddDeoptUse(node); + // Receiver and closure values have to be materialized, even if + // they don't otherwise escape. + if (reg == interpreter::Register::receiver() || + reg == interpreter::Register::function_closure()) { + node->add_use(); + } else { + AddDeoptUse(node); + } }); AddDeoptUse(ret.closure()); return ret; @@ -6965,15 +6972,21 @@ void MaglevGraphBuilder::VisitDeletePropertySloppy() { void MaglevGraphBuilder::VisitGetSuperConstructor() { ValueNode* active_function = GetAccumulator(); - ValueNode* map_proto; + // TODO(victorgomes): Maybe BuildLoadTaggedField should support constants + // instead. if (compiler::OptionalHeapObjectRef constant = TryGetConstant(active_function)) { - map_proto = GetConstant(constant->map(broker()).prototype(broker())); - } else { - ValueNode* map = - BuildLoadTaggedField(active_function, HeapObject::kMapOffset); - map_proto = BuildLoadTaggedField(map, Map::kPrototypeOffset); + compiler::MapRef map = constant->map(broker()); + if (map.is_stable()) { + broker()->dependencies()->DependOnStableMap(map); + ValueNode* map_proto = GetConstant(map.prototype(broker())); + StoreRegister(iterator_.GetRegisterOperand(0), map_proto); + return; + } } + ValueNode* map = + BuildLoadTaggedField(active_function, HeapObject::kMapOffset); + ValueNode* map_proto = BuildLoadTaggedField(map, Map::kPrototypeOffset); StoreRegister(iterator_.GetRegisterOperand(0), map_proto); } diff --git a/deps/v8/src/parsing/parser-base.h b/deps/v8/src/parsing/parser-base.h index cab57b448cb9b8..8be0567a93f0d6 100644 --- a/deps/v8/src/parsing/parser-base.h +++ b/deps/v8/src/parsing/parser-base.h @@ -620,26 +620,32 @@ class ParserBase { return instance_members_scope != nullptr; } - DeclarationScope* EnsureStaticElementsScope(ParserBase* parser, - int beg_pos) { + DeclarationScope* EnsureStaticElementsScope(ParserBase* parser, int beg_pos, + int info_id) { if (!has_static_elements()) { static_elements_scope = parser->NewFunctionScope( FunctionKind::kClassStaticInitializerFunction); static_elements_scope->SetLanguageMode(LanguageMode::kStrict); static_elements_scope->set_start_position(beg_pos); - static_elements_function_id = parser->GetNextInfoId(); + static_elements_function_id = info_id; + // Actually consume the id. The id that was passed in might be an + // earlier id in case of computed property names. + parser->GetNextInfoId(); } return static_elements_scope; } DeclarationScope* EnsureInstanceMembersScope(ParserBase* parser, - int beg_pos) { + int beg_pos, int info_id) { if (!has_instance_members()) { instance_members_scope = parser->NewFunctionScope( FunctionKind::kClassMembersInitializerFunction); instance_members_scope->SetLanguageMode(LanguageMode::kStrict); instance_members_scope->set_start_position(beg_pos); - instance_members_function_id = parser->GetNextInfoId(); + instance_members_function_id = info_id; + // Actually consume the id. The id that was passed in might be an + // earlier id in case of computed property names. + parser->GetNextInfoId(); } return instance_members_scope; } @@ -1321,7 +1327,7 @@ class ParserBase { ParseFunctionFlags flags, bool is_static, bool* has_seen_constructor); ExpressionT ParseMemberInitializer(ClassInfo* class_info, int beg_pos, - bool is_static); + int info_id, bool is_static); BlockT ParseClassStaticBlock(ClassInfo* class_info); ObjectLiteralPropertyT ParseObjectPropertyDefinition( ParsePropertyInfo* prop_info, bool* has_seen_proto); @@ -2624,6 +2630,8 @@ ParserBase::ParseClassPropertyDefinition(ClassInfo* class_info, DCHECK_NOT_NULL(class_info); DCHECK_EQ(prop_info->position, PropertyPosition::kClassLiteral); + int next_info_id = PeekNextInfoId(); + Token::Value name_token = peek(); int property_beg_pos = peek_position(); int name_token_position = property_beg_pos; @@ -2667,12 +2675,18 @@ ParserBase::ParseClassPropertyDefinition(ClassInfo* class_info, // field. DCHECK_IMPLIES(prop_info->is_computed_name, !prop_info->is_private); - if (!prop_info->is_computed_name) { + if (prop_info->is_computed_name) { + if (!has_error() && next_info_id != PeekNextInfoId() && + !(prop_info->is_static ? class_info->has_static_elements() + : class_info->has_instance_members())) { + impl()->ReindexComputedMemberName(name_expression); + } + } else { CheckClassFieldName(prop_info->name, prop_info->is_static); } - ExpressionT value = ParseMemberInitializer(class_info, property_beg_pos, - prop_info->is_static); + ExpressionT value = ParseMemberInitializer( + class_info, property_beg_pos, next_info_id, prop_info->is_static); ExpectSemicolon(); ClassLiteralPropertyT result; @@ -2786,11 +2800,12 @@ ParserBase::ParseClassPropertyDefinition(ClassInfo* class_info, template typename ParserBase::ExpressionT ParserBase::ParseMemberInitializer( - ClassInfo* class_info, int beg_pos, bool is_static) { + ClassInfo* class_info, int beg_pos, int info_id, bool is_static) { FunctionParsingScope body_parsing_scope(impl()); DeclarationScope* initializer_scope = - is_static ? class_info->EnsureStaticElementsScope(this, beg_pos) - : class_info->EnsureInstanceMembersScope(this, beg_pos); + is_static + ? class_info->EnsureStaticElementsScope(this, beg_pos, info_id) + : class_info->EnsureInstanceMembersScope(this, beg_pos, info_id); if (Check(Token::kAssign)) { FunctionState initializer_state(&function_state_, &scope_, @@ -2811,7 +2826,7 @@ typename ParserBase::BlockT ParserBase::ParseClassStaticBlock( Consume(Token::kStatic); DeclarationScope* initializer_scope = - class_info->EnsureStaticElementsScope(this, position()); + class_info->EnsureStaticElementsScope(this, position(), PeekNextInfoId()); FunctionState initializer_state(&function_state_, &scope_, initializer_scope); FunctionParsingScope body_parsing_scope(impl()); diff --git a/deps/v8/src/parsing/parser.cc b/deps/v8/src/parsing/parser.cc index a7bdc72000c86d..a34aedeaefaa65 100644 --- a/deps/v8/src/parsing/parser.cc +++ b/deps/v8/src/parsing/parser.cc @@ -2735,6 +2735,13 @@ void Parser::ReindexArrowFunctionFormalParameters( } } +void Parser::ReindexComputedMemberName(Expression* computed_name) { + // Make space for the member initializer function above the computed property + // name. + AstFunctionLiteralIdReindexer reindexer(stack_limit_, 1); + reindexer.Reindex(computed_name); +} + void Parser::PrepareGeneratorVariables() { // Calling a generator returns a generator object. That object is stored // in a temporary variable, a definition that is used by "yield" diff --git a/deps/v8/src/parsing/parser.h b/deps/v8/src/parsing/parser.h index 04239abdf03cc9..e36f004cf4b785 100644 --- a/deps/v8/src/parsing/parser.h +++ b/deps/v8/src/parsing/parser.h @@ -894,6 +894,7 @@ class V8_EXPORT_PRIVATE Parser : public NON_EXPORTED_BASE(ParserBase) { } void ReindexArrowFunctionFormalParameters(ParserFormalParameters* parameters); + void ReindexComputedMemberName(Expression* computed_name); void DeclareArrowFunctionFormalParameters( ParserFormalParameters* parameters, Expression* params, const Scanner::Location& params_loc); diff --git a/deps/v8/src/parsing/preparser.h b/deps/v8/src/parsing/preparser.h index a086d268df25db..ae967e6c8ef5b2 100644 --- a/deps/v8/src/parsing/preparser.h +++ b/deps/v8/src/parsing/preparser.h @@ -1556,6 +1556,8 @@ class PreParser : public ParserBase { V8_INLINE void ReindexArrowFunctionFormalParameters( PreParserFormalParameters* parameters) {} + V8_INLINE void ReindexComputedMemberName( + const PreParserExpression& expression) {} V8_INLINE void DeclareFormalParameters( const PreParserFormalParameters* parameters) { if (!parameters->is_simple) parameters->scope->SetHasNonSimpleParameters(); diff --git a/deps/v8/src/wasm/graph-builder-interface.cc b/deps/v8/src/wasm/graph-builder-interface.cc index fbc035a29e5319..8ca8c637ae5f95 100644 --- a/deps/v8/src/wasm/graph-builder-interface.cc +++ b/deps/v8/src/wasm/graph-builder-interface.cc @@ -1206,7 +1206,6 @@ class WasmGraphBuildingInterface { void BrOnNonNull(FullDecoder* decoder, const Value& ref_object, Value* result, uint32_t depth, bool /* drop_null_on_fallthrough */) { - result->node = ref_object.node; SsaEnv* false_env = ssa_env_; SsaEnv* true_env = Split(decoder->zone(), false_env); false_env->SetNotMerged(); @@ -1214,6 +1213,8 @@ class WasmGraphBuildingInterface { builder_->BrOnNull(ref_object.node, ref_object.type); builder_->SetControl(false_env->control); ScopedSsaEnv scoped_env(this, true_env); + // Make sure the TypeGuard has the right Control dependency. + SetAndTypeNode(result, builder_->TypeGuard(ref_object.node, result->type)); BrOrRet(decoder, depth); } diff --git a/deps/v8/src/wasm/streaming-decoder.cc b/deps/v8/src/wasm/streaming-decoder.cc index 40a11803e4d506..f3745b4c055a41 100644 --- a/deps/v8/src/wasm/streaming-decoder.cc +++ b/deps/v8/src/wasm/streaming-decoder.cc @@ -294,6 +294,10 @@ void AsyncStreamingDecoder::Finish(bool can_use_compiled_module) { if (!full_wire_bytes_.back().empty()) { size_t total_length = 0; for (auto& bytes : full_wire_bytes_) total_length += bytes.size(); + if (ok()) { + // {DecodeSectionLength} enforces this with graceful error reporting. + CHECK_LE(total_length, max_module_size()); + } auto all_bytes = base::OwnedVector::NewForOverwrite(total_length); uint8_t* ptr = all_bytes.begin(); for (auto& bytes : full_wire_bytes_) { @@ -627,6 +631,18 @@ std::unique_ptr AsyncStreamingDecoder::DecodeSectionLength::NextWithValue( AsyncStreamingDecoder* streaming) { TRACE_STREAMING("DecodeSectionLength(%zu)\n", value_); + // Check if this section fits into the overall module length limit. + // Note: {this->module_offset_} is the position of the section ID byte, + // {streaming->module_offset_} is the start of the section's payload (i.e. + // right after the just-decoded section length varint). + // The latter can already exceed the max module size, when the previous + // section barely fit into it, and this new section's ID or length crossed + // the threshold. + uint32_t payload_start = streaming->module_offset(); + size_t max_size = max_module_size(); + if (payload_start > max_size || max_size - payload_start < value_) { + return streaming->ToErrorState(); + } SectionBuffer* buf = streaming->CreateNewBuffer(module_offset_, section_id_, value_, buffer().SubVector(0, bytes_consumed_)); diff --git a/deps/v8/src/wasm/wasm-engine.cc b/deps/v8/src/wasm/wasm-engine.cc index f09af199169731..7a5546c93ad976 100644 --- a/deps/v8/src/wasm/wasm-engine.cc +++ b/deps/v8/src/wasm/wasm-engine.cc @@ -2016,10 +2016,11 @@ uint32_t max_table_init_entries() { // {max_module_size} is declared in wasm-limits.h. size_t max_module_size() { - // Clamp the value of --wasm-max-module-size between 16 and just below 2GB. + // Clamp the value of --wasm-max-module-size between 16 and the maximum + // that the implementation supports. constexpr size_t kMin = 16; - constexpr size_t kMax = RoundDown(size_t{kMaxInt}); - static_assert(kMin <= kV8MaxWasmModuleSize && kV8MaxWasmModuleSize <= kMax); + constexpr size_t kMax = kV8MaxWasmModuleSize; + static_assert(kMin <= kV8MaxWasmModuleSize); return std::clamp(v8_flags.wasm_max_module_size.value(), kMin, kMax); } diff --git a/deps/v8/src/wasm/wasm-js.cc b/deps/v8/src/wasm/wasm-js.cc index e1d7bc808ce82c..a9245918765788 100644 --- a/deps/v8/src/wasm/wasm-js.cc +++ b/deps/v8/src/wasm/wasm-js.cc @@ -202,8 +202,8 @@ GET_FIRST_ARGUMENT_AS(Tag) #undef GET_FIRST_ARGUMENT_AS i::wasm::ModuleWireBytes GetFirstArgumentAsBytes( - const v8::FunctionCallbackInfo& info, ErrorThrower* thrower, - bool* is_shared) { + const v8::FunctionCallbackInfo& info, size_t max_length, + ErrorThrower* thrower, bool* is_shared) { DCHECK(i::ValidateCallbackInfo(info)); const uint8_t* start = nullptr; size_t length = 0; @@ -234,7 +234,6 @@ i::wasm::ModuleWireBytes GetFirstArgumentAsBytes( if (length == 0) { thrower->CompileError("BufferSource argument is empty"); } - size_t max_length = i::wasm::max_module_size(); if (length > max_length) { // The spec requires a CompileError for implementation-defined limits, see // https://webassembly.github.io/spec/js-api/index.html#limits. @@ -637,7 +636,8 @@ void WebAssemblyCompileImpl(const v8::FunctionCallbackInfo& info) { new AsyncCompilationResolver(isolate, context, promise_resolver)); bool is_shared = false; - auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared); + auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(), + &thrower, &is_shared); if (thrower.error()) { resolver->OnCompilationFailed(thrower.Reify()); return; @@ -669,8 +669,11 @@ void WasmStreamingCallbackForTesting( v8::WasmStreaming::Unpack(info.GetIsolate(), info.Data()); bool is_shared = false; + // We don't check the buffer length up front, to allow d8 to test that the + // streaming decoder implementation handles overly large inputs correctly. + size_t unlimited = std::numeric_limits::max(); i::wasm::ModuleWireBytes bytes = - GetFirstArgumentAsBytes(info, &thrower, &is_shared); + GetFirstArgumentAsBytes(info, unlimited, &thrower, &is_shared); if (thrower.error()) { streaming->Abort(Utils::ToLocal(thrower.Reify())); return; @@ -771,7 +774,8 @@ void WebAssemblyValidateImpl(const v8::FunctionCallbackInfo& info) { ErrorThrower thrower(i_isolate, "WebAssembly.validate()"); bool is_shared = false; - auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared); + auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(), + &thrower, &is_shared); v8::ReturnValue return_value = info.GetReturnValue(); @@ -850,7 +854,8 @@ void WebAssemblyModuleImpl(const v8::FunctionCallbackInfo& info) { } bool is_shared = false; - auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared); + auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(), + &thrower, &is_shared); if (thrower.error()) { return; @@ -1168,7 +1173,8 @@ void WebAssemblyInstantiateImpl( } bool is_shared = false; - auto bytes = GetFirstArgumentAsBytes(info, &thrower, &is_shared); + auto bytes = GetFirstArgumentAsBytes(info, i::wasm::max_module_size(), + &thrower, &is_shared); if (thrower.error()) { resolver->OnInstantiationFailed(thrower.Reify()); return; diff --git a/deps/v8/test/mjsunit/maglev/regress-369630648.js b/deps/v8/test/mjsunit/maglev/regress-369630648.js new file mode 100644 index 00000000000000..e467fee5ee473f --- /dev/null +++ b/deps/v8/test/mjsunit/maglev/regress-369630648.js @@ -0,0 +1,18 @@ +// Copyright 2024 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. +// +// Flags: --allow-natives-syntax --no-lazy-feedback-allocation + +class C extends Array { + constructor() { + (() => (() => super())())(); + } +} +%PrepareFunctionForOptimization(C); +new C(); +new C(); +%OptimizeFunctionOnNextCall(C); +new C(); +C.__proto__ = [1]; +assertThrows(() => { new C() }, TypeError); diff --git a/deps/v8/test/mjsunit/regress/regress-363538434.js b/deps/v8/test/mjsunit/regress/regress-363538434.js new file mode 100644 index 00000000000000..0eb4c3f0e5515d --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-363538434.js @@ -0,0 +1,9 @@ +// Copyright 2024 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. + +try { + new class { + static [function(){}] = [].trigger_error(); + } +} catch (e) {} diff --git a/deps/v8/test/mjsunit/regress/regress-366323452.js b/deps/v8/test/mjsunit/regress/regress-366323452.js new file mode 100644 index 00000000000000..18e03de1fcd3f3 --- /dev/null +++ b/deps/v8/test/mjsunit/regress/regress-366323452.js @@ -0,0 +1,7 @@ +// Copyright 2024 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. + +assertThrows(`new class { + static [{aaa(){}]; +};`); diff --git a/deps/v8/test/mjsunit/regress/wasm/regress-368241691.js b/deps/v8/test/mjsunit/regress/wasm/regress-368241691.js new file mode 100644 index 00000000000000..8a8d9e95089fbf --- /dev/null +++ b/deps/v8/test/mjsunit/regress/wasm/regress-368241691.js @@ -0,0 +1,43 @@ +// Copyright 2024 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. + +// Flags: --allow-natives-syntax + +d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); + +let builder = new WasmModuleBuilder(); + +let struct0 = builder.addStruct([makeField(kWasmI32, true)]); +let return_struct0 = builder.addType(makeSig([], [wasmRefType(struct0)])); + +builder.addFunction("makeStruct", return_struct0) + .exportFunc() + .addBody([ + kExprI32Const, 42, + kGCPrefix, kExprStructNew, struct0, + ]); + +let callee = builder.addFunction("callee", makeSig([wasmRefType(struct0)], [kWasmI32])) + .addBody([ + kExprLocalGet, 0, + kGCPrefix, kExprStructGet, struct0, 0, + ]); + +builder.addFunction("main", makeSig([wasmRefNullType(struct0)], [kWasmI32])) + .exportFunc() + .addBody([ + kExprBlock, return_struct0, + kExprLocalGet, 0, + kExprBrOnNonNull, 0, + kExprUnreachable, + kExprEnd, + kExprCallFunction, callee.index, + ]); + +let instance = builder.instantiate(); +let obj = instance.exports.makeStruct(); + +instance.exports.main(obj); +%WasmTierUpFunction(instance.exports.main); +assertEquals(42, instance.exports.main(obj));