Skip to content

Commit e3acd9f

Browse files
manoskoukCommit Bot
authored andcommitted
[wasm-gc] Implement non-nullable function tables
This adds the possibility to define non-nullable function tables of heap types kFunc and user-defined functions. When such table is defined, it is obligatory to provide an initializer expression after its limits. Currently, this can only be a function reference. Changes: - Change WasmTableObject::raw_type to encode the whole entry type. - Restructure call_indirect to load the signature only if needed, and do null checks only if needed. - Add the requirement to provide an initializer expression for non-nullable tables in module-decoder. - Rename "global initializer" -> "initializer expression" everywhere. - Add table initialization in module-instantiate. - Edit both the C++ and JS WasmModuleBuilder. - Add and slightly improve tests. - Format wasm-module-builder.js. Bug: v8:9495 Change-Id: I7453ee7d567afd5b5fe48a4f1653513787cfe99a Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2732673 Commit-Queue: Manos Koukoutos <manoskouk@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/master@{#73215}
1 parent 476b527 commit e3acd9f

15 files changed

Lines changed: 251 additions & 68 deletions

src/builtins/wasm.tq

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@ extern macro Allocate(intptr, constexpr AllocationFlag): HeapObject;
4141
}
4242

4343
namespace wasm {
44-
const kFuncTableType: constexpr int31 generates 'wasm::HeapType::kFunc';
44+
const kExternTableType: constexpr int31
45+
generates 'wasm::kWasmExternRef.raw_bit_field()';
46+
const kExternNonNullTableType: constexpr int31
47+
generates 'wasm::kWasmExternNonNullableRef.raw_bit_field()';
4548

4649
extern macro WasmBuiltinsAssembler::LoadInstanceFromFrame(): WasmInstanceObject;
4750

@@ -177,8 +180,12 @@ builtin WasmTableSet(tableIndex: intptr, index: int32, value: Object): Object {
177180

178181
// Fall back to the runtime to set funcrefs, since we have to update
179182
// function dispatch tables.
183+
// TODO(7748): Update this if further table types are supported.
180184
const tableType: Smi = table.raw_type;
181-
if (tableType == SmiConstant(kFuncTableType)) goto CallRuntime;
185+
if (tableType != SmiConstant(kExternTableType) &&
186+
tableType != SmiConstant(kExternNonNullTableType)) {
187+
goto CallRuntime;
188+
}
182189

183190
const entriesCount: intptr = Convert<intptr, Smi>(table.current_length);
184191
if (entryIndex >= entriesCount) goto IndexOutOfRange;

src/compiler/wasm-compiler.cc

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3007,28 +3007,35 @@ Node* WasmGraphBuilder::BuildIndirectCall(uint32_t table_index,
30073007
key = gasm_->Word32And(key, mask);
30083008
}
30093009

3010-
Node* int32_scaled_key =
3011-
Uint32ToUintptr(gasm_->Word32Shl(key, Int32Constant(2)));
3012-
3013-
Node* loaded_sig =
3014-
gasm_->Load(MachineType::Int32(), ift_sig_ids, int32_scaled_key);
3015-
// Check that the dynamic type of the function is a subtype of its static
3016-
// (table) type. Currently, the only subtyping between function types is
3017-
// $t <: funcref for all $t: function_type.
3018-
// TODO(7748): Expand this with function subtyping.
3019-
const bool needs_typechecking =
3020-
env_->module->tables[table_index].type == wasm::kWasmFuncRef;
3021-
if (needs_typechecking) {
3022-
int32_t expected_sig_id = env_->module->canonicalized_type_ids[sig_index];
3023-
Node* sig_match =
3024-
gasm_->Word32Equal(loaded_sig, Int32Constant(expected_sig_id));
3025-
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
3026-
} else {
3027-
// We still have to check that the entry is initialized.
3028-
// TODO(9495): Skip this check for non-nullable tables when they are
3029-
// allowed.
3030-
Node* function_is_null = gasm_->Word32Equal(loaded_sig, Int32Constant(-1));
3031-
TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position);
3010+
const wasm::ValueType table_type = env_->module->tables[table_index].type;
3011+
// Check that the table entry is not null and that the type of the function is
3012+
// a subtype of the function type declared at the call site. In the absence of
3013+
// function subtyping, the latter can only happen if the table type is (ref
3014+
// null? func). Also, subtyping reduces to normalized signature equality
3015+
// checking.
3016+
// TODO(7748): Expand this with function subtyping once we have that.
3017+
const bool needs_signature_check =
3018+
table_type.is_reference_to(wasm::HeapType::kFunc) ||
3019+
table_type.is_nullable();
3020+
if (needs_signature_check) {
3021+
Node* int32_scaled_key =
3022+
Uint32ToUintptr(gasm_->Word32Shl(key, Int32Constant(2)));
3023+
3024+
Node* loaded_sig =
3025+
gasm_->Load(MachineType::Int32(), ift_sig_ids, int32_scaled_key);
3026+
3027+
if (table_type.is_reference_to(wasm::HeapType::kFunc)) {
3028+
int32_t expected_sig_id = env_->module->canonicalized_type_ids[sig_index];
3029+
Node* sig_match =
3030+
gasm_->Word32Equal(loaded_sig, Int32Constant(expected_sig_id));
3031+
TrapIfFalse(wasm::kTrapFuncSigMismatch, sig_match, position);
3032+
} else {
3033+
// If the table entries are nullable, we still have to check that the
3034+
// entry is initialized.
3035+
Node* function_is_null =
3036+
gasm_->Word32Equal(loaded_sig, Int32Constant(-1));
3037+
TrapIfTrue(wasm::kTrapNullDereference, function_is_null, position);
3038+
}
30323039
}
30333040

30343041
Node* key_intptr = Uint32ToUintptr(key);

src/wasm/baseline/liftoff-compiler.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5190,6 +5190,7 @@ class LiftoffCompiler {
51905190
__ Load(LiftoffRegister(scratch), table, index, 0, LoadType::kI32Load,
51915191
pinned);
51925192

5193+
// TODO(9495): Do not always compare signatures, same as wasm-compiler.cc.
51935194
// Compare against expected signature.
51945195
__ LoadConstant(LiftoffRegister(tmp_const), WasmValue(canonical_sig_num));
51955196

src/wasm/module-decoder.cc

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,9 @@ class ModuleDecoderImpl : public Decoder {
728728
"table elements", "elements", std::numeric_limits<uint32_t>::max(),
729729
&table->initial_size, &table->has_maximum_size,
730730
std::numeric_limits<uint32_t>::max(), &table->maximum_size, flags);
731+
if (!table_type.is_defaultable()) {
732+
table->initial_value = consume_init_expr(module_.get(), table_type, 0);
733+
}
731734
}
732735
}
733736

@@ -1713,7 +1716,7 @@ class ModuleDecoderImpl : public Decoder {
17131716
if (V8_UNLIKELY(!enabled_features_.has_reftypes() &&
17141717
!enabled_features_.has_eh())) {
17151718
errorf(pc(),
1716-
"invalid opcode 0x%x in global initializer, enable with "
1719+
"invalid opcode 0x%x in initializer expression, enable with "
17171720
"--experimental-wasm-reftypes or --experimental-wasm-eh",
17181721
kExprRefNull);
17191722
return {};
@@ -1729,7 +1732,7 @@ class ModuleDecoderImpl : public Decoder {
17291732
case kExprRefFunc: {
17301733
if (V8_UNLIKELY(!enabled_features_.has_reftypes())) {
17311734
errorf(pc(),
1732-
"invalid opcode 0x%x in global initializer, enable with "
1735+
"invalid opcode 0x%x in initializer expression, enable with "
17331736
"--experimental-wasm-reftypes",
17341737
kExprRefFunc);
17351738
return {};
@@ -1752,7 +1755,7 @@ class ModuleDecoderImpl : public Decoder {
17521755
// the type check or stack height check at the end.
17531756
opcode = read_prefixed_opcode<validate>(pc(), &len);
17541757
if (V8_UNLIKELY(opcode != kExprS128Const)) {
1755-
errorf(pc(), "invalid SIMD opcode 0x%x in global initializer",
1758+
errorf(pc(), "invalid SIMD opcode 0x%x in initializer expression",
17561759
opcode);
17571760
return {};
17581761
}
@@ -1804,7 +1807,8 @@ class ModuleDecoderImpl : public Decoder {
18041807
break;
18051808
}
18061809
default: {
1807-
errorf(pc(), "invalid opcode 0x%x in global initializer", opcode);
1810+
errorf(pc(), "invalid opcode 0x%x in initializer expression",
1811+
opcode);
18081812
return {};
18091813
}
18101814
}
@@ -1813,24 +1817,24 @@ class ModuleDecoderImpl : public Decoder {
18131817
case kExprEnd:
18141818
break;
18151819
default: {
1816-
errorf(pc(), "invalid opcode 0x%x in global initializer", opcode);
1820+
errorf(pc(), "invalid opcode 0x%x in initializer expression", opcode);
18171821
return {};
18181822
}
18191823
}
18201824
pc_ += len;
18211825
}
18221826

18231827
if (V8_UNLIKELY(pc() > end())) {
1824-
error(end(), "Global initializer extending beyond code end");
1828+
error(end(), "Initializer expression extending beyond code end");
18251829
return {};
18261830
}
18271831
if (V8_UNLIKELY(opcode != kExprEnd)) {
1828-
error(pc(), "Global initializer is missing 'end'");
1832+
error(pc(), "Initializer expression is missing 'end'");
18291833
return {};
18301834
}
18311835
if (V8_UNLIKELY(stack.size() != 1)) {
18321836
errorf(pc(),
1833-
"Found 'end' in global initalizer, but %s expressions were "
1837+
"Found 'end' in initializer expression, but %s expressions were "
18341838
"found on the stack",
18351839
stack.size() > 1 ? "more than one" : "no");
18361840
return {};

src/wasm/module-instantiate.cc

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1841,12 +1841,50 @@ void InstanceBuilder::ProcessExports(Handle<WasmInstanceObject> instance) {
18411841

18421842
void InstanceBuilder::InitializeIndirectFunctionTables(
18431843
Handle<WasmInstanceObject> instance) {
1844-
for (int i = 0; i < static_cast<int>(module_->tables.size()); ++i) {
1845-
const WasmTable& table = module_->tables[i];
1844+
for (int table_index = 0;
1845+
table_index < static_cast<int>(module_->tables.size()); ++table_index) {
1846+
const WasmTable& table = module_->tables[table_index];
18461847

18471848
if (IsSubtypeOf(table.type, kWasmFuncRef, module_)) {
18481849
WasmInstanceObject::EnsureIndirectFunctionTableWithMinimumSize(
1849-
instance, i, table.initial_size);
1850+
instance, table_index, table.initial_size);
1851+
}
1852+
1853+
if (!table.type.is_defaultable()) {
1854+
// Function constant is currently the only viable initializer.
1855+
DCHECK(table.initial_value.kind() == WasmInitExpr::kRefFuncConst);
1856+
uint32_t func_index = table.initial_value.immediate().index;
1857+
1858+
uint32_t sig_id =
1859+
module_->canonicalized_type_ids[module_->functions[func_index]
1860+
.sig_index];
1861+
MaybeHandle<WasmExternalFunction> wasm_external_function =
1862+
WasmInstanceObject::GetWasmExternalFunction(isolate_, instance,
1863+
func_index);
1864+
auto table_object = handle(
1865+
WasmTableObject::cast(instance->tables().get(table_index)), isolate_);
1866+
for (uint32_t entry_index = 0; entry_index < table.initial_size;
1867+
entry_index++) {
1868+
// Update the local dispatch table first.
1869+
IndirectFunctionTableEntry(instance, table_index, entry_index)
1870+
.Set(sig_id, instance, func_index);
1871+
1872+
// Update the table object's other dispatch tables.
1873+
if (wasm_external_function.is_null()) {
1874+
// No JSFunction entry yet exists for this function. Create a {Tuple2}
1875+
// holding the information to lazily allocate one.
1876+
WasmTableObject::SetFunctionTablePlaceholder(
1877+
isolate_, table_object, entry_index, instance, func_index);
1878+
} else {
1879+
table_object->entries().set(
1880+
entry_index, *wasm_external_function.ToHandleChecked());
1881+
}
1882+
// UpdateDispatchTables() updates all other dispatch tables, since
1883+
// we have not yet added the dispatch table we are currently building.
1884+
WasmTableObject::UpdateDispatchTables(
1885+
isolate_, table_object, entry_index,
1886+
module_->functions[func_index].sig, instance, func_index);
1887+
}
18501888
}
18511889
}
18521890
}

src/wasm/value-type.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -530,6 +530,8 @@ class ValueType {
530530

531531
static_assert(sizeof(ValueType) <= kUInt32Size,
532532
"ValueType is small and can be passed by value");
533+
static_assert(ValueType::kLastUsedBit < 8 * sizeof(ValueType) - kSmiTagSize,
534+
"ValueType has space to be encoded in a Smi");
533535

534536
inline size_t hash_value(ValueType type) {
535537
return static_cast<size_t>(type.kind());
@@ -560,6 +562,10 @@ constexpr ValueType kWasmDataRef =
560562
ValueType::Ref(HeapType::kData, kNonNullable);
561563
constexpr ValueType kWasmAnyRef = ValueType::Ref(HeapType::kAny, kNullable);
562564

565+
// This is used in wasm.tq.
566+
constexpr ValueType kWasmExternNonNullableRef =
567+
ValueType::Ref(HeapType::kExtern, kNonNullable);
568+
563569
#define FOREACH_WASMVALUE_CTYPES(V) \
564570
V(kI32, int32_t) \
565571
V(kI64, int64_t) \

src/wasm/wasm-module-builder.cc

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ uint32_t WasmModuleBuilder::AllocateIndirectFunctions(uint32_t count) {
315315
if (tables_.empty()) {
316316
// This cannot use {AddTable} because that would flip the
317317
// {allocating_indirect_functions_allowed_} flag.
318-
tables_.push_back({kWasmFuncRef, new_size, max, true});
318+
tables_.push_back({kWasmFuncRef, new_size, max, true, {}});
319319
} else {
320320
// There can only be the indirect function table so far, otherwise the
321321
// {allocating_indirect_functions_allowed_} flag would have been false.
@@ -347,7 +347,7 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size) {
347347
#if DEBUG
348348
allocating_indirect_functions_allowed_ = false;
349349
#endif
350-
tables_.push_back({type, min_size, 0, false});
350+
tables_.push_back({type, min_size, 0, false, {}});
351351
return static_cast<uint32_t>(tables_.size() - 1);
352352
}
353353

@@ -356,7 +356,16 @@ uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size,
356356
#if DEBUG
357357
allocating_indirect_functions_allowed_ = false;
358358
#endif
359-
tables_.push_back({type, min_size, max_size, true});
359+
tables_.push_back({type, min_size, max_size, true, {}});
360+
return static_cast<uint32_t>(tables_.size() - 1);
361+
}
362+
363+
uint32_t WasmModuleBuilder::AddTable(ValueType type, uint32_t min_size,
364+
uint32_t max_size, WasmInitExpr init) {
365+
#if DEBUG
366+
allocating_indirect_functions_allowed_ = false;
367+
#endif
368+
tables_.push_back({type, min_size, max_size, true, std::move(init)});
360369
return static_cast<uint32_t>(tables_.size() - 1);
361370
}
362371

@@ -432,8 +441,8 @@ void WriteValueType(ZoneBuffer* buffer, const ValueType& type) {
432441
}
433442
}
434443

435-
void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init,
436-
ValueType type) {
444+
void WriteInitializerExpression(ZoneBuffer* buffer, const WasmInitExpr& init,
445+
ValueType type) {
437446
switch (init.kind()) {
438447
case WasmInitExpr::kI32Const:
439448
buffer->write_u8(kExprI32Const);
@@ -512,7 +521,7 @@ void WriteGlobalInitializer(ZoneBuffer* buffer, const WasmInitExpr& init,
512521
break;
513522
case WasmInitExpr::kRttSub:
514523
// The operand to rtt.sub must be emitted first.
515-
WriteGlobalInitializer(buffer, *init.operand(), kWasmBottom);
524+
WriteInitializerExpression(buffer, *init.operand(), kWasmBottom);
516525
STATIC_ASSERT((kExprRttSub >> 8) == kGCPrefix);
517526
buffer->write_u8(kGCPrefix);
518527
buffer->write_u8(static_cast<uint8_t>(kExprRttSub));
@@ -611,6 +620,9 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
611620
buffer->write_u8(table.has_maximum ? kWithMaximum : kNoMaximum);
612621
buffer->write_size(table.min_size);
613622
if (table.has_maximum) buffer->write_size(table.max_size);
623+
if (table.init.kind() != WasmInitExpr::kNone) {
624+
WriteInitializerExpression(buffer, table.init, table.type);
625+
}
614626
}
615627
FixupSection(buffer, start);
616628
}
@@ -651,7 +663,7 @@ void WasmModuleBuilder::WriteTo(ZoneBuffer* buffer) const {
651663
for (const WasmGlobal& global : globals_) {
652664
WriteValueType(buffer, global.type);
653665
buffer->write_u8(global.mutability ? 1 : 0);
654-
WriteGlobalInitializer(buffer, global.init, global.type);
666+
WriteInitializerExpression(buffer, global.init, global.type);
655667
buffer->write_u8(kExprEnd);
656668
}
657669
FixupSection(buffer, start);

src/wasm/wasm-module-builder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,8 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
261261
void SetMaxTableSize(uint32_t max);
262262
uint32_t AddTable(ValueType type, uint32_t min_size);
263263
uint32_t AddTable(ValueType type, uint32_t min_size, uint32_t max_size);
264+
uint32_t AddTable(ValueType type, uint32_t min_size, uint32_t max_size,
265+
WasmInitExpr init);
264266
void MarkStartFunction(WasmFunctionBuilder* builder);
265267
void AddExport(Vector<const char> name, ImportExportKindCode kind,
266268
uint32_t index);
@@ -340,6 +342,7 @@ class V8_EXPORT_PRIVATE WasmModuleBuilder : public ZoneObject {
340342
uint32_t min_size;
341343
uint32_t max_size;
342344
bool has_maximum;
345+
WasmInitExpr init;
343346
};
344347

345348
struct WasmDataSegment {

src/wasm/wasm-module.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ struct WasmTable {
354354
// TODO(9495): Update this function as more table types are supported, or
355355
// remove it completely when all reference types are allowed.
356356
static bool IsValidTableType(ValueType type, const WasmModule* module) {
357-
if (!type.is_nullable()) return false;
357+
if (!type.is_object_reference()) return false;
358358
HeapType heap_type = type.heap_type();
359359
return heap_type == HeapType::kFunc || heap_type == HeapType::kExtern ||
360360
(module != nullptr && heap_type.is_index() &&
@@ -367,6 +367,7 @@ struct WasmTable {
367367
bool has_maximum_size = false; // true if there is a maximum size.
368368
bool imported = false; // true if imported.
369369
bool exported = false; // true if exported.
370+
WasmInitExpr initial_value;
370371
};
371372

372373
inline bool is_asmjs_module(const WasmModule* module) {

src/wasm/wasm-objects-inl.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,7 @@ ACCESSORS(WasmIndirectFunctionTable, refs, FixedArray, kRefsOffset)
393393
#undef PRIMITIVE_ACCESSORS
394394

395395
wasm::ValueType WasmTableObject::type() {
396-
// TODO(7748): Support other table types? Wait for spec to clear up.
397-
return wasm::ValueType::Ref(raw_type(), wasm::kNullable);
396+
return wasm::ValueType::FromRawBitField(raw_type());
398397
}
399398

400399
bool WasmMemoryObject::has_maximum_pages() { return maximum_pages() >= 0; }

0 commit comments

Comments
 (0)