Skip to content

reduceObjectProp leaks the FFI @inline always annotation onto unrelated bindings, forcing unconditional duplication #169

Description

@Unisay

Problem

Regression from #153 (reduceObjectProp, merged in #157). The Linker synthesizes exactly one shape carrying Just Inline.Always: a foreign name's alias binding, QName moduleName name = ObjectProp (Just Always) (Ref foreign) (PropName name) (see Note [Foreign bindings structure emitted by the Linker] in Linker.hs). isInlinableExpr treats that annotation as "always duplicate this expression at every use site", bypassing the use-count check entirely.

reduceObjectProp folds ObjectProp _ann (LiteralObject _ props) prop to List.lookup prop props, returning the field's value verbatim, annotation included. When a record literal has a field that happens to be one of these Linker-synthesized Always-marked foreign aliases, and something elsewhere projects that same field back out, the projection's result inherits Just Always even though the outer projection itself was never annotated. That annotation then becomes the root annotation of whatever binding the projection belongs to, making isInlinableExpr return True regardless of how many times that binding is actually used.

Concretely, in Golden.TailRecM2Shadow.Test, the Semiring Int dictionary literal has add: Data.Semiring.foreign.intAdd as a field. The module's own top-level add binding projects that field out of the (inlined) dictionary and is referenced twice. Before #157, add's root annotation stayed Nothing, so add correctly stayed a single shared top-level binding. After #157, the fold gives add's root the dictionary field's own Just Always, so add is unconditionally duplicated at both call sites, and DCE then drops the now-unreferenced Data.Semiring.foreign table entirely — the foreign function's raw source ends up duplicated inline at each call site instead of one shared entry in the foreign import table:

-- before
M.Golden_TailRecM2Shadow_Test_add = M.Data_Semiring_foreign.intAdd
a = M.Golden_TailRecM2Shadow_Test_add(o_S_31.a)(o_S_31.b)

-- after
a = (function(x) return function(y) return x + y end end)(o_S_31.a)(o_S_31.b)

For a foreign function with a nontrivial implementation this duplicates real code at every call site instead of one shared table entry.

Approach

The folded result must not inherit an annotation that was never meant to describe it. reduceObjectProp should discard the projected field's own annotation rather than letting it become the root annotation of the rewrite's result.

Prerequisites / Relations

Optimizer bug, independent in the dependency graph. Related to #171 — both concern the interaction of the Linker's auto-@inline always annotation with the inliner — but distinct: this one is an annotation leak in reduceObjectProp, whereas #171 is the bare-Ref alias interaction.

Verification / Measurement

Projecting a field whose value is an Always-marked foreign alias no longer marks the enclosing binding inlinable. In Golden.TailRecM2Shadow.Test, add stays a single shared top-level binding (M.Golden_TailRecM2Shadow_Test_add = M.Data_Semiring_foreign.intAdd) referenced from both call sites, rather than the foreign lambda being duplicated inline at each.

Metadata

Metadata

Assignees

Labels

area: irIR / optimizer / DCE / inlinerbugSomething isn't working

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions