compiler: lower interfaces in a separate pass

This commit changes many things:

  * Most interface-related operations are moved into an optimization
    pass for more modularity. IR construction creates pseudo-calls which
    are lowered in this pass.
  * Type codes are assigned in this interface lowering pass, after DCE.
  * Type codes are sorted by usage: types more often used in type
    asserts are assigned lower numbers to ease jump table construction
    during machine code generation.
  * Interface assertions are optimized: they are replaced by constant
    false, comparison against a constant, or a typeswitch with only
    concrete types in the general case.
  * Interface calls are replaced with unreachable, direct calls, or a
    concrete type switch with direct calls depending on the number of
    implementing types. This hopefully makes some interface patterns
    zero-cost.

These changes lead to a ~0.5K reduction in code size on Cortex-M for
testdata/interface.go. It appears that a major cause for this is the
replacement of function pointers with direct calls, which are far more
susceptible to optimization. Also, not having a fixed global array of
function pointers greatly helps dead code elimination.

This change also makes future optimizations easier, like optimizations
on interface value comparisons.
Этот коммит содержится в:
Ayke van Laethem 2018-11-09 17:16:36 +01:00
родитель e45c4ac182
коммит b4c90f3677
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: E97FF5335DFDFDED
13 изменённых файлов: 1039 добавлений и 535 удалений

Просмотреть файл

@ -43,29 +43,30 @@ type Config struct {
type Compiler struct { type Compiler struct {
Config Config
mod llvm.Module mod llvm.Module
ctx llvm.Context ctx llvm.Context
builder llvm.Builder builder llvm.Builder
dibuilder *llvm.DIBuilder dibuilder *llvm.DIBuilder
cu llvm.Metadata cu llvm.Metadata
difiles map[string]llvm.Metadata difiles map[string]llvm.Metadata
ditypes map[string]llvm.Metadata ditypes map[string]llvm.Metadata
machine llvm.TargetMachine machine llvm.TargetMachine
targetData llvm.TargetData targetData llvm.TargetData
intType llvm.Type intType llvm.Type
i8ptrType llvm.Type // for convenience i8ptrType llvm.Type // for convenience
uintptrType llvm.Type uintptrType llvm.Type
coroIdFunc llvm.Value coroIdFunc llvm.Value
coroSizeFunc llvm.Value coroSizeFunc llvm.Value
coroBeginFunc llvm.Value coroBeginFunc llvm.Value
coroSuspendFunc llvm.Value coroSuspendFunc llvm.Value
coroEndFunc llvm.Value coroEndFunc llvm.Value
coroFreeFunc llvm.Value coroFreeFunc llvm.Value
initFuncs []llvm.Value initFuncs []llvm.Value
deferFuncs []*ir.Function deferFuncs []*ir.Function
deferInvokeFuncs []InvokeDeferFunction deferInvokeFuncs []InvokeDeferFunction
ctxDeferFuncs []ContextDeferFunction ctxDeferFuncs []ContextDeferFunction
ir *ir.Program interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program
} }
type Frame struct { type Frame struct {
@ -489,6 +490,15 @@ func (c *Compiler) Compile(mainPath string) error {
c.builder.CreateRetVoid() c.builder.CreateRetVoid()
} }
// Define the already declared functions that wrap methods for use in
// interfaces.
for _, state := range c.interfaceInvokeWrappers {
err = c.createInterfaceInvokeWrapper(state)
if err != nil {
return err
}
}
// After all packages are imported, add a synthetic initializer function // After all packages are imported, add a synthetic initializer function
// that calls the initializer of each package. // that calls the initializer of each package.
initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function)) initFn := c.ir.GetFunction(c.ir.Program.ImportedPackage("runtime").Members["initAll"].(*ssa.Function))
@ -534,13 +544,6 @@ func (c *Compiler) Compile(mainPath string) error {
} }
c.builder.CreateRetVoid() c.builder.CreateRetVoid()
// Add runtime type information for interfaces: interface calls and type
// asserts.
err = c.createInterfaceRTTI()
if err != nil {
return err
}
// see: https://reviews.llvm.org/D18355 // see: https://reviews.llvm.org/D18355
c.mod.AddNamedMetadataOperand("llvm.module.flags", c.mod.AddNamedMetadataOperand("llvm.module.flags",
c.ctx.MDNode([]llvm.Metadata{ c.ctx.MDNode([]llvm.Metadata{
@ -995,17 +998,6 @@ func (c *Compiler) getInterpretedValue(prefix string, value ir.Value) (llvm.Valu
ptr := llvm.ConstInBoundsGEP(value.Global.LLVMGlobal, []llvm.Value{zero}) ptr := llvm.ConstInBoundsGEP(value.Global.LLVMGlobal, []llvm.Value{zero})
return ptr, nil return ptr, nil
case *ir.InterfaceValue:
underlying := llvm.ConstPointerNull(c.i8ptrType) // could be any 0 value
if value.Elem != nil {
elem, err := c.getInterpretedValue(prefix, value.Elem)
if err != nil {
return llvm.Value{}, err
}
underlying = elem
}
return c.parseMakeInterface(underlying, value.Type, prefix)
case *ir.MapValue: case *ir.MapValue:
// Create initial bucket. // Create initial bucket.
firstBucketGlobal, keySize, valueSize, err := c.initMapNewBucket(prefix, value.Type) firstBucketGlobal, keySize, valueSize, err := c.initMapNewBucket(prefix, value.Type)

715
compiler/interface-lowering.go Обычный файл
Просмотреть файл

@ -0,0 +1,715 @@
package compiler
// This file provides function to lower interface intrinsics to their final LLVM
// form, optimizing them in the process.
//
// During SSA construction, the following pseudo-calls are created:
// runtime.makeInterface(typecode, methodSet)
// runtime.typeAssert(typecode, assertedType)
// runtime.interfaceImplements(typecode, interfaceMethodSet)
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
// See src/runtime/interface.go for details.
// These calls are to declared but not defined functions, so the optimizer will
// leave them alone.
//
// This pass lowers the above functions to their final form:
//
// makeInterface:
// Replaced with a constant typecode.
//
// typeAssert:
// Replaced with an icmp instruction so it can be directly used in a type
// switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement.
// When this type assert is not possible (the type is never used in an
// interface with makeInterface), this call is replaced with a constant
// false to optimize the type assert away completely.
//
// interfaceImplements:
// This call is translated into a call that checks whether the underlying
// type is one of the types implementing this interface.
// When there is only one type implementing this interface, the check is
// replaced with a simple icmp instruction, just like a type assert.
// When there is no type at all that implements this interface, it is
// replaced with a constant false to optimize it completely.
//
// interfaceMethod:
// This call is replaced with a call to a function that calls the
// appropriate method depending on the underlying type.
// When there is only one type implementing this interface, this call is
// translated into a direct call of that method.
// When there is no type implementing this interface, this code is marked
// unreachable as there is no way such an interface could be constructed.
//
// 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/aykevl/go-llvm"
)
// 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
}
// methodName takes a method name like "func String()" and returns only the
// name, which is "String" in this case.
func (s *signatureInfo) methodName() string {
if !strings.HasPrefix(s.name, "func ") {
panic("signature must start with \"func \"")
}
methodName := s.name[len("func "):]
if openingParen := strings.IndexByte(methodName, '('); openingParen < 0 {
panic("no opening paren in signature name")
} else {
return methodName[:openingParen]
}
}
// 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
methodSet llvm.Value
num uint64 // the type number after lowering
countMakeInterfaces int // how often this type is used in an interface
countTypeAsserts int // how often a type assert happens on this method
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")
}
// id returns the fully-qualified type name including import path, removing the
// $type suffix.
func (t *typeInfo) id() string {
if !strings.HasSuffix(t.name, "$type") {
panic("concrete type does not have $type suffix: " + t.name)
}
return t.name[:len(t.name)-len("$type")]
}
// typeInfoSlice implements sort.Slice, sorting the most commonly used types
// first.
type typeInfoSlice []*typeInfo
func (t typeInfoSlice) Len() int { return len(t) }
func (t typeInfoSlice) Less(i, j int) bool {
// Try to sort the most commonly used types first.
if t[i].countTypeAsserts != t[j].countTypeAsserts {
return t[i].countTypeAsserts < t[j].countTypeAsserts
}
if t[i].countMakeInterfaces != t[j].countMakeInterfaces {
return t[i].countMakeInterfaces < t[j].countMakeInterfaces
}
return t[i].name < t[j].name
}
func (t typeInfoSlice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
// interfaceInfo keeps information about a Go interface type, including all
// methods it has.
type interfaceInfo struct {
name string // name with $interface suffix
signatures []*signatureInfo // method set
types typeInfoSlice // types this interface implements
assertFunc llvm.Value // runtime.interfaceImplements replacement
methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature
}
// id removes the $interface suffix from the name and returns the clean
// interface name including import path.
func (itf *interfaceInfo) id() string {
if !strings.HasSuffix(itf.name, "$interface") {
panic("interface type does not have $interface suffix: " + itf.name)
}
return itf.name[:len(itf.name)-len("$interface")]
}
// 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 {
*Compiler
types map[string]*typeInfo
signatures map[string]*signatureInfo
interfaces map[string]*interfaceInfo
}
// Lower all interface functions. They are emitted by the compiler as
// higher-level intrinsics that 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 (c *Compiler) LowerInterfaces() {
p := &lowerInterfacesPass{
Compiler: c,
types: make(map[string]*typeInfo),
signatures: make(map[string]*signatureInfo),
interfaces: make(map[string]*interfaceInfo),
}
p.run()
}
// run runs the pass itself.
func (p *lowerInterfacesPass) run() {
// Count per type how often it is put in an interface. Also, collect all
// methods this type has (if it is named).
makeInterface := p.mod.NamedFunction("runtime.makeInterface")
makeInterfaceUses := getUses(makeInterface)
for _, use := range makeInterfaceUses {
typecode := use.Operand(0)
name := typecode.Name()
if t, ok := p.types[name]; !ok {
// This is the first time this type has been seen, add it to the
// list of types.
t = p.addType(typecode)
p.addTypeMethods(t, use.Operand(1))
} else {
p.addTypeMethods(t, use.Operand(1))
}
// Count the number of MakeInterface instructions, for sorting the
// typecodes later.
p.types[name].countMakeInterfaces++
}
// Count per type how often it is type asserted on (e.g. in a switch
// statement).
typeAssert := p.mod.NamedFunction("runtime.typeAssert")
typeAssertUses := getUses(typeAssert)
for _, use := range typeAssertUses {
typecode := use.Operand(1)
name := typecode.Name()
if _, ok := p.types[name]; !ok {
p.addType(typecode)
}
p.types[name].countTypeAsserts++
}
// Find all interface method calls.
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod")
interfaceMethodUses := getUses(interfaceMethod)
for _, use := range interfaceMethodUses {
methodSet := use.Operand(1).Operand(0)
name := methodSet.Name()
if _, ok := p.interfaces[name]; !ok {
p.addInterface(methodSet)
}
}
// Find all interface type asserts.
interfaceImplements := p.mod.NamedFunction("runtime.interfaceImplements")
interfaceImplementsUses := getUses(interfaceImplements)
for _, use := range interfaceImplementsUses {
methodSet := use.Operand(1).Operand(0)
name := methodSet.Name()
if _, ok := p.interfaces[name]; !ok {
p.addInterface(methodSet)
}
}
// 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, to check for more common types
// first.
for _, itf := range p.interfaces {
sort.Sort(itf.types)
}
// Replace all interface methods with their uses, if possible.
for _, use := range interfaceMethodUses {
typecode := use.Operand(0)
signature := p.signatures[use.Operand(2).Name()]
// If the interface was created in the same function, we can insert a
// direct call. This may not happen often but it is an easy
// optimization so let's do it anyway.
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
name := typecode.Operand(0).Name()
typ := p.types[name]
p.replaceInvokeWithCall(use, typ, signature)
continue
}
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
if len(itf.types) == 0 {
// This method call is impossible: no type implements this
// interface. In fact, the previous type assert that got this
// interface value should already have returned false.
// Replace the function pointer with undef (which will then be
// called), indicating to the optimizer this code is unreachable.
use.ReplaceAllUsesWith(llvm.Undef(p.i8ptrType))
use.EraseFromParentAsInstruction()
} else if len(itf.types) == 1 {
// There is only one implementation of the given type.
// Call that function directly.
p.replaceInvokeWithCall(use, itf.types[0], signature)
} else {
// There are multiple types implementing this interface, thus there
// are multiple possible functions to call. Delegate calling the
// right function to a special wrapper function.
bitcasts := getUses(use)
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
panic("expected exactly one bitcast use of runtime.interfaceMethod")
}
bitcast := bitcasts[0]
calls := getUses(bitcast)
if len(calls) != 1 || calls[0].IsACallInst().IsNil() {
panic("expected exactly one call use of runtime.interfaceMethod")
}
call := calls[0]
// Set up parameters for the call. First copy the regular params...
params := make([]llvm.Value, call.OperandsCount())
paramTypes := make([]llvm.Type, len(params))
for i := 0; i < len(params)-1; i++ {
params[i] = call.Operand(i)
paramTypes[i] = params[i].Type()
}
// then add the typecode to the end of the list.
params[len(params)-1] = typecode
paramTypes[len(params)-1] = p.uintptrType
// Create a function that redirects the call to the destination
// call, after selecting the right concrete type.
redirector := p.getInterfaceMethodFunc(itf, signature, call.Type(), paramTypes)
// Replace the old lookup/bitcast/call with the new call.
p.builder.SetInsertPointBefore(call)
retval := p.builder.CreateCall(redirector, params, "")
if retval.Type().TypeKind() != llvm.VoidTypeKind {
call.ReplaceAllUsesWith(retval)
}
call.EraseFromParentAsInstruction()
bitcast.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
}
// Replace all typeasserts on interface types with matches on their concrete
// types, if possible.
for _, use := range interfaceImplementsUses {
actualType := use.Operand(0)
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
// Type assert is in the same function that creates the interface
// value. This means the underlying type is already known so match
// on that.
// This may not happen often but it is an easy optimization.
name := actualType.Operand(0).Name()
typ := p.types[name]
p.builder.SetInsertPointBefore(use)
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
continue
}
methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()]
if len(itf.types) == 0 {
// There are no types implementing this interface, so this assert
// can never succeed.
// Signal this to the optimizer by branching on constant false. It
// should remove the "then" block.
use.ReplaceAllUsesWith(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
use.EraseFromParentAsInstruction()
} else if len(itf.types) == 1 {
// There is only one type implementing this interface.
// Transform this interface assert into comparison against a
// constant.
p.builder.SetInsertPointBefore(use)
assertedType := p.builder.CreatePtrToInt(itf.types[0].typecode, p.uintptrType, "typeassert.typecode")
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
} else {
// There are multiple possible types implementing this interface.
// Create a function that does a type switch on all available types
// that implement this interface.
fn := p.getInterfaceImplementsFunc(itf)
p.builder.SetInsertPointBefore(use)
commaOk := p.builder.CreateCall(fn, []llvm.Value{actualType}, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
}
}
// Make a slice of types sorted by frequency of use.
typeSlice := make(typeInfoSlice, 0, len(p.types))
for _, t := range p.types {
typeSlice = append(typeSlice, t)
}
sort.Sort(typeSlice)
// A type code must fit in 16 bits.
if len(typeSlice) >= 1<<16 {
panic("typecode does not fit in a uint16: too many types in this program")
}
// Assign a type code for each type.
for i, t := range typeSlice {
t.num = uint64(i + 1)
}
// Replace each call to runtime.makeInterface with the constant type code.
for _, use := range makeInterfaceUses {
global := use.Operand(0)
t := p.types[global.Name()]
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType))
use.EraseFromParentAsInstruction()
}
// Replace each type assert with an actual type comparison or (if the type
// assert is impossible) the constant false.
for _, use := range typeAssertUses {
actualType := use.Operand(0)
assertedTypeGlobal := use.Operand(1)
t := p.types[assertedTypeGlobal.Name()]
var commaOk llvm.Value
if t.countMakeInterfaces == 0 {
// impossible type assert: optimize accordingly
commaOk = llvm.ConstInt(llvm.Int1Type(), 0, false)
} else {
// regular type assert
p.builder.SetInsertPointBefore(use)
commaOk = p.builder.CreateICmp(llvm.IntEQ, llvm.ConstPtrToInt(assertedTypeGlobal, p.uintptrType), actualType, "typeassert.ok")
}
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
}
// Fill in each helper function for type asserts on interfaces
// (interface-to-interface matches).
for _, itf := range p.interfaces {
if !itf.assertFunc.IsNil() {
p.createInterfaceImplementsFunc(itf)
}
for signature := range itf.methodFuncs {
p.createInterfaceMethodFunc(itf, signature)
}
}
// Replace all ptrtoint typecode placeholders with their final type code
// numbers.
for _, typ := range p.types {
for _, use := range getUses(typ.typecode) {
if use.IsConstant() && use.Opcode() == llvm.PtrToInt {
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
}
}
}
// Remove method sets of types. Unnecessary, but cleans up the IR for
// inspection.
for _, typ := range p.types {
if !typ.methodSet.IsNil() {
typ.methodSet.EraseFromParentAsGlobal()
typ.methodSet = llvm.Value{}
}
}
}
// addType retrieves Go type information based on a i16 global variable.
// Only the name of the i16 is relevant, the object itself is const-propagated
// and discared afterwards.
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
name := typecode.Name()
t := &typeInfo{
name: name,
typecode: typecode,
}
p.types[name] = t
return t
}
// 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() || methodSet.IsNull() {
// no methods or methods already read
return
}
methodSet = methodSet.Operand(0) // get global from GEP
// This type has methods, collect all methods of this type.
t.methodSet = methodSet
set := methodSet.Initializer() // get value from global
for i := 0; i < set.Type().ArrayLength(); i++ {
methodData := llvm.ConstExtractValue(set, []uint32{uint32(i)})
signatureName := llvm.ConstExtractValue(methodData, []uint32{0}).Name()
function := llvm.ConstExtractValue(methodData, []uint32{1}).Operand(0)
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(methodSet llvm.Value) {
name := methodSet.Name()
t := &interfaceInfo{
name: name,
}
p.interfaces[name] = t
methodSet = methodSet.Initializer() // get global value from getelementptr
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
signatureName := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)}).Name()
signature := p.getSignature(signatureName)
signature.interfaces = append(signature.interfaces, t)
t.signatures = append(t.signatures, 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]
}
// replaceInvokeWithCall replaces a runtime.interfaceMethod + bitcast with a
// concrete method. This can be done when only one type implements the
// interface.
func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInfo, signature *signatureInfo) {
bitcasts := getUses(use)
if len(bitcasts) != 1 || bitcasts[0].IsABitCastInst().IsNil() {
panic("expected exactly one bitcast use of runtime.interfaceMethod")
}
bitcast := bitcasts[0]
function := typ.getMethod(signature).function
if bitcast.Type() != function.Type() {
p.builder.SetInsertPointBefore(use)
function = p.builder.CreateBitCast(function, bitcast.Type(), "")
}
bitcast.ReplaceAllUsesWith(function)
bitcast.EraseFromParentAsInstruction()
use.EraseFromParentAsInstruction()
}
// getInterfaceImplementsFunc returns a function that checks whether a given
// interface type implements a given interface, by checking all possible types
// that implement this interface.
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value {
if !itf.assertFunc.IsNil() {
return itf.assertFunc
}
// Create the function and function signature.
// TODO: debug info
fnName := itf.id() + "$typeassert"
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
itf.assertFunc = llvm.AddFunction(p.mod, fnName, fnType)
itf.assertFunc.Param(0).SetName("actualType")
// Type asserts will be made for each type, so increment the counter for
// those.
for _, typ := range itf.types {
typ.countTypeAsserts++
}
return itf.assertFunc
}
// createInterfaceImplementsFunc finishes the work of
// getInterfaceImplementsFunc, because it needs to run after types have a type
// code assigned.
//
// The type match is implemented using a big type switch over all possible
// types.
func (p *lowerInterfacesPass) createInterfaceImplementsFunc(itf *interfaceInfo) {
fn := itf.assertFunc
fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true)
// TODO: debug info
// Create all used basic blocks.
entry := llvm.AddBasicBlock(fn, "entry")
thenBlock := llvm.AddBasicBlock(fn, "then")
elseBlock := llvm.AddBasicBlock(fn, "else")
// Add all possible types as cases.
p.builder.SetInsertPointAtEnd(entry)
actualType := fn.Param(0)
sw := p.builder.CreateSwitch(actualType, elseBlock, len(itf.types))
for _, typ := range itf.types {
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), thenBlock)
}
// Fill 'then' block (type assert was successful).
p.builder.SetInsertPointAtEnd(thenBlock)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
// Fill 'else' block (type asserted failed).
p.builder.SetInsertPointAtEnd(elseBlock)
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
}
// getInterfaceMethodFunc return a function that returns a function pointer for
// calling a method on an interface. It only declares the function,
// createInterfaceMethodFunc actually defines the function.
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
if fn, ok := itf.methodFuncs[signature]; ok {
// This function has already been created.
return fn
}
if itf.methodFuncs == nil {
// initialize the above map
itf.methodFuncs = make(map[*signatureInfo]llvm.Value)
}
// Construct the function name, which is of the form:
// (main.Stringer).String
fnName := "(" + itf.id() + ")." + signature.methodName()
fnType := llvm.FunctionType(returnType, params, false)
fn := llvm.AddFunction(p.mod, fnName, fnType)
fn.LastParam().SetName("actualType")
itf.methodFuncs[signature] = fn
return fn
}
// createInterfaceMethodFunc finishes the work of getInterfaceMethodFunc,
// because it needs to run after type codes have been assigned to concrete
// types.
//
// Matching the actual type is implemented using a big type switch over all
// possible types.
func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo) {
fn := itf.methodFuncs[signature]
fn.SetLinkage(llvm.InternalLinkage)
fn.SetUnnamedAddr(true)
// TODO: debug info
// Create entry block.
entry := llvm.AddBasicBlock(fn, "entry")
// Create default block and make it unreachable (which it is, because all
// possible types are checked).
defaultBlock := llvm.AddBasicBlock(fn, "default")
p.builder.SetInsertPointAtEnd(defaultBlock)
p.builder.CreateUnreachable()
// Create type switch in entry block.
p.builder.SetInsertPointAtEnd(entry)
actualType := fn.LastParam()
sw := p.builder.CreateSwitch(actualType, defaultBlock, len(itf.types))
// 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()-2)
for i := range params {
params[i] = fn.Param(i + 1)
}
// Define all possible functions that can be called.
for _, typ := range itf.types {
bb := llvm.AddBasicBlock(fn, typ.id())
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
// The function we will redirect to when the interface has this type.
function := typ.getMethod(signature).function
p.builder.SetInsertPointAtEnd(bb)
receiver := fn.FirstParam()
if receiver.Type() != function.FirstParam().Type() {
// When the receiver is a pointer, it is not wrapped. This means the
// i8* has to be cast to the correct pointer type of the target
// function.
receiver = p.builder.CreateBitCast(receiver, function.FirstParam().Type(), "")
}
retval := p.builder.CreateCall(function, append([]llvm.Value{receiver}, params...), "")
if retval.Type().TypeKind() == llvm.VoidTypeKind {
p.builder.CreateRetVoid()
} else {
p.builder.CreateRet(retval)
}
}
}

