Problem
The IR inliner categorically refuses to inline a ForeignImport binding, even when it is referenced exactly once (not (isForeignImport expr) in the optimizer's top-level inlining decision; Note [Inline annotations and inlining heuristics]). As a blanket rule the veto is correct: a textually single use may sit under a lambda, where pasting the import re-evaluates the foreign source per call and mints fresh table identities. That is the #175 class, pinned by the Golden.ForeignSharing eval oracle.
The blanket form is blind to evaluation frequency, though. In a module whose only reference to the foreign table is a projection in a once-evaluated position, the fold is sound, and today's output keeps a dead outer table alive:
local Golden_ForeignSharing_Token_foreign = { token = {} }
return { token = Golden_ForeignSharing_Token_foreign.token }
could be return { token = {} }: the same single allocation at init, the same identity, one dead table constructor and one hash read less.
Approach
Relax the veto to inline a ForeignImport when three conditions hold: it has exactly one reference; its parsed source has no header statements (a header may carry side effects whose order relative to other init statements must not move, while a header-free constructor is pure, so its evaluation commutes with intervening init effects); and the path from the referencing expression's root to the occurrence crosses only multiplicity-one positions (application heads and arguments, table-constructor rows, projection bases). Crossing an Abs (0..N evaluations; IR lazy thunks are lambdas, so the same test excludes them), an IfThenElse branch or an and/or right operand (0..1), or a loop body (N) declines. The soundness argument in one line: identity is minted per evaluation of the constructor, so preserving evaluation cardinality preserves identity.
No new fold is needed downstream: the inlined import lowers to a field access into the table constructor, which is the live trigger of reduceTableDefinitionAccessor (#140) and the scope-call fold (#159); those finish the job.
Low priority. The multiplicity-one condition confines the fold to init-position code, so the win is one table allocation and one hash read per fully-projected single-use foreign module, plus cleaner output. Hot sites are exactly the ones where the fold is unsound; they are served by promotion (#174) and the accessor-default revisit (#248) instead.
Verification / Measurement
Golden.ForeignSharing.Token and Golden.TestReturnTableField collapse to a direct export-table row. The Golden.ForeignSharing.Test eval oracle still prints "shared": its import has two references after beta reduction, so the veto holds there on the reference count alone, and the projection under the lambda stays a read of the hoisted shared table. Eval goldens unchanged corpus-wide.
Prerequisites / Relations
Independent. Refines the #175 fix rather than reverting it; the #140 and #159 rewrite rules are the downstream consumers. #248 covers the multi-use side of foreign-reference cost.
Problem
The IR inliner categorically refuses to inline a
ForeignImportbinding, even when it is referenced exactly once (not (isForeignImport expr)in the optimizer's top-level inlining decision; Note [Inline annotations and inlining heuristics]). As a blanket rule the veto is correct: a textually single use may sit under a lambda, where pasting the import re-evaluates the foreign source per call and mints fresh table identities. That is the #175 class, pinned by theGolden.ForeignSharingeval oracle.The blanket form is blind to evaluation frequency, though. In a module whose only reference to the foreign table is a projection in a once-evaluated position, the fold is sound, and today's output keeps a dead outer table alive:
could be
return { token = {} }: the same single allocation at init, the same identity, one dead table constructor and one hash read less.Approach
Relax the veto to inline a
ForeignImportwhen three conditions hold: it has exactly one reference; its parsed source has no header statements (a header may carry side effects whose order relative to other init statements must not move, while a header-free constructor is pure, so its evaluation commutes with intervening init effects); and the path from the referencing expression's root to the occurrence crosses only multiplicity-one positions (application heads and arguments, table-constructor rows, projection bases). Crossing anAbs(0..N evaluations; IR lazy thunks are lambdas, so the same test excludes them), anIfThenElsebranch or anand/orright operand (0..1), or a loop body (N) declines. The soundness argument in one line: identity is minted per evaluation of the constructor, so preserving evaluation cardinality preserves identity.No new fold is needed downstream: the inlined import lowers to a field access into the table constructor, which is the live trigger of
reduceTableDefinitionAccessor(#140) and the scope-call fold (#159); those finish the job.Low priority. The multiplicity-one condition confines the fold to init-position code, so the win is one table allocation and one hash read per fully-projected single-use foreign module, plus cleaner output. Hot sites are exactly the ones where the fold is unsound; they are served by promotion (#174) and the accessor-default revisit (#248) instead.
Verification / Measurement
Golden.ForeignSharing.TokenandGolden.TestReturnTableFieldcollapse to a direct export-table row. TheGolden.ForeignSharing.Testeval oracle still prints "shared": its import has two references after beta reduction, so the veto holds there on the reference count alone, and the projection under the lambda stays a read of the hoisted shared table. Eval goldens unchanged corpus-wide.Prerequisites / Relations
Independent. Refines the #175 fix rather than reverting it; the #140 and #159 rewrite rules are the downstream consumers. #248 covers the multi-use side of foreign-reference cost.