diff --git a/src/heap/heap.cc b/src/heap/heap.cc index f0ba316714..582cb6f31d 100644 --- a/src/heap/heap.cc +++ b/src/heap/heap.cc @@ -2183,6 +2183,17 @@ void Heap::CheckCollectionRequested() { current_gc_callback_flags_); } +#if V8_ENABLE_WEBASSEMBLY +void Heap::EnsureWasmCanonicalRttsSize(int length) { + Handle current_rtts = handle(wasm_canonical_rtts(), isolate_); + if (length <= current_rtts->length()) return; + Handle result = WeakArrayList::EnsureSpace( + isolate(), current_rtts, length, AllocationType::kOld); + result->set_length(length); + set_wasm_canonical_rtts(*result); +} +#endif + void Heap::UpdateSurvivalStatistics(int start_new_space_size) { if (start_new_space_size == 0) return; diff --git a/src/heap/heap.h b/src/heap/heap.h index 29aa5aad76..d2eeff5f21 100644 --- a/src/heap/heap.h +++ b/src/heap/heap.h @@ -783,6 +783,12 @@ class Heap { std::min(max_old_generation_size(), std::max(heap_limit, min_limit))); } +#if V8_ENABLE_WEBASSEMBLY + // TODO(manoskouk): Inline this if STRONG_MUTABLE_MOVABLE_ROOT_LIST setters + // become public. + void EnsureWasmCanonicalRttsSize(int length); +#endif + // =========================================================================== // Initialization. =========================================================== // =========================================================================== diff --git a/src/heap/setup-heap-internal.cc b/src/heap/setup-heap-internal.cc index 806da907c0..34e6c1b433 100644 --- a/src/heap/setup-heap-internal.cc +++ b/src/heap/setup-heap-internal.cc @@ -829,6 +829,7 @@ void Heap::CreateInitialObjects() { #ifdef V8_ENABLE_WEBASSEMBLY set_active_continuation(roots.undefined_value()); set_active_suspender(roots.undefined_value()); + set_wasm_canonical_rtts(roots.empty_weak_array_list()); #endif // V8_ENABLE_WEBASSEMBLY set_script_list(roots.empty_weak_array_list()); diff --git a/src/roots/roots.h b/src/roots/roots.h index 75eb6b7700..cdace8ed7b 100644 --- a/src/roots/roots.h +++ b/src/roots/roots.h @@ -323,7 +323,8 @@ class Symbol; V(ArrayList, basic_block_profiling_data, BasicBlockProfilingData) \ V(WeakArrayList, shared_wasm_memories, SharedWasmMemories) \ IF_WASM(V, HeapObject, active_continuation, ActiveContinuation) \ - IF_WASM(V, HeapObject, active_suspender, ActiveSuspender) + IF_WASM(V, HeapObject, active_suspender, ActiveSuspender) \ + IF_WASM(V, WeakArrayList, wasm_canonical_rtts, WasmCanonicalRtts) // Entries in this list are limited to Smis and are not visited during GC. #define SMI_ROOT_LIST(V) \ diff --git a/src/wasm/module-instantiate.cc b/src/wasm/module-instantiate.cc index bb3dd5d562..eb556a426f 100644 --- a/src/wasm/module-instantiate.cc +++ b/src/wasm/module-instantiate.cc @@ -139,9 +139,6 @@ Handle CreateArrayDescriptorArray( return descriptors; } -} // namespace - -// TODO(jkummerow): Move these elsewhere. Handle CreateStructMap(Isolate* isolate, const WasmModule* module, int struct_index, Handle opt_rtt_parent, Handle instance) { @@ -218,6 +215,24 @@ void CreateMapForType(Isolate* isolate, const WasmModule* module, Handle maps) { // Recursive calls for supertypes may already have created this map. if (maps->get(type_index).IsMap()) return; + + Handle canonical_rtts; + uint32_t canonical_type_index = + module->isorecursive_canonical_type_ids[type_index]; + + if (FLAG_wasm_type_canonicalization) { + // Try to find the canonical map for this type in the isolate store. + canonical_rtts = handle(isolate->heap()->wasm_canonical_rtts(), isolate); + DCHECK_GT(static_cast(canonical_rtts->length()), + canonical_type_index); + MaybeObject maybe_canonical_map = canonical_rtts->Get(canonical_type_index); + if (maybe_canonical_map.IsStrongOrWeak() && + maybe_canonical_map.GetHeapObject().IsMap()) { + maps->set(type_index, maybe_canonical_map.GetHeapObject()); + return; + } + } + Handle rtt_parent; // If the type with {type_index} has an explicit supertype, make sure the // map for that supertype is created first, so that the supertypes list @@ -239,13 +254,17 @@ void CreateMapForType(Isolate* isolate, const WasmModule* module, break; case TypeDefinition::kFunction: // TODO(7748): Create funcref RTTs lazily? - // TODO(7748): Canonicalize function maps (cross-module)? map = CreateFuncRefMap(isolate, module, rtt_parent, instance); break; } + if (FLAG_wasm_type_canonicalization) { + canonical_rtts->Set(canonical_type_index, HeapObjectReference::Weak(*map)); + } maps->set(type_index, *map); } +} // namespace + // A helper class to simplify instantiating a module from a module object. // It closes over the {Isolate}, the {ErrorThrower}, etc. class InstanceBuilder { @@ -643,6 +662,15 @@ MaybeHandle InstanceBuilder::Build() { // list. //-------------------------------------------------------------------------- if (enabled_.has_gc()) { + if (FLAG_wasm_type_canonicalization) { + uint32_t maximum_canonical_type_index = + *std::max_element(module_->isorecursive_canonical_type_ids.begin(), + module_->isorecursive_canonical_type_ids.end()); + // Make sure all canonical indices have been set. + DCHECK_NE(maximum_canonical_type_index, kNoSuperType); + isolate_->heap()->EnsureWasmCanonicalRttsSize( + maximum_canonical_type_index + 1); + } Handle maps = isolate_->factory()->NewFixedArray( static_cast(module_->types.size())); for (uint32_t index = 0; index < module_->types.size(); index++) { diff --git a/src/wasm/wasm-module.h b/src/wasm/wasm-module.h index 25982e2b4d..613dbc3c70 100644 --- a/src/wasm/wasm-module.h +++ b/src/wasm/wasm-module.h @@ -443,7 +443,8 @@ struct V8_EXPORT_PRIVATE WasmModule { ? signature_map.FindOrInsert(*type.function_sig) : 0; canonicalized_type_ids.push_back(canonical_id); - isorecursive_canonical_type_ids.push_back(-1); // Will be computed later. + // Canonical type will be computed later. + isorecursive_canonical_type_ids.push_back(kNoSuperType); } bool has_type(uint32_t index) const { return index < types.size(); } diff --git a/src/wasm/wasm-objects.h b/src/wasm/wasm-objects.h index 5cff97a24a..dd3c5180d9 100644 --- a/src/wasm/wasm-objects.h +++ b/src/wasm/wasm-objects.h @@ -1036,14 +1036,6 @@ class WasmSuspenderObject #undef DECL_OPTIONAL_ACCESSORS namespace wasm { - -Handle CreateStructMap(Isolate* isolate, const WasmModule* module, - int struct_index, MaybeHandle rtt_parent, - Handle instance); -Handle CreateArrayMap(Isolate* isolate, const WasmModule* module, - int array_index, MaybeHandle rtt_parent, - Handle instance); - bool TypecheckJSObject(Isolate* isolate, const WasmModule* module, Handle value, ValueType expected, const char** error_message); diff --git a/test/cctest/test-roots.cc b/test/cctest/test-roots.cc index 33f0873530..21bc365ab7 100644 --- a/test/cctest/test-roots.cc +++ b/test/cctest/test-roots.cc @@ -56,6 +56,7 @@ bool IsInitiallyMutable(Factory* factory, Address object_address) { V(retaining_path_targets) \ V(serialized_global_proxy_sizes) \ V(serialized_objects) \ + IF_WASM(V, wasm_canonical_rtts) \ V(weak_refs_keep_during_job) #define TEST_CAN_BE_READ_ONLY(name) \ diff --git a/test/cctest/wasm/test-gc.cc b/test/cctest/wasm/test-gc.cc index 7ceaf2985e..4f79eb6bdd 100644 --- a/test/cctest/wasm/test-gc.cc +++ b/test/cctest/wasm/test-gc.cc @@ -39,6 +39,9 @@ class WasmGCTester { execution_tier == TestExecutionTier::kLiftoff), flag_liftoff_only(&v8::internal::FLAG_liftoff_only, execution_tier == TestExecutionTier::kLiftoff), + // Test both setups with canonicalization and without. + flag_canonicalization(&v8::internal::FLAG_wasm_type_canonicalization, + execution_tier == TestExecutionTier::kTurbofan), flag_tierup(&v8::internal::FLAG_wasm_tier_up, false), zone_(&allocator, ZONE_NAME), builder_(&zone_), @@ -181,6 +184,7 @@ class WasmGCTester { const FlagScope flag_typedfuns; const FlagScope flag_liftoff; const FlagScope flag_liftoff_only; + const FlagScope flag_canonicalization; const FlagScope flag_tierup; byte DefineFunctionImpl(WasmFunctionBuilder* fun, diff --git a/test/mjsunit/wasm/runtime-type-canonicalization.js b/test/mjsunit/wasm/runtime-type-canonicalization.js new file mode 100644 index 0000000000..dc89fed3c8 --- /dev/null +++ b/test/mjsunit/wasm/runtime-type-canonicalization.js @@ -0,0 +1,67 @@ +// Copyright 2022 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: --expose-gc --experimental-wasm-gc + +d8.file.execute("test/mjsunit/wasm/wasm-module-builder.js"); + +let builder = new WasmModuleBuilder(); + +let struct_index = builder.addStruct([makeField(kWasmI32, true)]); +let identical_struct_index = builder.addStruct([makeField(kWasmI32, true)]); +let distinct_struct_index = builder.addStruct([makeField(kWasmI64, true)]); + +let struct_init = builder.addFunction("struct_init", + makeSig([], [kWasmDataRef])) + .addBody([kGCPrefix, kExprStructNewDefault, struct_index]) + .exportFunc(); +let test_pass = builder.addFunction("test_pass", + makeSig([kWasmDataRef], [kWasmI32])) + .addBody([kExprLocalGet, 0, + kGCPrefix, kExprRefTestStatic, identical_struct_index]) + .exportFunc(); +let test_fail = builder.addFunction("test_fail", + makeSig([kWasmDataRef], [kWasmI32])) + .addBody([kExprLocalGet, 0, + kGCPrefix, kExprRefTestStatic, distinct_struct_index]) + .exportFunc(); + +(function TestCanonicalizationSameInstance() { + print(arguments.callee.name); + let instance = builder.instantiate({}); + assertEquals(1, instance.exports.test_pass(instance.exports.struct_init())); + assertEquals(0, instance.exports.test_fail(instance.exports.struct_init())); +})(); + +(function TestCanonicalizationSameModuleDifferentInstances() { + print(arguments.callee.name); + let module = builder.toModule(); + let instance1 = new WebAssembly.Instance(module, {}); + let instance2 = new WebAssembly.Instance(module, {}); + assertEquals(1, instance2.exports.test_pass(instance1.exports.struct_init())); + assertEquals(0, instance2.exports.test_fail(instance1.exports.struct_init())); +})(); + +// GC between tests so that the type registry is cleared. +gc(); + +(function TestCanonicalizationDifferentModules() { + print(arguments.callee.name); + let instance1 = builder.instantiate({}); + let instance2 = builder.instantiate({}); + assertEquals(1, instance2.exports.test_pass(instance1.exports.struct_init())); + assertEquals(0, instance2.exports.test_fail(instance1.exports.struct_init())); +})(); + +(function TestCanonicalizationDifferentModulesAfterGC() { + print(arguments.callee.name); + let struct = (function make_struct() { + return builder.instantiate({}).exports.struct_init(); + })(); + // A the live {struct} object keeps the instance alive. + gc(); + let instance = builder.instantiate({}); + assertEquals(1, instance.exports.test_pass(struct)); + assertEquals(0, instance.exports.test_fail(struct)); +})();