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"
	"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})
	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")
	return b.emitPointerUnpack(valuePtr, []llvm.Type{llvmType})[0]
}

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
}

// 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)
	hasMethodSet := ms.Len() != 0
	_, isInterface := typ.Underlying().(*types.Interface)
	if isInterface {
		hasMethodSet = false
	}

	// 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
	for i := 0; i < ms.Len(); i++ {
		if isInterface || ms.At(i).Obj().Exported() {
			numMethods++
		}
	}

	// 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 := 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 i := 0; i < ms.Len(); i++ {
			methods = append(methods, ms.At(i).Obj().(*types.Func))
		}
		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 := c.targetData.TypeAllocSize(c.dataPtrType)
		if alignment < 4 {
			alignment = 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("<Go type>")
			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.
func getTypeCodeName(t types.Type) (string, bool) {
	switch t := types.Unalias(t).(type) {
	case *types.Named:
		if t.Obj().Parent() != t.Obj().Pkg().Scope() {
			return "named:" + t.String() + "$local", true
		}
		return "named:" + t.String(), false
	case *types.Array:
		s, isLocal := 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 := 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 := getTypeCodeName(t.Method(i).Type())
			if local {
				isLocal = true
			}
			methods[i] = name + ":" + s
		}
		return "interface:" + "{" + strings.Join(methods, ",") + "}", isLocal
	case *types.Map:
		keyType, keyLocal := getTypeCodeName(t.Key())
		elemType, elemLocal := getTypeCodeName(t.Elem())
		return "map:" + "{" + keyType + "," + elemType + "}", keyLocal || elemLocal
	case *types.Pointer:
		s, isLocal := 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 := 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 := 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 := 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 := 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())
	}
}

// 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 {
	globalName := typ.String() + "$methodset"
	global := c.mod.NamedGlobal(globalName)
	if global.IsNil() {
		ms := c.program.MethodSets.MethodSet(typ)

		// Create method set.
		var signatures, wrappers []llvm.Value
		for i := 0; i < ms.Len(); i++ {
			method := ms.At(i)
			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(ms.Len()), 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 {
	signature := methodSignature(method)
	var globalName string
	if token.IsExported(method.Name()) {
		globalName = "reflect/methods." + signature
	} else {
		globalName = method.Type().(*types.Signature).Recv().Pkg().Path() + ".$methods." + signature
	}
	return globalName
}

// 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, _ := 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")
	nextBlock := b.insertBasicBlock("typeassert.next")
	b.currentBlockInfo.exit = nextBlock // adjust outgoing block for phi nodes
	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})

	if expr.CommaOk {
		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 {
		// This is kind of dirty as the branch above becomes mostly useless,
		// but hopefully this gets optimized away.
		b.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
		return phi
	}
}

// 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, _ := 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("<Go type>")
				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 {
	s, _ := getTypeCodeName(instr.Value.Type().Underlying())
	fnName := s + "." + instr.Method.Name() + "$invoke"
	llvmFn := c.mod.NamedFunction(fnName)
	if llvmFn.IsNil() {
		sig := instr.Method.Type().(*types.Signature)
		var paramTuple []*types.Var
		for i := 0; i < sig.Params().Len(); i++ {
			paramTuple = append(paramTuple, sig.Params().At(i))
		}
		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)))
		methods := c.getMethodsString(instr.Value.Type().Underlying().(*types.Interface))
		llvmFn.AddFunctionAttr(c.ctx.CreateStringAttribute("tinygo-methods", methods))
	}
	return llvmFn
}

// 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, _ := 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
	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
	paramTypes := append([]llvm.Type{c.dataPtrType}, llvmFnType.ParamTypes()[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)

	receiverValue := b.emitPointerUnpack(wrapper.Param(0), []llvm.Type{receiverType})[0]
	params := append(b.expandFormalParam(receiverValue), wrapper.Params()[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
}

// methodSignature creates a readable version of a method signature (including
// the function name, excluding the receiver name). This string is used
// internally to match interfaces and to call the correct method on an
// interface. Examples:
//
//	String() string
//	Read([]byte) (int, error)
func methodSignature(method *types.Func) string {
	return method.Name() + signature(method.Type().(*types.Signature))
}

// Make a readable version of a function (pointer) signature.
// Examples:
//
//	() string
//	(string, int) (int, error)
func signature(sig *types.Signature) string {
	s := ""
	if sig.Params().Len() == 0 {
		s += "()"
	} else {
		s += "("
		for i := 0; i < sig.Params().Len(); i++ {
			if i > 0 {
				s += ", "
			}
			s += typestring(sig.Params().At(i).Type())
		}
		s += ")"
	}
	if sig.Results().Len() == 0 {
		// keep as-is
	} else if sig.Results().Len() == 1 {
		s += " " + typestring(sig.Results().At(0).Type())
	} else {
		s += " ("
		for i := 0; i < sig.Results().Len(); i++ {
			if i > 0 {
				s += ", "
			}
			s += typestring(sig.Results().At(i).Type())
		}
		s += ")"
	}
	return s
}

// typestring returns a stable (human-readable) type string for the given type
// that can be used for interface equality checks. It is almost (but not
// exactly) the same as calling t.String(). The main difference is some
// normalization around `byte` vs `uint8` for example.
func typestring(t types.Type) string {
	// See: https://github.com/golang/go/blob/master/src/go/types/typestring.go
	switch t := types.Unalias(t).(type) {
	case *types.Array:
		return "[" + strconv.FormatInt(t.Len(), 10) + "]" + typestring(t.Elem())
	case *types.Basic:
		return basicTypeNames[t.Kind()]
	case *types.Chan:
		switch t.Dir() {
		case types.SendRecv:
			return "chan (" + typestring(t.Elem()) + ")"
		case types.SendOnly:
			return "chan<- (" + typestring(t.Elem()) + ")"
		case types.RecvOnly:
			return "<-chan (" + typestring(t.Elem()) + ")"
		default:
			panic("unknown channel direction")
		}
	case *types.Interface:
		methods := make([]string, t.NumMethods())
		for i := range methods {
			method := t.Method(i)
			methods[i] = method.Name() + signature(method.Type().(*types.Signature))
		}
		return "interface{" + strings.Join(methods, ";") + "}"
	case *types.Map:
		return "map[" + typestring(t.Key()) + "]" + typestring(t.Elem())
	case *types.Named:
		return t.String()
	case *types.Pointer:
		return "*" + typestring(t.Elem())
	case *types.Signature:
		return "func" + signature(t)
	case *types.Slice:
		return "[]" + typestring(t.Elem())
	case *types.Struct:
		fields := make([]string, t.NumFields())
		for i := range fields {
			field := t.Field(i)
			fields[i] = field.Name() + " " + typestring(field.Type())
			if tag := t.Tag(i); tag != "" {
				fields[i] += " " + strconv.Quote(tag)
			}
		}
		return "struct{" + strings.Join(fields, ";") + "}"
	default:
		panic("unknown type: " + t.String())
	}
}
