Skip to content

Commit ab330d4

Browse files
committed
ffi: remove unsnapshotted CType conversion
1 parent 5f29cac commit ab330d4

10 files changed

Lines changed: 28 additions & 50 deletions

notes/ffi-callxs-production-scalars-2026-07-18.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ activation changes this call site to `crec_ct2irt_snapshot()`. Scalar behavior
117117
is identical, while enum child relocation now retries or aborts recording
118118
instead of racing the table.
119119

120+
A follow-up production audit removed the older `crec_ct2irt()` surface
121+
entirely. Its last caller, the generic cdata-to-cdata recorder conversion, now
122+
uses the same snapshot-aware helper for both source and destination types.
123+
Current callers normally pre-resolve enum children, so this does not add work
124+
to ordinary scalar conversion; it closes the latent unsafe fallback if a
125+
future conversion reaches that helper with an enum still wrapped.
126+
120127
## Why boxed results remain interpreted
121128

122129
The existing post-CALLXS `IR_CNEWI` code is not an activation path. CNEWI can

src/lj_ccall.h

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,10 @@ typedef struct CCallNativeState {
199199
uint32_t post_winerr; /* Paired Win32 LastError; zero elsewhere. */
200200
} CCallNativeState;
201201

202-
/* Dormant structural substrate for generated generic FFI calls. Push/pop are
203-
** single-owner and allocation-free. Capacity exhaustion is the only ordinary
204-
** push failure and leaves sequence, depth and payload unchanged. Snapshot
205-
** copies every active frame without dereferencing any published stack offset.
202+
/* Published structural substrate for generated generic FFI calls. Push/pop
203+
** are single-owner and allocation-free. Capacity exhaustion is the only
204+
** ordinary push failure and leaves sequence, depth and payload unchanged.
205+
** Snapshot copies every active frame without dereferencing a stack offset.
206206
*/
207207
LJ_FUNC void lj_ffi_native_frame_init(TGState *tg);
208208
LJ_FUNC void lj_ffi_native_frame_fini(const TGState *tg);
@@ -219,9 +219,9 @@ LJ_FUNC uint32_t lj_ffi_native_frame_depth_acq(const TGState *tg);
219219
** allocation-free and nonwaiting; every attempt consumes XSAVE staging and
220220
** zero requests a pre-call interpreter exit. Ordinary leave removes the frame.
221221
** A forced leave converts it to POSTCALL and retains its exact pin until the
222-
** non-side-linkable caller-state trace exit invokes cleanup. The default
223-
** recorder remains gated; explicit test-only activation exercises the admitted
224-
** scalar lifecycle, including callback suspension and nested generated calls.
222+
** non-side-linkable caller-state trace exit invokes cleanup. Production x64
223+
** recording admits the nonallocating scalar result classes whose complete
224+
** lifecycle includes callback suspension and nested generated calls.
225225
*/
226226
#define LJ_FFI_NATIVE_LEAVE_FORCE_EXIT 0x80000000u
227227
LJ_FUNC int lj_ffi_native_trace_enter(lua_State *L, struct GCtrace *T,

src/lj_crecord.c

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -947,38 +947,6 @@ static IRType crec_ct2irt_snapshot(jit_State *J, CTState *cts, CType *ct)
947947
return IRT_CDATA;
948948
}
949949

950-
/* Convert CType to IRType (if possible). */
951-
static IRType crec_ct2irt(CTState *cts, CType *ct)
952-
{
953-
CTInfo info = ctype_info_acq(ct);
954-
CTSize size;
955-
if (ctype_isenum(info)) {
956-
ct = ctype_child(cts, ct);
957-
info = ctype_info_acq(ct);
958-
}
959-
size = ctype_size_acq(ct);
960-
if (LJ_LIKELY(ctype_isnum(info))) {
961-
if ((info & CTF_FP)) {
962-
if (size == sizeof(double))
963-
return IRT_NUM;
964-
else if (size == sizeof(float))
965-
return IRT_FLOAT;
966-
} else {
967-
uint32_t b = lj_fls(size);
968-
if (b <= 3)
969-
return IRT_I8 + 2*b + ((info & CTF_UNSIGNED) ? 1 : 0);
970-
}
971-
} else if (ctype_isptr(info)) {
972-
return (LJ_64 && size == 8) ? IRT_P64 : IRT_P32;
973-
} else if (ctype_iscomplex(info)) {
974-
if (size == 2*sizeof(double))
975-
return IRT_NUM;
976-
else if (size == 2*sizeof(float))
977-
return IRT_FLOAT;
978-
}
979-
return IRT_CDATA;
980-
}
981-
982950
/* -- Optimized memory fill and copy -------------------------------------- */
983951

