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 }