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("<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.
//
// 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("<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 {
	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
}
