package compiler // This file transforms interface-related instructions (*ssa.MakeInterface, // *ssa.TypeAssert, calls on interface types) to an intermediate IR form, to be // lowered to the final form by the interface lowering pass. See // interface-lowering.go for more details. import ( "encoding/binary" "fmt" "go/token" "go/types" "path/filepath" "sort" "strconv" "strings" "golang.org/x/tools/go/ssa" "tinygo.org/x/go-llvm" ) // numMethodHasMethodSet is a flag in bit 15 of the numMethod field (uint16) in // Named, Pointer, and Struct type descriptors. When set, an inline method set // is present in the type descriptor. Must match the constant in // src/internal/reflectlite/type.go. const numMethodHasMethodSet = 0x8000 // Type kinds for basic types. // They must match the constants for the Kind type in src/reflect/type.go. var basicTypes = [...]uint8{ types.Bool: 1, types.Int: 2, types.Int8: 3, types.Int16: 4, types.Int32: 5, types.Int64: 6, types.Uint: 7, types.Uint8: 8, types.Uint16: 9, types.Uint32: 10, types.Uint64: 11, types.Uintptr: 12, types.Float32: 13, types.Float64: 14, types.Complex64: 15, types.Complex128: 16, types.String: 17, types.UnsafePointer: 18, } // These must also match the constants for the Kind type in src/reflect/type.go. const ( typeKindChan = 19 typeKindInterface = 20 typeKindPointer = 21 typeKindSlice = 22 typeKindArray = 23 typeKindSignature = 24 typeKindMap = 25 typeKindStruct = 26 ) // Flags stored in the first byte of the struct field byte array. Must be kept // up to date with src/reflect/type.go. const ( structFieldFlagAnonymous = 1 << iota structFieldFlagHasTag structFieldFlagIsExported structFieldFlagIsEmbedded ) type reflectChanDir int const ( refRecvDir reflectChanDir = 1 << iota // <-chan refSendDir // chan<- refBothDir = refRecvDir | refSendDir // chan ) // createMakeInterface emits the LLVM IR for the *ssa.MakeInterface instruction. // It tries to put the type in the interface value, but if that's not possible, // it will do an allocation of the right size and put that in the interface // value field. // // An interface value is a {typecode, value} tuple named runtime._interface. func (b *builder) createMakeInterface(val llvm.Value, typ types.Type, pos token.Pos) llvm.Value { itfValue := b.emitPointerPack([]llvm.Value{val}, pos) return b.createMakeInterfaceFromPointer(itfValue, typ) } func (b *builder) createMakeInterfaceFromPointer(itfValue llvm.Value, typ types.Type) llvm.Value { itfType := b.getTypeCode(typ) itf := llvm.Undef(b.getLLVMRuntimeType("_interface")) itf = b.CreateInsertValue(itf, itfType, 0, "") itf = b.CreateInsertValue(itf, itfValue, 1, "") return itf } // extractValueFromInterface extract the value from an interface value // (runtime._interface) under the assumption that it is of the type given in // llvmType. The behavior is undefined if the interface is nil or llvmType // doesn't match the underlying type of the interface. func (b *builder) extractValueFromInterface(itf llvm.Value, llvmType llvm.Type) llvm.Value { valuePtr := b.CreateExtractValue(itf, 1, "typeassert.value.ptr") if b.isIndirectAggregate(llvmType) { return valuePtr } return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0] } func (b *builder) extractValuePointerFromInterface(itf llvm.Value) llvm.Value { return b.CreateExtractValue(itf, 1, "typeassert.value.ptr") } func (c *compilerContext) pkgPathPtr(pkgpath string) llvm.Value { pkgpathName := "reflect/types.type.pkgpath.empty" if pkgpath != "" { pkgpathName = "reflect/types.type.pkgpath:" + pkgpath } pkgpathGlobal := c.mod.NamedGlobal(pkgpathName) if pkgpathGlobal.IsNil() { pkgpathInitializer := c.ctx.ConstString(pkgpath+"\x00", false) pkgpathGlobal = llvm.AddGlobal(c.mod, pkgpathInitializer.Type(), pkgpathName) pkgpathGlobal.SetInitializer(pkgpathInitializer) pkgpathGlobal.SetAlignment(1) pkgpathGlobal.SetUnnamedAddr(true) pkgpathGlobal.SetLinkage(llvm.LinkOnceODRLinkage) pkgpathGlobal.SetGlobalConstant(true) } pkgPathPtr := llvm.ConstGEP(pkgpathGlobal.GlobalValueType(), pkgpathGlobal, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false), }) return pkgPathPtr } // isGenericMethod returns true for a method that has its own type parameters // (independent of any type parameters on its receiver), e.g. the N method in // "func (r *Rand) N[Int intType](n Int) Int { ... }". Like the reflect // package, such methods are excluded from runtime method sets: they aren't // instantiated, so their signature can't be represented in a type code, and // they can never satisfy an interface method anyway. func isGenericMethod(fn *types.Func) bool { return fn.Signature().TypeParams().Len() > 0 } // getTypeCode returns a reference to a type code. // A type code is a pointer to a constant global that describes the type. // This function returns a pointer to the 'kind' field (which might not be the // first field in the struct). func (c *compilerContext) getTypeCode(typ types.Type) llvm.Value { // Resolve alias types: alias types are resolved at compile time. typ = types.Unalias(typ) ms := c.program.MethodSets.MethodSet(typ) _, isInterface := typ.Underlying().(*types.Interface) // As defined in https://pkg.go.dev/reflect#Type: // NumMethod returns the number of methods accessible using Method. // For a non-interface type, it returns the number of exported methods. // For an interface type, it returns the number of exported and unexported methods. var numMethods int var hasMethodSet bool for method := range ms.Methods() { if isGenericMethod(method.Obj().(*types.Func)) { continue } hasMethodSet = true if isInterface || method.Obj().Exported() { numMethods++ } } if isInterface { hasMethodSet = false } // Short-circuit all the global pointer logic here for pointers to pointers. 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()) // if the type is already *****T or higher, we can't make it. if typstr := typ.String(); strings.HasPrefix(typstr, "*****") { c.addError(token.NoPos, fmt.Sprintf("too many levels of pointers for typecode: %s", typstr)) } return llvm.ConstGEP(c.ctx.Int8Type(), ptr, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 1, false), }) } } typeCodeName, isLocal := c.getTypeCodeName(typ) globalName := "reflect/types.type:" + typeCodeName var global llvm.Value if isLocal { // This type is a named type inside a function, like this: // // func foo() any { // type named int // return named(0) // } if obj := c.interfaceTypes.At(typ); obj != nil { global = obj.(llvm.Value) } } else { // Regular type (named or otherwise). global = c.mod.NamedGlobal(globalName) } if global.IsNil() { var typeFields []llvm.Value // Define the type fields. These must match the structs in // src/reflect/type.go (ptrType, arrayType, etc). See the comment at the // top of src/reflect/type.go for more information on the layout of these structs. typeFieldTypes := []*types.Var{ types.NewVar(token.NoPos, nil, "kind", types.Typ[types.Int8]), } // Compute the method set value for types that support methods. var methods []*types.Func for method := range ms.Methods() { fn := method.Obj().(*types.Func) if isGenericMethod(fn) { continue } methods = append(methods, fn) } methodSetType := types.NewStruct([]*types.Var{ types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "methods", types.NewArray(types.Typ[types.UnsafePointer], int64(len(methods)))), }, nil) methodSetValue := c.getMethodSetValue(methods) switch typ := typ.(type) { case *types.Basic: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), ) case *types.Named: name := typ.Obj().Name() var pkgname string if pkg := typ.Obj().Pkg(); pkg != nil { pkgname = pkg.Name() } typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "underlying", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]), ) if len(methods) > 0 { typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "methods", methodSetType), ) } typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "name", types.NewArray(types.Typ[types.Int8], int64(len(pkgname)+1+len(name)+1))), ) case *types.Chan: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), // reuse for select chan direction types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) case *types.Slice: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) case *types.Pointer: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), ) if len(methods) > 0 { typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "methods", methodSetType), ) } case *types.Array: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "length", types.Typ[types.Uintptr]), types.NewVar(token.NoPos, nil, "sliceOf", types.Typ[types.UnsafePointer]), ) case *types.Map: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "elementType", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "keyType", types.Typ[types.UnsafePointer]), ) case *types.Struct: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "numMethods", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "pkgpath", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "size", types.Typ[types.Uint32]), types.NewVar(token.NoPos, nil, "numFields", types.Typ[types.Uint16]), types.NewVar(token.NoPos, nil, "fields", types.NewArray(c.getRuntimeType("structField"), int64(typ.NumFields()))), ) if len(methods) > 0 { typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "methods", methodSetType), ) } case *types.Interface: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), types.NewVar(token.NoPos, nil, "methods", methodSetType), ) case *types.Signature: typeFieldTypes = append(typeFieldTypes, types.NewVar(token.NoPos, nil, "ptrTo", types.Typ[types.UnsafePointer]), ) // TODO: signature params and return values } if hasMethodSet { // This method set is appended at the start of the struct. It is // removed in the interface lowering pass. // TODO: don't remove these and instead do what upstream Go is doing // instead. See: https://research.swtch.com/interfaces. This can // likely be optimized in LLVM using // https://llvm.org/docs/TypeMetadata.html. typeFieldTypes = append([]*types.Var{ types.NewVar(token.NoPos, nil, "methodSet", types.Typ[types.UnsafePointer]), }, typeFieldTypes...) } globalType := types.NewStruct(typeFieldTypes, nil) global = llvm.AddGlobal(c.mod, c.getLLVMType(globalType), globalName) if isLocal { c.interfaceTypes.Set(typ, global) } metabyte := getTypeKind(typ) // Precompute these so we don't have to calculate them at runtime. if types.Comparable(typ) { metabyte |= 1 << 6 } if hashmapIsBinaryKey(typ) { metabyte |= 1 << 7 } switch typ := typ.(type) { case *types.Basic: typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} case *types.Named: name := typ.Obj().Name() var pkgpath string var pkgname string if pkg := typ.Obj().Pkg(); pkg != nil { pkgpath = pkg.Path() pkgname = pkg.Name() } pkgPathPtr := c.pkgPathPtr(pkgpath) namedNumMethods := uint64(numMethods) if namedNumMethods&numMethodHasMethodSet != 0 { panic("numMethods overflow: too many exported methods on named type " + name) } if len(methods) > 0 { namedNumMethods |= numMethodHasMethodSet } typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), namedNumMethods, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Underlying()), // underlying pkgPathPtr, // pkgpath pointer } if len(methods) > 0 { typeFields = append(typeFields, methodSetValue) // methods } typeFields = append(typeFields, c.ctx.ConstString(pkgname+"."+name+"\x00", false)) // name metabyte |= 1 << 5 // "named" flag case *types.Chan: var dir reflectChanDir switch typ.Dir() { case types.SendRecv: dir = refBothDir case types.RecvOnly: dir = refRecvDir case types.SendOnly: dir = refSendDir } typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), uint64(dir), false), // actually channel direction c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elementType } case *types.Slice: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elementType } case *types.Pointer: ptrNumMethods := uint64(numMethods) if ptrNumMethods&numMethodHasMethodSet != 0 { panic("numMethods overflow: too many exported methods on pointer type") } if len(methods) > 0 { ptrNumMethods |= numMethodHasMethodSet } typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), ptrNumMethods, false), // numMethods c.getTypeCode(typ.Elem()), } if len(methods) > 0 { typeFields = append(typeFields, methodSetValue) } case *types.Array: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elementType llvm.ConstInt(c.uintptrType, uint64(typ.Len()), false), // length c.getTypeCode(types.NewSlice(typ.Elem())), // slicePtr } case *types.Map: typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), 0, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo c.getTypeCode(typ.Elem()), // elem c.getTypeCode(typ.Key()), // key } case *types.Struct: var pkgpath string if typ.NumFields() > 0 { if pkg := typ.Field(0).Pkg(); pkg != nil { pkgpath = pkg.Path() } } pkgPathPtr := c.pkgPathPtr(pkgpath) llvmStructType := c.getLLVMType(typ) size := c.targetData.TypeStoreSize(llvmStructType) structNumMethods := uint64(numMethods) if structNumMethods&numMethodHasMethodSet != 0 { panic("numMethods overflow: too many exported methods on struct type") } if len(methods) > 0 { structNumMethods |= numMethodHasMethodSet } typeFields = []llvm.Value{ llvm.ConstInt(c.ctx.Int16Type(), structNumMethods, false), // numMethods c.getTypeCode(types.NewPointer(typ)), // ptrTo pkgPathPtr, llvm.ConstInt(c.ctx.Int32Type(), uint64(size), false), // size llvm.ConstInt(c.ctx.Int16Type(), uint64(typ.NumFields()), false), // numFields } structFieldType := c.getLLVMRuntimeType("structField") var fields []llvm.Value for i := 0; i < typ.NumFields(); i++ { field := typ.Field(i) offset := c.targetData.ElementOffset(llvmStructType, i) var flags uint8 if field.Anonymous() { flags |= structFieldFlagAnonymous } if typ.Tag(i) != "" { flags |= structFieldFlagHasTag } if token.IsExported(field.Name()) { flags |= structFieldFlagIsExported } if field.Embedded() { flags |= structFieldFlagIsEmbedded } var offsBytes [binary.MaxVarintLen32]byte offLen := binary.PutUvarint(offsBytes[:], offset) data := string(flags) + string(offsBytes[:offLen]) + field.Name() + "\x00" if typ.Tag(i) != "" { if len(typ.Tag(i)) > 0xff { c.addError(field.Pos(), fmt.Sprintf("struct tag is %d bytes which is too long, max is 255", len(typ.Tag(i)))) } data += string([]byte{byte(len(typ.Tag(i)))}) + typ.Tag(i) } dataInitializer := c.ctx.ConstString(data, false) dataGlobal := llvm.AddGlobal(c.mod, dataInitializer.Type(), globalName+"."+field.Name()) dataGlobal.SetInitializer(dataInitializer) dataGlobal.SetAlignment(1) dataGlobal.SetUnnamedAddr(true) dataGlobal.SetLinkage(llvm.InternalLinkage) dataGlobal.SetGlobalConstant(true) fieldType := c.getTypeCode(field.Type()) fields = append(fields, llvm.ConstNamedStruct(structFieldType, []llvm.Value{ fieldType, llvm.ConstGEP(dataGlobal.GlobalValueType(), dataGlobal, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), 0, false), }), })) } typeFields = append(typeFields, llvm.ConstArray(structFieldType, fields)) if len(methods) > 0 { typeFields = append(typeFields, methodSetValue) } case *types.Interface: typeFields = []llvm.Value{ c.getTypeCode(types.NewPointer(typ)), methodSetValue, } case *types.Signature: typeFields = []llvm.Value{c.getTypeCode(types.NewPointer(typ))} // TODO: params, return values, etc } // Prepend metadata byte. typeFields = append([]llvm.Value{ llvm.ConstInt(c.ctx.Int8Type(), uint64(metabyte), false), }, typeFields...) if hasMethodSet { typeFields = append([]llvm.Value{ c.getTypeMethodSet(typ), }, typeFields...) } alignment := max(c.targetData.TypeAllocSize(c.dataPtrType), 4) globalValue := c.ctx.ConstStruct(typeFields, false) global.SetInitializer(globalValue) if isLocal { global.SetLinkage(llvm.InternalLinkage) } else { global.SetLinkage(llvm.LinkOnceODRLinkage) } global.SetGlobalConstant(true) global.SetAlignment(int(alignment)) if c.Debug { file := c.getDIFile("") diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{ Name: "type " + typ.String(), File: file, Line: 1, Type: c.getDIType(globalType), LocalToUnit: false, Expr: c.dibuilder.CreateExpression(nil), AlignInBits: uint32(alignment * 8), }) global.AddMetadata(0, diglobal) } } offset := uint64(0) if hasMethodSet { // The pointer to the method set is always the first element of the // global (if there is a method set). However, the pointer we return // should point to the 'kind' field not the method set. offset = 1 } return llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{ llvm.ConstInt(c.ctx.Int32Type(), 0, false), llvm.ConstInt(c.ctx.Int32Type(), offset, false), }) } // getTypeKind returns the type kind for the given type, as defined by // reflect.Kind. func getTypeKind(t types.Type) uint8 { switch t := t.Underlying().(type) { case *types.Basic: return basicTypes[t.Kind()] case *types.Chan: return typeKindChan case *types.Interface: return typeKindInterface case *types.Pointer: return typeKindPointer case *types.Slice: return typeKindSlice case *types.Array: return typeKindArray case *types.Signature: return typeKindSignature case *types.Map: return typeKindMap case *types.Struct: return typeKindStruct default: panic("unknown type") } } var basicTypeNames = [...]string{ types.Bool: "bool", types.Int: "int", types.Int8: "int8", types.Int16: "int16", types.Int32: "int32", types.Int64: "int64", types.Uint: "uint", types.Uint8: "uint8", types.Uint16: "uint16", types.Uint32: "uint32", types.Uint64: "uint64", types.Uintptr: "uintptr", types.Float32: "float32", types.Float64: "float64", types.Complex64: "complex64", types.Complex128: "complex128", types.String: "string", types.UnsafePointer: "unsafe.Pointer", } // getTypeCodeName returns a name for this type that can be used in the // interface lowering pass to assign type codes as expected by the reflect // package. See getTypeCodeNum. // // isLocal is true when the type is declared inside a function body. // Such types need a per-declaration (or per instantiation) suffix // because their printed names are not unique. // // Ordinary function-local types (TypeName.Parent() != nil) are // disambiguated lazily from their declaration position: every // declaration in the package has a distinct (file, line, column) // triple, and the position is taken un-//line-adjusted so it is // stable across builds. Such types are only nameable inside their // declaring package, so the name does not need to agree with anything // computed in another package. // // Synthetic locals (TypeName.Parent() == nil), produced by generic // instantiation, are pre-registered by scanLocalTypes because their // names must agree across packages that materialize the same instance. func (c *compilerContext) getTypeCodeName(t types.Type) (name string, isLocal bool) { switch t := types.Unalias(t).(type) { case *types.Named: tn := t.Obj() if tn.Pkg() == nil || tn.Parent() == tn.Pkg().Scope() { name := tn.Name() if tn.Pkg() != nil { name = tn.Pkg().Path() + "." + name } isLocal := false if targs := t.TypeArgs(); targs.Len() != 0 { parts := make([]string, targs.Len()) for i := range parts { var local bool parts[i], local = c.getTypeCodeName(targs.At(i)) isLocal = isLocal || local } name += "[" + strings.Join(parts, ",") + "]" } return "named:" + name, isLocal } if tn.Parent() != nil { // Ordinary function-local type. Use the un-//line-adjusted // declaration position as the disambiguator. pos := c.program.Fset.PositionFor(tn.Pos(), false) return fmt.Sprintf("named:%s$%s:%d:%d", t.String(), filepath.Base(pos.Filename), pos.Line, pos.Column), true } // Synthetic local from generic instantiation: must have been // pre-registered by scanLocalTypes. v := c.localTypeNames.At(t) if v == nil { panic("compiler: synthetic local type " + tn.Name() + " was not registered by scanLocalTypes") } return "named:" + v.(string), true case *types.Array: s, isLocal := c.getTypeCodeName(t.Elem()) return "array:" + strconv.FormatInt(t.Len(), 10) + ":" + s, isLocal case *types.Basic: return "basic:" + basicTypeNames[t.Kind()], false case *types.Chan: s, isLocal := c.getTypeCodeName(t.Elem()) var dir string switch t.Dir() { case types.SendOnly: dir = "s:" case types.RecvOnly: dir = "r:" case types.SendRecv: dir = "sr:" } return "chan:" + dir + s, isLocal case *types.Interface: isLocal := false methods := make([]string, t.NumMethods()) for i := 0; i < t.NumMethods(); i++ { name := t.Method(i).Name() if !token.IsExported(name) { name = t.Method(i).Pkg().Path() + "." + name } s, local := c.getTypeCodeName(t.Method(i).Type()) if local { isLocal = true } methods[i] = name + ":" + s } return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal case *types.Map: keyType, keyLocal := c.getTypeCodeName(t.Key()) elemType, elemLocal := c.getTypeCodeName(t.Elem()) return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal case *types.Pointer: s, isLocal := c.getTypeCodeName(t.Elem()) return "pointer:" + s, isLocal case *types.Signature: isLocal := false params := make([]string, t.Params().Len()) for i := 0; i < t.Params().Len(); i++ { s, local := c.getTypeCodeName(t.Params().At(i).Type()) if local { isLocal = true } params[i] = s } results := make([]string, t.Results().Len()) for i := 0; i < t.Results().Len(); i++ { s, local := c.getTypeCodeName(t.Results().At(i).Type()) if local { isLocal = true } results[i] = s } return "func:" + "{" + strings.Join(params, ",") + "}{" + strings.Join(results, ",") + "}", isLocal case *types.Slice: s, isLocal := c.getTypeCodeName(t.Elem()) return "slice:" + s, isLocal case *types.Struct: elems := make([]string, t.NumFields()) isLocal := false for i := 0; i < t.NumFields(); i++ { embedded := "" if t.Field(i).Embedded() { embedded = "#" } s, local := c.getTypeCodeName(t.Field(i).Type()) if local { isLocal = true } elems[i] = embedded + t.Field(i).Name() + ":" + s if t.Tag(i) != "" { elems[i] += "`" + t.Tag(i) + "`" } } return "struct:" + "{" + strings.Join(elems, ",") + "}", isLocal default: panic("unknown type: " + t.String()) } } // scanLocalTypes assigns names to every synthetic *types.TypeName // (TypeName.Parent() == nil) reachable from this package and stores // them in c.localTypeNames. // // Synthetic TypeNames are produced by generic instantiation: two // instantiations of the same generic function (e.g. F[int] and // F[string]) produce TypeNames with the same printed name and the // same source position, so each is named with the enclosing instance's // canonical function name as prefix. The function name encodes the type // arguments, matching Go's runtime behavior, where F[int].Inner and // F[string].Inner are distinct types even when Inner does not mention // the type parameter. // // A given instance may be materialized by several packages (the body // of F[int] is compiled in every package that calls F[int]); its // reflect/types.type:* global has LinkOnceODRLinkage and is merged by // name at link time. The chosen name therefore depends only on // intrinsic SSA properties (the canonical function name and raw token.Pos), // so any package compiling the same instance produces the same // identifier. // // Ordinary function-local TypeNames (TypeName.Parent() != nil) are // not handled here: they are nameable only inside their declaring // package, and getTypeCodeName derives a stable per-declaration name // for them directly from their source position. func (c *compilerContext) scanLocalTypes(ssaPkg *ssa.Package) { // Locate every generic instance reachable from this package // (including instances declared in imported packages and any // function reached through an instance subtree). var instances []*ssa.Function seen := map[*ssa.Function]struct{}{} var walk func(fn *ssa.Function, inInstance bool) walk = func(fn *ssa.Function, inInstance bool) { if fn == nil { return } if _, ok := seen[fn]; ok { return } // fn belongs to an instance subtree if it is itself an // instantiation or if we reached it from one. // // len(TypeArgs()) is used instead of fn.Origin() because // Origin() may call Build() on fn's declaring package, which // would defeat per-package compilation. isInstanceRoot := len(fn.TypeArgs()) > 0 if !isInstanceRoot && !inInstance && fn.Pkg != ssaPkg { return } if fn.Blocks == nil && fn.AnonFuncs == nil { return } seen[fn] = struct{}{} isInInstance := inInstance || isInstanceRoot if isInInstance { instances = append(instances, fn) } for _, anon := range fn.AnonFuncs { walk(anon, isInInstance) } var ops [10]*ssa.Value for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range instr.Operands(ops[:0]) { if op == nil || *op == nil { continue } if callee, ok := (*op).(*ssa.Function); ok { walk(callee, isInInstance) } } } } } for _, member := range ssaPkg.Members { switch m := member.(type) { case *ssa.Function: walk(m, false) case *ssa.Type: mset := c.program.MethodSets.MethodSet(m.Type()) for method := range mset.Methods() { walk(c.program.MethodValue(method), false) } pmset := c.program.MethodSets.MethodSet(types.NewPointer(m.Type())) for method := range pmset.Methods() { walk(c.program.MethodValue(method), false) } } } // Registration is first-writer-wins (a synthetic TypeName may be // reachable from several instances), so visit instances in a // deterministic order. Pos() is a defensive tiebreaker. sort.Slice(instances, func(i, j int) bool { ri, rj := instances[i].RelString(nil), instances[j].RelString(nil) if ri != rj { return ri < rj } return instances[i].Pos() < instances[j].Pos() }) for _, fn := range instances { c.registerSyntheticLocalTypes(fn) } } // registerSyntheticLocalTypes walks every type reachable from fn's // body and records each synthetic *types.Named (TypeName.Parent() == // nil) in c.localTypeNames. Each is named with the canonical function // name plus a per-function counter assigned in source order. // // First-writer-wins: a *types.Named already present in // c.localTypeNames is left alone, so a synthetic type reachable from // several instances keeps the name assigned by the first (in // scanLocalTypes' deterministic order). func (c *compilerContext) registerSyntheticLocalTypes(fn *ssa.Function) { var found []*types.Named seen := map[types.Type]struct{}{} var visit func(t types.Type) visit = func(t types.Type) { if t == nil { return } if _, ok := seen[t]; ok { return } seen[t] = struct{}{} switch t := t.(type) { case *types.Alias: visit(types.Unalias(t)) case *types.Named: tn := t.Obj() if tn.Pkg() != nil && tn.Parent() == nil { if c.localTypeNames.At(t) == nil { // Reserve the slot so later calls within this // scanLocalTypes invocation skip it; the final // name is filled in after sorting, before any // getTypeCodeName lookups happen. c.localTypeNames.Set(t, "") found = append(found, t) } } targs := t.TypeArgs() for t := range targs.Types() { visit(t) } visit(t.Underlying()) case *types.Pointer: visit(t.Elem()) case *types.Slice: visit(t.Elem()) case *types.Array: visit(t.Elem()) case *types.Chan: visit(t.Elem()) case *types.Map: visit(t.Key()) visit(t.Elem()) case *types.Struct: for field := range t.Fields() { visit(field.Type()) } case *types.Signature: if p := t.Params(); p != nil { for v := range p.Variables() { visit(v.Type()) } } if r := t.Results(); r != nil { for v := range r.Variables() { visit(v.Type()) } } case *types.Tuple: for v := range t.Variables() { visit(v.Type()) } case *types.Interface: // A synthetic local type can be reachable only through a // local interface's method signature, so descend into // them. getTypeCodeName encodes those signatures into // the interface's identifier, and the seen map breaks // cycles formed by methods that mention the interface // itself. for method := range t.Methods() { visit(method.Type()) } } } for _, p := range fn.Params { visit(p.Type()) } for _, fv := range fn.FreeVars { visit(fv.Type()) } for _, l := range fn.Locals { visit(l.Type()) } var ops [10]*ssa.Value for _, b := range fn.Blocks { for _, instr := range b.Instrs { if v, ok := instr.(ssa.Value); ok { visit(v.Type()) } for _, op := range instr.Operands(ops[:0]) { if op != nil && *op != nil { visit((*op).Type()) } } } } if len(found) == 0 { return } // Sort by raw token.Pos: this gives a total order on declarations // that is stable across builds and unaffected by //line directives // (which only adjust the human-facing position from Fset.Position). sort.Slice(found, func(i, j int) bool { return found[i].Obj().Pos() < found[j].Obj().Pos() }) enclosing := c.canonicalFunctionName(fn) for i, named := range found { c.localTypeNames.Set(named, fmt.Sprintf("%s.%s$%d", enclosing, named.Obj().Name(), i)) } } // getTypeMethodSet returns a reference (GEP) to a global method set. This // method set should be unreferenced after the interface lowering pass. func (c *compilerContext) getTypeMethodSet(typ types.Type) llvm.Value { typeName, _ := c.getTypeCodeName(typ) globalName := typeName + "$methodset" global := c.mod.NamedGlobal(globalName) if global.IsNil() { ms := c.program.MethodSets.MethodSet(typ) // Create method set. var signatures, wrappers []llvm.Value for method := range ms.Methods() { if isGenericMethod(method.Obj().(*types.Func)) { continue } signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func)) signatures = append(signatures, signatureGlobal) fn := c.program.MethodValue(method) llvmFnType, llvmFn := c.getFunction(fn) if llvmFn.IsNil() { // compiler error, so panic panic("cannot find function: " + c.getFunctionInfo(fn).linkName) } wrapper := c.getInterfaceInvokeWrapper(fn, llvmFnType, llvmFn) wrappers = append(wrappers, wrapper) } // Construct global value. globalValue := c.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(c.uintptrType, uint64(len(signatures)), false), llvm.ConstArray(c.dataPtrType, signatures), c.ctx.ConstStruct(wrappers, false), }, false) global = llvm.AddGlobal(c.mod, globalValue.Type(), globalName) global.SetInitializer(globalValue) global.SetGlobalConstant(true) global.SetUnnamedAddr(true) global.SetLinkage(llvm.LinkOnceODRLinkage) } return global } // getMethodSignatureName returns a unique name (that can be used as the name of // a global) for the given method. func (c *compilerContext) getMethodSignatureName(method *types.Func) string { name := method.Name() var prefix string if token.IsExported(method.Name()) { prefix = "reflect/methods." } else { prefix = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." } signature, _ := c.getTypeCodeName(method.Type()) return prefix + name + ":" + signature } // getMethodSignature returns a global variable which is a reference to an // external *i8 indicating the indicating the signature of this method. It is // used during the interface lowering pass. func (c *compilerContext) getMethodSignature(method *types.Func) llvm.Value { globalName := c.getMethodSignatureName(method) signatureGlobal := c.mod.NamedGlobal(globalName) if signatureGlobal.IsNil() { // TODO: put something useful in these globals, such as the method // signature. Useful to one day implement reflect.Value.Method(n). signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName) signatureGlobal.SetInitializer(llvm.ConstInt(c.ctx.Int8Type(), 0, false)) signatureGlobal.SetLinkage(llvm.LinkOnceODRLinkage) signatureGlobal.SetGlobalConstant(true) signatureGlobal.SetAlignment(1) } return signatureGlobal } // createTypeAssert will emit the code for a typeassert, used in if statements // and in type switches (Go SSA does not have type switches, only if/else // chains). Note that even though the Go SSA does not contain type switches, // LLVM will recognize the pattern and make it a real switch in many cases. // // Type asserts on concrete types are trivial: just compare type numbers. Type // asserts on interfaces are more difficult, see the comments in the function. func (b *builder) createTypeAssert(expr *ssa.TypeAssert) llvm.Value { itf := b.getValue(expr.X, getPos(expr)) assertedType := b.getLLVMType(expr.AssertedType) actualTypeNum := b.CreateExtractValue(itf, 0, "interface.type") commaOk := llvm.Value{} if intf, ok := expr.AssertedType.Underlying().(*types.Interface); ok { if intf.Empty() { // intf is the empty interface => no methods // This type assertion always succeeds, so we can just set commaOk to true. commaOk = llvm.ConstInt(b.ctx.Int1Type(), 1, true) } else { // Type assert on an interface type with methods. // Create a call to a declared-but-not-defined function that will // be lowered by the interface lowering pass into a type-ID // comparison chain. commaOk = b.createInterfaceTypeAssert(intf, actualTypeNum) } } else { name, _ := b.getTypeCodeName(expr.AssertedType) globalName := "reflect/types.typeid:" + name assertedTypeCodeGlobal := b.mod.NamedGlobal(globalName) if assertedTypeCodeGlobal.IsNil() { // Create a new typecode global. assertedTypeCodeGlobal = llvm.AddGlobal(b.mod, b.ctx.Int8Type(), globalName) assertedTypeCodeGlobal.SetGlobalConstant(true) } // Type assert on concrete type. // Call runtime.typeAssert, which will be lowered to a simple icmp or // const false in the interface lowering pass. commaOk = b.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode") } // Add 2 new basic blocks (that should get optimized away): one for the // 'ok' case and one for all instructions following this type assert. // This is necessary because we need to insert the casted value or the // nil value based on whether the assert was successful. Casting before // this check tells LLVM that it can use this value and may // speculatively dereference pointers before the check. This can lead to // a miscompilation resulting in a segfault at runtime. // Additionally, this is even required by the Go spec: a failed // typeassert should return a zero value, not an incorrectly casted // value. prevBlock := b.GetInsertBlock() okBlock := b.insertBasicBlock("typeassert.ok") if expr.CommaOk { nextBlock := b.insertBasicBlock("typeassert.next") b.currentBlockInfo.exit = nextBlock if b.isIndirectAggregate(assertedType) { resultType := b.getLLVMType(expr.Type()) result := b.createIndirectStorage(resultType, "typeassert.result") b.zeroIndirectStorage(result, resultType) b.CreateCondBr(commaOk, okBlock, nextBlock) b.SetInsertPointAtEnd(okBlock) valuePtr := b.extractValuePointerFromInterface(itf) b.copyIndirectAggregate(b.CreateStructGEP(resultType, result, 0, ""), valuePtr, assertedType) b.CreateBr(nextBlock) b.SetInsertPointAtEnd(nextBlock) b.CreateStore(commaOk, b.CreateStructGEP(resultType, result, 1, "")) return result } b.CreateCondBr(commaOk, okBlock, nextBlock) // Retrieve the value from the interface if the type assert was // successful. b.SetInsertPointAtEnd(okBlock) var valueOk llvm.Value if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { // Type assert on interface type. Easy: just return the same // interface value. valueOk = itf } else { // Type assert on concrete type. Extract the underlying type from // the interface (but only after checking it matches). valueOk = b.extractValueFromInterface(itf, assertedType) } b.CreateBr(nextBlock) // Continue after the if statement. b.SetInsertPointAtEnd(nextBlock) phi := b.CreatePHI(assertedType, "typeassert.value") phi.AddIncoming([]llvm.Value{llvm.ConstNull(assertedType), valueOk}, []llvm.BasicBlock{prevBlock, okBlock}) tuple := b.ctx.ConstStruct([]llvm.Value{llvm.Undef(assertedType), llvm.Undef(b.ctx.Int1Type())}, false) // create empty tuple tuple = b.CreateInsertValue(tuple, phi, 0, "") // insert value tuple = b.CreateInsertValue(tuple, commaOk, 1, "") // insert 'comma ok' boolean return tuple } else { // Type assert without comma-ok. If it fails, panic. faultBlock := b.getInterfaceAssertBlock() b.currentBlockInfo.exit = okBlock b.CreateCondBr(commaOk, okBlock, faultBlock) // OK: extract the value from the interface. b.SetInsertPointAtEnd(okBlock) if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok { return itf } return b.extractValueFromInterface(itf, assertedType) } } func (b *builder) getInterfaceAssertBlock() llvm.BasicBlock { if !b.interfaceAssertBlock.IsNil() { return b.interfaceAssertBlock } savedBlock := b.GetInsertBlock() block := b.ctx.AddBasicBlock(b.llvmFn, "typeassert.throw") b.interfaceAssertBlock = block b.SetInsertPointAtEnd(block) if b.hasDeferFrame() { b.createFaultCheckpoint() } b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{llvm.ConstInt(b.ctx.Int1Type(), 0, false)}, "") b.CreateUnreachable() b.SetInsertPointAtEnd(savedBlock) return block } // getMethodsString returns a string to be used in the "tinygo-methods" string // attribute for interface functions. func (c *compilerContext) getMethodsString(itf *types.Interface) string { methods := make([]string, itf.NumMethods()) for i := range methods { methods[i] = c.getMethodSignatureName(itf.Method(i)) } return strings.Join(methods, "; ") } // getMethodSetValue creates the method set struct value for a list of methods. // The struct contains a length and a sorted array of method signature pointers. func (c *compilerContext) getMethodSetValue(methods []*types.Func) llvm.Value { // Create a sorted list of method signature global names. type methodRef struct { name string value llvm.Value } var refs []methodRef for _, method := range methods { name := method.Name() if !token.IsExported(name) { name = method.Pkg().Path() + "." + name } s, _ := c.getTypeCodeName(method.Type()) globalName := "reflect/types.signature:" + name + ":" + s value := c.mod.NamedGlobal(globalName) if value.IsNil() { value = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName) value.SetInitializer(llvm.ConstNull(c.ctx.Int8Type())) value.SetGlobalConstant(true) value.SetLinkage(llvm.LinkOnceODRLinkage) value.SetAlignment(1) if c.Debug { file := c.getDIFile("") diglobal := c.dibuilder.CreateGlobalVariableExpression(file, llvm.DIGlobalVariableExpression{ Name: globalName, File: file, Line: 1, Type: c.getDIType(types.Typ[types.Uint8]), LocalToUnit: false, Expr: c.dibuilder.CreateExpression(nil), AlignInBits: 8, }) value.AddMetadata(0, diglobal) } } refs = append(refs, methodRef{globalName, value}) } sort.Slice(refs, func(i, j int) bool { return refs[i].name < refs[j].name }) var values []llvm.Value for _, ref := range refs { values = append(values, ref.value) } return c.ctx.ConstStruct([]llvm.Value{ llvm.ConstInt(c.uintptrType, uint64(len(values)), false), llvm.ConstArray(c.dataPtrType, values), }, false) } // getInvokeFunction returns the thunk to call the given interface method. The // thunk is declared, not defined: it will be defined by the interface lowering // pass. func (c *compilerContext) getInvokeFunction(instr *ssa.CallCommon) llvm.Value { fnName := c.getInvokeFunctionName(instr) llvmFn := c.mod.NamedFunction(fnName) if llvmFn.IsNil() { sig := instr.Method.Type().(*types.Signature) var paramTuple []*types.Var for v := range sig.Params().Variables() { paramTuple = append(paramTuple, v) } paramTuple = append(paramTuple, types.NewVar(token.NoPos, nil, "$typecode", types.Typ[types.UnsafePointer])) llvmFnType := c.getLLVMFunctionType(types.NewSignature(sig.Recv(), types.NewTuple(paramTuple...), sig.Results(), false)) llvmFn = llvm.AddFunction(c.mod, fnName, llvmFnType) c.addStandardDeclaredAttributes(llvmFn) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-invoke", c.getMethodSignatureName(instr.Method))) if _, indirect := c.hasIndirectResult(sig); indirect { llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-indirect-result", "true")) } methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface)) llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods)) } return llvmFn } func (c *compilerContext) getInvokeFunctionName(instr *ssa.CallCommon) string { s, _ := c.getTypeCodeName(instr.Value.Type().Underlying()) return s + "." + instr.Method.Name() + "$invoke" } // createInterfaceTypeAssert creates a call to a declared-but-not-defined // $typeassert function for the given interface. This function will be defined // by the interface lowering pass as a type-ID comparison chain, avoiding the // need for runtime.typeImplementsMethodSet at compile time. func (b *builder) createInterfaceTypeAssert(intf *types.Interface, actualType llvm.Value) llvm.Value { s, _ := b.getTypeCodeName(intf) fnName := s + ".$typeassert" llvmFn := b.mod.NamedFunction(fnName) if llvmFn.IsNil() { llvmFnType := llvm.FunctionType(b.ctx.Int1Type(), []llvm.Type{b.dataPtrType}, false) llvmFn = llvm.AddFunction(b.mod, fnName, llvmFnType) b.addStandardDeclaredAttributes(llvmFn) methods := b.getMethodsString(intf) llvmFn.AddFunctionAttr(b.ctx.CreateStringAttribute("tinygo-methods", methods)) } return b.CreateCall(llvmFn.GlobalValueType(), llvmFn, []llvm.Value{actualType}, "") } // getInterfaceInvokeWrapper returns a wrapper for the given method so it can be // invoked from an interface. The wrapper takes in a pointer to the underlying // value, dereferences or unpacks it if necessary, and calls the real method. // If the method to wrap has a pointer receiver, no wrapping is necessary and // the function is returned directly. func (c *compilerContext) getInterfaceInvokeWrapper(fn *ssa.Function, llvmFnType llvm.Type, llvmFn llvm.Value) llvm.Value { wrapperName := llvmFn.Name() + "$invoke" wrapper := c.mod.NamedFunction(wrapperName) if !wrapper.IsNil() { // Wrapper already created. Return it directly. return wrapper } // Get the expanded receiver type. receiverType := c.getLLVMType(fn.Signature.Recv().Type()) var expandedReceiverType []llvm.Type receiverIndirect := c.isIndirectAggregate(receiverType) for _, info := range c.expandFormalParamType(receiverType, "", nil) { expandedReceiverType = append(expandedReceiverType, info.llvmType) } // Does this method even need any wrapping? if len(expandedReceiverType) == 1 && receiverType.TypeKind() == llvm.PointerTypeKind { // Nothing to wrap. // Casting a function signature to a different signature and calling it // with a receiver pointer bitcasted to *i8 (as done in calls on an // interface) is hopefully a safe (defined) operation. return llvmFn } // create wrapper function resultOffset := 0 if _, indirect := c.hasIndirectResult(fn.Signature); indirect { resultOffset = 1 } paramTypes := append([]llvm.Type{}, llvmFnType.ParamTypes()[:resultOffset]...) paramTypes = append(paramTypes, c.dataPtrType) paramTypes = append(paramTypes, llvmFnType.ParamTypes()[resultOffset+len(expandedReceiverType):]...) wrapFnType := llvm.FunctionType(llvmFnType.ReturnType(), paramTypes, false) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType) c.addStandardAttributes(wrapper) wrapper.SetLinkage(llvm.LinkOnceODRLinkage) wrapper.SetUnnamedAddr(true) // Create a new builder just to create this wrapper. b := builder{ compilerContext: c, Builder: c.ctx.NewBuilder(), } defer b.Builder.Dispose() // add debug info if needed if c.Debug { pos := c.program.Fset.Position(fn.Pos()) difunc := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line) b.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) } // set up IR builder block := b.ctx.AddBasicBlock(wrapper, "entry") b.SetInsertPointAtEnd(block) params := append([]llvm.Value{}, wrapper.Params()[:resultOffset]...) receiverParam := wrapper.Param(resultOffset) if receiverIndirect { params = append(params, receiverParam) } else { receiverValue := b.emitPointerUnpack(receiverParam, []llvm.Type{receiverType})[0] params = append(params, b.expandFormalParam(receiverValue)...) } params = append(params, wrapper.Params()[resultOffset+1:]...) if llvmFnType.ReturnType().TypeKind() == llvm.VoidTypeKind { b.CreateCall(llvmFnType, llvmFn, params, "") b.CreateRetVoid() } else { ret := b.CreateCall(llvmFnType, llvmFn, params, "ret") b.CreateRet(ret) } return wrapper }