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
20 changes: 10 additions & 10 deletions bench/goldens/fnew_Bench.EffectStep.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ function-body FNEW: 11
total FNEW: 22
prototypes: 23
function-body FNEW sites:
Bench.EffectStep.lua:4
Bench.EffectStep.lua:6
Bench.EffectStep.lua:6
Bench.EffectStep.lua:3
Bench.EffectStep.lua:5
Bench.EffectStep.lua:5
Bench.EffectStep.lua:8
Bench.EffectStep.lua:9
Bench.EffectStep.lua:10
Bench.EffectStep.lua:12
Bench.EffectStep.lua:12
Bench.EffectStep.lua:33
Bench.EffectStep.lua:44
Bench.EffectStep.lua:47
Bench.EffectStep.lua:56
Bench.EffectStep.lua:11
Bench.EffectStep.lua:11
Bench.EffectStep.lua:32
Bench.EffectStep.lua:43
Bench.EffectStep.lua:46
Bench.EffectStep.lua:55
16 changes: 8 additions & 8 deletions bench/goldens/fnew_Bench.RefLoop.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ function-body FNEW: 12
total FNEW: 20
prototypes: 21
function-body FNEW sites:
Bench.RefLoop.lua:6
Bench.RefLoop.lua:6
Bench.RefLoop.lua:9
Bench.RefLoop.lua:9
Bench.RefLoop.lua:17
Bench.RefLoop.lua:5
Bench.RefLoop.lua:5
Bench.RefLoop.lua:8
Bench.RefLoop.lua:8
Bench.RefLoop.lua:16
Bench.RefLoop.lua:15
Bench.RefLoop.lua:14
Bench.RefLoop.lua:18
Bench.RefLoop.lua:19
Bench.RefLoop.lua:20
Bench.RefLoop.lua:28
Bench.RefLoop.lua:27
Bench.RefLoop.lua:41
Bench.RefLoop.lua:26
Bench.RefLoop.lua:40
2 changes: 1 addition & 1 deletion bench/goldens/fnew_Bench.StateStep.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ function-body FNEW: 1
total FNEW: 4
prototypes: 5
function-body FNEW sites:
Bench.StateStep.lua:9
Bench.StateStep.lua:6
6 changes: 3 additions & 3 deletions bench/goldens/tnew_Bench.EffectStep.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
chunk: Bench.EffectStep.lua
runtime: LuaJIT 2.1.1741730670
main-chunk TNEW+TDUP: 4
main-chunk TNEW+TDUP: 3
function-body TNEW+TDUP: 1
total TNEW+TDUP: 5
total TNEW+TDUP: 4
prototypes: 23
function-body TNEW+TDUP sites:
Bench.EffectStep.lua:9 TDUP
Bench.EffectStep.lua:8 TDUP
6 changes: 3 additions & 3 deletions bench/goldens/tnew_Bench.RefLoop.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
chunk: Bench.RefLoop.lua
runtime: LuaJIT 2.1.1741730670
main-chunk TNEW+TDUP: 5
main-chunk TNEW+TDUP: 4
function-body TNEW+TDUP: 1
total TNEW+TDUP: 6
total TNEW+TDUP: 5
prototypes: 21
function-body TNEW+TDUP sites:
Bench.RefLoop.lua:19 TDUP
Bench.RefLoop.lua:18 TDUP
4 changes: 2 additions & 2 deletions bench/goldens/tnew_Bench.StateStep.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
chunk: Bench.StateStep.lua
runtime: LuaJIT 2.1.1741730670
main-chunk TNEW+TDUP: 2
main-chunk TNEW+TDUP: 1
function-body TNEW+TDUP: 0
total TNEW+TDUP: 2
total TNEW+TDUP: 1
prototypes: 5
function-body TNEW+TDUP sites:
18 changes: 9 additions & 9 deletions bench/goldens/trace_effect_step.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,19 @@ spec: effect_step
runtime: LuaJIT 2.1.1741730670
workload: n=100000 reps=4 result=1500000
aborts (distinct site -- reason):
Bench.EffectStep.lua:10 -- NYI: bytecode FNEW
Bench.EffectStep.lua:12 -- NYI: bytecode FNEW
Bench.EffectStep.lua:44 -- NYI: bytecode FNEW
Bench.EffectStep.lua:56 -- NYI: bytecode FNEW
Bench.EffectStep.lua:11 -- NYI: bytecode FNEW
Bench.EffectStep.lua:43 -- NYI: bytecode FNEW
Bench.EffectStep.lua:55 -- NYI: bytecode FNEW
Bench.EffectStep.lua:8 -- NYI: bytecode FNEW
Bench.EffectStep.lua:9 -- NYI: bytecode FNEW
bytecode end state (J*=compiled, I*=blacklisted):
Bench.EffectStep.lua:10 IFUNCF
Bench.EffectStep.lua:10 JFUNCF
Bench.EffectStep.lua:11 IFUNCF
Bench.EffectStep.lua:12 IFUNCF
Bench.EffectStep.lua:12 JFUNCF
Bench.EffectStep.lua:17 IFUNCF
Bench.EffectStep.lua:11 JFUNCF
Bench.EffectStep.lua:16 IFUNCF
Bench.EffectStep.lua:35 IFUNCF
Bench.EffectStep.lua:36 IFUNCF
Bench.EffectStep.lua:37 IFUNCF
Bench.EffectStep.lua:8 JFUNCF
Bench.EffectStep.lua:9 IFUNCF
Bench.EffectStep.lua:9 JFUNCF
counts: aborts=5 compiled=3 blacklisted=6
11 changes: 6 additions & 5 deletions bench/goldens/trace_ref_loop.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ spec: ref_loop
runtime: LuaJIT 2.1.1741730670
workload: n=100000 reps=4 result=4999950000
aborts (distinct site -- reason):
Bench.RefLoop.lua:41 -- NYI: bytecode FNEW
Bench.RefLoop.lua:40 -- NYI: bytecode FNEW
bytecode end state (J*=compiled, I*=blacklisted):
Bench.RefLoop.lua:11 JFUNCF
Bench.RefLoop.lua:35 JFORI
Bench.RefLoop.lua:35 JFORL
counts: aborts=1 compiled=3 blacklisted=0
Bench.RefLoop.lua:10 JFUNCF
Bench.RefLoop.lua:32 JFUNCF
Bench.RefLoop.lua:34 JFORI
Bench.RefLoop.lua:34 JFORL
counts: aborts=1 compiled=4 blacklisted=0
10 changes: 5 additions & 5 deletions bench/goldens/trace_state_step.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ spec: state_step
runtime: LuaJIT 2.1.1741730670
workload: n=100000 reps=4 result=99998
aborts (distinct site -- reason):
Bench.StateStep.lua:9 -- NYI: bytecode FNEW
Bench.StateStep.lua:6 -- NYI: bytecode FNEW
bytecode end state (J*=compiled, I*=blacklisted):
Bench.StateStep.lua:13 IFUNCF
Bench.StateStep.lua:35 ILOOP
Bench.StateStep.lua:4 IFUNCF
Bench.StateStep.lua:5 JFUNCF
Bench.StateStep.lua:1 IFUNCF
Bench.StateStep.lua:2 JFUNCF
Bench.StateStep.lua:30 ILOOP
Bench.StateStep.lua:8 IFUNCF
counts: aborts=1 compiled=1 blacklisted=3
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
### Changed

