Skip to content

Commit

Permalink
Bug 1737267 - Memory64 JSAPI tweaks. r=yury
Browse files Browse the repository at this point in the history
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
  • Loading branch information
Lars T Hansen committed Oct 26, 2021
1 parent 3e4bf73 commit e35ee71
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 101 deletions.
17 changes: 11 additions & 6 deletions js/src/jit-test/tests/wasm/memory64/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -254,15 +250,24 @@ 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});
assertEq(m64.type().index, "i64");

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
Expand Down
212 changes: 117 additions & 95 deletions js/src/wasm/WasmJS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand All @@ -838,87 +842,136 @@ 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 GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
Limits* limits) {
JSAtom* initialAtom = Atomize(cx, "initial", strlen("initial"));
if (!initialAtom) {
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;
}
RootedId initialId(cx, AtomToId(initialAtom));

const char* noun = (kind == LimitsKind::Memory ? "Memory" : "Table");
*u32 = uint32_t(u64);
return true;
}

RootedValue initialVal(cx);
if (!GetProperty(cx, obj, obj, initialId, &initialVal)) {
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));

uint32_t initial = 0;
if (!initialVal.isUndefined() &&
!EnforceRangeU32(cx, initialVal, noun, "initial size", &initial)) {
RootedValue val(cx);
if (!GetProperty(cx, obj, obj, id, &val)) {
return false;
}
limits->initial = initial;

#ifdef ENABLE_WASM_TYPE_REFLECTIONS
// Get minimum parameter.
JSAtom* minimumAtom = Atomize(cx, "minimum", strlen("minimum"));
if (!minimumAtom) {
return false;
if (val.isUndefined()) {
*found = false;
return true;
}
RootedId minimumId(cx, AtomToId(minimumAtom));
*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) {
limits->indexType = IndexType::I32;

RootedValue minimumVal(cx);
if (!GetProperty(cx, obj, obj, minimumId, &minimumVal)) {
// 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) {
#ifdef ENABLE_WASM_MEMORY64
// Get the index type field
JSAtom* indexTypeAtom = Atomize(cx, "index", strlen("index"));
if (!indexTypeAtom) {
return false;
}
RootedId indexTypeId(cx, AtomToId(indexTypeAtom));

RootedValue indexTypeVal(cx);
if (!GetProperty(cx, obj, obj, indexTypeId, &indexTypeVal)) {
return false;
}

// The index type has a default value
if (!indexTypeVal.isUndefined()) {
if (!ToIndexType(cx, indexTypeVal, &limits->indexType)) {
return false;
}

if (limits->indexType == IndexType::I64 && !Memory64Available(cx)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_MEM64_LINK);
return false;
}
}
#endif
}

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;
}
if (haveInitial) {
limits->initial = limit;
}

uint32_t minimum = 0;
if (!minimumVal.isUndefined() &&
!EnforceRangeU32(cx, minimumVal, noun, "initial size", &minimum)) {
bool haveMinimum = false;
#ifdef ENABLE_WASM_TYPE_REFLECTIONS
if (!GetLimit(cx, obj, "minimum", noun, "initial size", range, &haveMinimum,
&limit)) {
return false;
}
if (!minimumVal.isUndefined()) {
limits->initial = minimum;
if (haveMinimum) {
limits->initial = limit;
}
#endif

// Get maximum parameter.
JSAtom* maximumAtom = Atomize(cx, "maximum", strlen("maximum"));
if (!maximumAtom) {
if (!(haveInitial || haveMinimum)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_REQUIRED, "initial");
return false;
}
RootedId maximumId(cx, AtomToId(maximumAtom));

RootedValue maxVal(cx);
if (!GetProperty(cx, obj, obj, maximumId, &maxVal)) {
if (haveInitial && haveMinimum) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_SUPPLY_ONLY_ONE, "minimum", "initial");
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);
bool haveMaximum = false;
if (!GetLimit(cx, obj, "maximum", noun, "maximum size", range, &haveMaximum,
&limit)) {
return false;
}
if (haveMaximum) {
limits->maximum = Some(limit);
}

limits->indexType = IndexType::I32;
limits->shared = Shareable::False;

// Memory limits may be shared or specify an alternate index type
// Memory limits may be shared.
if (kind == LimitsKind::Memory) {
// Get the shared field
JSAtom* sharedAtom = Atomize(cx, "shared", strlen("shared"));
Expand All @@ -938,7 +991,7 @@ static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
ToBoolean(sharedVal) ? Shareable::True : Shareable::False;

if (limits->shared == Shareable::True) {
if (maxVal.isUndefined()) {
if (!haveMaximum) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_MAXIMUM, noun);
return false;
Expand All @@ -953,50 +1006,8 @@ static bool GetLimits(JSContext* cx, HandleObject obj, LimitsKind kind,
}
}
}

#ifdef ENABLE_WASM_MEMORY64
// Get the index type field
JSAtom* indexTypeAtom = Atomize(cx, "index", strlen("index"));
if (!indexTypeAtom) {
return false;
}
RootedId indexTypeId(cx, AtomToId(indexTypeAtom));

RootedValue indexTypeVal(cx);
if (!GetProperty(cx, obj, obj, indexTypeId, &indexTypeVal)) {
return false;
}

// The index type has a default value
if (!indexTypeVal.isUndefined()) {
if (!ToIndexType(cx, indexTypeVal, &limits->indexType)) {
return false;
}

if (limits->indexType == IndexType::I64 && !Memory64Available(cx)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_WASM_NO_MEM64_LINK);
return false;
}
}
#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");
return false;
}
#else
if (initialVal.isUndefined()) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_WASM_MISSING_REQUIRED, "initial");
return false;
}
#endif

return true;
}

Expand Down Expand Up @@ -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;
}
Expand Down

0 comments on commit e35ee71

Please sign in to comment.