Move interface method calls in Go from LLVM IR + documentation
This commit moves the itfmethod call implemented directly in LLVM IR to a Go implementation in the runtime. Additionally, it fixes variable names to be more obvious and adds a lot of documentation to explain how interfaces actually work in TinyGo. Code size changes for src/examples/hello: nrf: -144 unix: -93 I'm guessing this code size reduction is a result of removing the 'noinline' function attribute.
Этот коммит содержится в:
родитель
309de00fd6
коммит
539de9db9e
3 изменённых файлов: 88 добавлений и 69 удалений
56
compiler.go
56
compiler.go
|
@ -368,23 +368,24 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize runtime type information, for interfaces.
|
// Initialize runtime type information, for interfaces.
|
||||||
|
// See src/runtime/interface.go for more details.
|
||||||
dynamicTypes := c.ir.AllDynamicTypes()
|
dynamicTypes := c.ir.AllDynamicTypes()
|
||||||
numDynamicTypes := 0
|
numDynamicTypes := 0
|
||||||
for _, meta := range dynamicTypes {
|
for _, meta := range dynamicTypes {
|
||||||
numDynamicTypes += len(meta.Methods)
|
numDynamicTypes += len(meta.Methods)
|
||||||
}
|
}
|
||||||
tuples := make([]llvm.Value, 0, len(dynamicTypes))
|
ranges := make([]llvm.Value, 0, len(dynamicTypes))
|
||||||
funcPointers := make([]llvm.Value, 0, numDynamicTypes)
|
funcPointers := make([]llvm.Value, 0, numDynamicTypes)
|
||||||
signatures := make([]llvm.Value, 0, numDynamicTypes)
|
signatures := make([]llvm.Value, 0, numDynamicTypes)
|
||||||
startIndex := 0
|
startIndex := 0
|
||||||
tupleType := c.mod.GetTypeByName("interface_tuple")
|
rangeType := c.mod.GetTypeByName("runtime.methodSetRange")
|
||||||
for _, meta := range dynamicTypes {
|
for _, meta := range dynamicTypes {
|
||||||
tupleValues := []llvm.Value{
|
rangeValues := []llvm.Value{
|
||||||
llvm.ConstInt(llvm.Int32Type(), uint64(startIndex), false),
|
llvm.ConstInt(llvm.Int32Type(), uint64(startIndex), false),
|
||||||
llvm.ConstInt(llvm.Int32Type(), uint64(len(meta.Methods)), false),
|
llvm.ConstInt(llvm.Int32Type(), uint64(len(meta.Methods)), false),
|
||||||
}
|
}
|
||||||
tuple := llvm.ConstNamedStruct(tupleType, tupleValues)
|
rangeValue := llvm.ConstNamedStruct(rangeType, rangeValues)
|
||||||
tuples = append(tuples, tuple)
|
ranges = append(ranges, rangeValue)
|
||||||
for _, method := range meta.Methods {
|
for _, method := range meta.Methods {
|
||||||
f := c.ir.GetFunction(program.MethodValue(method))
|
f := c.ir.GetFunction(program.MethodValue(method))
|
||||||
if f.llvmFn.IsNil() {
|
if f.llvmFn.IsNil() {
|
||||||
|
@ -398,33 +399,34 @@ func (c *Compiler) Parse(mainPath string, buildTags []string) error {
|
||||||
}
|
}
|
||||||
startIndex += len(meta.Methods)
|
startIndex += len(meta.Methods)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the pre-created arrays with the generated arrays.
|
// Replace the pre-created arrays with the generated arrays.
|
||||||
tupleArray := llvm.ConstArray(tupleType, tuples)
|
rangeArray := llvm.ConstArray(rangeType, ranges)
|
||||||
tupleArrayNewGlobal := llvm.AddGlobal(c.mod, tupleArray.Type(), "interface_tuples.tmp")
|
rangeArrayNewGlobal := llvm.AddGlobal(c.mod, rangeArray.Type(), "runtime.methodSetRanges.tmp")
|
||||||
tupleArrayNewGlobal.SetInitializer(tupleArray)
|
rangeArrayNewGlobal.SetInitializer(rangeArray)
|
||||||
tupleArrayNewGlobal.SetLinkage(llvm.PrivateLinkage)
|
rangeArrayNewGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||||
tupleArrayOldGlobal := c.mod.NamedGlobal("interface_tuples")
|
rangeArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetRanges")
|
||||||
tupleArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(tupleArrayNewGlobal, tupleArrayOldGlobal.Type()))
|
rangeArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(rangeArrayNewGlobal, rangeArrayOldGlobal.Type()))
|
||||||
tupleArrayOldGlobal.EraseFromParentAsGlobal()
|
rangeArrayOldGlobal.EraseFromParentAsGlobal()
|
||||||
tupleArrayNewGlobal.SetName("interface_tuples")
|
rangeArrayNewGlobal.SetName("runtime.methodSetRanges")
|
||||||
funcArray := llvm.ConstArray(c.i8ptrType, funcPointers)
|
funcArray := llvm.ConstArray(c.i8ptrType, funcPointers)
|
||||||
funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "interface_functions.tmp")
|
funcArrayNewGlobal := llvm.AddGlobal(c.mod, funcArray.Type(), "runtime.methodSetFunctions.tmp")
|
||||||
funcArrayNewGlobal.SetInitializer(funcArray)
|
funcArrayNewGlobal.SetInitializer(funcArray)
|
||||||
funcArrayNewGlobal.SetLinkage(llvm.PrivateLinkage)
|
funcArrayNewGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||||
funcArrayOldGlobal := c.mod.NamedGlobal("interface_functions")
|
funcArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetFunctions")
|
||||||
funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type()))
|
funcArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(funcArrayNewGlobal, funcArrayOldGlobal.Type()))
|
||||||
funcArrayOldGlobal.EraseFromParentAsGlobal()
|
funcArrayOldGlobal.EraseFromParentAsGlobal()
|
||||||
funcArrayNewGlobal.SetName("interface_functions")
|
funcArrayNewGlobal.SetName("runtime.methodSetFunctions")
|
||||||
signatureArray := llvm.ConstArray(llvm.Int32Type(), signatures)
|
signatureArray := llvm.ConstArray(llvm.Int32Type(), signatures)
|
||||||
signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "interface_signatures.tmp")
|
signatureArrayNewGlobal := llvm.AddGlobal(c.mod, signatureArray.Type(), "runtime.methodSetSignatures.tmp")
|
||||||
signatureArrayNewGlobal.SetInitializer(signatureArray)
|
signatureArrayNewGlobal.SetInitializer(signatureArray)
|
||||||
signatureArrayNewGlobal.SetLinkage(llvm.PrivateLinkage)
|
signatureArrayNewGlobal.SetLinkage(llvm.PrivateLinkage)
|
||||||
signatureArrayOldGlobal := c.mod.NamedGlobal("interface_signatures")
|
signatureArrayOldGlobal := c.mod.NamedGlobal("runtime.methodSetSignatures")
|
||||||
signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type()))
|
signatureArrayOldGlobal.ReplaceAllUsesWith(llvm.ConstBitCast(signatureArrayNewGlobal, signatureArrayOldGlobal.Type()))
|
||||||
signatureArrayOldGlobal.EraseFromParentAsGlobal()
|
signatureArrayOldGlobal.EraseFromParentAsGlobal()
|
||||||
signatureArrayNewGlobal.SetName("interface_signatures")
|
signatureArrayNewGlobal.SetName("runtime.methodSetSignatures")
|
||||||
|
|
||||||
c.mod.NamedGlobal("first_interface_num").SetInitializer(llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.FirstDynamicType()), false))
|
c.mod.NamedGlobal("runtime.firstInterfaceNum").SetInitializer(llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.FirstDynamicType()), false))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -461,7 +463,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
|
||||||
return llvm.Type{}, errors.New("todo: unknown basic type: " + typ.String())
|
return llvm.Type{}, errors.New("todo: unknown basic type: " + typ.String())
|
||||||
}
|
}
|
||||||
case *types.Interface:
|
case *types.Interface:
|
||||||
return c.mod.GetTypeByName("interface"), nil
|
return c.mod.GetTypeByName("runtime._interface"), nil
|
||||||
case *types.Map:
|
case *types.Map:
|
||||||
return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0), nil
|
return llvm.PointerType(c.mod.GetTypeByName("runtime.hashmap"), 0), nil
|
||||||
case *types.Named:
|
case *types.Named:
|
||||||
|
@ -500,7 +502,7 @@ func (c *Compiler) getLLVMType(goType types.Type) (llvm.Type, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return llvm.Type{}, err
|
return llvm.Type{}, err
|
||||||
}
|
}
|
||||||
if recv.StructName() == "interface" {
|
if recv.StructName() == "runtime._interface" {
|
||||||
recv = c.i8ptrType
|
recv = c.i8ptrType
|
||||||
}
|
}
|
||||||
paramTypes = append(paramTypes, recv)
|
paramTypes = append(paramTypes, recv)
|
||||||
|
@ -748,7 +750,7 @@ func (c *Compiler) getInterpretedValue(value Value) (llvm.Value, error) {
|
||||||
llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false),
|
llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false),
|
||||||
llvm.Undef(c.i8ptrType),
|
llvm.Undef(c.i8ptrType),
|
||||||
}
|
}
|
||||||
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), fields)
|
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
|
||||||
return itf, nil
|
return itf, nil
|
||||||
|
|
||||||
case *MapValue:
|
case *MapValue:
|
||||||
|
@ -1334,7 +1336,7 @@ func (c *Compiler) parseCall(frame *Frame, instr *ssa.CallCommon, parentHandle l
|
||||||
itf,
|
itf,
|
||||||
llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.MethodNum(instr.Method)), false),
|
llvm.ConstInt(llvm.Int32Type(), uint64(c.ir.MethodNum(instr.Method)), false),
|
||||||
}
|
}
|
||||||
fn := c.builder.CreateCall(c.mod.NamedFunction("itfmethod"), values, "invoke.func")
|
fn := c.builder.CreateCall(c.mod.NamedFunction("runtime.itfmethod"), values, "invoke.func")
|
||||||
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
|
fnCast := c.builder.CreateBitCast(fn, llvmFnType, "invoke.func.cast")
|
||||||
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
receiverValue := c.builder.CreateExtractValue(itf, 1, "invoke.func.receiver")
|
||||||
args := []llvm.Value{receiverValue}
|
args := []llvm.Value{receiverValue}
|
||||||
|
@ -1501,7 +1503,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
// TODO: runtime.lookupBoundsCheck is undefined in packages imported by
|
// TODO: runtime.lookupBoundsCheck is undefined in packages imported by
|
||||||
// package runtime, so we have to remove it. This should be fixed.
|
// package runtime, so we have to remove it. This should be fixed.
|
||||||
lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck")
|
lookupBoundsCheck := c.mod.NamedFunction("runtime.lookupBoundsCheck")
|
||||||
if !lookupBoundsCheck.IsNil() {
|
if !lookupBoundsCheck.IsNil() && frame.fn.llvmFn.Name() != "runtime.itfmethod" {
|
||||||
c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{buflen, index}, "")
|
c.builder.CreateCall(lookupBoundsCheck, []llvm.Value{buflen, index}, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1607,7 +1609,7 @@ func (c *Compiler) parseExpr(frame *Frame, expr ssa.Value) (llvm.Value, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itfTypeNum, _ := c.ir.TypeNum(expr.X.Type())
|
itfTypeNum, _ := c.ir.TypeNum(expr.X.Type())
|
||||||
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), []llvm.Value{llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)})
|
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), []llvm.Value{llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false), llvm.Undef(c.i8ptrType)})
|
||||||
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
|
||||||
return itf, nil
|
return itf, nil
|
||||||
case *ssa.MakeMap:
|
case *ssa.MakeMap:
|
||||||
|
@ -1938,7 +1940,7 @@ func (c *Compiler) parseConst(expr *ssa.Const) (llvm.Value, error) {
|
||||||
llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false),
|
llvm.ConstInt(llvm.Int32Type(), uint64(itfTypeNum), false),
|
||||||
llvm.Undef(c.i8ptrType),
|
llvm.Undef(c.i8ptrType),
|
||||||
}
|
}
|
||||||
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("interface"), fields)
|
itf := llvm.ConstNamedStruct(c.mod.GetTypeByName("runtime._interface"), fields)
|
||||||
return itf, nil
|
return itf, nil
|
||||||
case *types.Pointer:
|
case *types.Pointer:
|
||||||
if expr.Value != nil {
|
if expr.Value != nil {
|
||||||
|
|
59
src/runtime/interface.go
Обычный файл
59
src/runtime/interface.go
Обычный файл
|
@ -0,0 +1,59 @@
|
||||||
|
package runtime
|
||||||
|
|
||||||
|
// This file implements Go interfaces.
|
||||||
|
//
|
||||||
|
// Interfaces are represented as a pair of {typecode, value}, where value can be
|
||||||
|
// 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 <
|
||||||
|
// firstInterfaceNum do not have any methods and typecodes >= firstInterfaceNum
|
||||||
|
// 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 firstInterfaceNum.
|
||||||
|
//
|
||||||
|
// 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 {
|
||||||
|
typecode uint32
|
||||||
|
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 uint32 // start index into interfaceSignatures and interfaceFunctions
|
||||||
|
length uint32 // 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 (
|
||||||
|
methodSetRanges [0]methodSetRange // indexes into methodSetSignatures and methodSetFunctions
|
||||||
|
methodSetSignatures [0]uint32 // uniqued method ID
|
||||||
|
methodSetFunctions [0]*uint8 // function pointer of method
|
||||||
|
firstInterfaceNum uint32 // the lowest typecode that has at least one method
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get the function pointer for the method on the interface.
|
||||||
|
// This is a compiler intrinsic.
|
||||||
|
func itfmethod(itf _interface, method uint32) *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.itfmethod
|
||||||
|
// calls when the method actually exists on this interface (proven by the
|
||||||
|
// typechecker).
|
||||||
|
i := methodSetRanges[itf.typecode-firstInterfaceNum].index
|
||||||
|
for {
|
||||||
|
if methodSetSignatures[i] == method {
|
||||||
|
return methodSetFunctions[i]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,5 @@
|
||||||
source_filename = "runtime/runtime.ll"
|
source_filename = "runtime/runtime.ll"
|
||||||
|
|
||||||
%interface = type { i32, i8* }
|
|
||||||
|
|
||||||
declare void @runtime.initAll()
|
declare void @runtime.initAll()
|
||||||
declare void @main.main()
|
declare void @main.main()
|
||||||
declare i8* @main.main$async(i8*)
|
declare i8* @main.main$async(i8*)
|
||||||
|
@ -10,15 +8,6 @@ declare void @runtime.scheduler(i8*)
|
||||||
; Will be changed to true if there are 'go' statements in the compiled program.
|
; Will be changed to true if there are 'go' statements in the compiled program.
|
||||||
@has_scheduler = private unnamed_addr constant i1 false
|
@has_scheduler = private unnamed_addr constant i1 false
|
||||||
|
|
||||||
; Will be changed by the compiler to the first type number with methods.
|
|
||||||
@first_interface_num = private unnamed_addr constant i32 0
|
|
||||||
|
|
||||||
; Will be filled by the compiler with runtime type information.
|
|
||||||
%interface_tuple = type { i32, i32 } ; { index, len }
|
|
||||||
@interface_tuples = external global [0 x %interface_tuple]
|
|
||||||
@interface_signatures = external global [0 x i32] ; array of method IDs
|
|
||||||
@interface_functions = external global [0 x i8*] ; array of function pointers
|
|
||||||
|
|
||||||
define i32 @main() {
|
define i32 @main() {
|
||||||
call void @runtime.initAll()
|
call void @runtime.initAll()
|
||||||
%has_scheduler = load i1, i1* @has_scheduler
|
%has_scheduler = load i1, i1* @has_scheduler
|
||||||
|
@ -36,34 +25,3 @@ without_scheduler:
|
||||||
call void @main.main()
|
call void @main.main()
|
||||||
ret i32 0
|
ret i32 0
|
||||||
}
|
}
|
||||||
|
|
||||||
; Get the function pointer for the method on the interface.
|
|
||||||
; This function only reads constant global data and it's own arguments so it can
|
|
||||||
; be 'readnone' (a pure function).
|
|
||||||
define i8* @itfmethod(%interface %itf, i32 %method) noinline readnone {
|
|
||||||
entry:
|
|
||||||
; Calculate the index in @interface_tuples
|
|
||||||
%concrete_type_num = extractvalue %interface %itf, 0
|
|
||||||
%first_interface_num = load i32, i32* @first_interface_num
|
|
||||||
%index = sub i32 %concrete_type_num, %first_interface_num
|
|
||||||
|
|
||||||
; Calculate the index for @interface_signatures and @interface_functions
|
|
||||||
%itf_index_ptr = getelementptr inbounds [0 x %interface_tuple], [0 x %interface_tuple]* @interface_tuples, i32 0, i32 %index, i32 0
|
|
||||||
%itf_index = load i32, i32* %itf_index_ptr
|
|
||||||
br label %find_method
|
|
||||||
|
|
||||||
; This is a while loop until the method has been found.
|
|
||||||
; It must be in here, so avoid checking the length.
|
|
||||||
find_method:
|
|
||||||
%itf_index.phi = phi i32 [ %itf_index, %entry], [ %itf_index.phi.next, %find_method]
|
|
||||||
%m_ptr = getelementptr inbounds [0 x i32], [0 x i32]* @interface_signatures, i32 0, i32 %itf_index.phi
|
|
||||||
%m = load i32, i32* %m_ptr
|
|
||||||
%found = icmp eq i32 %m, %method
|
|
||||||
%itf_index.phi.next = add i32 %itf_index.phi, 1
|
|
||||||
br i1 %found, label %found_method, label %find_method
|
|
||||||
|
|
||||||
found_method:
|
|
||||||
%fp_ptr = getelementptr inbounds [0 x i8*], [0 x i8*]* @interface_functions, i32 0, i32 %itf_index.phi
|
|
||||||
%fp = load i8*, i8** %fp_ptr
|
|
||||||
ret i8* %fp
|
|
||||||
}
|
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче