Skip to content

Commit 011a855

Browse files
committed
tests: isolate frozen SMR entry probes
1 parent 7ed86c6 commit 011a855

1 file changed

Lines changed: 75 additions & 17 deletions

File tree

tests/t-jit-idle-reclaim-entry.c

Lines changed: 75 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,18 @@ int main(void)
123123
IdleReclaimCtx ctx;
124124
PatchedPC loop_patch, ret_patch, itern_patch;
125125
pthread_t reclaimer;
126+
int cache_base, jfuncf_idx, loop_idx, ret_idx, itern_idx, itern_input_idx;
127+
int probe_idx, generic_idx, generic_input_idx;
126128

127129
assert(g != NULL);
128130
lua_gc(L, LUA_GCSTOP, 0);
129131
publish_ptr(L, "__idle_reclaim_vmstate", &G2TG(g)->vmstate);
130132
publish_ptr(L, "__idle_reclaim_hits", &native_hits);
133+
/* The native-entry probe intentionally uses FFI/cdata metadata and therefore
134+
** runs only while the metadata SMR writer can make progress. The separate
135+
** closed-window numeric and generic traces below use scalar/upvalue state;
136+
** an unrelated retained metadata lookup would correctly wait for the test's
137+
** deliberately frozen exclusive writer and obscure the JIT-entry proof. */
131138
ljt_lua_dostring(L,
132139
"jit.flush()\n"
133140
"jit.on()\n"
@@ -151,11 +158,20 @@ int main(void)
151158
" return x\n"
152159
"end\n"
153160
"for _ = 1, 20 do assert(__idle_reclaim_probe(400) == 80200) end\n"
154-
"generic_input = {11, 22, 33, 44, 55}\n"
155-
"function __idle_reclaim_generic(t)\n"
161+
"function __idle_reclaim_closed_probe(n)\n"
162+
" local x = 0\n"
163+
" for i = 1, n do x = x + i end\n"
164+
" return x\n"
165+
"end\n"
166+
"for _ = 1, 20 do assert(__idle_reclaim_closed_probe(400) == 80200) end\n"
167+
"local function idle_generic_iter(limit, i)\n"
168+
" i = i + 1\n"
169+
" if i <= limit then return i, i * 11 end\n"
170+
"end\n"
171+
"generic_input = 5\n"
172+
"function __idle_reclaim_generic(limit)\n"
156173
" local n, x = 0, 0\n"
157-
" for _, v in ipairs(t) do\n"
158-
" hits[0] = hits[0] + bit.rshift(bit.bnot(vmstate[0]), 31)\n"
174+
" for _, v in idle_generic_iter, limit, 0 do\n"
159175
" n, x = n + 1, x + v\n"
160176
" end\n"
161177
" return n, x\n"
@@ -175,9 +191,11 @@ int main(void)
175191
"function __idle_shadow_ret(x) return x + 7 end\n"
176192
"jit.off(__idle_shadow_ret, true)\n"
177193
"idle_shadow_input = {11, 22, 33, 44, 55}\n"
178-
"idle_real_next = next\n"
194+
"local idle_real_next = next\n"
195+
"local next = idle_real_next\n"
179196
"function idle_next_wrapper(t, k) return idle_real_next(t, k) end\n"
180197
"jit.off(idle_next_wrapper, true)\n"
198+
"function __idle_shadow_set_next(f) next = f end\n"
181199
"function __idle_shadow_itern(t)\n"
182200
" local n, x = 0, 0\n"
183201
" for _, v in next, t do n, x = n + 1, x + v end\n"
@@ -221,6 +239,32 @@ int main(void)
221239
}
222240
lj_gc2_test_idle_reclaim_leave(g);
223241

