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.
Assume any external function won't let pointers live longer than the
call itself. This is true in the vast majority of cases (apparently
everywhere currently) but might not always be true.
TODO: add a //go:noescape (or maybe //go:escape) to handle this, instead
of this assumption.
This optimization makes sure the following pattern doesn't do a heap
allocation (assuming Write doesn't modify the slice):
var w *machine.UART = ...
w.Write([]byte("foo"))
As long as Write doesn't modify the slice and LLVM can detect this, a
call to runtime.stringToBytes with the necessary allocation + copy is
avoided.