Skip to content

Commit 00180e5

Browse files
committed
First pass on JS built-ins support
1 parent ebc664e commit 00180e5

File tree

6 files changed

+175
-22
lines changed

6 files changed

+175
-22
lines changed

.github/workflows/test.yml

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ jobs:
2020
outputs:
2121
hosts: ${{ steps.matrix.outputs.hosts }}
2222
containers: ${{ steps.matrix.outputs.containers }}
23+
emscriptens: ${{ steps.matrix.outputs.emscriptens }}
2324

2425
steps:
2526
- name: Define Matrix
@@ -53,9 +54,15 @@ jobs:
5354
20: 'ubuntu-latest',
5455
21: 'ubuntu-latest'
5556
}
57+
emscripten_versions = [
58+
'3.1.74',
59+
'4.0.15',
60+
]
5661
5762
hosts = []
5863
containers=[]
64+
emscriptens=[]
65+
5966
6067
#macOS
6168
for runon, xcode in macos_map.items():
@@ -81,9 +88,14 @@ jobs:
8188
for clang, runon in clang_map.items():
8289
hosts.append({'os': runon, 'compiler': 'clang', 'version': clang, 'jobname': f'Linux - Clang{clang}'})
8390
91+
#emscripten
92+
for emscripten in emscripten_versions:
93+
emscriptens.append({'version': emscripten, 'jobname': f'Emscripten - {emscripten}'})
94+
8495
with open(os.environ['GITHUB_OUTPUT'], 'w') as env:
8596
print('hosts=' + json.dumps(hosts), file=env)
8697
print('containers=' + json.dumps(containers), file=env)
98+
print('emscriptens=' + json.dumps(emscriptens), file=env)
8799
88100
desktop:
89101
needs: define-matrix
@@ -206,7 +218,7 @@ jobs:
206218
- name: Configure
207219
shell: bash
208220
run: |
209-
cmake -S . -B out -DCMAKE_BUILD_TYPE=MinSizeRel
221+
cmake -GNinja -S . -B out -DCMAKE_BUILD_TYPE=MinSizeRel
210222
211223
- name: Build and Test
212224
shell: bash
@@ -297,8 +309,14 @@ jobs:
297309
echo "::endgroup::"
298310
299311
emscripten:
312+
needs: define-matrix
313+
name: ${{ matrix.jobname }}
300314
runs-on: ubuntu-latest
301-
container: emscripten/emsdk:3.1.70
315+
container: emscripten/emsdk:${{ matrix.version }}
316+
strategy:
317+
fail-fast: false
318+
matrix:
319+
include: ${{ fromJSON(needs.define-matrix.outputs.emscriptens) }}
302320

303321
steps:
304322
- name: Checkout
@@ -319,7 +337,7 @@ jobs:
319337
320338
- name: Configure
321339
shell: bash
322-
run: cmake -S . -B out -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=MinSizeRel
340+
run: cmake -GNinja -S . -B out -DCMAKE_TOOLCHAIN_FILE=$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake -DCMAKE_BUILD_TYPE=MinSizeRel
323341

324342

325343
- name: Build and Test

lib/inc/sys_string/impl/platforms/emscripten_javascript.h

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,19 @@
1212

1313
#include <sys_string/impl/util/generic_impl.h>
1414

15+
//Like EM_JS but with inline and extern "C" only on data
16+
#define SYSSTR_EM_JS(ret, c_name, js_name, params, ...) \
17+
ret c_name params EM_IMPORT(js_name); \
18+
extern "C" \
19+
__attribute__((visibility("hidden"))) \
20+
inline void * __em_js_ref_##c_name = (void*)&c_name; \
21+
extern "C" \
22+
EMSCRIPTEN_KEEPALIVE \
23+
__attribute__((section("em_js"), aligned(1))) \
24+
inline char __em_js__##js_name[] = \
25+
#params "<::>" #__VA_ARGS__;
26+
27+
1528
namespace sysstr::util
1629
{
1730
struct emscripten_traits
@@ -33,6 +46,40 @@ namespace sysstr::util
3346

3447
using emscripten_char_access = generic::char_access<emscripten_traits::storage_type, emscripten_traits::size_type>;
3548

49+
#ifdef __wasm_reference_types__
50+
51+
#if SYS_STRING_USE_WASM_JS_STRING
52+
uint32_t js_length(__externref_t str) __attribute__((import_module("wasm:js-string"), import_name("length")));
53+
uint32_t js_charCodeAt(__externref_t str, uint32_t idx) __attribute__((import_module("wasm:js-string"), import_name("charCodeAt")));
54+
#else
55+
56+
SYSSTR_EM_JS(uint32_t, js_length, sysstr_length, (__externref_t str), {
57+
return str.length;
58+
});
59+
60+
SYSSTR_EM_JS(uint32_t, js_charCodeAt, sysstr_charCodeAt, (__externref_t str, uint32_t idx), {
61+
return str.charCodeAt(idx);
62+
});
63+
#endif
64+
65+
SYSSTR_EM_JS(__externref_t, js_UTF16ToString, sysstr_UTF16ToString, (const void * ptr, size_t length), {
66+
let start = (ptr>>1);
67+
if (length > 16 && UTF16Decoder)
68+
return UTF16Decoder.decode(HEAPU16.subarray(start, start + length));
69+
70+
let ret = "";
71+
72+
for (let i = 0; i < length; ++i) {
73+
const codeUnit = HEAPU16[start];
74+
ret += String.fromCharCode(codeUnit);
75+
++start;
76+
}
77+
78+
return ret;
79+
});
80+
81+
#endif
82+
3683
}
3784

