Bug 1546592 - Allow element segments to be 'anyref', and generalize. r=rhunt

The main changes here are:

* Element segments that carry an elementexpr can carry a type T that
  is other than 'funcref', though we require T <: anyref

* We generalize type handling around table initialization so that a
  table-of-T can be initialized from segment-of-U if U <: T.


Also:

* A declared element segment needs to have type funcref.  The spec is
  silent on this, but it's a conservative choice.

* A drive-by fix in serialize/deserialize, the 'kind' field was not
  being handled properly.  This doesn't affect any shipping product as
  the serialize/deserialize code is currently unused.

Differential Revision: https://phabricator.services.mozilla.com/D48690

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Lars T Hansen 2019-10-17 13:25:24 +00:00
parent 089165087b
commit c031dd3036
14 changed files with 287 additions and 91 deletions

View File

@ -356,6 +356,50 @@ function elemSection(elemArrays) {
return { name: elemId, body };
}
// For now, the encoding spec is here:
// https://github.com/WebAssembly/bulk-memory-operations/issues/98#issuecomment-507330729
const LegacyActiveExternVal = 0;
const PassiveExternVal = 1;
const ActiveExternVal = 2;
const DeclaredExternVal = 3;
const LegacyActiveElemExpr = 4;
const PassiveElemExpr = 5;
const ActiveElemExpr = 6;
const DeclaredElemExpr = 7;
function generalElemSection(elemObjs) {
let body = [];
body.push(...varU32(elemObjs.length));
for (let elemObj of elemObjs) {
body.push(elemObj.flag);
if ((elemObj.flag & 3) == 2)
body.push(...varU32(elemObj.table));
// TODO: This is not very flexible
if ((elemObj.flag & 1) == 0) {
body.push(...varU32(I32ConstCode));
body.push(...varS32(elemObj.offset));
body.push(...varU32(EndCode));
}
if (elemObj.flag & 4) {
if (elemObj.flag & 3)
body.push(elemObj.typeCode & 255);
// Each element is an array of bytes
body.push(...varU32(elemObj.elems.length));
for (let elemBytes of elemObj.elems)
body.push(...elemBytes);
} else {
if (elemObj.flag & 3)
body.push(elemObj.externKind & 255);
// Each element is a putative function index
body.push(...varU32(elemObj.elems.length));
for (let elem of elemObj.elems)
body.push(...varU32(elem));
}
}
return { name: elemId, body };
}
function moduleNameSubsection(moduleName) {
var body = [];
body.push(...varU32(nameTypeModule));

View File

@ -1,5 +1,7 @@
// |jit-test| skip-if: !wasmReftypesEnabled()
load(libdir + "wasm-binary.js");
// 'ref.func' parses, validates and returns a non-null value
wasmFullPass(`
(module
@ -71,10 +73,10 @@ assertErrorMessage(() => {
`);
}, WebAssembly.CompileError, /function index out of range/);
function validFuncRefText(forwardDeclare) {
function validFuncRefText(forwardDeclare, tbl_type) {
return wasmEvalText(`
(module
(table 1 funcref)
(table 1 ${tbl_type})
(func $test (result funcref) ref.func $referenced)
(func $referenced)
${forwardDeclare}
@ -83,13 +85,53 @@ function validFuncRefText(forwardDeclare) {
}
// referenced function must be forward declared somehow
assertErrorMessage(() => validFuncRefText(''), WebAssembly.CompileError, /function index is not in an element segment/);
assertErrorMessage(() => validFuncRefText('', 'funcref'), WebAssembly.CompileError, /function index is not in an element segment/);
// referenced function can be forward declared via segments
assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)') instanceof WebAssembly.Instance, true);
assertEq(validFuncRefText('(elem func $referenced)') instanceof WebAssembly.Instance, true);
assertEq(validFuncRefText('(elem declared $referenced)') instanceof WebAssembly.Instance, true);
assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
assertEq(validFuncRefText('(elem func $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
assertEq(validFuncRefText('(elem declared $referenced)', 'funcref') instanceof WebAssembly.Instance, true);
// also when the segment is passive or active 'anyref'
assertEq(validFuncRefText('(elem 0 (i32.const 0) anyref (ref.func $referenced))', 'anyref') instanceof WebAssembly.Instance, true);
assertEq(validFuncRefText('(elem anyref (ref.func $referenced))', 'anyref') instanceof WebAssembly.Instance, true);
// referenced function cannot be forward declared via start section or export
assertErrorMessage(() => validFuncRefText('(start $referenced)'), WebAssembly.CompileError, /function index is not in an element segment/);
assertErrorMessage(() => validFuncRefText('(export "referenced" $referenced)'), WebAssembly.CompileError, /function index is not in an element segment/);
assertErrorMessage(() => validFuncRefText('(start $referenced)', 'funcref'),
WebAssembly.CompileError,
/function index is not in an element segment/);
assertErrorMessage(() => validFuncRefText('(export "referenced" $referenced)', 'funcref'),
WebAssembly.CompileError,
/function index is not in an element segment/);
// Tests not expressible in the text format.
// element segment with elemexpr can carry non-reference type, but this must be
// rejected.
assertErrorMessage(() => new WebAssembly.Module(
moduleWithSections([generalElemSection([{ flag: PassiveElemExpr,
typeCode: I32Code,
elems: [] }])])),
WebAssembly.CompileError,
/segments with element expressions can only contain references/);
// declared element segment with elemexpr can carry type anyref, but this must be rejected.
assertErrorMessage(() => new WebAssembly.Module(
moduleWithSections([generalElemSection([{ flag: DeclaredElemExpr,
typeCode: AnyrefCode,
elems: [] }])])),
WebAssembly.CompileError,
/declared segment's element type must be subtype of funcref/);
// declared element segment of type funcref with elemexpr can carry a null
// value, but the null value must be rejected.
assertErrorMessage(() => new WebAssembly.Module(
moduleWithSections([generalElemSection([{ flag: DeclaredElemExpr,
typeCode: AnyFuncCode,
elems: [[RefNullCode]] }])])),
WebAssembly.CompileError,
/declared element segments cannot contain ref.null/);

View File

@ -116,15 +116,59 @@ assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
WebAssembly.CompileError,
/expression has type anyref but expected funcref/);
// Wasm: element segments targeting table-of-anyref is forbidden
// Wasm: Element segments can target tables of anyref whether the element type
// is anyref or funcref.
for (let elem_ty of ["funcref", "anyref"]) {
let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
`(module
(func $f1 (export "f") (result i32) (i32.const 0))
(func $f2 (result i32) (i32.const 0)) ;; on purpose not exported
(table (export "t") 10 anyref)
(elem (table 0) (i32.const 0) ${elem_ty} (ref.func $f1) (ref.func $f2))
)`)));
let t = ins.exports.t;
let f = ins.exports.f;
assertEq(t.get(0), f);
assertEq(t.get(2), null); // not much of a test since that's the default value
}
// Wasm: Element segments of anyref can't target tables of funcref
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
`(module
(func $f1 (result i32) (i32.const 0))
(table 10 anyref)
(elem 0 (i32.const 0) func $f1))`)),
(table (export "t") 10 funcref)
(elem 0 (i32.const 0) anyref (ref.func $f1)))`)),
WebAssembly.CompileError,
/only tables of 'funcref' may have element segments/);
/segment's element type must be subtype of table's element type/);
// Wasm: table.init on table-of-anyref is allowed whether the segment has
// anyrefs or funcrefs.
for (let elem_ty of ["funcref", "anyref"]) {
let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
`(module
(func $f1 (result i32) (i32.const 0))
(table (export "t") 10 anyref)
(elem ${elem_ty} (ref.func $f1))
(func (export "f")
(table.init 0 (i32.const 2) (i32.const 0) (i32.const 1))))`)));
ins.exports.f();
assertEq(typeof ins.exports.t.get(2), "function");
}
// Wasm: table.init on table-of-funcref is not allowed when the segment has
// anyref.
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
`(module
(table 10 funcref)
(elem anyref (ref.null))
(func
(table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
WebAssembly.CompileError,
/expression has type anyref but expected funcref/);
// Wasm: table types must match at link time
@ -459,19 +503,3 @@ let VALUES = [null,
t.grow(0, 1789);
assertEq(t.get(0), 1337);
}
// Currently 'anyref' segments are not allowed whether passive or active,
// though that will change
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
`(module
(elem (i32.const 0) anyref (ref.null)))`)),
SyntaxError,
/parsing wasm text/);
assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
`(module
(elem anyref (ref.null)))`)),
SyntaxError,
/parsing wasm text/);

View File

@ -334,7 +334,7 @@ function checkPassiveElemSegment(mangle, err) {
}
checkPassiveElemSegment("");
checkPassiveElemSegment("type", /segments with element expressions can only contain function references/);
checkPassiveElemSegment("type", /segments with element expressions can only contain references/);
checkPassiveElemSegment("ref.func", /failed to read initializer operation/);
checkPassiveElemSegment("end", /failed to read end of initializer expression/);

View File

@ -1307,20 +1307,24 @@ class AstElemSegment : public AstNode {
AstElemSegmentKind kind_;
AstRef targetTable_;
AstExpr* offsetIfActive_;
ValType elemType_;
AstElemVector elems_;
public:
AstElemSegment(AstElemSegmentKind kind, AstRef targetTable,
AstExpr* offsetIfActive, AstElemVector&& elems)
AstExpr* offsetIfActive, ValType elemType,
AstElemVector&& elems)
: kind_(kind),
targetTable_(targetTable),
offsetIfActive_(offsetIfActive),
elemType_(elemType),
elems_(std::move(elems)) {}
AstElemSegmentKind kind() const { return kind_; }
AstRef targetTable() const { return targetTable_; }
AstRef& targetTableRef() { return targetTable_; }
AstExpr* offsetIfActive() const { return offsetIfActive_; }
ValType elemType() const { return elemType_; }
AstElemVector& elems() { return elems_; }
const AstElemVector& elems() const { return elems_; }
};

View File

@ -367,22 +367,22 @@ bool ModuleGenerator::init(Metadata* maybeAsmJSMetadata) {
}
for (const ElemSegment* seg : env_->elemSegments) {
TableKind kind = !seg->active() ? TableKind::FuncRef
: env_->tables[seg->tableIndex].kind;
switch (kind) {
case TableKind::FuncRef:
for (uint32_t funcIndex : seg->elemFuncIndices) {
if (funcIndex == NullFuncIndex) {
continue;
}
addOrMerge(ExportedFunc(funcIndex, false));
// For now, the segments always carry function indices regardless of the
// segment's declared element type; this works because the only legal
// element types are funcref and anyref and the only legal values are
// functions and null. We always add functions in segments as exported
// functions, regardless of the segment's type. In the future, if we make
// the representation of AnyRef segments different, we will have to consider
// function values in those segments specially.
bool isAsmJS =
seg->active() && env_->tables[seg->tableIndex].kind == TableKind::AsmJS;
if (!isAsmJS) {
for (uint32_t funcIndex : seg->elemFuncIndices) {
if (funcIndex == NullFuncIndex) {
continue;
}
break;
case TableKind::AsmJS:
// asm.js functions are not exported.
break;
case TableKind::AnyRef:
break;
addOrMerge(ExportedFunc(funcIndex, false));
}
}
}

View File

@ -753,7 +753,7 @@ static int32_t PerformWait(Instance* instance, uint32_t byteOffset, T value,
return 0;
}
void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
bool Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
uint32_t dstOffset, uint32_t srcOffset, uint32_t len) {
Table& table = *tables_[tableIndex];
MOZ_ASSERT(dstOffset <= table.length());
@ -773,6 +773,13 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
uint32_t funcIndex = elemFuncIndices[srcOffset + i];
if (funcIndex == NullFuncIndex) {
table.setNull(dstOffset + i);
} else if (!table.isFunction()) {
// Note, fnref must be rooted if we do anything more than just store it.
void* fnref = Instance::funcRef(this, funcIndex);
if (fnref == AnyRef::invalid().forCompiledCode()) {
return false; // OOM, which has already been reported.
}
table.fillAnyRef(dstOffset + i, 1, AnyRef::fromCompiledCode(fnref));
} else {
if (funcIndex < funcImports.length()) {
FuncImportTls& import = funcImportTls(funcImports[funcIndex]);
@ -801,6 +808,7 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
table.setFuncRef(dstOffset + i, code, this);
}
}
return true;
}
/* static */ int32_t Instance::tableInit(Instance* instance, uint32_t dstOffset,
@ -825,10 +833,6 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
const Table& table = *instance->tables()[tableIndex];
const uint32_t tableLen = table.length();
// Element segments cannot currently contain arbitrary values, and anyref
// tables cannot be initialized from segments.
MOZ_ASSERT(table.kind() == TableKind::FuncRef);
// We are proposing to copy
//
// seg[ srcOffset .. srcOffset + len - 1 ]
@ -860,7 +864,9 @@ void Instance::initElems(uint32_t tableIndex, const ElemSegment& seg,
}
if (len > 0) {
instance->initElems(tableIndex, seg, dstOffset, srcOffset, len);
if (!instance->initElems(tableIndex, seg, dstOffset, srcOffset, len)) {
return -1; // OOM, which has already been reported.
}
}
if (!mustTrap) {

View File

@ -162,8 +162,9 @@ class Instance {
// Called to apply a single ElemSegment at a given offset, assuming
// that all bounds validation has already been performed.
void initElems(uint32_t tableIndex, const ElemSegment& seg,
uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
MOZ_MUST_USE bool initElems(uint32_t tableIndex, const ElemSegment& seg,
uint32_t dstOffset, uint32_t srcOffset,
uint32_t len);
// Debugger support:

View File

@ -592,7 +592,9 @@ bool Module::initSegments(JSContext* cx, HandleWasmInstanceObject instanceObj,
}
}
if (count) {
instance.initElems(seg->tableIndex, *seg, offset, 0, count);
if (!instance.initElems(seg->tableIndex, *seg, offset, 0, count)) {
return false; // OOM
}
}
if (fail) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,

View File

@ -2446,14 +2446,13 @@ inline bool OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
}
*dstTableIndex = memOrTableIndex;
// Element segments must carry functions exclusively and funcref is not
// yet a subtype of anyref.
if (env_.tables[*dstTableIndex].kind != TableKind::FuncRef) {
return fail("only tables of 'funcref' may have element segments");
}
if (*segIndex >= env_.elemSegments.length()) {
return fail("table.init segment index out of range");
}
if (!checkIsSubtypeOf(env_.elemSegments[*segIndex]->elemType(),
ToElemValType(env_.tables[*dstTableIndex].kind))) {
return false;
}
}
return true;

View File

@ -5045,22 +5045,26 @@ static bool ParseTable(WasmParseContext& c, WasmToken token,
return false;
}
AstElemSegment* segment = new (c.lifo) AstElemSegment(
AstElemSegmentKind::Active, AstRef(name), zero, std::move(elems));
AstElemSegment* segment =
new (c.lifo) AstElemSegment(AstElemSegmentKind::Active, AstRef(name),
zero, ValType::FuncRef, std::move(elems));
return segment && module->append(segment);
}
static bool TryParseFuncOrFuncRef(WasmParseContext& c, bool* isFunc) {
static bool TryParseElemType(WasmParseContext& c, bool* isFunc, ValType* ty) {
if (c.ts.getIf(WasmToken::Func)) {
*isFunc = true;
*ty = ValType::FuncRef;
return true;
}
WasmToken token = c.ts.peek();
if (token.kind() == WasmToken::ValueType &&
token.valueType() == ValType::FuncRef) {
(token.valueType() == ValType::FuncRef ||
token.valueType() == ValType::AnyRef)) {
c.ts.get();
*isFunc = false;
*ty = token.valueType();
return true;
}
@ -5072,6 +5076,7 @@ static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
// <init-expr> is any <expr> or (offset <const>).
// <table-use> is (table <n>) or just <n> (an extension).
// <fnref> is a naked function reference (index or name)
// <elem-type> is funcref or anyref
//
// Active initializer for table 0 which must be table-of-functions, this is
// sugar:
@ -5082,18 +5087,19 @@ static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
//
// Active initializer for a given table of functions, allowing null. Note the
// parens are required also around ref.null:
// (elem <table-use> <init-expr> funcref
// (elem <table-use> <init-expr> <elem-type>
// "(" (ref.func <fnref>|ref.null) ")" ...)
//
// Passive initializers:
// (elem func <fnref> ...)
// (elem funcref "(" (ref.func <fnref>|ref.null) ")" ...)
// (elem <elem-type> "(" (ref.func <fnref>|ref.null) ")" ...)
//
// Forward declaration for ref.func uses:
// (elem declared <fnref> ...)
AstRef targetTable = AstRef(0);
AstExpr* offsetIfActive = nullptr;
ValType elemType = ValType::FuncRef;
bool haveTableref = false;
AstElemSegmentKind kind;
@ -5126,9 +5132,9 @@ static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
c.error);
return nullptr;
}
if (!TryParseFuncOrFuncRef(c, &nakedFnrefs)) {
if (!TryParseElemType(c, &nakedFnrefs, &elemType)) {
c.ts.generateError(c.ts.peek(),
"'func' or 'funcref' required for elem segment",
"'func' or element type required for elem segment",
c.error);
return nullptr;
}
@ -5136,8 +5142,8 @@ static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
} else if (c.ts.getIf(WasmToken::Declared)) {
kind = AstElemSegmentKind::Declared;
nakedFnrefs = true;
} else if (TryParseFuncOrFuncRef(c, &nakedFnrefs)) {
// 'func' or 'funcref' for a passive segment.
} else if (TryParseElemType(c, &nakedFnrefs, &elemType)) {
// 'func' or element type for a passive segment.
kind = AstElemSegmentKind::Passive;
} else {
if ((offsetIfActive = ParseInitializerExpression(c)) == nullptr) {
@ -5187,8 +5193,8 @@ static AstElemSegment* ParseElemSegment(WasmParseContext& c) {
}
}
return new (c.lifo)
AstElemSegment(kind, targetTable, offsetIfActive, std::move(elems));
return new (c.lifo) AstElemSegment(kind, targetTable, offsetIfActive,
elemType, std::move(elems));
}
static bool ParseGlobal(WasmParseContext& c, AstModule* module) {
@ -7421,10 +7427,20 @@ static bool EncodeDataCountSection(Encoder& e, AstModule& module) {
}
static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
// There are three bits that control the encoding of an element segment for
// up to eight possible encodings. We try to select the encoding for an
// element segment that takes the least amount of space, which depends on
// whether there are null references in the segment.
// There are three bits that control the encoding of an element segment for up
// to eight possible encodings. We try to select the encoding for an element
// segment that takes the least amount of space. We can use various
// compressed encodings if some or all of these are true:
//
// - the selected element type is FuncRef
// - there are no null references in the segment
// - the table and initialization indices are both zero
//
// Choosing the best encoding is tricky because not all encodings can
// represent all situations. For example, if we have a type other than
// FuncRef, or a null value, or a table index, then we can't use the legacy
// "Active" encoding, compact though it is.
bool hasRefNull = false;
for (const AstElem& elem : segment.elems()) {
if (elem.is<AstNullValue>()) {
@ -7433,11 +7449,16 @@ static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
}
}
// Select the encoding that takes the least amount of space
ElemSegmentPayload payload =
hasRefNull || segment.elemType() != ValType::FuncRef
? ElemSegmentPayload::ElemExpression
: ElemSegmentPayload::ExternIndex;
ElemSegmentKind kind;
switch (segment.kind()) {
case AstElemSegmentKind::Active: {
kind = segment.targetTable().index()
kind = segment.targetTable().index() ||
payload != ElemSegmentPayload::ExternIndex
? ElemSegmentKind::ActiveWithTableIndex
: ElemSegmentKind::Active;
break;
@ -7451,8 +7472,6 @@ static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
break;
}
}
ElemSegmentPayload payload = hasRefNull ? ElemSegmentPayload::ElemExpression
: ElemSegmentPayload::ExternIndex;
// Write the flags field.
if (!e.writeVarU32(ElemSegmentFlags(kind, payload).encoded())) {
@ -7482,7 +7501,8 @@ static bool EncodeElemSegment(Encoder& e, AstElemSegment& segment) {
// encoding, which doesn't include an explicit type or definition kind.
if (kind != ElemSegmentKind::Active) {
if (payload == ElemSegmentPayload::ElemExpression &&
!e.writeFixedU8(uint8_t(TypeCode::FuncRef))) {
!e.writeFixedU8(
uint8_t(UnpackTypeCodeType(segment.elemType().packed())))) {
return false;
}
if (payload == ElemSegmentPayload::ExternIndex &&

View File

@ -479,19 +479,23 @@ size_t Export::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const {
}
size_t ElemSegment::serializedSize() const {
return sizeof(tableIndex) + sizeof(offsetIfActive) +
SerializedPodVectorSize(elemFuncIndices);
return sizeof(kind) + sizeof(tableIndex) + sizeof(elementType) +
sizeof(offsetIfActive) + SerializedPodVectorSize(elemFuncIndices);
}
uint8_t* ElemSegment::serialize(uint8_t* cursor) const {
cursor = WriteBytes(cursor, &kind, sizeof(kind));
cursor = WriteBytes(cursor, &tableIndex, sizeof(tableIndex));
cursor = WriteBytes(cursor, &elementType, sizeof(elementType));
cursor = WriteBytes(cursor, &offsetIfActive, sizeof(offsetIfActive));
cursor = SerializePodVector(cursor, elemFuncIndices);
return cursor;
}
const uint8_t* ElemSegment::deserialize(const uint8_t* cursor) {
(cursor = ReadBytes(cursor, &tableIndex, sizeof(tableIndex))) &&
(cursor = ReadBytes(cursor, &kind, sizeof(kind))) &&
(cursor = ReadBytes(cursor, &tableIndex, sizeof(tableIndex))) &&
(cursor = ReadBytes(cursor, &elementType, sizeof(elementType))) &&
(cursor = ReadBytes(cursor, &offsetIfActive, sizeof(offsetIfActive))) &&
(cursor = DeserializePodVector(cursor, &elemFuncIndices));
return cursor;

View File

@ -1203,6 +1203,7 @@ struct ElemSegment : AtomicRefCounted<ElemSegment> {
Kind kind;
uint32_t tableIndex;
ValType elementType;
Maybe<InitExpr> offsetIfActive;
Uint32Vector elemFuncIndices; // Element may be NullFuncIndex
@ -1212,6 +1213,8 @@ struct ElemSegment : AtomicRefCounted<ElemSegment> {
size_t length() const { return elemFuncIndices.length(); }
ValType elemType() const { return elementType; }
WASM_DECLARE_SERIALIZABLE(ElemSegment)
};

View File

@ -2363,9 +2363,6 @@ static bool DecodeElemSection(Decoder& d, ModuleEnvironment* env) {
if (tableIndex >= env->tables.length()) {
return d.fail("table index out of range for element segment");
}
if (env->tables[tableIndex].kind != TableKind::FuncRef) {
return d.fail("only tables of 'funcref' may have element segments");
}
seg->tableIndex = tableIndex;
InitExpr offset;
@ -2383,11 +2380,14 @@ static bool DecodeElemSection(Decoder& d, ModuleEnvironment* env) {
}
ElemSegmentPayload payload = flags->payload();
ValType elemType;
// `ActiveWithTableIndex`, `Declared`, and `Passive` element segments encode
// the type or definition kind of the payload. `Active` element segments are
// restricted to MVP behavior, which assumes only function indices.
if (kind != ElemSegmentKind::Active) {
if (kind == ElemSegmentKind::Active) {
elemType = ValType::FuncRef;
} else {
uint8_t form;
if (!d.readFixedU8(&form)) {
return d.fail("expected type or extern kind");
@ -2395,10 +2395,23 @@ static bool DecodeElemSection(Decoder& d, ModuleEnvironment* env) {
switch (payload) {
case ElemSegmentPayload::ElemExpression: {
if (form != uint8_t(TypeCode::FuncRef)) {
return d.fail(
"segments with element expressions can only contain function "
"references");
switch (form) {
case uint8_t(TypeCode::FuncRef):
// Below we must in principle check every element expression to
// ensure that it is a subtype of FuncRef. However, the only
// reference expressions allowed are ref.null and ref.func, and
// they both pass that test, and so no additional check is needed
// at this time.
elemType = ValType::FuncRef;
break;
case uint8_t(TypeCode::AnyRef):
// Ditto, for AnyRef, just even more trivial.
elemType = ValType::AnyRef;
break;
default:
return d.fail(
"segments with element expressions can only contain "
"references");
}
break;
}
@ -2408,10 +2421,40 @@ static bool DecodeElemSection(Decoder& d, ModuleEnvironment* env) {
"segments with extern indices can only contain function "
"references");
}
elemType = ValType::FuncRef;
}
}
}
// Check constraints on the element type.
switch (kind) {
case ElemSegmentKind::Declared: {
if (!(elemType.isReference() &&
env->isRefSubtypeOf(elemType, ValType::FuncRef))) {
return d.fail(
"declared segment's element type must be subtype of funcref");
}
break;
}
case ElemSegmentKind::Active:
case ElemSegmentKind::ActiveWithTableIndex: {
ValType tblElemType = ToElemValType(env->tables[seg->tableIndex].kind);
if (!(elemType == tblElemType ||
(elemType.isReference() && tblElemType.isReference() &&
env->isRefSubtypeOf(elemType, tblElemType)))) {
return d.fail(
"segment's element type must be subtype of table's element type");
}
break;
}
case ElemSegmentKind::Passive: {
// By construction, above.
MOZ_ASSERT(elemType.isReference());
break;
}
}
seg->elementType = elemType;
uint32_t numElems;
if (!d.readVarU32(&numElems)) {
return d.fail("expected segment size");