Skip to content

Commit 5b7087e

Browse files
authored
Add API for C closures
1 parent 00a1cfd commit 5b7087e

File tree

3 files changed

+170
-0
lines changed

3 files changed

+170
-0
lines changed

api-test.c

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@ static JSValue cfuncdata_callback(JSContext *ctx, JSValueConst this_val,
2525
return JS_ThrowTypeError(ctx, "from cfuncdata");
2626
}
2727

28+
static JSValue cclosure_callback(JSContext *ctx, JSValueConst this_val,
29+
int argc, JSValueConst *argv,
30+
int magic, void *func_data)
31+
{
32+
return JS_ThrowTypeError(ctx, "from cclosure");
33+
}
34+
35+
static bool closure_finalized = false;
36+
37+
static void cclosure_opaque_finalize(void *opaque)
38+
{
39+
if ((intptr_t)opaque == 12)
40+
closure_finalized = true;
41+
}
42+
2843
static void cfunctions(void)
2944
{
3045
uint32_t length;
@@ -37,9 +52,13 @@ static void cfunctions(void)
3752
JSValue cfuncdata =
3853
JS_NewCFunctionData2(ctx, cfuncdata_callback, "cfuncdata",
3954
/*length*/1337, /*magic*/0, /*data_len*/0, NULL);
55+
JSValue cclosure =
56+
JS_NewCClosure(ctx, cclosure_callback, "cclosure", cclosure_opaque_finalize,
57+
/*length*/0xC0DE, /*magic*/11, /*opaque*/(void*)12);
4058
JSValue global = JS_GetGlobalObject(ctx);
4159
JS_SetPropertyStr(ctx, global, "cfunc", cfunc);
4260
JS_SetPropertyStr(ctx, global, "cfuncdata", cfuncdata);
61+
JS_SetPropertyStr(ctx, global, "cclosure", cclosure);
4362
JS_FreeValue(ctx, global);
4463

4564
ret = eval(ctx, "cfunc.name");
@@ -70,6 +89,20 @@ static void cfunctions(void)
7089
assert(0 == JS_ToUint32(ctx, &length, ret));
7190
assert(length == 1337);
7291

92+
ret = eval(ctx, "cclosure.name");
93+
assert(!JS_IsException(ret));
94+
assert(JS_IsString(ret));
95+
s = JS_ToCString(ctx, ret);
96+
JS_FreeValue(ctx, ret);
97+
assert(s);
98+
assert(!strcmp(s, "cclosure"));
99+
JS_FreeCString(ctx, s);
100+
ret = eval(ctx, "cclosure.length");
101+
assert(!JS_IsException(ret));
102+
assert(JS_IsNumber(ret));
103+
assert(0 == JS_ToUint32(ctx, &length, ret));
104+
assert(length == 0xC0DE);
105+
73106
ret = eval(ctx, "cfunc()");
74107
assert(JS_IsException(ret));
75108
ret = JS_GetException(ctx);
@@ -104,8 +137,27 @@ static void cfunctions(void)
104137
assert(!strcmp(s, "TypeError: from cfuncdata"));
105138
JS_FreeCString(ctx, s);
106139

140+
ret = eval(ctx, "cclosure()");
141+
assert(JS_IsException(ret));
142+
ret = JS_GetException(ctx);
143+
assert(JS_IsError(ret));
144+
stack = JS_GetPropertyStr(ctx, ret, "stack");
145+
assert(JS_IsString(stack));
146+
s = JS_ToCString(ctx, stack);
147+
JS_FreeValue(ctx, stack);
148+
assert(s);
149+
assert(!strcmp(s, " at cclosure (native)\n at <eval> (<input>:1:1)\n"));
150+
JS_FreeCString(ctx, s);
151+
s = JS_ToCString(ctx, ret);
152+
JS_FreeValue(ctx, ret);
153+
assert(s);
154+
assert(!strcmp(s, "TypeError: from cclosure"));
155+
JS_FreeCString(ctx, s);
156+
107157
JS_FreeContext(ctx);
108158
JS_FreeRuntime(rt);
159+
160+
assert(closure_finalized);
109161
}
110162

111163
#define MAX_TIME 10

