// visitFunc analyses function body. func visitFunc(fn *ssa.Function, infer *TypeInfer, f *Function) { infer.Env.MigoProg.AddFunction(f.FuncDef) infer.Logger.Printf(f.Sprintf(FuncEnterSymbol+"───── func %s ─────", fn.Name())) defer infer.Logger.Printf(f.Sprintf(FuncExitSymbol+"───── func %s ─────", fn.Name())) if fn.Name() == "init" { if _, ok := f.Prog.InitPkgs[fn.Package()]; !ok { f.Prog.InitPkgs[fn.Package()] = true } f.hasBody = true return } for val, instance := range f.locals { infer.Logger.Printf(f.Sprintf(ParamSymbol+"%s = %s", val.Name(), instance)) f.revlookup[instance.String()] = val.Name() // If it comes from params.. } if fn.Blocks == nil { infer.Logger.Print(f.Sprintf(MoreSymbol + "« no function body »")) f.hasBody = false // No body return } // When entering function, always visit as block 0 block0 := NewBlock(f, fn.Blocks[0], 0) visitBasicBlock(fn.Blocks[0], infer, f, block0, &Loop{Parent: f}) f.hasBody = true }
// CheckNames exists because of this comment in the SSA code documentation: // "Many objects are nonetheless named to aid in debugging, but it is not essential that the names be either accurate or unambiguous. " func CheckNames(f *ssa.Function) error { names := make(map[string]*ssa.Instruction) //fmt.Println("DEBUG Check Names for ", f.String()) for blk := range f.Blocks { for ins := range f.Blocks[blk].Instrs { instrVal, hasVal := f.Blocks[blk].Instrs[ins].(ssa.Value) if hasVal { register := instrVal.Name() //fmt.Println("DEBUG name ", register) val, found := names[register] if found { if val != &f.Blocks[blk].Instrs[ins] { return fmt.Errorf("internal error, ssa register names not unique in function %s var name %s", f.String(), register) } } else { names[register] = &f.Blocks[blk].Instrs[ins] } } } } return nil }
// findIntrinsic returns the constraint generation function for an // intrinsic function fn, or nil if the function should be handled normally. // func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic { // Consult the *Function-keyed cache. // A cached nil indicates a normal non-intrinsic function. impl, ok := a.intrinsics[fn] if !ok { impl = intrinsicsByName[fn.String()] // may be nil if a.isReflect(fn) { if !a.config.Reflection { impl = ext۰NoEffect // reflection disabled } else if impl == nil { // Ensure all "reflect" code is treated intrinsically. impl = ext۰NotYetImplemented } } else if impl == nil && fn.Pkg != nil && fn.Pkg.Pkg.Path() == "runtime" { // Ignore "runtime" (except SetFinalizer): // it has few interesting effects on aliasing // and is full of unsafe code we can't analyze. impl = ext۰NoEffect } a.intrinsics[fn] = impl } return impl }
// Switches examines the control-flow graph of fn and returns the // set of inferred value and type switches. A value switch tests an // ssa.Value for equality against two or more compile-time constant // values. Switches involving link-time constants (addresses) are // ignored. A type switch type-asserts an ssa.Value against two or // more types. // // The switches are returned in dominance order. // // The resulting switches do not necessarily correspond to uses of the // 'switch' keyword in the source: for example, a single source-level // switch statement with non-constant cases may result in zero, one or // many Switches, one per plural sequence of constant cases. // Switches may even be inferred from if/else- or goto-based control flow. // (In general, the control flow constructs of the source program // cannot be faithfully reproduced from the SSA representation.) // func Switches(fn *ssa.Function) []Switch { // Traverse the CFG in dominance order, so we don't // enter an if/else-chain in the middle. var switches []Switch seen := make(map[*ssa.BasicBlock]bool) // TODO(adonovan): opt: use ssa.blockSet for _, b := range fn.DomPreorder() { if x, k := isComparisonBlock(b); x != nil { // Block b starts a switch. sw := Switch{Start: b, X: x} valueSwitch(&sw, k, seen) if len(sw.ConstCases) > 1 { switches = append(switches, sw) } } if y, x, T := isTypeAssertBlock(b); y != nil { // Block b starts a type switch. sw := Switch{Start: b, X: x} typeSwitch(&sw, y, T, seen) if len(sw.TypeCases) > 1 { switches = append(switches, sw) } } } return switches }
// Emit the start of a function. func emitFuncStart(fn *ssa.Function, blks []*ssa.BasicBlock, trackPhi bool, canOptMap map[string]bool, mustSplitCode bool, reconstruct []tgossa.BlockFormat) { l := TargetLang posStr := CodePosition(fn.Pos()) pName, mName := GetFnNameParts(fn) isPublic := unicode.IsUpper(rune(mName[0])) // TODO check rules for non-ASCII 1st characters and fix fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].FuncStart(pName, mName, fn, blks, posStr, isPublic, trackPhi, grMap[fn] || mustSplitCode, canOptMap, reconstruct)) }
func findCallSite(fn *ssa.Function, call *ast.CallExpr) (ssa.CallInstruction, error) { instr, _ := fn.ValueForExpr(call) callInstr, _ := instr.(ssa.CallInstruction) if instr == nil { return nil, fmt.Errorf("this call site is unreachable in this analysis") } return callInstr, nil }
func (fa *FairnessAnalysis) Visit(fn *ssa.Function) { visitedBlk := make(map[*ssa.BasicBlock]bool) fa.logger.Printf("Visiting: %s", fn.String()) for _, blk := range fn.Blocks { if _, visited := visitedBlk[blk]; !visited { visitedBlk[blk] = true fa.logger.Printf(" block %d %s", blk.Index, blk.Comment) // First consider blocks with loop initialisation blocks. if blk.Comment == "rangeindex.loop" { fa.total++ fa.logger.Println(color.GreenString("✓ range loops are fair")) } else if blk.Comment == "rangechan.loop" { fa.total++ hasClose := false for _, ch := range fa.info.FindChan(blk.Instrs[0].(*ssa.UnOp).X) { if ch.Type == ssabuilder.ChanClose { fa.logger.Println(color.GreenString("✓ found corresponding close() - channel range likely fair")) hasClose = true } } if !hasClose { fa.logger.Println(color.RedString("❌ range over channel w/o close() likely unfair (%s)", fa.info.FSet.Position(blk.Instrs[0].Pos()))) fa.unsafe++ } } else if blk.Comment == "for.loop" { fa.total++ if fa.isLikelyUnsafe(blk) { fa.logger.Println(color.RedString("❌ for.loop maybe bad")) fa.unsafe++ } else { fa.logger.Println(color.GreenString("✓ for.loop is ok")) } } else { // Normal blocks (or loops without initialisation blocks). if len(blk.Instrs) > 1 { if ifInst, ok := blk.Instrs[len(blk.Instrs)-1].(*ssa.If); ok { _, thenVisited := visitedBlk[ifInst.Block().Succs[0]] _, elseVisited := visitedBlk[ifInst.Block().Succs[1]] if thenVisited || elseVisited { // there is a loop! fa.total++ if !fa.isCondFair(ifInst.Cond) { fa.logger.Println(color.YellowString("Warning: recurring block condition probably unfair")) fa.unsafe++ } else { fa.logger.Println(color.GreenString("✓ recurring block is ok")) } } } else if jInst, ok := blk.Instrs[len(blk.Instrs)-1].(*ssa.Jump); ok { if _, visited := visitedBlk[jInst.Block().Succs[0]]; visited { fa.total++ fa.unsafe++ fa.logger.Println(color.RedString("❌ infinite loop or recurring block, probably bad (%s)", fa.info.FSet.Position(blk.Instrs[0].Pos()))) } } } } } } }
// prepareCallFn prepares a caller Function to visit performing necessary context switching and returns a new callee Function. // rcvr is non-nil if invoke call func (caller *Function) prepareCallFn(common *ssa.CallCommon, fn *ssa.Function, rcvr ssa.Value) *Function { callee := NewFunction(caller) callee.Fn = fn // This function was called before if _, ok := callee.Prog.FuncInstance[callee.Fn]; ok { callee.Prog.FuncInstance[callee.Fn]++ } else { callee.Prog.FuncInstance[callee.Fn] = 0 } callee.FuncDef.Name = fn.String() callee.id = callee.Prog.FuncInstance[callee.Fn] for i, param := range callee.Fn.Params { var argCaller ssa.Value if rcvr != nil { if i == 0 { argCaller = rcvr } else { argCaller = common.Args[i-1] } } else { argCaller = common.Args[i] } if _, ok := argCaller.Type().(*types.Chan); ok { callee.FuncDef.AddParams(&migo.Parameter{Caller: argCaller, Callee: param}) } if inst, ok := caller.locals[argCaller]; ok { callee.locals[param] = inst callee.revlookup[argCaller.Name()] = param.Name() // Copy array and struct from parent. if elems, ok := caller.arrays[inst]; ok { callee.arrays[inst] = elems } if fields, ok := caller.structs[inst]; ok { callee.structs[inst] = fields } if maps, ok := caller.maps[inst]; ok { callee.maps[inst] = maps } } else if c, ok := argCaller.(*ssa.Const); ok { callee.locals[param] = &Const{c} } } if inst, ok := caller.locals[common.Value]; ok { if cap, ok := caller.Prog.closures[inst]; ok { for i, fv := range callee.Fn.FreeVars { callee.locals[fv] = cap[i] if _, ok := derefType(fv.Type()).(*types.Chan); ok { callee.FuncDef.AddParams(&migo.Parameter{Caller: fv, Callee: fv}) } } } } return callee }
// FunctionName returns a unique function path and name. // TODO refactor this code and everywhere it is called to remove duplication. func FuncPathName(fn *ssa.Function) (path, name string) { rx := fn.Signature.Recv() pf := MakeID(rootProgram.Fset.Position(fn.Pos()).String()) //fmt.Sprintf("fn%d", fn.Pos()) if rx != nil { // it is not the name of a normal function, but that of a method, so append the method description pf = rx.Type().String() // NOTE no underlying() } else { if fn.Pkg != nil { pf = fn.Pkg.Object.Path() // was .Name(), but not unique } } return pf, fn.Name() }
func GetFnNameParts(fn *ssa.Function) (pack, nam string) { mName := fn.Name() pName, _ := FuncPathName(fn) //fmt.Sprintf("fn%d", fn.Pos()) //uintptr(unsafe.Pointer(fn))) if fn.Pkg != nil { if fn.Pkg.Object != nil { pName = fn.Pkg.Object.Path() // was .Name() } } if fn.Signature.Recv() != nil { // we have a method pName = fn.Signature.Recv().Pkg().Name() + ":" + fn.Signature.Recv().Type().String() // note no underlying() //pName = LanguageList[l].PackageOverloadReplace(pName) } return pName, mName }
func getFnPath(fn *ssa.Function) string { if fn == nil { //println("DEBUG getFnPath nil function") return "" } ob := fn.Object() if ob == nil { //println("DEBUG getFnPath nil object: name,synthetic=", fn.Name(), ",", fn.Synthetic) return "" } pk := ob.Pkg() if pk == nil { //println("DEBUG getFnPath nil package: name,synthetic=", fn.Name(), ",", fn.Synthetic) return "" } return pk.Path() }
// FuncPathName returns a unique function path and name. func (comp *Compilation) FuncPathName(fn *ssa.Function) (path, name string) { rx := fn.Signature.Recv() pf := tgoutil.MakeID(comp.rootProgram.Fset.Position(fn.Pos()).String()) //fmt.Sprintf("fn%d", fn.Pos()) if rx != nil { // it is not the name of a normal function, but that of a method, so append the method description pf = rx.Type().String() // NOTE no underlying() } else { if fn.Pkg != nil { pf = fn.Pkg.Object.Path() // was .Name(), but not unique } else { goroot := tgoutil.MakeID(LanguageList[comp.TargetLang].GOROOT + string(os.PathSeparator)) pf1 := strings.Split(pf, goroot) // make auto-generated names shorter if len(pf1) == 2 { pf = pf1[1] } // TODO use GOPATH for names not in std pkgs } } return pf, fn.Name() }
func funcToken(fn *ssa.Function) token.Pos { switch syntax := fn.Syntax().(type) { case *ast.FuncLit: return syntax.Type.Func case *ast.FuncDecl: return syntax.Type.Func } return token.NoPos }
// findIntrinsic returns the constraint generation function for an // intrinsic function fn, or nil if the function should be handled normally. // func (a *analysis) findIntrinsic(fn *ssa.Function) intrinsic { // Consult the *Function-keyed cache. // A cached nil indicates a normal non-intrinsic function. impl, ok := a.intrinsics[fn] if !ok { impl = intrinsicsByName[fn.String()] // may be nil if a.isReflect(fn) { if !a.config.Reflection { impl = ext۰NoEffect // reflection disabled } else if impl == nil { // Ensure all "reflect" code is treated intrinsically. impl = ext۰NotYetImplemented } } a.intrinsics[fn] = impl } return impl }
func LowerAllocsToStack(f *ssa.Function) { pending := make([]ssa.Value, 0, 10) for _, b := range f.Blocks { for _, instr := range b.Instrs { if alloc, ok := instr.(*ssa.Alloc); ok && alloc.Heap && !escapes(alloc, alloc.Block(), pending) { alloc.Heap = false f.Locals = append(f.Locals, alloc) } } } }
// prettyFunc pretty-prints fn for the user interface. // TODO(adonovan): return HTML so we have more markup freedom. func prettyFunc(this *types.Package, fn *ssa.Function) string { if fn.Parent() != nil { return fmt.Sprintf("%s in %s", types.TypeString(fn.Signature, types.RelativeTo(this)), prettyFunc(this, fn.Parent())) } if fn.Synthetic != "" && fn.Name() == "init" { // (This is the actual initializer, not a declared 'func init'). if fn.Pkg.Pkg == this { return "package initializer" } return fmt.Sprintf("%q package initializer", fn.Pkg.Pkg.Path()) } return fn.RelString(this) }
func (u *unit) getFunctionLinkage(f *ssa.Function) llvm.Linkage { switch { case f.Pkg == nil: // Synthetic functions outside packages may appear in multiple packages. return llvm.LinkOnceODRLinkage case f.Parent() != nil: // Anonymous. return llvm.InternalLinkage case f.Signature.Recv() == nil && !ast.IsExported(f.Name()) && !(f.Name() == "main" && f.Pkg.Object.Path() == "main") && f.Name() != "init": // Unexported methods may be referenced as part of an interface method // table in another package. TODO(pcc): detect when this cannot happen. return llvm.InternalLinkage default: return llvm.ExternalLinkage } }
// callSSA interprets a call to function fn with arguments args, // and lexical environment env, returning its result. // callpos is the position of the callsite. // func callSSA(i *interpreter, caller *frame, callpos token.Pos, fn *ssa.Function, args []value, env []value) value { if i.mode&EnableTracing != 0 { fset := fn.Prog.Fset // TODO(adonovan): fix: loc() lies for external functions. fmt.Fprintf(os.Stderr, "Entering %s%s.\n", fn, loc(fset, fn.Pos())) suffix := "" if caller != nil { suffix = ", resuming " + caller.fn.String() + loc(fset, callpos) } defer fmt.Fprintf(os.Stderr, "Leaving %s%s.\n", fn, suffix) } fr := &frame{ i: i, caller: caller, // for panic/recover fn: fn, } if fn.Parent() == nil { name := fn.String() if ext := externals[name]; ext != nil { if i.mode&EnableTracing != 0 { fmt.Fprintln(os.Stderr, "\t(external)") } return ext(fr, args) } if fn.Blocks == nil { panic("no code for function: " + name) } } fr.env = make(map[ssa.Value]value) fr.block = fn.Blocks[0] fr.locals = make([]value, len(fn.Locals)) for i, l := range fn.Locals { fr.locals[i] = zero(deref(l.Type())) fr.env[l] = &fr.locals[i] } for i, p := range fn.Params { fr.env[p] = args[i] } for i, fv := range fn.FreeVars { fr.env[fv] = env[i] } for fr.block != nil { runFrame(fr) } // Destroy the locals to avoid accidental use after return. for i := range fn.Locals { fr.locals[i] = bad{} } return fr.result }
func checkFn(fn *ssa.Function, prog *ssa.Program, ptrResult *pointer.Result, roots []*ssa.Function) { nam := fn.String() if strings.HasPrefix(strings.TrimPrefix(nam, "("), *prefix) { hasPath := false usedExternally := false for _, r := range roots { if r != nil { nod, ok := ptrResult.CallGraph.Nodes[r] if ok { //fmt.Println("NODE root", r.Name()) pth := callgraph.PathSearch(nod, func(n *callgraph.Node) bool { if n == nil { return false } if n.Func == fn { for _, ine := range n.In { if ine.Caller.Func.Pkg != fn.Pkg { //fmt.Println("DEBUG diff? ", // ine.Caller.Func.Pkg, fn.Pkg) usedExternally = true break } } return true } return false }) if pth != nil { //fmt.Printf("DEBUG path from %v to %v = %v\n", // r, fn, pth) hasPath = true break } } } } isUpper := unicode.IsUpper(rune(fn.Name()[0])) pos := fn.Pos() //if strings.HasPrefix(nam, "(") && (!hasPath || (!usedExternally && isUpper)) { // fmt.Println("bad Pos", pos, prog.Fset.Position(pos).String()) //} loc := strings.TrimPrefix( prog.Fset.Position(pos).String(), gopath+"/src/"+*prefix+"/") showFuncResult(loc, nam, hasPath, usedExternally, isUpper) } wg.Done() }
func IsOverloaded(f *ssa.Function) bool { pn := "unknown" // Defensive, as some synthetic or other edge-case functions may not have a valid package name rx := f.Signature.Recv() if rx == nil { // ordinary function if f.Pkg != nil { if f.Pkg.Object != nil { pn = f.Pkg.Object.Path() //was .Name() } } else { if f.Object() != nil { if f.Object().Pkg() != nil { pn = f.Object().Pkg().Path() //was .Name() } } } } else { // determine the package information from the type description typ := rx.Type() ts := typ.String() if ts[0] == '*' { ts = ts[1:] } tss := strings.Split(ts, ".") if len(tss) >= 2 { ts = tss[len(tss)-2] // take the part before the final dot } else { ts = tss[0] // no dot! } pn = ts } tss := strings.Split(pn, "/") // TODO check this also works in Windows ts := tss[len(tss)-1] // take the last part of the path pn = ts // TODO this is incorrect, but not currently a problem as there is no function overloading //println("DEBUG package name: " + pn) if LanguageList[TargetLang].FunctionOverloaded(pn, f.Name()) || strings.HasPrefix(pn, "_") { // the package is not in the target language, signaled by a leading underscore and return true } return false }
func (l langType) FuncName(fnx *ssa.Function) string { pn := "" if fnx.Signature.Recv() != nil { pn = fnx.Signature.Recv().Type().String() // NOTE no use of underlying here } else { pn, _ = pogo.FuncPathName(fnx) //fmt.Sprintf("fn%d", fnx.Pos()) fn := ssa.EnclosingFunction(fnx.Package(), []ast.Node{fnx.Syntax()}) if fn == nil { fn = fnx } if fn.Pkg != nil { if fn.Pkg.Object != nil { pn = fn.Pkg.Object.Path() // was .Name() } } else { if fn.Object() != nil { if fn.Object().Pkg() != nil { pn = fn.Object().Pkg().Path() // was .Name() } } } } return l.LangName(pn, fnx.Name()) }
func (u *unit) defineFunction(f *ssa.Function) { // Only define functions from this package, or synthetic // wrappers (which do not have a package). if f.Pkg != nil && f.Pkg != u.pkg { return } llfn := u.resolveFunctionGlobal(f) linkage := u.getFunctionLinkage(f) isMethod := f.Signature.Recv() != nil // Methods cannot be referred to via a descriptor. if !isMethod { llfd := u.resolveFunctionDescriptorGlobal(f) llfd.SetInitializer(llvm.ConstBitCast(llfn, llvm.PointerType(llvm.Int8Type(), 0))) llfd.SetLinkage(linkage) } // We only need to emit a descriptor for functions without bodies. if len(f.Blocks) == 0 { return } ssaopt.LowerAllocsToStack(f) if u.DumpSSA { f.WriteTo(os.Stderr) } fr := newFrame(u, llfn) defer fr.dispose() fr.addCommonFunctionAttrs(fr.function) fr.function.SetLinkage(linkage) fr.logf("Define function: %s", f.String()) fti := u.llvmtypes.getSignatureInfo(f.Signature) delete(u.undefinedFuncs, f) fr.retInf = fti.retInf // Push the compile unit and function onto the debug context. if u.GenerateDebug { u.debug.PushFunction(fr.function, f.Signature, f.Pos()) defer u.debug.PopFunction() u.debug.SetLocation(fr.builder, f.Pos()) } // If a function calls recover, we create a separate function to // hold the real function, and this function calls __go_can_recover // and bridges to it. if callsRecover(f) { fr = fr.bridgeRecoverFunc(fr.function, fti) } fr.blocks = make([]llvm.BasicBlock, len(f.Blocks)) fr.lastBlocks = make([]llvm.BasicBlock, len(f.Blocks)) for i, block := range f.Blocks { fr.blocks[i] = llvm.AddBasicBlock(fr.function, fmt.Sprintf(".%d.%s", i, block.Comment)) } fr.builder.SetInsertPointAtEnd(fr.blocks[0]) prologueBlock := llvm.InsertBasicBlock(fr.blocks[0], "prologue") fr.builder.SetInsertPointAtEnd(prologueBlock) // Map parameter positions to indices. We use this // when processing locals to map back to parameters // when generating debug metadata. paramPos := make(map[token.Pos]int) for i, param := range f.Params { paramPos[param.Pos()] = i llparam := fti.argInfos[i].decode(llvm.GlobalContext(), fr.builder, fr.builder) if isMethod && i == 0 { if _, ok := param.Type().Underlying().(*types.Pointer); !ok { llparam = fr.builder.CreateBitCast(llparam, llvm.PointerType(fr.types.ToLLVM(param.Type()), 0), "") llparam = fr.builder.CreateLoad(llparam, "") } } fr.env[param] = newValue(llparam, param.Type()) } // Load closure, extract free vars. if len(f.FreeVars) > 0 { for _, fv := range f.FreeVars { fr.env[fv] = newValue(llvm.ConstNull(u.llvmtypes.ToLLVM(fv.Type())), fv.Type()) } elemTypes := make([]llvm.Type, len(f.FreeVars)+1) elemTypes[0] = llvm.PointerType(llvm.Int8Type(), 0) // function pointer for i, fv := range f.FreeVars { elemTypes[i+1] = u.llvmtypes.ToLLVM(fv.Type()) } structType := llvm.StructType(elemTypes, false) closure := fr.runtime.getClosure.call(fr)[0] closure = fr.builder.CreateBitCast(closure, llvm.PointerType(structType, 0), "") for i, fv := range f.FreeVars { ptr := fr.builder.CreateStructGEP(closure, i+1, "") ptr = fr.builder.CreateLoad(ptr, "") fr.env[fv] = newValue(ptr, fv.Type()) } } // Allocate stack space for locals in the prologue block. for _, local := range f.Locals { typ := fr.llvmtypes.ToLLVM(deref(local.Type())) alloca := fr.builder.CreateAlloca(typ, local.Comment) fr.memsetZero(alloca, llvm.SizeOf(typ)) bcalloca := fr.builder.CreateBitCast(alloca, llvm.PointerType(llvm.Int8Type(), 0), "") value := newValue(bcalloca, local.Type()) fr.env[local] = value if fr.GenerateDebug { paramIndex, ok := paramPos[local.Pos()] if !ok { paramIndex = -1 } fr.debug.Declare(fr.builder, local, alloca, paramIndex) } } // If this is the "init" function, enable init-specific optimizations. if !isMethod && f.Name() == "init" { fr.isInit = true } // If the function contains any defers, we must first create // an unwind block. We can short-circuit the check for defers with // f.Recover != nil. if f.Recover != nil || hasDefer(f) { fr.unwindBlock = llvm.AddBasicBlock(fr.function, "") fr.frameptr = fr.builder.CreateAlloca(llvm.Int8Type(), "") } term := fr.builder.CreateBr(fr.blocks[0]) fr.allocaBuilder.SetInsertPointBefore(term) for _, block := range f.DomPreorder() { fr.translateBlock(block, fr.blocks[block.Index]) } fr.fixupPhis() if !fr.unwindBlock.IsNil() { fr.setupUnwindBlock(f.Recover, f.Signature.Results()) } // The init function needs to register the GC roots first. We do this // after generating code for it because allocations may have caused // additional GC roots to be created. if fr.isInit { fr.builder.SetInsertPointBefore(prologueBlock.FirstInstruction()) fr.registerGcRoots() } }
func (visit *visitor) function(fn *ssa.Function, isOvl isOverloaded) { if !visit.seen[fn] { // been, exists := visit.seen[fn]; !been || !exists { vprintln("DEBUG 1st visit to: ", fn.String()) visit.seen[fn] = true visit.usesGR[fn] = false if isOvl(fn) { vprintln("DEBUG overloaded: ", fn.String()) return } if len(fn.Blocks) == 0 { // exclude functions that reference C/assembler code // NOTE: not marked as seen, because we don't want to include in output // if used, the symbol will be included in the golibruntime replacement packages // TODO review vprintln("DEBUG no code for: ", fn.String()) return // external functions cannot use goroutines } var buf [10]*ssa.Value // avoid alloc in common case for _, b := range fn.Blocks { for _, instr := range b.Instrs { for _, op := range instr.Operands(buf[:0]) { areRecursing := false afn, isFn := (*op).(*ssa.Function) if isFn { if afn == fn { areRecursing = true } visit.function(afn, isOvl) if visit.usesGR[afn] { vprintln("marked as using GR because referenced func uses GR") visit.usesGR[fn] = true } vprintln(fn.Name(), " calls ", afn.Name()) } // TODO, review if this code should be included if !visit.usesGR[fn] { if _, ok := (*op).(ssa.Value); ok { typ := (*op).Type() typ = DeRefUl(typ) switch typ.(type) { // TODO use oracle techniques to determine which interfaces or functions may require GR case *types.Chan, *types.Interface: visit.usesGR[fn] = true // may be too conservative vprintln("marked as using GR because uses Chan/Interface") case *types.Signature: if !areRecursing { if !isFn { visit.usesGR[fn] = true vprintln("marked as using GR because uses Signature") } } } } } } if _, ok := instr.(*ssa.Call); ok { switch instr.(*ssa.Call).Call.Value.(type) { case *ssa.Builtin: //NoOp default: cc := instr.(*ssa.Call).Common() if cc != nil { afn := cc.StaticCallee() if afn != nil { visit.function(afn, isOvl) if visit.usesGR[afn] { visit.usesGR[fn] = true vprintln("marked as using GR because call target uses GR") } vprintln(fn.Name(), " calls ", afn.Name()) } } } } if !visit.usesGR[fn] { switch instr.(type) { case *ssa.Go, *ssa.MakeChan, *ssa.Defer, *ssa.Panic, *ssa.Send, *ssa.Select: vprintln("usesGR because uses Go...", fn.Name()) visit.usesGR[fn] = true case *ssa.UnOp: if instr.(*ssa.UnOp).Op.String() == "<-" { vprintln("usesGR because uses <-", fn.Name()) visit.usesGR[fn] = true } } } } } } }
// Emit a particular function. func emitFunc(fn *ssa.Function) { /* TODO research if the ssautil.Switches() function can be incorporated to provide any run-time improvement to the code // it would give a big adavantage, but only to a very small number of functions - so definately TODO sw := ssautil.Switches(fn) fmt.Printf("DEBUG Switches for : %s \n", fn) for num,swi := range sw { fmt.Printf("DEBUG Switches[%d]= %+v\n", num, swi) } */ var subFnList []subFnInstrs // where the sub-functions are canOptMap := make(map[string]bool) // TODO review use of this mechanism //println("DEBUG processing function: ", fn.Name()) MakePosHash(fn.Pos()) // mark that we have entered a function trackPhi := true switch len(fn.Blocks) { case 0: // NoOp - only output a function if it has a body... so ignore pure definitions (target language may generate an error, if truely undef) //fmt.Printf("DEBUG function has no body, ignored: %v %v \n", fn.Name(), fn.String()) case 1: // Only one block, so no Phi tracking required trackPhi = false fallthrough default: if trackPhi { // check that there actually are Phi instructions to track trackPhi = false phiSearch: for b := range fn.Blocks { for i := range fn.Blocks[b].Instrs { _, trackPhi = fn.Blocks[b].Instrs[i].(*ssa.Phi) if trackPhi { break phiSearch } } } } instrCount := 0 for b := range fn.Blocks { instrCount += len(fn.Blocks[b].Instrs) } mustSplitCode := false if instrCount > LanguageList[TargetLang].InstructionLimit { //println("DEBUG mustSplitCode => large function length:", instrCount, " in ", fn.Name()) mustSplitCode = true } blks := fn.DomPreorder() // was fn.Blocks for b := range blks { // go though the blocks looking for sub-functions instrsEmitted := 0 inSubFn := false for i := range blks[b].Instrs { canPutInSubFn := true in := blks[b].Instrs[i] switch in.(type) { case *ssa.Phi: // phi uses self-referential temp vars that must be pre-initialised canPutInSubFn = false case *ssa.Return: canPutInSubFn = false case *ssa.Call: switch in.(*ssa.Call).Call.Value.(type) { case *ssa.Builtin: //NoOp default: canPutInSubFn = false } case *ssa.Select, *ssa.Send, *ssa.Defer, *ssa.RunDefers, *ssa.Panic: canPutInSubFn = false case *ssa.UnOp: if in.(*ssa.UnOp).Op == token.ARROW { canPutInSubFn = false } } if canPutInSubFn { if inSubFn { if instrsEmitted > LanguageList[TargetLang].SubFnInstructionLimit { subFnList[len(subFnList)-1].end = i subFnList = append(subFnList, subFnInstrs{b, i, 0}) instrsEmitted = 0 } } else { subFnList = append(subFnList, subFnInstrs{b, i, 0}) inSubFn = true } } else { if inSubFn { subFnList[len(subFnList)-1].end = i inSubFn = false } } instrsEmitted++ } if inSubFn { subFnList[len(subFnList)-1].end = len(blks[b].Instrs) } } for sf := range subFnList { // go though the sub-functions looking for optimisable temp vars var instrMap = make(map[ssa.Instruction]bool) for ii := subFnList[sf].start; ii < subFnList[sf].end; ii++ { instrMap[blks[subFnList[sf].block].Instrs[ii]] = true } for i := subFnList[sf].start; i < subFnList[sf].end; i++ { instrVal, hasVal := blks[subFnList[sf].block].Instrs[i].(ssa.Value) if hasVal { refs := *blks[subFnList[sf].block].Instrs[i].(ssa.Value).Referrers() switch len(refs) { case 0: // no other instruction uses the result of this one default: //multiple usage of the register canOpt := true for r := range refs { user := refs[r] if user.Block() != blks[subFnList[sf].block] { canOpt = false break } _, inRange := instrMap[user] if !inRange { canOpt = false break } } if canOpt && !LanguageList[TargetLang].CanInline(blks[subFnList[sf].block].Instrs[i]) { canOptMap[instrVal.Name()] = true } } } } } reconstruct := tgossa.Reconstruct(blks, grMap[fn] || mustSplitCode) if reconstruct != nil { //fmt.Printf("DEBUG reconstruct %s %#v\n",fn.String(),reconstruct) } emitFuncStart(fn, blks, trackPhi, canOptMap, mustSplitCode, reconstruct) thisSubFn := 0 for b := range blks { emitPhi := trackPhi emitBlockStart(blks, b, emitPhi) inSubFn := false for i := 0; i < len(blks[b].Instrs); i++ { if thisSubFn >= 0 && thisSubFn < len(subFnList) { // not at the end of the list if b == subFnList[thisSubFn].block { if i >= subFnList[thisSubFn].end && inSubFn { inSubFn = false thisSubFn++ if thisSubFn >= len(subFnList) { thisSubFn = -1 // we have come to the end of the list } } } } if thisSubFn >= 0 && thisSubFn < len(subFnList) { // not at the end of the list if b == subFnList[thisSubFn].block { if i == subFnList[thisSubFn].start { inSubFn = true l := TargetLang if mustSplitCode { fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].SubFnCall(thisSubFn)) } else { emitSubFn(fn, blks, subFnList, thisSubFn, mustSplitCode, canOptMap) } } } } if !inSubFn { // optimize phi case statements phiList := 0 phiLoop: switch blks[b].Instrs[i+phiList].(type) { case *ssa.Phi: if len(*blks[b].Instrs[i+phiList].(*ssa.Phi).Referrers()) > 0 { phiList++ if (i + phiList) < len(blks[b].Instrs) { goto phiLoop } } } if phiList > 0 { peephole(blks[b].Instrs[i : i+phiList]) i += phiList - 1 } else { emitPhi = emitInstruction(blks[b].Instrs[i], blks[b].Instrs[i].Operands(make([]*ssa.Value, 0))) } } } if thisSubFn >= 0 && thisSubFn < len(subFnList) { // not at the end of the list if b == subFnList[thisSubFn].block { if inSubFn { thisSubFn++ if thisSubFn >= len(subFnList) { thisSubFn = -1 // we have come to the end of the list } } } } emitBlockEnd(blks, b, emitPhi && trackPhi) } emitRunEnd(fn) if mustSplitCode { for sf := range subFnList { emitSubFn(fn, blks, subFnList, sf, mustSplitCode, canOptMap) } } emitFuncEnd(fn) } }