Skip to content

Commit bbcd4c8

Browse files
committed
sketch out more thorough impl of Runtime and Context wrappers
1 parent 876ec54 commit bbcd4c8

File tree

11 files changed

+757
-213
lines changed

11 files changed

+757
-213
lines changed

c/interface.c

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@
6969
*/
7070
#define MaybeAsync(T) T
7171

72+
#define JSVoid void
73+
7274
void qts_log(char *msg) {
7375
fputs(PKG, stderr);
7476
fputs(msg, stderr);
@@ -189,6 +191,54 @@ void QTS_RuntimeDisableInterruptHandler(JSRuntime *rt) {
189191
JS_SetInterruptHandler(rt, NULL, NULL);
190192
}
191193

194+
/**
195+
* Module loading
196+
*/
197+
typedef JSModuleDef *QTS_C_To_HostLoadModuleFunc(JSRuntime *rt, JSContext *ctx, const char *module_name);
198+
QTS_C_To_HostLoadModuleFunc *bound_load_module = NULL;
199+
200+
// See js_module_loader in quickjs/quickjs-libc.c:567
201+
JSModuleDef *qts_load_module(JSContext *ctx, const char *module_name, void *_unused) {
202+
if (bound_load_module == NULL) {
203+
printf(PKG "cannot load module because no QTS_C_To_HostLoadModuleFunc set");
204+
abort();
205+
}
206+
207+
JSRuntime *rt = JS_GetRuntime(ctx);
208+
// TODO: this will need to suspend.
209+
JSModuleDef *result_ptr = (*bound_load_module)(rt, ctx, module_name);
210+
return result_ptr;
211+
}
212+
213+
void QTS_SetLoadModuleFunc(QTS_C_To_HostLoadModuleFunc *cb) {
214+
bound_load_module = cb;
215+
}
216+
217+
void QTS_RuntimeEnableModuleLoader(JSRuntime *rt) {
218+
if (bound_load_module == NULL) {
219+
printf(PKG "cannot enable module loader because no QTS_C_To_HostLoadModuleFunc set");
220+
abort();
221+
}
222+
223+
JS_SetModuleLoaderFunc(rt, /* use default name normalizer */ NULL, &qts_load_module, NULL);
224+
}
225+
226+
void QTS_RuntimeDisableModuleLoader(JSRuntime *rt) {
227+
JS_SetModuleLoaderFunc(rt, NULL, NULL, NULL);
228+
}
229+
230+
JSModuleDef *QTS_CompileModule(JSContext *ctx, const char *module_name, HeapChar *module_body) {
231+
JSValue func_val = JS_Eval(ctx, module_body, strlen(module_body), module_name, JS_EVAL_TYPE_MODULE | JS_EVAL_FLAG_COMPILE_ONLY);
232+
if (JS_IsException(func_val)) {
233+
return NULL;
234+
}
235+
// TODO: Is exception ok?
236+
// TODO: set import.meta?
237+
JSModuleDef *module = JS_VALUE_GET_PTR(func_val);
238+
JS_FreeValue(ctx, func_val);
239+
return module;
240+
}
241+
192242
/**
193243
* Limits.
194244
*/
@@ -373,6 +423,10 @@ void QTS_FreeValuePointer(JSContext *ctx, JSValue *value) {
373423
free(value);
374424
}
375425

426+
void QTS_FreeVoidPointer(JSContext *ctx, JSVoid *ptr) {
427+
js_free(ctx, ptr);
428+
}
429+
376430
JSValue *QTS_DupValuePointer(JSContext *ctx, JSValueConst *val) {
377431
return jsvalue_to_heap(JS_DupValue(ctx, *val));
378432
}

generate.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const FFI_TYPES_PATH = process.env.FFI_TYPES_PATH || './ts/ffi-types.ts'
88
const DEBUG = process.env.DEBUG === 'true'
99
const ASYNCIFY = process.env.ASYNCIFY === 'true'
1010

11-
const ASSERT_SYNC_FN = 'assertSync'
11+
// const ASSERT_SYNC_FN = 'assertSync'
1212

1313
const INCLUDE_RE = /^#include.*$/gm
1414
const TYPEDEF_RE = /^\s*typedef\s+(.+)$/gm
@@ -85,7 +85,7 @@ function cTypeToTypescriptType(ctype: string) {
8585
async = true
8686
type = type.slice(MaybeAsync.length, -1)
8787
}
88-
const maybeAsync = (type: string) => (async && ASYNCIFY ? `Promise<${type}>` : type)
88+
const maybeAsync = (type: string) => (async && ASYNCIFY ? `${type} | Promise<${type}>` : type)
8989

9090
// mapping
9191
if (type.includes('char*')) {
@@ -138,22 +138,35 @@ function buildFFI(matches: RegExpExecArray[]) {
138138
JSON.stringify(fn.returnType.ffi),
139139
ffiParams,
140140
]
141-
if (ASYNCIFY && fn.returnType.async) {
141+
if (ASYNCIFY && DEBUG && fn.returnType.async) {
142142
// https://emscripten.org/docs/porting/asyncify.html#usage-with-ccall
143+
// Passing {async:true} to cwrap/ccall will wrap all return values in
144+
// Promise.resolve(...), even if the c code doesn't suspend and returns a
145+
// primitive value.
146+
//
147+
// When compiled with -s ASSERTIONS=1, Emscripten will throw if the
148+
// function suspends and {async: true} wasn't passed.
149+
//
150+
// However, we'd like to avoid Promise/async overhead if the call can
151+
// return a primitive value directly. So, we compile in {async:true}
152+
// only in DEBUG mode, where assertions are enabled.
153+
//
154+
// Then we rely on our type system to ensure our code supports both
155+
// primitive and promise-wrapped return values in production mode.
143156
cwrapArgs.push('{ async: true }')
144157
}
145158
let cwrap = `this.module.cwrap(${cwrapArgs.join(', ')})`
146-
if (DEBUG && ASYNCIFY && !fn.returnType.async) {
147-
cwrap = `${ASSERT_SYNC_FN}(${cwrap})`
148-
}
159+
// if (DEBUG && ASYNCIFY && !fn.returnType.async) {
160+
// cwrap = `${ASSERT_SYNC_FN}(${cwrap})`
161+
// }
149162
return ` ${fn.functionName}: ${typescriptFnType} =\n ${cwrap}`
150163
})
151164

152165
const ffiTypes = fs.readFileSync(FFI_TYPES_PATH, 'utf-8')
153166
const importFromFfiTypes = matchAll(TS_EXPORT_TYPE_RE, ffiTypes).map(match => match[1])
154-
if (DEBUG && ASYNCIFY) {
155-
importFromFfiTypes.push(ASSERT_SYNC_FN)
156-
}
167+
// if (DEBUG && ASYNCIFY) {
168+
// importFromFfiTypes.push(ASSERT_SYNC_FN)
169+
// }
157170

158171
const ffiClassName = ASYNCIFY ? 'QuickJSAsyncFFI' : 'QuickJSFFI'
159172
const moduleTypeName = ASYNCIFY ? 'QuickJSAsyncEmscriptenModule' : 'QuickJSEmscriptenModule'

ts/ffi-asyncify.ts

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ import { QuickJSAsyncEmscriptenModule } from './emscripten-types'
33
import {
44
JSRuntimePointer,
55
JSContextPointer,
6+
JSModuleDefPointer,
67
JSValuePointer,
78
JSValueConstPointer,
89
JSValuePointerPointer,
910
JSValueConstPointerPointer,
1011
QTS_C_To_HostCallbackFuncPointer,
1112
QTS_C_To_HostInterruptFuncPointer,
13+
QTS_C_To_HostLoadModuleFuncPointer,
1214
HeapCharPointer,
15+
JSVoidPointer,
1316
} from './ffi-types'
1417

1518
/**
@@ -67,6 +70,28 @@ export class QuickJSAsyncFFI {
6770
rt: JSRuntimePointer
6871
) => void = this.module.cwrap('QTS_RuntimeDisableInterruptHandler', null, ['number'])
6972

73+
QTS_SetLoadModuleFunc: (
74+
cb: QTS_C_To_HostLoadModuleFuncPointer
75+
) => void = this.module.cwrap('QTS_SetLoadModuleFunc', null, ['number'])
76+
77+
QTS_RuntimeEnableModuleLoader: (
78+
rt: JSRuntimePointer
79+
) => void = this.module.cwrap('QTS_RuntimeEnableModuleLoader', null, ['number'])
80+
81+
QTS_RuntimeDisableModuleLoader: (
82+
rt: JSRuntimePointer
83+
) => void = this.module.cwrap('QTS_RuntimeDisableModuleLoader', null, ['number'])
84+
85+
QTS_CompileModule: (
86+
ctx: JSContextPointer,
87+
module_name: string,
88+
module_body: HeapCharPointer
89+
) => JSModuleDefPointer = this.module.cwrap('QTS_CompileModule', 'number', [
90+
'number',
91+
'string',
92+
'number',
93+
])
94+
7095
QTS_RuntimeSetMemoryLimit: (
7196
rt: JSRuntimePointer,
7297
limit: number
@@ -113,6 +138,11 @@ export class QuickJSAsyncFFI {
113138
value: JSValuePointer
114139
) => void = this.module.cwrap('QTS_FreeValuePointer', null, ['number', 'number'])
115140

141+
QTS_FreeVoidPointer: (
142+
ctx: JSContextPointer,
143+
ptr: JSVoidPointer
144+
) => void = this.module.cwrap('QTS_FreeVoidPointer', null, ['number', 'number'])
145+
116146
QTS_DupValuePointer: (
117147
ctx: JSContextPointer,
118148
val: JSValuePointer | JSValueConstPointer
@@ -164,7 +194,7 @@ export class QuickJSAsyncFFI {
164194
QTS_ExecutePendingJob: (
165195
rt: JSRuntimePointer,
166196
maxJobsToExecute: number
167-
) => Promise<JSValuePointer> = this.module.cwrap(
197+
) => JSValuePointer | Promise<JSValuePointer> = this.module.cwrap(
168198
'QTS_ExecutePendingJob',
169199
'number',
170200
['number', 'number'],
@@ -175,7 +205,7 @@ export class QuickJSAsyncFFI {
175205
ctx: JSContextPointer,
176206
this_val: JSValuePointer | JSValueConstPointer,
177207
prop_name: JSValuePointer | JSValueConstPointer
178-
) => Promise<JSValuePointer> = this.module.cwrap(
208+
) => JSValuePointer | Promise<JSValuePointer> = this.module.cwrap(
179209
'QTS_GetProp',
180210
'number',
181211
['number', 'number', 'number'],
@@ -187,7 +217,7 @@ export class QuickJSAsyncFFI {
187217
this_val: JSValuePointer | JSValueConstPointer,
188218
prop_name: JSValuePointer | JSValueConstPointer,
189219
prop_value: JSValuePointer | JSValueConstPointer
190-
) => Promise<void> = this.module.cwrap(
220+
) => void | Promise<void> = this.module.cwrap(
191221
'QTS_SetProp',
192222
null,
193223
['number', 'number', 'number', 'number'],
@@ -222,7 +252,7 @@ export class QuickJSAsyncFFI {
222252
this_obj: JSValuePointer | JSValueConstPointer,
223253
argc: number,
224254
argv_ptrs: JSValueConstPointerPointer
225-
) => Promise<JSValuePointer> = this.module.cwrap(
255+
) => JSValuePointer | Promise<JSValuePointer> = this.module.cwrap(
226256
'QTS_Call',
227257
'number',
228258
['number', 'number', 'number', 'number', 'number'],
@@ -237,15 +267,15 @@ export class QuickJSAsyncFFI {
237267
QTS_Dump: (
238268
ctx: JSContextPointer,
239269
obj: JSValuePointer | JSValueConstPointer
240-
) => Promise<string> = this.module.cwrap('QTS_Dump', 'string', ['number', 'number'], {
270+
) => string | Promise<string> = this.module.cwrap('QTS_Dump', 'string', ['number', 'number'], {
241271
async: true,
242272
})
243273

244274
QTS_Eval: (
245275
ctx: JSContextPointer,
246276
js_code: HeapCharPointer,
247277
filename: string
248-
) => Promise<JSValuePointer> = this.module.cwrap(
278+
) => JSValuePointer | Promise<JSValuePointer> = this.module.cwrap(
249279
'QTS_Eval',
250280
'number',
251281
['number', 'number', 'string'],

ts/ffi-types.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ export type JSRuntimePointer = Pointer<'JSRuntime'>
1616
*/
1717
export type JSContextPointer = Pointer<'JSContext'>
1818

19+
/**
20+
* `JSModuleDef*`.
21+
*/
22+
export type JSModuleDefPointer = Pointer<'JSModuleDef'>
23+
1924
/**
2025
* `JSValue*`.
2126
* See [[JSValue]].
@@ -53,12 +58,22 @@ export type QTS_C_To_HostCallbackFuncPointer = Pointer<'C_To_HostCallbackFunc'>
5358
*/
5459
export type QTS_C_To_HostInterruptFuncPointer = Pointer<'C_To_HostInterruptFunc'>
5560

61+
/**
62+
* Used internally for C-to-Javascript module loading.
63+
*/
64+
export type QTS_C_To_HostLoadModuleFuncPointer = Pointer<'C_To_HostLoadModuleFunc'>
65+
5666
/**
5767
* Used internally for Javascript-to-C calls that may contain strings too large
5868
* for the Emscripten stack.
5969
*/
6070
export type HeapCharPointer = Pointer<'char'>
6171

72+
/**
73+
* Opaque pointer that was allocated by js_malloc.
74+
*/
75+
export type JSVoidPointer = Pointer<any>
76+
6277
export function assertSync<Args extends any[], R>(fn: (...args: Args) => R): (...args: Args) => R {
6378
return function mustBeSync(...args: Args): R {
6479
const result = fn(...args)

ts/ffi.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// This file generated by "generate.ts ffi" in the root of the repo.
22
import { QuickJSEmscriptenModule } from "./emscripten-types"
3-
import { JSRuntimePointer, JSContextPointer, JSValuePointer, JSValueConstPointer, JSValuePointerPointer, JSValueConstPointerPointer, QTS_C_To_HostCallbackFuncPointer, QTS_C_To_HostInterruptFuncPointer, HeapCharPointer } from "./ffi-types"
3+
import { JSRuntimePointer, JSContextPointer, JSModuleDefPointer, JSValuePointer, JSValueConstPointer, JSValuePointerPointer, JSValueConstPointerPointer, QTS_C_To_HostCallbackFuncPointer, QTS_C_To_HostInterruptFuncPointer, QTS_C_To_HostLoadModuleFuncPointer, HeapCharPointer, JSVoidPointer } from "./ffi-types"
44

55
/**
66
* Low-level FFI bindings to QuickJS's Emscripten module.
@@ -36,6 +36,18 @@ export class QuickJSFFI {
3636
QTS_RuntimeDisableInterruptHandler: (rt: JSRuntimePointer) => void =
3737
this.module.cwrap("QTS_RuntimeDisableInterruptHandler", null, ["number"])
3838

39+
QTS_SetLoadModuleFunc: (cb: QTS_C_To_HostLoadModuleFuncPointer) => void =
40+
this.module.cwrap("QTS_SetLoadModuleFunc", null, ["number"])
41+
42+
QTS_RuntimeEnableModuleLoader: (rt: JSRuntimePointer) => void =
43+
this.module.cwrap("QTS_RuntimeEnableModuleLoader", null, ["number"])
44+
45+
QTS_RuntimeDisableModuleLoader: (rt: JSRuntimePointer) => void =
46+
this.module.cwrap("QTS_RuntimeDisableModuleLoader", null, ["number"])
47+
48+
QTS_CompileModule: (ctx: JSContextPointer, module_name: string, module_body: HeapCharPointer) => JSModuleDefPointer =
49+
this.module.cwrap("QTS_CompileModule", "number", ["number","string","number"])
50+
3951
QTS_RuntimeSetMemoryLimit: (rt: JSRuntimePointer, limit: number) => void =
4052
this.module.cwrap("QTS_RuntimeSetMemoryLimit", null, ["number","number"])
4153

@@ -72,6 +84,9 @@ export class QuickJSFFI {
7284
QTS_FreeValuePointer: (ctx: JSContextPointer, value: JSValuePointer) => void =
7385
this.module.cwrap("QTS_FreeValuePointer", null, ["number","number"])
7486

87+
QTS_FreeVoidPointer: (ctx: JSContextPointer, ptr: JSVoidPointer) => void =
88+
this.module.cwrap("QTS_FreeVoidPointer", null, ["number","number"])
89+
7590
QTS_DupValuePointer: (ctx: JSContextPointer, val: JSValuePointer | JSValueConstPointer) => JSValuePointer =
7691
this.module.cwrap("QTS_DupValuePointer", "number", ["number","number"])
7792

0 commit comments

Comments
 (0)