Просмотреть файл

@ -1,5 +1,10 @@
package compiler 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 ( import (
"errors" "errors"
"go/types" "go/types"
@ -32,10 +37,10 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
// Allocate on the heap and put a pointer in the interface. // Allocate on the heap and put a pointer in the interface.
// TODO: escape analysis. // TODO: escape analysis.
sizeValue := llvm.ConstInt(c.uintptrType, size, false) sizeValue := llvm.ConstInt(c.uintptrType, size, false)
alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "") alloc := c.createRuntimeCall("alloc", []llvm.Value{sizeValue}, "makeinterface.alloc")
itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "") itfValueCast := c.builder.CreateBitCast(alloc, llvm.PointerType(val.Type(), 0), "makeinterface.cast.value")
c.builder.CreateStore(val, itfValueCast) c.builder.CreateStore(val, itfValueCast)
itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "") itfValue = c.builder.CreateBitCast(itfValueCast, c.i8ptrType, "makeinterface.cast.i8ptr")
} }
} else if size == 0 { } else if size == 0 {
itfValue = llvm.ConstPointerNull(c.i8ptrType) itfValue = llvm.ConstPointerNull(c.i8ptrType)
@ -43,38 +48,136 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, global str
// Directly place the value in the interface. // Directly place the value in the interface.
switch val.Type().TypeKind() { switch val.Type().TypeKind() {
case llvm.IntegerTypeKind: case llvm.IntegerTypeKind:
itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "") itfValue = c.builder.CreateIntToPtr(val, c.i8ptrType, "makeinterface.cast.int")
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "") itfValue = c.builder.CreateBitCast(val, c.i8ptrType, "makeinterface.cast.ptr")
case llvm.StructTypeKind: case llvm.StructTypeKind:
// A bitcast would be useful here, but bitcast doesn't allow // A bitcast would be useful here, but bitcast doesn't allow
// aggregate types. So we'll bitcast it using an alloca. // aggregate types. So we'll bitcast it using an alloca.
// Hopefully this will get optimized away. // Hopefully this will get optimized away.
mem := c.builder.CreateAlloca(c.i8ptrType, "") mem := c.builder.CreateAlloca(c.i8ptrType, "makeinterface.cast.struct")
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "") memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(val.Type(), 0), "makeinterface.cast.struct.cast")
c.builder.CreateStore(val, memStructPtr) c.builder.CreateStore(val, memStructPtr)
itfValue = c.builder.CreateLoad(mem, "") itfValue = c.builder.CreateLoad(mem, "makeinterface.cast.load")
default: default:
return llvm.Value{}, errors.New("todo: makeinterface: cast small type to i8*") return llvm.Value{}, errors.New("todo: makeinterface: cast small type to i8*")
} }
} }
itfTypeNum, _ := c.ir.TypeNum(typ) itfTypeCodeGlobal := c.getTypeCode(typ)
if itfTypeNum >= 1<<16 { itfMethodSetGlobal, err := c.getTypeMethodSet(typ)
return llvm.Value{}, errors.New("interface typecodes do not fit in a 16-bit integer") if err != nil {
return llvm.Value{}, nil
} }
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), []llvm.Value{llvm.ConstInt(c.ctx.Int16Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)}) itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
return itf, nil return itf, nil
} }
// getTypeCode returns a reference to a type code.
// It returns a pointer to an external global which should be replaced with the
// real type in the interface lowering pass.
func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$type")
if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), typ.String()+"$type")
global.SetGlobalConstant(true)
}
return global
}
// getTypeMethodSet returns a reference (GEP) to a global method set. This
// method set should be unreferenced after the interface lowering pass.
func (c *Compiler) getTypeMethodSet(typ types.Type) (llvm.Value, error) {
global := c.mod.NamedGlobal(typ.String() + "$methodset")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// the method set already exists
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
}
ms := c.ir.Program.MethodSets.MethodSet(typ)
if ms.Len() == 0 {
// no methods, so can leave that one out
return llvm.ConstPointerNull(llvm.PointerType(c.mod.GetTypeByName("runtime.interfaceMethodInfo"), 0)), nil
}
methods := make([]llvm.Value, ms.Len())
interfaceMethodInfoType := c.mod.GetTypeByName("runtime.interfaceMethodInfo")
for i := 0; i < ms.Len(); i++ {
method := ms.At(i)
signatureGlobal := c.getMethodSignature(method.Obj().(*types.Func))
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
// compiler error, so panic
panic("cannot find function: " + f.LinkName())
}
fn, err := c.getInterfaceInvokeWrapper(f)
if err != nil {
return llvm.Value{}, err
}
methodInfo := llvm.ConstNamedStruct(interfaceMethodInfoType, []llvm.Value{
signatureGlobal,
llvm.ConstBitCast(fn, c.i8ptrType),
})
methods[i] = methodInfo
}
arrayType := llvm.ArrayType(interfaceMethodInfoType, len(methods))
value := llvm.ConstArray(interfaceMethodInfoType, methods)
global = llvm.AddGlobal(c.mod, arrayType, typ.String()+"$methodset")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero}), nil
}
// getInterfaceMethodSet returns a global variable with the method set of the
// given named interface type. This method set is used by the interface lowering
// pass.
func (c *Compiler) getInterfaceMethodSet(typ *types.Named) llvm.Value {
global := c.mod.NamedGlobal(typ.String() + "$interface")
zero := llvm.ConstInt(c.ctx.Int32Type(), 0, false)
if !global.IsNil() {
// method set already exist, return it
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// Every method is a *i16 reference indicating the signature of this method.
methods := make([]llvm.Value, typ.Underlying().(*types.Interface).NumMethods())
for i := range methods {
method := typ.Underlying().(*types.Interface).Method(i)
methods[i] = c.getMethodSignature(method)
}
value := llvm.ConstArray(methods[0].Type(), methods)
global = llvm.AddGlobal(c.mod, value.Type(), typ.String()+"$interface")
global.SetInitializer(value)
global.SetGlobalConstant(true)
global.SetLinkage(llvm.PrivateLinkage)
return llvm.ConstGEP(global, []llvm.Value{zero, zero})
}
// getMethodSignature returns a global variable which is a reference to an
// external *i16 indicating the indicating the signature of this method. It is
// used during the interface lowering pass.
func (c *Compiler) getMethodSignature(method *types.Func) llvm.Value {
signature := ir.MethodSignature(method)
signatureGlobal := c.mod.NamedGlobal("func " + signature)
if signatureGlobal.IsNil() {
signatureGlobal = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), "func "+signature)
signatureGlobal.SetGlobalConstant(true)
}
return signatureGlobal
}
// parseTypeAssert will emit the code for a typeassert, used in if statements // parseTypeAssert will emit the code for a typeassert, used in if statements
// and in switch statements (Go SSA does not have type switches, only if/else // 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, // 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. // 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 // Type asserts on concrete types are trivial: just compare type numbers. Type
// asserts on interfaces are more difficult to implement and so are delegated to // asserts on interfaces are more difficult, see the comments in the function.
// a runtime library function.
func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) { func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Value, error) {
itf, err := c.parseExpr(frame, expr.X) itf, err := c.parseExpr(frame, expr.X)
if err != nil { if err != nil {
@ -91,38 +194,24 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type") actualTypeNum := c.builder.CreateExtractValue(itf, 0, "interface.type")
commaOk := llvm.Value{} commaOk := llvm.Value{}
if itf, ok := expr.AssertedType.Underlying().(*types.Interface); ok { if _, ok := expr.AssertedType.Underlying().(*types.Interface); ok {
// Type assert on interface type. // Type assert on interface type.
// This is slightly non-trivial: at runtime the list of methods // This pseudo call will be lowered in the interface lowering pass to a
// needs to be checked to see whether it implements the interface. // real call which checks whether the provided typecode is any of the
// At the same time, the interface value itself is unchanged. // concrete types that implements this interface.
itfTypeNum := c.ir.InterfaceNum(itf) // This is very different from how interface asserts are implemented in
itfTypeNumValue := llvm.ConstInt(c.ctx.Int16Type(), uint64(itfTypeNum), false) // the main Go compiler, where the runtime checks whether the type
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, itfTypeNumValue}, "") // implements each method of the interface. See:
// https://research.swtch.com/interfaces
methodSet := c.getInterfaceMethodSet(expr.AssertedType.(*types.Named))
commaOk = c.createRuntimeCall("interfaceImplements", []llvm.Value{actualTypeNum, methodSet}, "")
} else { } else {
// Type assert on concrete type. // Type assert on concrete type.
// This is easy: just compare the type number. // Call runtime.typeAssert, which will be lowered to a simple icmp or
assertedTypeNum, typeExists := c.ir.TypeNum(expr.AssertedType) // const false in the interface lowering pass.
if !typeExists { assertedTypeCodeGlobal := c.getTypeCode(expr.AssertedType)
// Static analysis has determined this type assert will never apply. commaOk = c.createRuntimeCall("typeAssert", []llvm.Value{actualTypeNum, assertedTypeCodeGlobal}, "typecode")
// Using undef here so that LLVM knows we'll never get here and
// can optimize accordingly.
undef := llvm.Undef(assertedType)
commaOk := llvm.ConstInt(c.ctx.Int1Type(), 0, false)
if expr.CommaOk {
return c.ctx.ConstStruct([]llvm.Value{undef, commaOk}, false), nil
} else {
c.createRuntimeCall("interfaceTypeAssert", []llvm.Value{commaOk}, "")
return undef, nil
}
}
if assertedTypeNum >= 1<<16 {
return llvm.Value{}, errors.New("interface typecodes do not fit in a 16-bit integer")
}
assertedTypeNumValue := llvm.ConstInt(c.ctx.Int16Type(), uint64(assertedTypeNum), false)
commaOk = c.builder.CreateICmp(llvm.IntEQ, assertedTypeNumValue, actualTypeNum, "")
} }
// Add 2 new basic blocks (that should get optimized away): one for the // Add 2 new basic blocks (that should get optimized away): one for the
@ -171,16 +260,14 @@ func (c *Compiler) parseTypeAssert(frame *Frame, expr *ssa.TypeAssert) (llvm.Val
valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok") valueOk = c.builder.CreatePtrToInt(valuePtr, assertedType, "typeassert.value.ok")
case llvm.PointerTypeKind: case llvm.PointerTypeKind:
valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok") valueOk = c.builder.CreateBitCast(valuePtr, assertedType, "typeassert.value.ok")
case llvm.StructTypeKind: default: // struct, float, etc.
// A bitcast would be useful here, but bitcast doesn't allow // A bitcast would be useful here, but bitcast doesn't allow
// aggregate types. So we'll bitcast it using an alloca. // aggregate types. So we'll bitcast it using an alloca.
// Hopefully this will get optimized away. // Hopefully this will get optimized away.
mem := c.builder.CreateAlloca(c.i8ptrType, "") mem := c.builder.CreateAlloca(c.i8ptrType, "")
c.builder.CreateStore(valuePtr, mem) c.builder.CreateStore(valuePtr, mem)
memStructPtr := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "") memCast := c.builder.CreateBitCast(mem, llvm.PointerType(assertedType, 0), "")
valueOk = c.builder.CreateLoad(memStructPtr, "typeassert.value.ok") valueOk = c.builder.CreateLoad(memCast, "typeassert.value.ok")
default:
return llvm.Value{}, errors.New("todo: typeassert: bitcast small types")
} }
} }
} }
@ -231,7 +318,8 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode") typecode := c.builder.CreateExtractValue(itf, 0, "invoke.typecode")
values := []llvm.Value{ values := []llvm.Value{
typecode, typecode,
llvm.ConstInt(c.ctx.Int16Type(), uint64(c.ir.MethodNum(instr.Method)), false), c.getInterfaceMethodSet(instr.Value.Type().(*types.Named)),
c.getMethodSignature(instr.Method),
} }
fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func") fn := c.createRuntimeCall("interfaceMethod", values, "invoke.func")
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast") fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
@ -255,142 +343,40 @@ func (c *Compiler) getInvokeCall(frame *Frame, instr *ssa.CallCommon) (llvm.Valu
return fnCast, args, nil return fnCast, args, nil
} }
// Initialize runtime type information, for interfaces. // interfaceInvokeWrapper keeps some state between getInterfaceInvokeWrapper and
// See src/runtime/interface.go for more details. // createInterfaceInvokeWrapper. The former is called during IR construction
func (c *Compiler) createInterfaceRTTI() error { // itself and the latter is called when finishing up the IR.
dynamicTypes := c.ir.AllDynamicTypes() type interfaceInvokeWrapper struct {
numDynamicTypes := 0 fn *ir.Function
for _, meta := range dynamicTypes { wrapper llvm.Value
numDynamicTypes += len(meta.Methods) receiverType llvm.Type
}
ranges := make([]llvm.Value, 0, len(dynamicTypes))
funcPointers := make([]llvm.Value, 0, numDynamicTypes)
signatures := make([]llvm.Value, 0, numDynamicTypes)
startIndex := 0
rangeType := c.mod.GetTypeByName("runtime.methodSetRange")
for _, meta := range dynamicTypes {
rangeValues := []llvm.Value{
llvm.ConstInt(c.ctx.Int16Type(), uint64(startIndex), false),
llvm.ConstInt(c.ctx.Int16Type(), uint64(len(meta.Methods)), false),
}
rangeValue := llvm.ConstNamedStruct(rangeType, rangeValues)
ranges = append(ranges, rangeValue)
methods := make([]*types.Selection, 0, len(meta.Methods))
for _, method := range meta.Methods {
methods = append(methods, method)
}
c.ir.SortMethods(methods)
for _, method := range methods {
f := c.ir.GetFunction(c.ir.Program.MethodValue(method))
if f.LLVMFn.IsNil() {
return errors.New("cannot find function: " + f.LinkName())
}
fn, err := c.wrapInterfaceInvoke(f)
if err != nil {
return err
}
fnPtr := llvm.ConstBitCast(fn, c.i8ptrType)
funcPointers = append(funcPointers, fnPtr)
signatureNum := c.ir.MethodNum(method.Obj().(*types.Func))
signature := llvm.ConstInt(c.ctx.Int16Type(), uint64(signatureNum), false)
signatures = append(signatures, signature)
}
startIndex += len(meta.Methods)
}
interfaceTypes := c.ir.AllInterfaces()
interfaceIndex := make([]llvm.Value, len(interfaceTypes))
interfaceLengths := make([]llvm.Value, len(interfaceTypes))
interfaceMethods := make([]llvm.Value, 0)
for i, itfType := range interfaceTypes {
if itfType.Type.NumMethods() > 0xff {
return errors.New("too many methods for interface " + itfType.Type.String())
}
interfaceIndex[i] = llvm.ConstInt(c.ctx.Int16Type(), uint64(i), false)
interfaceLengths[i] = llvm.ConstInt(c.ctx.Int8Type(), uint64(itfType.Type.NumMethods()), false)
funcs := make([]*types.Func, itfType.Type.NumMethods())
for i := range funcs {
funcs[i] = itfType.Type.Method(i)
}
c.ir.SortFuncs(funcs)
for _, f := range funcs {
id := llvm.ConstInt(c.ctx.Int16Type(), uint64(c.ir.MethodNum(f)), false)
interfaceMethods = append(interfaceMethods, id)
}
}
if len(ranges) >= 1<<16 {
return errors.New("method call numbers do not fit in a 16-bit integer")
}
// Replace the pre-created arrays with the generated arrays.
rangeArray := llvm.ConstArray(rangeType, ranges)
rangeArrayNewGlobal := llvm.AddGlobal(c.mod, rangeArray.Type(), "runtime.methodSetRanges.tmp")
rangeArrayNewGlobal.SetInitializer(rangeArray)
rangeArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
rangeArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetRanges")
rangeArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(rangeArrayNewGlobal, rangeArrayOldGlobal.Type()))
rangeArrayOldGlobal.EraseFromParentAsGlobal()
rangeArrayNewGlobal.SetName("runtime.methodSetRanges")
funcArray := llvm.ConstArray(c.i8ptrType, funcPointers)
funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "runtime.methodSetFunctions.tmp")
funcArrayNewGlobal.SetInitializer(funcArray)
funcArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
funcArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetFunctions")
funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type()))
funcArrayOldGlobal.EraseFromParentAsGlobal()
funcArrayNewGlobal.SetName("runtime.methodSetFunctions")
signatureArray := llvm.ConstArray(c.ctx.Int16Type(), signatures)
signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "runtime.methodSetSignatures.tmp")
signatureArrayNewGlobal.SetInitializer(signatureArray)
signatureArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
signatureArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetSignatures")
signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type()))
signatureArrayOldGlobal.EraseFromParentAsGlobal()
signatureArrayNewGlobal.SetName("runtime.methodSetSignatures")
interfaceIndexArray := llvm.ConstArray(c.ctx.Int16Type(), interfaceIndex)
interfaceIndexArrayNewGlobal := llvm.AddGlobal(c.mod, interfaceIndexArray.Type(), "runtime.interfaceIndex.tmp")
interfaceIndexArrayNewGlobal.SetInitializer(interfaceIndexArray)
interfaceIndexArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
interfaceIndexArrayOldGlobal := c.mod.NamedGlobal("runtime.interfaceIndex")
interfaceIndexArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(interfaceIndexArrayNewGlobal, interfaceIndexArrayOldGlobal.Type()))
interfaceIndexArrayOldGlobal.EraseFromParentAsGlobal()
interfaceIndexArrayNewGlobal.SetName("runtime.interfaceIndex")
interfaceLengthsArray := llvm.ConstArray(c.ctx.Int8Type(), interfaceLengths)
interfaceLengthsArrayNewGlobal := llvm.AddGlobal(c.mod, interfaceLengthsArray.Type(), "runtime.interfaceLengths.tmp")
interfaceLengthsArrayNewGlobal.SetInitializer(interfaceLengthsArray)
interfaceLengthsArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
interfaceLengthsArrayOldGlobal := c.mod.NamedGlobal("runtime.interfaceLengths")
interfaceLengthsArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(interfaceLengthsArrayNewGlobal, interfaceLengthsArrayOldGlobal.Type()))
interfaceLengthsArrayOldGlobal.EraseFromParentAsGlobal()
interfaceLengthsArrayNewGlobal.SetName("runtime.interfaceLengths")
interfaceMethodsArray := llvm.ConstArray(c.ctx.Int16Type(), interfaceMethods)
interfaceMethodsArrayNewGlobal := llvm.AddGlobal(c.mod, interfaceMethodsArray.Type(), "runtime.interfaceMethods.tmp")
interfaceMethodsArrayNewGlobal.SetInitializer(interfaceMethodsArray)
interfaceMethodsArrayNewGlobal.SetLinkage(llvm.InternalLinkage)
interfaceMethodsArrayOldGlobal := c.mod.NamedGlobal("runtime.interfaceMethods")
interfaceMethodsArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(interfaceMethodsArrayNewGlobal, interfaceMethodsArrayOldGlobal.Type()))
interfaceMethodsArrayOldGlobal.EraseFromParentAsGlobal()
interfaceMethodsArrayNewGlobal.SetName("runtime.interfaceMethods")
c.mod.NamedGlobal("runtime.firstTypeWithMethods").SetInitializer(llvm.ConstInt(c.ctx.Int16Type(), uint64(c.ir.FirstDynamicType()), false))
return nil
} }
// Wrap an interface method function pointer. The wrapper takes in a pointer to // Wrap an interface method function pointer. The wrapper takes in a pointer to
// the underlying value, dereferences it, and calls the real method. This // the underlying value, dereferences it, and calls the real method. This
// wrapper is only needed when the interface value actually doesn't fit in a // wrapper is only needed when the interface value actually doesn't fit in a
// pointer and a pointer to the value must be created. // pointer and a pointer to the value must be created.
func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) { func (c *Compiler) getInterfaceInvokeWrapper(f *ir.Function) (llvm.Value, error) {
wrapperName := f.LinkName() + "$invoke"
wrapper := c.mod.NamedFunction(wrapperName)
if !wrapper.IsNil() {
// Wrapper already created. Return it directly.
return wrapper, nil
}
// Get the expanded receiver type.
receiverType, err := c.getLLVMType(f.Params[0].Type()) receiverType, err := c.getLLVMType(f.Params[0].Type())
if err != nil { if err != nil {
return llvm.Value{}, err return llvm.Value{}, err
} }
expandedReceiverType := c.expandFormalParamType(receiverType) expandedReceiverType := c.expandFormalParamType(receiverType)
if c.targetData.TypeAllocSize(receiverType) <= c.targetData.TypeAllocSize(c.i8ptrType) && len(expandedReceiverType) == 1 { // Does this method even need any wrapping?
// nothing to wrap 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 f.LLVMFn, nil return f.LLVMFn, nil
} }
@ -398,16 +384,30 @@ func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
fnType := f.LLVMFn.Type().ElementType() fnType := f.LLVMFn.Type().ElementType()
paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...) paramTypes := append([]llvm.Type{c.i8ptrType}, fnType.ParamTypes()[len(expandedReceiverType):]...)
wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false) wrapFnType := llvm.FunctionType(fnType.ReturnType(), paramTypes, false)
wrapper := llvm.AddFunction(c.mod, f.LinkName()+"$invoke", wrapFnType) wrapper = llvm.AddFunction(c.mod, wrapperName, wrapFnType)
c.interfaceInvokeWrappers = append(c.interfaceInvokeWrappers, interfaceInvokeWrapper{
fn: f,
wrapper: wrapper,
receiverType: receiverType,
})
return wrapper, nil
}
// createInterfaceInvokeWrapper finishes the work of getInterfaceInvokeWrapper,
// see that function for details.
func (c *Compiler) createInterfaceInvokeWrapper(state interfaceInvokeWrapper) error {
wrapper := state.wrapper
fn := state.fn
receiverType := state.receiverType
wrapper.SetLinkage(llvm.InternalLinkage) wrapper.SetLinkage(llvm.InternalLinkage)
wrapper.SetUnnamedAddr(true) wrapper.SetUnnamedAddr(true)
// add debug info // add debug info if needed
if c.Debug { if c.Debug {
pos := c.ir.Program.Fset.Position(f.Pos()) pos := c.ir.Program.Fset.Position(fn.Pos())
difunc, err := c.attachDebugInfoRaw(f, wrapper, "$invoke", pos.Filename, pos.Line) difunc, err := c.attachDebugInfoRaw(fn, wrapper, "$invoke", pos.Filename, pos.Line)
if err != nil { if err != nil {
return llvm.Value{}, err return err
} }
c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{}) c.builder.SetCurrentDebugLocation(uint(pos.Line), uint(pos.Column), difunc, llvm.Metadata{})
} }
@ -424,7 +424,7 @@ func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
// Load the underlying value. // Load the underlying value.
receiverPtrType := llvm.PointerType(receiverType, 0) receiverPtrType := llvm.PointerType(receiverType, 0)
receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr") receiverPtr = c.builder.CreateBitCast(wrapper.Param(0), receiverPtrType, "receiver.ptr")
} else if len(expandedReceiverType) != 1 { } else {
// The value is stored in the interface, but it is of type struct which // The value is stored in the interface, but it is of type struct which
// is expanded to multiple parameters (e.g. {i8, i8}). So we have to // is expanded to multiple parameters (e.g. {i8, i8}). So we have to
// receive the struct as parameter, expand it, and pass it on to the // receive the struct as parameter, expand it, and pass it on to the
@ -435,19 +435,17 @@ func (c *Compiler) wrapInterfaceInvoke(f *ir.Function) (llvm.Value, error) {
alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca") alloca := c.builder.CreateAlloca(c.i8ptrType, "receiver.alloca")
c.builder.CreateStore(wrapper.Param(0), alloca) c.builder.CreateStore(wrapper.Param(0), alloca)
receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr") receiverPtr = c.builder.CreateBitCast(alloca, llvm.PointerType(receiverType, 0), "receiver.ptr")
} else {
panic("unreachable")
} }
receiverValue := c.builder.CreateLoad(receiverPtr, "receiver") receiverValue := c.builder.CreateLoad(receiverPtr, "receiver")
params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...) params := append(c.expandFormalParam(receiverValue), wrapper.Params()[1:]...)
if fnType.ReturnType().TypeKind() == llvm.VoidTypeKind { if fn.LLVMFn.Type().ElementType().ReturnType().TypeKind() == llvm.VoidTypeKind {
c.builder.CreateCall(f.LLVMFn, params, "") c.builder.CreateCall(fn.LLVMFn, params, "")
c.builder.CreateRetVoid() c.builder.CreateRetVoid()
} else { } else {
ret := c.builder.CreateCall(f.LLVMFn, params, "ret") ret := c.builder.CreateCall(fn.LLVMFn, params, "ret")
c.builder.CreateRet(ret) c.builder.CreateRet(ret)
} }
return wrapper, nil return nil
} }

