Skip to content

JS_Free*: public vararg macros#1535

Open
vitawrap wants to merge 4 commits into
quickjs-ng:masterfrom
vitawrap:master
Open

JS_Free*: public vararg macros#1535
vitawrap wants to merge 4 commits into
quickjs-ng:masterfrom
vitawrap:master

Conversation

@vitawrap

@vitawrap vitawrap commented Jun 17, 2026

Copy link
Copy Markdown

This introduces 4 new public macros and 4 public functions (though the macros are preferred, which is why the functions use the lowercase js_* prefix instead).

The functions to use directly are

  • JS_FreeValues
  • JS_FreeValuesRT
  • JS_FreeAtoms
  • JS_FreeAtomsRT

note the "s"

Example usage
JS_FreeValues(ctx, myval1, myval2, someotherval3);

Why?
I am editing dense code with tight interop with QJS and i've noticed an annoying pattern of FreeValue/Atom accumulating, especially when dup'ing for calls when the JSValue layout isn't initially linear, or when going down object chains with atoms. I also feel like this could benefit more users than me.

vitawrap added 3 commits June 17, 2026 15:13
useful for dense code with visible towers of frees
why didn't I do that the first time
this also tends to pile up
@vitawrap vitawrap changed the title JS_FreeValue: public vararg macros JS_Free*: public vararg macros Jun 17, 2026
@saghul

saghul commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Thoughts @bnoordhuis ? No strong opinion,I don't see myself using these.

@vitawrap

vitawrap commented Jun 18, 2026

Copy link
Copy Markdown
Author

Thoughts @bnoordhuis ? No strong opinion,I don't see myself using these.

Just now I was editing quickjs.c and met this big epilogue of JS_FreeValue calls
image
I feel like reducing such patterns to only one line before each end of scope would be very satisfying visually (at least in equivalent situations within personal code that uses quickjs)

@saghul

saghul commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

You have a very fair point there! Have you measured any performance difference?

@vitawrap

Copy link
Copy Markdown
Author

I have not measured the performance but with the vararg stack pushing requirement it will indeed always be slower than a normal function, however the body of those functions is the simplest it can be.

A vararg macro that applies another subtitution to each of its args is technically possible in C but that'd require me to pollute the qjs header with something like 16 or such linear macros or a few exponential macros... 🤷

@bnoordhuis

Copy link
Copy Markdown
Contributor

You mean like this, right?

#define JS_COUNT_ARGS_(_0, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
#define JS_COUNT_ARGS(...) JS_COUNT_ARGS_(0, __VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define JS_FreeValueV1(ctx, a) do { JS_FreeValue(ctx, a); } while (0)
#define JS_FreeValueV2(ctx, a, b) do { JS_FreeValue(ctx, a); JS_FreeValue(ctx, b); } while (0)
#define JS_FreeValueV3(ctx, a, b, c) do { JS_FreeValue(ctx, a); JS_FreeValueV2(ctx, b, c); } while (0)
#define JS_FreeValueV4(ctx, a, b, c, d) do { JS_FreeValue(ctx, a); JS_FreeValueV3(ctx, b, c, d); } while (0)
#define JS_FreeValueV5(ctx, a, b, c, d, e) do { JS_FreeValue(ctx, a); JS_FreeValueV4(ctx, b, c, d, e); } while (0)
#define JS_FreeValueV6(ctx, a, b, c, d, e, f) do { JS_FreeValue(ctx, a); JS_FreeValueV5(ctx, b, c, d, e, f); } while (0)
#define JS_FreeValueV7(ctx, a, b, c, d, e, f, g) do { JS_FreeValue(ctx, a); JS_FreeValueV6(ctx, b, c, d, e, f, g); } while (0)
#define JS_FreeValueV8(ctx, a, b, c, d, e, f, g, h) do { JS_FreeValue(ctx, a); } JS_FreeValueV7(ctx, b, c, d, e, f, g, h); } while (0)
#define JS_FreeValueV__(ctx, N, ...) JS_FreeValueV##N(ctx, __VA_ARGS__)
#define JS_FreeValueV_(ctx, N, ...) JS_FreeValueV__(ctx, N, __VA_ARGS__)
#define JS_FreeValueV(ctx, ...) JS_FreeValueV_(ctx, JS_COUNT_ARGS(__VA_ARGS__), __VA_ARGS__)

Seems fine to me. The generated code is an ungainly mess of nested do/while statements but no one looks at that, and it's definitely more efficient than a variadic function.

What would be interesting is measuring if it's better to expand to direct JS_FreeValue calls, or have JS_FreeValueV1, JS_FreeValueV2, etc. as public API functions that call JS_FreeValue in turn - i.e., more direct calls, or fewer indirect calls.

