I was wondering how much better it is to use TypeFor (a static operation) versus TypeOf, when the type is known statically, and the answer is: it's worse, because TypeFor is implemented as a subroutine around TypeOf. In particular it has to handle a theoretical but impossible nil case when casting PtrType.Elem.
Presumably TypeFor could be intrinsified to use the same logic in the compiler that constructs the rtype during type erasure of interface conversions, and then it would return the type:*int
symbol directly.
Compare:
func f() reflect.Type {
return reflect.TypeOf((*int)(nil))
}
main.f STEXT size=32 args=0x0 locals=0x0 funcid=0x0 align=0x0 leaf
...
0x0000 00000 L0031 HINT $0
0x0004 00004 L1312 HINT $0
0x0008 00008 L0032 MOVD $go:itab.*reflect.rtype,reflect.Type(SB), R0
0x0010 00016 L0032 MOVD $type:*int(SB), R1
0x0018 00024 L2733 RET (R30)
And:
func f() reflect.Type {
return reflect.TypeFor[*int]()
}
main.f STEXT size=48 args=0x0 locals=0x0 funcid=0x0 align=0x0 leaf
...
0x0000 00000 L0031 HINT $0
0x0004 00004 L0031 PCDATA $0, $-3
0x0004 00004 L1317 MOVD reflect..dict.TypeFor[*int](SB), R2
0x000c 00012 L1317 PCDATA $0, $-1
0x000c 00012 L0193 MOVD (R2), R2
0x0010 00016 L0193 MOVD 48(R2), R2 // load PtrType.Elem
0x0014 00020 NOP
0x0014 00020 NOP
0x0014 00020 NOP
0x0014 00020 L2733 CMP $0, R2
0x0018 00024 L0033 MOVD $go:itab.*reflect.rtype,reflect.Type(SB), R3
0x0020 00032 L0033 CSEL NE, R3, ZR, R0
0x0024 00036 L0033 CSEL NE, R2, ZR, R1
0x0028 00040 L2733 RET (R30)
Comment From: gabyhelp
Related Issues
- cmd/compile: inline reflect.TypeOf (or make it intrinsic) #16869
- reflect: add TypeFor #60088 (closed)
- cmd/compile: Devirtualize calls when concrete type behind interface is statically known #19361 (closed)
- reflect, cmd/compile: TypeOf(a) == TypeOf(b) doesn't optimize well #65524 (closed)
- cmd/compile: optimize reflect.TypeOf(x)==reflect.TypeOf(y) #70738 (closed)
Related Code Changes
- reflect: simplify TypeOf implementation
- reflect: avoid TypeOf in init
- reflect: optimize TypeFor for non-interface types
Related Discussions
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)
Comment From: jakebailey
Funnily, I actually have this on a branch, I just wasn't sure if there was interest in intrinsifying it in the compiler itself.
Absolutely happy to send that over.
Comment From: gopherbot
Change https://go.dev/cl/700136 mentions this issue: cmd/compile/internal/ssa: handle reflect.TypeFor in SSA
Comment From: mateusz834
I just wasn't sure if there was interest in intrinsifying it in the compiler itself.
Have you tried doing this without intrinsifying specifically the reflect.TypeFor
?
I remember once thinking about that, and my idea was to create something similar to:
https://github.com/golang/go/blob/8bcda6c79d40f49363cd04cdaf5bd7909de8f94f/src/cmd/compile/internal/ssa/_gen/generic.rules#L2760-L2774
I have not tried doing it since, so it might be more complex though.
Comment From: jakebailey
That seems like a reasonable idea.
My CL doesn't seem to work anymore, unfortunately; I guess enough has changed in the past 9 months to make what I have no longer work. Probably improved inlining causing the call to go away and so my rewrite never hits.
Comment From: mateusz834
Probably improved inlining causing the call to go away and so my rewrite never hits.
That's me! 😄 CL 648395
Comment From: jakebailey
Bah, I remember seeing that and thinking it'd mess up my CL 😄
That's what I get for trying to hold back on my CLs until typescript-go was public...
Comment From: jakebailey
It turns out that half the problem is that the rewrite rules mentioned in https://github.com/golang/go/issues/75203#issuecomment-3239446773 are missing all of the "no offset" cases. I need to test to see what else that changes.
The other half is unfortunate, though. Similar to #37753, the phielim
/copyelim
stage merges multiple Copy
values that point to the same place, but does so without consindering their types. This means that the SSA "forgets" that it's trying to OffPtr
a *abi.PtrType
, and so I can't write a rewrite rule that targets reads from Elem
, because all I have is *abi.Type
, so have no idea the original Load
was from *abi.PtrType
.
The other way I'd handle this is to instead stop special casing reads from type:...
and instead have their structure like other syms, but I can't figure out where that should go, or if that info is even available. It seems like things like type:*int
are totally opaque, having type Sxxx
, not even SRODATA
.
Comment From: fengyoulin
I have a CL that optimizes away the dictionaries. I'm still working on it.
Comment From: gopherbot
Change https://go.dev/cl/700336 mentions this issue: cmd/compile/internal/ssa: load constant values from dictionaries
Comment From: jakebailey
That CL is the first part I described, yes (though it can be done for all of the rules).
I'm also working on a solution to the second problem. I don't like how it's turning out, but is technically in line with the existing special case.
Comment From: fengyoulin
Done.
main.f STEXT size=32 args=0x0 locals=0x0 funcid=0x0 align=0x0 leaf
...
0x0000 00000 L0007 HINT $0
0x0004 00004 L1317 HINT $0
0x0008 00008 L1317 HINT $0
0x000c 00012 L0008 MOVD $go:itab.*reflect.rtype,reflect.Type(SB), R0
0x0014 00020 L0008 MOVD $type:int(SB), R1
0x001c 00028 L2735 RET (R30)
Comment From: prattmic
cc @golang/compiler