Redesign treatment of intra-module static indirections.
Motivation
Consider this module:
x1,x2,x3 :: Int
x1 = 1
x2 = 1
x3 = 1
{-# NOINLINE f #-}
f, g, h :: Num a => a -> a
f x = x + 1
g x = f x
h = f
Here we expect x2,x3 to be indirections to x1. g
and h
also simply indirectly call f
.
This looks somewhat like we expect:
x1 = CCS_DONT_CARE I#! [1#];
x2 = \u [] x1;
x3 = \u [] x1;
lvl_rCi = CCS_DONT_CARE S#! [1#];
$wf =
\r [ww_sD3 ww1_sD4 w_sD5]
let { sat_sD6 = \u [] ww1_sD4 lvl_rCi; } in ww_sD3 w_sD5 sat_sD6;
f = \r [w_sD7 w1_sD8]
case w_sD7 of {
C:Num ww1_sDa _ _ _ _ _ ww7_sDg -> $wf ww1_sDa ww7_sDg w1_sD8;
};
g = \r [eta_B2 eta_B1] f eta_B2 eta_B1;
h = \r [eta_B2 eta_B1] f eta_B2 eta_B1;
It's fairly obvious that both the calls and the values are just indirections.
In Cmm this becomes obvious: x2 and x3 are compiled to static indirections like the one below:
==================== Output Cmm ====================
[section ""data" . x2_closure" {
x2_closure:
const stg_IND_STATIC_info;
const x1_closure+1;
const 0;
const 0;
}]
Similar for g
:
[g_entry() { // [R3, R2]
{ info_tbls: [(cE2,
label: g_info
rep: HeapRep static { Fun {arity: 2 fun_type: ArgSpec 15} }
srt: Nothing)]
stack_info: arg_space: 8 updfr_space: Just 8
}
{offset
cE2:
R3 = R3;
R2 = R2;
call f_info(R3, R2) args: 8, res: 0, upd: 8;
}
},
section ""data" . g_closure" {
g_closure:
const g_info;
}]
For x2,x3 we have a hack in the backend which will remove the static indirection and replace it with an assembly aliasing directive. See also !10 (merged)
For g
, h
we generate the expected code (which thankfully is only a jump, but still).
It's trivial to realize these bindings will result in indirections when looking at the STG Syntax.
Proposal
- During STG -> Cmm codegen look for bindings of the above forms.
- Return a cg_loc representing the target of the indirection.
- Return a new type of CmmDecl for the backend, which makes the indirection explicit.
Something like:
data GenCmmDecl d h g
= ...
| CmmInd
{ indirectee :: CLabel
, label :: CLabel
, isFunction :: FunctionOrData
}
- The backend decides how to handle those Indirections explicitly.
Pros and Cons
Pros
- Backends don't have to resort to pattern matching on Cmm to eliminate STATIC_IND.
- We have to run less Cmm code through the pipeline which will help performance.
- Cmm dumps correspond better to the actual binaries.
- It would seamlessly work for call indirections.
- It would make it easier to expose this information in interface files. Allowing us to eliminate IND_STATIC eventually. See #16831
Cons
- Implementation is required.
- Adding the CmmInd constructor means all places have to handle it.
- Adding a new constructor to CmmDecl might slow down the compiler slightly.
I think doing this is worthwhile if we are going to eliminate IND_STATIC completely.
Until then the current approach in !10 (merged) works well enough and is performant. However it doesn't cover (indirect) function calls, so this still wouldn't be wasted work. Just not a priority.