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.
This commit is contained in:
Ayke van Laethem
2018-11-09 17:16:36 +01:00
parent e45c4ac182
commit b4c90f3677
13 changed files with 1039 additions and 535 deletions
+33 -41
View File
@@ -43,29 +43,30 @@ type Config struct {
type Compiler struct {
Config
mod llvm.Module
ctx llvm.Context
builder llvm.Builder
dibuilder *llvm.DIBuilder
cu llvm.Metadata
difiles map[string]llvm.Metadata
ditypes map[string]llvm.Metadata
machine llvm.TargetMachine
targetData llvm.TargetData
intType llvm.Type
i8ptrType llvm.Type // for convenience
uintptrType llvm.Type
coroIdFunc llvm.Value
coroSizeFunc llvm.Value
coroBeginFunc llvm.Value
coroSuspendFunc llvm.Value
coroEndFunc llvm.Value
coroFreeFunc llvm.Value
initFuncs []llvm.Value
deferFuncs []*ir.Function
deferInvokeFuncs []InvokeDeferFunction
ctxDeferFuncs []ContextDeferFunction
ir *ir.Program
mod llvm.Module
ctx llvm.Context
builder llvm.Builder
dibuilder *llvm.DIBuilder
cu llvm.Metadata
difiles map[string]llvm.Metadata
ditypes map[string]llvm.Metadata
machine llvm.TargetMachine
targetData llvm.TargetData
intType llvm.Type
i8ptrType llvm.Type // for convenience
uintptrType llvm.Type
coroIdFunc llvm.Value
coroSizeFunc llvm.Value
coroBeginFunc llvm.Value
coroSuspendFunc llvm.Value
coroEndFunc llvm.Value
coroFreeFunc llvm.Value
initFuncs []llvm.Value
deferFuncs []*ir.Function
deferInvokeFuncs []InvokeDeferFunction
ctxDeferFuncs []ContextDeferFunction
interfaceInvokeWrappers []interfaceInvokeWrapper
ir *ir.Program
}
type Frame struct {
@@ -489,6 +490,15 @@ func (c *Compiler) Compile(mainPath string) error {
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
// that calls the initializer of each package.
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()
// 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
c.mod.AddNamedMetadataOperand("llvm.module.flags",
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})
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:
// Create initial bucket.
firstBucketGlobal, keySize, valueSize, err := c.initMapNewBucket(prefix, value.Type)