Skip to content

Rewrite rules not applied exhaustively when simplifying from plugin

Consider this program:

{-# OPTIONS_GHC -O -fplugin TestPlugin #-}
module Test where

foo :: Int -> Int
foo = id
{-# INLINE [0] foo #-}

{-# RULES
"foo1" [1] foo 1 = foo 2
"foo2" [1] foo 2 = foo 3
 #-}

fun :: Int -> Int -> Int
fun = (+)
{-# NOINLINE fun #-}

test = foo 1 `fun` foo 2

I would expect that one run of the simplifier in phase 1 will turn this into

test = foo 3 `fun` foo 3

I am using this plugin to test this:

module TestPlugin where

import System.Exit
import Control.Monad

import GhcPlugins
import Simplify
import CoreStats
import SimplMonad
import FamInstEnv
import SimplEnv

-- Plugin boiler plate

plugin :: Plugin
plugin = defaultPlugin { installCoreToDos = install }

install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
install _ (simpl:xs) = return $ simpl : pass : xs
  where pass = CoreDoPluginPass "Test" testPass

-- The plugin

testPass :: ModGuts -> CoreM ModGuts
testPass guts = do
    let [expr] = [ e | NonRec v e <- mg_binds guts
                     , occNameString (occName v) == "test" ]
    simplified_expression <- simplify guts expr

    putMsg $
        text "Test" $$
        nest 4 (hang (text "Before" <> colon) 4 (ppr expr)) $$
        nest 4 (hang (text "After" <> colon) 4 (ppr simplified_expression))

    liftIO $ exitFailure

-- A simplifier

simplify :: ModGuts -> CoreExpr -> CoreM CoreExpr
simplify guts expr = do
    dflags <- getDynFlags

    let dflags' = dflags { ufVeryAggressive = True }
    us <- liftIO $ mkSplitUniqSupply 's'
    let sz = exprSize expr

    rule_base <- getRuleBase
    vis_orphs <- getVisibleOrphanMods
    let rule_base2 = extendRuleBaseList rule_base (mg_rules guts)
    let rule_env = RuleEnv rule_base2 vis_orphs

    (expr', _) <- liftIO $ initSmpl dflags' rule_env emptyFamInstEnvs us sz $
        simplExpr (simplEnv 1) >=> simplExpr (simplEnv 1) $
        expr
    return expr'

simplEnv :: Int -> SimplEnv
simplEnv p = mkSimplEnv $ SimplMode { sm_names = ["Test"]
                                    , sm_phase = Phase p
                                    , sm_rules = True
                                    , sm_inline = True
                                    , sm_eta_expand = True
                                    , sm_case_case = True }

But I get:

$ ghc-head -package ghc Test.hs
[1 of 2] Compiling TestPlugin       ( TestPlugin.hs, TestPlugin.o )
[2 of 2] Compiling Test             ( Test.hs, Test.o )
Test
    Before: fun (foo (GHC.Types.I# 1#)) (foo (GHC.Types.I# 2#))
    After: fun (foo (GHC.Types.I# 2#)) (foo (GHC.Types.I# 3#))

If I however compile this without the plugin, and look at what’s happening with -dverbose-core2core, I observe this:


test :: Int
test = fun (foo (GHC.Types.I# 1#)) (foo (GHC.Types.I# 2#))

==================== Simplifier ====================
  Max iterations = 4
  SimplMode {Phase = 1 [main],
             inline,
             rules,
             eta-expand,
             case-of-case}

test :: Int
test = fun (foo (GHC.Types.I# 3#)) (foo (GHC.Types.I# 3#))

So what am I doing wrong in my plugin? Any help is appreciated.

Trac metadata
Trac field Value
Version 8.1
Type Bug
TypeOfFailure OtherFailure
Priority normal
Resolution Unresolved
Component GHC API
Test case
Differential revisions
BlockedBy
Related
Blocking
CC
Operating system
Architecture
To upload designs, you'll need to enable LFS and have an admin enable hashed storage. More information