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/19b3527c-e490-4c83-ac36-d8a8aa6acfccn%40googlegroups.com.

Reply via email to