984952
/* Maximum length and unroll of inlined copy/fill. */
@@ -1278,8 +1246,9 @@ static int crec_isnonzero(CType *s, void *p)
12781246
static TRef crec_ct_ct(jit_State *J, CType *d, CType *s, TRef dp, TRef sp,
12791247
void *svisnz)
12801248
{
1281-
IRType dt = crec_ct2irt(ctype_ctsG(J2G(J)), d);
1282-
IRType st = crec_ct2irt(ctype_ctsG(J2G(J)), s);
1249+
CTState *cts = ctype_ctsG(J2G(J));
1250+
IRType dt = crec_ct2irt_snapshot(J, cts, d);
1251+
IRType st = crec_ct2irt_snapshot(J, cts, s);
12831252
CTSize dsize = ctype_size_acq(d), ssize = ctype_size_acq(s);
12841253
CTInfo dinfo = ctype_info_acq(d), sinfo = ctype_info_acq(s);
12851254

src/lj_ffrecord.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -687,8 +687,8 @@ static void LJ_FASTCALL recff_math_abs(jit_State *J, RecordFFData *rd)
687687
{
688688
TRef tr = lj_ir_tonum(J, J->base[0]);
689689
#ifdef LJ_XSAVE_TEST_HELPERS
690-
/* Test-only dormant-path injection. Production recorders do not emit XSAVE
691-
** until the generic native-call protocol consumes its pending TG fields. */
690+
/* Test-only math-path injection. Production generic native calls emit XSAVE
691+
** at their exact entry boundary and consume its pending TG fields there. */
692692
lj_ffrecord_xsave(J);
693693
#endif
694694
J->base[0] = emitir(IRTN(IR_ABS), tr, lj_ir_ksimd(J, LJ_KSIMD_ABS));

src/lj_mtfields.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ whenever a shared field is introduced or migrated.
4141
| GCtrace.native_pins | exact native-body admission + lease count | high `CLOSED` bit and low 31-bit count share one CAS acq_rel word; pin increments only while open under an independent body lease; retirement closes after the epoch LP and before slot disposition; final closed unpin publishes count zero before notifying reclaim and must not dereference the body afterward |
4242
| J->trace_pin_release_seq / J->{trace,mcode}_reclaim_pin_seq | final native-unpin notification + token-owned scan memos | final closed unpin increments by CAS acq_rel; reclaimers load acq before scanning; memo fields are mutated only with recorder token + exact reclaimer gate and are authoritative only when both completed epoch and release sequence match |
4343
| TG.ffi_native_seq | whole-stack generic FFI native-frame sequence | single owner publishes odd with release then a writer barrier before mutation; final even generation stores release; observers load the initial and final generation acquire and accept only the same even value; wrap poisons odd and fail-stops |
44-
| TG.ffi_native_depth / TG.ffi_native_frame[] payload | dormant generic FFI native-frame stack | depth and every remotely sampled payload word use atomic release/acquire access; a coherent snapshot additionally requires the enclosing same-even `ffi_native_seq`; offsets are opaque until a later certified stack scanner validates them |
44+
| TG.ffi_native_depth / TG.ffi_native_frame[] payload | production generic FFI native-frame stack | depth and every remotely sampled payload word use atomic release/acquire access; a coherent snapshot additionally requires the enclosing same-even `ffi_native_seq`; offsets are opaque until the certified stack scanner validates them |
4545
| g->gc2.ffi_native_scan_* | certified native-frame scanner telemetry | relaxed atomic increments; acquire stats snapshots; counters carry no root-scanning authority |
4646
| GCtrace.unused1 entry-gate bits | trace metadata flags | `TRACE_SCOPE_FLUSH_PENDING` and `TRACE_ENTRY_INVALIDATED` publish by byte CAS acq_rel and VM/C entry loads acq; invalidation gates entry only, while scoped pending authorizes dependency closure + `EXIT_TRACES` retirement; exittab ownership remains an independent relaxed bit |
4747
| g->vmevent_owner | VM-event callback owner TG id | one nonwaiting CAS acq_rel; exact-owner CAS release to zero |

src/lj_tg.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,8 @@ struct TGState {
153153
LJTGRegistryKey registry_key;
154154
uint8_t registry_shadow_missed; /* Legacy-only attach after slot OOM. */
155155
#if LJ_HASFFI
156-
/* Keep the dormant generic frame substrate last so it cannot perturb any
157-
** existing VM/ABI-sensitive TG offset before generated entry code lands. */
156+
/* Keep generic frame publication last so production CALLXS does not perturb
157+
** any existing VM/ABI-sensitive TG offset. */
158158
uint64_t ffi_native_seq; /* Even stable, odd owner transition. */
159159
uint32_t ffi_native_depth;
160160
LJFFINativeFrame ffi_native_frame[LJ_FFI_NATIVE_FRAME_MAX];

tests/suites/m6_jit.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -936,7 +936,7 @@ assert(threading.gcworkers(0) == 1)
936936

937937
add({
938938
name = "m6_jit_xsave",
939-
description = "dormant XSAVE snapshots retain materialized allocation roots",
939+
description = "exact XSAVE snapshots retain materialized allocation roots",
940940
run = function(t)
941941
local flags = "-DLUA_USE_ASSERT -DLJ_XSAVE_TEST_HELPERS " ..
942942
"-DLJ_GC2_TEST_HELPERS"

tests/suites/m7_ffi.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ local function assert_generic_ccall_source(t)
5050
local recorder = read_all(files[1])
5151
assert(plain_count(recorder, "LJ_FFI_CALLXS_TEST_ACTIVATE") == 0,
5252
"generic CALLXS still has a test-only production gate")
53+
assert(plain_count(recorder, "static IRType crec_ct2irt(") == 0,
54+
"recorder retained a non-snapshot CType-to-IR conversion")
5355
assert(plain_count(recorder, "static TRef crec_call_args(") == 1,
5456
"generic C-call argument recorder is not unique")
5557
assert(plain_count(recorder, "IRT(IR_CALLXS, t)") == 1,

tests/t-ffi-native-frames.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/*
2-
** Deterministic structural tests for the dormant generic FFI native-frame
3-
** publisher. No test frame grants GC authority or enters native/JIT state.
2+
** Deterministic structural tests for the generic FFI native-frame publisher.
3+
** No synthetic test frame grants GC authority or enters native/JIT state.
44
*/
55

66
#include <assert.h>

tests/t-jit-xsave.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
** Focused dormant-path test for IR_XSAVE snapshot identity, allocation
2+
** Focused exact-path test for IR_XSAVE snapshot identity, allocation
33
** materialization and x64 TG-private root staging.
44
*/
55

0 commit comments

Comments
 (0)