Skip to content

Commit 9cd026c

Browse files
authored
[wasm64] Add EM_ASM support for MEMORY64 (#16931)
1 parent 79e872c commit 9cd026c

File tree

11 files changed

+149
-18
lines changed

11 files changed

+149
-18
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -381,7 +381,7 @@ jobs:
381381
executor: bionic
382382
steps:
383383
- run-tests-linux:
384-
test_targets: "wasm64.test_hello_world wasm64.test_ccall wasm64l.test_hello_world wasm64l.test_mmap wasm64l.test_unistd_* skip:wasm64l.test_unistd_sysconf wasm64l.test_mmap_file wasm64l.test_ccall wasm64l.test_signals wasm64l.test_emscripten_get_compiler_setting wasm64l.test_float_builtins wasm64l.test_getopt"
384+
test_targets: "wasm64.test_hello_world wasm64.test_ccall wasm64l.test_hello_world wasm64l.test_mmap wasm64l.test_unistd_* skip:wasm64l.test_unistd_sysconf wasm64l.test_mmap_file wasm64l.test_ccall wasm64l.test_signals wasm64l.test_emscripten_get_compiler_setting wasm64l.test_float_builtins wasm64l.test_getopt wasm64l.test_em_asm*"
385385
test-other:
386386
executor: bionic
387387
steps:

site/source/docs/api_reference/emscripten.h.rst

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,9 @@ Defines
166166

167167
.. c:macro:: EM_ASM_INT(code, ...)
168168
169-
This macro, as well as the :c:macro:`EM_ASM_DOUBLE` one, behave like :c:macro:`EM_ASM`, but in addition they also return a value back to C code. The output value is passed back with a ``return`` statement:
169+
This macro, as well as :c:macro:`EM_ASM_DOUBLE` and :c:macro:`EM_ASM_PTR`,
170+
behave like :c:macro:`EM_ASM`, but in addition they also return a value back
171+
to C code. The output value is passed back with a ``return`` statement:
170172

171173
.. code-block:: none
172174
@@ -176,11 +178,18 @@ Defines
176178
177179
int y = EM_ASM_INT(return HEAP8.length);
178180
179-
Strings can be returned back to C from JavaScript, but one needs to be careful about memory management.
181+
.. c:macro:: EM_ASM_PTR(code, ...)
182+
183+
Similar to :c:macro:`EM_ASM_INT` but for a pointer-sized return values.
184+
When building with ``-sMEMORY64`` this results in i64 return value, otherwise
185+
it results in an i32 return value.
186+
187+
Strings can be returned back to C from JavaScript, but one needs to be careful
188+
about memory management.
180189

181190
.. code-block:: none
182191
183-
char *str = (char*)EM_ASM_INT({
192+
char *str = (char*)EM_ASM_PTR({
184193
var jsString = 'Hello with some exotic Unicode characters: Tässä on yksi lumiukko: ☃, ole hyvä.';
185194
var lengthBytes = lengthBytesUTF8(jsString)+1;
186195
// 'jsString.length' would return the length of the string as UTF-16

site/source/docs/porting/connecting_cpp_and_javascript/Interacting-with-code.rst

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -319,8 +319,8 @@ for example::
319319

320320
This will show ``I received: 100``.
321321

322-
You can also receive values back, for example the following will print out ``I received: 100``
323-
and then ``101``::
322+
You can also receive values back, for example the following will print out ``I
323+
received: 100`` and then ``101``::
324324

325325
int x = EM_ASM_INT({
326326
console.log('I received: ' + $0);
@@ -332,9 +332,12 @@ See the :c:macro:`emscripten.h docs <EM_ASM_>` for more details.
332332

333333
.. note::
334334

335-
- You need to specify if the return value is an ``int`` or a ``double``
336-
using the appropriate macro :c:macro:`EM_ASM_INT` or
337-
:c:macro:`EM_ASM_DOUBLE`.
335+
- You need to specify if the return value is an ``int``, ``double``
336+
or pointer type using the appropriate macro :c:macro:`EM_ASM_INT`,
337+
:c:macro:`EM_ASM_DOUBLE` or :c:macro:`EM_ASM_PTR`.
338+
(:c:macro:`EM_ASM_PTR` is the same as :c:macro:`EM_ASM_INT` unless
339+
``MEMORY64`` is used, so is mostly needed in code that wants to be
340+
compatible with ``MEMORY64``).
338341
- The input values appear as ``$0``, ``$1``, etc.
339342
- ``return`` is used to provide the value sent from JavaScript back to C.
340343
- See how ``{`` and ``}`` are used here to enclose the code. This is

src/library.js

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2875,7 +2875,12 @@ LibraryManager.library = {
28752875
#endif
28762876

28772877
$readAsmConstArgsArray: '=[]',
2878-
$readAsmConstArgs__deps: ['$readAsmConstArgsArray'],
2878+
$readAsmConstArgs__deps: [
2879+
'$readAsmConstArgsArray',
2880+
#if MEMORY64
2881+
'$readI53FromI64',
2882+
#endif
2883+
],
28792884
$readAsmConstArgs: function(sigPtr, buf) {
28802885
{{{ from64(['sigPtr', 'buf']) }}};
28812886
#if ASSERTIONS
@@ -2891,14 +2896,28 @@ LibraryManager.library = {
28912896
buf >>= 2;
28922897
while (ch = HEAPU8[sigPtr++]) {
28932898
#if ASSERTIONS
2894-
assert(ch === 100/*'d'*/ || ch === 102/*'f'*/ || ch === 105 /*'i'*/ || ch === 106 /*'j'*/, 'Invalid character ' + ch + '("' + String.fromCharCode(ch) + '") in readAsmConstArgs! Use only "d", "f" or "i", and do not specify "v" for void return argument.');
2895-
#if !WASM_BIGINT
2896-
assert(ch !== 106/*'j'*/, "i64 arguments to ASM_JS function are not available without WASM_BIGINT");
2899+
var chr = String.fromCharCode(ch);
2900+
var validChars = ['d', 'f', 'i'];
2901+
#if WASM_BIGINT
2902+
// In WASM_BIGINT mode we support passing i64 values as bigint.
2903+
validChars.push('j');
28972904
#endif
2905+
#if MEMORY64
2906+
// In MEMORY64 mode we also support passing i64 pointer types which
2907+
// get automatically converted to int53/Double.
2908+
validChars.push('p');
2909+
#endif
2910+
assert(validChars.includes(chr), 'Invalid character ' + ch + '("' + chr + '") in readAsmConstArgs! Use only [' + validChars + '], and do not specify "v" for void return argument.');
28982911
#endif
28992912
// Floats are always passed as doubles, and doubles and int64s take up 8
29002913
// bytes (two 32-bit slots) in memory, align reads to these:
2901-
buf += (ch != 105) & buf;
2914+
buf += (ch != 105/*i*/) & buf;
2915+
#if MEMORY64
2916+
// Special case for pointers under wasm64 which we read as int53 Numbers.
2917+
if (ch == 112/*p*/) {
2918+
readAsmConstArgsArray.push(readI53FromI64(buf++ << 2));
2919+
} else
2920+
#endif
29022921
readAsmConstArgsArray.push(
29032922
ch == 105/*i*/ ? HEAP32[buf] :
29042923
#if WASM_BIGINT
@@ -2912,7 +2931,7 @@ LibraryManager.library = {
29122931
return readAsmConstArgsArray;
29132932
},
29142933

2915-
emscripten_asm_const_int__sig: 'iiii',
2934+
emscripten_asm_const_int__sig: 'ippp',
29162935
emscripten_asm_const_int__deps: ['$readAsmConstArgs'],
29172936
emscripten_asm_const_int: function(code, sigPtr, argbuf) {
29182937
#if RELOCATABLE
@@ -2922,10 +2941,24 @@ LibraryManager.library = {
29222941
#if ASSERTIONS
29232942
if (!ASM_CONSTS.hasOwnProperty(code)) abort('No EM_ASM constant found at address ' + code);
29242943
#endif
2944+
#if MEMORY64
2945+
return Number(ASM_CONSTS[code].apply(null, args));
2946+
#else
29252947
return ASM_CONSTS[code].apply(null, args);
2948+
#endif
29262949
},
29272950
emscripten_asm_const_double: 'emscripten_asm_const_int',
29282951

2952+
#if MEMORY64
2953+
emscripten_asm_const_ptr__sig: 'pppp',
2954+
emscripten_asm_const_ptr__deps: ['emscripten_asm_const_int'],
2955+
emscripten_asm_const_ptr: function(code, sigPtr, argbuf) {
2956+
return _emscripten_asm_const_int(code, sigPtr, argbuf);
2957+
},
2958+
#else
2959+
emscripten_asm_const_ptr: 'emscripten_asm_const_int',
2960+
#endif
2961+
29292962
$mainThreadEM_ASM__deps: ['$readAsmConstArgs'],
29302963
$mainThreadEM_ASM: function(code, sigPtr, argbuf, sync) {
29312964
#if RELOCATABLE

system/include/emscripten/em_asm.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ extern "C" {
1919
__attribute__((nothrow))
2020
int emscripten_asm_const_int(const char* code, const char* arg_sigs, ...);
2121
__attribute__((nothrow))
22+
void* emscripten_asm_const_ptr(const char* code, const char* arg_sigs, ...);
23+
__attribute__((nothrow))
2224
double emscripten_asm_const_double(const char* code, const char* arg_sigs, ...);
2325

2426
__attribute__((nothrow))
@@ -42,6 +44,7 @@ void emscripten_asm_const_async_on_main_thread(
4244
#define EM_ASM_ERROR _Pragma("GCC error(\"EM_ASM does not work in -std=c* modes, use -std=gnu* modes instead\")")
4345
#define EM_ASM(...) EM_ASM_ERROR
4446
#define EM_ASM_INT(...) EM_ASM_ERROR
47+
#define EM_ASM_PTR(...) EM_ASM_ERROR
4548
#define EM_ASM_DOUBLE(...) EM_ASM_ERROR
4649
#define MAIN_THREAD_EM_ASM(...) EM_ASM_ERROR
4750
#define MAIN_THREAD_EM_ASM_INT(...) EM_ASM_ERROR
@@ -67,11 +70,32 @@ void emscripten_asm_const_async_on_main_thread(
6770
// We can use the generic selection C11 feature (that clang supports pre-C11
6871
// as an extension) to emulate function overloading in C.
6972
// All other types, including *all* pointer types go through the default case.
73+
// This means we need to use a different default type for `__wasm64__` where
74+
// pointers are 64-bits wide, and we also need to include more non-default
75+
// cases, for example, we don't want `short` to end up using the wider default.
76+
#if __wasm64__
77+
#define _EM_ASM_SIG_CHAR(x) _Generic((x), \
78+
float: 'f', \
79+
double: 'd', \
80+
char: 'i', \
81+
unsigned char: 'i', \
82+
unsigned short: 'i', \
83+
unsigned int: 'i', \
84+
unsigned long: 'j', \
85+
unsigned long long: 'j', \
86+
signed char: 'i', \
87+
signed short: 'i', \
88+
signed int: 'i', \
89+
signed long: 'j', \
90+
signed long long: 'j', \
91+
default: 'p')
92+
#else
7093
#define _EM_ASM_SIG_CHAR(x) _Generic((x), \
7194
float: 'f', \
7295
double: 'd', \
7396
long long: 'j', \
7497
default: 'i')
98+
#endif
7599

76100
// This indirection is needed to allow us to concatenate computed results, e.g.
77101
// #define BAR(N) _EM_ASM_CONCATENATE(FOO_, N)
@@ -135,13 +159,22 @@ template<> struct __em_asm_sig<short> { static const char value = 'i'; };
135159
template<> struct __em_asm_sig<unsigned short> { static const char value = 'i'; };
136160
template<> struct __em_asm_sig<int> { static const char value = 'i'; };
137161
template<> struct __em_asm_sig<unsigned int> { static const char value = 'i'; };
162+
#if __wasm64__
163+
template<> struct __em_asm_sig<long> { static const char value = 'j'; };
164+
template<> struct __em_asm_sig<unsigned long> { static const char value = 'j'; };
165+
#else
138166
template<> struct __em_asm_sig<long> { static const char value = 'i'; };
139167
template<> struct __em_asm_sig<unsigned long> { static const char value = 'i'; };
168+
#endif
140169
template<> struct __em_asm_sig<bool> { static const char value = 'i'; };
141170
template<> struct __em_asm_sig<wchar_t> { static const char value = 'i'; };
142171
template<> struct __em_asm_sig<long long> { static const char value = 'j'; };
143172
template<> struct __em_asm_sig<unsigned long long> { static const char value = 'j'; };
173+
#if __wasm64__
174+
template<typename T> struct __em_asm_sig<T*> { static const char value = 'p'; };
175+
#else
144176
template<typename T> struct __em_asm_sig<T*> { static const char value = 'i'; };
177+
#endif
145178

146179
// Explicit support for enums, they're passed as int via variadic arguments.
147180
template<bool> struct __em_asm_if { };
@@ -191,9 +224,13 @@ const char __em_asm_sig_builder<__em_asm_type_tuple<Args...> >::buffer[] = { __e
191224
// Runs the given JavaScript code on the calling thread (synchronously), and returns no value back.
192225
#define EM_ASM(code, ...) ((void)emscripten_asm_const_int(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__)))
193226

194-
// Runs the given JavaScript code on the calling thread (synchronously), and returns an integer back.
227+
// Runs the given JavaScript code on the calling thread (synchronously), and returns an i32 back.
195228
#define EM_ASM_INT(code, ...) emscripten_asm_const_int(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))
196229

230+
// Runs the given JavaScript code on the calling thread (synchronously), and returns an pointer back.
231+
// On wasm32 this is the same as emscripten_asm_const_int but on wasm64 it returns an i64.
232+
#define EM_ASM_PTR(code, ...) emscripten_asm_const_ptr(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))
233+
197234
// Runs the given JavaScript code on the calling thread (synchronously), and returns a double back.
198235
#define EM_ASM_DOUBLE(code, ...) emscripten_asm_const_double(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))
199236

tests/core/test_em_asm.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,27 @@ int main() {
1010
printf("BEGIN\n");
1111
EM_ASM({ out("no args works"); });
1212

13+
unsigned long p = 8;
14+
EM_ASM({
15+
console.log("int types:");
16+
out(" char : " + $0);
17+
out(" signed char : " + $1);
18+
out("unsigned char : " + $2);
19+
out(" short: " + $3);
20+
out(" signed short: " + $4);
21+
out("unsigned short: " + $5);
22+
out(" int : " + $6);
23+
out(" signed int : " + $7);
24+
out("unsigned int : " + $8);
25+
out(" long : " + $9);
26+
out(" signed long : " + $10);
27+
out("unsigned long : " + $11);
28+
out(" terminator: " + $12);
29+
}, (char)1, (signed char)2, (unsigned char)3,
30+
(short)4, (signed short)5, (unsigned short)6,
31+
(int)7, (signed int)8, (unsigned int)9,
32+
(long)10, (signed long)11, (unsigned long)12, 42);
33+
1334
// The following two lines are deprecated, test them still.
1435
printf(" EM_ASM_INT_V returned: %d\n", EM_ASM_INT_V({ out("no args returning int"); return 12; }));
1536
printf(" EM_ASM_DOUBLE_V returned: %f\n", EM_ASM_DOUBLE_V({ out("no args returning double"); return 12.25; }));

tests/core/test_em_asm.out

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
BEGIN
22
no args works
3+
int types:
4+
char : 1
5+
signed char : 2
6+
unsigned char : 3
7+
short: 4
8+
signed short: 5
9+
unsigned short: 6
10+
int : 7
11+
signed int : 8
12+
unsigned int : 9
13+
long : 10
14+
signed long : 11
15+
unsigned long : 12
16+
terminator: 42
317
no args returning int
418
EM_ASM_INT_V returned: 12
519
no args returning double

tests/core/test_em_asm_types.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,25 @@ int main(int argc, char **argv) {
1616
// Promotions of arrays, function/member pointers and objects implicitly
1717
// convertible to numbers are excluded because they will not be translated
1818
// to corresponding JS objects.
19-
#define TEST_TYPE(type, value) EM_ASM({console.log(#type, $0);}, (type)(value));
19+
#define TEST_TYPE(type, value) EM_ASM({console.log(#type, Number($0));}, (type)(value));
2020
TEST_TYPE(int*, 0);
2121
TEST_TYPE(float, 1.5f);
2222
TEST_TYPE(double, 2.5);
23+
2324
TEST_TYPE(char, 10);
2425
TEST_TYPE(signed char, -10);
2526
TEST_TYPE(unsigned char, 10);
27+
2628
TEST_TYPE(short, -20);
29+
TEST_TYPE(signed short, -20);
2730
TEST_TYPE(unsigned short, 20);
31+
2832
TEST_TYPE(int, -30);
33+
TEST_TYPE(signed int, -30);
2934
TEST_TYPE(unsigned int, 30);
35+
3036
TEST_TYPE(long, -40);
37+
TEST_TYPE(signed long, -40);
3138
TEST_TYPE(unsigned long, 40);
3239

3340
struct WithBitField w;

tests/core/test_em_asm_types.out

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ char 10
55
signed char -10
66
unsigned char 10
77
short -20
8+
signed short -20
89
unsigned short 20
910
int -30
11+
signed int -30
1012
unsigned int 30
1113
long -40
14+
signed long -40
1215
unsigned long 40
1316
bit field 3
1417
bool 1

tests/test_core.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,8 @@ def test_inlinejs4(self):
21022102

21032103
def test_em_asm(self):
21042104
self.do_core_test('test_em_asm.cpp')
2105+
2106+
def test_em_asm_c(self):
21052107
self.emcc_args.append('-std=gnu89')
21062108
self.do_core_test('test_em_asm.cpp', force_c=True)
21072109

@@ -2139,6 +2141,8 @@ def test_em_asm_unicode(self):
21392141

21402142
def test_em_asm_types(self):
21412143
self.do_core_test('test_em_asm_types.cpp')
2144+
2145+
def test_em_asm_types_c(self):
21422146
self.do_core_test('test_em_asm_types.cpp', force_c=True)
21432147

21442148
def test_em_asm_unused_arguments(self):

0 commit comments

Comments
 (0)