mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 10:15:41 +00:00
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:
parent
089165087b
commit
c031dd3036
@ -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));
|
||||
|
@ -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/);
|
||||
|
||||
|
@ -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/);
|
||||
|
@ -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/);
|
||||
|
||||
|
@ -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_; }
|
||||
};
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 &&
|
||||
|
@ -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;
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -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");
|
||||
|
Loading…
Reference in New Issue
Block a user