Bug 1784499 - Add WebAssembly.Table initiazer for non-nullable type. r=rhunt

Differential Revision: https://phabricator.services.mozilla.com/D162772
This commit is contained in:
Yury Delendik 2023-03-23 13:46:15 +00:00
Родитель 57664750c6
Коммит ee4bdd5b2e
15 изменённых файлов: 286 добавлений и 79 удалений

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

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

Просмотреть файл

@ -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<WasmInstanceObject*> 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];

Просмотреть файл

@ -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<uint32_t> 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,

Просмотреть файл

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

Просмотреть файл

@ -749,7 +749,7 @@ bool Module::instantiateLocalTable(JSContext* cx, const TableDesc& td,
SharedTable table;
Rooted<WasmTableObject*> 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));

Просмотреть файл

@ -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<uint32_t> maximumLength;
Maybe<InitExpr> initExpr;
TableDesc() = default;
TableDesc(RefType elemType, uint32_t initialLength,
Maybe<uint32_t> maximumLength, bool isAsmJS,
bool isImportedOrExported = false)
Maybe<uint32_t> maximumLength, Maybe<InitExpr>&& 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<TableDesc, 0, SystemAllocPolicy>;

Просмотреть файл

@ -720,13 +720,16 @@ CoderResult CodeCustomSection(Coder<mode>& coder,
template <CoderMode mode>
CoderResult CodeTableDesc(Coder<mode>& coder, CoderArg<mode, TableDesc> 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<mode, InitExpr, &CodeInitExpr<mode>>(coder, &item->initExpr)));
return Ok();
}

Просмотреть файл

@ -60,8 +60,8 @@ Table::Table(JSContext* cx, const TableDesc& desc,
/* static */
SharedTable Table::create(JSContext* cx, const TableDesc& desc,
Handle<WasmTableObject*> 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);

Просмотреть файл

@ -116,6 +116,13 @@ class Table : public ShareableBase<Table> {
[[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;

Просмотреть файл

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