Skip to content

GitLab

  • Projects
  • Groups
  • Snippets
  • Help
    • Loading...
  • Help
    • Help
    • Support
    • Community forum
    • Submit feedback
  • Sign in / Register
GHC
GHC
  • Project overview
    • Project overview
    • Details
    • Activity
    • Releases
  • Repository
    • Repository
    • Files
    • Commits
    • Branches
    • Tags
    • Contributors
    • Graph
    • Compare
    • Locked Files
  • Issues 4,332
    • Issues 4,332
    • List
    • Boards
    • Labels
    • Service Desk
    • Milestones
    • Iterations
  • Merge Requests 363
    • Merge Requests 363
  • Requirements
    • Requirements
    • List
  • CI / CD
    • CI / CD
    • Pipelines
    • Jobs
    • Schedules
  • Security & Compliance
    • Security & Compliance
    • Dependency List
    • License Compliance
  • Operations
    • Operations
    • Incidents
    • Environments
  • Analytics
    • Analytics
    • CI / CD
    • Code Review
    • Insights
    • Issue
    • Repository
    • Value Stream
  • Wiki
    • Wiki
  • Snippets
    • Snippets
  • Members
    • Members
  • Collapse sidebar
  • Activity
  • Graph
  • Create a new issue
  • Jobs
  • Commits
  • Issue Boards
  • Glasgow Haskell Compiler
  • GHCGHC
  • Issues
  • #16345

Closed
Open
Opened Feb 20, 2019 by Simon Peyton Jones@simonpjDeveloper

Duplicated allocation in case-of-known-constructor

Consider this

x = reverse "hello"

y = x `seq` Just True

f p = y `seq` (p, y)

You'd expect that we'd end up iwth

f = \p -> case y of DEFAULT -> (p, y)

or just possibly

f = \p -> case x of DEFAULT -> (p, y)

But we don't. We get

Foo3.f
  = \ (@ a_a13D) (p_a13b [Occ=Once] :: a_a13D) ->
      case Foo3.x of { __DEFAULT ->
      (p_a13b, Just @ Bool True)
      }

Yikes. Look at that completely-wasted Just True allocation, which will happen in every call to f.

There's nothing special about the top level here; this could happen for nested bindings too.

The culprit is this code in Simplify:

rebuildCase env scrut case_bndr alts cont
  | Just (wfloats, con, ty_args, other_args) <- exprIsConApp_maybe (getUnfoldingInRuleMatch env) scrut
  = do  { case findAlt (DataAlt con) alts of
            Nothing  -> missingAlt env case_bndr alts cont
            Just (DEFAULT, bs, rhs) -> let con_app = Var (dataConWorkId con)
                                                 `mkTyApps` ty_args
                                                 `mkApps`   other_args
                                       in simple_rhs wfloats con_app bs rhs
            Just (_, bs, rhs)       -> knownCon env scrut wfloats con ty_args other_args
                                                case_bndr bs rhs cont
        }

If we try to simplify

  case y of y' { DEFAULT -> (p, y') }

then exprIsConApp_maybe succeeds (with a floated case on x), effectively inlining y bodily, and we transform to

  case x of DEFAULT -> let y' = Just True in blah

Sigh. If making exprIsConApp_maybe to fire requires duplicating an allocation (here by inlining y), then perhaps we only want this rebuildCase transformation to fire if the case-binder y' is dead.

What happens in the knownCon case?

f = \p -> case y of y'
            Just t  -> (y',p)
            Nothing -> (p,p)

Here we correctly inline y, cancel the Just and end up with

f = \p -> case x of DEFAUT ->
          let y' = y in Just p

Where did that y' = y binding come from? Ah... the clever bind_case_bndr in knownCon. We should do something like this in the default case too.

Trac metadata
Trac field Value
Version 8.6.3
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component Compiler
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
Edited Mar 10, 2019 by Simon Peyton Jones
Assignee
Assign to
None
Milestone
None
Assign milestone
Time tracking
None
Due date
None
Reference: ghc/ghc#16345