Skip to content

Commit 8a6afb2

Browse files
justjakeclaude
andauthored
Update to emscripten 5.0.1 (justjake#242)
- Update emscripten version from 3.1.65 to 5.0.1 - Fix exported runtime methods for emscripten 5.0.1 compatibility: - Remove ___lsan_do_recoverable_leak_check (not a runtime method) - Add HEAPU8 and HEAP8 for memory access - Add separate exportedRuntimeMethods.asyncify.json with Asyncify export - Conditionally use asyncify exports only for asyncify builds - Fix asyncify function naming: remove underscore prefix from ASYNCIFY_REMOVE symbols to match emscripten 5.0.1 naming conventions - Fix bug in QTS_GetOwnPropertyNames: use total_props instead of uninitialized *out_len for malloc size calculation Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 57fb97c commit 8a6afb2

31 files changed

Lines changed: 411 additions & 83 deletions

File tree

CLAUDE.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# quickjs-emscripten
2+
3+
## Package Manager
4+
5+
Use `corepack yarn` to run yarn commands, e.g.:
6+
7+
- `corepack yarn install`
8+
- `corepack yarn build`
9+
- `corepack yarn build:ts`
10+
11+
## Building Variants
12+
13+
Variants are WASM/JS builds of QuickJS with different configurations. They are generated by `scripts/prepareVariants.ts`.
14+
15+
To build a variant:
16+
17+
```bash
18+
cd packages/variant-quickjs-<name>
19+
make # builds the C code with emscripten
20+
corepack yarn build:ts # builds the TypeScript wrapper
21+
```
22+
23+
## Emscripten
24+
25+
- Most variants use the default emscripten version (defined in `scripts/prepareVariants.ts` as `DEFAULT_EMSCRIPTEN_VERSION`)
26+
- The asmjs variant uses an older emscripten version (`ASMJS_EMSCRIPTEN_VERSION`) to avoid newer browser APIs
27+
- Emscripten runs via Docker when the local version doesn't match; see `scripts/emcc.sh`
28+
29+
## Key Files
30+
31+
- `scripts/prepareVariants.ts` - Generates all variant packages from templates
32+
- `scripts/generate.ts` - Generates FFI bindings and symbols
33+
- `templates/Variant.mk` - Makefile template for variants
34+
- `c/interface.c` - C interface to QuickJS exposed to JavaScript

c/interface.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,7 @@ MaybeAsync(JSValue *) QTS_GetOwnPropertyNames(JSContext *ctx, JSValue ***out_ptr
656656
}
657657
return jsvalue_to_heap(JS_GetException(ctx));
658658
}
659-
*out_ptrs = malloc(sizeof(JSValue) * *out_len);
659+
*out_ptrs = malloc(sizeof(JSValue) * total_props);
660660
for (int i = 0; i < total_props; i++) {
661661
JSAtom atom = tab[i].atom;
662662

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
["cwrap", "stringToUTF8", "lengthBytesUTF8", "UTF8ToString", "HEAPU8", "HEAP8", "Asyncify"]

exportedRuntimeMethods.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
["cwrap", "stringToUTF8", "lengthBytesUTF8", "UTF8ToString", "___lsan_do_recoverable_leak_check"]
1+
["cwrap", "stringToUTF8", "lengthBytesUTF8", "UTF8ToString", "HEAPU8", "HEAP8"]

packages/quickjs-emscripten/src/quickjs-in-quickjs-node-test.mts

Lines changed: 114 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,11 @@ import assert from "node:assert"
77
import { getQuickJS } from "./index.js"
88
import type { JSModuleLoader, JSModuleNormalizer, QuickJSHandle, QuickJSContext } from "./index.js"
99

10-
const DEBUG = false
10+
const DEBUG = true
1111

1212
const ttyLog = (...args: unknown[]) => {
1313
if (DEBUG) {
14-
const fd = fs.openSync("/dev/tty", "w")
15-
fs.writeSync(fd, util.format(...args) + "\n")
16-
fs.closeSync(fd)
14+
console.error("[DEBUG]", util.format(...args))
1715
}
1816
}
1917

@@ -120,7 +118,7 @@ class QuickJSNodeModuleLoader {
120118
}
121119
}
122120

