Commit 22d58229 authored by Simon Marlow's avatar Simon Marlow

Foreign calls may clobber caller-saves registers

See Note [foreign calls clobber GlobalRegs]
parent 6228e318
......@@ -51,10 +51,11 @@ data CmmNode e x where
[CmmFormal] -> -- zero or more results
[CmmActual] -> -- zero or more arguments
CmmNode O O
-- Semantics: kills only result regs; all other regs (both GlobalReg
-- and LocalReg) are preserved. But there is a current
-- bug for what can be put in arguments, see
-- Note [Register Parameter Passing]
-- Semantics: clobbers any GlobalRegs for which callerSaves r == True
-- See Note [foreign calls clobber GlobalRegs]
--
-- Also, there is a current bug for what can be put in
-- arguments, see Note [Register Parameter Passing]
CmmBranch :: ULabel -> CmmNode O C
-- Goto another block in the same procedure
......@@ -147,12 +148,31 @@ ultimately expands to
pop "return address"
We cannot "lower" a safe foreign call to this sequence of Cmms, because
after we've saved Sp all the Cmm optimiser's assumptions are broken.
Furthermore, currently the smart Cmm constructors know the calling
conventions for Haskell, the garbage collector, etc, and "lower" them
so that a LastCall passes no parameters or results. But the smart
constructors do *not* (currently) know the foreign call conventions.
Note that a safe foreign call needs an info table.
So Safe Foreign Calls must remain as last nodes until the stack is
made manifest in CmmLayoutStack, where they are lowered into the above
sequence.
-}
{- Note [foreign calls clobber GlobalRegs]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A foreign call is defined to clobber any GlobalRegs that are mapped to
caller-saves machine registers (according to the prevailing C ABI).
StgCmmUtils.callerSaves tells you which GlobalRegs are caller-saves.
This is a design choice that makes it easier to generate code later.
We could instead choose to say that foreign calls do *not* clobber
caller-saves regs, but then we would have to figure out which regs
were live across the call later and insert some saves/restores.
Furthermore when we generate code we never have any GlobalRegs live
across a call, because they are always copied-in to LocalRegs and
copied-out again before making a call/jump. So all we have to do is
avoid any code motion that would make a caller-saves GlobalReg live
across a foreign call during subsequent optimisations.
-}
{- Note [Register parameter passing]
......
......@@ -3,6 +3,8 @@ module CmmSink (
cmmSink
) where
import StgCmmUtils (callerSaves)
import Cmm
import BlockId
import CmmLive
......@@ -347,14 +349,29 @@ conflicts :: Assignment -> CmmNode O x -> Bool
| CmmStore addr' e <- node, memConflicts addr (loadAddr addr' (cmmExprWidth e)) = True
-- (3) an assignment to Hp/Sp conflicts with a heap/stack read respectively
| HeapMem <- addr, CmmAssign (CmmGlobal Hp) _ <- node = True
| StackMem <- addr, CmmAssign (CmmGlobal Sp) _ <- node = True
| SpMem{} <- addr, CmmAssign (CmmGlobal Sp) _ <- node = True
| HeapMem <- addr, CmmAssign (CmmGlobal Hp) _ <- node = True
| StackMem <- addr, CmmAssign (CmmGlobal Sp) _ <- node = True
| SpMem{} <- addr, CmmAssign (CmmGlobal Sp) _ <- node = True
-- (4) assignments that read caller-saves GlobalRegs conflict with a
-- foreign call. See Note [foreign calls clobber GlobalRegs].
| CmmUnsafeForeignCall{} <- node, anyCallerSavesRegs rhs = True
-- (5) foreign calls clobber memory, but not heap/stack memory
| CmmUnsafeForeignCall{} <- node, AnyMem <- addr = True
-- (4) otherwise, no conflict
-- (6) native calls clobber any memory
| CmmCall{} <- node, memConflicts addr AnyMem = True
-- (7) otherwise, no conflict
| otherwise = False
anyCallerSavesRegs :: CmmExpr -> Bool
anyCallerSavesRegs e = wrapRecExpf f e False
where f (CmmReg (CmmGlobal r)) _ | callerSaves r = True
f _ z = z
-- An abstraction of memory read or written.
data AbsMem
= NoMem -- no memory accessed
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment