[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:
parent
77f99a6bb6
commit
f918193221
@ -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);
|
||||
|
@ -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");
|
||||
|
@ -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));
|
||||
})();
|
||||
|
Loading…
Reference in New Issue
Block a user