-
Notifications
You must be signed in to change notification settings - Fork 974
reflect: tagged pointers #3691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
reflect: tagged pointers #3691
Conversation
9e4f337
to
fb95822
Compare
Note to self: I need to look into why OptimizeReflectImplements fails. |
Found so far:
|
I think I have a fix. Now I need to properly test it and clean up the code. |
This is necessary to get #3691 working.
This should fix most/all errors: #3737 I tested it by applying the 3 patches on top of this branch, and |
This is necessary to get #3691 working.
@dgryski can you rebased this PR? My fix has been merged. |
fb95822
to
2a636d6
Compare
Rebased. |
So my hacked up |
So
|
Yes, I'm pretty sure they did. Checking the |
Ok, more playing around here. The hacked up |
The problem with
We can see that while the type of |
I think this might be an issue with the pass that lowers type asserts. With ptrTo gone, it doesn't know the pointer type can still be created. |
I was looking at https://github.com/tinygo-org/tinygo/blob/dev/compiler/interface.go#L661 and wondering if the issue is more related to the fact that we don't handle those pointer types because there aren't named globals for them (but I'm not sure what the fix needs to be...) |
Yes, exactly, that's more or less what I meant. There are no named globals because the One possible fix could be to change the code that lowers type asserts and make sure it also checks whether the element type exists. See the TODO in this code: tinygo/transform/interface-lowering.go Lines 288 to 300 in f366cd5
|
Here is a very simple reproducer of the type switch issue: package main
func main() {
identify(0)
identify(new(int))
identify(new(*int))
}
func identify(itf any) {
switch itf.(type) {
case int:
println("type is int")
case *int:
println("type is *int")
case **int:
println("type is **int")
default:
println("other type??")
}
} Go:
TinyGo on the dev branch:
This branch:
So the problem has nothing to do with reflect, just with type asserts (type switches are implemented as an if-else chain made of type asserts). |
Here is a fix: diff --git a/compiler/interface.go b/compiler/interface.go
index bd6970b4..d9c0e138 100644
--- a/compiler/interface.go
+++ b/compiler/interface.go
@@ -673,11 +673,8 @@ func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value {
commaOk = b.CreateCall(fn.GlobalValueType(), fn, []llvm.Value{actualTypeNum}, "")
} else {
- assertedTypeGlobal := b.getTypeCode(expr.AssertedType)
- if !assertedTypeGlobal.IsAConstantExpr().IsNil() {
- assertedTypeGlobal = assertedTypeGlobal.Operand(0) // resolve the GEP operation
- }
- globalName := "reflect/types.typeid:" + strings.TrimPrefix(assertedTypeGlobal.Name(), "reflect/types.type:")
+ name, _ := getTypeCodeName(expr.AssertedType)
+ globalName := "reflect/types.typeid:" + name
assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName)
if assertedTypeCodeGlobal.IsNil() {
// Create a new typecode global.
diff --git a/transform/interface-lowering.go b/transform/interface-lowering.go
index ebd47ff8..1cbebee8 100644
--- a/transform/interface-lowering.go
+++ b/transform/interface-lowering.go
@@ -285,11 +285,26 @@ func (p *lowerInterfacesPass) run() error {
for _, use := range getUses(p.mod.NamedFunction("runtime.typeAssert")) {
actualType := use.Operand(0)
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
+ gepOffset := uint64(0)
+ for strings.HasPrefix(name, "pointer:pointer:") {
+ // This is a type like **int, which has the name pointer:pointer:int
+ // but is encoded using pointer tagging.
+ // Calculate the pointer tag, which is emitted as a GEP instruction.
+ name = name[len("pointer:"):]
+ gepOffset++
+ }
if t, ok := p.types[name]; ok {
// The type exists in the program, so lower to a regular pointer
// comparison.
p.builder.SetInsertPointBefore(use)
- commaOk := p.builder.CreateICmp(llvm.IntEQ, t.typecodeGEP, actualType, "typeassert.ok")
+ typecodeGEP := t.typecodeGEP
+ if gepOffset != 0 {
+ // This is a tagged pointer.
+ typecodeGEP = llvm.ConstInBoundsGEP(p.ctx.Int8Type(), typecodeGEP, []llvm.Value{
+ llvm.ConstInt(p.ctx.Int64Type(), gepOffset, false),
+ })
+ }
+ commaOk := p.builder.CreateICmp(llvm.IntEQ, typecodeGEP, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
} else {
// The type does not exist in the program, so lower to a constant |
Was that last commit intended to be part of this PR? |
Oops, probably not. I'll put that in a separate one. It was included in the original **T branch (since we need it) but yeah it should be its own PR. |
f78bdc8
to
b8b6a5b
Compare
@aykevl any other feedback on this PR? The previous change that you suggested has been added. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you run the test corpus on this PR? If not, that's something that I would recommend (because this is a rather invasive change).
Other than that, and the (hopefully easy to fix) issue mentioned below, this looks good to me.
compiler/interface.go
Outdated
if typ, ok := typ.(*types.Pointer); ok { | ||
if _, ok := typ.Elem().(*types.Pointer); ok { | ||
// For a pointer to a pointer, we just increase the pointer by 1 | ||
ptr := c.getTypeCode(typ.Elem()) | ||
return llvm.ConstGEP(c.ctx.Int8Type(), ptr, []llvm.Value{ | ||
llvm.ConstInt(llvm.Int32Type(), 1, false), | ||
}) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should have a check for *****T
types, which will overflow the pointer tagging scheme. Better have a compiler error than a mysterious runtime error.
You could for example use .Operand(...)
on the GEP expression to check the offset and make sure it's at most 3.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think @dgryski ?
I'd love if I could get a position to report the error from instead of just |
Hmm, that's difficult. If it's a pointer to a named type, you could use the location of the named type - but that's still not very useful. I think what is needed is to update all callers to provide a position because the position is not stored in the |
This is necessary to get tinygo-org#3691 working.
This is necessary to get #3691 working.
Doesn't work yet. Still segfaults the compiler.
EDIT by @deadprogram it does work now 😸