From 1f0aad34cebecc3420ef66bd294c487e970f177f Mon Sep 17 00:00:00 2001 From: slavek-kucera Date: Fri, 22 Aug 2025 21:07:14 +0200 Subject: [PATCH 01/34] missing functions in link.py --- tools/link.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/link.py b/tools/link.py index 1d9dbcf38bcba..739ca8320f729 100644 --- a/tools/link.py +++ b/tools/link.py @@ -1221,12 +1221,16 @@ def limit_incoming_module_api(): '$LE_HEAP_STORE_I16', '$LE_HEAP_STORE_U32', '$LE_HEAP_STORE_I32', + '$LE_HEAP_STORE_U64', + '$LE_HEAP_STORE_I64', '$LE_HEAP_STORE_F32', '$LE_HEAP_STORE_F64', '$LE_HEAP_LOAD_U16', '$LE_HEAP_LOAD_I16', '$LE_HEAP_LOAD_U32', '$LE_HEAP_LOAD_I32', + '$LE_HEAP_LOAD_U64', + '$LE_HEAP_LOAD_I64', '$LE_HEAP_LOAD_F32', '$LE_HEAP_LOAD_F64', '$LE_ATOMICS_NATIVE_BYTE_ORDER', From 821325039e48200056a575b81fd7fef593b7b042 Mon Sep 17 00:00:00 2001 From: slavek-kucera Date: Fri, 22 Aug 2025 21:18:01 +0200 Subject: [PATCH 02/34] refactor LE heap conversion --- tools/acorn-optimizer.mjs | 112 +++++++------------------------------- 1 file changed, 20 insertions(+), 92 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 22271d61befb9..8c839fa9829c3 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -956,6 +956,17 @@ function isEmscriptenHEAP(name) { } } +const littleEndianHelper = { + 'HEAP16': { width: 2, load: "LE_HEAP_LOAD_16", store: "LE_HEAP_STORE_16" }, + 'HEAPU16': { width: 2, load: "LE_HEAP_LOAD_U16", store: "LE_HEAP_STORE_U16" }, + 'HEAP32': { width: 4, load: "LE_HEAP_LOAD_32", store: "LE_HEAP_STORE_32" }, + 'HEAPU32': { width: 4, load: "LE_HEAP_LOAD_U32", store: "LE_HEAP_STORE_U32" }, + 'HEAP64': { width: 8, load: "LE_HEAP_LOAD_64", store: "LE_HEAP_STORE_64" }, + 'HEAPU64': { width: 8, load: "LE_HEAP_LOAD_U64", store: "LE_HEAP_STORE_U64" }, + 'HEAPF32': { width: 4, load: "LE_HEAP_LOAD_F32", store: "LE_HEAP_STORE_F32" }, + 'HEAPF64': { width: 8, load: "LE_HEAP_LOAD_F64", store: "LE_HEAP_STORE_F64" }, +}; + // Replaces each HEAP access with function call that uses DataView to enforce // LE byte order for HEAP buffer function littleEndianHeap(ast) { @@ -988,52 +999,10 @@ function littleEndianHeap(ast) { // replace the heap access with LE_HEAP_STORE const name = target.object.name; const idx = target.property; - switch (name) { - case 'HEAP8': - case 'HEAPU8': { - // no action required - storing only 1 byte - break; - } - case 'HEAP16': { - // change "name[idx] = value" to "LE_HEAP_STORE_I16(idx*2, value)" - makeCallExpression(node, 'LE_HEAP_STORE_I16', [multiply(idx, 2), value]); - break; - } - case 'HEAPU16': { - // change "name[idx] = value" to "LE_HEAP_STORE_U16(idx*2, value)" - makeCallExpression(node, 'LE_HEAP_STORE_U16', [multiply(idx, 2), value]); - break; - } - case 'HEAP32': { - // change "name[idx] = value" to "LE_HEAP_STORE_I32(idx*4, value)" - makeCallExpression(node, 'LE_HEAP_STORE_I32', [multiply(idx, 4), value]); - break; - } - case 'HEAPU32': { - // change "name[idx] = value" to "LE_HEAP_STORE_U32(idx*4, value)" - makeCallExpression(node, 'LE_HEAP_STORE_U32', [multiply(idx, 4), value]); - break; - } - case 'HEAP64': { - // change "name[idx] = value" to "LE_HEAP_STORE_I64(idx*8, value)" - makeCallExpression(node, 'LE_HEAP_STORE_I64', [multiply(idx, 8), value]); - break; - } - case 'HEAPU64': { - // change "name[idx] = value" to "LE_HEAP_STORE_U64(idx*8, value)" - makeCallExpression(node, 'LE_HEAP_STORE_U64', [multiply(idx, 8), value]); - break; - } - case 'HEAPF32': { - // change "name[idx] = value" to "LE_HEAP_STORE_F32(idx*4, value)" - makeCallExpression(node, 'LE_HEAP_STORE_F32', [multiply(idx, 4), value]); - break; - } - case 'HEAPF64': { - // change "name[idx] = value" to "LE_HEAP_STORE_F64(idx*8, value)" - makeCallExpression(node, 'LE_HEAP_STORE_F64', [multiply(idx, 8), value]); - break; - } + const helper = littleEndianHelper[name]; + if (helper) { + // "nameXX[idx] = value" -> "LE_HEAP_STORE_XX(idx*XX, value)" + makeCallExpression(node, helper.store, [multiply(idx, helper.width), value]); } } }, @@ -1064,53 +1033,12 @@ function littleEndianHeap(ast) { c(node.object); } else { // replace the heap access with LE_HEAP_LOAD + const name = node.object.name; const idx = node.property; - switch (node.object.name) { - case 'HEAP8': - case 'HEAPU8': { - // no action required - loading only 1 byte - break; - } - case 'HEAP16': { - // change "name[idx]" to "LE_HEAP_LOAD_I16(idx*2)" - makeCallExpression(node, 'LE_HEAP_LOAD_I16', [multiply(idx, 2)]); - break; - } - case 'HEAPU16': { - // change "name[idx]" to "LE_HEAP_LOAD_U16(idx*2)" - makeCallExpression(node, 'LE_HEAP_LOAD_U16', [multiply(idx, 2)]); - break; - } - case 'HEAP32': { - // change "name[idx]" to "LE_HEAP_LOAD_I32(idx*4)" - makeCallExpression(node, 'LE_HEAP_LOAD_I32', [multiply(idx, 4)]); - break; - } - case 'HEAPU32': { - // change "name[idx]" to "LE_HEAP_LOAD_U32(idx*4)" - makeCallExpression(node, 'LE_HEAP_LOAD_U32', [multiply(idx, 4)]); - break; - } - case 'HEAP64': { - // change "name[idx]" to "LE_HEAP_LOAD_I64(idx*8)" - makeCallExpression(node, 'LE_HEAP_LOAD_I64', [multiply(idx, 8)]); - break; - } - case 'HEAPU64': { - // change "name[idx]" to "LE_HEAP_LOAD_U64(idx*8)" - makeCallExpression(node, 'LE_HEAP_LOAD_U64', [multiply(idx, 8)]); - break; - } - case 'HEAPF32': { - // change "name[idx]" to "LE_HEAP_LOAD_F32(idx*4)" - makeCallExpression(node, 'LE_HEAP_LOAD_F32', [multiply(idx, 4)]); - break; - } - case 'HEAPF64': { - // change "name[idx]" to "LE_HEAP_LOAD_F64(idx*8)" - makeCallExpression(node, 'LE_HEAP_LOAD_F64', [multiply(idx, 8)]); - break; - } + const helper = littleEndianHelper[name]; + if (helper) { + // "nameXX[idx]" -> "LE_HEAP_LOAD_XX(idx*XX)" + makeCallExpression(node, helper.load, [multiply(idx, helper.width)]); } } }, From 5b9e064c005d0bb3aa3857d3e9ba5be15d5d8e4d Mon Sep 17 00:00:00 2001 From: slavek-kucera Date: Fri, 22 Aug 2025 21:26:02 +0200 Subject: [PATCH 03/34] return name of the heap from isHEAPAccess --- tools/acorn-optimizer.mjs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 8c839fa9829c3..6f527eea13870 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -992,18 +992,18 @@ function littleEndianHeap(ast) { const target = node.left; const value = node.right; c(value); - if (!isHEAPAccess(target)) { - // not accessing the HEAP - c(target); - } else { + const heap = isHEAPAccess(target); + if (heap) { // replace the heap access with LE_HEAP_STORE - const name = target.object.name; const idx = target.property; - const helper = littleEndianHelper[name]; + const helper = littleEndianHelper[heap]; if (helper) { // "nameXX[idx] = value" -> "LE_HEAP_STORE_XX(idx*XX, value)" makeCallExpression(node, helper.store, [multiply(idx, helper.width), value]); } + } else { + // not accessing the HEAP + c(target); } }, CallExpression(node, c) { @@ -1028,18 +1028,18 @@ function littleEndianHeap(ast) { }, MemberExpression(node, c) { c(node.property); - if (!isHEAPAccess(node)) { - // not accessing the HEAP - c(node.object); - } else { + const heap = isHEAPAccess(node); + if (heap) { // replace the heap access with LE_HEAP_LOAD - const name = node.object.name; const idx = node.property; - const helper = littleEndianHelper[name]; + const helper = littleEndianHelper[heap]; if (helper) { // "nameXX[idx]" -> "LE_HEAP_LOAD_XX(idx*XX)" makeCallExpression(node, helper.load, [multiply(idx, helper.width)]); } + } else { + // not accessing the HEAP + c(node.object); } }, }); @@ -1177,7 +1177,8 @@ function isHEAPAccess(node) { node.type === 'MemberExpression' && node.object.type === 'Identifier' && node.computed && // notice a[X] but not a.X - isEmscriptenHEAP(node.object.name) + isEmscriptenHEAP(node.object.name) && + node.object.name ); } From 5b2da5bc581e2fa18c1920eea90d14a845d3e8e9 Mon Sep 17 00:00:00 2001 From: slavek-kucera Date: Fri, 22 Aug 2025 21:52:06 +0200 Subject: [PATCH 04/34] extract make sequence code --- tools/acorn-optimizer.mjs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 6f527eea13870..202a15e66050b 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -1084,9 +1084,7 @@ function growableHeap(ast) { // Transform `HEAPxx` into `(growMemViews(), HEAPxx)`. // Important: don't just do `growMemViews(HEAPxx)` because `growMemViews` reassigns `HEAPxx` // and we want to get an updated value after that reassignment. - Object.assign(node, { - type: 'SequenceExpression', - expressions: [ + makeSequence(node, { type: 'CallExpression', callee: { @@ -1096,13 +1094,19 @@ function growableHeap(ast) { arguments: [], }, {...node}, - ], - }); + ); } }, }); } +function makeSequence(node, ...expressions) { + Object.assign(node, { + type: 'SequenceExpression', + expressions: expressions, + }); +} + // Make all JS pointers unsigned. We do this by modifying things like // HEAP32[X >> 2] to HEAP32[X >>> 2]. We also need to handle the case of // HEAP32[X] and make that HEAP32[X >>> 0], things like subarray(), etc. From 4bee8f8b9e3c59753a0880774262a2ba4e3f02da Mon Sep 17 00:00:00 2001 From: slavek-kucera Date: Fri, 22 Aug 2025 21:54:20 +0200 Subject: [PATCH 05/34] extract call growmemviews --- tools/acorn-optimizer.mjs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 202a15e66050b..2e7eb261e2b55 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -1085,14 +1085,7 @@ function growableHeap(ast) { // Important: don't just do `growMemViews(HEAPxx)` because `growMemViews` reassigns `HEAPxx` // and we want to get an updated value after that reassignment. makeSequence(node, - { - type: 'CallExpression', - callee: { - type: 'Identifier', - name: 'growMemViews', - }, - arguments: [], - }, + makeCallGrowMemViews(), {...node}, ); } @@ -1100,6 +1093,17 @@ function growableHeap(ast) { }); } +function makeCallGrowMemViews() { + return { + type: 'CallExpression', + callee: { + type: 'Identifier', + name: 'growMemViews', + }, + arguments: [], + }; +} + function makeSequence(node, ...expressions) { Object.assign(node, { type: 'SequenceExpression', From 249d3eb0073bfbb6f6b90c2a822f231ef9d8c443 Mon Sep 17 00:00:00 2001 From: slavek-kucera Date: Fri, 22 Aug 2025 22:00:53 +0200 Subject: [PATCH 06/34] heap access replacement --- tools/acorn-optimizer.mjs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 2e7eb261e2b55..b488e1cd4b24a 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -993,6 +993,7 @@ function littleEndianHeap(ast) { const value = node.right; c(value); const heap = isHEAPAccess(target); + const growHeap = isGrowHEAPAccess(target); if (heap) { // replace the heap access with LE_HEAP_STORE const idx = target.property; @@ -1001,6 +1002,16 @@ function littleEndianHeap(ast) { // "nameXX[idx] = value" -> "LE_HEAP_STORE_XX(idx*XX, value)" makeCallExpression(node, helper.store, [multiply(idx, helper.width), value]); } + } else if (growHeap) { + const idx = node.property; + const helper = littleEndianHelper[heap]; + if (helper) { + // "(growMemViews(),nameXX)[idx] = value" -> "LE_HEAP_STORE_XX((growMemViews(),idx*XX), value)" + makeCallExpression(node, helper.store, [ + makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)), + value + ]); + } } else { // not accessing the HEAP c(target); @@ -1029,6 +1040,7 @@ function littleEndianHeap(ast) { MemberExpression(node, c) { c(node.property); const heap = isHEAPAccess(node); + const growHeap = isGrowHEAPAccess(node); if (heap) { // replace the heap access with LE_HEAP_LOAD const idx = node.property; @@ -1037,6 +1049,15 @@ function littleEndianHeap(ast) { // "nameXX[idx]" -> "LE_HEAP_LOAD_XX(idx*XX)" makeCallExpression(node, helper.load, [multiply(idx, helper.width)]); } + } else if (growHeap) { + const idx = node.property; + const helper = littleEndianHelper[heap]; + if (helper) { + // "(growMemViews(),nameXX)[idx]" -> "LE_HEAP_LOAD_XX((growMemViews(),idx*XX))" + makeCallExpression(node, helper.load, [ + makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)) + ]); + } } else { // not accessing the HEAP c(node.object); @@ -1190,6 +1211,21 @@ function isHEAPAccess(node) { ); } +function isGrowHEAPAccess(node) { + return ( + node.type === 'MemberExpression' && + node.computed && // notice a[X] but not a.X + node.object.type === 'SequenceExpression' && + node.object.expressions.length === 2 && + node.object.expressions[0].type === 'CallExpression' && + node.object.expressions[0].callee.type === 'Identifier' && + node.object.expressions[0].callee.name === 'growMemViews' && + node.object.expressions[1].type === 'Identifier' && + isEmscriptenHEAP(node.object.expressions[1].name) && + node.object.expressions[1].name + ); +} + // Replace direct HEAP* loads/stores with calls into C, in which ASan checks // are applied. That lets ASan cover JS too. function asanify(ast) { From 22c3947f640e9a1d28bb01f9775b9fa8408004d5 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 25 Aug 2025 09:47:02 +0200 Subject: [PATCH 07/34] fixes --- tools/acorn-optimizer.mjs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index b488e1cd4b24a..536aa597d7d2d 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -957,11 +957,11 @@ function isEmscriptenHEAP(name) { } const littleEndianHelper = { - 'HEAP16': { width: 2, load: "LE_HEAP_LOAD_16", store: "LE_HEAP_STORE_16" }, + 'HEAP16': { width: 2, load: "LE_HEAP_LOAD_I16", store: "LE_HEAP_STORE_I16" }, 'HEAPU16': { width: 2, load: "LE_HEAP_LOAD_U16", store: "LE_HEAP_STORE_U16" }, - 'HEAP32': { width: 4, load: "LE_HEAP_LOAD_32", store: "LE_HEAP_STORE_32" }, + 'HEAP32': { width: 4, load: "LE_HEAP_LOAD_I32", store: "LE_HEAP_STORE_I32" }, 'HEAPU32': { width: 4, load: "LE_HEAP_LOAD_U32", store: "LE_HEAP_STORE_U32" }, - 'HEAP64': { width: 8, load: "LE_HEAP_LOAD_64", store: "LE_HEAP_STORE_64" }, + 'HEAP64': { width: 8, load: "LE_HEAP_LOAD_I64", store: "LE_HEAP_STORE_I64" }, 'HEAPU64': { width: 8, load: "LE_HEAP_LOAD_U64", store: "LE_HEAP_STORE_U64" }, 'HEAPF32': { width: 4, load: "LE_HEAP_LOAD_F32", store: "LE_HEAP_STORE_F32" }, 'HEAPF64': { width: 8, load: "LE_HEAP_LOAD_F64", store: "LE_HEAP_STORE_F64" }, @@ -1003,12 +1003,12 @@ function littleEndianHeap(ast) { makeCallExpression(node, helper.store, [multiply(idx, helper.width), value]); } } else if (growHeap) { - const idx = node.property; - const helper = littleEndianHelper[heap]; + const idx = target.property; + const helper = littleEndianHelper[growHeap]; if (helper) { // "(growMemViews(),nameXX)[idx] = value" -> "LE_HEAP_STORE_XX((growMemViews(),idx*XX), value)" makeCallExpression(node, helper.store, [ - makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)), + makeSequence({}, makeCallGrowMemViews(), multiply(idx, helper.width)), value ]); } @@ -1051,11 +1051,11 @@ function littleEndianHeap(ast) { } } else if (growHeap) { const idx = node.property; - const helper = littleEndianHelper[heap]; + const helper = littleEndianHelper[growHeap]; if (helper) { // "(growMemViews(),nameXX)[idx]" -> "LE_HEAP_LOAD_XX((growMemViews(),idx*XX))" makeCallExpression(node, helper.load, [ - makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)) + makeSequence({}, makeCallGrowMemViews(), multiply(idx, helper.width)) ]); } } else { @@ -1126,7 +1126,7 @@ function makeCallGrowMemViews() { } function makeSequence(node, ...expressions) { - Object.assign(node, { + return Object.assign(node, { type: 'SequenceExpression', expressions: expressions, }); From 15ab549715fac2c027b98fbb97c4cb166ec802a3 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 25 Aug 2025 10:48:37 +0200 Subject: [PATCH 08/34] minor cleanup --- tools/acorn-optimizer.mjs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 536aa597d7d2d..7cc8cceae473d 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -1008,7 +1008,7 @@ function littleEndianHeap(ast) { if (helper) { // "(growMemViews(),nameXX)[idx] = value" -> "LE_HEAP_STORE_XX((growMemViews(),idx*XX), value)" makeCallExpression(node, helper.store, [ - makeSequence({}, makeCallGrowMemViews(), multiply(idx, helper.width)), + makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)), value ]); } @@ -1055,7 +1055,7 @@ function littleEndianHeap(ast) { if (helper) { // "(growMemViews(),nameXX)[idx]" -> "LE_HEAP_LOAD_XX((growMemViews(),idx*XX))" makeCallExpression(node, helper.load, [ - makeSequence({}, makeCallGrowMemViews(), multiply(idx, helper.width)) + makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)) ]); } } else { @@ -1105,10 +1105,10 @@ function growableHeap(ast) { // Transform `HEAPxx` into `(growMemViews(), HEAPxx)`. // Important: don't just do `growMemViews(HEAPxx)` because `growMemViews` reassigns `HEAPxx` // and we want to get an updated value after that reassignment. - makeSequence(node, + Object.assign(node, makeSequence( makeCallGrowMemViews(), {...node}, - ); + )); } }, }); @@ -1125,11 +1125,11 @@ function makeCallGrowMemViews() { }; } -function makeSequence(node, ...expressions) { - return Object.assign(node, { +function makeSequence(...expressions) { + return { type: 'SequenceExpression', - expressions: expressions, - }); + expressions, + }; } // Make all JS pointers unsigned. We do this by modifying things like From cea89abab79e5e93314bc7aa417aa789bff50d9b Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 25 Aug 2025 12:02:19 +0200 Subject: [PATCH 09/34] add tests --- .../LittleEndianGrowableHeap-output.js | 55 +++++++++++++++++++ test/js_optimizer/LittleEndianGrowableHeap.js | 28 ++++++++++ test/test_other.py | 1 + 3 files changed, 84 insertions(+) create mode 100644 test/js_optimizer/LittleEndianGrowableHeap-output.js create mode 100644 test/js_optimizer/LittleEndianGrowableHeap.js diff --git a/test/js_optimizer/LittleEndianGrowableHeap-output.js b/test/js_optimizer/LittleEndianGrowableHeap-output.js new file mode 100644 index 0000000000000..358688c30bf93 --- /dev/null +++ b/test/js_optimizer/LittleEndianGrowableHeap-output.js @@ -0,0 +1,55 @@ +a = (growMemViews(), HEAP8)[x]; + +(growMemViews(), HEAP8)[x] = a; + +a = (growMemViews(), HEAPU8)[x]; + +(growMemViews(), HEAPU8)[x] = a; + +a = LE_HEAP_LOAD_I16((growMemViews(), x * 2)); + +LE_HEAP_STORE_I16((growMemViews(), x * 2), a); + +a = LE_HEAP_LOAD_U16((growMemViews(), x * 2)); + +LE_HEAP_STORE_U16((growMemViews(), x * 2), a); + +a = LE_HEAP_LOAD_I32((growMemViews(), x * 4)); + +LE_HEAP_STORE_I32((growMemViews(), x * 4), a); + +a = LE_HEAP_LOAD_U32((growMemViews(), x * 4)); + +LE_HEAP_STORE_U32((growMemViews(), x * 4), a); + +a = LE_HEAP_LOAD_F32((growMemViews(), x * 4)); + +LE_HEAP_STORE_F32((growMemViews(), x * 4), a); + +a = LE_HEAP_LOAD_F64((growMemViews(), x * 8)); + +LE_HEAP_STORE_F64((growMemViews(), 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); diff --git a/test/js_optimizer/LittleEndianGrowableHeap.js b/test/js_optimizer/LittleEndianGrowableHeap.js new file mode 100644 index 0000000000000..94d038b570f04 --- /dev/null +++ b/test/js_optimizer/LittleEndianGrowableHeap.js @@ -0,0 +1,28 @@ +a = HEAP8[x]; // HEAP8 +HEAP8[x] = a; +a = HEAPU8[x]; // HEAPU8 +HEAPU8[x] = a; +a = HEAP16[x]; // HEAP16 +HEAP16[x] = a; +a = HEAPU16[x]; // HEAPU16 +HEAPU16[x] = a; +a = HEAP32[x]; // HEAPI32 +HEAP32[x] = a; +a = HEAPU32[x]; // HEAPU32 +HEAPU32[x] = a; +a = HEAPF32[x]; // HEAPF32 +HEAPF32[x] = a; +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); diff --git a/test/test_other.py b/test/test_other.py index 0b74cdea86854..0de8402747745 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -2961,6 +2961,7 @@ def test_extern_prepost(self): 'safeHeap': (['safeHeap'],), 'object-literals': ([],), 'LittleEndianHeap': (['littleEndianHeap'],), + 'LittleEndianGrowableHeap': (['growableHeap','littleEndianHeap'],), }) @crossplatform def test_js_optimizer(self, passes, filename=None): From 6e2fe0ea73abdc52bf49526f449d6e9df5b0b271 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 25 Aug 2025 12:07:54 +0200 Subject: [PATCH 10/34] prettier --- tools/acorn-optimizer.mjs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 7cc8cceae473d..38b2f74c4f657 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -957,14 +957,14 @@ function isEmscriptenHEAP(name) { } const littleEndianHelper = { - 'HEAP16': { width: 2, load: "LE_HEAP_LOAD_I16", store: "LE_HEAP_STORE_I16" }, - 'HEAPU16': { width: 2, load: "LE_HEAP_LOAD_U16", store: "LE_HEAP_STORE_U16" }, - 'HEAP32': { width: 4, load: "LE_HEAP_LOAD_I32", store: "LE_HEAP_STORE_I32" }, - 'HEAPU32': { width: 4, load: "LE_HEAP_LOAD_U32", store: "LE_HEAP_STORE_U32" }, - 'HEAP64': { width: 8, load: "LE_HEAP_LOAD_I64", store: "LE_HEAP_STORE_I64" }, - 'HEAPU64': { width: 8, load: "LE_HEAP_LOAD_U64", store: "LE_HEAP_STORE_U64" }, - 'HEAPF32': { width: 4, load: "LE_HEAP_LOAD_F32", store: "LE_HEAP_STORE_F32" }, - 'HEAPF64': { width: 8, load: "LE_HEAP_LOAD_F64", store: "LE_HEAP_STORE_F64" }, + HEAP16: {width: 2, load: 'LE_HEAP_LOAD_I16', store: 'LE_HEAP_STORE_I16'}, + HEAPU16: {width: 2, load: 'LE_HEAP_LOAD_U16', store: 'LE_HEAP_STORE_U16'}, + HEAP32: {width: 4, load: 'LE_HEAP_LOAD_I32', store: 'LE_HEAP_STORE_I32'}, + HEAPU32: {width: 4, load: 'LE_HEAP_LOAD_U32', store: 'LE_HEAP_STORE_U32'}, + HEAP64: {width: 8, load: 'LE_HEAP_LOAD_I64', store: 'LE_HEAP_STORE_I64'}, + HEAPU64: {width: 8, load: 'LE_HEAP_LOAD_U64', store: 'LE_HEAP_STORE_U64'}, + HEAPF32: {width: 4, load: 'LE_HEAP_LOAD_F32', store: 'LE_HEAP_STORE_F32'}, + HEAPF64: {width: 8, load: 'LE_HEAP_LOAD_F64', store: 'LE_HEAP_STORE_F64'}, }; // Replaces each HEAP access with function call that uses DataView to enforce @@ -1009,7 +1009,7 @@ function littleEndianHeap(ast) { // "(growMemViews(),nameXX)[idx] = value" -> "LE_HEAP_STORE_XX((growMemViews(),idx*XX), value)" makeCallExpression(node, helper.store, [ makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)), - value + value, ]); } } else { @@ -1055,7 +1055,7 @@ function littleEndianHeap(ast) { if (helper) { // "(growMemViews(),nameXX)[idx]" -> "LE_HEAP_LOAD_XX((growMemViews(),idx*XX))" makeCallExpression(node, helper.load, [ - makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)) + makeSequence(makeCallGrowMemViews(), multiply(idx, helper.width)), ]); } } else { @@ -1105,10 +1105,7 @@ function growableHeap(ast) { // Transform `HEAPxx` into `(growMemViews(), HEAPxx)`. // Important: don't just do `growMemViews(HEAPxx)` because `growMemViews` reassigns `HEAPxx` // and we want to get an updated value after that reassignment. - Object.assign(node, makeSequence( - makeCallGrowMemViews(), - {...node}, - )); + Object.assign(node, makeSequence(makeCallGrowMemViews(), {...node})); } }, }); From 4154c8e4566e929390917145aef15aa566042401 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Wed, 27 Aug 2025 11:26:29 +0200 Subject: [PATCH 11/34] allow combined options to work --- src/runtime_asan.js | 11 +-- src/runtime_safe_heap.js | 8 -- .../LittleEndianGrowableSafeHeap-output.js | 55 +++++++++++ .../LittleEndianGrowableSafeHeap.js | 28 ++++++ test/js_optimizer/asanify-output.js | 54 +++++------ test/js_optimizer/asanify.js | 12 --- test/js_optimizer/safeHeap-output.js | 54 +++++------ test/js_optimizer/safeHeap.js | 12 --- test/test_other.py | 1 + tools/acorn-optimizer.mjs | 92 +++++++++---------- 10 files changed, 180 insertions(+), 147 deletions(-) create mode 100644 test/js_optimizer/LittleEndianGrowableSafeHeap-output.js create mode 100644 test/js_optimizer/LittleEndianGrowableSafeHeap.js diff --git a/src/runtime_asan.js b/src/runtime_asan.js index 34cafeffcf320..952db227e8d3a 100644 --- a/src/runtime_asan.js +++ b/src/runtime_asan.js @@ -21,14 +21,5 @@ function _asan_js_check_index(arr, index, asanFn) { const elemSize = arr.BYTES_PER_ELEMENT; asanFn(index * elemSize, elemSize); } -} - -function _asan_js_load(arr, index) { - _asan_js_check_index(arr, index, ___asan_loadN); - return arr[index]; -} - -function _asan_js_store(arr, index, value) { - _asan_js_check_index(arr, index, ___asan_storeN); - return arr[index] = value; + return index; } diff --git a/src/runtime_safe_heap.js b/src/runtime_safe_heap.js index 6f73e666d1188..53e2469ed17e3 100644 --- a/src/runtime_safe_heap.js +++ b/src/runtime_safe_heap.js @@ -35,14 +35,6 @@ function SAFE_HEAP_INDEX(arr, idx, action) { return idx; } -function SAFE_HEAP_LOAD(arr, idx) { - return arr[SAFE_HEAP_INDEX(arr, idx, 'loading')]; -} - -function SAFE_HEAP_STORE(arr, idx, value) { - return arr[SAFE_HEAP_INDEX(arr, idx, 'storing')] = value; -} - function segfault() { abort('segmentation fault'); } diff --git a/test/js_optimizer/LittleEndianGrowableSafeHeap-output.js b/test/js_optimizer/LittleEndianGrowableSafeHeap-output.js new file mode 100644 index 0000000000000..cdf51d93f9d5f --- /dev/null +++ b/test/js_optimizer/LittleEndianGrowableSafeHeap-output.js @@ -0,0 +1,55 @@ +a = (growMemViews(), HEAP8)[SAFE_HEAP_INDEX((growMemViews(), HEAP8), x, "loading")]; + +(growMemViews(), HEAP8)[SAFE_HEAP_INDEX((growMemViews(), HEAP8), x, "storing")] = a; + +a = (growMemViews(), HEAPU8)[SAFE_HEAP_INDEX((growMemViews(), HEAPU8), x, "loading")]; + +(growMemViews(), HEAPU8)[SAFE_HEAP_INDEX((growMemViews(), HEAPU8), x, "storing")] = a; + +a = LE_HEAP_LOAD_I16((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAP16), x, "loading") * 2)); + +LE_HEAP_STORE_I16((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAP16), x, "storing") * 2), a); + +a = LE_HEAP_LOAD_U16((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPU16), x, "loading") * 2)); + +LE_HEAP_STORE_U16((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPU16), x, "storing") * 2), a); + +a = LE_HEAP_LOAD_I32((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAP32), x, "loading") * 4)); + +LE_HEAP_STORE_I32((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAP32), x, "storing") * 4), a); + +a = LE_HEAP_LOAD_U32((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPU32), x, "loading") * 4)); + +LE_HEAP_STORE_U32((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPU32), x, "storing") * 4), a); + +a = LE_HEAP_LOAD_F32((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPF32), x, "loading") * 4)); + +LE_HEAP_STORE_F32((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPF32), x, "storing") * 4), a); + +a = LE_HEAP_LOAD_F64((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPF64), x, "loading") * 8)); + +LE_HEAP_STORE_F64((growMemViews(), SAFE_HEAP_INDEX((growMemViews(), HEAPF64), x, "storing") * 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); diff --git a/test/js_optimizer/LittleEndianGrowableSafeHeap.js b/test/js_optimizer/LittleEndianGrowableSafeHeap.js new file mode 100644 index 0000000000000..94d038b570f04 --- /dev/null +++ b/test/js_optimizer/LittleEndianGrowableSafeHeap.js @@ -0,0 +1,28 @@ +a = HEAP8[x]; // HEAP8 +HEAP8[x] = a; +a = HEAPU8[x]; // HEAPU8 +HEAPU8[x] = a; +a = HEAP16[x]; // HEAP16 +HEAP16[x] = a; +a = HEAPU16[x]; // HEAPU16 +HEAPU16[x] = a; +a = HEAP32[x]; // HEAPI32 +HEAP32[x] = a; +a = HEAPU32[x]; // HEAPU32 +HEAPU32[x] = a; +a = HEAPF32[x]; // HEAPF32 +HEAPF32[x] = a; +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); diff --git a/test/js_optimizer/asanify-output.js b/test/js_optimizer/asanify-output.js index 2c196180a3c3b..efc1c520218bf 100644 --- a/test/js_optimizer/asanify-output.js +++ b/test/js_optimizer/asanify-output.js @@ -1,57 +1,53 @@ -_asan_js_store(HEAP8, x, 1); +HEAP8[_asan_js_check_index(HEAP8, x, ___asan_storeN)] = 1; -_asan_js_store(HEAP16, x, 2); +HEAP16[_asan_js_check_index(HEAP16, x, ___asan_storeN)] = 2; -_asan_js_store(HEAP32, x, 3); +HEAP32[_asan_js_check_index(HEAP32, x, ___asan_storeN)] = 3; -_asan_js_store(HEAPU8, x, 4); +HEAPU8[_asan_js_check_index(HEAPU8, x, ___asan_storeN)] = 4; -_asan_js_store(HEAPU16, x, 5); +HEAPU16[_asan_js_check_index(HEAPU16, x, ___asan_storeN)] = 5; -_asan_js_store(HEAPU32, x, 6); +HEAPU32[_asan_js_check_index(HEAPU32, x, ___asan_storeN)] = 6; -_asan_js_store(HEAPF32, x, 7); +HEAPF32[_asan_js_check_index(HEAPF32, x, ___asan_storeN)] = 7; -_asan_js_store(HEAPF64, x, 8); +HEAPF64[_asan_js_check_index(HEAPF64, x, ___asan_storeN)] = 8; -_asan_js_store(HEAP64, x, 9n); +HEAP64[_asan_js_check_index(HEAP64, x, ___asan_storeN)] = 9n; -_asan_js_store(HEAPU64, x, 10n); +HEAPU64[_asan_js_check_index(HEAPU64, x, ___asan_storeN)] = 10n; -a1 = _asan_js_load(HEAP8, x); +a1 = HEAP8[_asan_js_check_index(HEAP8, x, ___asan_loadN)]; -a2 = _asan_js_load(HEAP16, x); +a2 = HEAP16[_asan_js_check_index(HEAP16, x, ___asan_loadN)]; -a3 = _asan_js_load(HEAP32, x); +a3 = HEAP32[_asan_js_check_index(HEAP32, x, ___asan_loadN)]; -a4 = _asan_js_load(HEAPU8, x); +a4 = HEAPU8[_asan_js_check_index(HEAPU8, x, ___asan_loadN)]; -a5 = _asan_js_load(HEAPU16, x); +a5 = HEAPU16[_asan_js_check_index(HEAPU16, x, ___asan_loadN)]; -a6 = _asan_js_load(HEAPU32, x); +a6 = HEAPU32[_asan_js_check_index(HEAPU32, x, ___asan_loadN)]; -a7 = _asan_js_load(HEAPF32, x); +a7 = HEAPF32[_asan_js_check_index(HEAPF32, x, ___asan_loadN)]; -a8 = _asan_js_load(HEAPF64, x); +a8 = HEAPF64[_asan_js_check_index(HEAPF64, x, ___asan_loadN)]; -a9 = _asan_js_load(HEAP64, x); +a9 = HEAP64[_asan_js_check_index(HEAP64, x, ___asan_loadN)]; -a10 = _asan_js_load(HEAPU64, x); +a10 = HEAPU64[_asan_js_check_index(HEAPU64, x, ___asan_loadN)]; -foo = _asan_js_store(HEAPU8, 1337, 42); +foo = HEAPU8[_asan_js_check_index(HEAPU8, 1337, ___asan_storeN)] = 42; -_asan_js_load(HEAP16, bar(_asan_js_load(HEAPF64, 5))); +HEAP16[_asan_js_check_index(HEAP16, bar(HEAPF64[_asan_js_check_index(HEAPF64, 5, ___asan_loadN)]), ___asan_loadN)]; -_asan_js_store(HEAPF32, x, _asan_js_load(HEAP32, y)); - -function _asan_js_load(ptr) { - return HEAP8[ptr]; -} +HEAPF32[_asan_js_check_index(HEAPF32, x, ___asan_storeN)] = HEAP32[_asan_js_check_index(HEAP32, y, ___asan_loadN)]; function somethingElse() { - return _asan_js_load(HEAP8, ptr); + return HEAP8[_asan_js_check_index(HEAP8, ptr, ___asan_loadN)]; } HEAP8.length; -_asan_js_load(HEAP8, length); +HEAP8[_asan_js_check_index(HEAP8, length, ___asan_loadN)]; diff --git a/test/js_optimizer/asanify.js b/test/js_optimizer/asanify.js index 30e4b3951ad69..fe86ca12153e9 100644 --- a/test/js_optimizer/asanify.js +++ b/test/js_optimizer/asanify.js @@ -29,18 +29,6 @@ foo = HEAPU8[1337] = 42; HEAP16[bar(HEAPF64[5])]; HEAPF32[x] = HEAP32[y]; -// Ignore the special asan functions themselves. that is, any JS memory access -// will turn into a function call to _asan_js_load_1 etc., which then does -// the memory access for it. It either calls into wasm to get the proper -// asan-instrumented operation, or before the wasm is ready to be called into, -// we must do the access in JS, unsafely. We should not instrument a heap -// access in these functions, as then we'd get infinite recursion - this is -// where we do actually need to still do a HEAP8[..] etc. operation without -// any ASan instrumentation. -function _asan_js_load(ptr) { - return HEAP8[ptr]; -} - // but do handle everything else function somethingElse() { return HEAP8[ptr]; diff --git a/test/js_optimizer/safeHeap-output.js b/test/js_optimizer/safeHeap-output.js index 07745ce63e0e8..b1469f38fed78 100644 --- a/test/js_optimizer/safeHeap-output.js +++ b/test/js_optimizer/safeHeap-output.js @@ -1,57 +1,53 @@ -SAFE_HEAP_STORE(HEAP8, x, 1); +HEAP8[SAFE_HEAP_INDEX(HEAP8, x, "storing")] = 1; -SAFE_HEAP_STORE(HEAP16, x, 2); +HEAP16[SAFE_HEAP_INDEX(HEAP16, x, "storing")] = 2; -SAFE_HEAP_STORE(HEAP32, x, 3); +HEAP32[SAFE_HEAP_INDEX(HEAP32, x, "storing")] = 3; -SAFE_HEAP_STORE(HEAPU8, x, 4); +HEAPU8[SAFE_HEAP_INDEX(HEAPU8, x, "storing")] = 4; -SAFE_HEAP_STORE(HEAPU16, x, 5); +HEAPU16[SAFE_HEAP_INDEX(HEAPU16, x, "storing")] = 5; -SAFE_HEAP_STORE(HEAPU32, x, 6); +HEAPU32[SAFE_HEAP_INDEX(HEAPU32, x, "storing")] = 6; -SAFE_HEAP_STORE(HEAPF32, x, 7); +HEAPF32[SAFE_HEAP_INDEX(HEAPF32, x, "storing")] = 7; -SAFE_HEAP_STORE(HEAPF64, x, 8); +HEAPF64[SAFE_HEAP_INDEX(HEAPF64, x, "storing")] = 8; -SAFE_HEAP_STORE(HEAP64, x, 9n); +HEAP64[SAFE_HEAP_INDEX(HEAP64, x, "storing")] = 9n; -SAFE_HEAP_STORE(HEAPU64, x, 10n); +HEAPU64[SAFE_HEAP_INDEX(HEAPU64, x, "storing")] = 10n; -a1 = SAFE_HEAP_LOAD(HEAP8, x); +a1 = HEAP8[SAFE_HEAP_INDEX(HEAP8, x, "loading")]; -a2 = SAFE_HEAP_LOAD(HEAP16, x); +a2 = HEAP16[SAFE_HEAP_INDEX(HEAP16, x, "loading")]; -a3 = SAFE_HEAP_LOAD(HEAP32, x); +a3 = HEAP32[SAFE_HEAP_INDEX(HEAP32, x, "loading")]; -a4 = SAFE_HEAP_LOAD(HEAPU8, x); +a4 = HEAPU8[SAFE_HEAP_INDEX(HEAPU8, x, "loading")]; -a5 = SAFE_HEAP_LOAD(HEAPU16, x); +a5 = HEAPU16[SAFE_HEAP_INDEX(HEAPU16, x, "loading")]; -a6 = SAFE_HEAP_LOAD(HEAPU32, x); +a6 = HEAPU32[SAFE_HEAP_INDEX(HEAPU32, x, "loading")]; -a7 = SAFE_HEAP_LOAD(HEAPF32, x); +a7 = HEAPF32[SAFE_HEAP_INDEX(HEAPF32, x, "loading")]; -a8 = SAFE_HEAP_LOAD(HEAPF64, x); +a8 = HEAPF64[SAFE_HEAP_INDEX(HEAPF64, x, "loading")]; -a9 = SAFE_HEAP_LOAD(HEAP64, x); +a9 = HEAP64[SAFE_HEAP_INDEX(HEAP64, x, "loading")]; -a10 = SAFE_HEAP_LOAD(HEAPU64, x); +a10 = HEAPU64[SAFE_HEAP_INDEX(HEAPU64, x, "loading")]; -foo = SAFE_HEAP_STORE(HEAPU8, 1337, 42); +foo = HEAPU8[SAFE_HEAP_INDEX(HEAPU8, 1337, "storing")] = 42; -SAFE_HEAP_LOAD(HEAP16, bar(SAFE_HEAP_LOAD(HEAPF64, 5))); +HEAP16[SAFE_HEAP_INDEX(HEAP16, bar(HEAPF64[SAFE_HEAP_INDEX(HEAPF64, 5, "loading")]), "loading")]; -SAFE_HEAP_STORE(HEAPF32, x, SAFE_HEAP_LOAD(HEAP32, y)); - -function SAFE_HEAP_FOO(ptr) { - return HEAP8[ptr]; -} +HEAPF32[SAFE_HEAP_INDEX(HEAPF32, x, "storing")] = HEAP32[SAFE_HEAP_INDEX(HEAP32, y, "loading")]; function somethingElse() { - return SAFE_HEAP_LOAD(HEAP8, ptr); + return HEAP8[SAFE_HEAP_INDEX(HEAP8, ptr, "loading")]; } HEAP8.length; -SAFE_HEAP_LOAD(HEAP8, length); +HEAP8[SAFE_HEAP_INDEX(HEAP8, length, "loading")]; diff --git a/test/js_optimizer/safeHeap.js b/test/js_optimizer/safeHeap.js index 848f928dddc49..fe86ca12153e9 100644 --- a/test/js_optimizer/safeHeap.js +++ b/test/js_optimizer/safeHeap.js @@ -29,18 +29,6 @@ foo = HEAPU8[1337] = 42; HEAP16[bar(HEAPF64[5])]; HEAPF32[x] = HEAP32[y]; -// Ignore the special functions themselves. that is, any JS memory access -// will turn into a function call to _asan_js_load_1 etc., which then does -// the memory access for it. It either calls into wasm to get the proper -// asan-instrumented operation, or before the wasm is ready to be called into, -// we must do the access in JS, unsafely. We should not instrument a heap -// access in these functions, as then we'd get infinite recursion - this is -// where we do actually need to still do a HEAP8[..] etc. operation without -// any ASan instrumentation. -function SAFE_HEAP_FOO(ptr) { - return HEAP8[ptr]; -} - // but do handle everything else function somethingElse() { return HEAP8[ptr]; diff --git a/test/test_other.py b/test/test_other.py index 0de8402747745..0347d48973786 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -2962,6 +2962,7 @@ def test_extern_prepost(self): 'object-literals': ([],), 'LittleEndianHeap': (['littleEndianHeap'],), 'LittleEndianGrowableHeap': (['growableHeap','littleEndianHeap'],), + 'LittleEndianGrowableSafeHeap': (['safeHeap','growableHeap','littleEndianHeap'],), }) @crossplatform def test_js_optimizer(self, passes, filename=None): diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 38b2f74c4f657..6c4bd6a881fd0 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -925,13 +925,17 @@ function createLiteral(value) { }; } +function makeIdentifier(name) { + return { + type: 'Identifier', + name: name, + }; +} + function makeCallExpression(node, name, args) { Object.assign(node, { type: 'CallExpression', - callee: { - type: 'Identifier', - name: name, - }, + callee: makeIdentifier(name), arguments: args, }); } @@ -1085,19 +1089,19 @@ function growableHeap(ast) { c(node.body); } }, - AssignmentExpression(node) { + AssignmentExpression(node, c) { if (node.left.type !== 'Identifier') { // Don't transform `HEAPxx =` assignments. - growableHeap(node.left); + c(node.left); } - growableHeap(node.right); + c(node.right); }, - VariableDeclarator(node) { + VariableDeclarator(node, c) { // Don't transform the var declarations for HEAP8 etc // but do transform anything that sets a var to // something from HEAP8 etc if (node.init) { - growableHeap(node.init); + c(node.init); } }, Identifier(node) { @@ -1124,8 +1128,11 @@ function makeCallGrowMemViews() { function makeSequence(...expressions) { return { - type: 'SequenceExpression', - expressions, + type: 'ParenthesizedExpression', + expression: { + type: 'SequenceExpression', + expressions, + } }; } @@ -1209,42 +1216,38 @@ function isHEAPAccess(node) { } function isGrowHEAPAccess(node) { + if ( + node.type !== 'MemberExpression' || + !node.computed || // notice a[X] but not a.X + node.object.type !== 'ParenthesizedExpression') + return false; + const obj = node.object.expression; return ( - node.type === 'MemberExpression' && - node.computed && // notice a[X] but not a.X - node.object.type === 'SequenceExpression' && - node.object.expressions.length === 2 && - node.object.expressions[0].type === 'CallExpression' && - node.object.expressions[0].callee.type === 'Identifier' && - node.object.expressions[0].callee.name === 'growMemViews' && - node.object.expressions[1].type === 'Identifier' && - isEmscriptenHEAP(node.object.expressions[1].name) && - node.object.expressions[1].name + obj.type === 'SequenceExpression' && + obj.expressions.length === 2 && + obj.expressions[0].type === 'CallExpression' && + obj.expressions[0].callee.type === 'Identifier' && + obj.expressions[0].callee.name === 'growMemViews' && + obj.expressions[1].type === 'Identifier' && + isEmscriptenHEAP(obj.expressions[1].name) && + obj.expressions[1].name ); } -// Replace direct HEAP* loads/stores with calls into C, in which ASan checks -// are applied. That lets ASan cover JS too. +function asanifyTransform(node, action) { + makeCallExpression(node.property, '_asan_js_check_index', [{ ...node.object }, { ...node.property }, makeIdentifier(action)]); +} +// Add ASan check to direct HEAP* loads/stores. +// That lets ASan cover JS too. function asanify(ast) { recursiveWalk(ast, { - FunctionDeclaration(node, c) { - if ( - node.id.type === 'Identifier' && - (node.id.name.startsWith('_asan_js_') || node.id.name === 'establishStackSpace') - ) { - // do not recurse into this js impl function, which we use during - // startup before the wasm is ready - } else { - c(node.body); - } - }, AssignmentExpression(node, c) { const target = node.left; const value = node.right; c(value); if (isHEAPAccess(target)) { // Instrument a store. - makeCallExpression(node, '_asan_js_store', [target.object, target.property, value]); + asanifyTransform(target, '___asan_storeN'); } else { c(target); } @@ -1255,7 +1258,7 @@ function asanify(ast) { c(node.object); } else { // Instrument a load. - makeCallExpression(node, '_asan_js_load', [node.object, node.property]); + asanifyTransform(node, '___asan_loadN'); } }, }); @@ -1270,24 +1273,19 @@ function multiply(value, by) { }; } -// Replace direct heap access with SAFE_HEAP* calls. +function safeHeapTransform(node, action) { + makeCallExpression(node.property, 'SAFE_HEAP_INDEX', [{ ...node.object }, { ...node.property }, createLiteral(action)]); +} +// Add SAFE_HEAP_INDEX check to heap access function safeHeap(ast) { recursiveWalk(ast, { - FunctionDeclaration(node, c) { - if (node.id.type === 'Identifier' && node.id.name.startsWith('SAFE_HEAP')) { - // do not recurse into this js impl function, which we use during - // startup before the wasm is ready - } else { - c(node.body); - } - }, AssignmentExpression(node, c) { const target = node.left; const value = node.right; c(value); if (isHEAPAccess(target)) { // Instrument a store. - makeCallExpression(node, 'SAFE_HEAP_STORE', [target.object, target.property, value]); + safeHeapTransform(target, 'storing'); } else { c(target); } @@ -1298,7 +1296,7 @@ function safeHeap(ast) { c(node.object); } else { // Instrument a load. - makeCallExpression(node, 'SAFE_HEAP_LOAD', [node.object, node.property]); + safeHeapTransform(node, 'loading'); } }, }); From 5d68fe73b95a656f23a52b38589219002afcf302 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Thu, 28 Aug 2025 12:14:08 +0200 Subject: [PATCH 12/34] skip establishStackSpace in asanifyTransform --- test/js_optimizer/asanify-output.js | 4 ++++ test/js_optimizer/asanify.js | 5 +++++ tools/acorn-optimizer.mjs | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/test/js_optimizer/asanify-output.js b/test/js_optimizer/asanify-output.js index efc1c520218bf..a8a16afd17882 100644 --- a/test/js_optimizer/asanify-output.js +++ b/test/js_optimizer/asanify-output.js @@ -44,6 +44,10 @@ HEAP16[_asan_js_check_index(HEAP16, bar(HEAPF64[_asan_js_check_index(HEAPF64, 5, HEAPF32[_asan_js_check_index(HEAPF32, x, ___asan_storeN)] = HEAP32[_asan_js_check_index(HEAP32, y, ___asan_loadN)]; +function establishStackSpace() { + HEAP32[0]; +} + function somethingElse() { return HEAP8[_asan_js_check_index(HEAP8, ptr, ___asan_loadN)]; } diff --git a/test/js_optimizer/asanify.js b/test/js_optimizer/asanify.js index fe86ca12153e9..6154b4a285179 100644 --- a/test/js_optimizer/asanify.js +++ b/test/js_optimizer/asanify.js @@ -29,6 +29,11 @@ foo = HEAPU8[1337] = 42; HEAP16[bar(HEAPF64[5])]; HEAPF32[x] = HEAP32[y]; +// skip establishStackSpace, because it sets up variables used by ASan itself +function establishStackSpace() { + HEAP32[0]; +} + // but do handle everything else function somethingElse() { return HEAP8[ptr]; diff --git a/tools/acorn-optimizer.mjs b/tools/acorn-optimizer.mjs index 6c4bd6a881fd0..fdc2306649a26 100755 --- a/tools/acorn-optimizer.mjs +++ b/tools/acorn-optimizer.mjs @@ -1241,6 +1241,13 @@ function asanifyTransform(node, action) { // That lets ASan cover JS too. function asanify(ast) { recursiveWalk(ast, { + FunctionDeclaration(node, c) { + if (node.id.type === 'Identifier' && node.id.name === 'establishStackSpace') { + // skip establishStackSpace, because it sets up variables used by ASan itself + } else { + c(node.body); + } + }, AssignmentExpression(node, c) { const target = node.left; const value = node.right; From 98aaf20d555ad79f8cb4a7ee42385fff9ac2dc73 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Thu, 28 Aug 2025 12:33:23 +0200 Subject: [PATCH 13/34] fix webidl support --- tools/webidl_binder.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/tools/webidl_binder.py b/tools/webidl_binder.py index 4e6fb9f8f487a..db46d2e7afef8 100644 --- a/tools/webidl_binder.py +++ b/tools/webidl_binder.py @@ -238,12 +238,6 @@ def build_constructor(name): } return ret; }, - copy(array, view, offset) { - offset /= view.BYTES_PER_ELEMENT; - for (var i = 0; i < array.length; i++) { - view[offset + i] = array[i]; - } - }, }; /** @suppress {duplicate} (TODO: avoid emitting this multiple times, it is redundant) */ @@ -251,7 +245,9 @@ def build_constructor(name): if (typeof value === 'string') { var intArray = intArrayFromString(value); var offset = ensureCache.alloc(intArray, HEAP8); - ensureCache.copy(intArray, HEAP8, offset); + for (var i = 0; i < intArray.length; i++) { + HEAP8[offset + i] = intArray[i]; + } return offset; } return value; @@ -261,7 +257,9 @@ def build_constructor(name): function ensureInt8(value) { if (typeof value === 'object') { var offset = ensureCache.alloc(value, HEAP8); - ensureCache.copy(value, HEAP8, offset); + for (var i = 0; i < value.length; i++) { + HEAP8[offset + i] = value[i]; + } return offset; } return value; @@ -271,7 +269,10 @@ def build_constructor(name): function ensureInt16(value) { if (typeof value === 'object') { var offset = ensureCache.alloc(value, HEAP16); - ensureCache.copy(value, HEAP16, offset); + var heapOffset = offset / 2; + for (var i = 0; i < value.length; i++) { + HEAP16[heapOffset + i] = value[i]; + } return offset; } return value; @@ -281,7 +282,10 @@ def build_constructor(name): function ensureInt32(value) { if (typeof value === 'object') { var offset = ensureCache.alloc(value, HEAP32); - ensureCache.copy(value, HEAP32, offset); + var heapOffset = offset / 4; + for (var i = 0; i < value.length; i++) { + HEAP32[heapOffset + i] = value[i]; + } return offset; } return value; @@ -291,7 +295,10 @@ def build_constructor(name): function ensureFloat32(value) { if (typeof value === 'object') { var offset = ensureCache.alloc(value, HEAPF32); - ensureCache.copy(value, HEAPF32, offset); + var heapOffset = offset / 4; + for (var i = 0; i < value.length; i++) { + HEAPF32[heapOffset + i] = value[i]; + } return offset; } return value; @@ -301,7 +308,10 @@ def build_constructor(name): function ensureFloat64(value) { if (typeof value === 'object') { var offset = ensureCache.alloc(value, HEAPF64); - ensureCache.copy(value, HEAPF64, offset); + var heapOffset = offset / 8; + for (var i = 0; i < value.length; i++) { + HEAPF64[heapOffset + i] = value[i]; + } return offset; } return value; From abb84380f2e1ef2145dfeda471d551325dedbd02 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Thu, 28 Aug 2025 14:41:56 +0200 Subject: [PATCH 14/34] incorrectly disabled SAFE_HEAP annotations --- src/lib/libhtml5.js | 8 ++++---- src/lib/libsdl.js | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib/libhtml5.js b/src/lib/libhtml5.js index 94ccce68b987f..5431ffbce3a6d 100644 --- a/src/lib/libhtml5.js +++ b/src/lib/libhtml5.js @@ -1001,7 +1001,7 @@ var LibraryHTML5 = { $fillFullscreenChangeEventData: (eventStruct) => { var fullscreenElement = getFullscreenElement(); var isFullscreen = !!fullscreenElement; -#if !SAFE_HEAP +#if SAFE_HEAP // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ #endif @@ -1544,7 +1544,7 @@ var LibraryHTML5 = { $fillPointerlockChangeEventData: (eventStruct) => { var pointerLockElement = document.pointerLockElement; var isPointerlocked = !!pointerLockElement; -#if !SAFE_HEAP +#if SAFE_HEAP // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ #endif @@ -1746,7 +1746,7 @@ var LibraryHTML5 = { var visibilityStates = [ "hidden", "visible", "prerender", "unloaded" ]; var visibilityState = visibilityStates.indexOf(document.visibilityState); -#if !SAFE_HEAP +#if SAFE_HEAP // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ #endif @@ -1943,7 +1943,7 @@ var LibraryHTML5 = { if (typeof e.buttons[i] == 'object') { {{{ makeSetValue('eventStruct+i', C_STRUCTS.EmscriptenGamepadEvent.digitalButton, 'e.buttons[i].pressed', 'i8') }}}; } else { -#if !SAFE_HEAP +#if SAFE_HEAP // Assigning a boolean to HEAP32, that's ok, but Closure would like to warn about it: /** @suppress {checkTypes} */ #endif diff --git a/src/lib/libsdl.js b/src/lib/libsdl.js index be2e06684dbdf..53659d61aa49e 100644 --- a/src/lib/libsdl.js +++ b/src/lib/libsdl.js @@ -857,7 +857,7 @@ var LibrarySDL = { var code = SDL.lookupKeyCodeForEvent(event); // Ignore key events that we don't (yet) map to SDL keys if (!code) return; -#if !SAFE_HEAP +#if SAFE_HEAP // Assigning a boolean to HEAP8, that's alright but Closure would like to warn about it. // TODO(https://github.com/emscripten-core/emscripten/issues/16311): // This is kind of ugly hack. Perhaps we can find a better way? From 9f7bb283de844cff6541c5794a9b10d5d2075839 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Thu, 28 Aug 2025 15:02:41 +0200 Subject: [PATCH 15/34] always suppress checkTypes --- src/lib/libhtml5.js | 8 -------- src/lib/libsdl.js | 2 -- 2 files changed, 10 deletions(-) diff --git a/src/lib/libhtml5.js b/src/lib/libhtml5.js index 5431ffbce3a6d..7b07ed3d9eab9 100644 --- a/src/lib/libhtml5.js +++ b/src/lib/libhtml5.js @@ -1001,10 +1001,8 @@ var LibraryHTML5 = { $fillFullscreenChangeEventData: (eventStruct) => { var fullscreenElement = getFullscreenElement(); var isFullscreen = !!fullscreenElement; -#if SAFE_HEAP // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ -#endif {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.isFullscreen, 'isFullscreen', 'i8') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenFullscreenChangeEvent.fullscreenEnabled, 'JSEvents.fullscreenEnabled()', 'i8') }}}; // If transitioning to fullscreen, report info about the element that is now fullscreen. @@ -1544,10 +1542,8 @@ var LibraryHTML5 = { $fillPointerlockChangeEventData: (eventStruct) => { var pointerLockElement = document.pointerLockElement; var isPointerlocked = !!pointerLockElement; -#if SAFE_HEAP // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ -#endif {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenPointerlockChangeEvent.isActive, 'isPointerlocked', 'i8') }}}; var nodeName = JSEvents.getNodeNameForTarget(pointerLockElement); var id = pointerLockElement?.id || ''; @@ -1746,10 +1742,8 @@ var LibraryHTML5 = { var visibilityStates = [ "hidden", "visible", "prerender", "unloaded" ]; var visibilityState = visibilityStates.indexOf(document.visibilityState); -#if SAFE_HEAP // Assigning a boolean to HEAP32 with expected type coercion. /** @suppress{checkTypes} */ -#endif {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenVisibilityChangeEvent.hidden, 'document.hidden', 'i8') }}}; {{{ makeSetValue('eventStruct', C_STRUCTS.EmscriptenVisibilityChangeEvent.visibilityState, 'visibilityState', 'i32') }}}; }, @@ -1943,10 +1937,8 @@ var LibraryHTML5 = { if (typeof e.buttons[i] == 'object') { {{{ makeSetValue('eventStruct+i', C_STRUCTS.EmscriptenGamepadEvent.digitalButton, 'e.buttons[i].pressed', 'i8') }}}; } else { -#if SAFE_HEAP // Assigning a boolean to HEAP32, that's ok, but Closure would like to warn about it: /** @suppress {checkTypes} */ -#endif {{{ makeSetValue('eventStruct+i', C_STRUCTS.EmscriptenGamepadEvent.digitalButton, 'e.buttons[i] == 1', 'i8') }}}; } } diff --git a/src/lib/libsdl.js b/src/lib/libsdl.js index 53659d61aa49e..9d3acc379f5fd 100644 --- a/src/lib/libsdl.js +++ b/src/lib/libsdl.js @@ -857,12 +857,10 @@ var LibrarySDL = { var code = SDL.lookupKeyCodeForEvent(event); // Ignore key events that we don't (yet) map to SDL keys if (!code) return; -#if SAFE_HEAP // Assigning a boolean to HEAP8, that's alright but Closure would like to warn about it. // TODO(https://github.com/emscripten-core/emscripten/issues/16311): // This is kind of ugly hack. Perhaps we can find a better way? /** @suppress{checkTypes} */ -#endif {{{ makeSetValue('SDL.keyboardState', 'code', 'down', 'i8') }}}; // TODO: lmeta, rmeta, numlock, capslock, KMOD_MODE, KMOD_RESERVED SDL.modState = From 7c753637287a89674d581a68924e72763c55d869 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Thu, 28 Aug 2025 16:44:13 +0200 Subject: [PATCH 16/34] disable wasm2js on big endian --- test/test_core.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_core.py b/test/test_core.py index f18b81aaddfe4..cb1f041ee2b04 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -8499,6 +8499,7 @@ def test_pthread_join_and_asyncify(self): # Test basic wasm2js functionality in all core compilation modes. @no_sanitize('no wasm2js support yet in sanitizers') @requires_wasm2js + @no_big_endian('wasm2js is currently not compatible with big endian') def test_wasm2js(self): if self.is_wasm2js(): self.skipTest('redundant to test wasm2js in wasm2js* mode') @@ -8509,6 +8510,7 @@ def test_wasm2js(self): @no_asan('no wasm2js support yet in asan') @requires_wasm2js @also_with_minimal_runtime + @no_big_endian('wasm2js is currently not compatible with big endian') def test_wasm2js_fallback(self): if self.is_wasm2js(): self.skipTest('redundant to test wasm2js in wasm2js* mode') From 1f0887dd5b1c86bebf5c96b95479d9e7cd880724 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Thu, 28 Aug 2025 18:03:50 +0200 Subject: [PATCH 17/34] closure does not know waitAsync --- src/lib/liblittle_endian_heap.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/liblittle_endian_heap.js b/src/lib/liblittle_endian_heap.js index 1f97eb6530010..bf87fb291bd0d 100644 --- a/src/lib/liblittle_endian_heap.js +++ b/src/lib/liblittle_endian_heap.js @@ -115,6 +115,7 @@ function LE_HEAP_UPDATE() { }, $LE_ATOMICS_WAITASYNC: (heap, offset, value, timeout) => { const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; + /** @suppress {checkTypes} */ return Atomics.waitAsync(heap, offset, order(value), timeout); }, $LE_ATOMICS_XOR: (heap, offset, value) => { From b7cef666cd36d2d967f4e37389ab15486aadb8db Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 10:18:50 +0200 Subject: [PATCH 18/34] closure false warning workaround --- src/lib/liblittle_endian_heap.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/liblittle_endian_heap.js b/src/lib/liblittle_endian_heap.js index bf87fb291bd0d..d0249ae972f91 100644 --- a/src/lib/liblittle_endian_heap.js +++ b/src/lib/liblittle_endian_heap.js @@ -115,8 +115,8 @@ function LE_HEAP_UPDATE() { }, $LE_ATOMICS_WAITASYNC: (heap, offset, value, timeout) => { const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; - /** @suppress {checkTypes} */ - return Atomics.waitAsync(heap, offset, order(value), timeout); + // this is suppressing incorrect closure JSC_INEXISTENT_PROPERTY warning that cannot be suppress via annotation + return Atomics.waitAsync && 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]; From b2917370f9ff0b51d7f0adac5fb9a8e985db09ad Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 10:20:59 +0200 Subject: [PATCH 19/34] BE flag not propagated --- test/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_core.py b/test/test_core.py index cb1f041ee2b04..23c8ea38f1d59 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -6910,6 +6910,7 @@ def test_bullet(self, use_cmake): @crossplatform @no_wasmfs('depends on MEMFS which WASMFS does not have') @no_strict('autoconfiguring is not compatible with STRICT') + @no_big_endian('SUPPORT_BIG_ENDIAN is not propagated') def test_poppler(self): # See https://github.com/emscripten-core/emscripten/issues/20757 self.cflags.extend(['-Wno-deprecated-declarations', '-Wno-nontrivial-memaccess']) From bb094e391c78be1e9a313891f286c7cda9900303 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 12:30:20 +0200 Subject: [PATCH 20/34] Fix marshaling of arrays of primitive types --- src/lib/libemval.js | 82 +++++++++++++++++++++++++++++++++ system/include/emscripten/val.h | 3 +- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 00a44da42192d..6568ba05ff868 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -101,6 +101,7 @@ var LibraryEmVal = { _emval_new_array__deps: ['$Emval'], _emval_new_array: () => Emval.toHandle([]), +#if !SUPPORT_BIG_ENDIAN _emval_new_array_from_memory_view__deps: ['$Emval'], _emval_new_array_from_memory_view: (view) => { view = Emval.toValue(view); @@ -109,6 +110,87 @@ var LibraryEmVal = { for (var i = 0; i < view.length; i++) a[i] = view[i]; return Emval.toHandle(a); }, + _emval_array_to_memory_view__deps: ['$Emval'], + _emval_array_to_memory_view: (dst, src) => { + dst = Emval.toValue(dst); + src = Emval.toValue(src); + dst.set(src); + }, +#else + _emval_new_array_from_memory_view__deps: ['$Emval'], + _emval_new_array_from_memory_view: (view) => { + view = Emval.toValue(view); + var reader = (() => { + if (view.BYTES_PER_ELEMENT===1) + return i => view[i]; + const dv = new DataView(view.buffer, view.byteOffset); + switch(view.BYTES_PER_ELEMENT) { + case 2: + if (view instanceof Int16Array) + return i => dv.getInt16(i * 2, true) + if (view instanceof Uint16Array) + return i => dv.getUint16(i * 2, true) + break; + case 4: + if (view instanceof Int32Array) + return i => dv.getInt32(i * 4, true) + if (view instanceof Uint32Array) + return i => dv.getUint32(i * 4, true) + if (view instanceof Float32Array) + return i => dv.getFloat32(i * 4, true) + break; + case 8: + if (view instanceof BigInt32Array) + return i => dv.getBigInt32(i * 8, true) + if (view instanceof BigUint32Array) + return i => dv.getBigUint32(i * 8, true) + if (view instanceof Float64Array) + return i => dv.getFloat64(i * 8, true) + break; + } + })(); + // using for..loop is faster than Array.from + var a = new Array(view.length); + for (var i = 0; i < view.length; i++) a[i] = reader(i); + return Emval.toHandle(a); + }, + _emval_array_to_memory_view__deps: ['$Emval'], + _emval_array_to_memory_view: (dst, src) => { + dst = Emval.toValue(dst); + src = Emval.toValue(src); + var writer = (() => { + if (dst.BYTES_PER_ELEMENT===1) + return (i, v) => { dst[i] = v; }; + const dv = new Datadst(dst.buffer, dst.byteOffset); + switch(dst.BYTES_PER_ELEMENT) { + case 2: + if (dst instanceof Int16Array) + return (i, v) => dv.setInt16(i * 2, v, true) + if (dst instanceof Uint16Array) + return (i, v) => dv.setUint16(i * 2, v, true) + break; + case 4: + if (dst instanceof Int32Array) + return (i, v) => dv.setInt32(i * 4, v, true) + if (dst instanceof Uint32Array) + return (i, v) => dv.setUint32(i * 4, v, true) + if (dst instanceof Float32Array) + return (i, v) => dv.setFloat32(i * 4, v, true) + break; + case 8: + if (dst instanceof BigInt32Array) + return (i, v) => dv.setBigInt32(i * 8, v, true) + if (dst instanceof BigUint32Array) + return (i, v) => dv.setBigUint32(i * 8, v, true) + if (dst instanceof Float64Array) + return (i, v) => dv.setFloat64(i * 8, v, true) + break; + } + })(); + // using for..loop is faster than Array.from + for (var i = 0; i < src.length; i++) writer(i, src[i]); + }, +#endif _emval_new_object__deps: ['$Emval'], _emval_new_object: () => Emval.toHandle({}), diff --git a/system/include/emscripten/val.h b/system/include/emscripten/val.h index a57c11a944bc5..c29ce7528244d 100644 --- a/system/include/emscripten/val.h +++ b/system/include/emscripten/val.h @@ -68,6 +68,7 @@ void _emval_run_destructors(EM_DESTRUCTORS handle); EM_VAL _emval_new_array(void); EM_VAL _emval_new_array_from_memory_view(EM_VAL mv); +void _emval_array_to_memory_view(EM_VAL dst, EM_VAL src); EM_VAL _emval_new_object(void); EM_VAL _emval_new_cstring(const char*); EM_VAL _emval_new_u8string(const char*); @@ -828,7 +829,7 @@ std::vector convertJSArrayToNumberVector(const val& v) { // See https://www.ecma-international.org/ecma-262/6.0/#sec-%typedarray%.prototype.set-array-offset // and https://www.ecma-international.org/ecma-262/6.0/#sec-tonumber val memoryView{ typed_memory_view(l, rv.data()) }; - memoryView.call("set", v); + internal::_emval_array_to_memory_view(memoryView.as_handle(), v.as_handle()); return rv; } From 1e933e9d266154b3c36cac5538687085e2baa209 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 13:20:11 +0200 Subject: [PATCH 21/34] disable incompatible tests --- test/test_core.py | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/test/test_core.py b/test/test_core.py index 23c8ea38f1d59..ec005d79c337e 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7547,6 +7547,7 @@ def test_embind_unbound_types(self): ''') self.do_runf('test.cpp', 'UnboundTypeError: Cannot call compute due to unbound types: Pi') + @no_big_endian("Accessing the array directly is not available on big endian system") def test_embind_memory_view(self): self.cflags += ['-lembind', '--post-js', 'post.js'] create_file('post.js', ''' @@ -7579,6 +7580,39 @@ def test_embind_memory_view(self): ''') self.do_runf('test.cpp', '107') + def test_embind_memory_view_be(self): + self.cflags += ['-lembind', '--post-js', 'post.js'] + create_file('post.js', ''' + function printFirstElement() { + const b = Module['getBufferView'](); + out(new DataView(b.buffer, b.byteOffset).getFloat64(0, true)); + } + ''') + create_file('test.cpp', r''' + #include + #include + #include + #include + using namespace emscripten; + + const size_t kBufferSize = 1024; + double buffer[kBufferSize]; + val getBufferView(void) { + val v = val(typed_memory_view(kBufferSize, buffer)); + return v; + } + EMSCRIPTEN_BINDINGS(my_module) { + function("getBufferView", &getBufferView); + } + + int main(int argc, char **argv) { + buffer[0] = 107; + EM_ASM(printFirstElement()); + return 0; + } + ''') + self.do_runf('test.cpp', '107') + def test_embind_inheritance(self): self.do_core_test('test_embind_inheritance.cpp', cflags=['-lembind']) @@ -7700,6 +7734,7 @@ def test_embind_dynamic_initialization(self): '': (False,), 'safe_heap': (True,), }) + @no_big_endian("Calling TypedArray.set directly is not available on big endian system") def test_embind_i64_val(self, safe_heap): if safe_heap and '-fsanitize=address' in self.cflags: self.skipTest('asan does not work with SAFE_HEAP') From e13fe0e67794c8f066908bd9a080722bb3dc2c1b Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 13:32:12 +0200 Subject: [PATCH 22/34] better waitAsync closure fix --- src/closure-externs/closure-externs.js | 1 + src/lib/liblittle_endian_heap.js | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index 1681cd2b4439d..bd8285b933549 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -64,6 +64,7 @@ var Atomics = {}; Atomics.compareExchange = function() {}; Atomics.exchange = function() {}; Atomics.wait = function() {}; +Atomics.waitAsync = function(a,b,c,d) {}; Atomics.notify = function() {}; Atomics.load = function() {}; Atomics.store = function() {}; diff --git a/src/lib/liblittle_endian_heap.js b/src/lib/liblittle_endian_heap.js index d0249ae972f91..1f97eb6530010 100644 --- a/src/lib/liblittle_endian_heap.js +++ b/src/lib/liblittle_endian_heap.js @@ -115,8 +115,7 @@ function LE_HEAP_UPDATE() { }, $LE_ATOMICS_WAITASYNC: (heap, offset, value, timeout) => { const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; - // this is suppressing incorrect closure JSC_INEXISTENT_PROPERTY warning that cannot be suppress via annotation - return Atomics.waitAsync && Atomics.waitAsync(heap, offset, order(value), timeout); + 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]; From 0a2dea19ba54def4caace3d6e81a5e2792440141 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 14:01:42 +0200 Subject: [PATCH 23/34] more closure... --- src/closure-externs/closure-externs.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index bd8285b933549..59f3aab0daebb 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -64,7 +64,7 @@ var Atomics = {}; Atomics.compareExchange = function() {}; Atomics.exchange = function() {}; Atomics.wait = function() {}; -Atomics.waitAsync = function(a,b,c,d) {}; +Atomics.waitAsync = function(...args) {}; Atomics.notify = function() {}; Atomics.load = function() {}; Atomics.store = function() {}; From 07ebbe8bb5612f18295439d08af9aa71a6e8b71c Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 14:33:07 +0200 Subject: [PATCH 24/34] signature update --- src/lib/libsigs.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 8dc66c98b74f1..46c2e1df16f08 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -335,6 +335,7 @@ sigs = { _emscripten_thread_mailbox_await__sig: 'vp', _emscripten_thread_set_strongref__sig: 'vp', _emscripten_throw_longjmp__sig: 'v', + _emval_array_to_memory_view__sig: 'vpp', _emval_await__sig: 'pp', _emval_coro_make_promise__sig: 'ppp', _emval_coro_suspend__sig: 'vpp', From 5b0a5f5bec56510ca1eaea2912f467f65ff44383 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Fri, 29 Aug 2025 15:26:37 +0200 Subject: [PATCH 25/34] and more closure... --- src/closure-externs/closure-externs.js | 1 - src/lib/liblittle_endian_heap.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index 59f3aab0daebb..1681cd2b4439d 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -64,7 +64,6 @@ var Atomics = {}; Atomics.compareExchange = function() {}; Atomics.exchange = function() {}; Atomics.wait = function() {}; -Atomics.waitAsync = function(...args) {}; Atomics.notify = function() {}; Atomics.load = function() {}; Atomics.store = function() {}; diff --git a/src/lib/liblittle_endian_heap.js b/src/lib/liblittle_endian_heap.js index 1f97eb6530010..74978375f1d21 100644 --- a/src/lib/liblittle_endian_heap.js +++ b/src/lib/liblittle_endian_heap.js @@ -115,7 +115,9 @@ function LE_HEAP_UPDATE() { }, $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); + /** @suppress {missingProperties} */ + const result = Atomics.waitAsync(heap, offset, order(value), timeout); + return result; }, $LE_ATOMICS_XOR: (heap, offset, value) => { const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; From 8561b2a5799022a4fbdcea8d4c647a2d6060eb8a Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 1 Sep 2025 08:57:12 +0200 Subject: [PATCH 26/34] cleanup --- src/lib/libemval.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 6568ba05ff868..cd2e2365fd037 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -149,7 +149,6 @@ var LibraryEmVal = { break; } })(); - // using for..loop is faster than Array.from var a = new Array(view.length); for (var i = 0; i < view.length; i++) a[i] = reader(i); return Emval.toHandle(a); @@ -186,8 +185,7 @@ var LibraryEmVal = { return (i, v) => dv.setFloat64(i * 8, v, true) break; } - })(); - // using for..loop is faster than Array.from + })(); for (var i = 0; i < src.length; i++) writer(i, src[i]); }, #endif From c93808abb35205394bdfbf872c78386177a1eb72 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 1 Sep 2025 09:00:12 +0200 Subject: [PATCH 27/34] one more chance for closure --- src/closure-externs/closure-externs.js | 5 +++++ src/lib/liblittle_endian_heap.js | 4 +--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/closure-externs/closure-externs.js b/src/closure-externs/closure-externs.js index 1681cd2b4439d..b5bfc58576197 100644 --- a/src/closure-externs/closure-externs.js +++ b/src/closure-externs/closure-externs.js @@ -64,6 +64,11 @@ var Atomics = {}; Atomics.compareExchange = function() {}; Atomics.exchange = function() {}; Atomics.wait = function() {}; +/** + * @param {number=} maxWaitMilliseconds + * @suppress {duplicate, checkTypes} + */ +Atomics.waitAsync = function(i32a, index, value, maxWaitMilliseconds) {}; Atomics.notify = function() {}; Atomics.load = function() {}; Atomics.store = function() {}; diff --git a/src/lib/liblittle_endian_heap.js b/src/lib/liblittle_endian_heap.js index 74978375f1d21..1f97eb6530010 100644 --- a/src/lib/liblittle_endian_heap.js +++ b/src/lib/liblittle_endian_heap.js @@ -115,9 +115,7 @@ function LE_HEAP_UPDATE() { }, $LE_ATOMICS_WAITASYNC: (heap, offset, value, timeout) => { const order = LE_ATOMICS_NATIVE_BYTE_ORDER[heap.BYTES_PER_ELEMENT - 1]; - /** @suppress {missingProperties} */ - const result = Atomics.waitAsync(heap, offset, order(value), timeout); - return result; + 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]; From 94f3e102973a81635dcfe393f31886c942870a5c Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 1 Sep 2025 16:52:15 +0200 Subject: [PATCH 28/34] typo --- src/lib/libemval.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index cd2e2365fd037..83dbc9a7b9196 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -160,7 +160,7 @@ var LibraryEmVal = { var writer = (() => { if (dst.BYTES_PER_ELEMENT===1) return (i, v) => { dst[i] = v; }; - const dv = new Datadst(dst.buffer, dst.byteOffset); + const dv = new DataView(dst.buffer, dst.byteOffset); switch(dst.BYTES_PER_ELEMENT) { case 2: if (dst instanceof Int16Array) From 1cb38932b1341ddf0d99c678d836ae9fa1fad6f9 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Mon, 1 Sep 2025 17:10:18 +0200 Subject: [PATCH 29/34] mark incompatible test --- test/test_core.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_core.py b/test/test_core.py index ec005d79c337e..e24c328513ae1 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7634,6 +7634,7 @@ def test_embind_unsigned(self): def test_embind_val(self): self.do_run_in_out_file_test('embind/test_val.cpp', cflags=['-lembind']) + @no_big_endian("Incompatible with SUPPORT_BIG_ENDIAN") def test_embind_val_read_pointer(self): self.do_runf('embind/test_val_read_pointer.cpp', cflags=['-lembind']) From 35fafdaa4825bba15629f36443ab90e4874e3898 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Tue, 2 Sep 2025 10:55:13 +0200 Subject: [PATCH 30/34] produce error when combining wasm2js and big endian support --- tools/link.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tools/link.py b/tools/link.py index 739ca8320f729..65ac42b47bd47 100644 --- a/tools/link.py +++ b/tools/link.py @@ -846,6 +846,8 @@ def phase_linker_setup(options, linker_args): # noqa: C901, PLR0912, PLR0915 if settings.SUPPORT_BIG_ENDIAN: diagnostics.warning('experimental', '-sSUPPORT_BIG_ENDIAN is experimental, not all features are fully supported.') + if settings.WASM2JS: + exit_with_error('WASMJ2S is currently not compatible with SUPPORT_BIG_ENDIAN') if settings.WASM_ESM_INTEGRATION: diagnostics.warning('experimental', '-sWASM_ESM_INTEGRATION is still experimental and not yet supported in browsers') From b7d947f73be8195b94818fe55fa91264184af1b3 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Wed, 10 Sep 2025 10:18:29 +0200 Subject: [PATCH 31/34] fix 32-64 typos --- src/lib/libemval.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 83dbc9a7b9196..b37f669dd2697 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -140,10 +140,10 @@ var LibraryEmVal = { return i => dv.getFloat32(i * 4, true) break; case 8: - if (view instanceof BigInt32Array) - return i => dv.getBigInt32(i * 8, true) - if (view instanceof BigUint32Array) - return i => dv.getBigUint32(i * 8, true) + if (view instanceof BigInt64Array) + return i => dv.getBigInt64(i * 8, true) + if (view instanceof BigUint64Array) + return i => dv.getBigUint64(i * 8, true) if (view instanceof Float64Array) return i => dv.getFloat64(i * 8, true) break; @@ -177,10 +177,10 @@ var LibraryEmVal = { return (i, v) => dv.setFloat32(i * 4, v, true) break; case 8: - if (dst instanceof BigInt32Array) - return (i, v) => dv.setBigInt32(i * 8, v, true) - if (dst instanceof BigUint32Array) - return (i, v) => dv.setBigUint32(i * 8, v, true) + if (dst instanceof BigInt64Array) + return (i, v) => dv.setBigInt64(i * 8, v, true) + if (dst instanceof BigUint64Array) + return (i, v) => dv.setBigUint64(i * 8, v, true) if (dst instanceof Float64Array) return (i, v) => dv.setFloat64(i * 8, v, true) break; From b387bdb07b7bc9204e7fa6e0b1ef6a422faa7d3d Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Wed, 10 Sep 2025 12:02:43 +0200 Subject: [PATCH 32/34] adjust pthread codesizes --- test/code_size/test_codesize_minimal_pthreads.json | 8 ++++---- .../test_codesize_minimal_pthreads_memgrowth.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/code_size/test_codesize_minimal_pthreads.json b/test/code_size/test_codesize_minimal_pthreads.json index 4f909f58e225c..e9250f7056faf 100644 --- a/test/code_size/test_codesize_minimal_pthreads.json +++ b/test/code_size/test_codesize_minimal_pthreads.json @@ -1,10 +1,10 @@ { - "a.out.js": 7659, - "a.out.js.gz": 3776, + "a.out.js": 7675, + "a.out.js.gz": 3780, "a.out.nodebug.wasm": 19599, "a.out.nodebug.wasm.gz": 9063, - "total": 27258, - "total_gz": 12839, + "total": 27274, + "total_gz": 12843, "sent": [ "a (memory)", "b (emscripten_get_now)", diff --git a/test/code_size/test_codesize_minimal_pthreads_memgrowth.json b/test/code_size/test_codesize_minimal_pthreads_memgrowth.json index 6ab7a856a15ac..21416e8da0d56 100644 --- a/test/code_size/test_codesize_minimal_pthreads_memgrowth.json +++ b/test/code_size/test_codesize_minimal_pthreads_memgrowth.json @@ -1,10 +1,10 @@ { - "a.out.js": 8086, - "a.out.js.gz": 3977, + "a.out.js": 8102, + "a.out.js.gz": 3980, "a.out.nodebug.wasm": 19600, "a.out.nodebug.wasm.gz": 9064, - "total": 27686, - "total_gz": 13041, + "total": 27702, + "total_gz": 13044, "sent": [ "a (memory)", "b (emscripten_get_now)", From 468dfb13ab9ded3f9f84281ec7bd8b73a5fe7b36 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Wed, 10 Sep 2025 16:21:37 +0200 Subject: [PATCH 33/34] ... --- src/lib/libemval.js | 86 ++++++++++++++------------------------------- 1 file changed, 26 insertions(+), 60 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index b37f669dd2697..a3d2f4ee95996 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -120,73 +120,39 @@ var LibraryEmVal = { _emval_new_array_from_memory_view__deps: ['$Emval'], _emval_new_array_from_memory_view: (view) => { view = Emval.toValue(view); - var reader = (() => { - if (view.BYTES_PER_ELEMENT===1) - return i => view[i]; - const dv = new DataView(view.buffer, view.byteOffset); - switch(view.BYTES_PER_ELEMENT) { - case 2: - if (view instanceof Int16Array) - return i => dv.getInt16(i * 2, true) - if (view instanceof Uint16Array) - return i => dv.getUint16(i * 2, true) - break; - case 4: - if (view instanceof Int32Array) - return i => dv.getInt32(i * 4, true) - if (view instanceof Uint32Array) - return i => dv.getUint32(i * 4, true) - if (view instanceof Float32Array) - return i => dv.getFloat32(i * 4, true) - break; - case 8: - if (view instanceof BigInt64Array) - return i => dv.getBigInt64(i * 8, true) - if (view instanceof BigUint64Array) - return i => dv.getBigUint64(i * 8, true) - if (view instanceof Float64Array) - return i => dv.getFloat64(i * 8, true) - break; - } - })(); + const reader = { + Int8Array: dv.getInt8, + Uint8Array: dv.getUint8, + Int16Array: dv.getInt16, + Uint16Array: dv.getUint16, + Int32Array: dv.getInt32, + Uint32Array: dv.getUint32, + BigInt64Array: dv.getBigInt64, + BigUint64Array: dv.getBigUint64, + Float32Array: dv.getFloat32, + Float64Array: dv.getFloat64, + }[view[Symbol.toStringTag]]; var a = new Array(view.length); - for (var i = 0; i < view.length; i++) a[i] = reader(i); + for (var i = 0; i < view.length; i++) a[i] = reader.call(dv, i * view.BYTES_PER_ELEMENT, true); return Emval.toHandle(a); }, _emval_array_to_memory_view__deps: ['$Emval'], _emval_array_to_memory_view: (dst, src) => { dst = Emval.toValue(dst); src = Emval.toValue(src); - var writer = (() => { - if (dst.BYTES_PER_ELEMENT===1) - return (i, v) => { dst[i] = v; }; - const dv = new DataView(dst.buffer, dst.byteOffset); - switch(dst.BYTES_PER_ELEMENT) { - case 2: - if (dst instanceof Int16Array) - return (i, v) => dv.setInt16(i * 2, v, true) - if (dst instanceof Uint16Array) - return (i, v) => dv.setUint16(i * 2, v, true) - break; - case 4: - if (dst instanceof Int32Array) - return (i, v) => dv.setInt32(i * 4, v, true) - if (dst instanceof Uint32Array) - return (i, v) => dv.setUint32(i * 4, v, true) - if (dst instanceof Float32Array) - return (i, v) => dv.setFloat32(i * 4, v, true) - break; - case 8: - if (dst instanceof BigInt64Array) - return (i, v) => dv.setBigInt64(i * 8, v, true) - if (dst instanceof BigUint64Array) - return (i, v) => dv.setBigUint64(i * 8, v, true) - if (dst instanceof Float64Array) - return (i, v) => dv.setFloat64(i * 8, v, true) - break; - } - })(); - for (var i = 0; i < src.length; i++) writer(i, src[i]); + const writer = { + Int8Array: dv.setInt8, + Uint8Array: dv.setUint8, + Int16Array: dv.setInt16, + Uint16Array: dv.setUint16, + Int32Array: dv.setInt32, + Uint32Array: dv.setUint32, + BigInt64Array: dv.setBigInt64, + BigUint64Array: dv.setBigUint64, + Float32Array: dv.setFloat32, + Float64Array: dv.setFloat64, + }[view[Symbol.toStringTag]]; + for (var i = 0; i < src.length; i++) writer.call(dv, i * view.BYTES_PER_ELEMENT, src[i], true); }, #endif From 7f5fe73ba5661e5e228b55a5d836d898925fe6a0 Mon Sep 17 00:00:00 2001 From: Slavomir Kucera Date: Wed, 10 Sep 2025 16:27:08 +0200 Subject: [PATCH 34/34] missing dataview --- src/lib/libemval.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index a3d2f4ee95996..38f4c8791925a 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -120,6 +120,7 @@ var LibraryEmVal = { _emval_new_array_from_memory_view__deps: ['$Emval'], _emval_new_array_from_memory_view: (view) => { view = Emval.toValue(view); + const dv = new DataView(view.buffer, view.byteOffset); const reader = { Int8Array: dv.getInt8, Uint8Array: dv.getUint8, @@ -140,6 +141,7 @@ var LibraryEmVal = { _emval_array_to_memory_view: (dst, src) => { dst = Emval.toValue(dst); src = Emval.toValue(src); + const dv = new DataView(dst.buffer, dst.byteOffset); const writer = { Int8Array: dv.setInt8, Uint8Array: dv.setUint8,