3885
namespace sysstr
@@ -59,6 +106,12 @@ namespace sysstr
59106
super(create_buffer(js_str))
60107
{}
61108

109+
#ifdef __wasm_reference_types__
110+
emscripten_storage(__externref_t js_str):
111+
super(create_buffer(js_str))
112+
{}
113+
#endif
114+
62115
#ifdef _MSC_VER
63116
template<has_utf_encoding Char>
64117
emscripten_storage(const Char * str, size_t len):
@@ -78,26 +131,12 @@ namespace sysstr
78131
using super::data;
79132
using super::copy_data;
80133

81-
auto make_js_string() const noexcept -> emscripten::EM_VAL
82-
{
134+
auto make_js_string() const noexcept -> emscripten::EM_VAL {
83135
#pragma clang diagnostic push
84136
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
85137

86138
auto ret = (emscripten::EM_VAL)EM_ASM_PTR({
87-
let ptr = $0;
88-
let length = $1;
89-
if (length > 16 && UTF16Decoder)
90-
return Emval.toHandle(UTF16Decoder.decode(HEAPU8.subarray(ptr, ptr + length * 2)));
91-
92-
let ret = "";
93-
94-
for (let i = 0; i < length; ++i) {
95-
const codeUnit = HEAP16[((ptr)>>1)];
96-
ret += String.fromCharCode(codeUnit);
97-
ptr += 2;
98-
}
99-
100-
return Emval.toHandle(ret);
139+
return Emval.toHandle(sysstr_UTF16ToString($0, $1));
101140
}, data(), size());
102141

103142
return ret;
@@ -106,6 +145,12 @@ namespace sysstr
106145

107146
}
108147

148+
#ifdef __wasm_reference_types__
149+
auto make_js_string_ref() const noexcept -> __externref_t {
150+
return util::js_UTF16ToString(data(), size());
151+
}
152+
#endif
153+
109154
protected:
110155
using super::size;
111156
using super::operator[];
@@ -133,14 +178,29 @@ namespace sysstr
133178
let ptr = $1;
134179
let length = str.length;
135180
for (let i = 0; i < length; ++i) {
136-
HEAP16[((ptr)>>1)] = str.charCodeAt(i);
181+
HEAPU16[((ptr)>>1)] = str.charCodeAt(i);
137182
ptr += 2;
138183
}
139184
}, js_str, ret.data());
140185
return ret;
141186

142187
#pragma clang diagnostic pop
143188
}
189+
190+
#ifdef __wasm_reference_types__
191+
static auto create_buffer(__externref_t js_str) -> buffer {
192+
uint32_t len = util::js_length(js_str);
193+
194+
if (len == 0)
195+
return buffer();
196+
197+
buffer ret(len);
198+
auto ptr = ret.data();
199+
for(uint32_t i = 0; i < len; ++i)
200+
ptr[i] = util::js_charCodeAt(js_str, i);
201+
return ret;
202+
}
203+
#endif
144204
};
145205
}
146206

test/CMakeLists.txt

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,24 @@ FetchContent_MakeAvailable(doctest)
1919

2020
find_package(ICU 67 COMPONENTS uc)
2121

