summaryrefslogtreecommitdiff
path: root/compiler/codeGen/CgClosure.lhs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/codeGen/CgClosure.lhs')
-rw-r--r--compiler/codeGen/CgClosure.lhs599
1 files changed, 599 insertions, 0 deletions
diff --git a/compiler/codeGen/CgClosure.lhs b/compiler/codeGen/CgClosure.lhs
new file mode 100644
index 0000000000..1a2cbc5202
--- /dev/null
+++ b/compiler/codeGen/CgClosure.lhs
@@ -0,0 +1,599 @@
+%
+% (c) The GRASP/AQUA Project, Glasgow University, 1992-1998
+%
+% $Id: CgClosure.lhs,v 1.72 2005/05/18 12:06:51 simonmar Exp $
+%
+\section[CgClosure]{Code generation for closures}
+
+This module provides the support code for @StgToAbstractC@ to deal
+with {\em closures} on the RHSs of let(rec)s. See also
+@CgCon@, which deals with constructors.
+
+\begin{code}
+module CgClosure ( cgTopRhsClosure,
+ cgStdRhsClosure,
+ cgRhsClosure,
+ emitBlackHoleCode,
+ ) where
+
+#include "HsVersions.h"
+
+import {-# SOURCE #-} CgExpr ( cgExpr )
+
+import CgMonad
+import CgBindery
+import CgHeapery
+import CgStackery ( mkVirtStkOffsets, pushUpdateFrame, getVirtSp,
+ setRealAndVirtualSp )
+import CgProf ( chooseDynCostCentres, ldvEnter, enterCostCentre,
+ costCentreFrom )
+import CgTicky
+import CgParallel ( granYield, granFetchAndReschedule )
+import CgInfoTbls ( emitClosureCodeAndInfoTable, getSRTInfo )
+import CgCallConv ( assignCallRegs, mkArgDescr )
+import CgUtils ( emitDataLits, addIdReps, cmmRegOffW,
+ emitRtsCallWithVols )
+import ClosureInfo -- lots and lots of stuff
+import SMRep ( CgRep, cgRepSizeW, argMachRep, fixedHdrSize, WordOff,
+ idCgRep )
+import MachOp ( MachHint(..) )
+import Cmm
+import CmmUtils ( CmmStmts, mkStmts, oneStmt, plusStmts, noStmts,
+ mkLblExpr )
+import CLabel
+import StgSyn
+import StaticFlags ( opt_DoTickyProfiling )
+import CostCentre
+import Id ( Id, idName, idType )
+import Name ( Name, isExternalName )
+import Module ( Module, pprModule )
+import ListSetOps ( minusList )
+import Util ( isIn, mapAccumL, zipWithEqual )
+import BasicTypes ( TopLevelFlag(..) )
+import Constants ( oFFSET_StgInd_indirectee, wORD_SIZE )
+import Outputable
+import FastString
+\end{code}
+
+%********************************************************
+%* *
+\subsection[closures-no-free-vars]{Top-level closures}
+%* *
+%********************************************************
+
+For closures bound at top level, allocate in static space.
+They should have no free variables.
+
+\begin{code}
+cgTopRhsClosure :: Id
+ -> CostCentreStack -- Optional cost centre annotation
+ -> StgBinderInfo
+ -> SRT
+ -> UpdateFlag
+ -> [Id] -- Args
+ -> StgExpr
+ -> FCode (Id, CgIdInfo)
+
+cgTopRhsClosure id ccs binder_info srt upd_flag args body = do
+ { -- LAY OUT THE OBJECT
+ let name = idName id
+ ; lf_info <- mkClosureLFInfo id TopLevel [] upd_flag args
+ ; srt_info <- getSRTInfo name srt
+ ; mod_name <- moduleName
+ ; let descr = closureDescription mod_name name
+ closure_info = mkClosureInfo True id lf_info 0 0 srt_info descr
+ closure_label = mkLocalClosureLabel name
+ cg_id_info = stableIdInfo id (mkLblExpr closure_label) lf_info
+ closure_rep = mkStaticClosureFields closure_info ccs True []
+
+ -- BUILD THE OBJECT, AND GENERATE INFO TABLE (IF NECESSARY)
+ ; emitDataLits closure_label closure_rep
+ ; forkClosureBody (closureCodeBody binder_info closure_info
+ ccs args body)
+
+ ; returnFC (id, cg_id_info) }
+\end{code}
+
+%********************************************************
+%* *
+\subsection[non-top-level-closures]{Non top-level closures}
+%* *
+%********************************************************
+
+For closures with free vars, allocate in heap.
+
+\begin{code}
+cgStdRhsClosure
+ :: Id
+ -> CostCentreStack -- Optional cost centre annotation
+ -> StgBinderInfo
+ -> [Id] -- Free vars
+ -> [Id] -- Args
+ -> StgExpr
+ -> LambdaFormInfo
+ -> [StgArg] -- payload
+ -> FCode (Id, CgIdInfo)
+
+cgStdRhsClosure bndr cc bndr_info fvs args body lf_info payload
+ = do -- AHA! A STANDARD-FORM THUNK
+ { -- LAY OUT THE OBJECT
+ amodes <- getArgAmodes payload
+ ; mod_name <- moduleName
+ ; let (tot_wds, ptr_wds, amodes_w_offsets)
+ = mkVirtHeapOffsets (isLFThunk lf_info) amodes
+
+ descr = closureDescription mod_name (idName bndr)
+ closure_info = mkClosureInfo False -- Not static
+ bndr lf_info tot_wds ptr_wds
+ NoC_SRT -- No SRT for a std-form closure
+ descr
+
+ ; (use_cc, blame_cc) <- chooseDynCostCentres cc args body
+
+ -- BUILD THE OBJECT
+ ; heap_offset <- allocDynClosure closure_info use_cc blame_cc amodes_w_offsets
+
+ -- RETURN
+ ; returnFC (bndr, heapIdInfo bndr heap_offset lf_info) }
+\end{code}
+
+Here's the general case.
+
+\begin{code}
+cgRhsClosure :: Id
+ -> CostCentreStack -- Optional cost centre annotation
+ -> StgBinderInfo
+ -> SRT
+ -> [Id] -- Free vars
+ -> UpdateFlag
+ -> [Id] -- Args
+ -> StgExpr
+ -> FCode (Id, CgIdInfo)
+
+cgRhsClosure bndr cc bndr_info srt fvs upd_flag args body = do
+ { -- LAY OUT THE OBJECT
+ -- If the binder is itself a free variable, then don't store
+ -- it in the closure. Instead, just bind it to Node on entry.
+ -- NB we can be sure that Node will point to it, because we
+ -- havn't told mkClosureLFInfo about this; so if the binder
+ -- _was_ a free var of its RHS, mkClosureLFInfo thinks it *is*
+ -- stored in the closure itself, so it will make sure that
+ -- Node points to it...
+ let
+ name = idName bndr
+ is_elem = isIn "cgRhsClosure"
+ bndr_is_a_fv = bndr `is_elem` fvs
+ reduced_fvs | bndr_is_a_fv = fvs `minusList` [bndr]
+ | otherwise = fvs
+
+ ; lf_info <- mkClosureLFInfo bndr NotTopLevel fvs upd_flag args
+ ; fv_infos <- mapFCs getCgIdInfo reduced_fvs
+ ; srt_info <- getSRTInfo name srt
+ ; mod_name <- moduleName
+ ; let bind_details :: [(CgIdInfo, VirtualHpOffset)]
+ (tot_wds, ptr_wds, bind_details)
+ = mkVirtHeapOffsets (isLFThunk lf_info) (map add_rep fv_infos)
+
+ add_rep info = (cgIdInfoArgRep info, info)
+
+ descr = closureDescription mod_name name
+ closure_info = mkClosureInfo False -- Not static
+ bndr lf_info tot_wds ptr_wds
+ srt_info descr
+
+ -- BUILD ITS INFO TABLE AND CODE
+ ; forkClosureBody (do
+ { -- Bind the fvs
+ let bind_fv (info, offset)
+ = bindNewToNode (cgIdInfoId info) offset (cgIdInfoLF info)
+ ; mapCs bind_fv bind_details
+
+ -- Bind the binder itself, if it is a free var
+ ; whenC bndr_is_a_fv (bindNewToReg bndr nodeReg lf_info)
+
+ -- Compile the body
+ ; closureCodeBody bndr_info closure_info cc args body })
+
+ -- BUILD THE OBJECT
+ ; let
+ to_amode (info, offset) = do { amode <- idInfoToAmode info
+ ; return (amode, offset) }
+ ; (use_cc, blame_cc) <- chooseDynCostCentres cc args body
+ ; amodes_w_offsets <- mapFCs to_amode bind_details
+ ; heap_offset <- allocDynClosure closure_info use_cc blame_cc amodes_w_offsets
+
+ -- RETURN
+ ; returnFC (bndr, heapIdInfo bndr heap_offset lf_info) }
+
+
+mkClosureLFInfo :: Id -- The binder
+ -> TopLevelFlag -- True of top level
+ -> [Id] -- Free vars
+ -> UpdateFlag -- Update flag
+ -> [Id] -- Args
+ -> FCode LambdaFormInfo
+mkClosureLFInfo bndr top fvs upd_flag args
+ | null args = return (mkLFThunk (idType bndr) top fvs upd_flag)
+ | otherwise = do { arg_descr <- mkArgDescr (idName bndr) args
+ ; return (mkLFReEntrant top fvs args arg_descr) }
+\end{code}
+
+
+%************************************************************************
+%* *
+\subsection[code-for-closures]{The code for closures}
+%* *
+%************************************************************************
+
+\begin{code}
+closureCodeBody :: StgBinderInfo
+ -> ClosureInfo -- Lots of information about this closure
+ -> CostCentreStack -- Optional cost centre attached to closure
+ -> [Id]
+ -> StgExpr
+ -> Code
+\end{code}
+
+There are two main cases for the code for closures. If there are {\em
+no arguments}, then the closure is a thunk, and not in normal form.
+So it should set up an update frame (if it is shared).
+NB: Thunks cannot have a primitive type!
+
+\begin{code}
+closureCodeBody binder_info cl_info cc [{- No args i.e. thunk -}] body = do
+ { body_absC <- getCgStmts $ do
+ { tickyEnterThunk cl_info
+ ; ldvEnter (CmmReg nodeReg) -- NB: Node always points when profiling
+ ; thunkWrapper cl_info $ do
+ -- We only enter cc after setting up update so
+ -- that cc of enclosing scope will be recorded
+ -- in update frame CAF/DICT functions will be
+ -- subsumed by this enclosing cc
+ { enterCostCentre cl_info cc body
+ ; cgExpr body }
+ }
+
+ ; emitClosureCodeAndInfoTable cl_info [] body_absC }
+\end{code}
+
+If there is /at least one argument/, then this closure is in
+normal form, so there is no need to set up an update frame.
+
+The Macros for GrAnSim are produced at the beginning of the
+argSatisfactionCheck (by calling fetchAndReschedule). There info if
+Node points to closure is available. -- HWL
+
+\begin{code}
+closureCodeBody binder_info cl_info cc args body
+ = ASSERT( length args > 0 )
+ do { -- Get the current virtual Sp (it might not be zero,
+ -- eg. if we're compiling a let-no-escape).
+ vSp <- getVirtSp
+ ; let (reg_args, other_args) = assignCallRegs (addIdReps args)
+ (sp_top, stk_args) = mkVirtStkOffsets vSp other_args
+
+ -- Allocate the global ticky counter
+ ; let ticky_ctr_lbl = mkRednCountsLabel (closureName cl_info)
+ ; emitTickyCounter cl_info args sp_top
+
+ -- ...and establish the ticky-counter
+ -- label for this block
+ ; setTickyCtrLabel ticky_ctr_lbl $ do
+
+ -- Emit the slow-entry code
+ { reg_save_code <- mkSlowEntryCode cl_info reg_args
+
+ -- Emit the main entry code
+ ; blks <- forkProc $
+ mkFunEntryCode cl_info cc reg_args stk_args
+ sp_top reg_save_code body
+ ; emitClosureCodeAndInfoTable cl_info [] blks
+ }}
+
+
+
+mkFunEntryCode :: ClosureInfo
+ -> CostCentreStack
+ -> [(Id,GlobalReg)] -- Args in regs
+ -> [(Id,VirtualSpOffset)] -- Args on stack
+ -> VirtualSpOffset -- Last allocated word on stack
+ -> CmmStmts -- Register-save code in case of GC
+ -> StgExpr
+ -> Code
+-- The main entry code for the closure
+mkFunEntryCode cl_info cc reg_args stk_args sp_top reg_save_code body = do
+ { -- Bind args to regs/stack as appropriate,
+ -- and record expected position of sps
+ ; bindArgsToRegs reg_args
+ ; bindArgsToStack stk_args
+ ; setRealAndVirtualSp sp_top
+
+ -- Enter the cost-centre, if required
+ -- ToDo: It's not clear why this is outside the funWrapper,
+ -- but the tickyEnterFun is inside. Perhaps we can put
+ -- them together?
+ ; enterCostCentre cl_info cc body
+
+ -- Do the business
+ ; funWrapper cl_info reg_args reg_save_code $ do
+ { tickyEnterFun cl_info
+ ; cgExpr body }
+ }
+\end{code}
+
+The "slow entry" code for a function. This entry point takes its
+arguments on the stack. It loads the arguments into registers
+according to the calling convention, and jumps to the function's
+normal entry point. The function's closure is assumed to be in
+R1/node.
+
+The slow entry point is used in two places:
+
+ (a) unknown calls: eg. stg_PAP_entry
+ (b) returning from a heap-check failure
+
+\begin{code}
+mkSlowEntryCode :: ClosureInfo -> [(Id,GlobalReg)] -> FCode CmmStmts
+-- If this function doesn't have a specialised ArgDescr, we need
+-- to generate the function's arg bitmap, slow-entry code, and
+-- register-save code for the heap-check failure
+-- Here, we emit the slow-entry code, and
+-- return the register-save assignments
+mkSlowEntryCode cl_info reg_args
+ | Just (_, ArgGen _) <- closureFunInfo cl_info
+ = do { emitSimpleProc slow_lbl (emitStmts load_stmts)
+ ; return save_stmts }
+ | otherwise = return noStmts
+ where
+ name = closureName cl_info
+ slow_lbl = mkSlowEntryLabel name
+
+ load_stmts = mkStmts load_assts `plusStmts` mkStmts [stk_adj_pop, jump_to_entry]
+ save_stmts = oneStmt stk_adj_push `plusStmts` mkStmts save_assts
+
+ reps_w_regs :: [(CgRep,GlobalReg)]
+ reps_w_regs = [(idCgRep id, reg) | (id,reg) <- reverse reg_args]
+ (final_stk_offset, stk_offsets)
+ = mapAccumL (\off (rep,_) -> (off + cgRepSizeW rep, off))
+ 0 reps_w_regs
+
+ load_assts = zipWithEqual "mk_load" mk_load reps_w_regs stk_offsets
+ mk_load (rep,reg) offset = CmmAssign (CmmGlobal reg)
+ (CmmLoad (cmmRegOffW spReg offset)
+ (argMachRep rep))
+
+ save_assts = zipWithEqual "mk_save" mk_save reps_w_regs stk_offsets
+ mk_save (rep,reg) offset = ASSERT( argMachRep rep == globalRegRep reg )
+ CmmStore (cmmRegOffW spReg offset)
+ (CmmReg (CmmGlobal reg))
+
+ stk_adj_pop = CmmAssign spReg (cmmRegOffW spReg final_stk_offset)
+ stk_adj_push = CmmAssign spReg (cmmRegOffW spReg (- final_stk_offset))
+ jump_to_entry = CmmJump (mkLblExpr (enterLocalIdLabel name)) []
+\end{code}
+
+
+%************************************************************************
+%* *
+\subsubsection[closure-code-wrappers]{Wrappers around closure code}
+%* *
+%************************************************************************
+
+\begin{code}
+thunkWrapper:: ClosureInfo -> Code -> Code
+thunkWrapper closure_info thunk_code = do
+ { let node_points = nodeMustPointToIt (closureLFInfo closure_info)
+
+ -- HWL: insert macros for GrAnSim; 2 versions depending on liveness of node
+ -- (we prefer fetchAndReschedule-style context switches to yield ones)
+ ; if node_points
+ then granFetchAndReschedule [] node_points
+ else granYield [] node_points
+
+ -- Stack and/or heap checks
+ ; thunkEntryChecks closure_info $ do
+ { -- Overwrite with black hole if necessary
+ whenC (blackHoleOnEntry closure_info && node_points)
+ (blackHoleIt closure_info)
+ ; setupUpdate closure_info thunk_code }
+ -- setupUpdate *encloses* the thunk_code
+ }
+
+funWrapper :: ClosureInfo -- Closure whose code body this is
+ -> [(Id,GlobalReg)] -- List of argument registers (if any)
+ -> CmmStmts -- reg saves for the heap check failure
+ -> Code -- Body of function being compiled
+ -> Code
+funWrapper closure_info arg_regs reg_save_code fun_body = do
+ { let node_points = nodeMustPointToIt (closureLFInfo closure_info)
+
+ -- Enter for Ldv profiling
+ ; whenC node_points (ldvEnter (CmmReg nodeReg))
+
+ -- GranSim yeild poin
+ ; granYield arg_regs node_points
+
+ -- Heap and/or stack checks wrap the function body
+ ; funEntryChecks closure_info reg_save_code
+ fun_body
+ }
+\end{code}
+
+
+%************************************************************************
+%* *
+\subsubsubsection[update-and-BHs]{Update and black-hole wrappers}
+%* *
+%************************************************************************
+
+
+\begin{code}
+blackHoleIt :: ClosureInfo -> Code
+-- Only called for closures with no args
+-- Node points to the closure
+blackHoleIt closure_info = emitBlackHoleCode (closureSingleEntry closure_info)
+
+emitBlackHoleCode :: Bool -> Code
+emitBlackHoleCode is_single_entry
+ | eager_blackholing = do
+ tickyBlackHole (not is_single_entry)
+ stmtC (CmmStore (CmmReg nodeReg) (CmmLit (CmmLabel bh_lbl)))
+ | otherwise =
+ nopC
+ where
+ bh_lbl | is_single_entry = mkRtsDataLabel SLIT("stg_SE_BLACKHOLE_info")
+ | otherwise = mkRtsDataLabel SLIT("stg_BLACKHOLE_info")
+
+ -- If we wanted to do eager blackholing with slop filling,
+ -- we'd need to do it at the *end* of a basic block, otherwise
+ -- we overwrite the free variables in the thunk that we still
+ -- need. We have a patch for this from Andy Cheadle, but not
+ -- incorporated yet. --SDM [6/2004]
+ --
+ -- Profiling needs slop filling (to support LDV profiling), so
+ -- currently eager blackholing doesn't work with profiling.
+ --
+ -- TICKY_TICKY needs EAGER_BLACKHOLING to verify no double-entries of
+ -- single-entry thunks.
+ eager_blackholing
+ | opt_DoTickyProfiling = True
+ | otherwise = False
+
+\end{code}
+
+\begin{code}
+setupUpdate :: ClosureInfo -> Code -> Code -- Only called for closures with no args
+ -- Nota Bene: this function does not change Node (even if it's a CAF),
+ -- so that the cost centre in the original closure can still be
+ -- extracted by a subsequent enterCostCentre
+setupUpdate closure_info code
+ | closureReEntrant closure_info
+ = code
+
+ | not (isStaticClosure closure_info)
+ = if closureUpdReqd closure_info
+ then do { tickyPushUpdateFrame; pushUpdateFrame (CmmReg nodeReg) code }
+ else do { tickyUpdateFrameOmitted; code }
+
+ | otherwise -- A static closure
+ = do { tickyUpdateBhCaf closure_info
+
+ ; if closureUpdReqd closure_info
+ then do -- Blackhole the (updatable) CAF:
+ { upd_closure <- link_caf closure_info True
+ ; pushUpdateFrame upd_closure code }
+ else do
+ { -- No update reqd, you'd think we don't need to
+ -- black-hole it. But when ticky-ticky is on, we
+ -- black-hole it regardless, to catch errors in which
+ -- an allegedly single-entry closure is entered twice
+ --
+ -- We discard the pointer returned by link_caf, because
+ -- we don't push an update frame
+ whenC opt_DoTickyProfiling -- Blackhole even a SE CAF
+ (link_caf closure_info False >> nopC)
+ ; tickyUpdateFrameOmitted
+ ; code }
+ }
+
+
+-----------------------------------------------------------------------------
+-- Entering a CAF
+--
+-- When a CAF is first entered, it creates a black hole in the heap,
+-- and updates itself with an indirection to this new black hole.
+--
+-- We update the CAF with an indirection to a newly-allocated black
+-- hole in the heap. We also set the blocking queue on the newly
+-- allocated black hole to be empty.
+--
+-- Why do we make a black hole in the heap when we enter a CAF?
+--
+-- - for a generational garbage collector, which needs a fast
+-- test for whether an updatee is in an old generation or not
+--
+-- - for the parallel system, which can implement updates more
+-- easily if the updatee is always in the heap. (allegedly).
+--
+-- When debugging, we maintain a separate CAF list so we can tell when
+-- a CAF has been garbage collected.
+
+-- newCAF must be called before the itbl ptr is overwritten, since
+-- newCAF records the old itbl ptr in order to do CAF reverting
+-- (which Hugs needs to do in order that combined mode works right.)
+--
+
+-- ToDo [Feb 04] This entire link_caf nonsense could all be moved
+-- into the "newCAF" RTS procedure, which we call anyway, including
+-- the allocation of the black-hole indirection closure.
+-- That way, code size would fall, the CAF-handling code would
+-- be closer together, and the compiler wouldn't need to know
+-- about off_indirectee etc.
+
+link_caf :: ClosureInfo
+ -> Bool -- True <=> updatable, False <=> single-entry
+ -> FCode CmmExpr -- Returns amode for closure to be updated
+-- To update a CAF we must allocate a black hole, link the CAF onto the
+-- CAF list, then update the CAF to point to the fresh black hole.
+-- This function returns the address of the black hole, so it can be
+-- updated with the new value when available. The reason for all of this
+-- is that we only want to update dynamic heap objects, not static ones,
+-- so that generational GC is easier.
+link_caf cl_info is_upd = do
+ { -- Alloc black hole specifying CC_HDR(Node) as the cost centre
+ ; let use_cc = costCentreFrom (CmmReg nodeReg)
+ blame_cc = use_cc
+ ; hp_offset <- allocDynClosure bh_cl_info use_cc blame_cc []
+ ; hp_rel <- getHpRelOffset hp_offset
+
+ -- Call the RTS function newCAF to add the CAF to the CafList
+ -- so that the garbage collector can find them
+ -- This must be done *before* the info table pointer is overwritten,
+ -- because the old info table ptr is needed for reversion
+ ; emitRtsCallWithVols SLIT("newCAF") [(CmmReg nodeReg,PtrHint)] [node]
+ -- node is live, so save it.
+
+ -- Overwrite the closure with a (static) indirection
+ -- to the newly-allocated black hole
+ ; stmtsC [ CmmStore (cmmRegOffW nodeReg off_indirectee) hp_rel
+ , CmmStore (CmmReg nodeReg) ind_static_info ]
+
+ ; returnFC hp_rel }
+ where
+ bh_cl_info :: ClosureInfo
+ bh_cl_info | is_upd = cafBlackHoleClosureInfo cl_info
+ | otherwise = seCafBlackHoleClosureInfo cl_info
+
+ ind_static_info :: CmmExpr
+ ind_static_info = mkLblExpr mkIndStaticInfoLabel
+
+ off_indirectee :: WordOff
+ off_indirectee = fixedHdrSize + oFFSET_StgInd_indirectee*wORD_SIZE
+\end{code}
+
+
+%************************************************************************
+%* *
+\subsection[CgClosure-Description]{Profiling Closure Description.}
+%* *
+%************************************************************************
+
+For "global" data constructors the description is simply occurrence
+name of the data constructor itself. Otherwise it is determined by
+@closureDescription@ from the let binding information.
+
+\begin{code}
+closureDescription :: Module -- Module
+ -> Name -- Id of closure binding
+ -> String
+ -- Not called for StgRhsCon which have global info tables built in
+ -- CgConTbls.lhs with a description generated from the data constructor
+closureDescription mod_name name
+ = showSDocDump (char '<' <>
+ (if isExternalName name
+ then ppr name -- ppr will include the module name prefix
+ else pprModule mod_name <> char '.' <> ppr name) <>
+ char '>')
+ -- showSDocDump, because we want to see the unique on the Name.
+\end{code}
+