242+
/* Retained table lookup now takes a short SMR reader while copying a global
243+
** value. Preload every value needed below before the test hook freezes the
244+
** exclusive reclaimer; otherwise lua_getglobal() would correctly wait for
245+
** the deliberately paused writer and the fixture would deadlock itself
246+
** before exercising closed-gate bytecode recovery. Keep the cached values on
247+
** the Lua stack as ordinary roots and duplicate them for each call. */
248+
cache_base = lua_gettop(L);
249+
lua_getglobal(L, "__idle_reclaim_jfuncf");
250+
jfuncf_idx = lua_gettop(L);
251+
lua_getglobal(L, "__idle_shadow_loop");
252+
loop_idx = lua_gettop(L);
253+
lua_getglobal(L, "__idle_shadow_ret");
254+
ret_idx = lua_gettop(L);
255+
lua_getglobal(L, "__idle_shadow_itern");
256+
itern_idx = lua_gettop(L);
257+
lua_getglobal(L, "idle_shadow_input");
258+
itern_input_idx = lua_gettop(L);
259+
lua_getglobal(L, "__idle_reclaim_closed_probe");
260+
probe_idx = lua_gettop(L);
261+
assert(lua_isfunction(L, jfuncf_idx));
262+
assert(lua_isfunction(L, loop_idx));
263+
assert(lua_isfunction(L, ret_idx));
264+
assert(lua_isfunction(L, itern_idx));
265+
assert(lua_istable(L, itern_input_idx));
266+
assert(lua_isfunction(L, probe_idx));
267+
224268
ctx.g = g;
225269
ctx.epoch = lj_gc2_retire_epoch(g) + 1u;
226270
ctx.reclaimed = ~(uint32_t)0;
@@ -235,20 +279,23 @@ int main(void)
235279
** immediately before its trace/mcode retired-slot release pass. A real
236280
** BC_JLOOP entry attempt must remain interpreted while the owned gate is
237281
** closed. Prototype-owned startins recovery must let the interpreter finish
238-
** every iteration before the paused SMR writer is released. */
282+
** every iteration before the paused SMR writer is released. The scalar
283+
** closed probe's displaced counter is the authoritative native-entry veto;
284+
** the FFI/vmstate probe independently proves native execution before and
285+
** after this artificial pause. */
239286
assert(gc2_smr_reclaiming_acq(g) != 0);
240287
assert(gc2_jit_phase_gate_acq(g) == 0);
241288
assert(!lj_gc2_jit_entry_open(g));
242289
gc2_jit_sweep_displaced_rel(g, 0);
243-
lua_getglobal(L, "__idle_reclaim_jfuncf");
290+
lua_pushvalue(L, jfuncf_idx);
244291
lua_pushinteger(L, 37);
245292
ljt_lua_pcall(L, 1, 1, "closed IDLE JFUNCF entry");
246293
assert(lua_tointeger(L, -1) == 112);
247294
lua_pop(L, 1);
248295
assert(gc2_jit_sweep_displaced_acq(g) != 0);
249296

250297
gc2_jit_sweep_displaced_rel(g, 0);
251-
lua_getglobal(L, "__idle_shadow_loop");
298+
lua_pushvalue(L, loop_idx);
252299
lua_pushinteger(L, 1000);
253300
ljt_lua_pcall(L, 1, 2, "closed IDLE LOOP shadow");
254301
assert(lua_tointeger(L, -2) == 1000);
@@ -257,7 +304,7 @@ int main(void)
257304
assert(gc2_jit_sweep_displaced_acq(g) != 0);
258305

259306
gc2_jit_sweep_displaced_rel(g, 0);
260-
lua_getglobal(L, "__idle_shadow_ret");
307+
lua_pushvalue(L, ret_idx);
261308
lua_pushinteger(L, 35);
262309
ljt_lua_pcall(L, 1, 1, "closed IDLE RET shadow");
263310
assert(lua_tointeger(L, -1) == 42);
@@ -266,8 +313,8 @@ int main(void)
266313

267314
gc2_jit_sweep_displaced_rel(g, 0);
268315
lj_trace_test_force_startins_retry(1);
269-
lua_getglobal(L, "__idle_shadow_itern");
270-
lua_getglobal(L, "idle_shadow_input");
316+
lua_pushvalue(L, itern_idx);
317+
lua_pushvalue(L, itern_input_idx);
271318
ljt_lua_pcall(L, 1, 2, "closed IDLE ITERN shadow");
272319
assert(lua_tointeger(L, -2) == 5);
273320
assert(lua_tointeger(L, -1) == 165);
@@ -278,7 +325,7 @@ int main(void)
278325

279326
gc2_jit_sweep_displaced_rel(g, 0);
280327
lj_trace_test_force_startins_retry(1);
281-
lua_getglobal(L, "__idle_reclaim_probe");
328+
lua_pushvalue(L, probe_idx);
282329
lua_pushinteger(L, 20000);
283330
ljt_lua_pcall(L, 1, 1, "closed IDLE reclaim entry");
284331
assert(lua_tonumber(L, -1) == 200010000.0);
@@ -293,15 +340,17 @@ int main(void)
293340
assert(la_load32_acq(&ctx.done) != 0);
294341
assert(gc2_smr_reclaiming_acq(g) == 0);
295342
assert(gc2_jit_phase_gate_acq(g) != 0);
343+
lua_settop(L, cache_base);
296344

297345
/* A failed ISNEXT must take a trace-body lease before deciding whether the
298346
** observed JLOOP still names the exact live ITERN generation and before
299347
** publishing JLOOP -> ITERC. Do that lease-dependent despecialization only
300348
** after the deliberately paused exclusive SMR writer has left. During the
301349
** pause the real builtin above still exercises closed-gate JLOOP recovery
302350
** through the immutable sidecar without waiting for body admission. */
351+
lua_getglobal(L, "__idle_shadow_set_next");
303352
lua_getglobal(L, "idle_next_wrapper");
304-
lua_setglobal(L, "next");
353+
ljt_lua_pcall(L, 1, 0, "set ITERN invalidation iterator");
305354
lua_getglobal(L, "__idle_shadow_itern");
306355
lua_getglobal(L, "idle_shadow_input");
307356
ljt_lua_pcall(L, 1, 2, "reopened IDLE ITERN invalidation");
@@ -310,8 +359,9 @@ int main(void)
310359
lua_pop(L, 2);
311360
assert(bc_op((BCIns)la_load32_acq((const uint32_t *)itern_patch.pc)) ==
312361
BC_ITERC);
313-
lua_getglobal(L, "idle_real_next");
314-
lua_setglobal(L, "next");
362+
lua_getglobal(L, "__idle_shadow_set_next");
363+
lua_getglobal(L, "next");
364+
ljt_lua_pcall(L, 1, 0, "restore ITERN iterator");
315365
restore_patch(&loop_patch);
316366
restore_patch(&ret_patch);
317367
restore_patch(&itern_patch);
@@ -323,12 +373,19 @@ int main(void)
323373
ctx.done = 0;
324374
la_store32_rel(&native_hits, 0);
325375
gc2_jit_sweep_displaced_rel(g, 0);
376+
cache_base = lua_gettop(L);
377+
lua_getglobal(L, "__idle_reclaim_generic");
378+
generic_idx = lua_gettop(L);
379+
lua_getglobal(L, "generic_input");
380+
generic_input_idx = lua_gettop(L);
381+
assert(lua_isfunction(L, generic_idx));
382+
assert(lua_isnumber(L, generic_input_idx));
326383
lj_gc2_test_idle_reclaim_pause_after_jit_quiescence();
327384
assert(pthread_create(&reclaimer, NULL, idle_reclaim_main, &ctx) == 0);
328385
wait_for_idle_reclaim_pause(&ctx);
329386
lj_trace_test_force_startins_retry(1);
330-
lua_getglobal(L, "__idle_reclaim_generic");
331-
lua_getglobal(L, "generic_input");
387+
lua_pushvalue(L, generic_idx);
388+
lua_pushvalue(L, generic_input_idx);
332389
ljt_lua_pcall(L, 1, 2, "closed IDLE generic-loop entry");
333390
assert(lua_tointeger(L, -2) == 5);
334391
assert(lua_tointeger(L, -1) == 165);
@@ -344,6 +401,7 @@ int main(void)
344401
assert(la_load32_acq(&ctx.done) != 0);
345402
assert(gc2_smr_reclaiming_acq(g) == 0);
346403
assert(gc2_jit_phase_gate_acq(g) != 0);
404+
lua_settop(L, cache_base);
347405

348406
lua_getglobal(L, "__idle_reclaim_probe");
349407
lua_pushinteger(L, 20000);

0 commit comments

Comments
 (0)