22+
if (EMSCRIPTEN)
23+
message(CHECK_START "Checking if ${NODE_JS_EXECUTABLE} supports wasm:js-string")
24+
25+
execute_process(
26+
COMMAND ${NODE_JS_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/check-wasm-js-string.js
27+
RESULT_VARIABLE NODE_JS_HAS_WASM_JS_STRING_RESULT
28+
)
29+
30+
if (${NODE_JS_HAS_WASM_JS_STRING_RESULT} EQUAL 0)
31+
set(NODE_JS_HAS_WASM_JS_STRING TRUE)
32+
message(CHECK_PASS "Success")
33+
else()
34+
set(NODE_JS_HAS_WASM_JS_STRING FALSE)
35+
message(CHECK_FAIL "Failure")
36+
endif()
37+
38+
endif()
39+
2240
if(${Python3_Development_FOUND} AND ${Python3_Interpreter_FOUND})
2341
include_directories(
2442
SYSTEM
@@ -162,6 +180,15 @@ foreach(LIB_SUFFIX ${LIB_TYPES})
162180
$<$<CXX_COMPILER_ID:GNU>:-Wall;-Wextra;-pedantic>
163181
)
164182

183+
if (EMSCRIPTEN AND STORAGE_SUFFIX STREQUAL "emscr")
184+
target_compile_definitions(test-${TEST_SUFFIX} PRIVATE
185+
SYS_STRING_USE_WASM_JS_STRING=$<IF:$<BOOL:${NODE_JS_HAS_WASM_JS_STRING}>,1,0>
186+
)
187+
target_compile_options(test-${TEST_SUFFIX} PRIVATE
188+
-sMAIN_MODULE=2 -sSTRICT
189+
)
190+
endif()
191+
165192
if ("${CMAKE_CXX_COMPILER_FRONTEND_VARIANT}" STREQUAL "MSVC")
166193
target_compile_options(test-${TEST_SUFFIX}
167194
PRIVATE
@@ -192,10 +219,21 @@ foreach(LIB_SUFFIX ${LIB_TYPES})
192219
endif()
193220

194221
target_link_options(test-${TEST_SUFFIX} PRIVATE
195-
196-
"$<$<PLATFORM_ID:Android>:-Wl,--export-dynamic>"
222+
$<$<PLATFORM_ID:Android>:-Wl,--export-dynamic>
197223
)
198224

225+
if (EMSCRIPTEN AND STORAGE_SUFFIX STREQUAL "emscr")
226+
target_link_options(test-${TEST_SUFFIX} PRIVATE
227+
-sMAIN_MODULE=2
228+
-sSTRICT
229+
-sERROR_ON_UNDEFINED_SYMBOLS=0
230+
-sWARN_ON_UNDEFINED_SYMBOLS=0
231+
--pre-js ${CMAKE_CURRENT_LIST_DIR}/prefix.js
232+
-sINCOMING_MODULE_JS_API=arguments,canvas,monitorRunDependencies,print,setStatus>
233+
)
234+
set_target_properties(test-${TEST_SUFFIX} PROPERTIES LINK_DEPENDS ${CMAKE_CURRENT_LIST_DIR}/prefix.js)
235+
endif()
236+
199237
if (SYS_STRING_COLLECT_COVERAGE)
200238

201239
target_compile_options(test-${TEST_SUFFIX} PRIVATE

test/check-wasm-js-string.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
let bytes = new Uint8Array([
2+
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 2, 23, 1, 14, 119, 97, 115,
3+
109, 58, 106, 115, 45, 115, 116, 114, 105, 110, 103, 4, 99, 97, 115, 116, 0,
4+
0,
5+
]);
6+
7+
process.exit(!WebAssembly.validate(bytes, { builtins: ["js-string"] }) ? 0 : 1);

test/prefix.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
3+
WebAssembly.instantiate = function() {
4+
let originalInstantiate = WebAssembly.instantiate;
5+
return function(binary, imports) {
6+
return originalInstantiate(binary, imports, {
7+
builtins: ["js-string"]
8+
});
9+
}
10+
}();
11+
12+
13+
14+

test/test_javascript.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ using namespace emscripten;
2020

2121
#if !SYS_STRING_USE_GENERIC && !SYS_STRING_USE_PYTHON
2222

23+
EM_JS(__externref_t, make_test_string, (const char * str), {
24+
return UTF8ToString(str);
25+
});
26+
2327
TEST_SUITE("javascript") {
2428

2529
TEST_CASE( "Javascript Conversions" ) {
@@ -52,6 +56,18 @@ using namespace emscripten;
5256
}, handle);
5357
CHECK(result);
5458
}
59+
60+
#if defined(__wasm_reference_types__)
61+
TEST_CASE( "Javascript Conversions with builtins" ) {
62+
63+
auto js_str = make_test_string("абвгдеёжзийклмнопрстуфхцчшщьыъэюя");
64+
65+
CHECK(sys_string(js_str) == S("абвгдеёжзийклмнопрстуфхцчшщьыъэюя"));
66+
67+
auto js_str2 = S("абвгдеёжзийклмнопрстуфхцчшщьыъэюя").make_js_string_ref();
68+
CHECK(sys_string(js_str2) == S("абвгдеёжзийклмнопрстуфхцчшщьыъэюя"));
69+
}
70+
#endif
5571

5672
}
5773

0 commit comments

Comments
 (0)