quickjs.c

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ enum {
138138
JS_CLASS_BYTECODE_FUNCTION, /* u.func */
139139
JS_CLASS_BOUND_FUNCTION, /* u.bound_function */
140140
JS_CLASS_C_FUNCTION_DATA, /* u.c_function_data_record */
141+
JS_CLASS_C_CLOSURE, /* u.c_closure_record */
141142
JS_CLASS_GENERATOR_FUNCTION, /* u.func */
142143
JS_CLASS_FOR_IN_ITERATOR, /* u.for_in_iterator */
143144
JS_CLASS_REGEXP, /* u.regexp */
@@ -980,6 +981,7 @@ struct JSObject {
980981
void *opaque;
981982
struct JSBoundFunction *bound_function; /* JS_CLASS_BOUND_FUNCTION */
982983
struct JSCFunctionDataRecord *c_function_data_record; /* JS_CLASS_C_FUNCTION_DATA */
984+
struct JSCClosureRecord *c_closure_record; /* JS_CLASS_C_CLOSURE */
983985
struct JSForInIterator *for_in_iterator; /* JS_CLASS_FOR_IN_ITERATOR */
984986
struct JSArrayBuffer *array_buffer; /* JS_CLASS_ARRAY_BUFFER, JS_CLASS_SHARED_ARRAY_BUFFER */
985987
struct JSTypedArray *typed_array; /* JS_CLASS_UINT8C_ARRAY..JS_CLASS_DATAVIEW */
@@ -1343,6 +1345,10 @@ static void js_c_function_data_mark(JSRuntime *rt, JSValueConst val,
13431345
static JSValue js_call_c_function_data(JSContext *ctx, JSValueConst func_obj,
13441346
JSValueConst this_val,
13451347
int argc, JSValueConst *argv, int flags);
1348+
static void js_c_closure_finalizer(JSRuntime *rt, JSValueConst val);
1349+
static JSValue js_call_c_closure(JSContext *ctx, JSValueConst func_obj,
1350+
JSValueConst this_val,
1351+
int argc, JSValueConst *argv, int flags);
13461352
static JSAtom js_symbol_to_atom(JSContext *ctx, JSValueConst val);
13471353
static void add_gc_object(JSRuntime *rt, JSGCObjectHeader *h,
13481354
JSGCObjectTypeEnum type);
@@ -1746,6 +1752,7 @@ static JSClassShortDef const js_std_class_def[] = {
17461752
{ JS_ATOM_Function, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_BYTECODE_FUNCTION */
17471753
{ JS_ATOM_Function, js_bound_function_finalizer, js_bound_function_mark }, /* JS_CLASS_BOUND_FUNCTION */
17481754
{ JS_ATOM_Function, js_c_function_data_finalizer, js_c_function_data_mark }, /* JS_CLASS_C_FUNCTION_DATA */
1755+
{ JS_ATOM_Function, js_c_closure_finalizer, NULL}, /* JS_CLASS_C_CLOSURE */
17491756
{ JS_ATOM_GeneratorFunction, js_bytecode_function_finalizer, js_bytecode_function_mark }, /* JS_CLASS_GENERATOR_FUNCTION */
17501757
{ JS_ATOM_ForInIterator, js_for_in_iterator_finalizer, js_for_in_iterator_mark }, /* JS_CLASS_FOR_IN_ITERATOR */
17511758
{ JS_ATOM_RegExp, js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */
@@ -1870,6 +1877,7 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
18701877

18711878
rt->class_array[JS_CLASS_C_FUNCTION].call = js_call_c_function;
18721879
rt->class_array[JS_CLASS_C_FUNCTION_DATA].call = js_call_c_function_data;
1880+
rt->class_array[JS_CLASS_C_CLOSURE].call = js_call_c_closure;
18731881
rt->class_array[JS_CLASS_BOUND_FUNCTION].call = js_call_bound_function;
18741882
rt->class_array[JS_CLASS_GENERATOR_FUNCTION].call = js_call_generator_function;
18751883
if (init_shape_hash(rt))
@@ -5547,6 +5555,101 @@ static void js_autoinit_mark(JSRuntime *rt, JSProperty *pr,
55475555
mark_func(rt, &js_autoinit_get_realm(pr)->header);
55485556
}
55495557

5558+
typedef struct JSCClosureRecord {
5559+
JSCClosure *func;
5560+
uint16_t length;
5561+
uint16_t magic;
5562+
void *opaque;
5563+
void (*opaque_finalize)(void *opaque);
5564+
} JSCClosureRecord;
5565+
5566+
static void js_c_closure_finalizer(JSRuntime *rt, JSValueConst val)
5567+
{
5568+
JSCClosureRecord *s = JS_GetOpaque(val, JS_CLASS_C_CLOSURE);
5569+
5570+
if (s) {
5571+
if (s->opaque_finalize)
5572+
s->opaque_finalize(s->opaque);
5573+
5574+
js_free_rt(rt, s);
5575+
}
5576+
}
5577+
5578+
static JSValue js_call_c_closure(JSContext *ctx, JSValueConst func_obj,
5579+
JSValueConst this_val,
5580+
int argc, JSValueConst *argv, int flags)
5581+
{
5582+
JSRuntime *rt = ctx->rt;
5583+
JSStackFrame sf_s, *sf = &sf_s, *prev_sf;
5584+
JSCClosureRecord *s = JS_GetOpaque(func_obj, JS_CLASS_C_CLOSURE);
5585+
JSValueConst *arg_buf;
5586+
JSValue ret;
5587+
int arg_count;
5588+
int i;
5589+
size_t stack_size;
5590+
5591+
arg_buf = argv;
5592+
arg_count = s->length;
5593+
if (unlikely(argc < arg_count)) {
5594+
stack_size = arg_count * sizeof(arg_buf[0]);
5595+
if (js_check_stack_overflow(rt, stack_size))
5596+
return JS_ThrowStackOverflow(ctx);
5597+
arg_buf = alloca(stack_size);
5598+
for (i = 0; i < argc; i++)
5599+
arg_buf[i] = argv[i];
5600+
for (i = argc; i < arg_count; i++)
5601+
arg_buf[i] = JS_UNDEFINED;
5602+
}
5603+
5604+
prev_sf = rt->current_stack_frame;
5605+
sf->prev_frame = prev_sf;
5606+
rt->current_stack_frame = sf;
5607+
// TODO(bnoordhuis) switch realms like js_call_c_function does
5608+
sf->is_strict_mode = false;
5609+
sf->cur_func = unsafe_unconst(func_obj);
5610+
sf->arg_count = argc;
5611+
ret = s->func(ctx, this_val, argc, arg_buf, s->magic, s->opaque);
5612+
rt->current_stack_frame = sf->prev_frame;
5613+
5614+
return ret;
5615+
}
5616+
5617+
JSValue JS_NewCClosure(JSContext *ctx, JSCClosure *func, const char *name,
5618+
JSCClosureFinalizerFunc *opaque_finalize,
5619+
int length, int magic, void *opaque)
5620+
{
5621+
JSCClosureRecord *s;
5622+
JSAtom name_atom;
5623+
JSValue func_obj;
5624+
5625+
func_obj = JS_NewObjectProtoClass(ctx, ctx->function_proto,
5626+
JS_CLASS_C_CLOSURE);
5627+
if (JS_IsException(func_obj))
5628+
return func_obj;
5629+
s = js_malloc(ctx, sizeof(*s));
5630+
if (!s) {
5631+
JS_FreeValue(ctx, func_obj);
5632+
return JS_EXCEPTION;
5633+
}
5634+
s->func = func;
5635+
s->length = length;
5636+
s->magic = magic;
5637+
s->opaque = opaque;
5638+
s->opaque_finalize = opaque_finalize;
5639+
JS_SetOpaqueInternal(func_obj, s);
5640+
name_atom = JS_ATOM_empty_string;
5641+
if (name && *name) {
5642+
name_atom = JS_NewAtom(ctx, name);
5643+
if (name_atom == JS_ATOM_NULL) {
5644+
JS_FreeValue(ctx, func_obj);
5645+
return JS_EXCEPTION;
5646+
}
5647+
}
5648+
js_function_set_properties(ctx, func_obj, name_atom, length);
5649+
JS_FreeAtom(ctx, name_atom);
5650+
return func_obj;
5651+
}
5652+
55505653
static void free_property(JSRuntime *rt, JSProperty *pr, int prop_flags)
55515654
{
55525655
if (unlikely(prop_flags & JS_PROP_TMASK)) {
@@ -6455,6 +6558,15 @@ void JS_ComputeMemoryUsage(JSRuntime *rt, JSMemoryUsage *s)
64556558
}
64566559
}
64576560
break;
6561+
case JS_CLASS_C_CLOSURE: /* u.c_closure_record */
6562+
{
6563+
JSCClosureRecord *c = p->u.c_closure_record;
6564+
if (c) {
6565+
s->memory_used_count += 1;
6566+
s->memory_used_size += sizeof(*c);
6567+
}
6568+
}
6569+
break;
64586570
case JS_CLASS_REGEXP: /* u.regexp */
64596571
compute_jsstring_size(p->u.regexp.pattern, hp);
64606572
compute_jsstring_size(p->u.regexp.bytecode, hp);

quickjs.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ static inline bool JS_VALUE_IS_NAN(JSValue v)
394394
typedef JSValue JSCFunction(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv);
395395
typedef JSValue JSCFunctionMagic(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic);
396396
typedef JSValue JSCFunctionData(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data);
397+
typedef JSValue JSCClosure(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, void *opaque);
397398

398399
typedef struct JSMallocFunctions {
399400
void *(*js_calloc)(void *opaque, size_t count, size_t size);
@@ -1165,6 +1166,11 @@ JS_EXTERN JSValue JS_NewCFunctionData2(JSContext *ctx, JSCFunctionData *func,
11651166
const char *name,
11661167
int length, int magic, int data_len,
11671168
JSValueConst *data);
1169+
typedef void JSCClosureFinalizerFunc(void*);
1170+
JS_EXTERN JSValue JS_NewCClosure(JSContext *ctx, JSCClosure *func,
1171+
const char *name,
1172+
JSCClosureFinalizerFunc *opaque_finalize,
1173+
int length, int magic, void *opaque);
11681174

11691175
static inline JSValue JS_NewCFunction(JSContext *ctx, JSCFunction *func,
11701176
const char *name, int length)

0 commit comments

Comments
 (0)