- A foreign module's export table is no longer kept hoisted when exactly one
once-evaluated site reads it (#251). The compiler binds each FFI module's
exports to one `ForeignImport` table and reads individual names off it, and
it used to refuse to fold that table into its reader however few readers
there were, because an export value can be a Lua table constructor with
identity (`unit = {}`) that a copy under a lambda would re-allocate per call.
A new late pass admits the cases where re-evaluation provably cannot happen:
one reference, a header-free FFI source (a bare `return { … }`, so nothing
side-effecting moves), and a path from the enclosing top-level right-hand
side to that reference crossing only positions evaluated exactly once — never
a lambda body, an `if` branch, or the right operand of `and`/`or`. The
emitted Lua then loses a table allocation and a hash read per folded import:
`local M_foreign = { token = {} }` plus `return { token = M_foreign.token }`
becomes `return { token = {} }`, and a shared accessor collapses in place
from `local M_log = M_foreign.log` to
`local M_log = function(s) return function() print(s) end end`.

- The pass runs directly after the accessor-sharing pass (#248), which is the
last one to change how many references an import has — dissolving an accessor
multiplies them over the use sites, re-binding a shared read collapses them
back to one. That placement is what lets it catch both shapes the fold has:
the read dissolved into an export expression, and the accessor binding kept
as a shared name whose right-hand side is the import's only reference.
11 changes: 8 additions & 3 deletions lib/Language/PureScript/Backend.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import Language.PureScript.Backend.IR.Inliner qualified as Inliner
import Language.PureScript.Backend.IR.Linker qualified as Linker
import Language.PureScript.Backend.IR.Linter qualified as Linter
import Language.PureScript.Backend.IR.Optimizer
( optimizedUberModule
( ProgramFacts (..)
, optimizedUberModule
, optimizedUberModuleChecked
)
import Language.PureScript.Backend.IR.Pass
Expand Down Expand Up @@ -59,10 +60,14 @@ compileModules outputDir foreignDir lintIR limits directives appOrModule = do
-- the whole pipeline can see through them (issue #178). DCE later prunes the
-- foreign source rows thus lifted away.
liftedModule ← ForeignLift.liftForeigns foreignDir linkedModule
-- See Note [Inlining a single-use foreign import]
factsHeaderFreeForeigns ←
liftIO (ForeignLift.headerFreeForeigns foreignDir liftedModule)
let facts = ProgramFacts {factsDataTypes = dataDecls, factsHeaderFreeForeigns}
uberModule ←
if untag lintIR
then Oops.hoistEither (optimizedUberModuleChecked dataDecls liftedModule)
else pure (optimizedUberModule dataDecls liftedModule)
then Oops.hoistEither (optimizedUberModuleChecked facts liftedModule)
else pure (optimizedUberModule facts liftedModule)
-- A dangling imported reference compiles to a read of a never-assigned
-- module-table field — a nil call at runtime — so refuse to emit code for
-- it (issue #297). Unconditional, unlike the per-pass linting behind
Expand Down
9 changes: 6 additions & 3 deletions lib/Language/PureScript/Backend/IR/Inliner.hs
Original file line number Diff line number Diff line change
Expand Up @@ -103,14 +103,17 @@ foreign bodies are the exception:
@always@ itself, because they exist to beta-reduce at saturated call sites.

The 'ForeignImport' expression itself — the table of a foreign module's
exports — is the one shape the optimizer refuses to inline even when it is
exports — is the one shape the inliner refuses to paste even when it is
referenced exactly once. Its export values are opaque to the IR, and some of
them are Lua table constructors with identity (e.g. @unit = {}@ in the
prelude), so pasting the import into its use site — possibly under a lambda —
would re-evaluate the foreign source per call and allocate fresh tables where
every site is supposed to share one. The table stays hoisted as a single
binding and the always-inlined 'ObjectProp' wrappers turn into field reads
off it.
binding and the 'ObjectProp' wrappers turn into field reads off it. One late
pass admits the cases where the re-evaluation provably cannot happen:
'Language.PureScript.Backend.IR.Optimizer.inlineSingleUseForeignImports'
dissolves an import that a header-free FFI source produced and that a single
once-evaluated site reads (see Note [Inlining a single-use foreign import]).
-}

--------------------------------------------------------------------------------
Expand Down
Loading