123-
test("quickjs-for-quickjs", () => {
121+
test("quickjs-for-quickjs", async () => {
124122
const moduleLoader = new QuickJSNodeModuleLoader()
125123
moduleLoader.mountImport({
126124
hostImport: "quickjs-emscripten-core",
@@ -143,20 +141,124 @@ test("quickjs-for-quickjs", () => {
143141
const logs: any[] = []
144142
addConsoleGlobal(context, logs)
145143

146-
ttyLog("hi")
144+
ttyLog("=== Starting quickjs-for-quickjs test ===")
147145

146+
// First, let's test if simple async functions work
147+
ttyLog("--- Test 1: Simple async function ---")
148+
const simpleAsyncResult = context.evalCode(
149+
`
150+
globalThis.simpleTest = (async () => {
151+
console.log('async step 1')
152+
await Promise.resolve()
153+
console.log('async step 2')
154+
return 42
155+
})()
156+
`,
157+
"/simple-async.mjs",
158+
)
159+
context.unwrapResult(simpleAsyncResult).dispose()
160+
161+
// Execute pending jobs and see how many were executed
162+
let jobResult = runtime.executePendingJobs()
163+
ttyLog("After simple async, executePendingJobs result:", jobResult)
164+
ttyLog("Logs so far:", logs)
165+
166+
// Check the Promise state
167+
const simpleTestHandle = context.getProp(context.global, "simpleTest")
168+
const simpleTestState = context.getPromiseState(simpleTestHandle)
169+
ttyLog("simpleTest Promise state:", simpleTestState)
170+
simpleTestHandle.dispose()
171+
172+
// Now test the full scenario with more logging
173+
ttyLog("--- Test 2: Full quickjs-for-quickjs ---")
148174
const result = context.evalCode(
149175
`
150176
import { newQuickJSWASMModuleFromVariant } from 'quickjs-emscripten-core/index.mjs'
151177
import variant from '@jitl/quickjs-asmjs-mjs-release-sync/index.mjs'
152-
globalThis.done = newQuickJSWASMModuleFromVariant(variant).then(QuickJS => {
153-
const result = QuickJS.evalCode('1+2')
154-
console.log('inner result', result)
155-
})
178+
179+
console.log('starting main code')
180+
console.log('variant type:', typeof variant)
181+
console.log('variant keys:', Object.keys(variant))
182+
183+
globalThis.done = (async () => {
184+
try {
185+
console.log('async function started')
186+
187+
// Test what the variant loader returns
188+
console.log('calling variant.importModuleLoader...')
189+
const loaderPromise = variant.importModuleLoader()
190+
console.log('importModuleLoader returned:', typeof loaderPromise)
191+
192+
const loader = await loaderPromise
193+
console.log('loader awaited, type:', typeof loader)
194+
195+
console.log('calling loader()...')
196+
const modulePromise = loader()
197+
console.log('loader() returned:', typeof modulePromise)
198+
199+
const wasmModule = await modulePromise
200+
console.log('wasmModule awaited, type:', typeof wasmModule)
201+
console.log('wasmModule.type:', wasmModule?.type)
202+
203+
// Now create the full QuickJS instance
204+
console.log('calling newQuickJSWASMModuleFromVariant...')
205+
const QuickJS = await newQuickJSWASMModuleFromVariant(variant)
206+
console.log('QuickJS created:', typeof QuickJS)
207+
208+
const evalResult = QuickJS.evalCode('1+2')
209+
console.log('inner result', evalResult)
210+
211+
return evalResult
212+
} catch (error) {
213+
console.log('ERROR in async function:', String(error))
214+
console.log('ERROR stack:', error?.stack || 'no stack')
215+
throw error
216+
}
217+
})()
218+
219+
console.log('done promise created:', typeof globalThis.done)
156220
`,
157221
"/script.mjs",
158222
)
159223
context.unwrapResult(result).dispose()
160-
runtime.executePendingJobs()
161-
assert.deepEqual(logs, [["inner result", 3]])
224+
225+
// Execute pending jobs multiple times with logging
226+
ttyLog("After evalCode, executing pending jobs...")
227+
for (let i = 0; i < 10; i++) {
228+
jobResult = runtime.executePendingJobs()
229+
ttyLog(`executePendingJobs iteration ${i}:`, jobResult)
230+
231+
// Check done Promise state
232+
const doneHandle = context.getProp(context.global, "done")
233+
const doneState = context.getPromiseState(doneHandle)
234+
ttyLog(`done Promise state at iteration ${i}:`, doneState)
235+
236+
if (doneState.type !== "pending") {
237+
ttyLog("Promise resolved/rejected, stopping loop")
238+
if (doneState.type === "fulfilled") {
239+
const value = context.dump(doneState.value)
240+
ttyLog("Fulfilled with:", value)
241+
doneState.value.dispose()
242+
} else if (doneState.type === "rejected") {
243+
const error = context.dump(doneState.error)
244+
ttyLog("Rejected with:", error)
245+
doneState.error.dispose()
246+
}
247+
doneHandle.dispose()
248+
break
249+
}
250+
doneHandle.dispose()
251+
252+
// Small delay to let any host promises settle
253+
await new Promise((resolve) => setTimeout(resolve, 10))
254+
}
255+
256+
ttyLog("=== Final logs ===")
257+
ttyLog("Logs array:", logs)
258+
259+
// For now, just check that we eventually see "inner result"
260+
const innerResultLog = logs.find((log: any[]) => log[0] === "inner result")
261+
ttyLog("Looking for 'inner result' log:", innerResultLog)
262+
assert.ok(innerResultLog, "Should have logged 'inner result'")
263+
assert.strictEqual(innerResultLog[1], 3, "Inner result should be 3")
162264
})

packages/variant-quickjs-asmjs-mjs-release-sync/Makefile

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tools
2-
EMSDK_VERSION=3.1.65
3-
EMSDK_DOCKER_IMAGE=emscripten/emsdk:3.1.65
2+
EMSDK_VERSION=3.1.43
3+
EMSDK_DOCKER_IMAGE=emscripten/emsdk:$(EMSDK_VERSION)
44
EMCC_SRC=../../scripts/emcc.sh
55
EMCC=EMSDK_VERSION=$(EMSDK_VERSION) EMSDK_DOCKER_IMAGE=$(EMSDK_DOCKER_IMAGE) EMSDK_PROJECT_ROOT=$(REPO_ROOT) EMSDK_DOCKER_CACHE=$(REPO_ROOT)/emsdk-cache/$(EMSDK_VERSION) $(EMCC_SRC)
66
GENERATE_TS=$(GENERATE_TS_ENV) ../../scripts/generate.ts
@@ -38,7 +38,7 @@ EMCC_EXPORTED_FUNCS+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.json
3838
EMCC_EXPORTED_FUNCS_ASYNCIFY+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.asyncify.json
3939

4040
# Emscripten options
41-
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
41+
# EXPORTED_RUNTIME_METHODS set below after SYNC is defined
4242
CFLAGS_WASM+=-s MODULARIZE=1
4343
CFLAGS_WASM+=-s IMPORTED_MEMORY=1 # Allow passing WASM memory to Emscripten
4444
CFLAGS_WASM+=-s EXPORT_NAME=QuickJSRaw
@@ -69,6 +69,14 @@ CFLAGS_BROWSER+=-s EXPORT_ES6=1
6969

7070
# VARIANT
7171
SYNC=SYNC
72+
73+
# Set EXPORTED_RUNTIME_METHODS based on sync mode (Asyncify only available in asyncify builds)
74+
ifeq ($(SYNC),ASYNCIFY)
75+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.asyncify.json
76+
else
77+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
78+
endif
79+
7280
CFLAGS_WASM_BROWSER=$(CFLAGS_WASM)
7381

7482
# Emscripten options - variant & target specific
@@ -80,6 +88,7 @@ CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-extension.js
8088
CFLAGS_WASM+=--pre-js $(TEMPLATES)/pre-wasmMemory.js
8189
CFLAGS_WASM+=-s WASM=0
8290
CFLAGS_WASM+=-s SINGLE_FILE=1
91+
CFLAGS_WASM+=-s WASM_ASYNC_COMPILATION=0
8392

8493
CFLAGS_MJS+=-s ENVIRONMENT=web,worker,node
8594

packages/variant-quickjs-asmjs-mjs-release-sync/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Full variant JSON description:
4848
"syncMode": "sync",
4949
"description": "Compiled to pure Javascript, no WebAssembly required.",
5050
"emscriptenInclusion": "asmjs",
51+
"emscriptenVersion": "3.1.43",
5152
"exports": {
5253
"import": {
5354
"emscriptenEnvironment": ["web", "worker", "node"]
@@ -67,6 +68,7 @@ Variant-specific Emscripten build flags:
6768
"--pre-js $(TEMPLATES)/pre-extension.js",
6869
"--pre-js $(TEMPLATES)/pre-wasmMemory.js",
6970
"-s WASM=0",
70-
"-s SINGLE_FILE=1"
71+
"-s SINGLE_FILE=1",
72+
"-s WASM_ASYNC_COMPILATION=0"
7173
]
7274
```

packages/variant-quickjs-ng-wasmfile-debug-asyncify/Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tools
2-
EMSDK_VERSION=3.1.65
3-
EMSDK_DOCKER_IMAGE=emscripten/emsdk:3.1.65
2+
EMSDK_VERSION=5.0.1
3+
EMSDK_DOCKER_IMAGE=emscripten/emsdk:$(EMSDK_VERSION)
44
EMCC_SRC=../../scripts/emcc.sh
55
EMCC=EMSDK_VERSION=$(EMSDK_VERSION) EMSDK_DOCKER_IMAGE=$(EMSDK_DOCKER_IMAGE) EMSDK_PROJECT_ROOT=$(REPO_ROOT) EMSDK_DOCKER_CACHE=$(REPO_ROOT)/emsdk-cache/$(EMSDK_VERSION) $(EMCC_SRC)
66
GENERATE_TS=$(GENERATE_TS_ENV) ../../scripts/generate.ts
@@ -38,7 +38,7 @@ EMCC_EXPORTED_FUNCS+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.json
3838
EMCC_EXPORTED_FUNCS_ASYNCIFY+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.asyncify.json
3939

4040
# Emscripten options
41-
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
41+
# EXPORTED_RUNTIME_METHODS set below after SYNC is defined
4242
CFLAGS_WASM+=-s MODULARIZE=1
4343
CFLAGS_WASM+=-s IMPORTED_MEMORY=1 # Allow passing WASM memory to Emscripten
4444
CFLAGS_WASM+=-s EXPORT_NAME=QuickJSRaw
@@ -69,6 +69,14 @@ CFLAGS_BROWSER+=-s EXPORT_ES6=1
6969

7070
# VARIANT
7171
SYNC=ASYNCIFY
72+
73+
# Set EXPORTED_RUNTIME_METHODS based on sync mode (Asyncify only available in asyncify builds)
74+
ifeq ($(SYNC),ASYNCIFY)
75+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.asyncify.json
76+
else
77+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
78+
endif
79+
7280
CFLAGS_WASM_BROWSER=$(CFLAGS_WASM)
7381

7482
# Emscripten options - variant & target specific

packages/variant-quickjs-ng-wasmfile-debug-sync/Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tools
2-
EMSDK_VERSION=3.1.65
3-
EMSDK_DOCKER_IMAGE=emscripten/emsdk:3.1.65
2+
EMSDK_VERSION=5.0.1
3+
EMSDK_DOCKER_IMAGE=emscripten/emsdk:$(EMSDK_VERSION)
44
EMCC_SRC=../../scripts/emcc.sh
55
EMCC=EMSDK_VERSION=$(EMSDK_VERSION) EMSDK_DOCKER_IMAGE=$(EMSDK_DOCKER_IMAGE) EMSDK_PROJECT_ROOT=$(REPO_ROOT) EMSDK_DOCKER_CACHE=$(REPO_ROOT)/emsdk-cache/$(EMSDK_VERSION) $(EMCC_SRC)
66
GENERATE_TS=$(GENERATE_TS_ENV) ../../scripts/generate.ts
@@ -38,7 +38,7 @@ EMCC_EXPORTED_FUNCS+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.json
3838
EMCC_EXPORTED_FUNCS_ASYNCIFY+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.asyncify.json
3939

4040
# Emscripten options
41-
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
41+
# EXPORTED_RUNTIME_METHODS set below after SYNC is defined
4242
CFLAGS_WASM+=-s MODULARIZE=1
4343
CFLAGS_WASM+=-s IMPORTED_MEMORY=1 # Allow passing WASM memory to Emscripten
4444
CFLAGS_WASM+=-s EXPORT_NAME=QuickJSRaw
@@ -69,6 +69,14 @@ CFLAGS_BROWSER+=-s EXPORT_ES6=1
6969

7070
# VARIANT
7171
SYNC=SYNC
72+
73+
# Set EXPORTED_RUNTIME_METHODS based on sync mode (Asyncify only available in asyncify builds)
74+
ifeq ($(SYNC),ASYNCIFY)
75+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.asyncify.json
76+
else
77+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
78+
endif
79+
7280
CFLAGS_WASM_BROWSER=$(CFLAGS_WASM)
7381

7482
# Emscripten options - variant & target specific

packages/variant-quickjs-ng-wasmfile-release-asyncify/Makefile

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Tools
2-
EMSDK_VERSION=3.1.65
3-
EMSDK_DOCKER_IMAGE=emscripten/emsdk:3.1.65
2+
EMSDK_VERSION=5.0.1
3+
EMSDK_DOCKER_IMAGE=emscripten/emsdk:$(EMSDK_VERSION)
44
EMCC_SRC=../../scripts/emcc.sh
55
EMCC=EMSDK_VERSION=$(EMSDK_VERSION) EMSDK_DOCKER_IMAGE=$(EMSDK_DOCKER_IMAGE) EMSDK_PROJECT_ROOT=$(REPO_ROOT) EMSDK_DOCKER_CACHE=$(REPO_ROOT)/emsdk-cache/$(EMSDK_VERSION) $(EMCC_SRC)
66
GENERATE_TS=$(GENERATE_TS_ENV) ../../scripts/generate.ts
@@ -38,7 +38,7 @@ EMCC_EXPORTED_FUNCS+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.json
3838
EMCC_EXPORTED_FUNCS_ASYNCIFY+=-s EXPORTED_FUNCTIONS=@$(BUILD_WRAPPER)/symbols.asyncify.json
3939

4040
# Emscripten options
41-
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
41+
# EXPORTED_RUNTIME_METHODS set below after SYNC is defined
4242
CFLAGS_WASM+=-s MODULARIZE=1
4343
CFLAGS_WASM+=-s IMPORTED_MEMORY=1 # Allow passing WASM memory to Emscripten
4444
CFLAGS_WASM+=-s EXPORT_NAME=QuickJSRaw
@@ -69,6 +69,14 @@ CFLAGS_BROWSER+=-s EXPORT_ES6=1
6969

7070
# VARIANT
7171
SYNC=ASYNCIFY
72+
73+
# Set EXPORTED_RUNTIME_METHODS based on sync mode (Asyncify only available in asyncify builds)
74+
ifeq ($(SYNC),ASYNCIFY)
75+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.asyncify.json
76+
else
77+
CFLAGS_WASM+=-s EXPORTED_RUNTIME_METHODS=@../../exportedRuntimeMethods.json
78+
endif
79+
7280
CFLAGS_WASM_BROWSER=$(CFLAGS_WASM)
7381

7482
# Emscripten options - variant & target specific

0 commit comments

Comments
 (0)