all: support interface asserts in interp

This adds support for the math/rand package.
Этот коммит содержится в:
Ayke van Laethem 2019-04-13 17:13:16 +02:00 коммит произвёл Ron Evans
родитель 02ecab833f
коммит 7de3d4be2b
7 изменённых файлов: 104 добавлений и 84 удалений

Просмотреть файл

@ -4,7 +4,6 @@ package compiler
// form, optimizing them in the process. // form, optimizing them in the process.
// //
// During SSA construction, the following pseudo-calls are created: // During SSA construction, the following pseudo-calls are created:
// runtime.makeInterface(typecode, methodSet)
// runtime.typeAssert(typecode, assertedType) // runtime.typeAssert(typecode, assertedType)
// runtime.interfaceImplements(typecode, interfaceMethodSet) // runtime.interfaceImplements(typecode, interfaceMethodSet)
// runtime.interfaceMethod(typecode, interfaceMethodSet, signature) // runtime.interfaceMethod(typecode, interfaceMethodSet, signature)
@ -14,16 +13,13 @@ package compiler
// //
// This pass lowers the above functions to their final form: // This pass lowers the above functions to their final form:
// //
// makeInterface:
// Replaced with a constant typecode.
//
// typeAssert: // typeAssert:
// Replaced with an icmp instruction so it can be directly used in a type // Replaced with an icmp instruction so it can be directly used in a type
// switch. This is very easy to optimize for LLVM: it will often translate a // switch. This is very easy to optimize for LLVM: it will often translate a
// type switch into a regular switch statement. // type switch into a regular switch statement.
// When this type assert is not possible (the type is never used in an // When this type assert is not possible (the type is never used in an
// interface with makeInterface), this call is replaced with a constant // interface), this call is replaced with a constant false to optimize the
// false to optimize the type assert away completely. // type assert away completely.
// //
// interfaceImplements: // interfaceImplements:
// This call is translated into a call that checks whether the underlying // This call is translated into a call that checks whether the underlying
@ -166,25 +162,36 @@ func (c *Compiler) LowerInterfaces() {
// run runs the pass itself. // run runs the pass itself.
func (p *lowerInterfacesPass) run() { func (p *lowerInterfacesPass) run() {
// Count per type how often it is put in an interface. Also, collect all // Collect all type codes.
// methods this type has (if it is named). typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0)
makeInterface := p.mod.NamedFunction("runtime.makeInterface") typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0)
makeInterfaceUses := getUses(makeInterface) var typesInInterfaces []llvm.Value
for _, use := range makeInterfaceUses { for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) {
typecode := use.Operand(0) switch global.Type() {
name := typecode.Name() case typecodeIDPtr:
if t, ok := p.types[name]; !ok { // Retrieve Go type information based on an opaque global variable.
// This is the first time this type has been seen, add it to the // Only the name of the global is relevant, the object itself is
// list of types. // discarded afterwards.
t = p.addType(typecode) name := global.Name()
p.addTypeMethods(t, use.Operand(1)) t := &typeInfo{
} else { name: name,
p.addTypeMethods(t, use.Operand(1)) typecode: global,
} }
p.types[name] = t
case typeInInterfacePtr:
// Count per type how often it is put in an interface. Also, collect
// all methods this type has (if it is named).
typesInInterfaces = append(typesInInterfaces, global)
initializer := global.Initializer()
typecode := llvm.ConstExtractValue(initializer, []uint32{0})
methodSet := llvm.ConstExtractValue(initializer, []uint32{1})
t := p.types[typecode.Name()]
p.addTypeMethods(t, methodSet)
// Count the number of MakeInterface instructions, for sorting the // Count the number of MakeInterface instructions, for sorting the
// typecodes later. // typecodes later.
p.types[name].countMakeInterfaces++ t.countMakeInterfaces += len(getUses(global))
}
} }
// Count per type how often it is type asserted on (e.g. in a switch // Count per type how often it is type asserted on (e.g. in a switch
@ -194,9 +201,6 @@ func (p *lowerInterfacesPass) run() {
for _, use := range typeAssertUses { for _, use := range typeAssertUses {
typecode := use.Operand(1) typecode := use.Operand(1)
name := typecode.Name() name := typecode.Name()
if _, ok := p.types[name]; !ok {
p.addType(typecode)
}
p.types[name].countTypeAsserts++ p.types[name].countTypeAsserts++
} }
@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() {
typecode := use.Operand(0) typecode := use.Operand(0)
signature := p.signatures[use.Operand(2).Name()] signature := p.signatures[use.Operand(2).Name()]
// If the interface was created in the same function, we can insert a
// direct call. This may not happen often but it is an easy
// optimization so let's do it anyway.
if !typecode.IsACallInst().IsNil() && typecode.CalledValue() == makeInterface {
name := typecode.Operand(0).Name()
typ := p.types[name]
p.replaceInvokeWithCall(use, typ, signature)
continue
}
methodSet := use.Operand(1).Operand(0) // global variable methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()] itf := p.interfaces[methodSet.Name()]
if len(itf.types) == 0 { if len(itf.types) == 0 {
@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() {
// types, if possible. // types, if possible.
for _, use := range interfaceImplementsUses { for _, use := range interfaceImplementsUses {
actualType := use.Operand(0) actualType := use.Operand(0)
if !actualType.IsACallInst().IsNil() && actualType.CalledValue() == makeInterface {
// Type assert is in the same function that creates the interface
// value. This means the underlying type is already known so match
// on that.
// This may not happen often but it is an easy optimization.
name := actualType.Operand(0).Name()
typ := p.types[name]
p.builder.SetInsertPointBefore(use)
assertedType := p.builder.CreatePtrToInt(typ.typecode, p.uintptrType, "typeassert.typecode")
commaOk := p.builder.CreateICmp(llvm.IntEQ, assertedType, actualType, "typeassert.ok")
use.ReplaceAllUsesWith(commaOk)
use.EraseFromParentAsInstruction()
continue
}
methodSet := use.Operand(1).Operand(0) // global variable methodSet := use.Operand(1).Operand(0) // global variable
itf := p.interfaces[methodSet.Name()] itf := p.interfaces[methodSet.Name()]
@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() {
// Assign a type code for each type. // Assign a type code for each type.
p.assignTypeCodes(typeSlice) p.assignTypeCodes(typeSlice)
// Replace each call to runtime.makeInterface with the constant type code. // Replace each use of a runtime.typeInInterface with the constant type
for _, use := range makeInterfaceUses { // code.
global := use.Operand(0) for _, global := range typesInInterfaces {
t := p.types[global.Name()] for _, use := range getUses(global) {
use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType)) t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()]
use.EraseFromParentAsInstruction() typecode := llvm.ConstInt(p.uintptrType, t.num, false)
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
@ -474,19 +456,6 @@ func (p *lowerInterfacesPass) run() {
} }
} }
// addType retrieves Go type information based on a i16 global variable.
// Only the name of the i16 is relevant, the object itself is const-propagated
// and discared afterwards.
func (p *lowerInterfacesPass) addType(typecode llvm.Value) *typeInfo {
name := typecode.Name()
t := &typeInfo{
name: name,
typecode: typecode,
}
p.types[name] = t
return t
}
// addTypeMethods reads the method set of the given type info struct. It // addTypeMethods reads the method set of the given type info struct. It
// retrieves the signatures and the references to the method functions // retrieves the signatures and the references to the method functions
// themselves for later type<->interface matching. // themselves for later type<->interface matching.

Просмотреть файл

@ -59,7 +59,15 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token.
if err != nil { if err != nil {
return llvm.Value{}, nil return llvm.Value{}, nil
} }
itfTypeCode := c.createRuntimeCall("makeInterface", []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}, "makeinterface.typecode") itfConcreteTypeGlobal := c.mod.NamedGlobal("typeInInterface:" + itfTypeCodeGlobal.Name())
if itfConcreteTypeGlobal.IsNil() {
typeInInterface := c.mod.GetTypeByName("runtime.typeInInterface")
itfConcreteTypeGlobal = llvm.AddGlobal(c.mod, typeInInterface, "typeInInterface:"+itfTypeCodeGlobal.Name())
itfConcreteTypeGlobal.SetInitializer(llvm.ConstNamedStruct(typeInInterface, []llvm.Value{itfTypeCodeGlobal, itfMethodSetGlobal}))
itfConcreteTypeGlobal.SetGlobalConstant(true)
itfConcreteTypeGlobal.SetLinkage(llvm.PrivateLinkage)
}
itfTypeCode := c.builder.CreatePtrToInt(itfConcreteTypeGlobal, c.uintptrType, "")
itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface")) itf := llvm.Undef(c.mod.GetTypeByName("runtime._interface"))
itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "") itf = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "")
itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") itf = c.builder.CreateInsertValue(itf, itfValue, 1, "")
@ -73,7 +81,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value {
globalName := "type:" + getTypeCodeName(typ) globalName := "type:" + getTypeCodeName(typ)
global := c.mod.NamedGlobal(globalName) global := c.mod.NamedGlobal(globalName)
if global.IsNil() { if global.IsNil() {
global = llvm.AddGlobal(c.mod, c.ctx.Int8Type(), globalName) global = llvm.AddGlobal(c.mod, c.mod.GetTypeByName("runtime.typecodeID"), globalName)
global.SetGlobalConstant(true) global.SetGlobalConstant(true)
} }
return global return global

Просмотреть файл

@ -307,9 +307,44 @@ func (fr *frame) evalBasicBlock(bb, incoming llvm.BasicBlock, indent string) (re
ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len ret = llvm.ConstInsertValue(ret, retLen, []uint32{1}) // len
ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap ret = llvm.ConstInsertValue(ret, retLen, []uint32{2}) // cap
fr.locals[inst] = &LocalValue{fr.Eval, ret} fr.locals[inst] = &LocalValue{fr.Eval, ret}
case callee.Name() == "runtime.makeInterface": case callee.Name() == "runtime.interfaceImplements":
uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8) typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)} interfaceMethodSet := fr.getLocal(inst.Operand(1)).(*LocalValue).Underlying
if typecode.IsAConstantExpr().IsNil() || typecode.Opcode() != llvm.PtrToInt {
panic("interp: expected typecode to be a ptrtoint")
}
typecode = typecode.Operand(0)
if interfaceMethodSet.IsAConstantExpr().IsNil() || interfaceMethodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set in runtime.interfaceImplements to be a constant gep")
}
interfaceMethodSet = interfaceMethodSet.Operand(0).Initializer()
methodSet := llvm.ConstExtractValue(typecode.Initializer(), []uint32{1})
if methodSet.IsAConstantExpr().IsNil() || methodSet.Opcode() != llvm.GetElementPtr {
panic("interp: expected method set to be a constant gep")
}
methodSet = methodSet.Operand(0).Initializer()
// Make a set of all the methods on the concrete type, for
// easier checking in the next step.
definedMethods := map[string]struct{}{}
for i := 0; i < methodSet.Type().ArrayLength(); i++ {
methodInfo := llvm.ConstExtractValue(methodSet, []uint32{uint32(i)})
name := llvm.ConstExtractValue(methodInfo, []uint32{0}).Name()
definedMethods[name] = struct{}{}
}
// Check whether all interface methods are also in the list
// of defined methods calculated above.
implements := uint64(1) // i1 true
for i := 0; i < interfaceMethodSet.Type().ArrayLength(); i++ {
name := llvm.ConstExtractValue(interfaceMethodSet, []uint32{uint32(i)}).Name()
if _, ok := definedMethods[name]; !ok {
// There is a method on the interface that is not
// implemented by the type.
implements = 0 // i1 false
break
}
}
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int1Type(), implements, false)}
case callee.Name() == "runtime.nanotime": case callee.Name() == "runtime.nanotime":
fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)} fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstInt(fr.Mod.Context().Int64Type(), 0, false)}
case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic": case strings.HasPrefix(callee.Name(), "runtime.print") || callee.Name() == "runtime._panic":

Просмотреть файл

@ -33,6 +33,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
return &sideEffectResult{severity: sideEffectNone} return &sideEffectResult{severity: sideEffectNone}
case "runtime._panic": case "runtime._panic":
return &sideEffectResult{severity: sideEffectLimited} return &sideEffectResult{severity: sideEffectLimited}
case "runtime.interfaceImplements":
return &sideEffectResult{severity: sideEffectNone}
} }
if e.sideEffectFuncs == nil { if e.sideEffectFuncs == nil {
e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult) e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult)
@ -84,11 +86,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult {
continue continue
} }
if child.IsDeclaration() { if child.IsDeclaration() {
switch child.Name() {
case "runtime.makeInterface":
// Can be interpreted so does not have side effects.
continue
}
// External function call. Assume only limited side effects // External function call. Assume only limited side effects
// (no affected globals, etc.). // (no affected globals, etc.).
if e.hasLocalSideEffects(dirtyLocals, inst) { if e.hasLocalSideEffects(dirtyLocals, inst) {

Просмотреть файл

@ -43,15 +43,22 @@ type interfaceMethodInfo struct {
funcptr uintptr // bitcast from the actual function pointer funcptr uintptr // bitcast from the actual function pointer
} }
// Pseudo function call used while putting a concrete value in an interface, type typecodeID struct{}
// that must be lowered to a constant uintptr.
func makeInterface(typecode *uint8, methodSet *interfaceMethodInfo) uintptr // Pseudo type used before interface lowering. By using a struct instead of a
// function call, this is simpler to reason about during init interpretation
// than a function call. Also, by keeping the method set around it is easier to
// implement interfaceImplements in the interp package.
type typeInInterface struct {
typecode *typecodeID
methodSet *interfaceMethodInfo // nil or a GEP of an array
}
// Pseudo function call used during a type assert. It is used during interface // 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 // 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 // asserts. Also, it is replaced with const false if this type assert can never
// happen. // happen.
func typeAssert(actualType uintptr, assertedType *uint8) bool func typeAssert(actualType uintptr, assertedType *typecodeID) bool
// Pseudo function call that returns whether a given type implements all methods // Pseudo function call that returns whether a given type implements all methods
// of the given interface. // of the given interface.

3
testdata/stdlib.go предоставленный
Просмотреть файл

@ -2,6 +2,7 @@ package main
import ( import (
"fmt" "fmt"
"math/rand"
"os" "os"
) )
@ -9,4 +10,6 @@ func main() {
fmt.Println("stdin: ", os.Stdin.Fd()) fmt.Println("stdin: ", os.Stdin.Fd())
fmt.Println("stdout:", os.Stdout.Fd()) fmt.Println("stdout:", os.Stdout.Fd())
fmt.Println("stderr:", os.Stderr.Fd()) fmt.Println("stderr:", os.Stderr.Fd())
fmt.Println("pseudorandom number:", rand.Int31())
} }

1
testdata/stdlib.txt предоставленный
Просмотреть файл

@ -1,3 +1,4 @@
stdin: 0 stdin: 0
stdout: 1 stdout: 1
stderr: 2 stderr: 2
pseudorandom number: 1298498081