--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -380,16 +380,17 @@ MSG_DEF(JSMSG_WASM_BAD_DESC_ARG, 1
MSG_DEF(JSMSG_WASM_BAD_ELEMENT, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"anyfunc\"")
MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG, 0, JSEXN_TYPEERR, "second argument must be an object")
MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD, 1, JSEXN_TYPEERR, "import object field '{0}' is not an Object")
MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE, 0, JSEXN_TYPEERR, "can only assign WebAssembly exported functions to Table")
MSG_DEF(JSMSG_WASM_BAD_I64_TYPE, 0, JSEXN_TYPEERR, "cannot pass i64 to or from JS")
MSG_DEF(JSMSG_WASM_NO_TRANSFER, 0, JSEXN_TYPEERR, "cannot transfer WebAssembly/asm.js ArrayBuffer")
MSG_DEF(JSMSG_WASM_STREAM_ERROR, 0, JSEXN_TYPEERR, "stream error during WebAssembly compilation")
MSG_DEF(JSMSG_WASM_TEXT_FAIL, 1, JSEXN_SYNTAXERR, "wasm text error: {0}")
+MSG_DEF(JSMSG_WASM_MISSING_MAXIMUM, 0, JSEXN_TYPEERR, "'shared' is true but maximum is not specified")
// Proxy
MSG_DEF(JSMSG_BAD_TRAP_RETURN_VALUE, 2, JSEXN_TYPEERR,"trap {1} for {0} returned a primitive value")
MSG_DEF(JSMSG_BAD_GETPROTOTYPEOF_TRAP_RETURN,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler returned a non-object, non-null value")
MSG_DEF(JSMSG_INCONSISTENT_GETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy getPrototypeOf handler didn't return the target object's prototype")
MSG_DEF(JSMSG_PROXY_SETPROTOTYPEOF_RETURNED_FALSE, 0, JSEXN_TYPEERR, "proxy setPrototypeOf handler returned false")
MSG_DEF(JSMSG_PROXY_ISEXTENSIBLE_RETURNED_FALSE,0,JSEXN_TYPEERR,"proxy isExtensible handler must return the same extensibility as target")
MSG_DEF(JSMSG_INCONSISTENT_SETPROTOTYPEOF_TRAP,0,JSEXN_TYPEERR,"proxy setPrototypeOf handler returned true, even though the target's prototype is immutable because the target is non-extensible")
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -104,17 +104,25 @@ enum class DefinitionKind
enum class GlobalTypeImmediate
{
IsMutable = 0x1,
AllowedMask = 0x1
};
enum class MemoryTableFlags
{
- Default = 0x0
+ Default = 0x0,
+ HasMaximum = 0x1,
+ IsShared = 0x2
+};
+
+enum class MemoryMasks
+{
+ AllowUnshared = 0x1,
+ AllowShared = 0x3
};
enum class Op
{
// Control flow operators
Unreachable = 0x00,
Nop = 0x01,
Block = 0x02,
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -1544,20 +1544,21 @@ AstCreateImports(AstDecodeContext& c)
{
size_t lastFunc = 0;
size_t lastGlobal = 0;
size_t lastTable = 0;
size_t lastMemory = 0;
Maybe<Limits> memory;
if (c.env().usesMemory()) {
- Limits limits;
- limits.initial = c.env().minMemoryLength;
- limits.maximum = c.env().maxMemoryLength;
- memory = Some(limits);
+ memory = Some(Limits(c.env().minMemoryLength,
+ c.env().maxMemoryLength,
+ c.env().memoryUsage == MemoryUsage::Shared
+ ? Shareable::True
+ : Shareable::False));
}
for (size_t importIndex = 0; importIndex < c.env().imports.length(); importIndex++) {
const Import& import = c.env().imports[importIndex];
AstName moduleName;
if (!ToAstName(c, import.module.get(), &moduleName))
return false;
@@ -1648,20 +1649,21 @@ AstCreateMemory(AstDecodeContext& c)
bool importedMemory = !!c.module().memories().length();
if (!c.env().usesMemory() || importedMemory)
return true;
AstName name;
if (!GenerateName(c, AstName(u"memory"), c.module().memories().length(), &name))
return false;
- Limits limits;
- limits.initial = c.env().minMemoryLength;
- limits.maximum = c.env().maxMemoryLength;
- return c.module().addMemory(name, limits);
+ return c.module().addMemory(name, Limits(c.env().minMemoryLength,
+ c.env().maxMemoryLength,
+ c.env().memoryUsage == MemoryUsage::Shared
+ ? Shareable::True
+ : Shareable::False));
}
static AstExpr*
ToAstExpr(AstDecodeContext& c, const InitExpr& initExpr)
{
switch (initExpr.kind()) {
case InitExpr::Kind::Constant: {
return new(c.lifo) AstConst(Val(initExpr.val()));
--- a/js/src/wasm/WasmBinaryToText.cpp
+++ b/js/src/wasm/WasmBinaryToText.cpp
@@ -1243,26 +1243,31 @@ RenderLimits(WasmRenderContext& c, const
if (!RenderInt32(c, limits.initial))
return false;
if (limits.maximum) {
if (!c.buffer.append(" "))
return false;
if (!RenderInt32(c, *limits.maximum))
return false;
}
+ if (limits.shared == Shareable::True) {
+ if (!c.buffer.append(" shared"))
+ return false;
+ }
return true;
}
static bool
RenderResizableTable(WasmRenderContext& c, const Limits& table)
{
if (!c.buffer.append("(table "))
return false;
if (!RenderLimits(c, table))
return false;
+ MOZ_ASSERT(table.shared == Shareable::False);
return c.buffer.append(" anyfunc)");
}
static bool
RenderTableSection(WasmRenderContext& c, const AstModule& module)
{
if (!module.hasTable())
return true;
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -317,17 +317,17 @@ EnforceRangeU32(JSContext* cx, HandleVal
*u32 = uint32_t(x);
MOZ_ASSERT(double(*u32) == x);
return true;
}
static bool
GetLimits(JSContext* cx, HandleObject obj, uint32_t maxInitial, uint32_t maxMaximum,
- const char* kind, Limits* limits)
+ const char* kind, Limits* limits, Shareable allowShared)
{
JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
if (!initialAtom)
return false;
RootedId initialId(cx, AtomToId(initialAtom));
RootedValue initialVal(cx);
if (!GetProperty(cx, obj, obj, initialId, &initialVal))
@@ -336,36 +336,65 @@ GetLimits(JSContext* cx, HandleObject ob
if (!EnforceRangeU32(cx, initialVal, maxInitial, kind, "initial size", &limits->initial))
return false;
JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
if (!maximumAtom)
return false;
RootedId maximumId(cx, AtomToId(maximumAtom));
- bool found;
- if (!HasProperty(cx, obj, maximumId, &found))
+ bool foundMaximum;
+ if (!HasProperty(cx, obj, maximumId, &foundMaximum))
return false;
- if (found) {
+ if (foundMaximum) {
RootedValue maxVal(cx);
if (!GetProperty(cx, obj, obj, maximumId, &maxVal))
return false;
limits->maximum.emplace();
if (!EnforceRangeU32(cx, maxVal, maxMaximum, kind, "maximum size", limits->maximum.ptr()))
return false;
if (limits->initial > *limits->maximum) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_UINT32,
kind, "maximum size");
return false;
}
}
+ limits->shared = Shareable::False;
+
+#ifdef ENABLE_WASM_THREAD_OPS
+ if (allowShared == Shareable::True) {
+ JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
+ if (!sharedAtom)
+ return false;
+ RootedId sharedId(cx, AtomToId(sharedAtom));
+
+ bool foundShared;
+ if (!HasProperty(cx, obj, sharedId, &foundShared))
+ return false;
+
+ if (foundShared) {
+ RootedValue sharedVal(cx);
+ if (!GetProperty(cx, obj, obj, sharedId, &sharedVal))
+ return false;
+
+ limits->shared = ToBoolean(sharedVal) ? Shareable::True : Shareable::False;
+
+ if (limits->shared == Shareable::True && !foundMaximum) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_MISSING_MAXIMUM,
+ kind);
+ return false;
+ }
+ }
+ }
+#endif
+
return true;
}
// ============================================================================
// WebAssembly.Module class and methods
const ClassOps WasmModuleObject::classOps_ =
{
@@ -1245,18 +1274,21 @@ WasmMemoryObject::construct(JSContext* c
if (!args.get(0).isObject()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_DESC_ARG, "memory");
return false;
}
RootedObject obj(cx, &args[0].toObject());
Limits limits;
- if (!GetLimits(cx, obj, MaxMemoryInitialPages, MaxMemoryMaximumPages, "Memory", &limits))
+ if (!GetLimits(cx, obj, MaxMemoryInitialPages, MaxMemoryMaximumPages, "Memory", &limits,
+ Shareable::True))
+ {
return false;
+ }
limits.initial *= PageSize;
if (limits.maximum)
limits.maximum = Some(*limits.maximum * PageSize);
RootedArrayBufferObject buffer(cx,
ArrayBufferObject::createForWasm(cx, limits.initial, limits.maximum));
if (!buffer)
@@ -1555,18 +1587,21 @@ WasmTableObject::construct(JSContext* cx
return false;
if (!StringEqualsAscii(elementStr, "anyfunc")) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
return false;
}
Limits limits;
- if (!GetLimits(cx, obj, MaxTableInitialLength, UINT32_MAX, "Table", &limits))
+ if (!GetLimits(cx, obj, MaxTableInitialLength, UINT32_MAX, "Table", &limits,
+ Shareable::False))
+ {
return false;
+ }
RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits));
if (!table)
return false;
args.rval().setObject(*table);
return true;
}
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -108,16 +108,17 @@ class WasmToken
Nop,
Offset,
OpenParen,
Param,
Result,
Return,
SetGlobal,
SetLocal,
+ Shared,
SignedInteger,
Start,
Store,
Table,
TeeLocal,
TernaryOpcode,
Text,
Then,
@@ -309,16 +310,17 @@ class WasmToken
case NegativeZero:
case Local:
case Module:
case Name:
case Offset:
case OpenParen:
case Param:
case Result:
+ case Shared:
case SignedInteger:
case Start:
case Table:
case Text:
case Then:
case Type:
case UnsignedInteger:
case ValueType:
@@ -1440,16 +1442,20 @@ WasmTokenStream::next()
case 's':
if (consume(u"select"))
return WasmToken(WasmToken::TernaryOpcode, Op::Select, begin, cur_);
if (consume(u"set_global"))
return WasmToken(WasmToken::SetGlobal, begin, cur_);
if (consume(u"set_local"))
return WasmToken(WasmToken::SetLocal, begin, cur_);
+#ifdef ENABLE_WASM_THREAD_OPS
+ if (consume(u"shared"))
+ return WasmToken(WasmToken::Shared, begin, cur_);
+#endif
if (consume(u"start"))
return WasmToken(WasmToken::Start, begin, cur_);
break;
case 't':
if (consume(u"table"))
return WasmToken(WasmToken::Table, begin, cur_);
if (consume(u"tee_local"))
@@ -2788,28 +2794,39 @@ ParseDataSegment(WasmParseContext& c)
if (!fragments.append(text.text()))
return nullptr;
}
return new(c.lifo) AstDataSegment(offset, Move(fragments));
}
static bool
-ParseLimits(WasmParseContext& c, Limits* limits)
+ParseLimits(WasmParseContext& c, Limits* limits, Shareable allowShared)
{
WasmToken initial;
if (!c.ts.match(WasmToken::Index, &initial, c.error))
return false;
Maybe<uint32_t> maximum;
WasmToken token;
if (c.ts.getIf(WasmToken::Index, &token))
maximum.emplace(token.index());
- *limits = Limits(initial.index(), maximum);
+ Shareable shared = Shareable::False;
+ if (c.ts.getIf(WasmToken::Shared, &token)) {
+ // A missing maximum is caught later.
+ if (allowShared == Shareable::True)
+ shared = Shareable::True;
+ else {
+ c.ts.generateError(token, "'shared' not allowed", c.error);
+ return false;
+ }
+ }
+
+ *limits = Limits(initial.index(), maximum, shared);
return true;
}
static bool
ParseMemory(WasmParseContext& c, WasmToken token, AstModule* module)
{
AstName name = c.ts.getIfName();
@@ -2818,17 +2835,17 @@ ParseMemory(WasmParseContext& c, WasmTok
if (c.ts.getIf(WasmToken::Import)) {
InlineImport names;
if (!ParseInlineImport(c, &names))
return false;
if (!c.ts.match(WasmToken::CloseParen, c.error))
return false;
Limits memory;
- if (!ParseLimits(c, &memory))
+ if (!ParseLimits(c, &memory, Shareable::True))
return false;
auto* imp = new(c.lifo) AstImport(name, names.module.text(), names.field.text(),
DefinitionKind::Memory, memory);
return imp && module->append(imp);
}
if (c.ts.getIf(WasmToken::Export)) {
@@ -2866,27 +2883,27 @@ ParseMemory(WasmParseContext& c, WasmTok
if (!segment || !module->append(segment))
return false;
pages = AlignBytes<size_t>(totalLength, PageSize) / PageSize;
if (pages != uint32_t(pages))
return false;
}
- if (!module->addMemory(name, Limits(pages, Some(pages))))
+ if (!module->addMemory(name, Limits(pages, Some(pages), Shareable::False)))
return false;
if (!c.ts.match(WasmToken::CloseParen, c.error))
return false;
return true;
}
Limits memory;
- if (!ParseLimits(c, &memory))
+ if (!ParseLimits(c, &memory, Shareable::True))
return false;
return module->addMemory(name, memory);
}
static bool
ParseStartFunc(WasmParseContext& c, WasmToken token, AstModule* module)
{
@@ -2926,17 +2943,17 @@ ParseElemType(WasmParseContext& c)
{
// Only AnyFunc is allowed at the moment.
return c.ts.match(WasmToken::AnyFunc, c.error);
}
static bool
ParseTableSig(WasmParseContext& c, Limits* table)
{
- return ParseLimits(c, table) &&
+ return ParseLimits(c, table, Shareable::False) &&
ParseElemType(c);
}
static AstImport*
ParseImport(WasmParseContext& c, AstModule* module)
{
AstName name = c.ts.getIfName();
@@ -2951,17 +2968,17 @@ ParseImport(WasmParseContext& c, AstModu
AstRef sigRef;
WasmToken openParen;
if (c.ts.getIf(WasmToken::OpenParen, &openParen)) {
if (c.ts.getIf(WasmToken::Memory)) {
if (name.empty())
name = c.ts.getIfName();
Limits memory;
- if (!ParseLimits(c, &memory))
+ if (!ParseLimits(c, &memory, Shareable::True))
return nullptr;
if (!c.ts.match(WasmToken::CloseParen, c.error))
return nullptr;
return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
DefinitionKind::Memory, memory);
}
if (c.ts.getIf(WasmToken::Table)) {
if (name.empty())
@@ -3158,17 +3175,17 @@ ParseTable(WasmParseContext& c, WasmToke
if (!c.ts.match(WasmToken::CloseParen, c.error))
return false;
uint32_t numElements = uint32_t(elems.length());
if (numElements != elems.length())
return false;
- if (!module->addTable(name, Limits(numElements, Some(numElements))))
+ if (!module->addTable(name, Limits(numElements, Some(numElements), Shareable::False)))
return false;
auto* zero = new(c.lifo) AstConst(Val(uint32_t(0)));
if (!zero)
return false;
AstElemSegment* segment = new(c.lifo) AstElemSegment(zero, Move(elems));
return segment && module->append(segment);
@@ -4398,17 +4415,22 @@ EncodeBytes(Encoder& e, AstName wasmName
TwoByteChars range(wasmName.begin(), wasmName.length());
UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str());
return utf8 && e.writeBytes(utf8.get(), strlen(utf8.get()));
}
static bool
EncodeLimits(Encoder& e, const Limits& limits)
{
- uint32_t flags = limits.maximum ? 1 : 0;
+ uint32_t flags = limits.maximum
+ ? uint32_t(MemoryTableFlags::HasMaximum)
+ : uint32_t(MemoryTableFlags::Default);
+ if (limits.shared == Shareable::True)
+ flags |= uint32_t(MemoryTableFlags::IsShared);
+
if (!e.writeVarU32(flags))
return false;
if (!e.writeVarU32(limits.initial))
return false;
if (limits.maximum) {
if (!e.writeVarU32(*limits.maximum))
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1392,26 +1392,37 @@ struct Assumptions
// A Module can either be asm.js or wasm.
enum ModuleKind
{
Wasm,
AsmJS
};
+enum class Shareable
+{
+ False,
+ True
+};
+
// Represents the resizable limits of memories and tables.
struct Limits
{
uint32_t initial;
Maybe<uint32_t> maximum;
+ // `shared` is Shareable::False for tables but may be Shareable::True for
+ // memories.
+ Shareable shared;
+
Limits() = default;
- explicit Limits(uint32_t initial, const Maybe<uint32_t>& maximum = Nothing())
- : initial(initial), maximum(maximum)
+ explicit Limits(uint32_t initial, const Maybe<uint32_t>& maximum = Nothing(),
+ Shareable shared = Shareable::False)
+ : initial(initial), maximum(maximum), shared(shared)
{}
};
// TableDesc describes a table as well as the offset of the table's base pointer
// in global memory. Currently, wasm only has "any function" and asm.js only
// "typed function".
enum class TableKind
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -864,42 +864,59 @@ DecodeSignatureIndex(Decoder& d, const S
if (*sigIndex >= sigs.length())
return d.fail("signature index out of range");
return true;
}
static bool
-DecodeLimits(Decoder& d, Limits* limits)
+DecodeLimits(Decoder& d, Limits* limits, Shareable allowShared = Shareable::False)
{
uint8_t flags;
if (!d.readFixedU8(&flags))
return d.fail("expected flags");
- if (flags & ~uint8_t(0x1))
- return d.failf("unexpected bits set in flags: %" PRIu32, (flags & ~uint8_t(0x1)));
+ uint8_t mask = allowShared == Shareable::True
+ ? uint8_t(MemoryMasks::AllowShared)
+ : uint8_t(MemoryMasks::AllowUnshared);
+
+ if (flags & ~uint8_t(mask))
+ return d.failf("unexpected bits set in flags: %" PRIu32, (flags & ~uint8_t(mask)));
if (!d.readVarU32(&limits->initial))
return d.fail("expected initial length");
- if (flags & 0x1) {
+ if (flags & uint8_t(MemoryTableFlags::HasMaximum)) {
uint32_t maximum;
if (!d.readVarU32(&maximum))
return d.fail("expected maximum length");
if (limits->initial > maximum) {
return d.failf("memory size minimum must not be greater than maximum; "
"maximum length %" PRIu32 " is less than initial length %" PRIu32,
maximum, limits->initial);
}
limits->maximum.emplace(maximum);
}
+ limits->shared = Shareable::False;
+
+#ifdef ENABLE_WASM_THREAD_OPS
+ if (allowShared == Shareable::True) {
+ if ((flags & uint8_t(MemoryTableFlags::IsShared)) && !(flags & uint8_t(MemoryTableFlags::HasMaximum)))
+ return d.fail("maximum length required for shared memory");
+
+ limits->shared = (flags & uint8_t(MemoryTableFlags::IsShared))
+ ? Shareable::True
+ : Shareable::False;
+ }
+#endif
+
return true;
}
static bool
DecodeTableLimits(Decoder& d, TableDescVector* tables)
{
uint8_t elementType;
if (!d.readFixedU8(&elementType))
@@ -959,17 +976,17 @@ DecodeGlobalType(Decoder& d, ValType* ty
static bool
DecodeMemoryLimits(Decoder& d, ModuleEnvironment* env)
{
if (env->usesMemory())
return d.fail("already have default memory");
Limits memory;
- if (!DecodeLimits(d, &memory))
+ if (!DecodeLimits(d, &memory, Shareable::True))
return false;
if (memory.initial > MaxMemoryInitialPages)
return d.fail("initial memory size too big");
CheckedInt<uint32_t> initialBytes = memory.initial;
initialBytes *= PageSize;
MOZ_ASSERT(initialBytes.isValid());
@@ -982,17 +999,19 @@ DecodeMemoryLimits(Decoder& d, ModuleEnv
CheckedInt<uint32_t> maximumBytes = *memory.maximum;
maximumBytes *= PageSize;
// Clamp the maximum memory value to UINT32_MAX; it's not semantically
// visible since growing will fail for values greater than INT32_MAX.
memory.maximum = Some(maximumBytes.isValid() ? maximumBytes.value() : UINT32_MAX);
}
- env->memoryUsage = MemoryUsage::Unshared;
+ env->memoryUsage = memory.shared == Shareable::True
+ ? MemoryUsage::Shared
+ : MemoryUsage::Unshared;
env->minMemoryLength = memory.initial;
env->maxMemoryLength = memory.maximum;
return true;
}
static bool
DecodeImport(Decoder& d, ModuleEnvironment* env)
{