I could see it going either way but I expect that, as JSValue is two words on 64 bits machines, the compiler spills to the stack more and more after JS_FreeValueV3 or JS_FreeValueV4, negating any performance benefits.

Maybe you can mix and match: split up in pairs, call JS_FreeValueV2 for each pair, then mop up the remaining JSValue if there is one with JS_FreeValue. Lots of opportunity to measure, tweak and micro-optimize. :-)

@chqrlie

chqrlie commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

Seems fine to me. The generated code is an ungainly mess of nested do/while statements but no one looks at that, and it's definitely more efficient than a variadic function.

May I suggest an alternative that is more generic and produces more readable C code:

#define JS_COUNT_ARGS_(_0, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
#define JS_COUNT_ARGS(...) JS_COUNT_ARGS_(0, __VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define JS_CALLX1(F,X,a)               do { F(X,a); } while (0)
#define JS_CALLX2(F,X,a,b)             do { F(X,a); F(X,b); } while (0)
#define JS_CALLX3(F,X,a,b,c)           do { F(X,a); F(X,b); F(X,c); } while (0)
#define JS_CALLX4(F,X,a,b,c,d)         do { F(X,a); F(X,b); F(X,c); F(X,d); } while (0)
#define JS_CALLX5(F,X,a,b,c,d,e)       do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); } while (0)
#define JS_CALLX6(F,X,a,b,c,d,e,f)     do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); F(X,f); } while (0)
#define JS_CALLX7(F,X,a,b,c,d,e,f,g)   do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); F(X,f); F(X,g); } while (0)
#define JS_CALLX8(F,X,a,b,c,d,e,f,g,h) do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); F(X,f); F(X,g); F(X,h); } while (0)

#define JS_CALLX__(F, X, N, ...) JS_CALLX##N(F, X, __VA_ARGS__)
#define JS_CALLX_(F, X, N, ...) JS_CALLX__(F, X, N, __VA_ARGS__)

#define JS_FreeValueV(ctx, ...) JS_CALLX_(JS_FreeValue, ctx, JS_COUNT_ARGS(__VA_ARGS__), __VA_ARGS__)

I don't particularly like this kind of preprocessor abuse, but we cannot do much better with the C preprocessor. I must admit I did author some other ones in QuickJS.

An extra advantage in using this macro is the order of evaluation of the arguments is fixed, left to right, as opposed to unspecified behavior for the variadic function.

@vitawrap

Copy link
Copy Markdown
Author

If we accept such macros it is true that they come with more benefits and I'd gladly replace the vararg funcs with them.

Seems fine to me. The generated code is an ungainly mess of nested do/while statements but no one looks at that, and it's definitely more efficient than a variadic function.

May I suggest an alternative that is more generic and produces more readable C code:

#define JS_COUNT_ARGS_(_0, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N
#define JS_COUNT_ARGS(...) JS_COUNT_ARGS_(0, __VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)

#define JS_CALLX1(F,X,a)               do { F(X,a); } while (0)
#define JS_CALLX2(F,X,a,b)             do { F(X,a); F(X,b); } while (0)
#define JS_CALLX3(F,X,a,b,c)           do { F(X,a); F(X,b); F(X,c); } while (0)
#define JS_CALLX4(F,X,a,b,c,d)         do { F(X,a); F(X,b); F(X,c); F(X,d); } while (0)
#define JS_CALLX5(F,X,a,b,c,d,e)       do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); } while (0)
#define JS_CALLX6(F,X,a,b,c,d,e,f)     do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); F(X,f); } while (0)
#define JS_CALLX7(F,X,a,b,c,d,e,f,g)   do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); F(X,f); F(X,g); } while (0)
#define JS_CALLX8(F,X,a,b,c,d,e,f,g,h) do { F(X,a); F(X,b); F(X,c); F(X,d); F(X,e); F(X,f); F(X,g); F(X,h); } while (0)

#define JS_CALLX__(F, X, N, ...) JS_CALLX##N(F, X, __VA_ARGS__)
#define JS_CALLX_(F, X, N, ...) JS_CALLX__(F, X, N, __VA_ARGS__)

#define JS_FreeValueV(ctx, ...) JS_CALLX_(JS_FreeValue, ctx, JS_COUNT_ARGS(__VA_ARGS__), __VA_ARGS__)

I don't particularly like this kind of preprocessor abuse, but we cannot do much better with the C preprocessor. I must admit I did author some other ones in QuickJS.

An extra advantage in using this macro is the order of evaluation of the arguments is fixed, left to right, as opposed to unspecified behavior for the variadic function.

I particularly like this solution because it seems like a set of macros that can then be applied to a lot of other potential bulk operations.

shamelessly adapted from chqrlie's response
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants