Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
76 changes: 76 additions & 0 deletions src/lib/liblittle_endian_heap.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,82 @@ var LibraryLittleEndianHeap = {

$LE_HEAP_LOAD_F64: (byteOffset) =>
HEAP_DATA_VIEW.getFloat64(byteOffset, true),

$LE_ATOMICS_NATIVE_BYTE_ORDER__postset: `
LE_ATOMICS_NATIVE_BYTE_ORDER = (new Int8Array(new Int16Array([1]).buffer)[0] === 1)
? [ /* little endian */
(x => x),
(x => x),
undefined,
(x => x),
]
: [ /* big endian */
(x => x),
(x => (((x & 0xff00) << 8) | ((x & 0xff) << 24)) >> 16),
undefined,
(x => ((x >> 24) & 0xff) | ((x >> 8) & 0xff00) | ((x & 0xff00) << 8) | ((x & 0xff) << 24)),
];
function LE_HEAP_UPDATE() {
HEAPU16.unsigned = (x => x & 0xffff);
HEAPU32.unsigned = (x => x >>> 0);
}
`,
$LE_ATOMICS_NATIVE_BYTE_ORDER: [],

$LE_ATOMICS_ADD: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.add(heap, offset, order(value)));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_AND: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.and(heap, offset, order(value)));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_COMPAREEXCHANGE: (heap, offset, expected, replacement) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.compareExchange(heap, offset, order(expected), order(replacement)));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_EXCHANGE: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.exchange(heap, offset, order(value)));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_ISLOCKFREE: (size) => Atomics.isLockFree(size),
$LE_ATOMICS_LOAD: (heap, offset) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.load(heap, offset));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_NOTIFY: (heap, offset, count) => Atomics.notify(heap, offset, count),
$LE_ATOMICS_OR: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.or(heap, offset, order(value)));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_STORE: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
Atomics.store(heap, offset, order(value));
},
$LE_ATOMICS_SUB: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.sub(heap, offset, order(value)));
return heap.unsigned ? heap.unsigned(res) : res;
},
$LE_ATOMICS_WAIT: (heap, offset, value, timeout) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
return Atomics.wait(heap, offset, order(value), timeout);
},
$LE_ATOMICS_WAITASYNC: (heap, offset, value, timeout) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
return Atomics.waitAsync(heap, offset, order(value), timeout);
},
$LE_ATOMICS_XOR: (heap, offset, value) => {
const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1];
const res = order(Atomics.xor(heap, offset, order(value)));
return heap.unsigned ? heap.unsigned(res) : res;
},
}

addToLibrary(LibraryLittleEndianHeap);
7 changes: 4 additions & 3 deletions src/runtime_shared.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,6 @@ var wasmOffsetConverter;

