transform: split interface and reflect lowering
These two passes are related, but can definitely work independently. Which is what this change does: it splits the two passes. This should make it easier to change these two new passes in the future. This change now also enables slightly better testing by testing these two passes independently. In particular, the reflect lowering pass got some actual tests: it was barely unit-tested before. I have verified that this doesn't really change code size, at least not on the microbit target. Two tests do change, but in a very minor way (and in opposite direction).
Этот коммит содержится в:
родитель
52d8655eec
коммит
cd517a30af
10 изменённых файлов: 395 добавлений и 280 удалений
|
@ -2,7 +2,6 @@ package transform_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"go/token"
|
"go/token"
|
||||||
"go/types"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -11,9 +10,6 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/tinygo-org/tinygo/compileopts"
|
|
||||||
"github.com/tinygo-org/tinygo/compiler"
|
|
||||||
"github.com/tinygo-org/tinygo/loader"
|
|
||||||
"github.com/tinygo-org/tinygo/transform"
|
"github.com/tinygo-org/tinygo/transform"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
@ -39,52 +35,7 @@ func (out allocsTestOutput) String() string {
|
||||||
func TestAllocs2(t *testing.T) {
|
func TestAllocs2(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
target, err := compileopts.LoadTarget("i686--linux")
|
mod := compileGoFileForTesting(t, "./testdata/allocs2.go")
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to load target:", err)
|
|
||||||
}
|
|
||||||
config := &compileopts.Config{
|
|
||||||
Options: &compileopts.Options{},
|
|
||||||
Target: target,
|
|
||||||
}
|
|
||||||
compilerConfig := &compiler.Config{
|
|
||||||
Triple: config.Triple(),
|
|
||||||
GOOS: config.GOOS(),
|
|
||||||
GOARCH: config.GOARCH(),
|
|
||||||
CodeModel: config.CodeModel(),
|
|
||||||
RelocationModel: config.RelocationModel(),
|
|
||||||
Scheduler: config.Scheduler(),
|
|
||||||
FuncImplementation: config.FuncImplementation(),
|
|
||||||
AutomaticStackSize: config.AutomaticStackSize(),
|
|
||||||
Debug: true,
|
|
||||||
}
|
|
||||||
machine, err := compiler.NewTargetMachine(compilerConfig)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create target machine:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load entire program AST into memory.
|
|
||||||
lprogram, err := loader.Load(config, []string{"./testdata/allocs2.go"}, config.ClangHeaders, types.Config{
|
|
||||||
Sizes: compiler.Sizes(machine),
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("failed to create target machine:", err)
|
|
||||||
}
|
|
||||||
err = lprogram.Parse()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("could not parse", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile AST to IR.
|
|
||||||
program := lprogram.LoadSSA()
|
|
||||||
pkg := lprogram.MainPkg()
|
|
||||||
mod, errs := compiler.CompilePackage("allocs2.go", pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
|
||||||
if errs != nil {
|
|
||||||
for _, err := range errs {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run functionattrs pass, which is necessary for escape analysis.
|
// Run functionattrs pass, which is necessary for escape analysis.
|
||||||
pm := llvm.NewPassManager()
|
pm := llvm.NewPassManager()
|
||||||
|
|
|
@ -74,12 +74,10 @@ type methodInfo struct {
|
||||||
// typeInfo describes a single concrete Go type, which can be a basic or a named
|
// 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. If it is a named type, it may have methods.
|
||||||
type typeInfo struct {
|
type typeInfo struct {
|
||||||
name string
|
name string
|
||||||
typecode llvm.Value
|
typecode llvm.Value
|
||||||
methodSet llvm.Value
|
methodSet llvm.Value
|
||||||
num uint64 // the type number after lowering
|
methods []*methodInfo
|
||||||
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
|
// getMethod looks up the method on this type with the given signature and
|
||||||
|
@ -94,27 +92,13 @@ func (t *typeInfo) getMethod(signature *signatureInfo) *methodInfo {
|
||||||
panic("could not find method")
|
panic("could not find method")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
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
|
// interfaceInfo keeps information about a Go interface type, including all
|
||||||
// methods it has.
|
// methods it has.
|
||||||
type interfaceInfo struct {
|
type interfaceInfo struct {
|
||||||
name string // name with $interface suffix
|
name string // name with $interface suffix
|
||||||
methodSet llvm.Value // global which this interfaceInfo describes
|
methodSet llvm.Value // global which this interfaceInfo describes
|
||||||
signatures []*signatureInfo // method set
|
signatures []*signatureInfo // method set
|
||||||
types typeInfoSlice // types this interface implements
|
types []*typeInfo // types this interface implements
|
||||||
assertFunc llvm.Value // runtime.interfaceImplements replacement
|
assertFunc llvm.Value // runtime.interfaceImplements replacement
|
||||||
methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature
|
methodFuncs map[*signatureInfo]llvm.Value // runtime.interfaceMethod replacements for each signature
|
||||||
}
|
}
|
||||||
|
@ -163,7 +147,6 @@ func LowerInterfaces(mod llvm.Module, sizeLevel int) error {
|
||||||
// run runs the pass itself.
|
// run runs the pass itself.
|
||||||
func (p *lowerInterfacesPass) run() error {
|
func (p *lowerInterfacesPass) run() error {
|
||||||
// Collect all type codes.
|
// Collect all type codes.
|
||||||
var typecodeIDs []llvm.Value
|
|
||||||
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
if strings.HasPrefix(global.Name(), "reflect/types.type:") {
|
if strings.HasPrefix(global.Name(), "reflect/types.type:") {
|
||||||
// Retrieve Go type information based on an opaque global variable.
|
// Retrieve Go type information based on an opaque global variable.
|
||||||
|
@ -171,7 +154,6 @@ func (p *lowerInterfacesPass) run() error {
|
||||||
// discarded afterwards.
|
// discarded afterwards.
|
||||||
name := strings.TrimPrefix(global.Name(), "reflect/types.type:")
|
name := strings.TrimPrefix(global.Name(), "reflect/types.type:")
|
||||||
if _, ok := p.types[name]; !ok {
|
if _, ok := p.types[name]; !ok {
|
||||||
typecodeIDs = append(typecodeIDs, global)
|
|
||||||
t := &typeInfo{
|
t := &typeInfo{
|
||||||
name: name,
|
name: name,
|
||||||
typecode: global,
|
typecode: global,
|
||||||
|
@ -187,18 +169,6 @@ func (p *lowerInterfacesPass) run() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 := strings.TrimPrefix(typecode.Name(), "reflect/types.typeid:")
|
|
||||||
if t, ok := p.types[name]; ok {
|
|
||||||
t.countTypeAsserts++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find all interface method calls.
|
// Find all interface method calls.
|
||||||
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod")
|
interfaceMethod := p.mod.NamedFunction("runtime.interfaceMethod")
|
||||||
interfaceMethodUses := getUses(interfaceMethod)
|
interfaceMethodUses := getUses(interfaceMethod)
|
||||||
|
@ -274,10 +244,11 @@ func (p *lowerInterfacesPass) run() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort all types added to the interfaces, to check for more common types
|
// Sort all types added to the interfaces.
|
||||||
// first.
|
|
||||||
for _, itf := range p.interfaces {
|
for _, itf := range p.interfaces {
|
||||||
sort.Sort(itf.types)
|
sort.Slice(itf.types, func(i, j int) bool {
|
||||||
|
return itf.types[i].name > itf.types[j].name
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace all interface methods with their uses, if possible.
|
// Replace all interface methods with their uses, if possible.
|
||||||
|
@ -339,43 +310,10 @@ func (p *lowerInterfacesPass) run() error {
|
||||||
use.EraseFromParentAsInstruction()
|
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(sort.Reverse(typeSlice))
|
|
||||||
|
|
||||||
// Assign a type code for each type.
|
|
||||||
assignTypeCodes(p.mod, typeSlice)
|
|
||||||
|
|
||||||
// Replace each use of a ptrtoint runtime.typecodeID with the constant type
|
|
||||||
// code.
|
|
||||||
for _, global := range typecodeIDs {
|
|
||||||
for _, use := range getUses(global) {
|
|
||||||
if use.IsAConstantExpr().IsNil() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
t := p.types[strings.TrimPrefix(global.Name(), "reflect/types.type:")]
|
|
||||||
typecode := llvm.ConstInt(p.uintptrType, t.num, false)
|
|
||||||
switch use.Opcode() {
|
|
||||||
case llvm.PtrToInt:
|
|
||||||
// Already of the correct type.
|
|
||||||
case llvm.BitCast:
|
|
||||||
// Could happen when stored in an interface (which is of type
|
|
||||||
// i8*).
|
|
||||||
typecode = llvm.ConstIntToPtr(typecode, use.Type())
|
|
||||||
default:
|
|
||||||
panic("unexpected constant expression")
|
|
||||||
}
|
|
||||||
use.ReplaceAllUsesWith(typecode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace each type assert with an actual type comparison or (if the type
|
// Replace each type assert with an actual type comparison or (if the type
|
||||||
// assert is impossible) the constant false.
|
// assert is impossible) the constant false.
|
||||||
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
llvmFalse := llvm.ConstInt(p.ctx.Int1Type(), 0, false)
|
||||||
for _, use := range typeAssertUses {
|
for _, use := range getUses(p.mod.NamedFunction("runtime.typeAssert")) {
|
||||||
actualType := use.Operand(0)
|
actualType := use.Operand(0)
|
||||||
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
|
name := strings.TrimPrefix(use.Operand(1).Name(), "reflect/types.typeid:")
|
||||||
if t, ok := p.types[name]; ok {
|
if t, ok := p.types[name]; ok {
|
||||||
|
@ -395,54 +333,13 @@ func (p *lowerInterfacesPass) run() error {
|
||||||
use.EraseFromParentAsInstruction()
|
use.EraseFromParentAsInstruction()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill in each helper function for type asserts on interfaces
|
// Remove all method sets, which are now unnecessary and inhibit later
|
||||||
// (interface-to-interface matches).
|
// optimizations if they are left in place.
|
||||||
for _, itf := range p.interfaces {
|
for _, t := range p.types {
|
||||||
if !itf.assertFunc.IsNil() {
|
initializer := t.typecode.Initializer()
|
||||||
p.createInterfaceImplementsFunc(itf)
|
methodSet := llvm.ConstExtractValue(initializer, []uint32{2})
|
||||||
}
|
initializer = llvm.ConstInsertValue(initializer, llvm.ConstNull(methodSet.Type()), []uint32{2})
|
||||||
for signature := range itf.methodFuncs {
|
t.typecode.SetInitializer(initializer)
|
||||||
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.IsAConstantExpr().IsNil() && use.Opcode() == llvm.PtrToInt {
|
|
||||||
use.ReplaceAllUsesWith(llvm.ConstInt(p.uintptrType, typ.num, false))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove most objects created for interface and reflect lowering.
|
|
||||||
// Unnecessary, but cleans up the IR for inspection and testing.
|
|
||||||
for _, typ := range p.types {
|
|
||||||
// Only some typecodes have an initializer.
|
|
||||||
initializer := typ.typecode.Initializer()
|
|
||||||
if !initializer.IsNil() {
|
|
||||||
references := llvm.ConstExtractValue(initializer, []uint32{0})
|
|
||||||
typ.typecode.SetInitializer(llvm.ConstNull(initializer.Type()))
|
|
||||||
if strings.HasPrefix(typ.name, "reflect/types.type:struct:") {
|
|
||||||
// Structs have a 'references' field that is not a typecode but
|
|
||||||
// a pointer to a runtime.structField array and therefore a
|
|
||||||
// bitcast. This global should be erased separately, otherwise
|
|
||||||
// typecode objects cannot be erased.
|
|
||||||
structFields := references.Operand(0)
|
|
||||||
structFields.EraseFromParentAsGlobal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !typ.methodSet.IsNil() {
|
|
||||||
typ.methodSet.EraseFromParentAsGlobal()
|
|
||||||
typ.methodSet = llvm.Value{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, itf := range p.interfaces {
|
|
||||||
// Remove method sets of interfaces.
|
|
||||||
itf.methodSet.EraseFromParentAsGlobal()
|
|
||||||
itf.methodSet = llvm.Value{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -559,6 +456,10 @@ func (p *lowerInterfacesPass) replaceInvokeWithCall(use llvm.Value, typ *typeInf
|
||||||
// getInterfaceImplementsFunc returns a function that checks whether a given
|
// getInterfaceImplementsFunc returns a function that checks whether a given
|
||||||
// interface type implements a given interface, by checking all possible types
|
// interface type implements a given interface, by checking all possible types
|
||||||
// that implement this interface.
|
// that implement this interface.
|
||||||
|
//
|
||||||
|
// The type match is implemented using an if/else chain over all possible types.
|
||||||
|
// This if/else chain is easily converted to a big switch over all possible
|
||||||
|
// types by the LLVM simplifycfg pass.
|
||||||
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value {
|
func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llvm.Value {
|
||||||
if !itf.assertFunc.IsNil() {
|
if !itf.assertFunc.IsNil() {
|
||||||
return itf.assertFunc
|
return itf.assertFunc
|
||||||
|
@ -568,60 +469,49 @@ func (p *lowerInterfacesPass) getInterfaceImplementsFunc(itf *interfaceInfo) llv
|
||||||
// TODO: debug info
|
// TODO: debug info
|
||||||
fnName := itf.id() + "$typeassert"
|
fnName := itf.id() + "$typeassert"
|
||||||
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
|
fnType := llvm.FunctionType(p.ctx.Int1Type(), []llvm.Type{p.uintptrType}, false)
|
||||||
itf.assertFunc = llvm.AddFunction(p.mod, fnName, fnType)
|
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
||||||
itf.assertFunc.Param(0).SetName("actualType")
|
itf.assertFunc = fn
|
||||||
|
fn.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.SetLinkage(llvm.InternalLinkage)
|
||||||
fn.SetUnnamedAddr(true)
|
fn.SetUnnamedAddr(true)
|
||||||
if p.sizeLevel >= 2 {
|
if p.sizeLevel >= 2 {
|
||||||
fn.AddFunctionAttr(p.ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
|
fn.AddFunctionAttr(p.ctx.CreateEnumAttribute(llvm.AttributeKindID("optsize"), 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: debug info
|
// Start the if/else chain at the entry block.
|
||||||
|
|
||||||
// Create all used basic blocks.
|
|
||||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||||
thenBlock := p.ctx.AddBasicBlock(fn, "then")
|
thenBlock := p.ctx.AddBasicBlock(fn, "then")
|
||||||
elseBlock := p.ctx.AddBasicBlock(fn, "else")
|
|
||||||
|
|
||||||
// Add all possible types as cases.
|
|
||||||
p.builder.SetInsertPointAtEnd(entry)
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
|
||||||
|
// Iterate over all possible types. Each iteration creates a new branch
|
||||||
|
// either to the 'then' block (success) or the .next block, for the next
|
||||||
|
// check.
|
||||||
actualType := fn.Param(0)
|
actualType := fn.Param(0)
|
||||||
sw := p.builder.CreateSwitch(actualType, elseBlock, len(itf.types))
|
|
||||||
for _, typ := range itf.types {
|
for _, typ := range itf.types {
|
||||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), thenBlock)
|
nextBlock := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
||||||
|
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp")
|
||||||
|
p.builder.CreateCondBr(cmp, thenBlock, nextBlock)
|
||||||
|
p.builder.SetInsertPointAtEnd(nextBlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The builder is now inserting at the last *.next block. Once we reach
|
||||||
|
// this point, all types have been checked so the type assert will have
|
||||||
|
// failed.
|
||||||
|
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
||||||
|
|
||||||
// Fill 'then' block (type assert was successful).
|
// Fill 'then' block (type assert was successful).
|
||||||
p.builder.SetInsertPointAtEnd(thenBlock)
|
p.builder.SetInsertPointAtEnd(thenBlock)
|
||||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
|
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 1, false))
|
||||||
|
|
||||||
// Fill 'else' block (type asserted failed).
|
return itf.assertFunc
|
||||||
p.builder.SetInsertPointAtEnd(elseBlock)
|
|
||||||
p.builder.CreateRet(llvm.ConstInt(p.ctx.Int1Type(), 0, false))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
|
// getInterfaceMethodFunc returns a thunk for calling a method on an interface.
|
||||||
// It only declares the function, createInterfaceMethodFunc actually defines the
|
//
|
||||||
// function.
|
// Matching the actual type is implemented using an if/else chain over all
|
||||||
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, params []llvm.Type) llvm.Value {
|
// possible types. This is later converted to a switch statement by the LLVM
|
||||||
|
// simplifycfg pass.
|
||||||
|
func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signature *signatureInfo, returnType llvm.Type, paramTypes []llvm.Type) llvm.Value {
|
||||||
if fn, ok := itf.methodFuncs[signature]; ok {
|
if fn, ok := itf.methodFuncs[signature]; ok {
|
||||||
// This function has already been created.
|
// This function has already been created.
|
||||||
return fn
|
return fn
|
||||||
|
@ -634,22 +524,11 @@ func (p *lowerInterfacesPass) getInterfaceMethodFunc(itf *interfaceInfo, signatu
|
||||||
// Construct the function name, which is of the form:
|
// Construct the function name, which is of the form:
|
||||||
// (main.Stringer).String
|
// (main.Stringer).String
|
||||||
fnName := "(" + itf.id() + ")." + signature.methodName()
|
fnName := "(" + itf.id() + ")." + signature.methodName()
|
||||||
fnType := llvm.FunctionType(returnType, append(params, llvm.PointerType(p.ctx.Int8Type(), 0)), false)
|
fnType := llvm.FunctionType(returnType, append(paramTypes, llvm.PointerType(p.ctx.Int8Type(), 0)), false)
|
||||||
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
fn := llvm.AddFunction(p.mod, fnName, fnType)
|
||||||
llvm.PrevParam(fn.LastParam()).SetName("actualType")
|
llvm.PrevParam(fn.LastParam()).SetName("actualType")
|
||||||
fn.LastParam().SetName("parentHandle")
|
fn.LastParam().SetName("parentHandle")
|
||||||
itf.methodFuncs[signature] = fn
|
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.SetLinkage(llvm.InternalLinkage)
|
||||||
fn.SetUnnamedAddr(true)
|
fn.SetUnnamedAddr(true)
|
||||||
if p.sizeLevel >= 2 {
|
if p.sizeLevel >= 2 {
|
||||||
|
@ -658,29 +537,6 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||||
|
|
||||||
// TODO: debug info
|
// TODO: debug info
|
||||||
|
|
||||||
// Create entry block.
|
|
||||||
entry := p.ctx.AddBasicBlock(fn, "entry")
|
|
||||||
|
|
||||||
// Create default block and call runtime.nilPanic.
|
|
||||||
// The only other possible value remaining is nil for nil interfaces. We
|
|
||||||
// could panic with a different message here such as "nil interface" but
|
|
||||||
// that would increase code size and "nil panic" is close enough. Most
|
|
||||||
// importantly, it avoids undefined behavior when accidentally calling a
|
|
||||||
// method on a nil interface.
|
|
||||||
defaultBlock := p.ctx.AddBasicBlock(fn, "default")
|
|
||||||
p.builder.SetInsertPointAtEnd(defaultBlock)
|
|
||||||
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
|
||||||
p.builder.CreateCall(nilPanic, []llvm.Value{
|
|
||||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
|
||||||
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
|
||||||
}, "")
|
|
||||||
p.builder.CreateUnreachable()
|
|
||||||
|
|
||||||
// Create type switch in entry block.
|
|
||||||
p.builder.SetInsertPointAtEnd(entry)
|
|
||||||
actualType := llvm.PrevParam(fn.LastParam())
|
|
||||||
sw := p.builder.CreateSwitch(actualType, defaultBlock, len(itf.types))
|
|
||||||
|
|
||||||
// Collect the params that will be passed to the functions to call.
|
// Collect the params that will be passed to the functions to call.
|
||||||
// These params exclude the receiver (which may actually consist of multiple
|
// These params exclude the receiver (which may actually consist of multiple
|
||||||
// parts).
|
// parts).
|
||||||
|
@ -689,10 +545,18 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||||
params[i] = fn.Param(i + 1)
|
params[i] = fn.Param(i + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start chain in the entry block.
|
||||||
|
entry := p.ctx.AddBasicBlock(fn, "entry")
|
||||||
|
p.builder.SetInsertPointAtEnd(entry)
|
||||||
|
|
||||||
// Define all possible functions that can be called.
|
// Define all possible functions that can be called.
|
||||||
|
actualType := llvm.PrevParam(fn.LastParam())
|
||||||
for _, typ := range itf.types {
|
for _, typ := range itf.types {
|
||||||
|
// Create type check (if/else).
|
||||||
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
bb := p.ctx.AddBasicBlock(fn, typ.name)
|
||||||
sw.AddCase(llvm.ConstInt(p.uintptrType, typ.num, false), bb)
|
next := p.ctx.AddBasicBlock(fn, typ.name+".next")
|
||||||
|
cmp := p.builder.CreateICmp(llvm.IntEQ, actualType, llvm.ConstPtrToInt(typ.typecode, p.uintptrType), typ.name+".icmp")
|
||||||
|
p.builder.CreateCondBr(cmp, bb, next)
|
||||||
|
|
||||||
// The function we will redirect to when the interface has this type.
|
// The function we will redirect to when the interface has this type.
|
||||||
function := typ.getMethod(signature).function
|
function := typ.getMethod(signature).function
|
||||||
|
@ -725,5 +589,25 @@ func (p *lowerInterfacesPass) createInterfaceMethodFunc(itf *interfaceInfo, sign
|
||||||
} else {
|
} else {
|
||||||
p.builder.CreateRet(retval)
|
p.builder.CreateRet(retval)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start next comparison in the 'next' block (which is jumped to when
|
||||||
|
// the type doesn't match).
|
||||||
|
p.builder.SetInsertPointAtEnd(next)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The builder now points to the last *.then block, after all types have
|
||||||
|
// been checked. Call runtime.nilPanic here.
|
||||||
|
// The only other possible value remaining is nil for nil interfaces. We
|
||||||
|
// could panic with a different message here such as "nil interface" but
|
||||||
|
// that would increase code size and "nil panic" is close enough. Most
|
||||||
|
// importantly, it avoids undefined behavior when accidentally calling a
|
||||||
|
// method on a nil interface.
|
||||||
|
nilPanic := p.mod.NamedFunction("runtime.nilPanic")
|
||||||
|
p.builder.CreateCall(nilPanic, []llvm.Value{
|
||||||
|
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
||||||
|
llvm.Undef(llvm.PointerType(p.ctx.Int8Type(), 0)),
|
||||||
|
}, "")
|
||||||
|
p.builder.CreateUnreachable()
|
||||||
|
|
||||||
|
return fn
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,5 +14,10 @@ func TestInterfaceLowering(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pm := llvm.NewPassManager()
|
||||||
|
defer pm.Dispose()
|
||||||
|
pm.AddGlobalDCEPass()
|
||||||
|
pm.Run(mod)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,7 +63,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
goPasses.AddFunctionAttrsPass()
|
goPasses.AddFunctionAttrsPass()
|
||||||
goPasses.Run(mod)
|
goPasses.Run(mod)
|
||||||
|
|
||||||
// Run Go-specific optimization passes.
|
// Run TinyGo-specific optimization passes.
|
||||||
OptimizeMaps(mod)
|
OptimizeMaps(mod)
|
||||||
OptimizeStringToBytes(mod)
|
OptimizeStringToBytes(mod)
|
||||||
OptimizeReflectImplements(mod)
|
OptimizeReflectImplements(mod)
|
||||||
|
@ -88,6 +88,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
goPasses.Run(mod)
|
goPasses.Run(mod)
|
||||||
|
|
||||||
// Run TinyGo-specific interprocedural optimizations.
|
// Run TinyGo-specific interprocedural optimizations.
|
||||||
|
LowerReflect(mod)
|
||||||
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
|
||||||
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
|
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
|
||||||
})
|
})
|
||||||
|
@ -100,6 +101,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config, optLevel, sizeLevel i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []error{err}
|
return []error{err}
|
||||||
}
|
}
|
||||||
|
LowerReflect(mod)
|
||||||
if config.FuncImplementation() == "switch" {
|
if config.FuncImplementation() == "switch" {
|
||||||
LowerFuncValues(mod)
|
LowerFuncValues(mod)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
|
@ -122,14 +123,45 @@ type typeCodeAssignmentState struct {
|
||||||
needsNamedNonBasicTypesSidetable bool
|
needsNamedNonBasicTypesSidetable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// assignTypeCodes is used to assign a type code to each type in the program
|
// LowerReflect is used to assign a type code to each type in the program
|
||||||
// that is ever stored in an interface. It tries to use the smallest possible
|
// that is ever stored in an interface. It tries to use the smallest possible
|
||||||
// numbers to make the code that works with interfaces as small as possible.
|
// numbers to make the code that works with interfaces as small as possible.
|
||||||
func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
func LowerReflect(mod llvm.Module) {
|
||||||
// if reflect were not used, we could skip generating the sidetable
|
// if reflect were not used, we could skip generating the sidetable
|
||||||
// this does not help in practice, and is difficult to do correctly
|
// this does not help in practice, and is difficult to do correctly
|
||||||
|
|
||||||
|
// Obtain slice of all types in the program.
|
||||||
|
type typeInfo struct {
|
||||||
|
typecode llvm.Value
|
||||||
|
name string
|
||||||
|
numUses int
|
||||||
|
}
|
||||||
|
var types []*typeInfo
|
||||||
|
for global := mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
|
||||||
|
if strings.HasPrefix(global.Name(), "reflect/types.type:") {
|
||||||
|
types = append(types, &typeInfo{
|
||||||
|
typecode: global,
|
||||||
|
name: global.Name(),
|
||||||
|
numUses: len(getUses(global)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the slice in a way that often used types are assigned a type code
|
||||||
|
// first.
|
||||||
|
sort.Slice(types, func(i, j int) bool {
|
||||||
|
if types[i].numUses != types[j].numUses {
|
||||||
|
return types[i].numUses < types[j].numUses
|
||||||
|
}
|
||||||
|
// It would make more sense to compare the name in the other direction,
|
||||||
|
// but for some reason that increases binary size. Could be a fluke, but
|
||||||
|
// could also have some good reason (and possibly hint at a small
|
||||||
|
// optimization).
|
||||||
|
return types[i].name > types[j].name
|
||||||
|
})
|
||||||
|
|
||||||
// Assign typecodes the way the reflect package expects.
|
// Assign typecodes the way the reflect package expects.
|
||||||
|
uintptrType := mod.Context().IntType(llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8)
|
||||||
state := typeCodeAssignmentState{
|
state := typeCodeAssignmentState{
|
||||||
fallbackIndex: 1,
|
fallbackIndex: 1,
|
||||||
uintptrLen: llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8,
|
uintptrLen: llvm.NewTargetData(mod.DataLayout()).PointerSize() * 8,
|
||||||
|
@ -143,7 +175,7 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||||
needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
needsStructNamesSidetable: len(getUses(mod.NamedGlobal("reflect.structNamesSidetable"))) != 0,
|
||||||
needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
needsArrayTypesSidetable: len(getUses(mod.NamedGlobal("reflect.arrayTypesSidetable"))) != 0,
|
||||||
}
|
}
|
||||||
for _, t := range typeSlice {
|
for _, t := range types {
|
||||||
num := state.getTypeCodeNum(t.typecode)
|
num := state.getTypeCodeNum(t.typecode)
|
||||||
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
|
if num.BitLen() > state.uintptrLen || !num.IsUint64() {
|
||||||
// TODO: support this in some way, using a side table for example.
|
// TODO: support this in some way, using a side table for example.
|
||||||
|
@ -152,7 +184,25 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||||
// AVR).
|
// AVR).
|
||||||
panic("compiler: could not store type code number inside interface type code")
|
panic("compiler: could not store type code number inside interface type code")
|
||||||
}
|
}
|
||||||
t.num = num.Uint64()
|
|
||||||
|
// Replace each use of the type code global with the constant type code.
|
||||||
|
for _, use := range getUses(t.typecode) {
|
||||||
|
if use.IsAConstantExpr().IsNil() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
typecode := llvm.ConstInt(uintptrType, num.Uint64(), false)
|
||||||
|
switch use.Opcode() {
|
||||||
|
case llvm.PtrToInt:
|
||||||
|
// Already of the correct type.
|
||||||
|
case llvm.BitCast:
|
||||||
|
// Could happen when stored in an interface (which is of type
|
||||||
|
// i8*).
|
||||||
|
typecode = llvm.ConstIntToPtr(typecode, use.Type())
|
||||||
|
default:
|
||||||
|
panic("unexpected constant expression")
|
||||||
|
}
|
||||||
|
use.ReplaceAllUsesWith(typecode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only create this sidetable when it is necessary.
|
// Only create this sidetable when it is necessary.
|
||||||
|
@ -180,6 +230,23 @@ func assignTypeCodes(mod llvm.Module, typeSlice typeInfoSlice) {
|
||||||
global.SetUnnamedAddr(true)
|
global.SetUnnamedAddr(true)
|
||||||
global.SetGlobalConstant(true)
|
global.SetGlobalConstant(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove most objects created for interface and reflect lowering.
|
||||||
|
// They would normally be removed anyway in later passes, but not always.
|
||||||
|
// It also cleans up the IR for testing.
|
||||||
|
for _, typ := range types {
|
||||||
|
initializer := typ.typecode.Initializer()
|
||||||
|
references := llvm.ConstExtractValue(initializer, []uint32{0})
|
||||||
|
typ.typecode.SetInitializer(llvm.ConstNull(initializer.Type()))
|
||||||
|
if strings.HasPrefix(typ.name, "reflect/types.type:struct:") {
|
||||||
|
// Structs have a 'references' field that is not a typecode but
|
||||||
|
// a pointer to a runtime.structField array and therefore a
|
||||||
|
// bitcast. This global should be erased separately, otherwise
|
||||||
|
// typecode objects cannot be erased.
|
||||||
|
structFields := references.Operand(0)
|
||||||
|
structFields.EraseFromParentAsGlobal()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// getTypeCodeNum returns the typecode for a given type as expected by the
|
// getTypeCodeNum returns the typecode for a given type as expected by the
|
||||||
|
|
77
transform/reflect_test.go
Обычный файл
77
transform/reflect_test.go
Обычный файл
|
@ -0,0 +1,77 @@
|
||||||
|
package transform_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/transform"
|
||||||
|
"tinygo.org/x/go-llvm"
|
||||||
|
)
|
||||||
|
|
||||||
|
type reflectAssert struct {
|
||||||
|
call llvm.Value
|
||||||
|
name string
|
||||||
|
expectedNumber uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test reflect lowering. This code looks at IR like this:
|
||||||
|
//
|
||||||
|
// call void @main.assertType(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef)
|
||||||
|
//
|
||||||
|
// and verifies that the ptrtoint constant (the first parameter of
|
||||||
|
// @main.assertType) is replaced with the correct type code. The expected
|
||||||
|
// output is this:
|
||||||
|
//
|
||||||
|
// call void @main.assertType(i32 4, i8* inttoptr (i32 3 to i8*), i32 4, i8* undef, i8* undef)
|
||||||
|
//
|
||||||
|
// The first and third parameter are compared and must match, the second
|
||||||
|
// parameter is ignored.
|
||||||
|
func TestReflect(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
mod := compileGoFileForTesting(t, "./testdata/reflect.go")
|
||||||
|
|
||||||
|
// Run the instcombine pass, to clean up the IR a bit (especially
|
||||||
|
// insertvalue/extractvalue instructions).
|
||||||
|
pm := llvm.NewPassManager()
|
||||||
|
defer pm.Dispose()
|
||||||
|
pm.AddInstructionCombiningPass()
|
||||||
|
pm.Run(mod)
|
||||||
|
|
||||||
|
// Get a list of all the asserts in the source code.
|
||||||
|
assertType := mod.NamedFunction("main.assertType")
|
||||||
|
var asserts []reflectAssert
|
||||||
|
for user := assertType.FirstUse(); !user.IsNil(); user = user.NextUse() {
|
||||||
|
use := user.User()
|
||||||
|
if use.IsACallInst().IsNil() {
|
||||||
|
t.Fatal("expected call use of main.assertType")
|
||||||
|
}
|
||||||
|
global := use.Operand(0).Operand(0)
|
||||||
|
expectedNumber := use.Operand(2).ZExtValue()
|
||||||
|
asserts = append(asserts, reflectAssert{
|
||||||
|
call: use,
|
||||||
|
name: global.Name(),
|
||||||
|
expectedNumber: expectedNumber,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanity check to show that the test is actually testing anything.
|
||||||
|
if len(asserts) < 3 {
|
||||||
|
t.Errorf("expected at least 3 test cases, got %d", len(asserts))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now lower the type codes.
|
||||||
|
transform.LowerReflect(mod)
|
||||||
|
|
||||||
|
// Check whether the values are as expected.
|
||||||
|
for _, assert := range asserts {
|
||||||
|
actualNumberValue := assert.call.Operand(0)
|
||||||
|
if actualNumberValue.IsAConstantInt().IsNil() {
|
||||||
|
t.Errorf("expected to see a constant for %s, got something else", assert.name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
actualNumber := actualNumberValue.ZExtValue()
|
||||||
|
if actualNumber != assert.expectedNumber {
|
||||||
|
t.Errorf("%s: expected number 0b%b, got 0b%b", assert.name, assert.expectedNumber, actualNumber)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
transform/testdata/interface.ll
предоставленный
4
transform/testdata/interface.ll
предоставленный
|
@ -4,10 +4,10 @@ target triple = "armv7m-none-eabi"
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
|
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||||
|
|
||||||
@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID
|
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
|
||||||
@"reflect/types.typeid:basic:uint8" = external constant i8
|
@"reflect/types.typeid:basic:uint8" = external constant i8
|
||||||
@"reflect/types.typeid:basic:int16" = external constant i8
|
@"reflect/types.typeid:basic:int16" = external constant i8
|
||||||
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
|
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
|
||||||
@"func NeverImplementedMethod()" = external constant i8
|
@"func NeverImplementedMethod()" = external constant i8
|
||||||
@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"]
|
@"Unmatched$interface" = private constant [1 x i8*] [i8* @"func NeverImplementedMethod()"]
|
||||||
@"func Double() int" = external constant i8
|
@"func Double() int" = external constant i8
|
||||||
|
|
50
transform/testdata/interface.out.ll
предоставленный
50
transform/testdata/interface.out.ll
предоставленный
|
@ -4,19 +4,9 @@ target triple = "armv7m-none-eabi"
|
||||||
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
|
%runtime.typecodeID = type { %runtime.typecodeID*, i32, %runtime.interfaceMethodInfo* }
|
||||||
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
%runtime.interfaceMethodInfo = type { i8*, i32 }
|
||||||
|
|
||||||
@"reflect/types.type:basic:uint8" = external constant %runtime.typecodeID
|
@"reflect/types.type:basic:uint8" = private constant %runtime.typecodeID zeroinitializer
|
||||||
@"reflect/types.typeid:basic:uint8" = external constant i8
|
@"reflect/types.type:basic:int" = private constant %runtime.typecodeID zeroinitializer
|
||||||
@"reflect/types.typeid:basic:int16" = external constant i8
|
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID { %runtime.typecodeID* @"reflect/types.type:basic:int", i32 0, %runtime.interfaceMethodInfo* null }
|
||||||
@"reflect/types.type:basic:int" = external constant %runtime.typecodeID
|
|
||||||
@"func NeverImplementedMethod()" = external constant i8
|
|
||||||
@"func Double() int" = external constant i8
|
|
||||||
@"reflect/types.type:named:Number" = private constant %runtime.typecodeID zeroinitializer
|
|
||||||
|
|
||||||
declare i1 @runtime.interfaceImplements(i32, i8**)
|
|
||||||
|
|
||||||
declare i1 @runtime.typeAssert(i32, i8*)
|
|
||||||
|
|
||||||
declare i32 @runtime.interfaceMethod(i32, i8**, i8*)
|
|
||||||
|
|
||||||
declare void @runtime.printuint8(i8)
|
declare void @runtime.printuint8(i8)
|
||||||
|
|
||||||
|
@ -31,9 +21,9 @@ declare void @runtime.printnl()
|
||||||
declare void @runtime.nilPanic(i8*, i8*)
|
declare void @runtime.nilPanic(i8*, i8*)
|
||||||
|
|
||||||
define void @printInterfaces() {
|
define void @printInterfaces() {
|
||||||
call void @printInterface(i32 4, i8* inttoptr (i32 5 to i8*))
|
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:int" to i32), i8* inttoptr (i32 5 to i8*))
|
||||||
call void @printInterface(i32 16, i8* inttoptr (i8 120 to i8*))
|
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), i8* inttoptr (i8 120 to i8*))
|
||||||
call void @printInterface(i32 68, i8* inttoptr (i32 3 to i8*))
|
call void @printInterface(i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32), i8* inttoptr (i32 3 to i8*))
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,7 +47,7 @@ typeswitch.Doubler: ; preds = %typeswitch.notUnmat
|
||||||
ret void
|
ret void
|
||||||
|
|
||||||
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
|
typeswitch.notDoubler: ; preds = %typeswitch.notUnmatched
|
||||||
%typeassert.ok2 = icmp eq i32 16, %typecode
|
%typeassert.ok2 = icmp eq i32 ptrtoint (%runtime.typecodeID* @"reflect/types.type:basic:uint8" to i32), %typecode
|
||||||
br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte
|
br i1 %typeassert.ok2, label %typeswitch.byte, label %typeswitch.notByte
|
||||||
|
|
||||||
typeswitch.byte: ; preds = %typeswitch.notDoubler
|
typeswitch.byte: ; preds = %typeswitch.notDoubler
|
||||||
|
@ -92,40 +82,34 @@ define i32 @"(Number).Double$invoke"(i8* %receiverPtr, i8* %parentHandle) {
|
||||||
|
|
||||||
define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr {
|
define internal i32 @"(Doubler).Double"(i8* %0, i8* %1, i32 %actualType, i8* %parentHandle) unnamed_addr {
|
||||||
entry:
|
entry:
|
||||||
switch i32 %actualType, label %default [
|
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
|
||||||
i32 68, label %"named:Number"
|
br i1 %"named:Number.icmp", label %"named:Number", label %"named:Number.next"
|
||||||
]
|
|
||||||
|
|
||||||
default: ; preds = %entry
|
|
||||||
call void @runtime.nilPanic(i8* undef, i8* undef)
|
|
||||||
unreachable
|
|
||||||
|
|
||||||
"named:Number": ; preds = %entry
|
"named:Number": ; preds = %entry
|
||||||
%2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1)
|
%2 = call i32 @"(Number).Double$invoke"(i8* %0, i8* %1)
|
||||||
ret i32 %2
|
ret i32 %2
|
||||||
|
|
||||||
|
"named:Number.next": ; preds = %entry
|
||||||
|
call void @runtime.nilPanic(i8* undef, i8* undef)
|
||||||
|
unreachable
|
||||||
}
|
}
|
||||||
|
|
||||||
define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr {
|
define internal i1 @"Doubler$typeassert"(i32 %actualType) unnamed_addr {
|
||||||
entry:
|
entry:
|
||||||
switch i32 %actualType, label %else [
|
%"named:Number.icmp" = icmp eq i32 %actualType, ptrtoint (%runtime.typecodeID* @"reflect/types.type:named:Number" to i32)
|
||||||
i32 68, label %then
|
br i1 %"named:Number.icmp", label %then, label %"named:Number.next"
|
||||||
]
|
|
||||||
|
|
||||||
then: ; preds = %entry
|
then: ; preds = %entry
|
||||||
ret i1 true
|
ret i1 true
|
||||||
|
|
||||||
else: ; preds = %entry
|
"named:Number.next": ; preds = %entry
|
||||||
ret i1 false
|
ret i1 false
|
||||||
}
|
}
|
||||||
|
|
||||||
define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr {
|
define internal i1 @"Unmatched$typeassert"(i32 %actualType) unnamed_addr {
|
||||||
entry:
|
entry:
|
||||||
switch i32 %actualType, label %else [
|
ret i1 false
|
||||||
]
|
|
||||||
|
|
||||||
then: ; No predecessors!
|
then: ; No predecessors!
|
||||||
ret i1 true
|
ret i1 true
|
||||||
|
|
||||||
else: ; preds = %entry
|
|
||||||
ret i1 false
|
|
||||||
}
|
}
|
||||||
|
|
56
transform/testdata/reflect.go
предоставленный
Обычный файл
56
transform/testdata/reflect.go
предоставленный
Обычный файл
|
@ -0,0 +1,56 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
// This file tests the type codes assigned by the reflect lowering pass.
|
||||||
|
// This test is not complete, most importantly, sidetables are not currently
|
||||||
|
// being tested.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// See the top of src/reflect/type.go
|
||||||
|
prefixChan = 0b0001
|
||||||
|
prefixInterface = 0b0011
|
||||||
|
prefixPtr = 0b0101
|
||||||
|
prefixSlice = 0b0111
|
||||||
|
prefixArray = 0b1001
|
||||||
|
prefixFunc = 0b1011
|
||||||
|
prefixMap = 0b1101
|
||||||
|
prefixStruct = 0b1111
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Check for some basic types.
|
||||||
|
assertType(3, uintptr(reflect.Int)<<1)
|
||||||
|
assertType(uint8(3), uintptr(reflect.Uint8)<<1)
|
||||||
|
assertType(byte(3), uintptr(reflect.Uint8)<<1)
|
||||||
|
assertType(int64(3), uintptr(reflect.Int64)<<1)
|
||||||
|
assertType("", uintptr(reflect.String)<<1)
|
||||||
|
assertType(3.5, uintptr(reflect.Float64)<<1)
|
||||||
|
assertType(unsafe.Pointer(nil), uintptr(reflect.UnsafePointer)<<1)
|
||||||
|
|
||||||
|
// Check for named types: they are given names in order.
|
||||||
|
// They are sorted in reverse, for no good reason.
|
||||||
|
const intNum = uintptr(reflect.Int) << 1
|
||||||
|
assertType(namedInt1(0), (3<<6)|intNum)
|
||||||
|
assertType(namedInt2(0), (2<<6)|intNum)
|
||||||
|
assertType(namedInt3(0), (1<<6)|intNum)
|
||||||
|
|
||||||
|
// Check for some "prefix-style" types.
|
||||||
|
assertType(make(chan int), (intNum<<5)|prefixChan)
|
||||||
|
assertType(new(int), (intNum<<5)|prefixPtr)
|
||||||
|
assertType([]int{}, (intNum<<5)|prefixSlice)
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
namedInt1 int
|
||||||
|
namedInt2 int
|
||||||
|
namedInt3 int
|
||||||
|
)
|
||||||
|
|
||||||
|
// Pseudo call that is being checked by the code in reflect_test.go.
|
||||||
|
// After reflect lowering, the type code as part of the interface should match
|
||||||
|
// the asserted type code.
|
||||||
|
func assertType(itf interface{}, assertedTypeCode uintptr)
|
|
@ -4,13 +4,19 @@ package transform_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/tinygo-org/tinygo/compileopts"
|
||||||
|
"github.com/tinygo-org/tinygo/compiler"
|
||||||
|
"github.com/tinygo-org/tinygo/loader"
|
||||||
"tinygo.org/x/go-llvm"
|
"tinygo.org/x/go-llvm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -128,3 +134,86 @@ func filterIrrelevantIRLines(lines []string) []string {
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// compileGoFileForTesting compiles the given Go file to run tests against.
|
||||||
|
// Only the given Go file is compiled (no dependencies) and no optimizations are
|
||||||
|
// run.
|
||||||
|
// If there are any errors, they are reported via the *testing.T instance.
|
||||||
|
func compileGoFileForTesting(t *testing.T, filename string) llvm.Module {
|
||||||
|
target, err := compileopts.LoadTarget("i686--linux")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("failed to load target:", err)
|
||||||
|
}
|
||||||
|
config := &compileopts.Config{
|
||||||
|
Options: &compileopts.Options{},
|
||||||
|
Target: target,
|
||||||
|
}
|
||||||
|
compilerConfig := &compiler.Config{
|
||||||
|
Triple: config.Triple(),
|
||||||
|
GOOS: config.GOOS(),
|
||||||
|
GOARCH: config.GOARCH(),
|
||||||
|
CodeModel: config.CodeModel(),
|
||||||
|
RelocationModel: config.RelocationModel(),
|
||||||
|
Scheduler: config.Scheduler(),
|
||||||
|
FuncImplementation: config.FuncImplementation(),
|
||||||
|
AutomaticStackSize: config.AutomaticStackSize(),
|
||||||
|
Debug: true,
|
||||||
|
}
|
||||||
|
machine, err := compiler.NewTargetMachine(compilerConfig)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("failed to create target machine:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load entire program AST into memory.
|
||||||
|
lprogram, err := loader.Load(config, []string{filename}, config.ClangHeaders, types.Config{
|
||||||
|
Sizes: compiler.Sizes(machine),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("failed to create target machine:", err)
|
||||||
|
}
|
||||||
|
err = lprogram.Parse()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("could not parse", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile AST to IR.
|
||||||
|
program := lprogram.LoadSSA()
|
||||||
|
pkg := lprogram.MainPkg()
|
||||||
|
mod, errs := compiler.CompilePackage(filename, pkg, program.Package(pkg.Pkg), machine, compilerConfig, false)
|
||||||
|
if errs != nil {
|
||||||
|
for _, err := range errs {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
return mod
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPosition returns the position information for the given value, as far as
|
||||||
|
// it is available.
|
||||||
|
func getPosition(val llvm.Value) token.Position {
|
||||||
|
if !val.IsAInstruction().IsNil() {
|
||||||
|
loc := val.InstructionDebugLoc()
|
||||||
|
if loc.IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
file := loc.LocationScope().ScopeFile()
|
||||||
|
return token.Position{
|
||||||
|
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||||
|
Line: int(loc.LocationLine()),
|
||||||
|
Column: int(loc.LocationColumn()),
|
||||||
|
}
|
||||||
|
} else if !val.IsAFunction().IsNil() {
|
||||||
|
loc := val.Subprogram()
|
||||||
|
if loc.IsNil() {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
file := loc.ScopeFile()
|
||||||
|
return token.Position{
|
||||||
|
Filename: filepath.Join(file.FileDirectory(), file.FileFilename()),
|
||||||
|
Line: int(loc.SubprogramLine()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return token.Position{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче