diff --git a/js/src/jit-test/tests/wasm/function-references/non-nullable-table.js b/js/src/jit-test/tests/wasm/function-references/non-nullable-table.js new file mode 100644 index 000000000000..97ab04713cb7 --- /dev/null +++ b/js/src/jit-test/tests/wasm/function-references/non-nullable-table.js @@ -0,0 +1,79 @@ +// |jit-test| skip-if: !wasmFunctionReferencesEnabled() + +// non-null table initialization +var { get1, get2, get3, get4 } = wasmEvalText(`(module + (type $dummy (func)) + (func $dummy) + + (table $t1 10 funcref) + (table $t2 10 funcref (ref.func $dummy)) + (table $t3 10 (ref $dummy) (ref.func $dummy)) + (table $t4 10 (ref func) (ref.func $dummy)) + + (func (export "get1") (result funcref) (table.get $t1 (i32.const 1))) + (func (export "get2") (result funcref) (table.get $t2 (i32.const 4))) + (func (export "get3") (result funcref) (table.get $t3 (i32.const 7))) + (func (export "get4") (result funcref) (table.get $t4 (i32.const 5))) +)`).exports; +assertEq(get1(), null); +assertEq(get2() != null, true); +assertEq(get3() != null, true); +assertEq(get4() != null, true); + +const sampleWasmFunction = get2(); +sampleWasmFunction(); + +// Invalid initializers +for (let i of [ + `(table $t1 10 (ref $dummy) (ref.func $dummy1))`, + `(table $t2 5 10 (ref func))`, + `(table $t3 10 10 (ref func) (ref.null $dummy1))`, + `(table $t4 10 (ref $dummy))`, + '(table $t5 1 (ref $dummy) (ref.null $dummy))', +]) { + wasmFailValidateText(`(module + (type $dummy (func)) + (type $dummy1 (func (param i32))) + (func $dummy1 (param i32)) + + ${i} + )`, /(type mismatch|table with non-nullable references requires initializer)/); +} + +var t1 = new WebAssembly.Table({initial: 10, element: {ref: 'func', nullable: false }}, sampleWasmFunction); +assertEq(t1.get(2) != null, true); +assertThrows(() => { + new WebAssembly.Table({initial: 10, element: {ref: 'func', nullable: false }}); +}); +assertThrows(() => { + new WebAssembly.Table({initial: 10, element: {ref: 'func', nullable: false }}, null); +}); + +var t2 = new WebAssembly.Table({initial: 6, maximum: 20, element: {ref: 'extern', nullable: false }}, {foo: "bar"}); +assertEq(t2.get(1).foo, "bar"); +assertThrows(() => { t2.get(7) }); +assertThrows(() => { t2.grow(9, null) }); +t2.grow(8, {t: "test"}); +assertEq(t2.get(3).foo, "bar"); +assertEq(t2.get(7).t, "test"); +assertThrows(() => { + new WebAssembly.Table({initial: 10, element: {ref: 'extern', nullable: false }}, null); +}); + +// Fail because tables come before globals in the binary format, so tables +// cannot refer to globals. +wasmFailValidateText(`(module + (global $g1 externref) + (table 10 externref (global.get $g1)) +)`, /global.get index out of range/); + +function assertThrows(f) { + var ok = false; + try { + f(); + } catch (exc) { + ok = true; + } + if (!ok) + throw new TypeError("Assertion failed: " + f + " did not throw as expected"); +} diff --git a/js/src/jit-test/tests/wasm/function-references/non-nullable.js b/js/src/jit-test/tests/wasm/function-references/non-nullable.js index f513844d28cc..b8fd20fd8393 100644 --- a/js/src/jit-test/tests/wasm/function-references/non-nullable.js +++ b/js/src/jit-test/tests/wasm/function-references/non-nullable.js @@ -159,10 +159,10 @@ assertErrorMessage(() => runMultiNullStack(), TypeError, /cannot pass null to no )`); } -// cannot have non-nullable tables +// cannot have non-nullable tables without initializer wasmFailValidateText(`(module (table (ref extern) (elem)) -)`, /non-nullable references not supported in tables/); +)`, /table with non-nullable references requires initializer/); // Testing internal wasmLosslessInvoke to pass non-nullable as params and arguments. let {t} = wasmEvalText(`(module diff --git a/js/src/jit-test/tests/wasm/function-references/reftype-parse.js b/js/src/jit-test/tests/wasm/function-references/reftype-parse.js index a233d10d8f33..643f753ec8c3 100644 --- a/js/src/jit-test/tests/wasm/function-references/reftype-parse.js +++ b/js/src/jit-test/tests/wasm/function-references/reftype-parse.js @@ -20,7 +20,14 @@ assertErrorMessage( // RefType/ValueType can be specified as an {ref: 'func', ...} object const t11 = new WebAssembly.Table({element: {ref: 'func', nullable: true}, initial: 3}); const t12 = new WebAssembly.Table({element: {ref: 'extern', nullable: true}, initial: 3}); -// TODO new WebAssembly.Table({element: {ref: 'func', nullable: false}, initial: 3}), +const t13 = new WebAssembly.Table({element: {ref: 'extern', nullable: false}, initial: 3}, {}); + +assertErrorMessage( + () => new WebAssembly.Table({element: {ref: 'func', nullable: false}, initial: 1}, null), + TypeError, /cannot pass null to non-nullable WebAssembly reference/); +assertErrorMessage( + () => new WebAssembly.Table({element: {ref: 'extern', nullable: false}, initial: 1}, null), + TypeError, /cannot pass null to non-nullable WebAssembly reference/); assertErrorMessage( () => new WebAssembly.Table({element: {ref: 'bar', nullable: true}, initial: 1}), @@ -28,8 +35,17 @@ assertErrorMessage( const g11 = new WebAssembly.Global({value: {ref: 'func', nullable: true}, mutable: true}); const g12 = new WebAssembly.Global({value: {ref: 'extern', nullable: true}, mutable: true}); -// TODO new WebAssembly.Global({value: {ref: 'extern', nullable: false}, mutable: true}); +const g13 = new WebAssembly.Global({value: {ref: 'extern', nullable: false}, mutable: true}, {}); +const g14 = new WebAssembly.Global({value: {ref: 'extern', nullable: false}, mutable: true}); +const g15 = new WebAssembly.Global({value: {ref: 'extern', nullable: false}, mutable: true}, void 0); +assertErrorMessage( + () => new WebAssembly.Global({value: {ref: 'func', nullable: false}, mutable: true}), + TypeError, /cannot pass null to non-nullable WebAssembly reference/); +assertErrorMessage( + () => new WebAssembly.Global({value: {ref: 'extern', nullable: false}, mutable: true}, null), + TypeError, /cannot pass null to non-nullable WebAssembly reference/); + assertErrorMessage( () => new WebAssembly.Global({value: {ref: 'bar', nullable: true}, mutable: true}), TypeError, /bad value type/); diff --git a/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js b/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js index 51e7b8712d1a..806896e9b850 100644 --- a/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js +++ b/js/src/jit-test/tests/wasm/ref-types/externref-global-object.js @@ -10,7 +10,7 @@ assertEq(new WebAssembly.Global({value: "externref"}) instanceof WebAssembly.Glo (function() { // Test initialization without a value. let g = new WebAssembly.Global({value: "externref"}); - assertEq(g.value, null); + assertEq(g.value, void 0); assertErrorMessage(() => g.value = 42, TypeError, /immutable global/); })(); diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index e81283b17bc4..35c15515833d 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -2053,6 +2053,7 @@ class MOZ_STACK_CLASS ModuleValidator : public ModuleValidatorShared { moduleEnv_.asmJSSigToTableIndex[sigIndex] = moduleEnv_.tables.length(); if (!moduleEnv_.tables.emplaceBack(RefType::func(), mask + 1, Nothing(), + /* initExpr */ Nothing(), /*isAsmJS*/ true)) { return false; } diff --git a/js/src/wasm/WasmConstants.h b/js/src/wasm/WasmConstants.h index 0af9216d0627..45470807a638 100644 --- a/js/src/wasm/WasmConstants.h +++ b/js/src/wasm/WasmConstants.h @@ -99,6 +99,9 @@ enum class TypeCode { // Type constructor for array types - gc proposal Array = 0x5e, // SLEB128(-0x22) + // Value for non-nullable type present. + TableHasInitExpr = 0x40, + // The 'empty' case of blocktype. BlockVoid = 0x40, // SLEB128(-0x40) diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index d08bfb7a4f42..7af6a1e58ad8 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -1029,18 +1029,14 @@ static int32_t MemDiscardShared(Instance* instance, I byteOffset, I byteLen, uint32_t oldSize = table.grow(delta); if (oldSize != uint32_t(-1) && initValue != nullptr) { - switch (table.repr()) { - case TableRepr::Ref: - table.fillAnyRef(oldSize, delta, ref); - break; - case TableRepr::Func: - MOZ_RELEASE_ASSERT(!table.isAsmJS()); - table.fillFuncRef(oldSize, delta, FuncRef::fromAnyRefUnchecked(ref), - cx); - break; - } + table.fillUninitialized(oldSize, delta, ref, cx); } +#ifdef DEBUG + if (!table.elemType().isNullable()) { + table.assertRangeNotNull(oldSize, delta); + } +#endif // DEBUG return oldSize; } @@ -1707,8 +1703,29 @@ bool Instance::init(JSContext* cx, const JSObjectVector& funcImports, TableInstanceData& table = tableInstanceData(td); table.length = tables_[i]->length(); table.elements = tables_[i]->instanceElements(); + // Non-imported tables, with init_expr, has to be initialized with + // the evaluated value. + if (!td.isImported && td.initExpr) { + Rooted instanceObj(cx, object()); + RootedVal val(cx); + if (!td.initExpr->evaluate(cx, instanceObj, &val)) { + return false; + } + RootedAnyRef ref(cx, val.get().ref()); + tables_[i]->fillUninitialized(0, tables_[i]->length(), ref, cx); + } } +#ifdef DEBUG + // All (linked) tables with non-nullable types must be initialized. + for (size_t i = 0; i < tables_.length(); i++) { + const TableDesc& td = metadata().tables[i]; + if (!td.elemType.isNullable()) { + tables_[i]->assertRangeNotNull(0, tables_[i]->length()); + } + } +#endif // DEBUG + // Initialize tags in the instance data for (size_t i = 0; i < metadata().tags.length(); i++) { const TagDesc& td = metadata().tags[i]; diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index 5ee22b5637a8..05d50b9cfd7b 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -3044,10 +3044,18 @@ void WasmTableObject::trace(JSTracer* trc, JSObject* obj) { // // [1] // https://webassembly.github.io/reference-types/js-api/index.html#defaultvalue -static Value TableDefaultValue(wasm::RefType tableType) { +static Value RefTypeDefautValue(wasm::RefType tableType) { return tableType.isExtern() ? UndefinedValue() : NullValue(); } +static bool CheckRefTypeValue(JSContext* cx, wasm::RefType type, + HandleValue value) { + RootedFunction fun(cx); + RootedAnyRef any(cx, AnyRef::null()); + + return CheckRefType(cx, type, value, &fun, &any); +} + /* static */ WasmTableObject* WasmTableObject::create(JSContext* cx, uint32_t initialLength, Maybe maximumLength, @@ -3062,8 +3070,9 @@ WasmTableObject* WasmTableObject::create(JSContext* cx, uint32_t initialLength, MOZ_ASSERT(obj->isNewborn()); - TableDesc td(tableType, initialLength, maximumLength, /*isAsmJS*/ false, - /*isImportedOrExported=*/true); + TableDesc td(tableType, initialLength, maximumLength, Nothing(), + /*isAsmJS*/ false, + /*isImported=*/true, /*isExported=*/true); SharedTable table = Table::create(cx, td, obj); if (!table) { @@ -3153,7 +3162,10 @@ bool WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) { // Initialize the table to a default value RootedValue initValue( - cx, args.length() < 2 ? TableDefaultValue(tableType) : args[1]); + cx, args.length() < 2 ? RefTypeDefautValue(tableType) : args[1]); + if (!CheckRefTypeValue(cx, tableType, initValue)) { + return false; + } // Skip initializing the table if the fill value is null, as that is the // default value. @@ -3164,7 +3176,10 @@ bool WasmTableObject::construct(JSContext* cx, unsigned argc, Value* vp) { #ifdef DEBUG // Assert that null is the default value of a new table. if (initValue.isNull()) { - table->assertRangeNull(0, initialLength); + table->table().assertRangeNull(0, initialLength); + } + if (!tableType.isNullable()) { + table->table().assertRangeNotNull(0, initialLength); } #endif @@ -3269,7 +3284,7 @@ bool WasmTableObject::setImpl(JSContext* cx, const CallArgs& args) { } RootedValue fillValue( - cx, args.length() < 2 ? TableDefaultValue(table.elemType()) : args[1]); + cx, args.length() < 2 ? RefTypeDefautValue(table.elemType()) : args[1]); if (!tableObj->fillRange(cx, index, 1, fillValue)) { return false; } @@ -3299,6 +3314,12 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { return false; } + RootedValue fillValue( + cx, args.length() < 2 ? RefTypeDefautValue(table.elemType()) : args[1]); + if (!CheckRefTypeValue(cx, table.elemType(), fillValue)) { + return false; + } + uint32_t oldLength = table.grow(delta); if (oldLength == uint32_t(-1)) { @@ -3307,10 +3328,6 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { return false; } - // Fill the grown range of the table - RootedValue fillValue( - cx, args.length() < 2 ? TableDefaultValue(table.elemType()) : args[1]); - // Skip filling the grown range of the table if the fill value is null, as // that is the default value. if (!fillValue.isNull() && @@ -3320,7 +3337,10 @@ bool WasmTableObject::growImpl(JSContext* cx, const CallArgs& args) { #ifdef DEBUG // Assert that null is the default value of the grown range. if (fillValue.isNull()) { - tableObj->assertRangeNull(oldLength, delta); + table.assertRangeNull(oldLength, delta); + } + if (!table.elemType().isNullable()) { + table.assertRangeNotNull(oldLength, delta); } #endif @@ -3373,25 +3393,6 @@ bool WasmTableObject::fillRange(JSContext* cx, uint32_t index, uint32_t length, return true; } -#ifdef DEBUG -void WasmTableObject::assertRangeNull(uint32_t index, uint32_t length) const { - Table& tab = table(); - switch (tab.repr()) { - case TableRepr::Func: - for (uint32_t i = index; i < index + length; i++) { - MOZ_ASSERT(tab.getFuncRef(i).instance == nullptr); - MOZ_ASSERT(tab.getFuncRef(i).code == nullptr); - } - break; - case TableRepr::Ref: - for (uint32_t i = index; i < index + length; i++) { - MOZ_ASSERT(tab.getAnyRef(i).isNull()); - } - break; - } -} -#endif - // ============================================================================ // WebAssembly.global class and methods @@ -3527,12 +3528,19 @@ bool WasmGlobalObject::construct(JSContext* cx, unsigned argc, Value* vp) { RootedVal globalVal(cx, globalType); // Override with non-undefined value, if provided. - RootedValue valueVal(cx, args.get(1)); - if (!valueVal.isUndefined() || - (args.length() >= 2 && globalType.isRefType())) { + RootedValue valueVal(cx); + if (globalType.isRefType()) { + valueVal.set(args.length() < 2 ? RefTypeDefautValue(globalType.refType()) + : args[1]); if (!Val::fromJSValue(cx, globalType, valueVal, &globalVal)) { return false; } + } else { + valueVal.set(args.get(1)); + if (!valueVal.isUndefined() && + !Val::fromJSValue(cx, globalType, valueVal, &globalVal)) { + return false; + } } RootedObject proto(cx, diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h index 8830e627a8d8..9963452e0e35 100644 --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -481,9 +481,6 @@ class WasmTableObject : public NativeObject { // the range is within bounds. Returns false if the coercion failed. bool fillRange(JSContext* cx, uint32_t index, uint32_t length, HandleValue value) const; -#ifdef DEBUG - void assertRangeNull(uint32_t index, uint32_t length) const; -#endif }; // The class of WebAssembly.Tag. This class is used to track exception tag diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp index cd50f0921d62..5dcb50e09a73 100644 --- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -749,7 +749,7 @@ bool Module::instantiateLocalTable(JSContext* cx, const TableDesc& td, SharedTable table; Rooted tableObj(cx); - if (td.isImportedOrExported) { + if (td.isExported) { RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable)); tableObj.set(WasmTableObject::create(cx, td.initialLength, td.maximumLength, td.elemType, proto)); diff --git a/js/src/wasm/WasmModuleTypes.h b/js/src/wasm/WasmModuleTypes.h index 3fa7bc2c27f4..1024733f6bf4 100644 --- a/js/src/wasm/WasmModuleTypes.h +++ b/js/src/wasm/WasmModuleTypes.h @@ -608,22 +608,26 @@ static_assert(MaxMemory32LimitField <= UINT64_MAX / PageSize); struct TableDesc { RefType elemType; - bool isImportedOrExported; + bool isImported; + bool isExported; bool isAsmJS; uint32_t globalDataOffset; uint32_t initialLength; Maybe maximumLength; + Maybe initExpr; TableDesc() = default; TableDesc(RefType elemType, uint32_t initialLength, - Maybe maximumLength, bool isAsmJS, - bool isImportedOrExported = false) + Maybe maximumLength, Maybe&& initExpr, + bool isAsmJS, bool isImported = false, bool isExported = false) : elemType(elemType), - isImportedOrExported(isImportedOrExported), + isImported(isImported), + isExported(isExported), isAsmJS(isAsmJS), globalDataOffset(UINT32_MAX), initialLength(initialLength), - maximumLength(maximumLength) {} + maximumLength(maximumLength), + initExpr(std::move(initExpr)) {} }; using TableDescVector = Vector; diff --git a/js/src/wasm/WasmSerialize.cpp b/js/src/wasm/WasmSerialize.cpp index e59d78693e50..59ecad4095c4 100644 --- a/js/src/wasm/WasmSerialize.cpp +++ b/js/src/wasm/WasmSerialize.cpp @@ -720,13 +720,16 @@ CoderResult CodeCustomSection(Coder& coder, template CoderResult CodeTableDesc(Coder& coder, CoderArg item) { - WASM_VERIFY_SERIALIZATION_FOR_SIZE(wasm::TableDesc, 32); + WASM_VERIFY_SERIALIZATION_FOR_SIZE(wasm::TableDesc, 120); MOZ_TRY(CodeRefType(coder, &item->elemType)); - MOZ_TRY(CodePod(coder, &item->isImportedOrExported)); + MOZ_TRY(CodePod(coder, &item->isImported)); + MOZ_TRY(CodePod(coder, &item->isExported)); MOZ_TRY(CodePod(coder, &item->isAsmJS)); MOZ_TRY(CodePod(coder, &item->globalDataOffset)); MOZ_TRY(CodePod(coder, &item->initialLength)); MOZ_TRY(CodePod(coder, &item->maximumLength)); + MOZ_TRY( + (CodeMaybe>(coder, &item->initExpr))); return Ok(); } diff --git a/js/src/wasm/WasmTable.cpp b/js/src/wasm/WasmTable.cpp index 13ba6ef2b86b..eb52745a2b8b 100644 --- a/js/src/wasm/WasmTable.cpp +++ b/js/src/wasm/WasmTable.cpp @@ -60,8 +60,8 @@ Table::Table(JSContext* cx, const TableDesc& desc, /* static */ SharedTable Table::create(JSContext* cx, const TableDesc& desc, Handle maybeObject) { - // We don't support non-nullable references in tables yet. - MOZ_RELEASE_ASSERT(desc.elemType.isNullable()); + // Tables are initialized with init_expr values at Instance::init or + // WasmTableObject::create. switch (desc.elemType.tableRepr()) { case TableRepr::Func: { @@ -330,8 +330,6 @@ bool Table::copy(JSContext* cx, const Table& srcTable, uint32_t dstIndex, } uint32_t Table::grow(uint32_t delta) { - MOZ_RELEASE_ASSERT(elemType_.isNullable()); - // This isn't just an optimization: movingGrowable() assumes that // onMovingGrowTable does not fire when length == maximum. if (!delta) { @@ -403,6 +401,58 @@ bool Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance) { return true; } +void Table::fillUninitialized(uint32_t index, uint32_t fillCount, + HandleAnyRef ref, JSContext* cx) { +#ifdef DEBUG + assertRangeNull(index, fillCount); +#endif // DEBUG + switch (repr()) { + case TableRepr::Func: { + MOZ_RELEASE_ASSERT(!isAsmJS_); + fillFuncRef(index, fillCount, FuncRef::fromAnyRefUnchecked(ref), cx); + break; + } + case TableRepr::Ref: { + fillAnyRef(index, fillCount, ref); + break; + } + } +} + +#ifdef DEBUG +void Table::assertRangeNull(uint32_t index, uint32_t length) const { + switch (repr()) { + case TableRepr::Func: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT(getFuncRef(i).instance == nullptr); + MOZ_ASSERT(getFuncRef(i).code == nullptr); + } + break; + case TableRepr::Ref: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT(getAnyRef(i).isNull()); + } + break; + } +} + +void Table::assertRangeNotNull(uint32_t index, uint32_t length) const { + switch (repr()) { + case TableRepr::Func: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT_IF(!isAsmJS_, getFuncRef(i).instance != nullptr); + MOZ_ASSERT(getFuncRef(i).code != nullptr); + } + break; + case TableRepr::Ref: + for (uint32_t i = index; i < index + length; i++) { + MOZ_ASSERT(!getAnyRef(i).isNull()); + } + break; + } +} +#endif // DEBUG + size_t Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const { if (isFunction()) { return functions_.sizeOfExcludingThis(mallocSizeOf); diff --git a/js/src/wasm/WasmTable.h b/js/src/wasm/WasmTable.h index 62b5c1d1127e..fddcc94b8603 100644 --- a/js/src/wasm/WasmTable.h +++ b/js/src/wasm/WasmTable.h @@ -116,6 +116,13 @@ class Table : public ShareableBase { [[nodiscard]] bool addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance); + void fillUninitialized(uint32_t index, uint32_t fillCount, HandleAnyRef ref, + JSContext* cx); +#ifdef DEBUG + void assertRangeNull(uint32_t index, uint32_t length) const; + void assertRangeNotNull(uint32_t index, uint32_t length) const; +#endif // DEBUG + // about:memory reporting: size_t sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const; diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index 8c7b8c73354b..40d57182d5f2 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -1877,15 +1877,24 @@ static bool DecodeLimits(Decoder& d, LimitsKind kind, Limits* limits) { return true; } -static bool DecodeTableTypeAndLimits(Decoder& d, const FeatureArgs& features, - const SharedTypeContext& types, - TableDescVector* tables) { - RefType tableElemType; - if (!d.readRefType(*types, features, &tableElemType)) { - return false; +static bool DecodeTableTypeAndLimits(Decoder& d, ModuleEnvironment* env) { + bool initExprPresent = false; + uint8_t typeCode; + if (!d.peekByte(&typeCode)) { + return d.fail("expected type code"); } - if (!tableElemType.isNullable()) { - return d.fail("non-nullable references not supported in tables"); + if (typeCode == (uint8_t)TypeCode::TableHasInitExpr) { + d.uncheckedReadFixedU8(); + uint8_t flags; + if (!d.readFixedU8(&flags) || flags != 0) { + return d.fail("expected reserved byte to be 0"); + } + initExprPresent = true; + } + + RefType tableElemType; + if (!d.readRefType(*env->types, env->features, &tableElemType)) { + return false; } Limits limits; @@ -1905,7 +1914,7 @@ static bool DecodeTableTypeAndLimits(Decoder& d, const FeatureArgs& features, return d.fail("too many table elements"); } - if (tables->length() >= MaxTables) { + if (env->tables.length() >= MaxTables) { return d.fail("too many tables"); } @@ -1917,8 +1926,22 @@ static bool DecodeTableTypeAndLimits(Decoder& d, const FeatureArgs& features, maximumLength = Some(uint32_t(*limits.maximum)); } - return tables->emplaceBack(tableElemType, initialLength, maximumLength, - /* isAsmJS */ false); + Maybe initExpr; + if (initExprPresent) { + InitExpr initializer; + if (!InitExpr::decodeAndValidate(d, env, tableElemType, + env->globals.length(), &initializer)) { + return false; + } + initExpr = Some(std::move(initializer)); + } else { + if (!tableElemType.isNullable()) { + return d.fail("table with non-nullable references requires initializer"); + } + } + + return env->tables.emplaceBack(tableElemType, initialLength, maximumLength, + std::move(initExpr), /* isAsmJS */ false); } static bool DecodeGlobalType(Decoder& d, const SharedTypeContext& types, @@ -2035,11 +2058,10 @@ static bool DecodeImport(Decoder& d, ModuleEnvironment* env) { break; } case DefinitionKind::Table: { - if (!DecodeTableTypeAndLimits(d, env->features, env->types, - &env->tables)) { + if (!DecodeTableTypeAndLimits(d, env)) { return false; } - env->tables.back().isImportedOrExported = true; + env->tables.back().isImported = true; break; } case DefinitionKind::Memory: { @@ -2176,7 +2198,7 @@ static bool DecodeTableSection(Decoder& d, ModuleEnvironment* env) { } for (uint32_t i = 0; i < numTables; ++i) { - if (!DecodeTableTypeAndLimits(d, env->features, env->types, &env->tables)) { + if (!DecodeTableTypeAndLimits(d, env)) { return false; } } @@ -2356,7 +2378,7 @@ static bool DecodeExport(Decoder& d, ModuleEnvironment* env, NameSet* dupSet) { if (tableIndex >= env->tables.length()) { return d.fail("exported table index out of bounds"); } - env->tables[tableIndex].isImportedOrExported = true; + env->tables[tableIndex].isExported = true; return env->exports.emplaceBack(std::move(fieldName), tableIndex, DefinitionKind::Table); }