github.com/tardisgo/tardisgo@v0.0.0-20161119180838-e0dd9a7e46b5/pogo/function.go (about)

     1  // Copyright 2014 Elliott Stoneham and The TARDIS Go Authors
     2  // Use of this source code is governed by an MIT-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package pogo
     6  
     7  import (
     8  	"fmt"
     9  	"go/token"
    10  	"go/types"
    11  	"strings"
    12  	"unicode"
    13  	"unsafe"
    14  
    15  	"github.com/tardisgo/tardisgo/tgossa"
    16  
    17  	"golang.org/x/tools/go/ssa"
    18  
    19  	/* for DCE tests
    20  	"golang.org/x/tools/go/callgraph"
    21  	"golang.org/x/tools/go/pointer"
    22  	*/)
    23  
    24  // FnIsCalled tells if the function is called
    25  func (comp *Compilation) FnIsCalled(fn *ssa.Function) bool {
    26  	return comp.fnMap[fn]
    27  }
    28  
    29  // For every function, maybe emit the code...
    30  func (comp *Compilation) emitFunctions() {
    31  	dceList := []*ssa.Package{
    32  		comp.mainPackage,
    33  		comp.rootProgram.ImportedPackage(LanguageList[comp.TargetLang].Goruntime),
    34  	}
    35  	dceExceptions := []string{}
    36  	if LanguageList[comp.TargetLang].TestFS != "" { // need to load file system
    37  		dceExceptions = append(dceExceptions, "syscall") // so that we keep UnzipFS()
    38  	}
    39  	dceExceptions = append(dceExceptions, comp.LibListNoDCE...)
    40  	for _, ex := range dceExceptions {
    41  		exip := comp.rootProgram.ImportedPackage(ex)
    42  		if exip != nil {
    43  			dceList = append(dceList, exip)
    44  		} else {
    45  			//fmt.Println("DEBUG exip nil for package: ",ex)
    46  		}
    47  	}
    48  	comp.fnMap, comp.grMap = tgossa.VisitedFunctions(comp.rootProgram, dceList, comp.IsOverloaded)
    49  
    50  	/* NOTE non-working code below attempts to improve Dead Code Elimination,
    51  	//	but is unreliable so far, in part because the target lang runtime may use "unsafe" pointers
    52  	//  and in any case, every program of any consequence uses "reflect"
    53  
    54  	// but pointer analysis only fully works when the "reflect" and "unsafe" packages are not used
    55  	canPointerAnalyse := len(dceExceptions) == 0 // and with no DCE exceptions
    56  	for _, pkg := range rootProgram.AllPackages() {
    57  		switch pkg.Object.Name() {
    58  		case "reflect", "unsafe":
    59  			canPointerAnalyse = false
    60  		}
    61  	}
    62  	if canPointerAnalyse {
    63  		println("DEBUG can use pointer analysis")
    64  		roots := []*ssa.Function{}
    65  		for _, pkg := range rootProgram.AllPackages() {
    66  			if pkg != nil {
    67  				for _, mem := range pkg.Members {
    68  					fn, ok := mem.(*ssa.Function)
    69  					if ok { // TODO - not hard-coded values, more descrimination
    70  						if (pkg.Object.Name() == "main" && fn.Name() == "main") ||
    71  							strings.HasPrefix(fn.Name(), "init") {
    72  							//(pkg.Object.Name() == LanguageList[TargetLang].Goruntime && fn.Name() == "main") ||
    73  							//pkg.Object.Name() == "reflect" ||
    74  							//(pkg.Object.Name() == "syscall" && fn.Name() == "UnzipFS") {
    75  							roots = append(roots, fn)
    76  							//fmt.Println("DEBUG root added:",fn.String())
    77  						}
    78  					}
    79  				}
    80  			}
    81  		}
    82  		config := &pointer.Config{
    83  			Mains:          []*ssa.Package{mainPackage},
    84  			BuildCallGraph: true,
    85  			Reflection:     true,
    86  		}
    87  		ptrResult, err := pointer.Analyze(config)
    88  		if err != nil {
    89  			panic(err)
    90  		}
    91  
    92  		for _, pkg := range rootProgram.AllPackages() {
    93  			funcs := []*ssa.Function{}
    94  			for _, mem := range pkg.Members {
    95  				fn, ok := mem.(*ssa.Function)
    96  				if ok {
    97  					funcs = append(funcs, fn)
    98  				}
    99  				typ, ok := mem.(*ssa.Type)
   100  				if ok {
   101  					mset := rootProgram.MethodSets.MethodSet(typ.Type())
   102  					for i, n := 0, mset.Len(); i < n; i++ {
   103  						mf := rootProgram.Method(mset.At(i))
   104  						funcs = append(funcs, mf)
   105  						//fmt.Printf("DEBUG method %v\n", mf)
   106  					}
   107  				}
   108  			}
   109  			for _, fn := range funcs {
   110  				notRoot := true
   111  				for _, r := range roots {
   112  					if r == fn {
   113  						notRoot = false
   114  						break
   115  					}
   116  				}
   117  				if notRoot {
   118  					_, found := fnMap[fn]
   119  					hasPath := false
   120  					if fn != nil {
   121  						for _, r := range roots {
   122  							if r != nil {
   123  								nod, ok := ptrResult.CallGraph.Nodes[r]
   124  								if ok {
   125  									pth := callgraph.PathSearch(nod,
   126  										func(n *callgraph.Node) bool {
   127  											if n == nil {
   128  												return false
   129  											}
   130  											return n.Func == fn
   131  										})
   132  									if pth != nil {
   133  										//fmt.Printf("DEBUG path from %v to %v = %v\n",
   134  										//	r, fn, pth)
   135  										hasPath = true
   136  										break
   137  									}
   138  
   139  								}
   140  							}
   141  						}
   142  					}
   143  					if found != hasPath {
   144  						if found { // we found it when we should not have
   145  							println("DEBUG DCE function not called: ", fn.String())
   146  							delete(fnMap, fn)
   147  							delete(grMap, fn)
   148  						} else {
   149  							panic("function not found in DCE cross-check: " + fn.String())
   150  						}
   151  					}
   152  				}
   153  			}
   154  		}
   155  	}
   156  	*/
   157  	/*
   158  		fmt.Println("DEBUG funcs not requiring goroutines:")
   159  		for df, db := range grMap {
   160  			if !db {
   161  				fmt.Println(df)
   162  			}
   163  		}
   164  	*/
   165  
   166  	// Remove virtual functions
   167  	for _, pkg := range comp.rootProgram.AllPackages() {
   168  		for _, vf := range LanguageList[comp.TargetLang].PseudoPkgPaths {
   169  			if pkg.Pkg.Path() == vf {
   170  				for _, mem := range pkg.Members {
   171  					fn, ok := mem.(*ssa.Function)
   172  					if ok {
   173  						//println("DEBUG DCE virtual function: ", fn.String())
   174  						delete(comp.fnMap, fn)
   175  						delete(comp.grMap, fn)
   176  					}
   177  				}
   178  			}
   179  		}
   180  	}
   181  
   182  	var dupCheck = make(map[string]*ssa.Function)
   183  	for f := range comp.fnMap {
   184  		p, n := comp.GetFnNameParts(f)
   185  		first, exists := dupCheck[p+"."+n]
   186  		if exists {
   187  			panic(fmt.Sprintf(
   188  				"duplicate function name: %s.%s\nparent orig %v new %v\n",
   189  				p, n, uintptr(unsafe.Pointer(first)), uintptr(unsafe.Pointer(f))))
   190  		}
   191  		dupCheck[p+"."+n] = f
   192  	}
   193  
   194  	for _, f := range comp.fnMapSorted() {
   195  		if !comp.IsOverloaded(f) {
   196  			if err := tgossa.CheckNames(f); err != nil {
   197  				panic(err)
   198  			}
   199  			comp.emitFunc(f)
   200  		}
   201  	}
   202  }
   203  
   204  // IsOverloaded reports if a function reference should be replaced
   205  func (comp *Compilation) IsOverloaded(f *ssa.Function) bool {
   206  	pn := "unknown" // Defensive, as some synthetic or other edge-case functions may not have a valid package name
   207  	rx := f.Signature.Recv()
   208  	if rx == nil { // ordinary function
   209  		if f.Pkg != nil {
   210  			if f.Pkg.Pkg != nil {
   211  				pn = f.Pkg.Pkg.Path() //was .Name()
   212  			}
   213  		} else {
   214  			if f.Object() != nil {
   215  				if f.Object().Pkg() != nil {
   216  					pn = f.Object().Pkg().Path() //was .Name()
   217  				}
   218  			}
   219  		}
   220  	} else { // determine the package information from the type description
   221  		typ := rx.Type()
   222  		ts := typ.String()
   223  		if ts[0] == '*' {
   224  			ts = ts[1:]
   225  		}
   226  		tss := strings.Split(ts, ".")
   227  		if len(tss) >= 2 {
   228  			ts = tss[len(tss)-2] // take the part before the final dot
   229  		} else {
   230  			ts = tss[0] // no dot!
   231  		}
   232  		pn = ts
   233  	}
   234  	tss := strings.Split(pn, "/") // TODO check this also works in Windows
   235  	ts := tss[len(tss)-1]         // take the last part of the path
   236  	pn = ts                       // TODO this is incorrect, but not currently a problem as there is no function overloading
   237  	//println("DEBUG package name: " + pn)
   238  	if LanguageList[comp.TargetLang].FunctionOverloaded(pn, f.Name()) ||
   239  		strings.HasPrefix(pn, "_") { // the package is not in the target language, signaled by a leading underscore and
   240  		return true
   241  	}
   242  	return false
   243  }
   244  
   245  //------------------------------------------------------------------------------------------------------------
   246  // Some target languages, notably Java and PHP, cannot handle very large functions like unicode.init(),
   247  // and so need to be split into a number of sub-functions. As the sub-functions can use stack-based temp vars,
   248  // this also has the advantage of reducing the number of temporary variables required on the heap.
   249  //------------------------------------------------------------------------------------------------------------
   250  
   251  // Type to track the details of each sub-function.
   252  type subFnInstrs struct {
   253  	block int
   254  	start int
   255  	end   int
   256  }
   257  
   258  // Emit a particular function.
   259  func (comp *Compilation) emitFunc(fn *ssa.Function) {
   260  
   261  	/* TODO research if the ssautil.Switches() function can be incorporated to provide any run-time improvement to the code
   262  	// it would give a big adavantage, but only to a very small number of functions - so definately TODO
   263  	sw := ssautil.Switches(fn)
   264  		fmt.Printf("DEBUG Switches for : %s \n", fn)
   265  	for num,swi := range sw {
   266  		fmt.Printf("DEBUG Switches[%d]= %+v\n", num, swi)
   267  	}
   268  	*/
   269  
   270  	var subFnList []subFnInstrs        // where the sub-functions are
   271  	canOptMap := make(map[string]bool) // TODO review use of this mechanism
   272  
   273  	//println("DEBUG processing function: ", fn.Name())
   274  	comp.MakePosHash(fn.Pos()) // mark that we have entered a function
   275  	trackPhi := true
   276  	switch len(fn.Blocks) {
   277  	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)
   278  		//fmt.Printf("DEBUG function has no body, ignored: %v %v \n", fn.Name(), fn.String())
   279  	case 1: // Only one block, so no Phi tracking required
   280  		trackPhi = false
   281  		fallthrough
   282  	default:
   283  		if trackPhi {
   284  			// check that there actually are Phi instructions to track
   285  			trackPhi = false
   286  		phiSearch:
   287  			for b := range fn.Blocks {
   288  				for i := range fn.Blocks[b].Instrs {
   289  					_, trackPhi = fn.Blocks[b].Instrs[i].(*ssa.Phi)
   290  					if trackPhi {
   291  						break phiSearch
   292  					}
   293  				}
   294  			}
   295  		}
   296  		instrCount := 0
   297  		for b := range fn.Blocks {
   298  			instrCount += len(fn.Blocks[b].Instrs)
   299  		}
   300  		mustSplitCode := false
   301  		if instrCount > LanguageList[comp.TargetLang].InstructionLimit {
   302  			//println("DEBUG mustSplitCode => large function length:", instrCount, " in ", fn.Name())
   303  			mustSplitCode = true
   304  		}
   305  		blks := fn.DomPreorder() // was fn.Blocks
   306  		for b := range blks {    // go though the blocks looking for sub-functions
   307  			instrsEmitted := 0
   308  			inSubFn := false
   309  			for i := range blks[b].Instrs {
   310  				canPutInSubFn := true
   311  				in := blks[b].Instrs[i]
   312  				switch in.(type) {
   313  				case *ssa.Phi: // phi uses self-referential temp vars that must be pre-initialised
   314  					canPutInSubFn = false
   315  				case *ssa.Return:
   316  					canPutInSubFn = false
   317  				case *ssa.Call:
   318  					switch in.(*ssa.Call).Call.Value.(type) {
   319  					case *ssa.Builtin:
   320  						//NoOp
   321  					default:
   322  						canPutInSubFn = false
   323  					}
   324  				case *ssa.Select, *ssa.Send, *ssa.Defer, *ssa.RunDefers, *ssa.Panic:
   325  					canPutInSubFn = false
   326  				case *ssa.UnOp:
   327  					if in.(*ssa.UnOp).Op == token.ARROW {
   328  						canPutInSubFn = false
   329  					}
   330  				}
   331  				if canPutInSubFn {
   332  					if inSubFn {
   333  						if instrsEmitted > LanguageList[comp.TargetLang].SubFnInstructionLimit {
   334  							subFnList[len(subFnList)-1].end = i
   335  							subFnList = append(subFnList, subFnInstrs{b, i, 0})
   336  							instrsEmitted = 0
   337  						}
   338  					} else {
   339  						subFnList = append(subFnList, subFnInstrs{b, i, 0})
   340  						inSubFn = true
   341  					}
   342  				} else {
   343  					if inSubFn {
   344  						subFnList[len(subFnList)-1].end = i
   345  						inSubFn = false
   346  					}
   347  				}
   348  				instrsEmitted++
   349  			}
   350  			if inSubFn {
   351  				subFnList[len(subFnList)-1].end = len(blks[b].Instrs)
   352  			}
   353  		}
   354  		for sf := range subFnList { // go though the sub-functions looking for optimisable temp vars
   355  			var instrMap = make(map[ssa.Instruction]bool)
   356  			for ii := subFnList[sf].start; ii < subFnList[sf].end; ii++ {
   357  				instrMap[blks[subFnList[sf].block].Instrs[ii]] = true
   358  			}
   359  
   360  			for i := subFnList[sf].start; i < subFnList[sf].end; i++ {
   361  				instrVal, hasVal := blks[subFnList[sf].block].Instrs[i].(ssa.Value)
   362  				if hasVal {
   363  					refs := *blks[subFnList[sf].block].Instrs[i].(ssa.Value).Referrers()
   364  					switch len(refs) {
   365  					case 0: // no other instruction uses the result of this one
   366  					default: //multiple usage of the register
   367  						canOpt := true
   368  						for r := range refs {
   369  							user := refs[r]
   370  							if user.Block() != blks[subFnList[sf].block] {
   371  								canOpt = false
   372  								break
   373  							}
   374  							_, inRange := instrMap[user]
   375  							if !inRange {
   376  								canOpt = false
   377  								break
   378  							}
   379  						}
   380  						if canOpt &&
   381  							!LanguageList[comp.TargetLang].CanInline(blks[subFnList[sf].block].Instrs[i]) {
   382  							canOptMap[instrVal.Name()] = true
   383  						}
   384  					}
   385  				}
   386  			}
   387  		}
   388  
   389  		reconstruct := tgossa.Reconstruct(blks, comp.grMap[fn] || mustSplitCode)
   390  		if reconstruct != nil {
   391  			//fmt.Printf("DEBUG reconstruct %s %#v\n",fn.String(),reconstruct)
   392  		}
   393  
   394  		comp.emitFuncStart(fn, blks, trackPhi, canOptMap, mustSplitCode, reconstruct)
   395  		thisSubFn := 0
   396  		for b := range blks {
   397  			emitPhi := trackPhi
   398  			comp.emitBlockStart(blks, b, emitPhi)
   399  			inSubFn := false
   400  			for i := 0; i < len(blks[b].Instrs); i++ {
   401  				if thisSubFn >= 0 && thisSubFn < len(subFnList) { // not at the end of the list
   402  					if b == subFnList[thisSubFn].block {
   403  						if i >= subFnList[thisSubFn].end && inSubFn {
   404  							inSubFn = false
   405  							thisSubFn++
   406  							if thisSubFn >= len(subFnList) {
   407  								thisSubFn = -1 // we have come to the end of the list
   408  							}
   409  						}
   410  					}
   411  				}
   412  				if thisSubFn >= 0 && thisSubFn < len(subFnList) { // not at the end of the list
   413  					if b == subFnList[thisSubFn].block {
   414  						if i == subFnList[thisSubFn].start {
   415  							inSubFn = true
   416  							l := comp.TargetLang
   417  							if mustSplitCode {
   418  								fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].SubFnCall(thisSubFn))
   419  							} else {
   420  								comp.emitSubFn(fn, blks, subFnList, thisSubFn, mustSplitCode, canOptMap)
   421  							}
   422  						}
   423  					}
   424  				}
   425  				if !inSubFn {
   426  					// optimize phi case statements
   427  					phiList := 0
   428  				phiLoop:
   429  					switch blks[b].Instrs[i+phiList].(type) {
   430  					case *ssa.Phi:
   431  						if len(*blks[b].Instrs[i+phiList].(*ssa.Phi).Referrers()) > 0 {
   432  							phiList++
   433  							if (i + phiList) < len(blks[b].Instrs) {
   434  								goto phiLoop
   435  							}
   436  						}
   437  					}
   438  					if phiList > 0 {
   439  						comp.peephole(blks[b].Instrs[i : i+phiList])
   440  						i += phiList - 1
   441  					} else {
   442  						emitPhi = comp.emitInstruction(blks[b].Instrs[i],
   443  							blks[b].Instrs[i].Operands(make([]*ssa.Value, 0)))
   444  					}
   445  				}
   446  			}
   447  			if thisSubFn >= 0 && thisSubFn < len(subFnList) { // not at the end of the list
   448  				if b == subFnList[thisSubFn].block {
   449  					if inSubFn {
   450  						thisSubFn++
   451  						if thisSubFn >= len(subFnList) {
   452  							thisSubFn = -1 // we have come to the end of the list
   453  						}
   454  					}
   455  				}
   456  			}
   457  			comp.emitBlockEnd(blks, b, emitPhi && trackPhi)
   458  		}
   459  		comp.emitRunEnd(fn)
   460  		if mustSplitCode {
   461  			for sf := range subFnList {
   462  				comp.emitSubFn(fn, blks, subFnList, sf, mustSplitCode, canOptMap)
   463  			}
   464  		}
   465  		comp.emitFuncEnd(fn)
   466  	}
   467  }
   468  
   469  func (comp *Compilation) emitSubFn(fn *ssa.Function, blks []*ssa.BasicBlock, subFnList []subFnInstrs, sf int, mustSplitCode bool, canOptMap map[string]bool) {
   470  	l := comp.TargetLang
   471  	fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].SubFnStart(sf, mustSplitCode,
   472  		blks[subFnList[sf].block].Instrs[subFnList[sf].start:subFnList[sf].end]))
   473  	for i := subFnList[sf].start; i < subFnList[sf].end; i++ {
   474  		instrVal, hasVal := blks[subFnList[sf].block].Instrs[i].(ssa.Value)
   475  		if hasVal {
   476  			if canOptMap[instrVal.Name()] == true {
   477  				l := comp.TargetLang
   478  				fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].DeclareTempVar(instrVal))
   479  			}
   480  		}
   481  	}
   482  	comp.peephole(blks[subFnList[sf].block].Instrs[subFnList[sf].start:subFnList[sf].end])
   483  	fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].SubFnEnd(sf, int(comp.LatestValidPosHash), mustSplitCode))
   484  }
   485  
   486  // GetFnNameParts gets the elements of the function's name
   487  func (comp *Compilation) GetFnNameParts(fn *ssa.Function) (pack, nam string) {
   488  	mName := fn.Name()
   489  	pName, _ := comp.FuncPathName(fn) //fmt.Sprintf("fn%d", fn.Pos()) //uintptr(unsafe.Pointer(fn)))
   490  	if fn.Pkg != nil {
   491  		if fn.Pkg.Pkg != nil {
   492  			pName = fn.Pkg.Pkg.Path() // was .Name()
   493  		}
   494  	}
   495  	if fn.Signature.Recv() != nil { // we have a method
   496  		pName = fn.Signature.Recv().Pkg().Name() + ":" + fn.Signature.Recv().Type().String() // note no underlying()
   497  		//pName = LanguageList[l].PackageOverloadReplace(pName)
   498  	}
   499  	return pName, mName
   500  }
   501  
   502  // Emit the start of a function.
   503  func (comp *Compilation) emitFuncStart(fn *ssa.Function, blks []*ssa.BasicBlock, trackPhi bool, canOptMap map[string]bool, mustSplitCode bool, reconstruct []tgossa.BlockFormat) {
   504  	l := comp.TargetLang
   505  	posStr := comp.CodePosition(fn.Pos())
   506  	pName, mName := comp.GetFnNameParts(fn)
   507  	isPublic := unicode.IsUpper(rune(mName[0])) // TODO check rules for non-ASCII 1st characters and fix
   508  	fmt.Fprintln(&LanguageList[l].buffer,
   509  		LanguageList[l].FuncStart(pName, mName, fn, blks, posStr, isPublic, trackPhi, comp.grMap[fn] || mustSplitCode, canOptMap, reconstruct))
   510  }
   511  
   512  // Emit the end of a function.
   513  func (comp *Compilation) emitFuncEnd(fn *ssa.Function) {
   514  	l := comp.TargetLang
   515  	fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].FuncEnd(fn))
   516  }
   517  
   518  // Emit code for after the end of all the case statements for a functions _Next phi switch, but before the sub-functions.
   519  func (comp *Compilation) emitRunEnd(fn *ssa.Function) {
   520  	l := comp.TargetLang
   521  	fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].RunEnd(fn))
   522  }
   523  
   524  // Emit the start of the code to handle a particular SSA code block
   525  func (comp *Compilation) emitBlockStart(block []*ssa.BasicBlock, num int, emitPhi bool) {
   526  	l := comp.TargetLang
   527  	fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].BlockStart(block, num, emitPhi))
   528  }
   529  
   530  // Emit the end of the SSA code block
   531  func (comp *Compilation) emitBlockEnd(block []*ssa.BasicBlock, num int, emitPhi bool) {
   532  	l := comp.TargetLang
   533  	fmt.Fprintln(&LanguageList[l].buffer, LanguageList[l].BlockEnd(block, num, emitPhi))
   534  }
   535  
   536  // Emit the code for a call to a function or builtin, which could be deferred.
   537  func (comp *Compilation) emitCall(isBuiltin, isGo, isDefer, usesGr bool, register string, callInfo ssa.CallCommon, errorInfo, comment string) {
   538  	// usesGr gives the default position
   539  	l := comp.TargetLang
   540  	fnToCall := ""
   541  	if isBuiltin {
   542  		fnToCall = callInfo.Value.(*ssa.Builtin).Name()
   543  		usesGr = false
   544  	} else if callInfo.StaticCallee() != nil {
   545  		pName, _ := comp.FuncPathName(callInfo.StaticCallee()) //fmt.Sprintf("fn%d", callInfo.StaticCallee().Pos())
   546  		if callInfo.Signature().Recv() != nil {
   547  			pName = callInfo.Signature().Recv().Pkg().Name() + ":" + callInfo.Signature().Recv().Type().String() // no use of Underlying() here
   548  		} else {
   549  			pkg := callInfo.StaticCallee().Package()
   550  			if pkg != nil {
   551  				pName = pkg.Pkg.Path() // was .Name()
   552  			}
   553  		}
   554  		fnToCall = LanguageList[l].LangName(pName, callInfo.StaticCallee().Name())
   555  		usesGr = comp.grMap[callInfo.StaticCallee()]
   556  	} else { // Dynamic call (take the default on usesGr)
   557  		fnToCall = LanguageList[l].Value(callInfo.Value, errorInfo)
   558  	}
   559  
   560  	if isBuiltin {
   561  		switch fnToCall {
   562  		case "len", "cap", "append", "real", "imag", "complex": //  "copy" may have the results unused
   563  			if register == "" {
   564  				comp.LogError(errorInfo, "pogo", fmt.Errorf("the result from a built-in function is not used"))
   565  			}
   566  		default:
   567  		}
   568  	} else {
   569  		if callInfo.Signature().Results().Len() > 0 {
   570  			if register == "" {
   571  				comp.LogWarning(errorInfo, "pogo", fmt.Errorf("the result from a function call is not used")) //TODO is this needed?
   572  			}
   573  		}
   574  	}
   575  	// target language code must do builtin emulation
   576  	text := LanguageList[l].Call(register, callInfo, callInfo.Args, isBuiltin, isGo, isDefer, usesGr, fnToCall, errorInfo)
   577  	fmt.Fprintln(&LanguageList[l].buffer, text+LanguageList[l].Comment(comment))
   578  }
   579  
   580  // FuncValue is a utility function to avoid publishing rootProgram from this package.
   581  func (comp *Compilation) FuncValue(obj *types.Func) ssa.Value {
   582  	return comp.rootProgram.FuncValue(obj)
   583  }