function updateMemoryViews() {
var b = wasmMemory.buffer;
#if SUPPORT_BIG_ENDIAN
{{{ maybeExportHeap('HEAP_DATA_VIEW') }}} HEAP_DATA_VIEW = new DataView(b);
#endif
{{{ maybeExportHeap('HEAP8') }}}HEAP8 = new Int8Array(b);
{{{ maybeExportHeap('HEAP16') }}}HEAP16 = new Int16Array(b);
{{{ maybeExportHeap('HEAPU8') }}}HEAPU8 = new Uint8Array(b);
Expand All @@ -81,6 +78,10 @@ function updateMemoryViews() {
{{{ maybeExportHeap('HEAP64') }}}HEAP64 = new BigInt64Array(b);
{{{ maybeExportHeap('HEAPU64') }}}HEAPU64 = new BigUint64Array(b);
#endif
#if SUPPORT_BIG_ENDIAN
{{{ maybeExportHeap('HEAP_DATA_VIEW') }}} HEAP_DATA_VIEW = new DataView(b);
LE_HEAP_UPDATE();
#endif
}

#if ENVIRONMENT_MAY_BE_NODE && MIN_NODE_VERSION < 160000
Expand Down
20 changes: 20 additions & 0 deletions test/js_optimizer/LittleEndianHeap-output.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,23 @@ LE_HEAP_STORE_F64(x * 8, a);
HEAP[x];

HeAp[x];

LE_ATOMICS_ADD(heap, offset, value);

LE_ATOMICS_AND(heap, offset, value);

LE_ATOMICS_COMPAREEXCHANGE(heap, offset, expected, replacement);

LE_ATOMICS_EXCHANGE(heap, offset, value);

LE_ATOMICS_LOAD(heap, offset);

LE_ATOMICS_OR(heap, offset, value);

LE_ATOMICS_SUB(heap, offset, value);

LE_ATOMICS_WAIT(heap, offset, value, timeout);

LE_ATOMICS_WAITASYNC(heap, offset, value, timeout);

LE_ATOMICS_XOR(heap, offset, value);
10 changes: 10 additions & 0 deletions test/js_optimizer/LittleEndianHeap.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,13 @@ a = HEAPF64[x]; // HEAPF64
HEAPF64[x] = a;
HEAP[x]; // should not be changed
HeAp[x];
Atomics.add(heap, offset, value);
Atomics.and(heap, offset, value);
Atomics.compareExchange(heap, offset, expected, replacement);
Atomics.exchange(heap, offset, value);
Atomics.load(heap, offset);
Atomics.or(heap, offset, value);
Atomics.sub(heap, offset, value);
Atomics.wait(heap, offset, value, timeout);
Atomics.waitAsync(heap, offset, value, timeout);
Atomics.xor(heap, offset, value);
41 changes: 39 additions & 2 deletions tools/acorn-optimizer.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -1106,10 +1106,21 @@ function littleEndianHeap(ast) {
recursiveWalk(ast, {
FunctionDeclaration: (node, c) => {
// do not recurse into LE_HEAP_STORE, LE_HEAP_LOAD functions
if (!(node.id.type === 'Identifier' && node.id.name.startsWith('LE_HEAP'))) {
if (
!(
node.id.type === 'Identifier' &&
(node.id.name.startsWith('LE_HEAP') || node.id.name.startsWith('LE_ATOMICS_'))
)
) {
c(node.body);
}
},
VariableDeclarator: (node, c) => {
if (!(node.id.type === 'Identifier' && node.id.name.startsWith('LE_ATOMICS_'))) {
c(node.id);
if (node.init) c(node.init);
}
},
AssignmentExpression: (node, c) => {
const target = node.left;
const value = node.right;
Expand Down Expand Up @@ -1160,6 +1171,27 @@ function littleEndianHeap(ast) {
}
}
},
CallExpression: (node, c) => {
if (node.arguments) {
for (var a of node.arguments) c(a);
}
if (
// Atomics.X(args) -> LE_ATOMICS_X(args)
node.callee.type === 'MemberExpression' &&
node.callee.object.type === 'Identifier' &&
node.callee.object.name === 'Atomics' &&
node.callee.property.type === 'Identifier' &&
!node.computed
) {
makeCallExpression(
node,
'LE_ATOMICS_' + node.callee.property.name.toUpperCase(),
node.arguments,
);
} else {
c(node.callee);
}
},
MemberExpression: (node, c) => {
c(node.property);
if (!isHEAPAccess(node)) {
Expand Down Expand Up @@ -1217,7 +1249,12 @@ function growableHeap(ast) {
recursiveWalk(ast, {
FunctionDeclaration(node, c) {
// Do not recurse into to GROWABLE_HEAP_ helper functions themselves.
if (!(node.id.type === 'Identifier' && node.id.name.startsWith('GROWABLE_HEAP_'))) {
if (
!(
node.id.type === 'Identifier' &&
(node.id.name.startsWith('GROWABLE_HEAP_') || node.id.name === 'LE_HEAP_UPDATE')
)
) {
c(node.body);
}
},
Expand Down
16 changes: 15 additions & 1 deletion tools/link.py
Original file line number Diff line number Diff line change
Expand Up @@ -1122,7 +1122,21 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915
'$LE_HEAP_LOAD_U32',
'$LE_HEAP_LOAD_I32',
'$LE_HEAP_LOAD_F32',
'$LE_HEAP_LOAD_F64'
'$LE_HEAP_LOAD_F64',
'$LE_ATOMICS_NATIVE_BYTE_ORDER',
'$LE_ATOMICS_ADD',
'$LE_ATOMICS_AND',
'$LE_ATOMICS_COMPAREEXCHANGE',
'$LE_ATOMICS_EXCHANGE',
'$LE_ATOMICS_ISLOCKFREE',
'$LE_ATOMICS_LOAD',
'$LE_ATOMICS_NOTIFY',
'$LE_ATOMICS_OR',
'$LE_ATOMICS_STORE',
'$LE_ATOMICS_SUB',
'$LE_ATOMICS_WAIT',
'$LE_ATOMICS_WAITASYNC',
'$LE_ATOMICS_XOR',

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

While I agree it would be good to add all of these, I also thing we might want to only add the ones that we actually use (and also test).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought that the whole point of this approach was to actually implement comprehensive support like was done to the non-atomic memory accesses done from JS code.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Yes, I suppose you right, seems reasonable to leave them all in then.

But lets skip 64-bit atomics and just fail in that case, to keep things simple. I don't think anyone should be doing that.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've tested it on the following program:

#include <atomic>
#include <print>
#include <emscripten.h>

int main(){
        std::atomic<unsigned long long> x = 0x0123456789ABCDEF;

        std::println("x: {:X}", x.load());

        EM_ASM({ Atomics.compareExchange(HEAP64, ($0)/8, BigInt("0x0123456789ABCDEF"), BigInt("0xFEDCBA9876543210")); }, &x);

        std::println("x: {:X}", x.load());

}

built with

em++ -o main.js -s USE_PTHREADS=1 -s PROXY_TO_PTHREAD=1 -s PTHREAD_POOL_SIZE=8 -s SUPPORT_BIG_ENDIAN=1 -pthread -s EXIT_RUNTIME=1 -s ALLOW_MEMORY_GROWTH=1 -std=c++23 main.cpp

And I do get the expected result on both LE and BE:

x: 123456789ABCDEF
x: FEDCBA9876543210

]

if settings.RUNTIME_DEBUG or settings.ASSERTIONS or settings.STACK_OVERFLOW_CHECK or settings.PTHREADS_PROFILING or settings.GL_ASSERTIONS:
Expand Down