Problem
reduceKnownConstructor (#177) only fires on a constructor that is syntactically in place. A scrutinee read more than once is never in place: beta reduction Let-binds a non-trivial argument instead of substituting it into every occurrence (the #167 discipline), and a dictionary method reads its scrutinee at least three times: a tag test, a payload read, and a second tag test for the fallthrough. So inlining a bind/apply method and beta-reducing lands on
let v = Just (x + 1) in
if justTag == ReflectCtor v then … v.value0 …
else if nothingTag == ReflectCtor v then Nothing
else …
and folds nothing: the rules see ReflectCtor (Ref v) and ObjectProp (Ref v) "value0", never the constructor. Checked against the current optimizer: that term is returned unchanged.
This is the step that turns an inlined method into straight-line code. Without it #180 inlines the method but leaves an opaque Let-bound match, which is a near-zero win for exactly the multi-use-scrutinee methods (bind, apply), which is most of them.
Approach
Propagate a known constructor from a Let binder into its reads. When a Standalone Let binding's RHS is a saturated Ctor application, fold the binder's reads in the body: ReflectCtor v becomes the tag string, and each payload read (ObjectProp v "valueᵢ", DataArgumentByIndex i v) becomes the iᵗʰ argument.
To keep the #167 discipline (a field expression must not be duplicated across reads), bind each read field to a fresh local rather than substituting the constructor into the read sites: GHC's case-binder to field-binder split. Trivial fields and dead field-binders then inline or DCE away. Drop the Let once the binder is no longer read as a whole value, keep it otherwise. GUC guarantees no shadowing, so the binder resolves by name.
With the reads folded, the surrounding Eq/if meets constant folding and removeUnreachable*, collapsing the decision tree to its live arm: the KnownBranch payoff #177 set up, now reachable through the Let.
Prerequisites / Relations
Extends #177 from in-place to Let-bound scrutinees. Consumes the ObjectProp field-read fold (#213) and the Let-binding discipline of #167. Prerequisite for #180: this is the enabler that makes method inlining pay off.
Verification / Measurement
Measured end to end by #172 through the LongMaybeBind / LongEitherBind / LongWriterBind / LongStateBind bind-chain goldens; the eval goldens are the correctness net, and they must not move. The rule rewrites binders and touches discard semantics, so stress the randomized optimizer specs across seeds and lean on the checked pipeline's scope-invariant linting.
Problem
reduceKnownConstructor(#177) only fires on a constructor that is syntactically in place. A scrutinee read more than once is never in place: beta reduction Let-binds a non-trivial argument instead of substituting it into every occurrence (the #167 discipline), and a dictionary method reads its scrutinee at least three times: a tag test, a payload read, and a second tag test for the fallthrough. So inlining abind/applymethod and beta-reducing lands onand folds nothing: the rules see
ReflectCtor (Ref v)andObjectProp (Ref v) "value0", never the constructor. Checked against the current optimizer: that term is returned unchanged.This is the step that turns an inlined method into straight-line code. Without it #180 inlines the method but leaves an opaque Let-bound match, which is a near-zero win for exactly the multi-use-scrutinee methods (
bind,apply), which is most of them.Approach
Propagate a known constructor from a Let binder into its reads. When a
StandaloneLet binding's RHS is a saturatedCtorapplication, fold the binder's reads in the body:ReflectCtor vbecomes the tag string, and each payload read (ObjectProp v "valueᵢ",DataArgumentByIndex i v) becomes the iᵗʰ argument.To keep the #167 discipline (a field expression must not be duplicated across reads), bind each read field to a fresh local rather than substituting the constructor into the read sites: GHC's case-binder to field-binder split. Trivial fields and dead field-binders then inline or DCE away. Drop the Let once the binder is no longer read as a whole value, keep it otherwise. GUC guarantees no shadowing, so the binder resolves by name.
With the reads folded, the surrounding
Eq/ifmeets constant folding andremoveUnreachable*, collapsing the decision tree to its live arm: the KnownBranch payoff #177 set up, now reachable through the Let.Prerequisites / Relations
Extends #177 from in-place to Let-bound scrutinees. Consumes the
ObjectPropfield-read fold (#213) and the Let-binding discipline of #167. Prerequisite for #180: this is the enabler that makes method inlining pay off.Verification / Measurement
Measured end to end by #172 through the
LongMaybeBind/LongEitherBind/LongWriterBind/LongStateBindbind-chain goldens; the eval goldens are the correctness net, and they must not move. The rule rewrites binders and touches discard semantics, so stress the randomized optimizer specs across seeds and lean on the checked pipeline's scope-invariant linting.