Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ Notable changes to shen-lua. Release notes for earlier versions are under

### Fixed

- A warm fasl hit no longer runs a nested `load` twice. When a loaded file
loaded another file, from a top-level `(load ...)` form or from a function
that loads, the nested file's output printed twice on every cached run.
The fasl format is now `SHENFASL7`, so caches written by older builds are
recompiled once rather than replayed (pyrex41/shen-lua#92).
- The reader reads list literals of any length: `shen.<s-exprs>` recursed
once per token, so past ~1550 elements LuaJIT's fixed stack overflowed and
`read-file` reported it as `reader error near here:` with no context. The
Expand Down
17 changes: 13 additions & 4 deletions boot.lua
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,9 @@ end
-- Known: (destroy ...) at the REPL between loads is not in the key.
-- SHEN_FASL=off disables; SHEN_FASL_DIR overrides ~/.cache/shen-lua-fasl;
-- SHEN_FASL_DEBUG=1 logs hits/misses to stderr.
local FASL_FORMAT = "SHENFASL6" -- 6: "dv" (shen.*datatypes* by name) replaces
local FASL_FORMAT = "SHENFASL7" -- 7: a nested load run inside a chunk is no
-- longer spliced into the outer fasl (#92);
-- 6: "dv" (shen.*datatypes* by name) replaces
-- "dt" (re-run shen.process-datatype);
-- 5: "lt" (shen.*lambdatable* delta by name);
-- 4: "e" (per-form value/type echo)
Expand Down Expand Up @@ -1320,17 +1322,24 @@ local function install_fasl()
-- on a miss — so the outer stream stays a complete description of everything
-- the outer span did, in order. An inner recording that could not be
-- serialized poisons the outer one for the same reason.
--
-- Only a load made OUTSIDE the outer span's chunks has a hole to fill (one
-- run at macroexpansion time, say). A load made while one of the outer
-- chunks executes — a top-level (load ...) form, or a function that loads —
-- is already in the outer stream as that chunk, and replaying the chunk
-- runs the load again; splicing it as well replays it twice (issue #92).
local function splice_into_outer(recs, uncacheable, deps)
local outer = FASL_STACK[#FASL_STACK]
if not outer then return end
if outer.deps and deps then
for _, d in ipairs(deps) do outer.deps[#outer.deps + 1] = d end
end
if outer.in_chunk then return end
for _, r in ipairs(recs) do
outer.n = outer.n + 1
outer[outer.n] = r
end
if uncacheable then outer.uncacheable = outer.uncacheable or uncacheable end
if outer.deps and deps then
for _, d in ipairs(deps) do outer.deps[#outer.deps + 1] = d end
end
end

local orig_load = F["load"]
Expand Down
49 changes: 49 additions & 0 deletions test/cli_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
-- itself on 42), --hush-load / SHEN_HUSH_LOAD=1 (load's echo dropped,
-- the program's own (output ...) kept), including that --hush-load output
-- is byte-identical cold and on a warm fasl hit;
-- * a warm fasl hit runs a nested (load) once, not twice (issue #92);
-- * shen.boot{hush_load = true}, the embedder spelling of that mode.
--
-- Every subprocess is wrapped in `timeout` when available, so an EOF-loop
Expand Down Expand Up @@ -370,6 +371,54 @@ do
for _, d in ipairs{dq, dh, dd, de} do os.execute("rm -rf " .. sh_quote(d)) end
end

-- ---------------------------------------------------------------------------
-- A warm fasl hit runs a nested (load) once (pyrex41/shen-lua#92).
-- A file that loads another file — from a top-level (load ...) form or from a
-- function that loads — used to replay the inner file's chunks twice on a hit:
-- once by re-running the outer chunk that calls load, and once more from a
-- copy of the inner records spliced into the outer fasl. Its output therefore
-- doubled on every warm run. The output must be the same cold, warm and with
-- SHEN_FASL=off.
-- ---------------------------------------------------------------------------
do
local fdir = os.tmpname(); os.remove(fdir)
local lib = os.tmpname() .. ".shen"
local h = io.open(lib, "w"); h:write('(output "LIB~%")\n'); h:close()
local function shen_file(body)
local p = os.tmpname() .. ".shen"
local fh = io.open(p, "w"); fh:write(body); fh:close()
return p
end
local ql = '"' .. lib .. '"'
local mains = {
["top-level load"] = shen_file('(output "BEGIN~%")\n(load ' .. ql .. ')\n'
.. '(load ' .. ql .. ')\n(output "END~%")\n'),
["load from a function"] = shen_file('(output "BEGIN~%")\n(load ' .. ql .. ')\n'
.. '(define fasl92-reload X -> (load X))\n(fasl92-reload ' .. ql .. ')\n'
.. '(output "END~%")\n'),
}
local want = "BEGIN\nLIB\nLIB\nEND\n"
for what, main in pairs(mains) do
os.execute("rm -rf " .. sh_quote(fdir))
local cmd = SHEN .. " --hush-load " .. sh_quote(main)
local envp = "env SHEN_FASL_DIR=" .. sh_quote(fdir) .. " "
local cold, ccode = run(envp .. cmd)
local warm, wcode = run(envp .. "SHEN_FASL_DEBUG=1 " .. cmd)
local warm2 = run(envp .. cmd)
local off = run("env SHEN_FASL=off " .. cmd)
check(ccode == 0 and wcode == 0, "#92 " .. what .. ": cold and warm runs exit 0")
check(warm:find("[fasl] hit", 1, true) ~= nil,
"#92 " .. what .. ": second run is a fasl HIT")
warm = warm:gsub("%[fasl%][^\n]*\n", "")
check(off == want, "#92 " .. what .. ": SHEN_FASL=off prints LIB twice")
check(cold == want, "#92 " .. what .. ": cold run prints LIB twice")
check(warm == want, "#92 " .. what .. ": warm hit prints LIB twice, not four times")
check(warm2 == want, "#92 " .. what .. ": second warm hit is unchanged")
os.remove(main)
end
os.remove(lib); os.execute("rm -rf " .. sh_quote(fdir))
end

-- ---------------------------------------------------------------------------
-- shen.boot{hush_load = true} — the embedder spelling of the same switch.
-- Drives a fresh LuaJIT process that boots in-library and (load)s a file, so
Expand Down
Loading