Bug 1737267 - Memory64 JSAPI tweaks. r=yury

Make the parsing of the memory descriptor for WA.Memory handle the
full page range for i64 memories, for initial, minimum, and maximum.
I had to refactor a little bit to make this sensible.  Add test case.

Make the memory descriptor returned by WA.Memory.type() handle the
full page range for i64 memories, for minimum and maximum.  Add test
case.

Differential Revision: https://phabricator.services.mozilla.com/D129396
This commit is contained in:
Lars T Hansen 2021-10-26 04:31:04 +00:00
parent 02982523bc
commit 3272719a36
2 changed files with 151 additions and 124 deletions

View File

@ -26,18 +26,14 @@ function validMemoryType(shared, initial, max) {
wasmEvalText(memoryTypeModuleText(shared, initial, max));
// TODO: JS-API cannot specify pages above UINT32_MAX
// https://github.com/WebAssembly/memory64/issues/24
if (max <= MaxUint32) {
new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max));
}
new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max));
}
function invalidMemoryType(shared, initial, max, compileMessage, jsMessage) {
wasmFailValidateText(memoryTypeModuleText(shared, initial, max), compileMessage);
assertErrorMessage(() => wasmEvalText(memoryTypeModuleText(shared, initial, max)), WebAssembly.CompileError, compileMessage);
// TODO: JS-API cannot specify pages above UINT32_MAX
// https://github.com/WebAssembly/memory64/issues/24
if (max === undefined || max <= MaxUint32) {
assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)), Error, jsMessage);
}
assertErrorMessage(() => new WebAssembly.Memory(memoryTypeDescriptor(shared, initial, max)), Error, jsMessage);
}
// valid to define a memory with i64
@ -254,6 +250,7 @@ if (getBuildConfiguration()["pointer-byte-size"] == 8) {
if (WebAssembly.Function) {
// TODO: "index" is not yet part of the spec
// TODO: values outside the u32 range are not yet part of the spec
// https://github.com/WebAssembly/memory64/issues/24
let m64 = new WebAssembly.Memory({index:"i64", initial:1});
@ -261,8 +258,16 @@ if (WebAssembly.Function) {
let m32 = new WebAssembly.Memory({initial:1});
assertEq(m32.type().index, "i32");
let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`
(module
(memory (export "mem") i64 1 0x100000000))`)));
assertEq(ins.exports.mem.type().minimum, 1);
assertEq(ins.exports.mem.type().maximum, 0x100000000);
}
// Instructions
const SMALL = 64; // < offsetguard everywhere
const BIG = 131072; // > offsetguard on 32-bit
const HUGE = 2147483656; // > offsetguard on 64-bit

View File

