Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ jobs:
executor: bionic
steps:
- run-tests-linux:
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"
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*"
test-other:
executor: bionic
steps:
Expand Down
15 changes: 12 additions & 3 deletions site/source/docs/api_reference/emscripten.h.rst
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ Defines

.. c:macro:: EM_ASM_INT(code, ...)

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:
This macro, as well as :c:macro:`EM_ASM_DOUBLE` and :c:macro:`EM_ASM_PTR`,
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:

.. code-block:: none

Expand All @@ -176,11 +178,18 @@ Defines

int y = EM_ASM_INT(return HEAP8.length);

Strings can be returned back to C from JavaScript, but one needs to be careful about memory management.
.. c:macro:: EM_ASM_PTR(code, ...)

Similar to :c:macro:`EM_ASM_INT` but for a pointer-sized return values.
When building with ``-sMEMORY64`` this results in i64 return value, otherwise
it results in an i32 return value.

Strings can be returned back to C from JavaScript, but one needs to be careful
about memory management.

.. code-block:: none

char *str = (char*)EM_ASM_INT({
char *str = (char*)EM_ASM_PTR({
var jsString = 'Hello with some exotic Unicode characters: Tässä on yksi lumiukko: ☃, ole hyvä.';
var lengthBytes = lengthBytesUTF8(jsString)+1;
// 'jsString.length' would return the length of the string as UTF-16
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,8 @@ for example::

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

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

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

.. note::

- You need to specify if the return value is an ``int`` or a ``double``
using the appropriate macro :c:macro:`EM_ASM_INT` or
:c:macro:`EM_ASM_DOUBLE`.
- You need to specify if the return value is an ``int``, ``double``
or pointer type using the appropriate macro :c:macro:`EM_ASM_INT`,
:c:macro:`EM_ASM_DOUBLE` or :c:macro:`EM_ASM_PTR`.
(:c:macro:`EM_ASM_PTR` is the same as :c:macro:`EM_ASM_INT` unless
``MEMORY64`` is used, so is mostly needed in code that wants to be
compatible with ``MEMORY64``).
- The input values appear as ``$0``, ``$1``, etc.
- ``return`` is used to provide the value sent from JavaScript back to C.
- See how ``{`` and ``}`` are used here to enclose the code. This is
Expand Down
45 changes: 39 additions & 6 deletions src/library.js
Original file line number Diff line number Diff line change
Expand Up @@ -2875,7 +2875,12 @@ LibraryManager.library = {
#endif

$readAsmConstArgsArray: '=[]',
$readAsmConstArgs__deps: ['$readAsmConstArgsArray'],
$readAsmConstArgs__deps: [
'$readAsmConstArgsArray',
#if MEMORY64
'$readI53FromI64',
#endif
],
$readAsmConstArgs: function(sigPtr, buf) {
{{{ from64(['sigPtr', 'buf']) }}};
#if ASSERTIONS
Expand All @@ -2891,14 +2896,28 @@ LibraryManager.library = {
buf >>= 2;
while (ch = HEAPU8[sigPtr++]) {
#if ASSERTIONS
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.');
#if !WASM_BIGINT
assert(ch !== 106/*'j'*/, "i64 arguments to ASM_JS function are not available without WASM_BIGINT");
var chr = String.fromCharCode(ch);
var validChars = ['d', 'f', 'i'];
#if WASM_BIGINT
// In WASM_BIGINT mode we support passing i64 values as bigint.
validChars.push('j');
#endif
#if MEMORY64
// In MEMORY64 mode we also support passing i64 pointer types which
// get automatically converted to int53/Double.
validChars.push('p');
#endif
assert(validChars.includes(chr), 'Invalid character ' + ch + '("' + chr + '") in readAsmConstArgs! Use only [' + validChars + '], and do not specify "v" for void return argument.');
#endif
// Floats are always passed as doubles, and doubles and int64s take up 8
// bytes (two 32-bit slots) in memory, align reads to these:
buf += (ch != 105) & buf;
buf += (ch != 105/*i*/) & buf;
#if MEMORY64
// Special case for pointers under wasm64 which we read as int53 Numbers.
if (ch == 112/*p*/) {
readAsmConstArgsArray.push(readI53FromI64(buf++ << 2));
} else
#endif
readAsmConstArgsArray.push(
ch == 105/*i*/ ? HEAP32[buf] :
#if WASM_BIGINT
Expand All @@ -2912,7 +2931,7 @@ LibraryManager.library = {
return readAsmConstArgsArray;
},

emscripten_asm_const_int__sig: 'iiii',
emscripten_asm_const_int__sig: 'ippp',
emscripten_asm_const_int__deps: ['$readAsmConstArgs'],
emscripten_asm_const_int: function(code, sigPtr, argbuf) {
#if RELOCATABLE
Expand All @@ -2922,10 +2941,24 @@ LibraryManager.library = {
#if ASSERTIONS
if (!ASM_CONSTS.hasOwnProperty(code)) abort('No EM_ASM constant found at address ' + code);
#endif
#if MEMORY64
return Number(ASM_CONSTS[code].apply(null, args));
#else
return ASM_CONSTS[code].apply(null, args);
#endif
},
emscripten_asm_const_double: 'emscripten_asm_const_int',

#if MEMORY64
emscripten_asm_const_ptr__sig: 'pppp',
emscripten_asm_const_ptr__deps: ['emscripten_asm_const_int'],
emscripten_asm_const_ptr: function(code, sigPtr, argbuf) {
return _emscripten_asm_const_int(code, sigPtr, argbuf);
},
#else
emscripten_asm_const_ptr: 'emscripten_asm_const_int',
#endif

$mainThreadEM_ASM__deps: ['$readAsmConstArgs'],
$mainThreadEM_ASM: function(code, sigPtr, argbuf, sync) {
#if RELOCATABLE
Expand Down
39 changes: 38 additions & 1 deletion system/include/emscripten/em_asm.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extern "C" {
__attribute__((nothrow))
int emscripten_asm_const_int(const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
void* emscripten_asm_const_ptr(const char* code, const char* arg_sigs, ...);
__attribute__((nothrow))
double emscripten_asm_const_double(const char* code, const char* arg_sigs, ...);

__attribute__((nothrow))
Expand All @@ -42,6 +44,7 @@ void emscripten_asm_const_async_on_main_thread(
#define EM_ASM_ERROR _Pragma("GCC error(\"EM_ASM does not work in -std=c* modes, use -std=gnu* modes instead\")")
#define EM_ASM(...) EM_ASM_ERROR
#define EM_ASM_INT(...) EM_ASM_ERROR
#define EM_ASM_PTR(...) EM_ASM_ERROR
#define EM_ASM_DOUBLE(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM(...) EM_ASM_ERROR
#define MAIN_THREAD_EM_ASM_INT(...) EM_ASM_ERROR
Expand All @@ -67,11 +70,32 @@ void emscripten_asm_const_async_on_main_thread(
// We can use the generic selection C11 feature (that clang supports pre-C11
// as an extension) to emulate function overloading in C.
// All other types, including *all* pointer types go through the default case.
// This means we need to use a different default type for `__wasm64__` where
// pointers are 64-bits wide, and we also need to include more non-default
// cases, for example, we don't want `short` to end up using the wider default.
#if __wasm64__
#define _EM_ASM_SIG_CHAR(x) _Generic((x), \
float: 'f', \
double: 'd', \
char: 'i', \
unsigned char: 'i', \
unsigned short: 'i', \
unsigned int: 'i', \
unsigned long: 'j', \
unsigned long long: 'j', \
signed char: 'i', \
signed short: 'i', \
signed int: 'i', \
signed long: 'j', \
signed long long: 'j', \
default: 'p')
#else
#define _EM_ASM_SIG_CHAR(x) _Generic((x), \
float: 'f', \
double: 'd', \
long long: 'j', \
default: 'i')
#endif

// This indirection is needed to allow us to concatenate computed results, e.g.
// #define BAR(N) _EM_ASM_CONCATENATE(FOO_, N)
Expand Down Expand Up @@ -135,13 +159,22 @@ template<> struct __em_asm_sig<short> { static const char value = 'i'; };
template<> struct __em_asm_sig<unsigned short> { static const char value = 'i'; };
template<> struct __em_asm_sig<int> { static const char value = 'i'; };
template<> struct __em_asm_sig<unsigned int> { static const char value = 'i'; };
#if __wasm64__
template<> struct __em_asm_sig<long> { static const char value = 'j'; };
template<> struct __em_asm_sig<unsigned long> { static const char value = 'j'; };
#else
template<> struct __em_asm_sig<long> { static const char value = 'i'; };
template<> struct __em_asm_sig<unsigned long> { static const char value = 'i'; };
#endif
template<> struct __em_asm_sig<bool> { static const char value = 'i'; };
template<> struct __em_asm_sig<wchar_t> { static const char value = 'i'; };
template<> struct __em_asm_sig<long long> { static const char value = 'j'; };
template<> struct __em_asm_sig<unsigned long long> { static const char value = 'j'; };
#if __wasm64__
template<typename T> struct __em_asm_sig<T*> { static const char value = 'p'; };
#else
template<typename T> struct __em_asm_sig<T*> { static const char value = 'i'; };
#endif

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

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

// Runs the given JavaScript code on the calling thread (synchronously), and returns an pointer back.
// On wasm32 this is the same as emscripten_asm_const_int but on wasm64 it returns an i64.
#define EM_ASM_PTR(code, ...) emscripten_asm_const_ptr(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))

// Runs the given JavaScript code on the calling thread (synchronously), and returns a double back.
#define EM_ASM_DOUBLE(code, ...) emscripten_asm_const_double(CODE_EXPR(#code) _EM_ASM_PREP_ARGS(__VA_ARGS__))

Expand Down
21 changes: 21 additions & 0 deletions tests/core/test_em_asm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,27 @@ int main() {
printf("BEGIN\n");
EM_ASM({ out("no args works"); });

unsigned long p = 8;
EM_ASM({
console.log("int types:");
out(" char : " + $0);
out(" signed char : " + $1);
out("unsigned char : " + $2);
out(" short: " + $3);
out(" signed short: " + $4);
out("unsigned short: " + $5);
out(" int : " + $6);
out(" signed int : " + $7);
out("unsigned int : " + $8);
out(" long : " + $9);
out(" signed long : " + $10);
out("unsigned long : " + $11);
out(" terminator: " + $12);
}, (char)1, (signed char)2, (unsigned char)3,
(short)4, (signed short)5, (unsigned short)6,
(int)7, (signed int)8, (unsigned int)9,
(long)10, (signed long)11, (unsigned long)12, 42);

// The following two lines are deprecated, test them still.
printf(" EM_ASM_INT_V returned: %d\n", EM_ASM_INT_V({ out("no args returning int"); return 12; }));
printf(" EM_ASM_DOUBLE_V returned: %f\n", EM_ASM_DOUBLE_V({ out("no args returning double"); return 12.25; }));
Expand Down
14 changes: 14 additions & 0 deletions tests/core/test_em_asm.out
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
BEGIN
no args works
int types:
char : 1
signed char : 2
unsigned char : 3
short: 4
signed short: 5
unsigned short: 6
int : 7
signed int : 8
unsigned int : 9
long : 10
signed long : 11
unsigned long : 12
terminator: 42
no args returning int
EM_ASM_INT_V returned: 12
no args returning double
Expand Down
9 changes: 8 additions & 1 deletion tests/core/test_em_asm_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,25 @@ int main(int argc, char **argv) {
// Promotions of arrays, function/member pointers and objects implicitly
// convertible to numbers are excluded because they will not be translated
// to corresponding JS objects.
#define TEST_TYPE(type, value) EM_ASM({console.log(#type, $0);}, (type)(value));
#define TEST_TYPE(type, value) EM_ASM({console.log(#type, Number($0));}, (type)(value));
TEST_TYPE(int*, 0);
TEST_TYPE(float, 1.5f);
TEST_TYPE(double, 2.5);

TEST_TYPE(char, 10);
TEST_TYPE(signed char, -10);
TEST_TYPE(unsigned char, 10);

TEST_TYPE(short, -20);
TEST_TYPE(signed short, -20);
TEST_TYPE(unsigned short, 20);

TEST_TYPE(int, -30);
TEST_TYPE(signed int, -30);
TEST_TYPE(unsigned int, 30);

TEST_TYPE(long, -40);
TEST_TYPE(signed long, -40);
TEST_TYPE(unsigned long, 40);

struct WithBitField w;
Expand Down
3 changes: 3 additions & 0 deletions tests/core/test_em_asm_types.out
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ char 10
signed char -10
unsigned char 10
short -20
signed short -20
unsigned short 20
int -30
signed int -30
unsigned int 30
long -40
signed long -40
unsigned long 40
bit field 3
bool 1
Expand Down
4 changes: 4 additions & 0 deletions tests/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -2102,6 +2102,8 @@ def test_inlinejs4(self):

def test_em_asm(self):
self.do_core_test('test_em_asm.cpp')

def test_em_asm_c(self):
self.emcc_args.append('-std=gnu89')
self.do_core_test('test_em_asm.cpp', force_c=True)

Expand Down Expand Up @@ -2139,6 +2141,8 @@ def test_em_asm_unicode(self):

def test_em_asm_types(self):
self.do_core_test('test_em_asm_types.cpp')

def test_em_asm_types_c(self):
self.do_core_test('test_em_asm_types.cpp', force_c=True)

def test_em_asm_unused_arguments(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/test_other.py
Original file line number Diff line number Diff line change
Expand Up @@ -6761,7 +6761,7 @@ def test_memory_growth_noasm(self):
assert 'use asm' not in src

def test_EM_ASM_i64(self):
expected = 'i64 arguments to ASM_JS function are not available without WASM_BIGINT'
expected = 'Invalid character 106("j") in readAsmConstArgs!'
self.do_runf(test_file('other/test_em_asm_i64.cpp'),
expected_output=expected,
assert_returncode=NON_ZERO)
Expand Down