Просмотреть файл

@ -1,12 +1,14 @@
package compiler package compiler
import ( import (
"errors"
"github.com/aykevl/go-llvm" "github.com/aykevl/go-llvm"
) )
// Run the LLVM optimizer over the module. // Run the LLVM optimizer over the module.
// The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold. // The inliner can be disabled (if necessary) by passing 0 to the inlinerThreshold.
func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) { func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) error {
builder := llvm.NewPassManagerBuilder() builder := llvm.NewPassManagerBuilder()
defer builder.Dispose() defer builder.Dispose()
builder.SetOptLevel(optLevel) builder.SetOptLevel(optLevel)
@ -40,7 +42,13 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
c.OptimizeMaps() c.OptimizeMaps()
c.OptimizeStringToBytes() c.OptimizeStringToBytes()
c.OptimizeAllocs() c.OptimizeAllocs()
c.Verify() c.LowerInterfaces()
} else {
// Must be run at any optimization level.
c.LowerInterfaces()
}
if err := c.Verify(); err != nil {
return errors.New("optimizations caused a verification failure")
} }
// Run module passes. // Run module passes.
@ -48,6 +56,8 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
defer modPasses.Dispose() defer modPasses.Dispose()
builder.Populate(modPasses) builder.Populate(modPasses)
modPasses.Run(c.mod) modPasses.Run(c.mod)
return nil
} }
// Eliminate created but not used maps. // Eliminate created but not used maps.
@ -299,6 +309,9 @@ func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
// Return a list of values (actually, instructions) where this value is used as // Return a list of values (actually, instructions) where this value is used as
// an operand. // an operand.
func getUses(value llvm.Value) []llvm.Value { func getUses(value llvm.Value) []llvm.Value {
if value.IsNil() {
return nil
}
var uses []llvm.Value var uses []llvm.Value
use := value.FirstUse() use := value.FirstUse()
for !use.IsNil() { for !use.IsNil() {

Просмотреть файл

@ -300,6 +300,8 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret} fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.makeInterface":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), fr.TargetData.IntPtrType())}
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic": case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":
// all print instructions, which necessarily have side // all print instructions, which necessarily have side
// effects but no results // effects but no results

Просмотреть файл

@ -73,7 +73,12 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
result.updateSeverity(sideEffectAll) result.updateSeverity(sideEffectAll)
continue continue
} }
name := child.Name()
if child.IsDeclaration() { if child.IsDeclaration() {
if name == "runtime.makeInterface" {
// Can be interpreted so does not have side effects.
continue
}
// External function call. Assume only limited side effects // External function call. Assume only limited side effects
// (no affected globals, etc.). // (no affected globals, etc.).
if result.hasLocalSideEffects(dirtyLocals, inst) { if result.hasLocalSideEffects(dirtyLocals, inst) {

Просмотреть файл

@ -278,8 +278,6 @@ func (p *Program) interpret(instrs []ssa.Instruction, paramKeys []*ssa.Parameter
} else { } else {
return i, errors.New("todo: init IndexAddr index: " + instr.Index.String()) return i, errors.New("todo: init IndexAddr index: " + instr.Index.String())
} }
case *ssa.MakeInterface:
locals[instr] = &InterfaceValue{instr.X.Type(), locals[instr.X]}
case *ssa.MakeMap: case *ssa.MakeMap:
locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil} locals[instr] = &MapValue{instr.Type().Underlying().(*types.Map), nil, nil}
case *ssa.MapUpdate: case *ssa.MapUpdate:
@ -388,7 +386,6 @@ func canInterpret(callee *ssa.Function) bool {
case *ssa.Extract: case *ssa.Extract:
case *ssa.FieldAddr: case *ssa.FieldAddr:
case *ssa.IndexAddr: case *ssa.IndexAddr:
case *ssa.MakeInterface:
case *ssa.MakeMap: case *ssa.MakeMap:
case *ssa.MapUpdate: case *ssa.MapUpdate:
case *ssa.Return: case *ssa.Return:
@ -447,8 +444,6 @@ func (p *Program) getZeroValue(t types.Type) (Value, error) {
return &ZeroBasicValue{typ}, nil return &ZeroBasicValue{typ}, nil
case *types.Signature: case *types.Signature:
return &FunctionValue{typ, nil}, nil return &FunctionValue{typ, nil}, nil
case *types.Interface:
return &InterfaceValue{typ, nil}, nil
case *types.Map: case *types.Map:
return &MapValue{typ, nil, nil}, nil return &MapValue{typ, nil, nil}, nil
case *types.Pointer: case *types.Pointer:
@ -492,11 +487,6 @@ type FunctionValue struct {
Elem *ssa.Function Elem *ssa.Function
} }
type InterfaceValue struct {
Type types.Type
Elem Value
}
type PointerBitCastValue struct { type PointerBitCastValue struct {
Type types.Type Type types.Type
Elem Value Elem Value

Просмотреть файл

@ -19,21 +19,18 @@ import (
// View on all functions, types, and globals in a program, with analysis // View on all functions, types, and globals in a program, with analysis
// results. // results.
type Program struct { type Program struct {
Program *ssa.Program Program *ssa.Program
mainPkg *ssa.Package mainPkg *ssa.Package
Functions []*Function Functions []*Function
functionMap map[*ssa.Function]*Function functionMap map[*ssa.Function]*Function
Globals []*Global Globals []*Global
globalMap map[*ssa.Global]*Global globalMap map[*ssa.Global]*Global
comments map[string]*ast.CommentGroup comments map[string]*ast.CommentGroup
NamedTypes []*NamedType NamedTypes []*NamedType
needsScheduler bool needsScheduler bool
goCalls []*ssa.Go goCalls []*ssa.Go
typesWithMethods map[string]*TypeWithMethods // see AnalyseInterfaceConversions typesInInterfaces map[string]struct{} // see AnalyseInterfaceConversions
typesWithoutMethods map[string]int // see AnalyseInterfaceConversions fpWithContext map[string]struct{} // see AnalyseFunctionPointers
methodSignatureNames map[string]int // see MethodNum
interfaces map[string]*Interface // see AnalyseInterfaceConversions
fpWithContext map[string]struct{} // see AnalyseFunctionPointers
} }
// Function or method. // Function or method.
@ -179,13 +176,11 @@ func NewProgram(lprogram *loader.Program, mainPath string) *Program {
} }
p := &Program{ p := &Program{
Program: program, Program: program,
mainPkg: mainPkg, mainPkg: mainPkg,
functionMap: make(map[*ssa.Function]*Function), functionMap: make(map[*ssa.Function]*Function),
globalMap: make(map[*ssa.Global]*Global), globalMap: make(map[*ssa.Global]*Global),
methodSignatureNames: make(map[string]int), comments: comments,
interfaces: make(map[string]*Interface),
comments: comments,
} }
for _, pkg := range packageList { for _, pkg := range packageList {
@ -270,18 +265,6 @@ func (p *Program) GetGlobal(ssaGlobal *ssa.Global) *Global {
return p.globalMap[ssaGlobal] return p.globalMap[ssaGlobal]
} }
// SortMethods sorts the list of methods by method ID.
func (p *Program) SortMethods(methods []*types.Selection) {
m := &methodList{methods: methods, program: p}
sort.Sort(m)
}
// SortFuncs sorts the list of functions by method ID.
func (p *Program) SortFuncs(funcs []*types.Func) {
m := &funcList{funcs: funcs, program: p}
sort.Sort(m)
}
func (p *Program) MainPkg() *ssa.Package { func (p *Program) MainPkg() *ssa.Package {
return p.mainPkg return p.mainPkg
} }
@ -442,46 +425,6 @@ func (p *Program) IsVolatile(t types.Type) bool {
} }
} }
// Wrapper type to implement sort.Interface for []*types.Selection.
type methodList struct {
methods []*types.Selection
program *Program
}
func (m *methodList) Len() int {
return len(m.methods)
}
func (m *methodList) Less(i, j int) bool {
iid := m.program.MethodNum(m.methods[i].Obj().(*types.Func))
jid := m.program.MethodNum(m.methods[j].Obj().(*types.Func))
return iid < jid
}
func (m *methodList) Swap(i, j int) {
m.methods[i], m.methods[j] = m.methods[j], m.methods[i]
}
// Wrapper type to implement sort.Interface for []*types.Func.
type funcList struct {
funcs []*types.Func
program *Program
}
func (fl *funcList) Len() int {
return len(fl.funcs)
}
func (fl *funcList) Less(i, j int) bool {
iid := fl.program.MethodNum(fl.funcs[i])
jid := fl.program.MethodNum(fl.funcs[j])
return iid < jid
}
func (fl *funcList) Swap(i, j int) {
fl.funcs[i], fl.funcs[j] = fl.funcs[j], fl.funcs[i]
}
// Return true if this is a CGo-internal function that can be ignored. // Return true if this is a CGo-internal function that can be ignored.
func isCGoInternal(name string) bool { func isCGoInternal(name string) bool {
if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") { if strings.HasPrefix(name, "_Cgo_") || strings.HasPrefix(name, "_cgo") {

Просмотреть файл

@ -2,8 +2,6 @@ package ir
import ( import (
"go/types" "go/types"
"sort"
"strings"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
) )
@ -59,18 +57,6 @@ func Signature(sig *types.Signature) string {
return s return s
} }
// Convert an interface type to a string of all method strings, separated by
// "; ". For example: "Read([]byte) (int, error); Close() error"
func InterfaceKey(itf *types.Interface) string {
methodStrings := []string{}
for i := 0; i < itf.NumMethods(); i++ {
method := itf.Method(i)
methodStrings = append(methodStrings, MethodSignature(method))
}
sort.Strings(methodStrings)
return strings.Join(methodStrings, ";")
}
// Fill in parents of all functions. // Fill in parents of all functions.
// //
// All packages need to be added before this pass can run, or it will produce // All packages need to be added before this pass can run, or it will produce
@ -117,30 +103,17 @@ func (p *Program) AnalyseCallgraph() {
// Find all types that are put in an interface. // Find all types that are put in an interface.
func (p *Program) AnalyseInterfaceConversions() { func (p *Program) AnalyseInterfaceConversions() {
// Clear, if AnalyseTypes has been called before. // Clear, if AnalyseInterfaceConversions has been called before.
p.typesWithoutMethods = map[string]int{"nil": 0} p.typesInInterfaces = map[string]struct{}{}
p.typesWithMethods = map[string]*TypeWithMethods{}
for _, f := range p.Functions { for _, f := range p.Functions {
for _, block := range f.Blocks { for _, block := range f.Blocks {
for _, instr := range block.Instrs { for _, instr := range block.Instrs {
switch instr := instr.(type) { switch instr := instr.(type) {
case *ssa.MakeInterface: case *ssa.MakeInterface:
methods := getAllMethods(f.Prog, instr.X.Type())
name := instr.X.Type().String() name := instr.X.Type().String()
if _, ok := p.typesWithMethods[name]; !ok && len(methods) > 0 { if _, ok := p.typesInInterfaces[name]; !ok {
t := &TypeWithMethods{ p.typesInInterfaces[name] = struct{}{}
t: instr.X.Type(),
Num: len(p.typesWithMethods),
Methods: make(map[string]*types.Selection),
}
for _, sel := range methods {
name := MethodSignature(sel.Obj().(*types.Func))
t.Methods[name] = sel
}
p.typesWithMethods[name] = t
} else if _, ok := p.typesWithoutMethods[name]; !ok && len(methods) == 0 {
p.typesWithoutMethods[name] = len(p.typesWithoutMethods)
} }
} }
} }
@ -349,75 +322,10 @@ func (p *Program) IsBlocking(f *Function) bool {
return f.blocking return f.blocking
} }
// Return the type number and whether this type is actually used. Used in
// interface conversions (type is always used) and type asserts (type may not be
// used, meaning assert is always false in this program).
//
// May only be used after all packages have been added to the analyser.
func (p *Program) TypeNum(typ types.Type) (int, bool) {
if n, ok := p.typesWithoutMethods[typ.String()]; ok {
return n, true
} else if meta, ok := p.typesWithMethods[typ.String()]; ok {
return len(p.typesWithoutMethods) + meta.Num, true
} else {
return -1, false // type is never put in an interface
}
}
// InterfaceNum returns the numeric interface ID of this type, for use in type
// asserts.
func (p *Program) InterfaceNum(itfType *types.Interface) int {
key := InterfaceKey(itfType)
if itf, ok := p.interfaces[key]; !ok {
num := len(p.interfaces)
p.interfaces[key] = &Interface{Num: num, Type: itfType}
return num
} else {
return itf.Num
}
}
// MethodNum returns the numeric ID of this method, to be used in method lookups
// on interfaces for example.
func (p *Program) MethodNum(method *types.Func) int {
name := MethodSignature(method)
if _, ok := p.methodSignatureNames[name]; !ok {
p.methodSignatureNames[name] = len(p.methodSignatureNames)
}
return p.methodSignatureNames[MethodSignature(method)]
}
// The start index of the first dynamic type that has methods.
// Types without methods always have a lower ID and types with methods have this
// or a higher ID.
//
// May only be used after all packages have been added to the analyser.
func (p *Program) FirstDynamicType() int {
return len(p.typesWithoutMethods)
}
// Return all types with methods, sorted by type ID.
func (p *Program) AllDynamicTypes() []*TypeWithMethods {
l := make([]*TypeWithMethods, len(p.typesWithMethods))
for _, m := range p.typesWithMethods {
l[m.Num] = m
}
return l
}
// Return all interface types, sorted by interface ID.
func (p *Program) AllInterfaces() []*Interface {
l := make([]*Interface, len(p.interfaces))
for _, itf := range p.interfaces {
l[itf.Num] = itf
}
return l
}
func (p *Program) FunctionNeedsContext(f *Function) bool { func (p *Program) FunctionNeedsContext(f *Function) bool {
if !f.addressTaken { if !f.addressTaken {
if f.Signature.Recv() != nil { if f.Signature.Recv() != nil {
_, hasInterfaceConversion := p.TypeNum(f.Signature.Recv().Type()) _, hasInterfaceConversion := p.typesInInterfaces[f.Signature.Recv().Type().String()]
if hasInterfaceConversion && p.SignatureNeedsContext(f.Signature) { if hasInterfaceConversion && p.SignatureNeedsContext(f.Signature) {
return true return true
} }

32
main.go
Просмотреть файл

@ -64,7 +64,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
fmt.Println(c.IR()) fmt.Println(c.IR())
} }
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return errors.New("verification error after IR construction")
} }
if config.initInterp { if config.initInterp {
@ -73,13 +73,13 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
return err return err
} }
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return errors.New("verification error after interpreting runtime.initAll")
} }
} }
c.ApplyFunctionSections() // -ffunction-sections c.ApplyFunctionSections() // -ffunction-sections
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return errors.New("verification error after applying function sections")
} }
// Browsers cannot handle external functions that have type i64 because it // Browsers cannot handle external functions that have type i64 because it
@ -92,7 +92,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
return err return err
} }
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return errors.New("verification error after running the wasm i64 hack")
} }
} }
@ -100,20 +100,23 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
// exactly. // exactly.
switch config.opt { switch config.opt {
case "none:", "0": case "none:", "0":
c.Optimize(0, 0, 0) // -O0 err = c.Optimize(0, 0, 0) // -O0
case "1": case "1":
c.Optimize(1, 0, 0) // -O1 err = c.Optimize(1, 0, 0) // -O1
case "2": case "2":
c.Optimize(2, 0, 225) // -O2 err = c.Optimize(2, 0, 225) // -O2
case "s": case "s":
c.Optimize(2, 1, 225) // -Os err = c.Optimize(2, 1, 225) // -Os
case "z": case "z":
c.Optimize(2, 2, 5) // -Oz, default err = c.Optimize(2, 2, 5) // -Oz, default
default: default:
return errors.New("unknown optimization level: -opt=" + config.opt) err = errors.New("unknown optimization level: -opt=" + config.opt)
}
if err != nil {
return err
} }
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return errors.New("verification failure after LLVM optimization passes")
} }
// On the AVR, pointers can point either to flash or to RAM, but we don't // On the AVR, pointers can point either to flash or to RAM, but we don't
@ -124,7 +127,7 @@ func Compile(pkgName, outpath string, spec *TargetSpec, config *BuildConfig, act
if strings.HasPrefix(spec.Triple, "avr") { if strings.HasPrefix(spec.Triple, "avr") {
c.NonConstGlobals() c.NonConstGlobals()
if err := c.Verify(); err != nil { if err := c.Verify(); err != nil {
return err return errors.New("verification error after making all globals non-constant on AVR")
} }
} }
@ -382,7 +385,10 @@ func Run(pkgName string) error {
// -Oz, which is the fastest optimization level (faster than -O0, -O1, -O2 // -Oz, which is the fastest optimization level (faster than -O0, -O1, -O2
// and -Os). Turn off the inliner, as the inliner increases optimization // and -Os). Turn off the inliner, as the inliner increases optimization
// time. // time.
c.Optimize(2, 2, 0) err = c.Optimize(2, 2, 0)
if err != nil {
return err
}
engine, err := llvm.NewExecutionEngine(c.Module()) engine, err := llvm.NewExecutionEngine(c.Module())
if err != nil { if err != nil {

Просмотреть файл

@ -4,64 +4,12 @@ package runtime
// //
// Interfaces are represented as a pair of {typecode, value}, where value can be // Interfaces are represented as a pair of {typecode, value}, where value can be
// anything (including non-pointers). // anything (including non-pointers).
//
// Signatures itself are not matched on strings, but on uniqued numbers that
// contain the name and the signature of the function (to save space), think of
// signatures as interned strings at compile time.
//
// The typecode is a small number unique for the Go type. All typecodes <
// firstTypeWithMethods do not have any methods and typecodes >=
// firstTypeWithMethods all have at least one method. This means that
// methodSetRanges does not need to contain types without methods and is thus
// indexed starting at a typecode with number firstTypeWithMethods.
//
// To further conserve some space, the methodSetRange (as the name indicates)
// doesn't contain a list of methods and function pointers directly, but instead
// just indexes into methodSetSignatures and methodSetFunctions which contains
// the mapping from uniqued signature to function pointer.
type _interface struct { type _interface struct {
typecode uint16 typecode uintptr
value *uint8 value *uint8
} }
// This struct indicates the range of methods in the methodSetSignatures and
// methodSetFunctions arrays that belong to this named type.
type methodSetRange struct {
index uint16 // start index into interfaceSignatures and interfaceFunctions
length uint16 // number of methods
}
// Global constants that will be set by the compiler. The arrays are of size 0,
// which is a dummy value, but will be bigger after the compiler has filled them
// in.
var (
firstTypeWithMethods uint16 // the lowest typecode that has at least one method
methodSetRanges [0]methodSetRange // indices into methodSetSignatures and methodSetFunctions
methodSetSignatures [0]uint16 // uniqued method ID
methodSetFunctions [0]*uint8 // function pointer of method
interfaceIndex [0]uint16 // mapping from interface ID to an index in interfaceMethods
interfaceLengths [0]uint8 // mapping from interface ID to the number of methods it has
interfaceMethods [0]uint16 // the method an interface implements (list of method IDs)
)
// Get the function pointer for the method on the interface.
// This is a compiler intrinsic.
//go:nobounds
func interfaceMethod(typecode uint16, method uint16) *uint8 {
// This function doesn't do bounds checking as the supplied method must be
// in the list of signatures. The compiler will only emit
// runtime.interfaceMethod calls when the method actually exists on this
// interface (proven by the typechecker).
i := methodSetRanges[typecode-firstTypeWithMethods].index
for {
if methodSetSignatures[i] == method {
return methodSetFunctions[i]
}
i++
}
}
// Return true iff both interfaces are equal. // Return true iff both interfaces are equal.
func interfaceEqual(x, y _interface) bool { func interfaceEqual(x, y _interface) bool {
if x.typecode != y.typecode { if x.typecode != y.typecode {
@ -76,67 +24,37 @@ func interfaceEqual(x, y _interface) bool {
panic("unimplemented: interface equality") panic("unimplemented: interface equality")
} }
// Return true iff the type implements all methods needed by the interface. This // interfaceTypeAssert is called when a type assert without comma-ok still
// means the type satisfies the interface. // returns false.
// This is a compiler intrinsic.
//go:nobounds
func interfaceImplements(typecode, interfaceNum uint16) bool {
// method set indices of the interface
itfIndex := interfaceIndex[interfaceNum]
itfIndexEnd := itfIndex + uint16(interfaceLengths[interfaceNum])
if itfIndex == itfIndexEnd {
// This interface has no methods, so it satisfies all types.
// TODO: this should be figured out at compile time (as it is known at
// compile time), so that this check is unnecessary at runtime.
return true
}
if typecode < firstTypeWithMethods {
// Type has no methods while the interface has (checked above), so this
// type does not satisfy this interface.
return false
}
// method set indices of the concrete type
methodSet := methodSetRanges[typecode-firstTypeWithMethods]
methodIndex := methodSet.index
methodIndexEnd := methodSet.index + methodSet.length
// Iterate over all methods of the interface:
for itfIndex < itfIndexEnd {
methodId := interfaceMethods[itfIndex]
if methodIndex >= methodIndexEnd {
// Reached the end of the list of methods, so interface doesn't
// implement this type.
return false
}
if methodId == methodSetSignatures[methodIndex] {
// Found a matching method, continue to the next method.
itfIndex++
methodIndex++
continue
} else if methodId > methodSetSignatures[methodIndex] {
// The method didn't match, but method ID of the concrete type was
// lower than that of the interface, so probably it has a method the
// interface doesn't implement.
// Move on to the next method of the concrete type.
methodIndex++
continue
} else {
// The concrete type is missing a method. This means the type assert
// fails.
return false
}
}
// Found a method for each expected method in the interface. This type
// assert is successful.
return true
}
func interfaceTypeAssert(ok bool) { func interfaceTypeAssert(ok bool) {
if !ok { if !ok {
runtimePanic("type assert failed") runtimePanic("type assert failed")
} }
} }
// The following declarations are only used during IR construction. They are
// lowered to inline IR in the interface lowering pass.
// See compiler/interface-lowering.go for details.
type interfaceMethodInfo struct {
signature *uint8 // external *i8 with a name identifying the Go function signature
funcptr *uint8 // bitcast from the actual function pointer
}
// Pseudo function call used while putting a concrete value in an interface,
// that must be lowered to a constant uintptr.
func makeInterface(typecode *uint8, methodSet *interfaceMethodInfo) uintptr
// Pseudo function call used during a type assert. It is used during interface
// lowering, to assign the lowest type numbers to the types with the most type
// asserts. Also, it is replaced with const false if this type assert can never
// happen.
func typeAssert(actualType uintptr, assertedType *uint8) bool
// Pseudo function call that returns whether a given type implements all methods
// of the given interface.
func interfaceImplements(typecode uintptr, interfaceMethodSet **uint8) bool
// Pseudo function that returns a function pointer to the method to call.
// See the interface lowering pass for how this is lowered to a real call.
func interfaceMethod(typecode uintptr, interfaceMethodSet **uint8, signature *uint8) *uint8

Просмотреть файл

@ -205,7 +205,14 @@ func printitf(msg interface{}) {
// cast to underlying type // cast to underlying type
itf := *(*_interface)(unsafe.Pointer(&msg)) itf := *(*_interface)(unsafe.Pointer(&msg))
putchar('(') putchar('(')
print(itf.typecode) switch unsafe.Sizeof(itf.typecode) {
case 2:
printuint16(uint16(itf.typecode))
case 4:
printuint32(uint32(itf.typecode))
case 8:
printuint64(uint64(itf.typecode))
}
putchar(':') putchar(':')
print(itf.value) print(itf.value)
putchar(')') putchar(')')

7
testdata/interface.go предоставленный
Просмотреть файл

@ -26,6 +26,8 @@ func main() {
func printItf(val interface{}) { func printItf(val interface{}) {
switch val := val.(type) { switch val := val.(type) {
case Unmatched:
panic("matched the unmatchable")
case Doubler: case Doubler:
println("is Doubler:", val.Double()) println("is Doubler:", val.Double())
case Tuple: case Tuple:
@ -127,3 +129,8 @@ func (p SmallPair) Nth(n int) uint32 {
func (p SmallPair) Print() { func (p SmallPair) Print() {
println("SmallPair.Print:", p.a, p.b) println("SmallPair.Print:", p.a, p.b)
} }
// There is no type that matches this method.
type Unmatched interface {
NeverImplementedMethod()
}