[wasm-gc] Optimize call_indirect type checks

Remove type check if declared signature exactly matches table
signature. Remove null check if the table is non-nullable.

Bug: v8:7748
Change-Id: Ie42bb77a40d76855dfa8379d58d9accd1e1c6d6d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/4136074
Commit-Queue: Manos Koukoutos <manoskouk@chromium.org>
Reviewed-by: Jakob Kummerow <jkummerow@chromium.org>
Cr-Commit-Position: refs/heads/main@{#85202}
This commit is contained in:
Manos Koukoutos 2023-01-04 13:31:42 +01:00 committed by V8 LUCI CQ
parent 77f99a6bb6
commit f918193221
3 changed files with 172 additions and 65 deletions

View File

@ -2855,73 +2855,91 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
Node* in_bounds = gasm_->Uint32LessThan(key, ift_size);
TrapIfFalse(wasm::kTrapTableOutOfBounds, in_bounds, position);
Node* isorecursive_canonical_types =
LOAD_INSTANCE_FIELD(IsorecursiveCanonicalTypes, MachineType::Pointer());
Node* expected_sig_id =
gasm_->LoadImmutable(MachineType::Uint32(), isorecursive_canonical_types,
gasm_->IntPtrConstant(sig_index * kInt32Size));
wasm::ValueType table_type = env_->module->tables[table_index].type;
Node* int32_scaled_key = gasm_->BuildChangeUint32ToUintPtr(
gasm_->Word32Shl(key, Int32Constant(2)));
Node* loaded_sig = gasm_->LoadFromObject(MachineType::Int32(), ift_sig_ids,
int32_scaled_key);
Node* sig_match = gasm_->Word32Equal(loaded_sig, expected_sig_id);
bool needs_type_check = !wasm::EquivalentTypes(
table_type.AsNonNull(), wasm::ValueType::Ref(sig_index), env_->module,
env_->module);
bool needs_null_check = table_type.is_nullable();
if (v8_flags.experimental_wasm_gc &&
!env_->module->types[sig_index].is_final) {
// Do a full subtyping check.
// TODO(7748): Optimize for non-nullable tables.
// TODO(7748): Optimize if type annotation matches table type.
auto end_label = gasm_->MakeLabel();
gasm_->GotoIf(sig_match, &end_label);
// Skip check if table type matches declared signature.
if (needs_type_check) {
Node* isorecursive_canonical_types =
LOAD_INSTANCE_FIELD(IsorecursiveCanonicalTypes, MachineType::Pointer());
Node* expected_sig_id = gasm_->LoadImmutable(
MachineType::Uint32(), isorecursive_canonical_types,
gasm_->IntPtrConstant(sig_index * kInt32Size));
// Trap on null element.
Node* int32_scaled_key = gasm_->BuildChangeUint32ToUintPtr(
gasm_->Word32Shl(key, Int32Constant(2)));
Node* loaded_sig = gasm_->LoadFromObject(MachineType::Int32(), ift_sig_ids,
int32_scaled_key);
Node* sig_match = gasm_->Word32Equal(loaded_sig, expected_sig_id);
if (v8_flags.experimental_wasm_gc &&
!env_->module->types[sig_index].is_final) {
// Do a full subtyping check.
auto end_label = gasm_->MakeLabel();
gasm_->GotoIf(sig_match, &end_label);
// Trap on null element.
if (needs_null_check) {
TrapIfTrue(wasm::kTrapFuncSigMismatch,
gasm_->Word32Equal(loaded_sig, Int32Constant(-1)), position);
}
Node* formal_rtt = RttCanon(sig_index);
int rtt_depth = wasm::GetSubtypingDepth(env_->module, sig_index);
DCHECK_GE(rtt_depth, 0);
// Since we have the canonical index of the real rtt, we have to load it
// from the isolate rtt-array (which is canonically indexed). Since this
// reference is weak, we have to promote it to a strong reference.
// Note: The reference cannot have been cleared: Since the loaded_sig
// corresponds to a function of the same canonical type, that function
// will have kept the type alive.
Node* rtts = LOAD_ROOT(WasmCanonicalRtts, wasm_canonical_rtts);
Node* real_rtt =
gasm_->WordAnd(gasm_->LoadWeakArrayListElement(rtts, loaded_sig),
gasm_->IntPtrConstant(~kWeakHeapObjectMask));
Node* type_info = gasm_->LoadWasmTypeInfo(real_rtt);
// If the depth of the rtt is known to be less than the minimum supertype
// array length, we can access the supertype without bounds-checking the
// supertype array.
if (static_cast<uint32_t>(rtt_depth) >=
wasm::kMinimumSupertypeArraySize) {
Node* supertypes_length =
gasm_->BuildChangeSmiToIntPtr(gasm_->LoadImmutableFromObject(
MachineType::TaggedSigned(), type_info,
wasm::ObjectAccess::ToTagged(
WasmTypeInfo::kSupertypesLengthOffset)));
TrapIfFalse(wasm::kTrapFuncSigMismatch,
gasm_->UintLessThan(gasm_->IntPtrConstant(rtt_depth),
supertypes_length),
position);
}
Node* maybe_match = gasm_->LoadImmutableFromObject(
MachineType::TaggedPointer(), type_info,
wasm::ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset +
kTaggedSize * rtt_depth));
TrapIfFalse(wasm::kTrapFuncSigMismatch,
gasm_->TaggedEqual(maybe_match, formal_rtt), position);
gasm_->Goto(&end_label);
gasm_->Bind(&end_label);
} else {
// In absence of subtyping, we just need to check for type equality.
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
}
} else if (needs_null_check) {
Node* int32_scaled_key = gasm_->BuildChangeUint32ToUintPtr(
gasm_->Word32Shl(key, Int32Constant(2)));
Node* loaded_sig = gasm_->LoadFromObject(MachineType::Int32(), ift_sig_ids,
int32_scaled_key);
TrapIfTrue(wasm::kTrapFuncSigMismatch,
gasm_->Word32Equal(loaded_sig, Int32Constant(-1)), position);
Node* formal_rtt = RttCanon(sig_index);
int rtt_depth = wasm::GetSubtypingDepth(env_->module, sig_index);
DCHECK_GE(rtt_depth, 0);
// Since we have the canonical index of the real rtt, we have to load it
// from the isolate rtt-array (which is canonically indexed). Since this
// reference is weak, we have to promote it to a strong reference.
// Note: The reference cannot have been cleared: Since the loaded_sig
// corresponds to a function of the same canonical type, that function will
// have kept the type alive.
Node* rtts = LOAD_ROOT(WasmCanonicalRtts, wasm_canonical_rtts);
Node* real_rtt =
gasm_->WordAnd(gasm_->LoadWeakArrayListElement(rtts, loaded_sig),
gasm_->IntPtrConstant(~kWeakHeapObjectMask));
Node* type_info = gasm_->LoadWasmTypeInfo(real_rtt);
// If the depth of the rtt is known to be less than the minimum supertype
// array length, we can access the supertype without bounds-checking the
// supertype array.
if (static_cast<uint32_t>(rtt_depth) >= wasm::kMinimumSupertypeArraySize) {
Node* supertypes_length =
gasm_->BuildChangeSmiToIntPtr(gasm_->LoadImmutableFromObject(
MachineType::TaggedSigned(), type_info,
wasm::ObjectAccess::ToTagged(
WasmTypeInfo::kSupertypesLengthOffset)));
TrapIfFalse(wasm::kTrapFuncSigMismatch,
gasm_->UintLessThan(gasm_->IntPtrConstant(rtt_depth),
supertypes_length),
position);
}
Node* maybe_match = gasm_->LoadImmutableFromObject(
MachineType::TaggedPointer(), type_info,
wasm::ObjectAccess::ToTagged(WasmTypeInfo::kSupertypesOffset +
kTaggedSize * rtt_depth));
TrapIfFalse(wasm::kTrapFuncSigMismatch,
gasm_->TaggedEqual(maybe_match, formal_rtt), position);
gasm_->Goto(&end_label);
gasm_->Bind(&end_label);
} else {
// In absence of subtyping, we just need to check for type equality.
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
}
Node* key_intptr = gasm_->BuildChangeUint32ToUintPtr(key);

View File

@ -7399,7 +7399,14 @@ class LiftoffCompiler {
index, table_size, trapping);
}
}
{
ValueType table_type = decoder->module_->tables[imm.table_imm.index].type;
bool needs_type_check = !EquivalentTypes(
table_type.AsNonNull(), ValueType::Ref(imm.sig_imm.index),
decoder->module_, decoder->module_);
bool needs_null_check = table_type.is_nullable();
if (needs_type_check) {
CODE_COMMENT("Check indirect call signature");
Register real_sig_id = tmp1;
Register formal_sig_id = tmp2;
@ -7434,8 +7441,10 @@ class LiftoffCompiler {
FREEZE_STATE(frozen);
__ emit_cond_jump(kEqual, &success_label, kI32, real_sig_id,
formal_sig_id, frozen);
__ emit_i32_cond_jumpi(kEqual, sig_mismatch_label, real_sig_id, -1,
frozen);
if (needs_null_check) {
__ emit_i32_cond_jumpi(kEqual, sig_mismatch_label, real_sig_id, -1,
frozen);
}
Register real_rtt = tmp3;
LOAD_INSTANCE_FIELD(real_rtt, IsolateRoot, kSystemPointerSize, pinned);
__ LoadFullPointer(
@ -7491,6 +7500,33 @@ class LiftoffCompiler {
__ emit_cond_jump(kUnequal, sig_mismatch_label, kI32, real_sig_id,
formal_sig_id, trapping);
}
} else if (needs_null_check) {
CODE_COMMENT("Check indirect call element for nullity");
Register real_sig_id = tmp1;
// Load the signature from {instance->ift_sig_ids[key]}
if (imm.table_imm.index == 0) {
LOAD_INSTANCE_FIELD(real_sig_id, IndirectFunctionTableSigIds,
kSystemPointerSize, pinned);
} else {
__ Load(LiftoffRegister(real_sig_id), indirect_function_table, no_reg,
wasm::ObjectAccess::ToTagged(
WasmIndirectFunctionTable::kSigIdsOffset),
kPointerLoadType);
}
static_assert((1 << 2) == kInt32Size);
__ Load(LiftoffRegister(real_sig_id), real_sig_id, index, 0,
LoadType::kI32Load, nullptr, false, false, true);
Label* sig_mismatch_label =
AddOutOfLineTrap(decoder, WasmCode::kThrowWasmTrapFuncSigMismatch);
__ DropValues(1);
FREEZE_STATE(frozen);
__ emit_i32_cond_jumpi(kEqual, sig_mismatch_label, real_sig_id, -1,
frozen);
} else {
__ DropValues(1);
}
{
CODE_COMMENT("Execute indirect call");

View File

@ -575,3 +575,56 @@ d8.file.execute('test/mjsunit/wasm/wasm-module-builder.js');
wasmTable.set(2, instance.exports.create_struct(333));
assertEquals(333, instance.exports.struct_getter(2));
})();
(function TestTypedTableCallIndirect() {
print(arguments.callee.name);
let builder = new WasmModuleBuilder();
let super_struct = builder.addStruct([makeField(kWasmI32, false)]);
let sub_struct = builder.addStruct(
[makeField(kWasmI32, false), makeField(kWasmI32, false)], super_struct);
let super_sig = builder.addType(
makeSig([kWasmI32], [wasmRefType(super_struct)]));
let sub_sig = builder.addType(
makeSig([kWasmI32], [wasmRefType(sub_struct)]), super_sig);
let super_func = builder.addFunction("super_func", super_sig)
.addBody([kExprLocalGet, 0, kGCPrefix, kExprStructNew, super_struct]);
let sub_func = builder.addFunction("super_func", sub_sig)
.addBody([kExprLocalGet, 0, kExprI32Const, 1, kExprI32Add,
kExprLocalGet, 0, kExprI32Const, 2, kExprI32Add,
kGCPrefix, kExprStructNew, sub_struct]);
let table = builder.addTable(wasmRefNullType(super_sig), 10, 10);
builder.addActiveElementSegment(
table.index, wasmI32Const(0),
[[kExprRefFunc, super_func.index], [kExprRefFunc, sub_func.index]],
wasmRefType(super_sig));
// Parameters: index, value.
builder.addFunction("call_indirect_super", kSig_i_ii)
.addBody([kExprLocalGet, 1, kExprLocalGet, 0,
kExprCallIndirect, super_sig, table.index,
kGCPrefix, kExprStructGet, super_struct, 0])
.exportFunc();
builder.addFunction("call_indirect_sub", kSig_i_ii)
.addBody([kExprLocalGet, 1, kExprLocalGet, 0,
kExprCallIndirect, sub_sig, table.index,
kGCPrefix, kExprStructGet, sub_struct, 0])
.exportFunc();
let instance = builder.instantiate();
// No type check needed, null check needed.
assertEquals(10, instance.exports.call_indirect_super(0, 10));
assertEquals(11, instance.exports.call_indirect_super(1, 10));
assertTraps(kTrapFuncSigMismatch,
() => instance.exports.call_indirect_super(2, 10));
// Type check and null check needed.
assertEquals(11, instance.exports.call_indirect_sub(1, 10));
assertTraps(kTrapFuncSigMismatch,
() => instance.exports.call_indirect_sub(0, 10));
assertTraps(kTrapFuncSigMismatch,
() => instance.exports.call_indirect_sub(2, 10));
})();