core2core

まあ、よい機会なので少しCore Simplifierがどんな感じなのか概観してみることに。以下の話は、GHC-6.6のソースに基づいている。GHC compilerの中核は、main/HscMain.lhsに記述されている。hscSimplifyとか見ると、実質的にcore2coreを読んでいるだけなので、core2coreを見る。こいつは、SimplCore/SimplCore.lhsにある

core2core :: HscEnv
	  -> ModGuts
	  -> IO ModGuts

core2core hsc_env guts
  = do
        let dflags = hsc_dflags hsc_env
	    core_todos = getCoreToDo dflags

	us <- mkSplitUniqSupply 's'
	let (cp_us, ru_us) = splitUniqSupply us

		-- COMPUTE THE RULE BASE TO USE
	(imp_rule_base, guts') <- prepareRules hsc_env guts ru_us

		-- DO THE BUSINESS
	(stats, guts'') <- doCorePasses hsc_env imp_rule_base cp_us
			 		(zeroSimplCount dflags) 
					guts' core_todos

	dumpIfSet_dyn dflags Opt_D_dump_simpl_stats
		  "Grand total simplifier statistics"
		  (pprSimplCount stats)

	return guts''

まあ、よく分からんが、主要な処理は、getCoreToDoで、やるべき処理を決めて、使うRULESプラグマを用意するとかの前処理をして、doCorePassesでメイン処理。getCoreToDoは、main/DynFlags.lhsにあって、dflagsはコンパイラオプションに相当するデータ。


getCoreToDoが返すのは、[CoreToDo]型で、CoreToDoは、同じくDynFlags.lhsに

data CoreToDo		-- These are diff core-to-core passes,
			-- which may be invoked in any order,
  			-- as many times as you like.

  = CoreDoSimplify SimplifierMode [SimplifierSwitch]
  | CoreDoFloatInwards
  | CoreDoFloatOutwards FloatOutSwitches
  | CoreLiberateCase
  | CoreDoPrintCore
  | CoreDoStaticArgs
  | CoreDoStrictness
  | CoreDoWorkerWrapper
  | CoreDoSpecialising
  | CoreDoSpecConstr
  | CoreDoOldStrictness
  | CoreDoGlomBinds
  | CoreCSE
  | CoreDoRuleCheck Int{-CompilerPhase-} String	-- Check for non-application of rules 
						-- matching this string
  | CoreDoNothing 	 -- useful when building up lists of these things

とか、それっぽいのが並んでる。


doCorePassesは、CoreToDoリストの先頭から順に処理を行うだけ

doCorePasses :: HscEnv
             -> RuleBase        -- the imported main rule base
             -> UniqSupply      -- uniques
	     -> SimplCount      -- simplifier stats
             -> ModGuts	        -- local binds in (with rules attached)
             -> [CoreToDo]      -- which passes to do
             -> IO (SimplCount, ModGuts)

doCorePasses hsc_env rb us stats guts []
  = return (stats, guts)

doCorePasses hsc_env rb us stats guts (to_do : to_dos) 
  = do
	let (us1, us2) = splitUniqSupply us
	(stats1, guts1) <- doCorePass to_do hsc_env us1 rb guts
	doCorePasses hsc_env rb us2 (stats `plusSimplCount` stats1) guts1 to_dos

doCorePass (CoreDoSimplify mode sws)   = _scc_ "Simplify"      simplifyPgm mode sws
doCorePass CoreCSE		       = _scc_ "CommonSubExpr" trBinds  cseProgram
doCorePass CoreLiberateCase	       = _scc_ "LiberateCase"  trBinds  liberateCase
doCorePass CoreDoFloatInwards          = _scc_ "FloatInwards"  trBinds  floatInwards
doCorePass (CoreDoFloatOutwards f)     = _scc_ "FloatOutwards" trBindsU (floatOutwards f)
doCorePass CoreDoStaticArgs	       = _scc_ "StaticArgs"    trBinds  doStaticArgs
doCorePass CoreDoStrictness	       = _scc_ "Stranal"       trBinds  dmdAnalPgm
doCorePass CoreDoWorkerWrapper         = _scc_ "WorkWrap"      trBindsU wwTopBinds
doCorePass CoreDoSpecialising          = _scc_ "Specialise"    trBindsU specProgram
doCorePass CoreDoSpecConstr	       = _scc_ "SpecConstr"    trBindsU specConstrProgram
doCorePass CoreDoGlomBinds	       = trBinds glomBinds
doCorePass CoreDoPrintCore	       = observe printCore
doCorePass (CoreDoRuleCheck phase pat) = observe (ruleCheck phase pat)
doCorePass CoreDoNothing	       = observe (\ _ _ -> return ())

trBindsやtrBindsUは第一引数の関数に処理を丸投げしてるだけなのだけど、RuleBaseとかは全く使ってない。というわけで、simplifyPgmを見ようと思ったけど、微妙に長いのでやめた(ダメ


まあ、知りたかったのは、Core Simplifierがどういう順番で、最適化を行うかということで、それはgetCoreToDoが決める。普通に最適化オプション(-O)付けると、返ってくるCoreToDoリストは

[CoreDoSimplify SimplGently [NoCaseOfCase, MaxSimplifierIterations max_iter],

 CoreDoSpecialising,

 if full_laziness then CoreDoFloatOutwards (FloatOutSw False False) else CoreDoNothing,

 CoreDoFloatInwards,

 CoreDoSimplify (SimplPhase 2) [MaxSimplifierIterations max_iter],

 case rule_check of { Just pat -> CoreDoRuleCheck 2 pat; Nothing -> CoreDoNothing },

 CoreDoSimplify (SimplPhase 1) [MaxSimplifierIterations max_iter],

 case rule_check of { Just pat -> CoreDoRuleCheck 1 pat; Nothing -> CoreDoNothing },

 CoreDoSimplify (SimplPhase 0) [MaxSimplifierIterations 3],

 case rule_check of { Just pat -> CoreDoRuleCheck 0 pat; Nothing -> CoreDoNothing },

#ifdef OLD_STRICTNESS
 CoreDoOldStrictness
#endif

 if strictness then CoreDoStrictness else CoreDoNothing,

 CoreDoWorkerWrapper,

 CoreDoGlomBinds,

 CoreDoSimplify (SimplPhase 0) [MaxSimplifierIterations max_iter],

 if full_laziness then CoreDoFloatOutwards (FloatOutSw False True) else CoreDoNothing,

 if cse then CoreCSE else CoreDoNothing,

 CoreDoFloatInwards,

 case rule_check of { Just pat -> CoreDoRuleCheck 0 pat; Nothing -> CoreDoNothing },

 CoreDoSimplify (SimplPhase 0) [MaxSimplifierIterations max_iter]
]

という感じになっている。結局、"hylo In lengthNT out (hylo In (mapNT suc) out ls)"が最適化されないのは、何故なんだぜ?と、より一層疑問が深まっただけなのだった。普通にphase control付けないRULESプラグマはどこで処理されるんだろう。探すのめんどくせ


そして、よく読んだら、こういう話はOnoueのD論に全部書いてあった(だから、リンク張ったやつくらいちゃんと読もうよ>私)


#どうでもいいけど、外人とか平気で、敬称なしで書いてるけど、日本人だとエライ違和感ある。「Onoueさん」と書きたくなってしまう。まあ、日本人でも、歴史上の人物とかは、地名みたいなもんで、特に敬意もわかないんだけど(織田信長とか、さん付けする人はあまりいない)。あと、教科書に名前が載ったり、定理名になったりするくらいの人は、私の中で歴史上の人物扱いになる(Knuthとかビル・ゲイツとかね)。ということから、エライ人に限って、さん付けされないという幾分奇妙な現象が起きる。けどまあ、エライ人に会うことはまずないのでいいのだ。エラくない人だと、新宿でばったり会って、お前何呼び捨てしてんねんとか因縁つけられる可能性が!(ありません)。まあ、うちから新宿行くには、一回乗換えが必要で面倒なので余り行かないのでセーフ。


ところで、

getCoreToDo dflags
  | Just todo <- coreToDo dflags = todo -- set explicitly by user
  | otherwise = core_todo
  where ...

となってるので、頑張ればコンパイラオプションで、最適化の順序を完全にユーザがコントロールできるんだろうか(そのうち調べるかもしれない)


GHCのExtCoreについて
http://citeseer.ist.psu.edu/tolmach01external.html
(あとで読む)