[wasm-gc] Implement runtime-type canonicalization

Add an array of canonical rtts on the isolate. Each wasm instance
copies its rtts from there, based on the type index -> canonical index
mapping in the module.

Bug: v8:7748
Change-Id: I0958686c51ecab15a3215a0da3bee1ad6d543cb3
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3548821
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Cr-Commit-Position: refs/heads/main@{#79712}
This commit is contained in:
Manos Koukoutos 2022-04-01 13:01:52 +00:00 committed by V8 LUCI CQ
parent d36f596e8a
commit c3ed607d63
10 changed files with 126 additions and 14 deletions

View File

@ -2183,6 +2183,17 @@ void Heap::CheckCollectionRequested() {
current_gc_callback_flags_);
}
#if V8_ENABLE_WEBASSEMBLY
void Heap::EnsureWasmCanonicalRttsSize(int length) {
Handle<WeakArrayList> current_rtts = handle(wasm_canonical_rtts(), isolate_);
if (length <= current_rtts->length()) return;
Handle<WeakArrayList> 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;

View File

@ -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. ===========================================================
// ===========================================================================

View File

@ -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());

View File

@ -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) \

View File

@ -139,9 +139,6 @@ Handle<DescriptorArray> CreateArrayDescriptorArray(
return descriptors;
}
} // namespace
// TODO(jkummerow): Move these elsewhere.
Handle<Map> CreateStructMap(Isolate* isolate, const WasmModule* module,
int struct_index, Handle<Map> opt_rtt_parent,
Handle<WasmInstanceObject> instance) {
@ -218,6 +215,24 @@ void CreateMapForType(Isolate* isolate, const WasmModule* module,
Handle<FixedArray> maps) {
// Recursive calls for supertypes may already have created this map.
if (maps->get(type_index).IsMap()) return;
Handle<WeakArrayList> 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<uint32_t>(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<Map> 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<WasmInstanceObject> 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<FixedArray> maps = isolate_->factory()->NewFixedArray(
static_cast<int>(module_->types.size()));
for (uint32_t index = 0; index < module_->types.size(); index++) {

View File

@ -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(); }

View File

@ -1036,14 +1036,6 @@ class WasmSuspenderObject
#undef DECL_OPTIONAL_ACCESSORS
namespace wasm {
Handle<Map> CreateStructMap(Isolate* isolate, const WasmModule* module,
int struct_index, MaybeHandle<Map> rtt_parent,
Handle<WasmInstanceObject> instance);
Handle<Map> CreateArrayMap(Isolate* isolate, const WasmModule* module,
int array_index, MaybeHandle<Map> rtt_parent,
Handle<WasmInstanceObject> instance);
bool TypecheckJSObject(Isolate* isolate, const WasmModule* module,
Handle<Object> value, ValueType expected,
const char** error_message);

View File

@ -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) \

View File

@ -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<bool> flag_typedfuns;
const FlagScope<bool> flag_liftoff;
const FlagScope<bool> flag_liftoff_only;
const FlagScope<bool> flag_canonicalization;
const FlagScope<bool> flag_tierup;
byte DefineFunctionImpl(WasmFunctionBuilder* fun,

View File

@ -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));
})();