mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-07-26 06:38:42 +00:00
9e7d89d4d5
LLVM ComputeValueVTs recursively expands arrays and structs into one value type per scalar leaf. SelectionDAG call lowering allocates data structures proportional to this count, which makes very large values exhaust memory or crash LLVM. Count scalar leaves and use pointers for internal parameters and results when the count exceeds 1024. A result pointer is the first parameter, and aggregate parameters point to read-only memory. Exported function types are unchanged. Keep these SSA values in memory and copy them with memcpy when needed. Handle calls, interfaces, maps, channels, selects, defers, goroutines, phis, and multiple results. Update the expected compiler IR and re-enable the native compress/flate tests.
792 lines
27 KiB
Go
792 lines
27 KiB
Go
package transform
|
|
|
|
// This file provides function to lower interface intrinsics to their final LLVM
|
|
// form, optimizing them in the process.
|
|
//
|
|
// During SSA construction, the following pseudo-call is created (see
|
|
// src/runtime/interface.go):
|
|
// runtime.typeAssert(typecode, assertedType)
|
|
// Additionally, interface type asserts and interface invoke functions are
|
|
// declared but not defined, so the optimizer will leave them alone.
|
|
//
|
|
// This pass lowers these functions to their final form:
|
|
//
|
|
// typeAssert:
|
|
// Replaced with an icmp instruction so it can be directly used in a type
|
|
// switch.
|
|
//
|
|
// interface type assert:
|
|
// These functions are defined by creating a big type switch over all the
|
|
// concrete types implementing this interface.
|
|
//
|
|
// interface invoke:
|
|
// These functions are defined with a similar type switch, but instead of
|
|
// checking for the appropriate type, these functions will call the
|
|
// underlying method instead.
|
|
//
|
|
// Note that this way of implementing interfaces is very different from how the
|
|
// main Go compiler implements them. For more details on how the main Go
|
|
// compiler does it: https://research.swtch.com/interfaces
|
|
|
|
import (
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/tinygo-org/tinygo/compileopts"
|
|
"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
|
|
|
|
// signatureInfo is a Go signature of an interface method. It does not represent
|
|
// any method in particular.
|
|
type signatureInfo struct {
|
|
name string
|
|
methods []*methodInfo
|
|
interfaces []*interfaceInfo
|
|
}
|
|
|
|
// methodInfo describes a single method on a concrete type.
|
|
type methodInfo struct {
|
|
*signatureInfo
|
|
function llvm.Value
|
|
}
|
|
|
|
// typeInfo describes a single concrete Go type, which can be a basic or a named
|
|
// type. If it is a named type, it may have methods.
|
|
type typeInfo struct {
|
|
name string
|
|
typecode llvm.Value
|
|
typecodeGEP llvm.Value
|
|
methodSet llvm.Value
|
|
methods []*methodInfo
|
|
}
|
|
|
|
// getMethod looks up the method on this type with the given signature and
|
|
// returns it. The method must exist on this type, otherwise getMethod will
|
|
// panic.
|
|
func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
|
for _, method := range t.methods {
|
|
if method.signatureInfo == signature {
|
|
return method
|
|
}
|
|
}
|
|
panic("could not find method")
|
|
}
|
|
|
|
// interfaceInfo keeps information about a Go interface type, including all
|
|
// methods it has.
|
|
type interfaceInfo struct {
|
|
name string // "tinygo-methods" attribute
|
|
signatures map[string]*signatureInfo // method set
|
|
types []*typeInfo // types this interface implements
|
|
}
|
|
|
|
// lowerInterfacesPass keeps state related to the interface lowering pass. The
|
|
// pass has been implemented as an object type because of its complexity, but
|
|
// should be seen as a regular function call (see LowerInterfaces).
|
|
type lowerInterfacesPass struct {
|
|
mod llvm.Module
|
|
config *compileopts.Config
|
|
builder llvm.Builder
|
|
dibuilder *llvm.DIBuilder
|
|
difiles map[string]llvm.Metadata
|
|
ctx llvm.Context
|
|
uintptrType llvm.Type
|
|
targetData llvm.TargetData
|
|
ptrType llvm.Type
|
|
types map[string]*typeInfo
|
|
signatures map[string]*signatureInfo
|
|
interfaces map[string]*interfaceInfo
|
|
}
|
|
|
|
// LowerInterfaces lowers all intermediate interface calls and globals that are
|
|
// emitted by the compiler as higher-level intrinsics. They need some lowering
|
|
// before LLVM can work on them. This is done so that a few cleanup passes can
|
|
// run before assigning the final type codes.
|
|
func LowerInterfaces(mod llvm.Module, config *compileopts.Config) error {
|
|
ctx := mod.Context()
|
|
targetData := llvm.NewTargetData(mod.DataLayout())
|
|
defer targetData.Dispose()
|
|
p := &lowerInterfacesPass{
|
|
mod: mod,
|
|
config: config,
|
|
builder: ctx.NewBuilder(),
|
|
ctx: ctx,
|
|
targetData: targetData,
|
|
uintptrType: mod.Context().IntType(targetData.PointerSize() * 8),
|
|
ptrType: llvm.PointerType(ctx.Int8Type(), 0),
|
|
types: make(map[string]*typeInfo),
|
|
signatures: make(map[string]*signatureInfo),
|
|
interfaces: make(map[string]*interfaceInfo),
|
|
}
|
|
defer p.builder.Dispose()
|
|
|
|
if config.Debug() {
|
|
p.dibuilder = llvm.NewDIBuilder(mod)
|
|
defer p.dibuilder.Destroy()
|
|
defer p.dibuilder.Finalize()
|
|
p.difiles = make(map[string]llvm.Metadata)
|
|
}
|
|
|
|
return p.run()
|
|
}
|
|
|
|
// run runs the pass itself.
|
|
func (p *lowerInterfacesPass) run() error {
|
|
if p.dibuilder != nil {
|
|
p.dibuilder.CreateCompileUnit(llvm.DICompileUnit{
|
|
Language: 0xb, // DW_LANG_C99 (0xc, off-by-one?)
|
|
File: "<unknown>",
|
|
Dir: "",
|
|
Producer: "TinyGo",
|
|
Optimized: true,
|
|
})
|
|
}
|
|
|
|
// Collect all type codes.
|
|
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
|
if after, ok := strings.CutPrefix(global.Name(), "reflect/types.type:"); ok {
|
|
// Retrieve Go type information based on an opaque global variable.
|
|
// Only the name of the global is relevant, the object itself is
|
|
// discarded afterwards.
|
|
name := after
|
|
if _, ok := p.types[name]; !ok {
|
|
t := &typeInfo{
|
|
name: name,
|
|
typecode: global,
|
|
}
|
|
p.types[name] = t
|
|
initializer := global.Initializer()
|
|
firstField := p.builder.CreateExtractValue(initializer, 0, "")
|
|
if firstField.Type() != p.ctx.Int8Type() {
|
|
// This type has a method set at index 0. Change the GEP to
|
|
// point to index 1 (the meta byte).
|
|
t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
|
|
llvm.ConstInt(p.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(p.ctx.Int32Type(), 1, false),
|
|
})
|
|
methodSet := stripPointerCasts(firstField)
|
|
if !strings.HasSuffix(methodSet.Name(), "$methodset") {
|
|
panic("expected method set")
|
|
}
|
|
p.addTypeMethods(t, methodSet)
|
|
} else {
|
|
// This type has no method set.
|
|
t.typecodeGEP = llvm.ConstGEP(global.GlobalValueType(), global, []llvm.Value{
|
|
llvm.ConstInt(p.ctx.Int32Type(), 0, false),
|
|
llvm.ConstInt(p.ctx.Int32Type(), 0, false),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find all interface type asserts and interface method thunks.
|
|
var interfaceAssertFunctions []llvm.Value
|
|
var interfaceInvokeFunctions []llvm.Value
|
|
for fn := p.mod.FirstFunction(); !fn.IsNil(); fn = llvm.NextFunction(fn) {
|
|
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
|
if methodsAttr.IsNil() {
|
|
continue
|
|
}
|
|
if !hasUses(fn) {
|
|
// Don't bother defining this function.
|
|
continue
|
|
}
|
|
p.addInterface(methodsAttr.GetStringValue())
|
|
invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
|
|
if invokeAttr.IsNil() {
|
|
// Type assert.
|
|
interfaceAssertFunctions = append(interfaceAssertFunctions, fn)
|
|
} else {
|
|
// Interface invoke.
|
|
interfaceInvokeFunctions = append(interfaceInvokeFunctions, fn)
|
|
}
|
|
}
|
|
|
|
// Find all the interfaces that are implemented per type.
|
|
for _, t := range p.types {
|
|
// This type has no methods, so don't spend time calculating them.
|
|
if len(t.methods) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Pre-calculate a set of signatures that this type has, for easy
|
|
// lookup/check.
|
|
typeSignatureSet := make(map[*signatureInfo]struct{})
|
|
for _, method := range t.methods {
|
|
typeSignatureSet[method.signatureInfo] = struct{}{}
|
|
}
|
|
|
|
// A set of interfaces, mapped from the name to the info.
|
|
// When the name maps to a nil pointer, one of the methods of this type
|
|
// exists in the given interface but not all of them so this type
|
|
// doesn't implement the interface.
|
|
satisfiesInterfaces := make(map[string]*interfaceInfo)
|
|
|
|
for _, method := range t.methods {
|
|
for _, itf := range method.interfaces {
|
|
if _, ok := satisfiesInterfaces[itf.name]; ok {
|
|
// interface already checked with a different method
|
|
continue
|
|
}
|
|
// check whether this interface satisfies this type
|
|
satisfies := true
|
|
for _, itfSignature := range itf.signatures {
|
|
if _, ok := typeSignatureSet[itfSignature]; !ok {
|
|
satisfiesInterfaces[itf.name] = nil // does not satisfy
|
|
satisfies = false
|
|
break
|
|
}
|
|
}
|
|
if !satisfies {
|
|
continue
|
|
}
|
|
satisfiesInterfaces[itf.name] = itf
|
|
}
|
|
}
|
|
|
|
// Add this type to all interfaces that satisfy this type.
|
|
for _, itf := range satisfiesInterfaces {
|
|
if itf == nil {
|
|
// Interface does not implement this type, but one of the
|
|
// methods on this type also exists on the interface.
|
|
continue
|
|
}
|
|
itf.types = append(itf.types, t)
|
|
}
|
|
}
|
|
|
|
// Sort all types added to the interfaces.
|
|
for _, itf := range p.interfaces {
|
|
sort.Slice(itf.types, func(i, j int) bool {
|
|
return itf.types[i].name > itf.types[j].name
|
|
})
|
|
}
|
|
|
|
// Define all interface invoke thunks.
|
|
for _, fn := range interfaceInvokeFunctions {
|
|
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
|
invokeAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-invoke")
|
|
itf := p.interfaces[methodsAttr.GetStringValue()]
|
|
signature := itf.signatures[invokeAttr.GetStringValue()]
|
|
p.defineInterfaceMethodFunc(fn, itf, signature)
|
|
}
|
|
|
|
// Define all interface type assert functions.
|
|
for _, fn := range interfaceAssertFunctions {
|
|
methodsAttr := fn.GetStringAttributeAtIndex(-1, "tinygo-methods")
|
|
itf := p.interfaces[methodsAttr.GetStringValue()]
|
|
p.defineInterfaceAssertFunc(fn, itf)
|
|
}
|
|
|
|
// Replace each type assert with an actual type comparison or (if the type
|
|
// assert is impossible) the constant false.
|
|
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
|
for _, use := range getUses(p.mod.NamedFunction("runtime.typeAssert")) {
|
|
actualType := use.Operand(0)
|
|
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
|
|
gepOffset := uint64(0)
|
|
for strings.HasPrefix(name, "pointer:pointer:") {
|
|
// This is a type like **int, which has the name pointer:pointer:int
|
|
// but is encoded using pointer tagging.
|
|
// Calculate the pointer tag, which is emitted as a GEP instruction.
|
|
name = name[len("pointer:"):]
|
|
gepOffset++
|
|
}
|
|
|
|
if t, ok := p.types[name]; ok {
|
|
// The type exists in the program, so lower to a regular pointer
|
|
// comparison.
|
|
p.builder.SetInsertPointBefore(use)
|
|
typecodeGEP := t.typecodeGEP
|
|
if gepOffset != 0 {
|
|
// This is a tagged pointer.
|
|
typecodeGEP = llvm.ConstInBoundsGEP(p.ctx.Int8Type(), typecodeGEP, []llvm.Value{
|
|
llvm.ConstInt(p.ctx.Int64Type(), gepOffset, false),
|
|
})
|
|
}
|
|
commaOk := p.builder.CreateICmp(llvm.IntEQ, typecodeGEP, actualType, "typeassert.ok")
|
|
use.ReplaceAllUsesWith(commaOk)
|
|
} else {
|
|
// The type does not exist in the program, so lower to a constant
|
|
// false. This is trivially further optimized.
|
|
// TODO: eventually it'll be necessary to handle reflect.PtrTo and
|
|
// reflect.New calls which create new types not present in the
|
|
// original program.
|
|
use.ReplaceAllUsesWith(llvmFalse)
|
|
}
|
|
use.EraseFromParentAsInstruction()
|
|
}
|
|
|
|
// Create a sorted list of type names, for predictable iteration.
|
|
var typeNames []string
|
|
for name := range p.types {
|
|
typeNames = append(typeNames, name)
|
|
}
|
|
sort.Strings(typeNames)
|
|
|
|
// Check whether runtime.typeImplementsMethodSet still has uses. Now that
|
|
// interface type assertions have been lowered to type-ID comparison
|
|
// chains, the only remaining callers would be from reflect
|
|
// (AssignableTo/Implements). If none remain, we can strip the inline
|
|
// method-set data from type descriptors to save binary size.
|
|
stripMethodSets := false
|
|
typeImplementsFn := p.mod.NamedFunction("runtime.typeImplementsMethodSet")
|
|
if !typeImplementsFn.IsNil() && !hasUses(typeImplementsFn) {
|
|
stripMethodSets = true
|
|
}
|
|
|
|
// Collect all method signatures that appear in any interface type
|
|
// descriptor. When reflect is imported and method sets are kept,
|
|
// concrete type method sets are pruned: individual methods not in any
|
|
// interface are removed, and types that can't fully satisfy at least
|
|
// one interface have their method sets emptied entirely.
|
|
//
|
|
// When method sets are stripped entirely (reflect not imported),
|
|
// methodFilter is nil and filterMethodSet replaces with empty.
|
|
var methodFilter map[string]struct{}
|
|
var ifaceMethodSets []map[string]struct{}
|
|
if !stripMethodSets {
|
|
methodFilter = make(map[string]struct{})
|
|
for _, name := range typeNames {
|
|
if !strings.HasPrefix(name, "interface:") {
|
|
continue
|
|
}
|
|
t := p.types[name]
|
|
initializer := t.typecode.Initializer()
|
|
ifaceSet := make(map[string]struct{})
|
|
for i := 0; i < initializer.Type().StructElementTypesCount(); i++ {
|
|
field := p.builder.CreateExtractValue(initializer, i, "")
|
|
for _, sig := range p.extractMethodSigs(field) {
|
|
methodFilter[sig] = struct{}{}
|
|
ifaceSet[sig] = struct{}{}
|
|
}
|
|
}
|
|
if len(ifaceSet) > 0 {
|
|
ifaceMethodSets = append(ifaceMethodSets, ifaceSet)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove all method sets, which are now unnecessary and inhibit later
|
|
// optimizations if they are left in place.
|
|
zero := llvm.ConstInt(p.ctx.Int32Type(), 0, false)
|
|
for _, name := range typeNames {
|
|
t := p.types[name]
|
|
if !t.methodSet.IsNil() {
|
|
initializer := t.typecode.Initializer()
|
|
numFields := initializer.Type().StructElementTypesCount()
|
|
|
|
// Read numMethods from the original type descriptor (index 2:
|
|
// after prefix pointer at 0 and kind byte at 1). For Named,
|
|
// Pointer, and Struct types, the numMethodHasMethodSet flag
|
|
// indicates that an inline method set is present.
|
|
var numMethodsConst uint64
|
|
var numMethodsIsI16 bool
|
|
if numFields > 2 {
|
|
nmField := p.builder.CreateExtractValue(initializer, 2, "")
|
|
if nmField.Type() == p.ctx.Int16Type() {
|
|
numMethodsConst = nmField.ZExtValue()
|
|
numMethodsIsI16 = true
|
|
}
|
|
}
|
|
|
|
var newInitializerFields []llvm.Value
|
|
for i := 1; i < numFields; i++ {
|
|
field := p.builder.CreateExtractValue(initializer, i, "")
|
|
field = p.filterMethodSet(field, methodFilter, ifaceMethodSets)
|
|
// Strip empty inline method sets for Named, Pointer, and
|
|
// Struct types. When the method set is pruned to empty, we
|
|
// remove it and clear the numMethodHasMethodSet flag (bit 15
|
|
// of numMethod) so the runtime skips reading it.
|
|
if numMethodsIsI16 && numMethodsConst&numMethodHasMethodSet != 0 && p.isMethodSetType(field.Type()) {
|
|
elems := field.Type().StructElementTypes()
|
|
if elems[1].ArrayLength() == 0 {
|
|
clearedNumMethods := numMethodsConst & ^uint64(numMethodHasMethodSet)
|
|
newInitializerFields[1] = llvm.ConstInt(p.ctx.Int16Type(), clearedNumMethods, false)
|
|
continue
|
|
}
|
|
}
|
|
newInitializerFields = append(newInitializerFields, field)
|
|
}
|
|
newInitializer := p.ctx.ConstStruct(newInitializerFields, false)
|
|
typecodeName := t.typecode.Name()
|
|
newGlobal := llvm.AddGlobal(p.mod, newInitializer.Type(), typecodeName+".tmp")
|
|
newGlobal.SetInitializer(newInitializer)
|
|
newGlobal.SetLinkage(t.typecode.Linkage())
|
|
newGlobal.SetGlobalConstant(true)
|
|
newGlobal.SetAlignment(t.typecode.Alignment())
|
|
for _, use := range getUses(t.typecode) {
|
|
if !use.IsAConstantExpr().IsNil() {
|
|
opcode := use.Opcode()
|
|
if opcode == llvm.GetElementPtr && use.OperandsCount() == 3 {
|
|
if use.Operand(1).ZExtValue() == 0 && use.Operand(2).ZExtValue() == 1 {
|
|
gep := p.builder.CreateInBoundsGEP(newGlobal.GlobalValueType(), newGlobal, []llvm.Value{zero, zero}, "")
|
|
use.ReplaceAllUsesWith(gep)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Fallback.
|
|
if hasUses(t.typecode) {
|
|
negativeOffset := -int64(p.targetData.TypeAllocSize(p.ptrType))
|
|
gep := p.builder.CreateInBoundsGEP(p.ctx.Int8Type(), newGlobal, []llvm.Value{llvm.ConstInt(p.ctx.Int32Type(), uint64(negativeOffset), true)}, "")
|
|
t.typecode.ReplaceAllUsesWith(gep)
|
|
}
|
|
t.typecode.EraseFromParentAsGlobal()
|
|
newGlobal.SetName(typecodeName)
|
|
t.typecode = newGlobal
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// addTypeMethods reads the method set of the given type info struct. It
|
|
// retrieves the signatures and the references to the method functions
|
|
// themselves for later type<->interface matching.
|
|
func (p *lowerInterfacesPass) addTypeMethods(t *typeInfo, methodSet llvm.Value) {
|
|
if !t.methodSet.IsNil() {
|
|
// no methods or methods already read
|
|
return
|
|
}
|
|
|
|
// This type has methods, collect all methods of this type.
|
|
t.methodSet = methodSet
|
|
set := methodSet.Initializer() // get value from global
|
|
signatures := p.builder.CreateExtractValue(set, 1, "")
|
|
wrappers := p.builder.CreateExtractValue(set, 2, "")
|
|
numMethods := signatures.Type().ArrayLength()
|
|
for i := range numMethods {
|
|
signatureGlobal := p.builder.CreateExtractValue(signatures, i, "")
|
|
function := p.builder.CreateExtractValue(wrappers, i, "")
|
|
function = stripPointerCasts(function) // strip bitcasts
|
|
signatureName := signatureGlobal.Name()
|
|
signature := p.getSignature(signatureName)
|
|
method := &methodInfo{
|
|
function: function,
|
|
signatureInfo: signature,
|
|
}
|
|
signature.methods = append(signature.methods, method)
|
|
t.methods = append(t.methods, method)
|
|
}
|
|
}
|
|
|
|
// addInterface reads information about an interface, which is the
|
|
// fully-qualified name and the signatures of all methods it has.
|
|
func (p *lowerInterfacesPass) addInterface(methodsString string) {
|
|
if _, ok := p.interfaces[methodsString]; ok {
|
|
return
|
|
}
|
|
t := &interfaceInfo{
|
|
name: methodsString,
|
|
signatures: make(map[string]*signatureInfo),
|
|
}
|
|
p.interfaces[methodsString] = t
|
|
for method := range strings.SplitSeq(methodsString, "; ") {
|
|
signature := p.getSignature(method)
|
|
signature.interfaces = append(signature.interfaces, t)
|
|
t.signatures[method] = signature
|
|
}
|
|
}
|
|
|
|
// getSignature returns a new *signatureInfo, creating it if it doesn't already
|
|
// exist.
|
|
func (p *lowerInterfacesPass) getSignature(name string) *signatureInfo {
|
|
if _, ok := p.signatures[name]; !ok {
|
|
p.signatures[name] = &signatureInfo{
|
|
name: name,
|
|
}
|
|
}
|
|
return p.signatures[name]
|
|
}
|
|
|
|
// defineInterfaceMethodFunc defines this thunk by calling the concrete method
|
|
// of the type that implements this interface.
|
|
//
|
|
// Matching the actual type is implemented using an if/else chain over all
|
|
// possible types. This is later converted to a switch statement by the LLVM
|
|
// simplifycfg pass.
|
|
func (p *lowerInterfacesPass) defineInterfaceMethodFunc(fn llvm.Value, itf *interfaceInfo, signature *signatureInfo) {
|
|
context := fn.LastParam()
|
|
actualType := llvm.PrevParam(context)
|
|
returnType := fn.GlobalValueType().ReturnType()
|
|
resultOffset := 0
|
|
if fn.GetStringAttributeAtIndex(-1, "tinygo-indirect-result").GetStringValue() == "true" {
|
|
resultOffset = 1
|
|
}
|
|
context.SetName("context")
|
|
actualType.SetName("actualType")
|
|
fn.SetLinkage(llvm.InternalLinkage)
|
|
fn.SetUnnamedAddr(true)
|
|
AddStandardAttributes(fn, p.config)
|
|
|
|
// Collect the params that will be passed to the functions to call.
|
|
// These params exclude the receiver (which may actually consist of multiple
|
|
// parts).
|
|
params := make([]llvm.Value, fn.ParamsCount()-3-resultOffset)
|
|
for i := range params {
|
|
params[i] = fn.Param(i + 1 + resultOffset)
|
|
}
|
|
params = append(params,
|
|
llvm.Undef(p.ptrType),
|
|
)
|
|
|
|
// Start chain in the entry block.
|
|
entry := p.ctx.AddBasicBlock(fn, "entry")
|
|
p.builder.SetInsertPointAtEnd(entry)
|
|
|
|
if p.dibuilder != nil {
|
|
difile := p.getDIFile("<Go interface method>")
|
|
diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
|
File: difile,
|
|
})
|
|
difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
|
|
Name: "(Go interface method)",
|
|
File: difile,
|
|
Line: 0,
|
|
Type: diFuncType,
|
|
LocalToUnit: true,
|
|
IsDefinition: true,
|
|
ScopeLine: 0,
|
|
Flags: llvm.FlagPrototyped,
|
|
Optimized: true,
|
|
})
|
|
fn.SetSubprogram(difunc)
|
|
p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{})
|
|
}
|
|
|
|
// Define all possible functions that can be called.
|
|
for _, typ := range itf.types {
|
|
// Create type check (if/else).
|
|
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
|
next := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
|
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
|
|
p.builder.CreateCondBr(cmp, bb, next)
|
|
|
|
// The function we will redirect to when the interface has this type.
|
|
function := typ.getMethod(signature).function
|
|
|
|
p.builder.SetInsertPointAtEnd(bb)
|
|
receiver := fn.Param(resultOffset)
|
|
|
|
callParams := make([]llvm.Value, 0, len(params)+2+resultOffset)
|
|
if resultOffset != 0 {
|
|
callParams = append(callParams, fn.FirstParam())
|
|
}
|
|
callParams = append(callParams, receiver)
|
|
callParams = append(callParams, params...)
|
|
paramTypes := make([]llvm.Type, len(callParams))
|
|
for i, param := range callParams {
|
|
paramTypes[i] = param.Type()
|
|
}
|
|
functionType := llvm.FunctionType(returnType, paramTypes, false)
|
|
retval := p.builder.CreateCall(functionType, function, callParams, "")
|
|
if retval.Type().TypeKind() == llvm.VoidTypeKind {
|
|
p.builder.CreateRetVoid()
|
|
} else {
|
|
p.builder.CreateRet(retval)
|
|
}
|
|
|
|
// Start next comparison in the 'next' block (which is jumped to when
|
|
// the type doesn't match).
|
|
p.builder.SetInsertPointAtEnd(next)
|
|
}
|
|
|
|
// The builder now points to the last *.then block, after all types have
|
|
// been checked. Call runtime.nilPanic here.
|
|
// The only other possible value remaining is nil for nil interfaces. We
|
|
// could panic with a different message here such as "nil interface" but
|
|
// that would increase code size and "nil panic" is close enough. Most
|
|
// importantly, it avoids undefined behavior when accidentally calling a
|
|
// method on a nil interface.
|
|
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
|
p.builder.CreateCall(nilPanic.GlobalValueType(), nilPanic, []llvm.Value{
|
|
llvm.Undef(p.ptrType),
|
|
}, "")
|
|
p.builder.CreateUnreachable()
|
|
}
|
|
|
|
func (p *lowerInterfacesPass) getDIFile(file string) llvm.Metadata {
|
|
difile, ok := p.difiles[file]
|
|
if !ok {
|
|
difile = p.dibuilder.CreateFile(file, "")
|
|
p.difiles[file] = difile
|
|
}
|
|
return difile
|
|
}
|
|
|
|
// defineInterfaceAssertFunc defines a $typeassert function for the given
|
|
// interface. The function returns true if the concrete type (passed as a
|
|
// type-ID pointer) implements the interface, using a chain of type-ID
|
|
// comparisons. This avoids pulling in runtime.typeImplementsMethodSet for
|
|
// programs that don't use reflect.
|
|
func (p *lowerInterfacesPass) defineInterfaceAssertFunc(fn llvm.Value, itf *interfaceInfo) {
|
|
actualType := fn.FirstParam()
|
|
actualType.SetName("actualType")
|
|
fn.SetLinkage(llvm.InternalLinkage)
|
|
fn.SetUnnamedAddr(true)
|
|
AddStandardAttributes(fn, p.config)
|
|
|
|
entry := p.ctx.AddBasicBlock(fn, "entry")
|
|
p.builder.SetInsertPointAtEnd(entry)
|
|
|
|
if p.dibuilder != nil {
|
|
difile := p.getDIFile("<Go interface type assert>")
|
|
diFuncType := p.dibuilder.CreateSubroutineType(llvm.DISubroutineType{
|
|
File: difile,
|
|
})
|
|
difunc := p.dibuilder.CreateFunction(difile, llvm.DIFunction{
|
|
Name: "(Go interface type assert)",
|
|
File: difile,
|
|
Line: 0,
|
|
Type: diFuncType,
|
|
LocalToUnit: true,
|
|
IsDefinition: true,
|
|
ScopeLine: 0,
|
|
Flags: llvm.FlagPrototyped,
|
|
Optimized: true,
|
|
})
|
|
fn.SetSubprogram(difunc)
|
|
p.builder.SetCurrentDebugLocation(0, 0, difunc, llvm.Metadata{})
|
|
}
|
|
|
|
// Build an OR chain: return (type == T1) || (type == T2) || ...
|
|
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
|
result := llvmFalse
|
|
for _, typ := range itf.types {
|
|
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, typ.typecodeGEP, typ.name+".icmp")
|
|
result = p.builder.CreateOr(result, cmp, "")
|
|
}
|
|
p.builder.CreateRet(result)
|
|
}
|
|
|
|
// isMethodSetType reports whether ty has the shape of a method-set struct:
|
|
// { uintptr, [N x ptr] }.
|
|
func (p *lowerInterfacesPass) isMethodSetType(ty llvm.Type) bool {
|
|
if ty.TypeKind() != llvm.StructTypeKind {
|
|
return false
|
|
}
|
|
elems := ty.StructElementTypes()
|
|
if len(elems) != 2 {
|
|
return false
|
|
}
|
|
if elems[0] != p.uintptrType {
|
|
return false
|
|
}
|
|
return elems[1].TypeKind() == llvm.ArrayTypeKind && elems[1].ElementType() == p.ptrType
|
|
}
|
|
|
|
// extractMethodSigs returns the names of method signature globals inside a
|
|
// method-set field ({ uintptr, [N x ptr] }). Returns nil if field is not a
|
|
// method set.
|
|
func (p *lowerInterfacesPass) extractMethodSigs(field llvm.Value) []string {
|
|
if !p.isMethodSetType(field.Type()) {
|
|
return nil
|
|
}
|
|
methodArray := p.builder.CreateExtractValue(field, 1, "")
|
|
n := methodArray.Type().ArrayLength()
|
|
sigs := make([]string, 0, n)
|
|
for j := range n {
|
|
sig := p.builder.CreateExtractValue(methodArray, j, "")
|
|
sig = stripPointerCasts(sig)
|
|
sigs = append(sigs, sig.Name())
|
|
}
|
|
return sigs
|
|
}
|
|
|
|
// filterMethodSet processes a type-descriptor field that may be a method set.
|
|
// Non-method-set fields are returned unchanged.
|
|
//
|
|
// If keepSigs is nil, the method set is replaced with an empty one (strip mode,
|
|
// used when reflect is not imported). If keepSigs is non-nil, the method set is
|
|
// pruned in two stages: first, methods not in keepSigs (the union of all
|
|
// interface signatures) are removed; then, if the remaining methods cannot
|
|
// fully satisfy at least one interface in ifaceSets, the entire method set is
|
|
// emptied.
|
|
func (p *lowerInterfacesPass) filterMethodSet(field llvm.Value, keepSigs map[string]struct{}, ifaceSets []map[string]struct{}) llvm.Value {
|
|
if !p.isMethodSetType(field.Type()) {
|
|
return field
|
|
}
|
|
|
|
methodArray := p.builder.CreateExtractValue(field, 1, "")
|
|
numMethods := methodArray.Type().ArrayLength()
|
|
|
|
// Strip mode: replace with empty method set.
|
|
if keepSigs == nil {
|
|
return p.ctx.ConstStruct([]llvm.Value{
|
|
llvm.ConstInt(p.uintptrType, 0, false),
|
|
llvm.ConstArray(p.ptrType, nil),
|
|
}, false)
|
|
}
|
|
|
|
if numMethods == 0 {
|
|
return field
|
|
}
|
|
|
|
// Extract all methods and their signature names.
|
|
type methodEntry struct {
|
|
value llvm.Value
|
|
name string
|
|
}
|
|
entries := make([]methodEntry, numMethods)
|
|
nameSet := make(map[string]struct{}, numMethods)
|
|
for j := range numMethods {
|
|
sig := p.builder.CreateExtractValue(methodArray, j, "")
|
|
stripped := stripPointerCasts(sig)
|
|
name := stripped.Name()
|
|
entries[j] = methodEntry{sig, name}
|
|
nameSet[name] = struct{}{}
|
|
}
|
|
|
|
// Check whether this type can fully implement at least one interface.
|
|
// If not, its method set can never produce a true result from
|
|
// typeImplementsMethodSet, so we can empty it entirely.
|
|
implementsAny := false
|
|
for _, ifaceSet := range ifaceSets {
|
|
if isSubsetOf(ifaceSet, nameSet) {
|
|
implementsAny = true
|
|
break
|
|
}
|
|
}
|
|
if !implementsAny {
|
|
return p.ctx.ConstStruct([]llvm.Value{
|
|
llvm.ConstInt(p.uintptrType, 0, false),
|
|
llvm.ConstArray(p.ptrType, nil),
|
|
}, false)
|
|
}
|
|
|
|
// Prune: keep only methods whose signature appears in keepSigs.
|
|
var kept []llvm.Value
|
|
for _, e := range entries {
|
|
if _, ok := keepSigs[e.name]; ok {
|
|
kept = append(kept, e.value)
|
|
}
|
|
}
|
|
|
|
if len(kept) == numMethods {
|
|
return field
|
|
}
|
|
|
|
return p.ctx.ConstStruct([]llvm.Value{
|
|
llvm.ConstInt(p.uintptrType, uint64(len(kept)), false),
|
|
llvm.ConstArray(p.ptrType, kept),
|
|
}, false)
|
|
}
|
|
|
|
// isSubsetOf reports whether every key in sub is also in super.
|
|
func isSubsetOf(sub, super map[string]struct{}) bool {
|
|
for k := range sub {
|
|
if _, ok := super[k]; !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|