Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use proxy objects for MEMORY64 HEAP access over 4Gb #19737

Merged
merged 27 commits into from
Jul 27, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion emcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -3716,7 +3716,8 @@ def phase_binaryen(target, options, wasm_target):
# >=2GB heap support requires pointers in JS to be unsigned. rather than
# require all pointers to be unsigned by default, which increases code size
# a little, keep them signed, and just unsign them here if we need that.
if settings.CAN_ADDRESS_2GB:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line can now be reverted since CAN_ADDRESS_2GB is never set when MEMORY64 is set: See #19755

# for MEMORY64 heap proxies are created that handle unsigning internally
if settings.CAN_ADDRESS_2GB and not settings.MEMORY64:
with ToolchainProfiler.profile_block('use_unsigned_pointers_in_js'):
final_js = building.use_unsigned_pointers_in_js(final_js)

Expand Down
4 changes: 2 additions & 2 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -419,7 +419,7 @@ mergeInto(LibraryManager.library, {
? (dest, src, num) => HEAPU8.copyWithin(dest, src, src + num)
: (dest, src, num) => HEAPU8.set(HEAPU8.subarray(src, src+num), dest)`,
#else
emscripten_memcpy_big: (dest, src, num) => HEAPU8.copyWithin(dest, src, src + num),
emscripten_memcpy_big: (dest, src, num) => HEAPU8.copyWithin(fixPointer(dest), fixPointer(src), fixPointer(src) + fixPointer(num)),
#endif

#endif
Expand Down Expand Up @@ -3090,7 +3090,7 @@ mergeInto(LibraryManager.library, {
// Used by wasm-emscripten-finalize to implement STACK_OVERFLOW_CHECK
__handle_stack_overflow__deps: ['emscripten_stack_get_base', 'emscripten_stack_get_end', '$ptrToString'],
__handle_stack_overflow: (requested) => {
requested = requested >>> 0;
requested = fixPointer(requested);
var base = _emscripten_stack_get_base();
var end = _emscripten_stack_get_end();
abort(`stack overflow (Attempt to set SP to ${ptrToString(requested)}` +
Expand Down
6 changes: 3 additions & 3 deletions src/library_fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1133,7 +1133,7 @@ FS.staticInit();` +
},
read: (stream, buffer, offset, length, position) => {
#if CAN_ADDRESS_2GB
offset >>>= 0;
offset = fixPointer(offset);
#endif
if (length < 0 || position < 0) {
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
Expand Down Expand Up @@ -1167,7 +1167,7 @@ FS.staticInit();` +
},
write: (stream, buffer, offset, length, position, canOwn) => {
#if CAN_ADDRESS_2GB
offset >>>= 0;
offset = fixPointer(offset);
#endif
if (length < 0 || position < 0) {
throw new FS.ErrnoError({{{ cDefs.EINVAL }}});
Expand Down Expand Up @@ -1243,7 +1243,7 @@ FS.staticInit();` +
},
msync: (stream, buffer, offset, length, mmapFlags) => {
#if CAN_ADDRESS_2GB
offset >>>= 0;
offset = fixPointer(offset);
#endif
if (!stream.stream_ops.msync) {
return 0;
Expand Down
6 changes: 3 additions & 3 deletions src/library_memfs.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ mergeInto(LibraryManager.library, {
// Never shrinks the storage.
expandFileStorage: function(node, newCapacity) {
#if CAN_ADDRESS_2GB
newCapacity >>>= 0;
newCapacity = fixPointer(newCapacity);
#endif
var prevCapacity = node.contents ? node.contents.length : 0;
if (prevCapacity >= newCapacity) return; // No need to expand, the storage was already large enough.
Expand All @@ -124,7 +124,7 @@ mergeInto(LibraryManager.library, {
// Performs an exact resize of the backing file storage to the given size, if the size is not exactly this, the storage is fully reallocated.
resizeFileStorage: function(node, newSize) {
#if CAN_ADDRESS_2GB
newSize >>>= 0;
newSize = fixPointer(newSize);
#endif
if (node.usedBytes == newSize) return;
if (newSize == 0) {
Expand Down Expand Up @@ -361,7 +361,7 @@ mergeInto(LibraryManager.library, {
throw new FS.ErrnoError({{{ cDefs.ENOMEM }}});
}
#if CAN_ADDRESS_2GB
ptr >>>= 0;
ptr = fixPointer(ptr);
#endif
HEAP8.set(contents, ptr);
}
Expand Down
13 changes: 8 additions & 5 deletions src/library_strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ mergeInto(LibraryManager.library, {
#endif
$UTF8ArrayToString: (heapOrArray, idx, maxBytesToRead) => {
#if CAN_ADDRESS_2GB
idx >>>= 0;
idx = fixPointer(idx);
#endif
var endIdx = idx + maxBytesToRead;
#if TEXTDECODER
Expand Down Expand Up @@ -119,7 +119,7 @@ mergeInto(LibraryManager.library, {
assert(typeof ptr == 'number');
#endif
#if CAN_ADDRESS_2GB
ptr >>>= 0;
ptr = fixPointer(ptr);
#endif
#if TEXTDECODER == 2
if (!ptr) return '';
Expand Down Expand Up @@ -155,7 +155,7 @@ mergeInto(LibraryManager.library, {
*/
$stringToUTF8Array: (str, heap, outIdx, maxBytesToWrite) => {
#if CAN_ADDRESS_2GB
outIdx >>>= 0;
outIdx = fixPointer(outIdx);
#endif
#if ASSERTIONS
assert(typeof str === 'string');
Expand Down Expand Up @@ -263,7 +263,7 @@ mergeInto(LibraryManager.library, {
// object.
$AsciiToString: (ptr) => {
#if CAN_ADDRESS_2GB
ptr >>>= 0;
ptr = fixPointer(ptr);
#endif
var str = '';
while (1) {
Expand All @@ -277,6 +277,9 @@ mergeInto(LibraryManager.library, {
// address 'outPtr', null-terminated and encoded in ASCII form. The copy will
// require at most str.length+1 bytes of space in the HEAP.
$stringToAscii: (str, buffer) => {
#if CAN_ADDRESS_2GB
buffer = fixPointer(buffer);
#endif
for (var i = 0; i < str.length; ++i) {
#if ASSERTIONS
assert(str.charCodeAt(i) === (str.charCodeAt(i) & 0xff));
Expand Down Expand Up @@ -430,7 +433,7 @@ mergeInto(LibraryManager.library, {
// Returns the number of bytes written, EXCLUDING the null terminator.
$stringToUTF32: (str, outPtr, maxBytesToWrite) => {
#if CAN_ADDRESS_2GB
outPtr >>>= 0;
outPtr = fixPointer(outPtr);
#endif
#if ASSERTIONS
assert(outPtr % 4 == 0, 'Pointer passed to stringToUTF32 must be aligned to four bytes!');
Expand Down
4 changes: 2 additions & 2 deletions src/library_syscall.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ var SyscallsLibrary = {
return 0;
}
#if CAN_ADDRESS_2GB
addr >>>= 0;
addr = fixPointer(addr);
#endif
var buffer = HEAPU8.slice(addr, addr + len);
FS.msync(stream, buffer, offset, len, flags);
Expand Down Expand Up @@ -143,7 +143,7 @@ var SyscallsLibrary = {
var ptr = res.ptr;
{{{ makeSetValue('allocated', 0, 'res.allocated', 'i32') }}};
#if CAN_ADDRESS_2GB
ptr >>>= 0;
ptr = fixPointer(ptr);
#endif
{{{ makeSetValue('addr', 0, 'ptr', '*') }}};
return 0;
Expand Down
6 changes: 3 additions & 3 deletions src/library_webgpu.js
Original file line number Diff line number Diff line change
Expand Up @@ -1841,7 +1841,7 @@ var LibraryWebGPU = {

if (size === 0) warnOnce('getMappedRange size=0 no longer means WGPU_WHOLE_MAP_SIZE');

size = size >>> 0;
size = fixPointer(size);
sbc100 marked this conversation as resolved.
Show resolved Hide resolved
if (size === {{{ gpu.WHOLE_MAP_SIZE }}}) size = undefined;

var mapped;
Expand Down Expand Up @@ -1875,7 +1875,7 @@ var LibraryWebGPU = {

if (size === 0) warnOnce('getMappedRange size=0 no longer means WGPU_WHOLE_MAP_SIZE');

size = size >>> 0;
size = fixPointer(size);
if (size === {{{ gpu.WHOLE_MAP_SIZE }}}) size = undefined;

if (bufferWrapper.mapMode !== {{{ gpu.MapMode.Write }}}) {
Expand Down Expand Up @@ -1916,7 +1916,7 @@ var LibraryWebGPU = {
bufferWrapper.onUnmap = [];
var buffer = bufferWrapper.object;

size = size >>> 0;
size = fixPointer(size);
if (size === {{{ gpu.WHOLE_MAP_SIZE }}}) size = undefined;

// `callback` takes (WGPUBufferMapAsyncStatus status, void * userdata)
Expand Down
142 changes: 139 additions & 3 deletions src/preamble.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,55 @@ var HEAP,
var HEAP_DATA_VIEW;
#endif

function fixPointer (ptr) {
if (typeof ptr === 'number' && ptr < 0) {
return ptr >>> 0;
}
if (typeof ptr === 'bigint') {
sbc100 marked this conversation as resolved.
Show resolved Hide resolved
return Number(ptr);
}
return ptr;
}

const getHeapBlock = (type, offset, length) => {
sbc100 marked this conversation as resolved.
Show resolved Hide resolved
const maxSize = Math.min(wasmMemory.buffer.byteLength, 4 * 1024 * 1024 * 1024 - 8)

const heap = wasmMemory.buffer
switch (type) {
case 'i1':
case 'i8':
return new Int8Array(heap, offset, length || maxSize / Int8Array.BYTES_PER_ELEMENT);
case 'u1':
case 'u8':
return new Uint8Array(heap, offset, length || maxSize / Uint8Array.BYTES_PER_ELEMENT);
case 'i16':
return new Int16Array(heap, offset, length || maxSize / Int16Array.BYTES_PER_ELEMENT);
case 'u16':
return new Uint16Array(heap, offset, length || maxSize / Uint16Array.BYTES_PER_ELEMENT);
case 'i32':
return new Int32Array(heap, offset, length || maxSize / Int32Array.BYTES_PER_ELEMENT);
case 'u32':
return new Uint32Array(heap, offset, length || maxSize / Uint32Array.BYTES_PER_ELEMENT);
case 'f32':
return new Float32Array(heap, offset, length || maxSize / Float32Array.BYTES_PER_ELEMENT);
case 'f64':
return new Float64Array(heap, offset, length || maxSize / Float64Array.BYTES_PER_ELEMENT);
case 'i64':
return new BigInt64Array(heap, offset, length || maxSize / BigInt64Array.BYTES_PER_ELEMENT);
case '*':
case 'u64':
return new BigUint64Array(heap, offset, length || maxSize / BigUint64Array.BYTES_PER_ELEMENT);
default:
throw new Error('Invalid type');
}
}

function updateMemoryViews() {
var b = wasmMemory.buffer;
#if SUPPORT_BIG_ENDIAN
Module['HEAP_DATA_VIEW'] = HEAP_DATA_VIEW = new DataView(b);
#endif
#if !WASM_BIGINT
sbc100 marked this conversation as resolved.
Show resolved Hide resolved
Module['HEAP8'] = HEAP8 = new Int8Array(b);
Module['HEAP16'] = HEAP16 = new Int16Array(b);
Module['HEAP32'] = HEAP32 = new Int32Array(b);
Expand All @@ -147,9 +191,101 @@ function updateMemoryViews() {
Module['HEAPU32'] = HEAPU32 = new Uint32Array(b);
Module['HEAPF32'] = HEAPF32 = new Float32Array(b);
Module['HEAPF64'] = HEAPF64 = new Float64Array(b);
#if WASM_BIGINT
Module['HEAP64'] = HEAP64 = new BigInt64Array(b);
Module['HEAPU64'] = HEAPU64 = new BigUint64Array(b);
#else
var maxArraySize = Math.min(b.byteLength, 4 * 1024 * 1024 * 1024 - 8);
var proxyHandler = (type, hb) => ({
heapBlock: hb,
copyWithin: function (target, start, end) {
target = fixPointer(target)
start = fixPointer(start)
end = fixPointer(end)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm hoping you can simply remove fixPointer since all incoming pointers should already i53 (and unsigned). See #19740.

const bpe = hb.BYTES_PER_ELEMENT
if (target * bpe >= maxArraySize || start * bpe >= maxArraySize || (end && end * bpe >= maxArraySize)) {
var len = end - start
var targetArray = getHeapBlock(type, target * bpe, len)
var sourceArray = getHeapBlock(type, start * bpe, len)
targetArray.set(sourceArray)
return hb
} else {
return hb.copyWithin(target, start, end)
}
},
setOverridden: function (array, offset) {
offset = fixPointer(offset)
var offsetReal = (offset || 0) * hb.BYTES_PER_ELEMENT
if (offsetReal >= maxArraySize || array.byteLength + offsetReal >= maxArraySize) {
var targetArray = getHeapBlock(type, offsetReal, array.length)
targetArray.set(array)
} else {
hb.set(array, offset)
}
},
subarray: function (start, end) {
start = fixPointer(start)
end = fixPointer(end)
var startReal = (start || 0) * hb.BYTES_PER_ELEMENT
var endReal = end ? end * hb.BYTES_PER_ELEMENT : wasmMemory.byteLength
if (startReal >= maxArraySize || endReal >= maxArraySize) {
return getHeapBlock(type, startReal, endReal - startReal)
} else {
return hb.subarray(start, end)
}
},
get: function (target, property) {
if (typeof property === 'number' || typeof property === 'bigint') {
var memoryOffset = fixPointer(property) * target.BYTES_PER_ELEMENT
if (memoryOffset >= maxArraySize) {
var heap = getHeapBlock(type, memoryOffset, 1);
return heap[0];
} else {
return this.heapBlock[property]
}
}

if (property === 'copyWithin') {
return this.copyWithin
}

if (property === 'set') {
return this.setOverridden
}

if (property === 'subarray') {
return this.subarray
}

return this.heapBlock[property]
},
set: function (target, property, value) {
if (typeof property === 'number' || typeof property === 'bigint') {
var memoryOffset = fixPointer(property) * target.BYTES_PER_ELEMENT
if (memoryOffset >= maxArraySize) {
var heap = getHeapBlock(type, memoryOffset, 1);
heap[0] = value;
return true;
}
}

this.heapBlock[property] = value
return true;
},
})
function createMemoryProxy (type)
{
const block = getHeapBlock(type, 0)
return new Proxy(block, proxyHandler(type, block));
}
Module["HEAP8"] = HEAP8 = createMemoryProxy('i8')
Module["HEAP16"] = HEAP16 = createMemoryProxy('i16')
Module["HEAP32"] = HEAP32 = createMemoryProxy('i32')
Module["HEAPU8"] = HEAPU8 = createMemoryProxy('u8')
Module["HEAPU16"] = HEAPU16 = createMemoryProxy('u16')
Module["HEAPU32"] = HEAPU32 = createMemoryProxy('u32')
Module["HEAPF32"] = HEAPF32 = createMemoryProxy('f32')
Module["HEAPF64"] = HEAPF64 = createMemoryProxy('f64')
Module["HEAP64"] = HEAP64 = createMemoryProxy('i64')
Module["HEAPU64"] = HEAPU64 = createMemoryProxy('u64')
Module["MEMORY"] = b;
#endif
}

Expand Down
8 changes: 4 additions & 4 deletions src/runtime_safe_heap.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ var SAFE_HEAP_COUNTER = 0;
/** @param {number|boolean=} isFloat */
function SAFE_HEAP_STORE(dest, value, bytes, isFloat) {
#if CAN_ADDRESS_2GB
dest >>>= 0;
dest = fixPointer(ptr);
#endif
#if SAFE_HEAP_LOG
out('SAFE_HEAP store: ' + [dest, value, bytes, isFloat, SAFE_HEAP_COUNTER++]);
Expand All @@ -42,7 +42,7 @@ function SAFE_HEAP_STORE(dest, value, bytes, isFloat) {
#else
if (runtimeInitialized) {
#endif
var brk = _sbrk() >>> 0;
var brk = fixPointer(_sbrk());
if (dest + bytes > brk) abort(`segmentation fault, exceeded the top of the available dynamic heap when storing ${bytes} bytes to address ${dest}. DYNAMICTOP=${brk}`);
assert(brk >= _emscripten_stack_get_base(), `brk >= _emscripten_stack_get_base() (brk=${brk}, _emscripten_stack_get_base()=${_emscripten_stack_get_base()})`); // sbrk-managed memory must be above the stack
assert(brk <= wasmMemory.buffer.byteLength, `brk <= wasmMemory.buffer.byteLength (brk=${brk}, wasmMemory.buffer.byteLength=${wasmMemory.buffer.byteLength})`);
Expand All @@ -57,7 +57,7 @@ function SAFE_HEAP_STORE_D(dest, value, bytes) {
/** @param {number|boolean=} isFloat */
function SAFE_HEAP_LOAD(dest, bytes, unsigned, isFloat) {
#if CAN_ADDRESS_2GB
dest >>>= 0;
dest = fixPointer(ptr);
#endif
if (dest <= 0) abort(`segmentation fault loading ${bytes} bytes from address ${dest}`);
sbc100 marked this conversation as resolved.
Show resolved Hide resolved
#if SAFE_HEAP == 1
Expand All @@ -70,7 +70,7 @@ function SAFE_HEAP_LOAD(dest, bytes, unsigned, isFloat) {
#else
if (runtimeInitialized) {
#endif
var brk = _sbrk() >>> 0;
var brk = fixPointer(_sbrk());
if (dest + bytes > brk) abort(`segmentation fault, exceeded the top of the available dynamic heap when loading ${bytes} bytes from address ${dest}. DYNAMICTOP=${brk}`);
assert(brk >= _emscripten_stack_get_base(), `brk >= _emscripten_stack_get_base() (brk=${brk}, _emscripten_stack_get_base()=${_emscripten_stack_get_base()})`); // sbrk-managed memory must be above the stack
assert(brk <= wasmMemory.buffer.byteLength, `brk <= wasmMemory.buffer.byteLength (brk=${brk}, wasmMemory.buffer.byteLength=${wasmMemory.buffer.byteLength})`);
Expand Down