mirror of
https://github.com/tinygo-org/tinygo.git
synced 2026-08-06 03:53:42 +00:00
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:
+30
-112
@@ -4,64 +4,12 @@ package runtime
|
||||
//
|
||||
// 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 <
|
||||
// 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 {
|
||||
typecode uint16
|
||||
typecode uintptr
|
||||
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.
|
||||
func interfaceEqual(x, y _interface) bool {
|
||||
if x.typecode != y.typecode {
|
||||
@@ -76,67 +24,37 @@ func interfaceEqual(x, y _interface) bool {
|
||||
panic("unimplemented: interface equality")
|
||||
}
|
||||
|
||||
// Return true iff the type implements all methods needed by the interface. This
|
||||
// means the type satisfies the interface.
|
||||
// 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
|
||||
}
|
||||
|
||||
// interfaceTypeAssert is called when a type assert without comma-ok still
|
||||
// returns false.
|
||||
func interfaceTypeAssert(ok bool) {
|
||||
if !ok {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user