// genInvoke generates constraints for a dynamic method invocation. func (a *analysis) genInvoke(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) { if call.Value.Type() == a.reflectType { a.genInvokeReflectType(caller, site, call, result) return } sig := call.Signature() // Allocate a contiguous targets/params/results block for this call. block := a.nextNode() // pts(targets) will be the set of possible call targets site.targets = a.addOneNode(sig, "invoke.targets", nil) p := a.addNodes(sig.Params(), "invoke.params") r := a.addNodes(sig.Results(), "invoke.results") // Copy the actual parameters into the call's params block. for i, n := 0, sig.Params().Len(); i < n; i++ { sz := a.sizeof(sig.Params().At(i).Type()) a.copy(p, a.valueNode(call.Args[i]), sz) p += nodeid(sz) } // Copy the call's results block to the actual results. if result != 0 { a.copy(result, r, a.sizeof(sig.Results())) } // We add a dynamic invoke constraint that will connect the // caller's and the callee's P/R blocks for each discovered // call target. a.addConstraint(&invokeConstraint{call.Method, a.valueNode(call.Value), block}) }
// genStaticCall generates constraints for a statically dispatched function call. func (a *analysis) genStaticCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) { fn := call.StaticCallee() // Special cases for inlined intrinsics. switch fn { case a.runtimeSetFinalizer: // Inline SetFinalizer so the call appears direct. site.targets = a.addOneNode(tInvalid, "SetFinalizer.targets", nil) a.addConstraint(&runtimeSetFinalizerConstraint{ targets: site.targets, x: a.valueNode(call.Args[0]), f: a.valueNode(call.Args[1]), }) return case a.reflectValueCall: // Inline (reflect.Value).Call so the call appears direct. dotdotdot := false ret := reflectCallImpl(a, caller, site, a.valueNode(call.Args[0]), a.valueNode(call.Args[1]), dotdotdot) if result != 0 { a.addressOf(fn.Signature.Results().At(0).Type(), result, ret) } return } // Ascertain the context (contour/cgnode) for a particular call. var obj nodeid if a.shouldUseContext(fn) { obj = a.makeFunctionObject(fn, site) // new contour } else { obj = a.objectNode(nil, fn) // shared contour } a.callEdge(caller, site, obj) sig := call.Signature() // Copy receiver, if any. params := a.funcParams(obj) args := call.Args if sig.Recv() != nil { sz := a.sizeof(sig.Recv().Type()) a.copy(params, a.valueNode(args[0]), sz) params += nodeid(sz) args = args[1:] } // Copy actual parameters into formal params block. // Must loop, since the actuals aren't contiguous. for i, arg := range args { sz := a.sizeof(sig.Params().At(i).Type()) a.copy(params, a.valueNode(arg), sz) params += nodeid(sz) } // Copy formal results block to actual result. if result != 0 { a.copy(result, a.funcResults(obj), a.sizeof(sig.Results())) } }
// genDynamicCall generates constraints for a dynamic function call. func (a *analysis) genDynamicCall(caller *cgnode, site *callsite, call *ssa.CallCommon, result nodeid) { // pts(targets) will be the set of possible call targets. site.targets = a.valueNode(call.Value) // We add dynamic closure rules that store the arguments into // the P-block and load the results from the R-block of each // function discovered in pts(targets). sig := call.Signature() var offset uint32 = 1 // P/R block starts at offset 1 for i, arg := range call.Args { sz := a.sizeof(sig.Params().At(i).Type()) a.genStore(caller, call.Value, a.valueNode(arg), offset, sz) offset += sz } if result != 0 { a.genLoad(caller, result, call.Value, offset, a.sizeof(sig.Results())) } }
// Emit the code for a call to a function or builtin, which could be deferred. func emitCall(isBuiltin, isGo, isDefer bool, register string, callInfo ssa.CallCommon, errorInfo, comment string) { l := TargetLang fnToCall := "" if isBuiltin { fnToCall = callInfo.Value.(*ssa.Builtin).Name() } else if callInfo.StaticCallee() != nil { pName := "unknown" if callInfo.Signature().Recv() != nil { pName = callInfo.Signature().Recv().Type().String() // no use of Underlying() here } else { pkg := callInfo.StaticCallee().Pkg if pkg != nil { pName = pkg.Object.Name() } } fnToCall = LanguageList[l].LangName(pName, callInfo.StaticCallee().Name()) } else { // Dynamic call fnToCall = LanguageList[l].Value(callInfo.Value, errorInfo) } if isBuiltin { switch fnToCall { case "len", "cap", "append", "real", "imag", "complex": // "copy" may have the results unused if register == "" { LogError(errorInfo, "pogo", fmt.Errorf("the result from a built-in function is not used")) } else { } default: } } else { if callInfo.Signature().Results().Len() > 0 { if register == "" { LogWarning(errorInfo, "pogo", fmt.Errorf("the result from a function call is not used")) //TODO is this needed? } else { } } } // target language code must do builtin emulation text := LanguageList[l].Call(register, callInfo, callInfo.Args, isBuiltin, isGo, isDefer, fnToCall, errorInfo) fmt.Fprintln(&LanguageList[l].buffer, text+LanguageList[l].Comment(comment)) }
func (l langType) Call(register string, cc ssa.CallCommon, args []ssa.Value, isBuiltin, isGo, isDefer bool, fnToCall, errorInfo string) string { isHaxeAPI := false hashIf := "" // #if - only if required hashEnd := "" // #end - ditto ret := "" pn := "" // package name if isBuiltin { if register != "" { register += "=" } switch fnToCall { // TODO handle other built-in functions? case "len", "cap": switch args[0].Type().Underlying().(type) { case *types.Chan, *types.Slice: if fnToCall == "len" { return register + "({var _v=" + l.IndirectValue(args[0], errorInfo) + ";_v==null?0:_v.len();});" } // cap return register + "({var _v=" + l.IndirectValue(args[0], errorInfo) + ";_v==null?0:_v.cap();});" case *types.Array: // assume len return register + l.IndirectValue(args[0], errorInfo /*, false*/) + ".length;" case *types.Map: // assume len(map) - requires counting the itterator return register + l.IndirectValue(args[0], errorInfo) + "==null?0:{var _l:Int=0;" + // TODO remove two uses of same variable "var _it=" + l.IndirectValue(args[0], errorInfo) + ".iterator();" + "while(_it.hasNext()) {_l++; _it.next();};" + "_l;};" case *types.Basic: // assume string as anything else would have produced an error previously return register + "Force.toUTF8length(this._goroutine," + l.IndirectValue(args[0], errorInfo /*, false*/) + ");" default: // TODO handle other types? // TODO error on string? pogo.LogError(errorInfo, "Haxe", fmt.Errorf("haxe.Call() - unhandled len/cap type: %s", reflect.TypeOf(args[0].Type().Underlying()))) return register + `null;` } case "print", "println": // NOTE ugly and target-specific output! ret += "trace(" + fmt.Sprintf("Go.CPos(%d)", pogo.LatestValidPosHash) if len(args) > 0 { // if there are more arguments to pass, add a comma ret += "," } case "delete": if l.LangType(args[1].Type().Underlying(), false, errorInfo) == "GOint64" { //useInt64 = true } return register + l.IndirectValue(args[0], errorInfo) + ".remove(" + l.IndirectValue(args[1], errorInfo) + ");" case "append": return register + l.append(args, errorInfo) + ";" case "copy": //TODO rework & test return register + "{var _n={if(" + l.IndirectValue(args[0], errorInfo) + ".len()<" + l.IndirectValue(args[1], errorInfo) + ".len())" + l.IndirectValue(args[0], errorInfo) + ".len();else " + l.IndirectValue(args[1], errorInfo) + ".len();};" + "for(_i in 0..._n)" + l.IndirectValue(args[0], errorInfo) + ".setAt(_i,Deep.copy(" + l.IndirectValue(args[1], errorInfo) + `.getAt(_i` + `))` + `);` + "_n;};" // TODO remove two uses of same variable in case of side-effects case "close": return register + "" + l.IndirectValue(args[0], errorInfo) + ".close();" case "recover": return register + "" + "Scheduler.recover(this._goroutine);" case "real": return register + "" + l.IndirectValue(args[0], errorInfo) + ".real;" case "imag": return register + "" + l.IndirectValue(args[0], errorInfo) + ".imag;" case "complex": return register + "new Complex(" + l.IndirectValue(args[0], errorInfo) + "," + l.IndirectValue(args[1], errorInfo) + ");" default: pogo.LogError(errorInfo, "Haxe", fmt.Errorf("haxe.Call() - Unhandled builtin function: %s", fnToCall)) ret = "MISSING_BUILTIN(" } } else { switch fnToCall { // // pogo specific function rewriting // case "tardisgolib_HAXE": // insert raw haxe code into the ouput file!! BEWARE nextReturnAddress-- //decrement to set new return address for next call generation if register != "" { register += "=" } return register + strings.Trim(l.IndirectValue(args[0], errorInfo), `"`) // NOTE user must supply own ";" if required case "tardisgolib_Host": nextReturnAddress-- //decrement to set new return address for next call generation return register + `="` + l.LanguageName() + `";` case "tardisgolib_Platform": nextReturnAddress-- //decrement to set new return address for next call generation return register + `=Go.Platform();` case "tardisgolib_CPos": nextReturnAddress-- //decrement to set new return address for next call generation return register + fmt.Sprintf("=Go.CPos(%d);", pogo.LatestValidPosHash) case "tardisgolib_Zilen": nextReturnAddress-- //decrement to set new return address for next call generation return register + "='хнЧ'.length;" // // Go library complex function rewriting // case "math_Inf": nextReturnAddress-- //decrement to set new return address for next call generation return register + "=(" + l.IndirectValue(args[0], errorInfo) + ">=0?Math.POSITIVE_INFINITY:Math.NEGATIVE_INFINITY);" default: if cc.Method != nil { pn = cc.Method.Pkg().Name() } else { if cc.StaticCallee() != nil { if cc.StaticCallee().Package() != nil { pn = cc.StaticCallee().Package().String() } else { if cc.StaticCallee().Object() != nil { if cc.StaticCallee().Object().Pkg() != nil { pn = cc.StaticCallee().Object().Pkg().Name() } } } } } pns := strings.Split(pn, "/") pn = pns[len(pns)-1] targetFunc := "Go_" + fnToCall + ".call" if strings.HasPrefix(pn, "_") && // in a package that starts with "_" !strings.HasPrefix(fnToCall, "_t") { // and not a temp var TODO this may not always be accurate // start _HAXELIB SPECIAL PROCESSING nextReturnAddress-- // decrement to set new return address for next call generation isBuiltin = true // pretend we are in a builtin function to avoid passing 1st param as bindings isHaxeAPI = true // we are calling a Haxe native function //************************** //TODO ensure correct conversions for interface{} <-> uintptr (haxe:Dynamic) //************************** bits := strings.Split(fnToCall, "_47_") // split the parts of the string separated by / endbit := bits[len(bits)-1] foundDot := false if strings.Contains(endbit, "_dot_") { // it's a dot ss := strings.Split(endbit, "_dot_") endbit = "_ignore_" + ss[len(ss)-1] foundDot = true } bits = strings.Split(endbit, "_") // split RHS after / into parts separated by _ bits = bits[2:] // discard the leading _ and package name switch bits[0][0:1] { // the letter that gives the Haxe language in which to use the api case "X": // cross platform, so noOp case "P": hashIf = " #if cpp " hashEnd = " #end " case "C": hashIf = " #if cs " hashEnd = " #end " case "F": hashIf = " #if flash " hashEnd = " #end " case "J": hashIf = " #if java " hashEnd = " #end " case "S": hashIf = " #if js " hashEnd = " #end " case "N": hashIf = " #if neko " hashEnd = " #end " case "H": hashIf = " #if php " hashEnd = " #end " case "i": if bits[0] == "init" { return "" // no calls to _haxelib init functions } fallthrough default: pogo.LogError(errorInfo, "Haxe", fmt.Errorf("call to function %s unknown Haxe API first letter %v of %v", fnToCall, bits[0][0:1], bits)) } bits[0] = bits[0][1:] // discard the magic letter from the front of the function name if foundDot { // it's a Haxe method ss := strings.Split(args[0].Type().String(), "/") rhs := ss[len(ss)-1] // lose leading slashes rxTypBits := strings.Split(strings.Split(rhs, ".")[1], "_") // loose module name rxTypBits[0] = rxTypBits[0][1:] // loose leading capital letter rxTyp := strings.Join(rxTypBits, ".") // reconstitute with the Haxe dots switch bits[len(bits)-1] { case "goget": return hashIf + register + "=cast(" + l.IndirectValue(args[0], errorInfo) + "," + rxTyp + ")." + bits[len(bits)-2][1:] + ";" + hashEnd case "goset": return hashIf + "cast(" + l.IndirectValue(args[0], errorInfo) + "," + rxTyp + ")." + bits[len(bits)-2][1:] + "=" + l.IndirectValue(args[1], errorInfo) + ";" + hashEnd default: targetFunc = "cast(" + l.IndirectValue(args[0], errorInfo) + "," targetFunc += rxTyp + ")." + bits[len(bits)-1][1:] //remove leading capital letter args = args[1:] } } else { switch bits[len(bits)-1] { case "new": // special processing for creating a new class targetFunc = "new " + strings.Join(bits[:len(bits)-1], ".") // put it back into the Haxe format for names case "goget": // special processing to get a class static variable return hashIf + register + "=" + strings.Join(strings.Split(strings.Join(bits[:len(bits)-1], "."), "..."), "_") + ";" + hashEnd case "goset": // special processing to set a class static variable return hashIf + strings.Join(strings.Split(strings.Join(bits[:len(bits)-1], "."), "..."), "_") + "=" + l.IndirectValue(args[0], errorInfo) + ";" + hashEnd default: targetFunc = strings.Join(bits, ".") // put it back into the Haxe format for names } } targetFunc = strings.Join(strings.Split(targetFunc, "..."), "_") // end _HAXELIB SPECIAL PROCESSING } else { olv, ok := fnToVarOverloadMap[fnToCall] if ok { // replace the function call with a variable nextReturnAddress-- //decrement to set new return address for next call generation if register == "" { return "" } return register + "=" + olv + ";" } olf, ok := fnOverloadMap[fnToCall] if ok { // replace one go function with another targetFunc = olf } else { olf, ok := builtinOverloadMap[fnToCall] if ok { // replace a go function with a haxe one targetFunc = olf nextReturnAddress-- //decrement to set new return address for next call generation isBuiltin = true // pretend we are in a builtin function to avoid passing 1st param as bindings or waiting for completion } else { // TODO at this point the package-level overloading could occur, but I cannot make it reliable, so code removed } } } switch cc.Value.(type) { case *ssa.Function: //simple case ret += targetFunc + "(" case *ssa.MakeClosure: // it is a closure, but with a static callee ret += targetFunc + "(" default: // closure with a dynamic callee ret += fnToCall + ".callFn([" // the callee is in a local variable } } } if !isBuiltin { if isGo { ret += "Scheduler.makeGoroutine()," } else { ret += "this._goroutine," } } switch cc.Value.(type) { case *ssa.Function: //simple case if !isBuiltin { // don't pass bindings to built-in functions ret += "[]" // goroutine + bindings } case *ssa.MakeClosure: // it is a closure, but with a static callee ret += "" + l.IndirectValue(cc.Value, errorInfo) + ".bds" default: // closure with a dynamic callee if !isBuiltin { // don't pass bindings to built-in functions ret += "" + fnToCall + ".bds" } } for arg := range args { if arg != 0 || !isBuiltin { ret += "," } // SAME LOGIC AS SWITCH IN INVOKE - keep in line switch args[arg].Type().Underlying().(type) { // TODO this may be in need of further optimization case *types.Pointer, *types.Slice, *types.Chan: // must pass a reference, not a copy ret += l.IndirectValue(args[arg], errorInfo) case *types.Basic: // NOTE Complex is an object as is Int64 (in java & cs), but copy does not seem to be required ret += l.IndirectValue(args[arg], errorInfo) case *types.Interface: if isHaxeAPI { // for Go interface{} parameters, substitute the Haxe Dynamic part ret += l.IndirectValue(args[arg], errorInfo) + ".val" // TODO check works in all situations } else { ret += l.IndirectValue(args[arg], errorInfo) } default: // TODO review ret += "Deep.copy(" + l.IndirectValue(args[arg], errorInfo) + ")" } } if isBuiltin { ret += ")" } else { switch cc.Value.(type) { case *ssa.Function, *ssa.MakeClosure: // it is a call with a list of args ret += ")" default: // it is a call with a single arg that is a list ret += "])" // the callee is in a local variable } } if isBuiltin { if register != "" { //************************** //TODO ensure correct conversions for interface{} <-> Dynamic when isHaxeAPI //************************** return hashIf + register + "=" + ret + ";" + hashEnd } return hashIf + ret + ";" + hashEnd } if isGo { return ret + "; " } if isDefer { return ret + ";\nthis.defer(Scheduler.pop(this._goroutine));" } return doCall(register, ret+";\n") }