[wasm][memory64] Support shared memory
For some reason we overlooked shared memory64 so far. Supporting it is trivial, we just need to fix flag parsing. To make parsing simpler, we replace the switch by a bit-decoding logic. R=jkummerow@chromium.org Bug: v8:10949, v8:13401 Change-Id: I1d884a174f901ed359c1d385055c9f2d24b0e2f4 Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3967904 Commit-Queue: Clemens Backes <clemensb@chromium.org> Reviewed-by: Jakob Kummerow <jkummerow@chromium.org> Cr-Commit-Position: refs/heads/main@{#83846}
This commit is contained in:
parent
92a7385171
commit
c745dab977
@ -1927,42 +1927,39 @@ class ModuleDecoderTemplate : public Decoder {
|
||||
return flags;
|
||||
}
|
||||
|
||||
uint8_t validate_memory_flags(bool* has_shared_memory, bool* is_memory64) {
|
||||
uint8_t validate_memory_flags(bool* is_shared_out, bool* is_memory64_out) {
|
||||
tracer_.Bytes(pc_, 1);
|
||||
uint8_t flags = consume_u8("memory limits flags");
|
||||
*has_shared_memory = false;
|
||||
switch (flags) {
|
||||
case kNoMaximum:
|
||||
case kWithMaximum:
|
||||
break;
|
||||
case kSharedNoMaximum:
|
||||
case kSharedWithMaximum:
|
||||
*has_shared_memory = true;
|
||||
// V8 does not support shared memory without a maximum.
|
||||
if (flags == kSharedNoMaximum) {
|
||||
errorf(pc() - 1,
|
||||
"memory limits flags must have maximum defined if shared is "
|
||||
"true");
|
||||
}
|
||||
break;
|
||||
case kMemory64NoMaximum:
|
||||
case kMemory64WithMaximum:
|
||||
if (!enabled_features_.has_memory64()) {
|
||||
errorf(pc() - 1,
|
||||
"invalid memory limits flags 0x%x (enable via "
|
||||
"--experimental-wasm-memory64)",
|
||||
flags);
|
||||
}
|
||||
*is_memory64 = true;
|
||||
break;
|
||||
default:
|
||||
errorf(pc() - 1, "invalid memory limits flags 0x%x", flags);
|
||||
break;
|
||||
// Flags 0..7 are valid (3 bits).
|
||||
if (flags & ~0x7) {
|
||||
errorf(pc() - 1, "invalid memory limits flags 0x%x", flags);
|
||||
}
|
||||
if (*has_shared_memory) tracer_.Description(" shared");
|
||||
if (*is_memory64) tracer_.Description(" mem64");
|
||||
tracer_.Description((flags & 1) ? " with maximum" : " no maximum");
|
||||
// Decode the three bits.
|
||||
bool is_memory64 = flags & 0x4;
|
||||
bool is_shared = flags & 0x2;
|
||||
bool has_maximum = flags & 0x1;
|
||||
// Store into output parameters.
|
||||
*is_shared_out = is_shared;
|
||||
*is_memory64_out = is_memory64;
|
||||
|
||||
// V8 does not support shared memory without a maximum.
|
||||
if (is_shared && !has_maximum) {
|
||||
errorf(pc() - 1, "shared memory must have a maximum defined");
|
||||
}
|
||||
|
||||
if (is_memory64 && !enabled_features_.has_memory64()) {
|
||||
errorf(pc() - 1,
|
||||
"invalid memory limits flags 0x%x (enable via "
|
||||
"--experimental-wasm-memory64)",
|
||||
flags);
|
||||
}
|
||||
|
||||
// Tracing.
|
||||
if (is_shared) tracer_.Description(" shared");
|
||||
if (is_memory64) tracer_.Description(" mem64");
|
||||
tracer_.Description(has_maximum ? " with maximum" : " no maximum");
|
||||
tracer_.NextLine();
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
@ -74,12 +74,14 @@ enum ImportExportKindCode : uint8_t {
|
||||
};
|
||||
|
||||
enum LimitsFlags : uint8_t {
|
||||
kNoMaximum = 0x00, // Also valid for table limits.
|
||||
kWithMaximum = 0x01, // Also valid for table limits.
|
||||
kSharedNoMaximum = 0x02, // Only valid for memory limits.
|
||||
kSharedWithMaximum = 0x03, // Only valid for memory limits.
|
||||
kMemory64NoMaximum = 0x04, // Only valid for memory limits.
|
||||
kMemory64WithMaximum = 0x05 // Only valid for memory limits.
|
||||
kNoMaximum = 0x00, // Also valid for table limits.
|
||||
kWithMaximum = 0x01, // Also valid for table limits.
|
||||
kSharedNoMaximum = 0x02, // Only valid for memory limits.
|
||||
kSharedWithMaximum = 0x03, // Only valid for memory limits.
|
||||
kMemory64NoMaximum = 0x04, // Only valid for memory limits.
|
||||
kMemory64WithMaximum = 0x05, // Only valid for memory limits.
|
||||
kMemory64SharedNoMaximum = 0x06, // Only valid for memory limits.
|
||||
kMemory64SharedWithMaximum = 0x07 // Only valid for memory limits.
|
||||
};
|
||||
|
||||
// Flags for data and element segments.
|
||||
|
@ -278,3 +278,96 @@ function allowOOM(fn) {
|
||||
assertTraps(kTrapMemOutOfBounds, () => fill(1n << 62n, 0, 1n));
|
||||
assertTraps(kTrapMemOutOfBounds, () => fill(1n << 63n, 0, 1n));
|
||||
})();
|
||||
|
||||
(function TestMemory64SharedBasic() {
|
||||
print(arguments.callee.name);
|
||||
let builder = new WasmModuleBuilder();
|
||||
builder.addMemory64(1, 10, true, true);
|
||||
builder.addFunction('load', makeSig([kWasmI64], [kWasmI32]))
|
||||
.addBody([
|
||||
kExprLocalGet, 0, // local.get 0
|
||||
kExprI32LoadMem, 0, 0, // i32.load_mem align=1 offset=0
|
||||
])
|
||||
.exportFunc();
|
||||
let instance = builder.instantiate();
|
||||
|
||||
assertTrue(instance.exports.memory instanceof WebAssembly.Memory);
|
||||
assertTrue(instance.exports.memory.buffer instanceof SharedArrayBuffer);
|
||||
assertEquals(0, instance.exports.load(0n));
|
||||
})();
|
||||
|
||||
(function TestMemory64SharedBetweenWorkers() {
|
||||
print(arguments.callee.name);
|
||||
// Generate a shared memory64 by instantiating an module that exports one.
|
||||
// TODO(clemensb): Use the proper API once that's decided.
|
||||
let shared_mem64 = (function() {
|
||||
let builder = new WasmModuleBuilder();
|
||||
builder.addMemory64(1, 10, true, true);
|
||||
return builder.instantiate().exports.memory;
|
||||
})();
|
||||
|
||||
let builder = new WasmModuleBuilder();
|
||||
builder.addImportedMemory('imp', 'mem', 1, 10, true, true);
|
||||
|
||||
builder.addFunction('grow', makeSig([kWasmI64], [kWasmI64]))
|
||||
.addBody([
|
||||
kExprLocalGet, 0, // local.get 0
|
||||
kExprMemoryGrow, 0, // memory.grow 0
|
||||
])
|
||||
.exportFunc();
|
||||
builder.addFunction('load', makeSig([kWasmI64], [kWasmI32]))
|
||||
.addBody([
|
||||
kExprLocalGet, 0, // local.get 0
|
||||
kExprI32LoadMem, 0, 0, // i32.load_mem align=1 offset=0
|
||||
])
|
||||
.exportFunc();
|
||||
builder.addFunction('store', makeSig([kWasmI64, kWasmI32], []))
|
||||
.addBody([
|
||||
kExprLocalGet, 0, // local.get 0
|
||||
kExprLocalGet, 1, // local.get 1
|
||||
kExprI32StoreMem, 0, 0, // i32.store_mem align=1 offset=0
|
||||
])
|
||||
.exportFunc();
|
||||
|
||||
let module = builder.toModule();
|
||||
let instance = new WebAssembly.Instance(module, {imp: {mem: shared_mem64}});
|
||||
|
||||
assertEquals(1n, instance.exports.grow(2n));
|
||||
assertEquals(3n, instance.exports.grow(1n));
|
||||
const kOffset1 = 47n;
|
||||
const kOffset2 = 128n;
|
||||
const kValue = 21;
|
||||
assertEquals(0, instance.exports.load(kOffset1));
|
||||
instance.exports.store(kOffset1, kValue);
|
||||
assertEquals(kValue, instance.exports.load(kOffset1));
|
||||
let worker = new Worker(function() {
|
||||
onmessage = function([mem, module]) {
|
||||
function workerAssert(condition, message) {
|
||||
if (!condition) postMessage(`Check failed: ${message}`);
|
||||
}
|
||||
|
||||
function workerAssertEquals(expected, actual, message) {
|
||||
if (expected != actual)
|
||||
postMessage(`Check failed (${message}): ${expected} != ${actual}`);
|
||||
}
|
||||
|
||||
const kOffset1 = 47n;
|
||||
const kOffset2 = 128n;
|
||||
const kValue = 21;
|
||||
workerAssert(mem instanceof WebAssembly.Memory, 'Wasm memory');
|
||||
workerAssert(mem.buffer instanceof SharedArrayBuffer);
|
||||
workerAssertEquals(4, mem.grow(1), 'grow');
|
||||
let instance = new WebAssembly.Instance(module, {imp: {mem: mem}});
|
||||
let exports = instance.exports;
|
||||
workerAssertEquals(kValue, exports.load(kOffset1), 'load 1');
|
||||
workerAssertEquals(0, exports.load(kOffset2), 'load 2');
|
||||
exports.store(kOffset2, kValue);
|
||||
workerAssertEquals(kValue, exports.load(kOffset2), 'load 3');
|
||||
postMessage('OK');
|
||||
}
|
||||
}, {type: 'function'});
|
||||
worker.postMessage([shared_mem64, module]);
|
||||
assertEquals('OK', worker.getMessage());
|
||||
assertEquals(kValue, instance.exports.load(kOffset2));
|
||||
assertEquals(5n, instance.exports.grow(1n));
|
||||
})();
|
||||
|
@ -90,6 +90,8 @@ let kLimitsSharedNoMaximum = 0x02;
|
||||
let kLimitsSharedWithMaximum = 0x03;
|
||||
let kLimitsMemory64NoMaximum = 0x04;
|
||||
let kLimitsMemory64WithMaximum = 0x05;
|
||||
let kLimitsMemory64SharedNoMaximum = 0x06;
|
||||
let kLimitsMemory64SharedWithMaximum = 0x07;
|
||||
|
||||
// Segment flags
|
||||
let kActiveNoIndex = 0;
|
||||
@ -1299,12 +1301,12 @@ class WasmModuleBuilder {
|
||||
return this;
|
||||
}
|
||||
|
||||
addMemory64(min, max, exported) {
|
||||
addMemory64(min, max, exported, shared) {
|
||||
this.memory = {
|
||||
min: min,
|
||||
max: max,
|
||||
exported: exported,
|
||||
shared: false,
|
||||
shared: shared || false,
|
||||
is_memory64: true
|
||||
};
|
||||
return this;
|
||||
@ -1457,14 +1459,15 @@ class WasmModuleBuilder {
|
||||
return this.num_imported_globals++;
|
||||
}
|
||||
|
||||
addImportedMemory(module, name, initial = 0, maximum, shared) {
|
||||
addImportedMemory(module, name, initial = 0, maximum, shared, is_memory64) {
|
||||
let o = {
|
||||
module: module,
|
||||
name: name,
|
||||
kind: kExternalMemory,
|
||||
initial: initial,
|
||||
maximum: maximum,
|
||||
shared: shared
|
||||
shared: !!shared,
|
||||
is_memory64: !!is_memory64
|
||||
};
|
||||
this.imports.push(o);
|
||||
return this;
|
||||
@ -1671,7 +1674,7 @@ class WasmModuleBuilder {
|
||||
});
|
||||
}
|
||||
|
||||
// Add imports section
|
||||
// Add imports section.
|
||||
if (wasm.imports.length > 0) {
|
||||
if (debug) print('emitting imports @ ' + binary.length);
|
||||
binary.emit_section(kImportSectionCode, section => {
|
||||
@ -1686,15 +1689,16 @@ class WasmModuleBuilder {
|
||||
section.emit_type(imp.type);
|
||||
section.emit_u8(imp.mutable);
|
||||
} else if (imp.kind == kExternalMemory) {
|
||||
var has_max = (typeof imp.maximum) != 'undefined';
|
||||
var is_shared = (typeof imp.shared) != 'undefined';
|
||||
if (is_shared) {
|
||||
section.emit_u8(has_max ? 3 : 2); // flags
|
||||
} else {
|
||||
section.emit_u8(has_max ? 1 : 0); // flags
|
||||
}
|
||||
section.emit_u32v(imp.initial); // initial
|
||||
if (has_max) section.emit_u32v(imp.maximum); // maximum
|
||||
const has_max = imp.maximum !== undefined;
|
||||
const is_shared = !!imp.shared;
|
||||
const is_memory64 = !!imp.is_memory64;
|
||||
let limits_byte =
|
||||
(is_memory64 ? 4 : 0) | (is_shared ? 2 : 0) | (has_max ? 1 : 0);
|
||||
section.emit_u8(limits_byte);
|
||||
let emit = val =>
|
||||
is_memory64 ? section.emit_u64v(val) : section.emit_u32v(val);
|
||||
emit(imp.initial);
|
||||
if (has_max) emit(imp.maximum);
|
||||
} else if (imp.kind == kExternalTable) {
|
||||
section.emit_type(imp.type);
|
||||
var has_max = (typeof imp.maximum) != 'undefined';
|
||||
@ -1752,23 +1756,15 @@ class WasmModuleBuilder {
|
||||
binary.emit_section(kMemorySectionCode, section => {
|
||||
section.emit_u8(1); // one memory entry
|
||||
const has_max = wasm.memory.max !== undefined;
|
||||
if (wasm.memory.is_memory64) {
|
||||
if (wasm.memory.shared) {
|
||||
throw new Error('sharing memory64 is not supported (yet)');
|
||||
}
|
||||
section.emit_u8(
|
||||
has_max ? kLimitsMemory64WithMaximum : kLimitsMemory64NoMaximum);
|
||||
section.emit_u64v(wasm.memory.min);
|
||||
if (has_max) section.emit_u64v(wasm.memory.max);
|
||||
} else {
|
||||
section.emit_u8(
|
||||
wasm.memory.shared ?
|
||||
(has_max ? kLimitsSharedWithMaximum :
|
||||
kLimitsSharedNoMaximum) :
|
||||
(has_max ? kLimitsWithMaximum : kLimitsNoMaximum));
|
||||
section.emit_u32v(wasm.memory.min);
|
||||
if (has_max) section.emit_u32v(wasm.memory.max);
|
||||
}
|
||||
const is_shared = !!wasm.memory.shared;
|
||||
const is_memory64 = !!wasm.memory.is_memory64;
|
||||
let limits_byte =
|
||||
(is_memory64 ? 4 : 0) | (is_shared ? 2 : 0) | (has_max ? 1 : 0);
|
||||
section.emit_u8(limits_byte);
|
||||
let emit = val =>
|
||||
is_memory64 ? section.emit_u64v(val) : section.emit_u32v(val);
|
||||
emit(wasm.memory.min);
|
||||
if (has_max) emit(wasm.memory.max);
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user