diff --git a/compiler/interface-lowering.go b/compiler/interface-lowering.go index 73623709..604521ee 100644 --- a/compiler/interface-lowering.go +++ b/compiler/interface-lowering.go @@ -4,7 +4,6 @@ package compiler // form, optimizing them in the process. // // During SSA construction, the following pseudo-calls are created: -// runtime.makeInterface(typecode, methodSet) // runtime.typeAssert(typecode, assertedType) // runtime.interfaceImplements(typecode, interfaceMethodSet) // runtime.interfaceMethod(typecode, interfaceMethodSet, signature) @@ -14,16 +13,13 @@ package compiler // // This pass lowers the above functions to their final form: // -// makeInterface: -// Replaced with a constant typecode. -// // typeAssert: // 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 // type switch into a regular switch statement. // When this type assert is not possible (the type is never used in an -// interface with makeInterface), this call is replaced with a constant -// false to optimize the type assert away completely. +// interface), this call is replaced with a constant false to optimize the +// type assert away completely. // // interfaceImplements: // 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. func (p *lowerInterfacesPass) run() { - // Count per type how often it is put in an interface. Also, collect all - // methods this type has (if it is named). - makeInterface := p.mod.NamedFunction("runtime.makeInterface") - makeInterfaceUses := getUses(makeInterface) - for _, use := range makeInterfaceUses { - typecode := use.Operand(0) - name := typecode.Name() - if t, ok := p.types[name]; !ok { - // This is the first time this type has been seen, add it to the - // list of types. - t = p.addType(typecode) - p.addTypeMethods(t, use.Operand(1)) - } else { - p.addTypeMethods(t, use.Operand(1)) - } + // Collect all type codes. + typecodeIDPtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typecodeID"), 0) + typeInInterfacePtr := llvm.PointerType(p.mod.GetTypeByName("runtime.typeInInterface"), 0) + var typesInInterfaces []llvm.Value + for global := p.mod.FirstGlobal(); !global.IsNil(); global = llvm.NextGlobal(global) { + switch global.Type() { + case typecodeIDPtr: + // Retrieve Go type information based on an opaque global variable. + // Only the name of the global is relevant, the object itself is + // discarded afterwards. + name := global.Name() + t := &typeInfo{ + name: name, + 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 - // typecodes later. - p.types[name].countMakeInterfaces++ + // Count the number of MakeInterface instructions, for sorting the + // typecodes later. + t.countMakeInterfaces += len(getUses(global)) + } } // 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 { typecode := use.Operand(1) name := typecode.Name() - if _, ok := p.types[name]; !ok { - p.addType(typecode) - } p.types[name].countTypeAsserts++ } @@ -286,16 +290,6 @@ func (p *lowerInterfacesPass) run() { typecode := use.Operand(0) 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 itf := p.interfaces[methodSet.Name()] if len(itf.types) == 0 { @@ -356,20 +350,6 @@ func (p *lowerInterfacesPass) run() { // types, if possible. for _, use := range interfaceImplementsUses { 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 itf := p.interfaces[methodSet.Name()] @@ -416,12 +396,14 @@ func (p *lowerInterfacesPass) run() { // Assign a type code for each type. p.assignTypeCodes(typeSlice) - // Replace each call to runtime.makeInterface with the constant type code. - for _, use := range makeInterfaceUses { - global := use.Operand(0) - t := p.types[global.Name()] - use.ReplaceAllUsesWith(llvm.ConstPtrToInt(t.typecode, p.uintptrType)) - use.EraseFromParentAsInstruction() + // Replace each use of a runtime.typeInInterface with the constant type + // code. + for _, global := range typesInInterfaces { + for _, use := range getUses(global) { + t := p.types[llvm.ConstExtractValue(global.Initializer(), []uint32{0}).Name()] + typecode := llvm.ConstInt(p.uintptrType, t.num, false) + use.ReplaceAllUsesWith(typecode) + } } // 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 // retrieves the signatures and the references to the method functions // themselves for later type<->interface matching. diff --git a/compiler/interface.go b/compiler/interface.go index f39bc105..c6cbad5c 100644 --- a/compiler/interface.go +++ b/compiler/interface.go @@ -59,7 +59,15 @@ func (c *Compiler) parseMakeInterface(val llvm.Value, typ types.Type, pos token. if err != 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 = c.builder.CreateInsertValue(itf, itfTypeCode, 0, "") itf = c.builder.CreateInsertValue(itf, itfValue, 1, "") @@ -73,7 +81,7 @@ func (c *Compiler) getTypeCode(typ types.Type) llvm.Value { globalName := "type:" + getTypeCodeName(typ) global := c.mod.NamedGlobal(globalName) 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) } return global diff --git a/interp/frame.go b/interp/frame.go index f587589a..18631eea 100644 --- a/interp/frame.go +++ b/interp/frame.go @@ -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{2}) // cap fr.locals[inst] = &LocalValue{fr.Eval, ret} - case callee.Name() == "runtime.makeInterface": - uintptrType := callee.Type().Context().IntType(fr.TargetData.PointerSize() * 8) - fr.locals[inst] = &LocalValue{fr.Eval, llvm.ConstPtrToInt(inst.Operand(0), uintptrType)} + case callee.Name() == "runtime.interfaceImplements": + typecode := fr.getLocal(inst.Operand(0)).(*LocalValue).Underlying + 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": 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": diff --git a/interp/scan.go b/interp/scan.go index 6b37b604..d388aa52 100644 --- a/interp/scan.go +++ b/interp/scan.go @@ -33,6 +33,8 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult { return &sideEffectResult{severity: sideEffectNone} case "runtime._panic": return &sideEffectResult{severity: sideEffectLimited} + case "runtime.interfaceImplements": + return &sideEffectResult{severity: sideEffectNone} } if e.sideEffectFuncs == nil { e.sideEffectFuncs = make(map[llvm.Value]*sideEffectResult) @@ -84,11 +86,6 @@ func (e *Eval) hasSideEffects(fn llvm.Value) *sideEffectResult { continue } 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 // (no affected globals, etc.). if e.hasLocalSideEffects(dirtyLocals, inst) { diff --git a/src/runtime/interface.go b/src/runtime/interface.go index 61d3114d..7f940ae6 100644 --- a/src/runtime/interface.go +++ b/src/runtime/interface.go @@ -43,15 +43,22 @@ type interfaceMethodInfo struct { funcptr uintptr // 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 +type typecodeID struct{} + +// 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 // 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 +func typeAssert(actualType uintptr, assertedType *typecodeID) bool // Pseudo function call that returns whether a given type implements all methods // of the given interface. diff --git a/testdata/stdlib.go b/testdata/stdlib.go index 29c9c9e1..2996cdb2 100644 --- a/testdata/stdlib.go +++ b/testdata/stdlib.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "math/rand" "os" ) @@ -9,4 +10,6 @@ func main() { fmt.Println("stdin: ", os.Stdin.Fd()) fmt.Println("stdout:", os.Stdout.Fd()) fmt.Println("stderr:", os.Stderr.Fd()) + + fmt.Println("pseudorandom number:", rand.Int31()) } diff --git a/testdata/stdlib.txt b/testdata/stdlib.txt index 3db2f187..27f48238 100644 --- a/testdata/stdlib.txt +++ b/testdata/stdlib.txt @@ -1,3 +1,4 @@ stdin: 0 stdout: 1 stderr: 2 +pseudorandom number: 1298498081