all: support interface asserts in interp
This adds support for the math/rand package.
Этот коммит содержится в:
родитель
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
предоставленный
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
testdata/stdlib.txt
предоставленный
|
@ -1,3 +1,4 @@
|
||||||
stdin: 0
|
stdin: 0
|
||||||
stdout: 1
|
stdout: 1
|
||||||
stderr: 2
|
stderr: 2
|
||||||
|
pseudorandom number: 1298498081
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче