Thank you, looking forward to new Go version with regabi. 在2021年3月25日星期四 UTC+8 上午12:04:39<drc...@google.com> 写道:
> Also, be aware that work that we really hope lands in 1.17 will tinker > with all the call operations. > > The goal is to switch to an ABI that passes parameters to/from calls in > registers, and the way > that ends up expressed in SSA is that first (and we do this part in 1.16) > the parameters to the > call will appear as inputs, and the results will be obtained with > OpSelectN. The call will still > receive and return a memory value, as last input and last element of > result. > > This is then lowered to a machine-agnostic level where some parameters are > stored in memory, > the others in registers -- where the registers appear as inputs/outputs of > the call -- and then > further lowered (simple OpCode rewrite) to machine-specific call > instructions. The registers themselves > are bound in the register allocator, by the "trick" of telling it that > Calls have only a single appropriate > register for each of their inputs/outputs. > > This might look a little odd the first time you see it. > > And, also, the order in which the registers inputs/outputs are encoded is > not fixed; for compiler > efficiency purposes, we *might* reorder them so that all the integer > registers come first, etc. > (This would allow a lot more sharing of register masks.) > > This full change is likely only for amd64 in 1.17, then for other > architectures once we figure out > the exact recipe. It touches many parts of the compiler and runtime. > > On Wednesday, March 24, 2021 at 4:01:36 AM UTC-4 Ge wrote: > >> Thank you Keith for clarification. It's really of great help. >> >> Ge >> >> 在2021年3月24日星期三 UTC+8 上午7:45:13<Keith Randall> 写道: >> >>> On Tuesday, March 23, 2021 at 9:11:13 AM UTC-7 Ge wrote: >>> >>>> >>>> Hi, >>>> Recently I encountered a problem which seems to be related to SSA >>>> optimization >>>> and feels hard to figure out what some SSA operation means. >>>> >>>> Code: >>>> case1: >>>> func main() { >>>> var x int >>>> go func() { >>>> for { >>>> x++ //no matter "-N" compile flags is specified or >>>> not, 'x++' will be optimized >>>> } >>>> }() >>>> println(x) >>>> } >>>> >>>> case2: >>>> func main() { >>>> var x int >>>> go func() { >>>> for { >>>> x++ >>>> dummy() // when empty function 'dummy' is added to this >>>> infinite loop, ''x++' stays last >>>> } >>>> }() >>>> println(x) >>>> } >>>> >>>> //go:noinline >>>> func dummy() { >>>> } >>>> >>>> I tried 'GOSSAFUNC=main.func1 go tool compile case2.go' and found the >>>> key point is >>>> deadcode phase in SSA. Here is CFG before 'early deadcode' phase: >>>> >>>> ``` *ssaoptx.go* >>>> 5 go func() { >>>> 6 for { >>>> 7 x++ >>>> 8 dummy() >>>> 9 } >>>> 10 }() >>>> ``` >>>> >>>> ``` *early copyelim* >>>> >>>> - b1: >>>> - >>>> - v1 (?) = InitMem <mem> >>>> - v2 (?) = SP <uintptr> >>>> - v3 (?) = SB <uintptr> >>>> - v4 (?) = LocalAddr <**int> {&x} v2 v1 >>>> - v5 (5) = Arg <*int> {&x} (&x[*int]) >>>> - v9 (?) = Const64 <int> [1] >>>> - Plain → b2 (*+6*) >>>> >>>> >>>> - b2: ← b1 b4 >>>> - >>>> - v14 (7) = Phi <mem> v1 v12 >>>> - v15 (7) = Copy <*int> v5 (&x[*int]) >>>> - Plain → b3 (7) >>>> >>>> >>>> - b3: ← b2 >>>> - >>>> - v6 (7) = Copy <*int> v5 (&x[*int]) >>>> - v7 (7) = Copy <mem> v14 >>>> - v8 (*+7*) = Load <int> v5 v14 >>>> - v10 (7) = Add64 <int> v8 v9 >>>> - v11 (7) = Store <mem> {int} v5 v10 v14 >>>> - v12 (*+8*) = StaticCall <mem> {"".dummy} v11 >>>> - Plain → b4 (8) >>>> >>>> >>>> - b4: ← b3 >>>> - Plain → b2 (7) >>>> >>>> >>>> - b5: >>>> - >>>> - v13 (10) = Unknown <mem> >>>> - Ret v13 >>>> >>>> ``` >>>> deadcode phase will traverse all blocks and find out the reachable >>>> blocks >>>> (In above example is b1,b2,b3,b4, while b5 is isolated block), Second >>>> it will >>>> find out live values based on reachable blocks and eliminate dead >>>> values. >>>> >>>> The call of dummy function makes v8,v10,v11 all live so 'x++' isn't >>>> optimized. >>>> I have read ssa/README.md but still had some questions. >>>> >>>> 1. The role of InitMem. >>>> It seems that every function starts with it, are some initialize >>>> work like >>>> stack space allocation and named return values initialization done >>>> by it? >>>> >>>> >>> Not really. Stack space and any zeroing required are done when >>> generating the preamble. They are not represented in SSA. >>> InitMem is just the initial state of memory on entry to the function. It >>> does not generate any actual code. >>> >>> >>>> 2. The meaning of 'v14 (7) = Phi <mem> v1 v12'. >>>> It looks like v14 = Φ(v1, v12), but I don't know why InitMem and >>>> dummy function >>>> call will affect here. >>>> >>>> 3. The meaning of of StaticCall's argument . >>>> Some ssa operations are easy to understand, for example, >>>> 'v8 (*+7*) = Load <int> v5 v14' means v8<int>=Load(v5) and v14 >>>> is the memory state which implies this load operation must >>>> happens after v14 is determined. >>>> >>>> That's all I know from README.md, but about other operations like >>>> StaticCall >>>> I can't get enough information. Here is the relevant souce In >>>> genericOps.go: >>>> ``` >>>> >>>> {name: "StaticCall", argLength: 1, aux: "CallOff", call: true}, >>>> >>>> // call function aux.(*obj.LSym), arg0=memory. auxint=arg size. Returns >>>> memory. >>>> ``` >>>> For 'v12 (*+8*) = StaticCall <mem> {"".dummy} v11' the only >>>> argument is v11 but >>>> obviously v11 seems not the address of dummy function. >>>> >>>> >>> The address of the target of the call is not stored in a separate SSA >>> value - it is encoded directly in the StaticCall Value (in the Aux field). >>> Other types of calls (the indirect ones whose target must be computed at >>> runtime, like InterCall) do take a target as an SSA value. >>> >>> >>>> 4. As threre are other incomprehensible ssa operations except InitMem, >>>> Phi, ... , >>>> Is there any documents which can help understanding? >>>> >>>> >>> >>> In general these all have to do with the concept of the "memory" type. >>> Values in SSA can have such a type, which means "the entire state of >>> memory". Function calls, for example, take a memory state as an argument >>> (as well as any explicit arguments) and return a new memory state. Same for >>> stores. Loads take a memory state as input. >>> >>> Phi operations are described here: >>> https://en.wikipedia.org/wiki/Static_single_assignment_form >>> Phis of memory mean the merge of two memory states. >>> >>> >>>> 'Thanks for you time. >>>> >>> -- You received this message because you are subscribed to the Google Groups "golang-nuts" group. To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/golang-nuts/0bc8524d-fc09-46ad-8524-8cf9d5a0ff4fn%40googlegroups.com.