diff --git a/compiler/compiler.go b/compiler/compiler.go index 13a29d43..b9c27a4f 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -1517,21 +1517,41 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) { // https://research.swtch.com/interfaces return c.parseExpr(frame, expr.X) case *ssa.ChangeType: + // This instruction changes the type, but the underlying value remains + // the same. This is often a no-op, but sometimes we have to change the + // LLVM type as well. x, err := c.parseExpr(frame, expr.X) if err != nil { return llvm.Value{}, err } - // The only case when we need to bitcast is when casting between named - // struct types, as those are actually different in LLVM. Let's just - // bitcast all struct types for ease of use. - if _, ok := expr.Type().Underlying().(*types.Struct); ok { - llvmType, err := c.getLLVMType(expr.X.Type()) - if err != nil { - return llvm.Value{}, err - } - return c.builder.CreateBitCast(x, llvmType, "changetype"), nil + llvmType, err := c.getLLVMType(expr.Type()) + if err != nil { + return llvm.Value{}, err + } + if x.Type() == llvmType { + // Different Go type but same LLVM type (for example, named int). + // This is the common case. + return x, nil + } + // Figure out what kind of type we need to cast. + switch llvmType.TypeKind() { + case llvm.StructTypeKind: + // Unfortunately, we can't just bitcast structs. We have to + // actually create a new struct of the correct type and insert the + // values from the previous struct in there. + value := llvm.Undef(llvmType) + for i := 0; i < llvmType.StructElementTypesCount(); i++ { + field := c.builder.CreateExtractValue(x, i, "changetype.field") + value = c.builder.CreateInsertValue(value, field, i, "changetype.struct") + } + return value, nil + case llvm.PointerTypeKind: + // This can happen with pointers to structs. This case is easy: + // simply bitcast the pointer to the destination type. + return c.builder.CreateBitCast(x, llvmType, "changetype.pointer"), nil + default: + return llvm.Value{}, errors.New("todo: unknown ChangeType type: " + expr.X.Type().String()) } - return x, nil case *ssa.Const: return c.parseConst(frame.fn.LinkName(), expr) case *ssa.Convert: diff --git a/testdata/structexpand.go b/testdata/structs.go similarity index 83% rename from testdata/structexpand.go rename to testdata/structs.go index 77613b2a..4ff5068d 100644 --- a/testdata/structexpand.go +++ b/testdata/structs.go @@ -28,6 +28,14 @@ type s4 struct { d byte } +// same struct, different type +type s4b struct { + a byte + b byte + c byte + d byte +} + type s5 struct { a struct { aa byte @@ -70,6 +78,16 @@ func test3(s s3) { func test4(s s4) { println("test4", s.a, s.b, s.c, s.d) + test4b(s4b(s)) + test4bp((*s4b)(&s)) +} + +func test4b(s s4b) { + println("test4b", s.a, s.b, s.c, s.d) +} + +func test4bp(s *s4b) { + println("test4bp", s.a, s.b, s.c, s.d) } func test5(s s5) { diff --git a/testdata/structexpand.txt b/testdata/structs.txt similarity index 78% rename from testdata/structexpand.txt rename to testdata/structs.txt index 2cfc411a..b94ce034 100644 --- a/testdata/structexpand.txt +++ b/testdata/structs.txt @@ -3,6 +3,8 @@ test1 1 test2 1 2 test3 1 2 3 test4 1 2 3 4 +test4b 1 2 3 4 +test4bp 1 2 3 4 test5 1 2 3 test6 foo 3 5 test7 (0:nil) 8