@ -814,8 +814,12 @@ bool wasm::DeserializeModule(JSContext* cx, const Bytes& serialized,
// '[EnforceRange] unsigned long' types are coerced with
// ConvertToInt(v, 32, 'unsigned')
// defined in Web IDL Section 3.2.4.9.
static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
const char* noun, uint32_t* u32) {
//
// This just generalizes that to an arbitrary limit that is representable as an
// integer in double form.
static bool EnforceRange(JSContext* cx, HandleValue v, const char* kind,
const char* noun, uint64_t max, uint64_t* val) {
// Step 4.
double x;
if (!ToNumber(cx, v, &x)) {
@ -838,122 +842,59 @@ static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
x = JS::ToInteger(x);
// Step 6.3.
if (x < 0 || x > double(UINT32_MAX)) {
if (x < 0 || x > double(max)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_BAD_UINT32, kind, noun);
return false;
}
*u32 = uint32_t(x);
MOZ_ASSERT(double(*u32) == x);
*val = uint64_t(x);
MOZ_ASSERT(double(*val) == x);
return true;
}
static bool EnforceRangeU32(JSContext* cx, HandleValue v, const char* kind,
const char* noun, uint32_t* u32) {
uint64_t u64 = 0;
if (!EnforceRange(cx, v, kind, noun, uint64_t(UINT32_MAX), &u64)) {
return false;
}
*u32 = uint32_t(u64);
return true;
}
static bool GetLimit(JSContext* cx, HandleObject obj, const char* name,
const char* noun, const char* msg, uint32_t range,
bool* found, uint64_t* value) {
JSAtom* atom = Atomize(cx, name, strlen(name));
if (!atom) {
return false;
}
RootedId id(cx, AtomToId(atom));
RootedValue val(cx);
if (!GetProperty(cx, obj, obj, id, &val)) {
return false;
}
if (val.isUndefined()) {
*found = false;
return true;
}
*found = true;
// The range can be greater than 53, but then the logic in EnforceRange has to
// change to avoid precision loss.
MOZ_ASSERT(range < 54);
return EnforceRange(cx, val, noun, msg, (uint64_t(1) << range) - 1, value);
}
static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
Limits* limits) {
JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
if (!initialAtom) {
return false;
}
RootedId initialId(cx, AtomToId(initialAtom));
const char* noun = (kind == LimitsKind::Memory ? "Memory" : "Table");
RootedValue initialVal(cx);
if (!GetProperty(cx, obj, obj, initialId, &initialVal)) {
return false;
}
uint32_t initial = 0;
if (!initialVal.isUndefined() &&
!EnforceRangeU32(cx, initialVal, noun, "initial size", &initial)) {
return false;
}
limits->initial = initial;
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
// Get minimum parameter.
JSAtom* minimumAtom = Atomize(cx, "minimum", strlen("minimum"));
if (!minimumAtom) {
return false;
}
RootedId minimumId(cx, AtomToId(minimumAtom));
RootedValue minimumVal(cx);
if (!GetProperty(cx, obj, obj, minimumId, &minimumVal)) {
return false;
}
uint32_t minimum = 0;
if (!minimumVal.isUndefined() &&
!EnforceRangeU32(cx, minimumVal, noun, "initial size", &minimum)) {
return false;
}
if (!minimumVal.isUndefined()) {
limits->initial = minimum;
}
#endif
// Get maximum parameter.
JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
if (!maximumAtom) {
return false;
}
RootedId maximumId(cx, AtomToId(maximumAtom));
RootedValue maxVal(cx);
if (!GetProperty(cx, obj, obj, maximumId, &maxVal)) {
return false;
}
// maxVal does not have a default value.
if (!maxVal.isUndefined()) {
uint32_t maximum = 0;
if (!EnforceRangeU32(cx, maxVal, noun, "maximum size", &maximum)) {
return false;
}
limits->maximum = Some(maximum);
}
limits->indexType = IndexType::I32;
limits->shared = Shareable::False;
// Memory limits may be shared or specify an alternate index type
// Memory limits may specify an alternate index type, and we need this to
// check the ranges for initial and maximum, so look for the index type first.
if (kind == LimitsKind::Memory) {
// Get the shared field
JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
if (!sharedAtom) {
return false;
}
RootedId sharedId(cx, AtomToId(sharedAtom));
RootedValue sharedVal(cx);
if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
return false;
}
// shared's default value is false, which is already the value set above.
if (!sharedVal.isUndefined()) {
limits->shared =
ToBoolean(sharedVal) ? Shareable::True : Shareable::False;
if (limits->shared == Shareable::True) {
if (maxVal.isUndefined()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_MAXIMUM, noun);
return false;
}
if (!cx->realm()
->creationOptions()
.getSharedMemoryAndAtomicsEnabled()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_SHMEM_LINK);
return false;
}
}
}
#ifdef ENABLE_WASM_MEMORY64
// Get the index type field
JSAtom* indexTypeAtom = Atomize(cx, "index", strlen("index"));
@ -982,20 +923,90 @@ static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
#endif
}
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
// Check both minimum and initial are not supplied.
if (minimumVal.isUndefined() == initialVal.isUndefined()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum", "initial");
const char* noun = (kind == LimitsKind::Memory ? "Memory" : "Table");
// 2^48 is a valid value, so the range goes to 49 bits. Values above 2^48 are
// filtered later, just as values above 2^16 are filtered for mem32.
const uint32_t range = limits->indexType == IndexType::I32 ? 32 : 49;
uint64_t limit = 0;
bool haveInitial = false;
if (!GetLimit(cx, obj, "initial", noun, "initial size", range, &haveInitial,
&limit)) {
return false;
}
#else
if (initialVal.isUndefined()) {
if (haveInitial) {
limits->initial = limit;
}
bool haveMinimum = false;
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
if (!GetLimit(cx, obj, "minimum", noun, "initial size", range, &haveMinimum,
&limit)) {
return false;
}
if (haveMinimum) {
limits->initial = limit;
}
#endif
if (!(haveInitial || haveMinimum)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_REQUIRED, "initial");
return false;
}
#endif
if (haveInitial && haveMinimum) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum", "initial");
return false;
}
bool haveMaximum = false;
if (!GetLimit(cx, obj, "maximum", noun, "maximum size", range, &haveMaximum,
&limit)) {
return false;
}
if (haveMaximum) {
limits->maximum = Some(limit);
}
limits->shared = Shareable::False;
// Memory limits may be shared.
if (kind == LimitsKind::Memory) {
// Get the shared field
JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
if (!sharedAtom) {
return false;
}
RootedId sharedId(cx, AtomToId(sharedAtom));
RootedValue sharedVal(cx);
if (!GetProperty(cx, obj, obj, sharedId, &sharedVal)) {
return false;
}
// shared's default value is false, which is already the value set above.
if (!sharedVal.isUndefined()) {
limits->shared =
ToBoolean(sharedVal) ? Shareable::True : Shareable::False;
if (limits->shared == Shareable::True) {
if (!haveMaximum) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_MAXIMUM, noun);
return false;
}
if (!cx->realm()
->creationOptions()
.getSharedMemoryAndAtomicsEnabled()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_SHMEM_LINK);
return false;
}
}
}
}
return true;
}
@ -1147,17 +1158,28 @@ static JSObject* MemoryTypeToObject(JSContext* cx, bool shared,
Maybe<wasm::Pages> maxPages) {
Rooted<IdValueVector> props(cx, IdValueVector(cx));
if (maxPages) {
uint32_t maxPages32 = mozilla::AssertedCast<uint32_t>(maxPages->value());
double maxPagesNum;
if (indexType == IndexType::I32) {
maxPagesNum = double(mozilla::AssertedCast<uint32_t>(maxPages->value()));
} else {
// The maximum number of pages is 2^48.
maxPagesNum = double(maxPages->value());
}
if (!props.append(IdValuePair(NameToId(cx->names().maximum),
Int32Value(maxPages32)))) {
NumberValue(maxPagesNum)))) {
ReportOutOfMemory(cx);
return nullptr;
}
}
uint32_t minPages32 = mozilla::AssertedCast<uint32_t>(minPages.value());
if (!props.append(
IdValuePair(NameToId(cx->names().minimum), Int32Value(minPages32)))) {
double minPagesNum;
if (indexType == IndexType::I32) {
minPagesNum = double(mozilla::AssertedCast<uint32_t>(minPages.value()));
} else {
minPagesNum = double(minPages.value());
}
if (!props.append(IdValuePair(NameToId(cx->names().minimum),
NumberValue(minPagesNum)))) {
ReportOutOfMemory(cx);
return nullptr;
}