package ssa import ( "git.smesh.lol/moxie/pkg/mxutil" "git.smesh.lol/moxie/pkg/syntax" "git.smesh.lol/moxie/pkg/token" . "git.smesh.lol/moxie/pkg/types" ) func asType(t Type) (tv Type) { return t } // ScopeParentMap stores scope parent relationships per function. // Populated during SSA building, consumed by the IR emitter. var ScopeParentMap map[*SSAFunction]map[int32]int32 func GetScopeParent(fn *SSAFunction) (m map[int32]int32) { return ScopeParentMap[fn] } func init() { ScopeParentMap = map[*SSAFunction]map[int32]int32{} } // CreatePackage builds the SSA Package from type-checked syntax files. func (prog *SSAProgram) CreatePackage(pkg *TCPackage, files []*syntax.File, info *Info) (s *SSAPackage) { p := &SSAPackage{ Prog: prog, Pkg: pkg, Members: map[string]SSAMember{}, } prog.packages[pkg] = p prog.imported[pkg.Path] = p pb := &ssaPkgBuilder{pkg: p, info: info, prog: prog} // Imports are file-scoped: each file gets a scope (parent = pkg scope) // holding its own PkgName bindings, so two files can bind the same // local name to different packages. The driver concatenates all package // files into one syntax.File with a hoisted+deduped import block, so the // per-file association comes from mxutil.ConcatImportSegs (per-file import // specs + concat start line), and decls map to their file's scope by // position. pb.fileScopes = map[*syntax.File]*Scope{} pb.varInitScopes = map[*syntax.VarDecl]*Scope{} for _, f := range files { pb.fileScopes[f] = NewScope(pkg.Scope) } pb.buildImportSegScopes() for _, f := range files { if f == nil { continue } pb.curFileScope = pb.fileScopes[f] pb.registerImportsAndTypes(f) } for _, f := range files { pb.curFileScope = pb.fileScopes[f] pb.registerFile(f) } for _, f := range files { pb.curFileScope = pb.fileScopes[f] pb.buildFile(f) } pb.curFileScope = nil if len(pb.varInits) > 0 { pb.buildVarInits() } return p } // ssaPkgBuilder builds one SSAPackage. type ssaPkgBuilder struct { pkg *SSAPackage info *Info prog *SSAProgram varInits []*syntax.VarDecl fileScopes map[*syntax.File]*Scope curFileScope *Scope varInitScopes map[*syntax.VarDecl]*Scope segScopes []segScope } // segScope is one source file's import scope in the concatenated package // stream, keyed by the concat line its body starts at. type segScope struct { outStart int32 scope *Scope } // buildImportSegScopes turns the driver's per-file import segments into // scopes holding each file's PkgName bindings. func (pb *ssaPkgBuilder) buildImportSegScopes() { pb.segScopes = nil for si := 0; si < len(mxutil.ConcatImportSegs); si++ { seg := mxutil.ConcatImportSegs[si] sc := NewScope(pb.pkg.Pkg.Scope) for _, spec := range seg.Specs { path := mxutil.ExtractQuoted(spec) if path == "" { continue } alias := importSpecAlias(spec) if alias == "." || alias == "_" { continue } target := pb.resolveImportTarget(path) if target == nil { continue } localName := alias if localName == "" { localName = target.Name } if localName == "" { for i := len(path) - 1; i >= 0; i-- { if path[i] == '/' { localName = path[i+1:] break } } if localName == "" { localName = path } } sc.Insert(NewPkgName(nil, localName, target)) } pb.segScopes = append(pb.segScopes, segScope{outStart: seg.OutStart, scope: sc}) } } // importSpecAlias returns the local alias of a raw import spec line // (`alias "path"`), or "" when the spec has no alias. func importSpecAlias(spec string) (s string) { for i := int32(0); i < int32(len(spec)); i++ { if spec[i] == '"' { return mxutil.TrimSpace(spec[:i]) } } return "" } func (pb *ssaPkgBuilder) resolveImportTarget(path string) (t *TCPackage) { impPkg := pb.prog.ImportedPackage(path) if impPkg != nil && impPkg.Pkg != nil { return impPkg.Pkg } EnsureImportRegistry() return ImportRegistry[path] } // declScope returns the import scope of the source file a decl came from. // Decls before the first segment (the hoisted import block) get nil. func (pb *ssaPkgBuilder) declScope(d syntax.Decl) (sc *Scope) { if len(pb.segScopes) == 0 { return pb.curFileScope } // Pos() lives on the embedded node base; interface dispatch does not // cover embedded-base methods of another package's types, so switch on // the concrete decl types. line := int32(0) switch dd := d.(type) { case *syntax.ImportDecl: dp := dd.Pos() line = int32(dp.Line()) case *syntax.ConstDecl: dp := dd.Pos() line = int32(dp.Line()) case *syntax.TypeDecl: dp := dd.Pos() line = int32(dp.Line()) case *syntax.VarDecl: dp := dd.Pos() line = int32(dp.Line()) case *syntax.FuncDecl: dp := dd.Pos() line = int32(dp.Line()) } // Parser positions are 0-based (p.pos subtracts Linebase); seg outStart // is a 1-based concat line. Convert at this boundary. line++ var best *Scope for i := 0; i < len(pb.segScopes); i++ { if pb.segScopes[i].outStart <= line { best = pb.segScopes[i].scope } } return best } func (pb *ssaPkgBuilder) registerImportsAndTypes(f *syntax.File) { for _, d := range f.DeclList { if d == nil { continue } pb.curFileScope = pb.declScope(d) switch dd := d.(type) { case *syntax.ImportDecl: pb.registerImport(dd) case *syntax.TypeDecl: pb.registerType(dd) default: } } } func (pb *ssaPkgBuilder) registerFile(f *syntax.File) { for _, d := range f.DeclList { pb.curFileScope = pb.declScope(d) switch dd := d.(type) { case *syntax.FuncDecl: pb.registerFunc(dd) case *syntax.VarDecl: pb.registerVar(dd) case *syntax.ConstDecl: pb.registerConst(dd) } } } func (pb *ssaPkgBuilder) registerImport(d *syntax.ImportDecl) { if d.Path == nil { return } path := d.Path.Value if len(path) >= 2 && path[0] == '"' { path = path[1 : len(path)-1] } localName := "" if d.LocalPkgName != nil { localName = d.LocalPkgName.Value } target := pb.resolveImportTarget(path) if target == nil { return } // Default name is the package clause name, NOT the path tail: the two // differ for packages like crypto/ec (package btcec). if localName == "" { localName = target.Name } if localName == "" { for i := len(path) - 1; i >= 0; i-- { if path[i] == '/' { localName = path[i+1:] break } } if localName == "" { localName = path } } if localName == "." || localName == "_" { return } // Track all direct imports regardless of local-name collisions. tcp := pb.pkg.Pkg found := false for _, ip := range tcp.Imports { if ip == target { found = true break } } if !found { tcp.Imports = append(tcp.Imports, target) } pn := NewPkgName(nil, localName, target) // File scope binding wins over a same-named import in another file. if pb.curFileScope != nil { pb.curFileScope.Insert(pn) } // Legacy fallback for lookup paths without file context: first-wins. if existing := pb.pkg.Pkg.Scope.Lookup(localName); existing != nil { return } pb.pkg.Pkg.Scope.Insert(pn) } func pkgResolveTypeAST(pkg *SSAPackage, e syntax.Expr) (t Type) { if e == nil { return nil } switch ev := e.(type) { case *syntax.Name: if _, obj := Universe.LookupParent(ev.Value); obj != nil { if tn, ok := obj.(*TypeName); ok { return tn.Typ } } if pkg != nil { obj := pkg.Pkg.Scope.Lookup(ev.Value) if tn, ok := obj.(*TypeName); ok { return tn.Typ } } case *syntax.Operation: if ev.Y == nil && ev.Op == token.Mul { base := pkgResolveTypeAST(pkg, ev.X) if base != nil { return NewPointer(base) } } case *syntax.ArrayType: elem := pkgResolveTypeAST(pkg, ev.Elem) if elem != nil { n := int64(-1) if lit, ok := ev.Len.(*syntax.BasicLit); ok { n = SSAParseInt64(lit.Value) } return NewArray(elem, n) } case *syntax.SliceType: elem := pkgResolveTypeAST(pkg, ev.Elem) if elem != nil { if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) } case *syntax.MapType: key := pkgResolveTypeAST(pkg, ev.Key) val := pkgResolveTypeAST(pkg, ev.Value) if key != nil && val != nil { return NewTCMap(key, val) } case *syntax.FuncType: return pkgResolveSigAST(pkg, ev) case *syntax.SelectorExpr: if x, ok := ev.X.(*syntax.Name); ok { pn, _ := pkg.Pkg.Scope.Lookup(x.Value).(*PkgName) if pn != nil && pn.Imported != nil { sel := pn.Imported.Scope.Lookup(ev.Sel.Value) if tn, ok2 := sel.(*TypeName); ok2 { return tn.Typ } } } case *syntax.InterfaceType: return resolveInterfaceFromAST(ev) case *syntax.DotsType: elem := pkgResolveTypeAST(pkg, ev.Elem) if elem != nil { if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) } } return nil } func pkgResolveSigAST(pkg *SSAPackage, ft *syntax.FuncType) (s *Signature) { if ft == nil { return nil } var params []*TCVar for _, p := range ft.ParamList { typ := pkgResolveTypeAST(pkg, p.Type) pname := "" if p.Name != nil { pname = p.Name.Value } params = append(params, NewTCVar(nil, pname, typ)) } var results []*TCVar for _, r := range ft.ResultList { typ := pkgResolveTypeAST(pkg, r.Type) rname := "" if r.Name != nil { rname = r.Name.Value } results = append(results, NewTCVar(nil, rname, typ)) } variadic := false if len(ft.ParamList) > 0 { if _, ok := ft.ParamList[len(ft.ParamList)-1].Type.(*syntax.DotsType); ok { variadic = true } } var pTuple, rTuple *Tuple if len(params) > 0 { pTuple = NewTuple(params...) } if len(results) > 0 { rTuple = NewTuple(results...) } return NewSignature(nil, pTuple, rTuple, variadic) } func (pb *ssaPkgBuilder) registerFunc(d *syntax.FuncDecl) { if len(d.TParamList) > 0 { return } if d.Recv != nil { pb.registerMethod(d) return } if d.Name.Value == "init" { mxutil.WriteStr(2, "compile error: init() is not allowed; use main() for package initialization\n") return } var obj *TCFunc if o := pb.pkg.Pkg.Scope.Lookup(d.Name.Value); o != nil { obj, _ = o.(*TCFunc) } var sig *Signature if obj != nil { sig, _ = obj.Typ.(*Signature) } if sig == nil && d.Type != nil { sig = pkgResolveSigAST(pb.pkg, d.Type) } fn := &SSAFunction{ Name: d.Name.Value, Object: obj, Signature: sig, pos: 0, Pkg: pb.pkg, Prog: pb.prog, } if syntax.CompileExportMap != nil { if ename, ok := syntax.CompileExportMap[d.Name.Value]; ok { fn.ExternalSymbol = ename fn.isExternC = true } } if d.Body == nil && isNoContextExtern(pb.pkg.Pkg.Path, d.Name.Value) { fn.isExternC = true } pb.pkg.SetMember(fn.Name, fn) } func checkInitBody(body *syntax.BlockStmt) (s string) { for _, st := range body.List { if err := checkInitStmt(st); err != "" { return err } } return "" } func checkInitStmt(s syntax.Stmt) (sv string) { switch ss := s.(type) { case *syntax.SelectStmt: _ = ss return "select{} not allowed in init()" case *syntax.BlockStmt: for _, sub := range ss.List { if err := checkInitStmt(sub); err != "" { return err } } case *syntax.IfStmt: if ss.Then != nil { if err := checkInitStmt(ss.Then); err != "" { return err } } if ss.Else != nil { if err := checkInitStmt(ss.Else); err != "" { return err } } case *syntax.ForStmt: if ss.Body != nil { if err := checkInitStmt(ss.Body); err != "" { return err } } case *syntax.SwitchStmt: for _, cc := range ss.Body { for _, sub := range cc.Body { if err := checkInitStmt(sub); err != "" { return err } } } case *syntax.ExprStmt: if err := checkInitExpr(ss.X); err != "" { return err } } return "" } func checkInitExpr(x syntax.Expr) (s string) { if x == nil { return "" } if c, ok := x.(*syntax.CallExpr); ok { if n, ok2 := c.Fun.(*syntax.Name); ok2 && n.Value == "spawn" { return "spawn() not allowed in init()" } } return "" } func isNoContextExtern(pkg, name string) (ok bool) { if pkg == "internal/runtime/syscall" && name == "Syscall6" { return true } if pkg == "syscall" { switch name { case "runtime_entersyscall", "runtime_exitsyscall", "Exit", "Getpagesize", "gettimeofday", "runtime_BeforeExec", "runtime_AfterExec", "runtime_BeforeFork", "runtime_AfterFork", "runtime_AfterForkInChild": return true } } if pkg == "os" { switch name { case "putchar", "getchar", "buffered", "gosched", "runtime_args": return true } } return false } func (pb *ssaPkgBuilder) registerMethod(d *syntax.FuncDecl) { recvTypeName := ssaRecvTypeName(d.Recv) if recvTypeName == "" { return } mangledName := recvTypeName | "." | d.Name.Value var obj *TCFunc obj = pb.findMethod(recvTypeName, d.Name.Value) var sig *Signature if obj != nil { sig, _ = obj.Typ.(*Signature) } if sig == nil && d.Type != nil { sig = pkgResolveSigAST(pb.pkg, d.Type) } if sig == nil { mxutil.WriteStr(2, "WARN: nil sig for method " | mangledName | "\n") } fn := &SSAFunction{ Name: mangledName, Object: obj, Signature: sig, pos: 0, Pkg: pb.pkg, Prog: pb.prog, } pb.pkg.SetMember(mangledName, fn) } func ssaRecvTypeName(recv *syntax.Field) (s string) { if recv == nil { return "" } switch t := recv.Type.(type) { case *syntax.Name: return t.Value case *syntax.Operation: if t.Y == nil && t.Op == token.Mul { if n, ok := t.X.(*syntax.Name); ok { return n.Value } } } return "" } func (pb *ssaPkgBuilder) findMethod(typeName string, methodName string) (t *TCFunc) { obj := pb.pkg.Pkg.Scope.Lookup(typeName) if obj == nil && Universe != nil { obj = Universe.Lookup(typeName) } if obj == nil { return nil } tn, ok := obj.(*TypeName) if !ok { return nil } switch tt := tn.Typ.(type) { case *Named: for i := int32(0); i < tt.NumMethods(); i++ { m := tt.Method(i) if m.Name == methodName { return m } } case *Basic: for i := int32(0); i < tt.NumMethods(); i++ { m := tt.Method(i) if m.Name == methodName { return m } } } return nil } func (pb *ssaPkgBuilder) registerVar(d *syntax.VarDecl) { for _, name := range d.NameList { var obj *TCVar if o := pb.pkg.Pkg.Scope.Lookup(name.Value); o != nil { obj, _ = o.(*TCVar) } var typ Type if obj != nil { typ = obj.Typ } if typ == nil && d.Type != nil { typ = pkgResolveTypeAST(pb.pkg, d.Type) } if typ == nil && d.Values != nil { typ = pkgResolveTypeAST(pb.pkg, d.Values) } g := &SSAGlobal{ Name: name.Value, Object: obj, Typ: NewPointer(typ), pos: 0, Pkg: pb.pkg, } pb.pkg.SetMember(name.Value, g) } if d.Values != nil { pb.varInits = append(pb.varInits, d) pb.varInitScopes[d] = pb.curFileScope } } func (pb *ssaPkgBuilder) registerType(d *syntax.TypeDecl) { var obj *TypeName if o := pb.pkg.Pkg.Scope.Lookup(d.Name.Value); o != nil { obj, _ = o.(*TypeName) } if obj == nil { underlying := pkgResolveTypeAST(pb.pkg, d.Type) if underlying == nil { underlying = &TCStruct{} } named := NewNamed(NewTypeName(pb.pkg.Pkg, d.Name.Value, nil), underlying) named.Obj.Typ = named pb.pkg.Pkg.Scope.Insert(named.Obj) obj = named.Obj } t := &SSAType_{object: obj, pkg: pb.pkg} pb.pkg.SetMember(d.Name.Value, t) } func (pb *ssaPkgBuilder) registerConst(d *syntax.ConstDecl) { for _, name := range d.NameList { var obj *TCConst if o := pb.pkg.Pkg.Scope.Lookup(name.Value); o != nil { obj, _ = o.(*TCConst) } if obj == nil { continue } c := &SSANamedConst{ object: obj, Value: &SSAConst{Typ: obj.Typ, Val: localizeConstVal(obj.Val, obj.Typ)}, pkg: pb.pkg, } pb.pkg.SetMember(name.Value, c) } } func (pb *ssaPkgBuilder) buildFile(f *syntax.File) { for _, d := range f.DeclList { if fd, ok := d.(*syntax.FuncDecl); ok { pb.curFileScope = pb.declScope(d) if fd.Recv != nil { ssaRecvTypeName(fd.Recv) } pb.buildFunc(fd) } } } func (pb *ssaPkgBuilder) buildVarInits() { sig := &Signature{ Params: NewTuple(), Results: NewTuple(), } fn, _ := pb.pkg.Members["main"].(*SSAFunction) if fn == nil { fn = &SSAFunction{ Name: "main", Signature: sig, Pkg: pb.pkg, Prog: pb.prog, } pb.pkg.SetMember("main", fn) } if len(fn.Blocks) > 0 { initSig := &Signature{Params: NewTuple(), Results: NewTuple()} initFn := &SSAFunction{ Name: "__varinit", Signature: initSig, Pkg: pb.pkg, Prog: pb.prog, } pb.pkg.SetMember("__varinit", initFn) fb2 := newSSAFuncBuilder(initFn, pb.info) entry2 := fb2.newBlock("entry") fb2.currentBlock = entry2 for _, d := range pb.varInits { if d.Values == nil { continue } fb2.fileScope = pb.varInitScopes[d] rhs := fb2.buildExpr(d.Values) if rhs == nil { continue } for _, name := range d.NameList { if name.Value == "_" { continue } fb2.buildStore(name, rhs) } } fb2.emit(&SSAReturn{}) // Inject call to __varinit at start of main callInstr := &SSACall{Call: SSACallCommon{Value: initFn}} callInstr.name = "vinit" oldEntry := fn.Blocks[0] n := int32(len(oldEntry.Instrs)) combined := []SSAInstruction{:n+1} combined[0] = callInstr for i := int32(0); i < n; i++ { combined[i+1] = oldEntry.Instrs[i] } oldEntry.Instrs = combined return } fb := newSSAFuncBuilder(fn, pb.info) entry := fb.newBlock("entry") fb.currentBlock = entry for _, d := range pb.varInits { if d.Values == nil { continue } fb.fileScope = pb.varInitScopes[d] rhs := fb.buildExpr(d.Values) if rhs == nil { continue } for _, name := range d.NameList { if name.Value == "_" { continue } fb.buildStore(name, rhs) } } fb.emit(&SSAReturn{}) } func (pb *ssaPkgBuilder) buildFunc(d *syntax.FuncDecl) { if len(d.TParamList) > 0 { return } if d.Body == nil { return } name := d.Name.Value if d.Recv != nil { rtn := ssaRecvTypeName(d.Recv) if rtn != "" { name = rtn | "." | d.Name.Value } } member := pb.pkg.Members[name] if member == nil { mxutil.WriteStr(2, "WARN: buildFunc no member for " | name | "\n") return } fn, ok := member.(*SSAFunction) if !ok || fn == nil { mxutil.WriteStr(2, "WARN: buildFunc member not SSAFunction for " | name | "\n") return } fb := newSSAFuncBuilder(fn, pb.info) fb.fileScope = pb.curFileScope fb.buildBody(d) if fb.scopeParent != nil { ScopeParentMap[fn] = fb.scopeParent } } // ssaFuncBuilder builds one SSAFunction body. type ssaFuncBuilder struct { fn *SSAFunction info *Info parent *ssaFuncBuilder currentBlock *SSABasicBlock vars map[Object]*SSAAlloc localTypes map[string]Type localConsts map[string]*SSAConst freeVarPtrs map[string]*SSAFreeVar namedResults []*SSAAlloc paramAllocas []*SSAAlloc tailBlock *SSABasicBlock counter int32 loops []ssaLoopState pendingLabel string labels map[string]*SSABasicBlock deferred int32 typeSubst map[string]Type srcScope *Scope fileScope *Scope // imports of the file declaring this function decl *syntax.FuncDecl scopeID int32 nextScopeID int32 scopeParent map[int32]int32 } type ssaLoopState struct { label string body *SSABasicBlock post *SSABasicBlock done *SSABasicBlock } func newSSAFuncBuilder(fn *SSAFunction, info *Info) (p *ssaFuncBuilder) { return &ssaFuncBuilder{ fn: fn, info: info, vars: map[Object]*SSAAlloc{}, localTypes: map[string]Type{}, } } func (fb *ssaFuncBuilder) newBlock(comment string) (s *SSABasicBlock) { b := NewSSABasicBlock(fb.fn, comment) b.ScopeID = fb.scopeID return b } func (fb *ssaFuncBuilder) enterScope() (prev int32) { fb.nextScopeID++ prev = fb.scopeID fb.scopeID = fb.nextScopeID if fb.scopeParent == nil { fb.scopeParent = map[int32]int32{} } fb.scopeParent[fb.scopeID] = prev return prev } func (fb *ssaFuncBuilder) exitScope(old int32) { fb.scopeID = old } func (fb *ssaFuncBuilder) labelBlock(name string) (s *SSABasicBlock) { if fb.labels == nil { fb.labels = map[string]*SSABasicBlock{} } if lb := fb.labels[name]; lb != nil { return lb } nb := fb.newBlock("label." | name) fb.labels[name] = nb return nb } func (fb *ssaFuncBuilder) emit(instr SSAInstruction) { if fb.currentBlock == nil { return } instr.setBlock(fb.currentBlock) fb.currentBlock.Instrs = append(fb.currentBlock.Instrs, instr) } func (fb *ssaFuncBuilder) nextName() (s string) { fb.counter++ return "t" | SSAItoa(fb.counter) } func (fb *ssaFuncBuilder) buildBody(d *syntax.FuncDecl) { fb.decl = d entry := fb.newBlock("entry") fb.currentBlock = entry sig := fb.fn.Signature if sig == nil { return } params := sig.Params if d.Recv != nil { recv := sig.Recv if recv != nil { recvName := recv.Name if recvName == "" && d.Recv.Name != nil { recvName = d.Recv.Name.Value } // All receivers are ptr in Moxie. If the type checker says // value receiver, wrap in pointer so the SSA type is *T. recvType := recv.Typ isValRecv := false if _, isP := SafeUnderlying(recvType).(*Pointer); !isP { recvType = NewPointer(recvType) isValRecv = true } p := &SSAParameter{ name: recvName, typ: recvType, pos: 0, parent: fb.fn, } fb.fn.Params = append(fb.fn.Params, p) if recvName != "" && recvName != "_" { recvObj := NewTCVar(fb.fn.Pkg.Pkg, recvName, recv.Typ) p.object = recvObj alloc := fb.emitAlloc(recv.Typ, 0) fb.vars[recvObj] = alloc fb.paramAllocas = append(fb.paramAllocas, alloc) if isValRecv { // p is *T; load the value T from the pointer, then store. deref := &SSAUnOp{Op: OpMul, X: p} deref.typ = recv.Typ fb.fn.Blocks[0].Instrs = append(fb.fn.Blocks[0].Instrs, deref) fb.emitStore(alloc, deref) } else { fb.emitStore(alloc, p) } } } } if params != nil { for i := 0; i < params.Len(); i++ { pvar := params.At(i) p := &SSAParameter{ name: pvar.Name, typ: pvar.Typ, pos: fb.fn.pos, parent: fb.fn, } fb.fn.Params = append(fb.fn.Params, p) if pvar.Name != "" && pvar.Name != "_" { obj := NewTCVar(fb.fn.Pkg.Pkg, pvar.Name, pvar.Typ) alloc := fb.emitAlloc(pvar.Typ, p.pos) fb.vars[obj] = alloc fb.paramAllocas = append(fb.paramAllocas, alloc) fb.emitStore(alloc, p) } } } if sig.Results != nil { for i := 0; i < sig.Results.Len(); i++ { r := sig.Results.At(i) if r.Name != "" && r.Name != "_" { alloc := fb.emitAlloc(r.Typ, fb.fn.pos) fb.namedResults = append(fb.namedResults, alloc) fb.fn.Locals = append(fb.fn.Locals, alloc) fb.vars[r] = alloc } } } if len(fb.paramAllocas) > 0 { fb.tailBlock = fb.newBlock("tailcall") fb.emit(&SSAJump{Comment: "enter.body"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, fb.tailBlock) fb.tailBlock.Preds = append(fb.tailBlock.Preds, fb.currentBlock) fb.currentBlock = fb.tailBlock } if d.Body != nil { fb.buildBlock(d.Body) } if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { if len(fb.namedResults) > 0 { var vals []SSAValue for _, nr := range fb.namedResults { elemType := nr.SSAType() if p, ok := elemType.(*Pointer); ok { elemType = p.Base } vals = append(vals, fb.emitLoad(nr, elemType)) } fb.emitReturn(vals, 0) } else { fb.emitReturn(nil, 0) } } fb.fn.NamedResults = fb.namedResults } func (fb *ssaFuncBuilder) emitAlloc(typ Type, pos int32) (s *SSAAlloc) { a := &SSAAlloc{Heap: false} a.typ = NewPointer(typ) a.pos = pos a.name = fb.nextName() fb.emit(a) fb.fn.Locals = append(fb.fn.Locals, a) return a } func (fb *ssaFuncBuilder) emitStore(addr SSAValue, val SSAValue) { fb.emit(&SSAStore{Addr: addr, Val: val}) } func (fb *ssaFuncBuilder) emitLoad(addr SSAValue, typ Type) (s SSAValue) { u := &SSAUnOp{Op: OpMul, X: addr} u.typ = typ u.name = fb.nextName() fb.emit(u) return u } func (fb *ssaFuncBuilder) emitReturn(vals []SSAValue, pos int32) { fb.emit(&SSAReturn{Results: vals, pos: pos}) fb.currentBlock = nil } func (fb *ssaFuncBuilder) blockTerminated(b *SSABasicBlock) (ok bool) { if len(b.Instrs) == 0 { return false } switch b.Instrs[len(b.Instrs)-1].(type) { case *SSAReturn, *SSAJump, *SSAIf, *SSAPanic: return true } return false } // Statement builders. func (fb *ssaFuncBuilder) buildBlock(b *syntax.BlockStmt) { if b == nil { return } for _, s := range b.List { fb.buildStmt(s) } } func (fb *ssaFuncBuilder) buildStmt(s syntax.Stmt) { if s == nil { return } if ls, ok := s.(*syntax.LabeledStmt); ok { if ls.Label != nil { fb.pendingLabel = ls.Label.Value lblBlock := fb.labelBlock(ls.Label.Value) if fb.currentBlock != nil { fb.emit(&SSAJump{Comment: "goto.label." | ls.Label.Value}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, lblBlock) lblBlock.Preds = append(lblBlock.Preds, fb.currentBlock) } fb.currentBlock = lblBlock } fb.buildStmt(ls.Stmt) fb.pendingLabel = "" return } if fb.currentBlock == nil { return } switch ss := s.(type) { case *syntax.EmptyStmt: _ = ss // nothing case *syntax.ExprStmt: fb.buildExpr(ss.X) case *syntax.AssignStmt: fb.buildAssign(ss) case *syntax.BlockStmt: fb.buildBlock(ss) case *syntax.DeclStmt: for _, d := range ss.DeclList { fb.buildLocalDecl(d) } case *syntax.IfStmt: fb.buildIf(ss) case *syntax.ForStmt: fb.buildFor(ss) case *syntax.SwitchStmt: fb.buildSwitch(ss) case *syntax.SelectStmt: fb.buildSelect(ss) case *syntax.ReturnStmt: fb.buildReturn(ss) case *syntax.BranchStmt: fb.buildBranch(ss) case *syntax.SendStmt: ch := fb.buildExpr(ss.Chan) val := fb.buildExpr(ss.Value) if ch != nil && val != nil { fb.emit(&SSASend{Chan: ch, X: val}) } case *syntax.CallStmt: call, _ := ss.Call.(*syntax.CallExpr) switch ss.Tok { case token.Go: if call != nil { fb.buildGoStmt(call) } case token.Defer: if call != nil { fb.buildDeferStmt(call) } default: fb.buildExpr(ss.Call) } } } func (fb *ssaFuncBuilder) buildAssign(s *syntax.AssignStmt) { if s.Rhs == nil { lv := fb.buildExpr(s.Lhs) if lv == nil { return } one := &SSAConst{Typ: lv.SSAType(), Val: &ConstInt{V:1}} op := OpAdd if s.Op == token.Sub { op = OpSub } bin := &SSABinOp{Op: op, X: lv, Y: one} bin.typ = lv.SSAType() bin.name = fb.nextName() fb.emit(bin) fb.buildStore(s.Lhs, bin) return } if s.Op == token.Def { fb.buildShortVarDecl(s) return } if s.Op != 0 { lv := fb.buildExpr(s.Lhs) rv := fb.buildExpr(s.Rhs) if lv == nil || rv == nil { return } op := CompoundOp(s.Op) bin := &SSABinOp{Op: op, X: lv, Y: rv} bin.typ = lv.SSAType() bin.name = fb.nextName() fb.emit(bin) fb.buildStore(s.Lhs, bin) return } lhsList, lhsIsList := s.Lhs.(*syntax.ListExpr) rhsList, rhsIsList := s.Rhs.(*syntax.ListExpr) if lhsIsList && rhsIsList && len(lhsList.ElemList) == len(rhsList.ElemList) { vals := []SSAValue{:len(rhsList.ElemList)} for i, re := range rhsList.ElemList { vals[i] = fb.buildExpr(re) } for i, le := range lhsList.ElemList { if vals[i] != nil { fb.buildStore(le, vals[i]) } } return } if lhsIsList && len(lhsList.ElemList) == 2 { if ae, ok := s.Rhs.(*syntax.AssertExpr); ok { x := fb.buildExpr(ae.X) assertedType := fb.resolveType(ae.Type) if x != nil && assertedType != nil { ta := &SSATypeAssert{X: x, AssertedType: assertedType, CommaOk: true} ta.typ = NewTuple( NewTCVar(nil, "val", assertedType), NewTCVar(nil, "ok", Typ[Bool]), ) ta.name = fb.nextName() fb.emit(ta) for i, le := range lhsList.ElemList { if n, isName := le.(*syntax.Name); isName && n.Value == "_" { continue } ext := &SSAExtract{Tuple: ta, Index: i} ext.typ = SSATupleElemType(ta.typ, i) ext.name = fb.nextName() fb.emit(ext) fb.buildStore(le, ext) } return } } if ie, ok := s.Rhs.(*syntax.IndexExpr); ok { x := fb.buildExpr(ie.X) idx := fb.buildExpr(ie.Index) if x != nil && idx != nil { if mt, ok2 := SafeUnderlying(x.SSAType()).(*TCMap); ok2 { idx = fb.coerceToInterface(idx, mt.Key) l := &SSALookup{X: x, Index: idx, CommaOk: true} l.typ = NewTuple( NewTCVar(nil, "v", mt.Elem), NewTCVar(nil, "ok", Typ[Bool]), ) l.name = fb.nextName() fb.emit(l) for i, le := range lhsList.ElemList { if n, isName := le.(*syntax.Name); isName && n.Value == "_" { continue } ext := &SSAExtract{Tuple: l, Index: i} ext.typ = SSATupleElemType(l.typ, i) ext.name = fb.nextName() fb.emit(ext) fb.buildStore(le, ext) } return } } } if oe, ok := s.Rhs.(*syntax.Operation); ok && oe.Y == nil && oe.Op == token.Recv { x := fb.buildExpr(oe.X) if x != nil { elemType := SSAChanElemType(x.SSAType()) u := &SSAUnOp{Op: OpArrow, X: x, CommaOk: true} u.typ = NewTuple( NewTCVar(nil, "v", elemType), NewTCVar(nil, "ok", Typ[Bool]), ) u.name = fb.nextName() fb.emit(u) for i, le := range lhsList.ElemList { if n, isName := le.(*syntax.Name); isName && n.Value == "_" { continue } ext := &SSAExtract{Tuple: u, Index: i} ext.typ = SSATupleElemType(u.typ, i) ext.name = fb.nextName() fb.emit(ext) fb.buildStore(le, ext) } return } } } rhs := fb.buildExpr(s.Rhs) if rhs == nil { return } fb.buildStore(s.Lhs, rhs) } func (fb *ssaFuncBuilder) coerceToInterface(val SSAValue, targetType Type) (s SSAValue) { if targetType == nil || val == nil || val.SSAType() == nil { return val } if _, isIface := SafeUnderlying(targetType).(*TCInterface); isIface { if _, alreadyIface := SafeUnderlying(val.SSAType()).(*TCInterface); !alreadyIface { mi := &SSAMakeInterface{X: val} mi.typ = targetType mi.name = fb.nextName() fb.emit(mi) return mi } } return val } func (fb *ssaFuncBuilder) buildStore(lhs syntax.Expr, val SSAValue) { if lhs == nil { panic("buildStore: nil lhs") } switch lv := lhs.(type) { case *syntax.Name: if lv == nil { panic("buildStore: nil *syntax.Name after type switch") } if lv.Value == "_" { return } obj := fb.lookupObject(lv.Value) if obj == nil { return } if alloc, ok2 := fb.vars[obj]; ok2 { val = fb.coerceToInterface(val, ObjectType(obj)) fb.emitStore(alloc, val) } else if fv, ok3 := fb.freeVarPtrs[lv.Value]; ok3 { val = fb.coerceToInterface(val, ObjectType(obj)) fb.emitStore(fv, val) } else if g2, ok4 := fb.fn.Pkg.Members[lv.Value].(*SSAGlobal); ok4 { if p, ok5 := g2.SSAType().(*Pointer); ok5 && p.Base != nil { val = fb.coerceToInterface(val, p.Base) } fb.emitStore(g2, val) } else if v, ok6 := obj.(*TCVar); ok6 && v.Pkg != nil && v.Pkg != fb.fn.Pkg.Pkg { // dot-imported global from another package impPkg := fb.ensureImportedSSAPackage(v.Pkg) g3, ok7 := impPkg.Members[lv.Value].(*SSAGlobal) if !ok7 { g3 = &SSAGlobal{ Name: lv.Value, Typ: NewPointer(v.Typ), Pkg: impPkg, } impPkg.SetMember(lv.Value, g3) } if p, ok8 := g3.SSAType().(*Pointer); ok8 && p.Base != nil { val = fb.coerceToInterface(val, p.Base) } fb.emitStore(g3, val) } case *syntax.Operation: if lv.Y == nil && lv.Op == token.Mul { ptr := fb.buildExpr(lv.X) if ptr != nil { if p, ok2 := ptr.SSAType().(*Pointer); ok2 && p.Base != nil { val = fb.coerceToInterface(val, p.Base) } fb.emitStore(ptr, val) } } case *syntax.SelectorExpr: addr := fb.buildSelectorAddr(lv) if addr == nil { return } if p, ok2 := addr.SSAType().(*Pointer); ok2 && p.Base != nil { val = fb.coerceToInterface(val, p.Base) } fb.emitStore(addr, val) case *syntax.IndexExpr: base := fb.buildExpr(lv.X) idx := fb.buildExpr(lv.Index) if base == nil || idx == nil { return } if m, isMap := SafeUnderlying(base.SSAType()).(*TCMap); isMap { idx = fb.coerceToInterface(idx, m.Key) val = fb.coerceToInterface(val, m.Elem) fb.emit(&SSAMapUpdate{Map: base, Key: idx, Value: val}) return } // For array LHS assignment, resolve the address of the base directly // so SSAIndexAddr GEPs into the original, not a stack copy. var baseAddr SSAValue if n, ok := lv.X.(*syntax.Name); ok { obj := fb.lookupObject(n.Value) if obj != nil { if alloc, ok2 := fb.vars[obj]; ok2 { baseAddr = alloc } else if g4, ok10 := fb.fn.Pkg.Members[n.Value].(*SSAGlobal); ok10 { baseAddr = g4 } } } else if sel, ok11 := lv.X.(*syntax.SelectorExpr); ok11 { baseAddr = fb.buildSelectorAddr(sel) } if baseAddr != nil { if _, isArr := SafeUnderlying(base.SSAType()).(*Array); isArr { ia2 := &SSAIndexAddr{X: baseAddr, Index: idx} ia2.typ = NewPointer(SSAElemType(base.SSAType())) ia2.name = fb.nextName() fb.emit(ia2) if p, ok9 := ia2.SSAType().(*Pointer); ok9 && p.Base != nil { val = fb.coerceToInterface(val, p.Base) } fb.emitStore(ia2, val) return } } ia := &SSAIndexAddr{X: base, Index: idx} ia.typ = NewPointer(SSAElemType(base.SSAType())) ia.name = fb.nextName() fb.emit(ia) if p, ok2 := ia.SSAType().(*Pointer); ok2 && p.Base != nil { val = fb.coerceToInterface(val, p.Base) } fb.emitStore(ia, val) case *syntax.ListExpr: for i, e := range lv.ElemList { ext := &SSAExtract{Tuple: val, Index: i} ext.typ = SSATupleElemType(val.SSAType(), i) ext.name = fb.nextName() fb.emit(ext) fb.buildStore(e, ext) } } } func (fb *ssaFuncBuilder) buildShortVarDecl(s *syntax.AssignStmt) { names := ssaExprNames(s.Lhs) if len(names) == 2 { if ae, ok := s.Rhs.(*syntax.AssertExpr); ok { x := fb.buildExpr(ae.X) assertedType := fb.resolveType(ae.Type) if assertedType != nil { if x == nil { x = &SSAConst{Typ: NewTCInterface(nil, nil)} } ta := &SSATypeAssert{X: x, AssertedType: assertedType, CommaOk: true} ta.typ = NewTuple( NewTCVar(nil, "val", assertedType), NewTCVar(nil, "ok", Typ[Bool]), ) ta.name = fb.nextName() fb.emit(ta) for i, name := range names { if name.Value == "_" { continue } ext := &SSAExtract{Tuple: ta, Index: i} ext.typ = SSATupleElemType(ta.typ, i) ext.name = fb.nextName() fb.emit(ext) fb.removeVar(name.Value) obj := NewTCVar(fb.fn.Pkg.Pkg, name.Value, ext.typ) alloc := fb.emitAlloc(ext.typ, 0) fb.vars[obj] = alloc fb.emitStore(alloc, ext) } return } } if ie, ok := s.Rhs.(*syntax.IndexExpr); ok { x := fb.buildExpr(ie.X) idx := fb.buildExpr(ie.Index) if x != nil && idx != nil { if mt, ok2 := SafeUnderlying(x.SSAType()).(*TCMap); ok2 { idx = fb.coerceToInterface(idx, mt.Key) l := &SSALookup{X: x, Index: idx, CommaOk: true} l.typ = NewTuple( NewTCVar(nil, "v", mt.Elem), NewTCVar(nil, "ok", Typ[Bool]), ) l.name = fb.nextName() fb.emit(l) for i, name := range names { if name.Value == "_" { continue } ext := &SSAExtract{Tuple: l, Index: i} ext.typ = SSATupleElemType(l.typ, i) ext.name = fb.nextName() fb.emit(ext) fb.removeVar(name.Value) var obj Object if fb.info != nil { obj = fb.info.Defs[name] } if obj == nil { obj = NewTCVar(fb.fn.Pkg.Pkg, name.Value, ext.typ) } alloc := fb.emitAlloc(ext.typ, 0) fb.vars[obj] = alloc fb.emitStore(alloc, ext) } return } } } if oe, ok := s.Rhs.(*syntax.Operation); ok && oe.Y == nil && oe.Op == token.Recv { x := fb.buildExpr(oe.X) if x != nil { elemType := SSAChanElemType(x.SSAType()) u := &SSAUnOp{Op: OpArrow, X: x, CommaOk: true} u.typ = NewTuple( NewTCVar(nil, "v", elemType), NewTCVar(nil, "ok", Typ[Bool]), ) u.name = fb.nextName() fb.emit(u) for i, name := range names { if name.Value == "_" { continue } ext := &SSAExtract{Tuple: u, Index: i} ext.typ = SSATupleElemType(u.typ, i) ext.name = fb.nextName() fb.emit(ext) fb.removeVar(name.Value) var obj Object if fb.info != nil { obj = fb.info.Defs[name] } if obj == nil { obj = NewTCVar(fb.fn.Pkg.Pkg, name.Value, ext.typ) } alloc := fb.emitAlloc(ext.typ, 0) fb.vars[obj] = alloc fb.emitStore(alloc, ext) } return } } } if rhsList, ok := s.Rhs.(*syntax.ListExpr); ok && len(rhsList.ElemList) == len(names) { vals := []SSAValue{:len(rhsList.ElemList)} for i, re := range rhsList.ElemList { vals[i] = fb.buildExpr(re) } for i, name := range names { if name.Value == "_" { continue } var typ Type if vals[i] != nil { typ = vals[i].SSAType() } fb.removeVar(name.Value) var obj Object if fb.info != nil { obj = fb.info.Defs[name] } if obj == nil { obj = NewTCVar(fb.fn.Pkg.Pkg, name.Value, typ) } alloc := fb.emitAlloc(typ, 0) fb.vars[obj] = alloc if vals[i] != nil { fb.emitStore(alloc, vals[i]) } } return } rhs := fb.buildExpr(s.Rhs) isTuple := false var tup *Tuple if rhs != nil { tup, isTuple = rhs.SSAType().(*Tuple) } for i, name := range names { if name.Value == "_" { continue } var typ Type if rhs != nil { if isTuple && i < tup.Len() { typ = tup.At(i).Typ } else if i == 0 { typ = rhs.SSAType() } } if typ == nil { rhsInfo := "nil" if rhs != nil { if rhs.SSAType() == nil { rhsInfo = "ssatype=nil" } else if tup2, ok2 := rhs.SSAType().(*Tuple); ok2 { rhsInfo = "tuple[" | token.Itoa(tup2.Len()) | "] i=" | token.Itoa(i) if i < tup2.Len() { if tup2.At(i).Typ == nil { rhsInfo = rhsInfo | " At.Type=nil" } else { rhsInfo = rhsInfo | " At.Type=set" } } } else { rhsInfo = "non-tuple" } } typ = Typ[Int32] } fb.removeVar(name.Value) var obj Object if fb.info != nil { obj = fb.info.Defs[name] } if obj == nil { obj = NewTCVar(fb.fn.Pkg.Pkg, name.Value, typ) } alloc := fb.emitAlloc(typ, 0) fb.vars[obj] = alloc var initVal SSAValue if rhs != nil { if isTuple { ext := &SSAExtract{Tuple: rhs, Index: i} ext.typ = typ ext.name = fb.nextName() fb.emit(ext) initVal = ext } else if i == 0 { initVal = rhs } } if initVal != nil { fb.emitStore(alloc, initVal) } } } func (fb *ssaFuncBuilder) buildLocalDecl(d syntax.Decl) { switch dd := d.(type) { case *syntax.VarDecl: var typ Type if fb.info != nil && len(dd.NameList) > 0 { if obj := fb.info.Defs[dd.NameList[0]]; obj != nil { typ = ObjectType(obj) } } if typ == nil && dd.Type != nil { typ = fb.resolveType(dd.Type) } initVal := fb.buildExpr(dd.Values) for _, name := range dd.NameList { if name.Value == "_" { continue } fb.removeVar(name.Value) var obj Object if fb.info != nil { obj = fb.info.Defs[name] } if obj == nil { obj = NewTCVar(fb.fn.Pkg.Pkg, name.Value, typ) } alloc := fb.emitAlloc(ObjectType(obj), 0) fb.vars[obj] = alloc fb.fn.Locals = append(fb.fn.Locals, alloc) if initVal != nil { initVal = fb.coerceToInterface(initVal, ObjectType(obj)) fb.emitStore(alloc, initVal) } else { fb.emitStore(alloc, &SSAConst{Typ: ObjectType(obj), Val: nil}) } } case *syntax.ConstDecl: if dd.Values != nil && len(dd.NameList) > 0 { if fb.localConsts == nil { fb.localConsts = map[string]*SSAConst{} } if list, ok := dd.Values.(*syntax.ListExpr); ok { for i, name := range dd.NameList { if i < len(list.ElemList) { if v := fb.buildExpr(list.ElemList[i]); v != nil { if c := ssaExtractConst(v); c != nil { fb.localConsts[name.Value] = c } } } } } else if len(dd.NameList) == 1 { if v := fb.buildExpr(dd.Values); v != nil { if c := ssaExtractConst(v); c != nil { fb.localConsts[dd.NameList[0].Value] = c } } } } case *syntax.TypeDecl: if dd.Name != nil { resolved := fb.resolveTypeAST(dd.Type) if resolved != nil { fb.localTypes[dd.Name.Value] = resolved } } } } func (fb *ssaFuncBuilder) evalLocalConstExpr(e syntax.Expr) (c ConstVal) { if e == nil { return nil } switch ev := e.(type) { case *syntax.BasicLit: return EvalBasicLitLocal(ev) case *syntax.Name: if lc, ok := fb.localConsts[ev.Value]; ok && lc != nil && lc.Val != nil { return lc.Val } return fb.fn.Prog.EvalConstExpr(ev, fb.fn.Pkg.Pkg.Scope, -1) case *syntax.Operation: if ev.Y == nil { xr2 := fb.evalLocalConstExpr(ev.X) if xr2 == nil { return nil } return EvalUnaryLocal(ev.Op, xr2) } xr := fb.evalLocalConstExpr(ev.X) yr := fb.evalLocalConstExpr(ev.Y) if xr == nil || yr == nil { return nil } return EvalBinaryLocal(ev.Op, xr, yr) case *syntax.ParenExpr: return fb.evalLocalConstExpr(ev.X) } return nil } func (fb *ssaFuncBuilder) buildIf(s *syntax.IfStmt) { var savedVars map[Object]*SSAAlloc if s.Init != nil { savedVars = fb.saveVars() fb.buildStmt(s.Init) } if fb.currentBlock == nil { if savedVars != nil { fb.vars = savedVars } return } cond := fb.buildExpr(s.Cond) if cond == nil { if savedVars != nil { fb.vars = savedVars } return } thenBlock := fb.newBlock("if.then") var elseBlock *SSABasicBlock doneBlock := fb.newBlock("if.done") if s.Else != nil { elseBlock = fb.newBlock("if.else") } else { elseBlock = doneBlock } fb.emit(&SSAIf{Cond: cond}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, thenBlock, elseBlock) thenBlock.Preds = append(thenBlock.Preds, fb.currentBlock) elseBlock.Preds = append(elseBlock.Preds, fb.currentBlock) oldThen := fb.enterScope() thenBlock.ScopeID = fb.scopeID fb.currentBlock = thenBlock fb.buildBlock(s.Then) if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "if.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } fb.exitScope(oldThen) if s.Else != nil { oldElse := fb.enterScope() elseBlock.ScopeID = fb.scopeID fb.currentBlock = elseBlock fb.buildStmt(s.Else) if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "if.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } fb.exitScope(oldElse) } fb.currentBlock = doneBlock if savedVars != nil { fb.vars = savedVars } } func (fb *ssaFuncBuilder) buildFor(s *syntax.ForStmt) { var savedVars map[Object]*SSAAlloc if s.Init != nil { if rc, ok := s.Init.(*syntax.RangeClause); ok { fb.buildRangeLoop(s, rc) return } savedVars = fb.saveVars() fb.buildStmt(s.Init) } condBlock := fb.newBlock("for.cond") bodyBlock := fb.newBlock("for.body") postBlock := fb.newBlock("for.post") doneBlock := fb.newBlock("for.done") fb.emit(&SSAJump{Comment: "for.cond"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock) condBlock.Preds = append(condBlock.Preds, fb.currentBlock) fb.loops = append(fb.loops, ssaLoopState{label: fb.pendingLabel, body: bodyBlock, post: postBlock, done: doneBlock}) fb.pendingLabel = "" fb.currentBlock = condBlock if s.Cond != nil { cond := fb.buildExpr(s.Cond) if cond != nil { fb.emit(&SSAIf{Cond: cond}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, bodyBlock, doneBlock) bodyBlock.Preds = append(bodyBlock.Preds, condBlock) doneBlock.Preds = append(doneBlock.Preds, condBlock) } } else { fb.emit(&SSAJump{Comment: "for.body"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, bodyBlock) bodyBlock.Preds = append(bodyBlock.Preds, condBlock) } forScope := fb.enterScope() bodyBlock.ScopeID = fb.scopeID fb.currentBlock = bodyBlock fb.buildBlock(s.Body) if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "for.post"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, postBlock) postBlock.Preds = append(postBlock.Preds, fb.currentBlock) } postBlock.ScopeID = fb.scopeID fb.currentBlock = postBlock if s.Post != nil { fb.buildStmt(s.Post) } if fb.currentBlock != nil { fb.emit(&SSAJump{Comment: "for.cond"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock) condBlock.Preds = append(condBlock.Preds, fb.currentBlock) } fb.exitScope(forScope) fb.loops = fb.loops[:len(fb.loops)-1] fb.currentBlock = doneBlock if savedVars != nil { fb.vars = savedVars } } func (fb *ssaFuncBuilder) buildRangeLoop(s *syntax.ForStmt, rc *syntax.RangeClause) { savedVars := fb.saveVars() iterExpr := fb.buildExpr(rc.X) if iterExpr == nil { return } r := &SSARange{X: iterExpr} r.typ = Typ[Invalid] r.name = fb.nextName() fb.emit(r) bodyBlock := fb.newBlock("range.body") doneBlock := fb.newBlock("range.done") condBlock := fb.newBlock("range.cond") fb.loops = append(fb.loops, ssaLoopState{label: fb.pendingLabel, body: bodyBlock, post: condBlock, done: doneBlock}) fb.pendingLabel = "" fb.emit(&SSAJump{Comment: "range.cond"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock) condBlock.Preds = append(condBlock.Preds, fb.currentBlock) fb.currentBlock = condBlock iterSSAType := iterExpr.SSAType() if iterSSAType == nil { switch x := rc.X.(type) { case *syntax.SelectorExpr: if x != nil { tv := x.GetTypeInfo() if tv.Type != nil { if t, ok := tv.Type.(Type); ok { iterSSAType = t } } } case *syntax.Name: if x != nil { tv := x.GetTypeInfo() if tv.Type != nil { if t, ok := tv.Type.(Type); ok { iterSSAType = t } } } } if iterSSAType == nil { mxutil.WriteStr(2, "WARN: unresolved range type in " | fb.fn.Pkg.Pkg.Path | "." | fb.fn.Name | "\n") return } } nxt := &SSANext{Iter: r, IsString: SSAIsStringType(iterSSAType)} keyTyp := asType(Typ[Int32]) valTyp := asType(Typ[Invalid]) iterT := iterSSAType iterU := SafeUnderlying(iterT) if sl, ok := iterU.(*Slice); ok { valTyp = sl.Elem } else if sl2, ok12 := iterT.(*Slice); ok12 { valTyp = sl2.Elem } else if mt2, ok13 := iterU.(*TCMap); ok13 { keyTyp = mt2.Key valTyp = mt2.Elem } else if mt3, ok14 := iterT.(*TCMap); ok14 { keyTyp = mt3.Key valTyp = mt3.Elem } else if ar2, ok15 := iterU.(*Array); ok15 { valTyp = ar2.Elem } else if ar3, ok16 := iterT.(*Array); ok16 { valTyp = ar3.Elem } else if p, ok17 := iterU.(*Pointer); ok17 && p.Base != nil { if ar4, ok18 := SafeUnderlying(p.Base).(*Array); ok18 { valTyp = ar4.Elem } } if valTyp == asType(Typ[Invalid]) && SSAIsStringType(iterSSAType) { valTyp = asType(Typ[Uint8]) } nxt.typ = NewTuple( NewTCVar(nil, "ok", Typ[Bool]), NewTCVar(nil, "k", keyTyp), NewTCVar(nil, "v", valTyp), ) nxt.name = fb.nextName() fb.emit(nxt) okExt := &SSAExtract{Tuple: nxt, Index: 0} okExt.typ = Typ[Bool] okExt.name = fb.nextName() fb.emit(okExt) fb.emit(&SSAIf{Cond: okExt}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, bodyBlock, doneBlock) bodyBlock.Preds = append(bodyBlock.Preds, condBlock) doneBlock.Preds = append(doneBlock.Preds, condBlock) rangeScope := fb.enterScope() bodyBlock.ScopeID = fb.scopeID fb.currentBlock = bodyBlock if rc.Lhs != nil && rc.Def { names := ssaExprNames(rc.Lhs) for i, name := range names { if name.Value == "_" { continue } ext := &SSAExtract{Tuple: nxt, Index: i + 1} ext.typ = SSATupleElemType(nxt.typ, i+1) ext.name = fb.nextName() fb.emit(ext) var obj Object if fb.info != nil { obj = fb.info.Defs[name] } if obj == nil { obj = NewTCVar(fb.fn.Pkg.Pkg, name.Value, ext.typ) } fb.removeVar(name.Value) alloc := fb.emitAlloc(ext.typ, 0) fb.vars[obj] = alloc fb.emitStore(alloc, ext) } } else if rc.Lhs != nil && !rc.Def { names := ssaExprNames(rc.Lhs) for i, name := range names { if name.Value == "_" { continue } ext := &SSAExtract{Tuple: nxt, Index: i + 1} ext.typ = SSATupleElemType(nxt.typ, i+1) ext.name = fb.nextName() fb.emit(ext) fb.buildStore(name, ext) } } fb.buildBlock(s.Body) if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "range.cond"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, condBlock) condBlock.Preds = append(condBlock.Preds, fb.currentBlock) } fb.exitScope(rangeScope) fb.loops = fb.loops[:len(fb.loops)-1] fb.currentBlock = doneBlock fb.vars = savedVars } func (fb *ssaFuncBuilder) buildSwitch(s *syntax.SwitchStmt) { var savedVars map[Object]*SSAAlloc if s.Init != nil { savedVars = fb.saveVars() fb.buildStmt(s.Init) } if fb.currentBlock == nil { if savedVars != nil { fb.vars = savedVars } return } doneBlock := fb.newBlock("switch.done") savedLoops := fb.loops fb.loops = append(fb.loops, ssaLoopState{label: fb.pendingLabel, done: doneBlock}) fb.pendingLabel = "" if s.Tag != nil { if tsg, ok := s.Tag.(*syntax.TypeSwitchGuard); ok { fb.buildTypeSwitch(s, tsg, doneBlock) fb.loops = savedLoops fb.currentBlock = doneBlock if savedVars != nil { fb.vars = savedVars } return } } var tag SSAValue if s.Tag != nil { tag = fb.buildExpr(s.Tag) } for _, clause := range s.Body { caseBlock := fb.newBlock("switch.case") nextBlock := fb.newBlock("switch.next") if clause.Cases != nil && tag != nil { var cond SSAValue caseExprs := []syntax.Expr{clause.Cases} if list, ok := clause.Cases.(*syntax.ListExpr); ok { caseExprs = list.ElemList } for _, ce := range caseExprs { caseVal := fb.buildExpr(ce) cmp := &SSABinOp{Op: OpEql, X: tag, Y: caseVal} cmp.typ = Typ[Bool] cmp.name = fb.nextName() fb.emit(cmp) if cond == nil { cond = cmp } else { orOp := &SSABinOp{Op: OpLor, X: cond, Y: cmp} orOp.typ = Typ[Bool] orOp.name = fb.nextName() fb.emit(orOp) cond = orOp } } fb.emit(&SSAIf{Cond: cond}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock, nextBlock) caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock) nextBlock.Preds = append(nextBlock.Preds, fb.currentBlock) } else if clause.Cases != nil && tag == nil { cond := fb.buildExpr(clause.Cases) fb.emit(&SSAIf{Cond: cond}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock, nextBlock) caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock) nextBlock.Preds = append(nextBlock.Preds, fb.currentBlock) } else { fb.emit(&SSAJump{Comment: "switch.case"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock) caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock) } caseScope := fb.enterScope() caseBlock.ScopeID = fb.scopeID fb.currentBlock = caseBlock caseSaved := fb.saveVars() for _, stmt := range clause.Body { fb.buildStmt(stmt) if fb.currentBlock == nil { break } } if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "switch.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } fb.exitScope(caseScope) fb.vars = caseSaved fb.currentBlock = nextBlock } if fb.currentBlock != nil { fb.emit(&SSAJump{Comment: "switch.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } fb.loops = savedLoops fb.currentBlock = doneBlock if savedVars != nil { fb.vars = savedVars } } func (fb *ssaFuncBuilder) buildTypeSwitch(s *syntax.SwitchStmt, tsg *syntax.TypeSwitchGuard, doneBlock *SSABasicBlock) { x := fb.buildExpr(tsg.X) var savedObj Object var savedAlloc *SSAAlloc if tsg.Lhs != nil { for obj, alloc := range fb.vars { if ObjectName(obj) == tsg.Lhs.Value { savedObj = obj savedAlloc = alloc break } } } for _, clause := range s.Body { caseBlock := fb.newBlock("typeswitch.case") nextBlock := fb.newBlock("typeswitch.next") if clause.Cases != nil { caseExprs := []syntax.Expr{clause.Cases} if list, ok := clause.Cases.(*syntax.ListExpr); ok { caseExprs = list.ElemList } var cond SSAValue var firstTA *SSATypeAssert var firstType Type for _, ce := range caseExprs { if n, isName := ce.(*syntax.Name); isName && n.Value == "nil" { nilType := x.SSAType() if nilType == nil { nilType = NewTCInterface(nil, nil) } nilCheck := &SSABinOp{Op: OpEql, X: x, Y: &SSAConst{Typ: nilType, Val: nil}} nilCheck.typ = Typ[Bool] nilCheck.name = fb.nextName() fb.emit(nilCheck) if cond == nil { cond = nilCheck } else { orOp := &SSABinOp{Op: OpLor, X: cond, Y: nilCheck} orOp.typ = Typ[Bool] orOp.name = fb.nextName() fb.emit(orOp) cond = orOp } continue } var assertedType Type if fb.info != nil { tv := fb.info.Types[ce] assertedType = tv.Type } if assertedType == nil { assertedType = fb.resolveType(ce) } if assertedType == nil { assertedType = Typ[Invalid] } ta := &SSATypeAssert{X: x, AssertedType: assertedType, CommaOk: true} ta.typ = NewTuple( NewTCVar(nil, "val", assertedType), NewTCVar(nil, "ok", Typ[Bool]), ) ta.name = fb.nextName() fb.emit(ta) okExt := &SSAExtract{Tuple: ta, Index: 1} okExt.typ = Typ[Bool] okExt.name = fb.nextName() fb.emit(okExt) if firstTA == nil { firstTA = ta firstType = assertedType } if cond == nil { cond = okExt } else { orOp := &SSABinOp{Op: OpLor, X: cond, Y: okExt} orOp.typ = Typ[Bool] orOp.name = fb.nextName() fb.emit(orOp) cond = orOp } } fb.emit(&SSAIf{Cond: cond}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock, nextBlock) caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock) nextBlock.Preds = append(nextBlock.Preds, fb.currentBlock) fb.currentBlock = caseBlock if tsg.Lhs != nil { var guardVal SSAValue var guardType Type if firstTA != nil { ext := &SSAExtract{Tuple: firstTA, Index: 0} ext.typ = firstType ext.name = fb.nextName() fb.emit(ext) guardVal = ext guardType = firstType } else { guardType = x.SSAType() if guardType == nil { guardType = NewTCInterface(nil, nil) } guardVal = &SSAConst{Typ: guardType, Val: nil} } for old := range fb.vars { if ObjectName(old) == tsg.Lhs.Value { delete(fb.vars, old) break } } obj := NewTCVar(fb.fn.Pkg.Pkg, tsg.Lhs.Value, guardType) alloc := fb.emitAlloc(guardType, 0) fb.vars[obj] = alloc fb.emitStore(alloc, guardVal) } } else { fb.emit(&SSAJump{Comment: "typeswitch.case"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, caseBlock) caseBlock.Preds = append(caseBlock.Preds, fb.currentBlock) fb.currentBlock = caseBlock if tsg.Lhs != nil { guardType := x.SSAType() if guardType == nil { guardType = NewTCInterface(nil, nil) } for old := range fb.vars { if ObjectName(old) == tsg.Lhs.Value { delete(fb.vars, old) break } } obj := NewTCVar(fb.fn.Pkg.Pkg, tsg.Lhs.Value, guardType) alloc := fb.emitAlloc(guardType, 0) fb.vars[obj] = alloc fb.emitStore(alloc, x) } } tsScope := fb.enterScope() caseBlock.ScopeID = fb.scopeID caseSaved := fb.saveVars() for _, stmt := range clause.Body { fb.buildStmt(stmt) if fb.currentBlock == nil { break } } if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "switch.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } fb.exitScope(tsScope) fb.vars = caseSaved fb.currentBlock = nextBlock } if savedObj != nil && savedAlloc != nil { for old := range fb.vars { if ObjectName(old) == tsg.Lhs.Value { delete(fb.vars, old) break } } fb.vars[savedObj] = savedAlloc } if fb.currentBlock != nil { fb.emit(&SSAJump{Comment: "switch.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } } func (fb *ssaFuncBuilder) buildSelect(s *syntax.SelectStmt) { doneBlock := fb.newBlock("select.done") var states []*SSASelectState caseBlocks := [](*SSABasicBlock){:0:len(s.Body)} for i, clause := range s.Body { cb := fb.newBlock("select.case") caseBlocks = append(caseBlocks, cb) state := &SSASelectState{} if clause.Comm != nil { switch comm := clause.Comm.(type) { case *syntax.SendStmt: state.Dir = SelectDirSend state.Chan = fb.buildExpr(comm.Chan) state.Send = fb.buildExpr(comm.Value) case *syntax.AssignStmt: var chanExpr syntax.Expr if op, ok2 := comm.Rhs.(*syntax.Operation); ok2 && op.Y == nil && op.Op == token.Recv { chanExpr = op.X } if chanExpr != nil { state.Dir = SelectDirRecv state.Chan = fb.buildExpr(chanExpr) } case *syntax.ExprStmt: if op, ok2 := comm.X.(*syntax.Operation); ok2 && op.Y == nil && op.Op == token.Recv { state.Dir = SelectDirRecv state.Chan = fb.buildExpr(op.X) } } } states = append(states, state) _ = i } // Build tuple type with recv element types. tupleVars := []*TCVar{ NewTCVar(nil, "index", Typ[Int32]), NewTCVar(nil, "recvOk", Typ[Bool]), } recvFieldIdx := int32(2) // first recv value in tuple for _, st := range states { if st.Dir == SelectDirRecv && st.Chan != nil { elemType := SSAChanElemType(st.Chan.SSAType()) tupleVars = append(tupleVars, NewTCVar(nil, "recv", elemType)) } } hasDefault := false for _, clause := range s.Body { if clause.Comm == nil { hasDefault = true break } } sel := &SSASelect{States: states, Blocking: !hasDefault} sel.typ = &Tuple{Vars: tupleVars} sel.name = fb.nextName() fb.emit(sel) // Extract index from select result. idxExt := &SSAExtract{Tuple: sel, Index: 0} idxExt.typ = Typ[Int32] idxExt.name = fb.nextName() fb.emit(idxExt) // Build if/else chain: if index == 0 -> case0, elif index == 1 -> case1, ... entryBlock := fb.currentBlock for i := int32(0); i < int32(len(s.Body)); i++ { if i == int32(len(s.Body))-1 { // Last case: unconditional jump. fb.emit(&SSAJump{Comment: "select.last"}) entryBlock.Succs = append(entryBlock.Succs, caseBlocks[i]) caseBlocks[i].Preds = append(caseBlocks[i].Preds, entryBlock) break } nextCheck := fb.newBlock("select.check") cmpVal := &SSAConst{Typ: Typ[Int32], Val: &ConstInt{V:int64(i)}} cmp := &SSABinOp{Op: OpEql, X: idxExt, Y: cmpVal} cmp.typ = Typ[Bool] cmp.name = fb.nextName() fb.emit(cmp) fb.emit(&SSAIf{Cond: cmp}) entryBlock.Succs = append(entryBlock.Succs, caseBlocks[i]) caseBlocks[i].Preds = append(caseBlocks[i].Preds, entryBlock) entryBlock.Succs = append(entryBlock.Succs, nextCheck) nextCheck.Preds = append(nextCheck.Preds, entryBlock) fb.currentBlock = nextCheck entryBlock = nextCheck } // Build case bodies. recvIdx := recvFieldIdx for i, clause := range s.Body { selScope := fb.enterScope() caseBlocks[i].ScopeID = fb.scopeID fb.currentBlock = caseBlocks[i] // For recv cases, extract the received value from the select tuple // and bind it to the variable (don't re-do chanRecv). if clause.Comm != nil && states[i].Dir == SelectDirRecv { switch comm := clause.Comm.(type) { case *syntax.AssignStmt: // v := <-ch or v = <-ch ext := &SSAExtract{Tuple: sel, Index: recvIdx} ext.typ = SSAChanElemType(states[i].Chan.SSAType()) ext.name = fb.nextName() fb.emit(ext) if comm.Op == token.Def { names := ssaExprNames(comm.Lhs) for _, name := range names { if name.Value == "_" { continue } fb.removeVar(name.Value) obj := NewTCVar(fb.fn.Pkg.Pkg, name.Value, ext.typ) alloc := fb.emitAlloc(ext.typ, 0) fb.vars[obj] = alloc fb.emitStore(alloc, ext) } } else { fb.buildStore(comm.Lhs, ext) } } recvIdx++ } // For send cases, the send was already done by chanSelect. Skip comm. for _, stmt := range clause.Body { fb.buildStmt(stmt) if fb.currentBlock == nil { break } } if fb.currentBlock != nil && !fb.blockTerminated(fb.currentBlock) { fb.emit(&SSAJump{Comment: "select.done"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) } fb.exitScope(selScope) } fb.currentBlock = doneBlock } func (fb *ssaFuncBuilder) isSelfTailCall(e syntax.Expr) (ce *syntax.CallExpr, found bool) { call, ok := e.(*syntax.CallExpr) if !ok || fb.tailBlock == nil || fb.deferred > 0 { return nil, false } name, ok := call.Fun.(*syntax.Name) if !ok { return nil, false } obj := fb.lookupObject(name.Value) if obj == nil { return nil, false } tc, ok := obj.(*TCFunc) if !ok || tc == nil { return nil, false } fn, _ := fb.fn.Pkg.Members[name.Value].(*SSAFunction) if fn != fb.fn { return nil, false } return call, true } func (fb *ssaFuncBuilder) buildReturn(s *syntax.ReturnStmt) { if s.Results != nil { if call, ok := fb.isSelfTailCall(s.Results); ok { args := fb.buildArgs(call.ArgList) if len(args) == len(fb.paramAllocas) { for i, arg := range args { fb.emitStore(fb.paramAllocas[i], arg) } fb.emit(&SSAJump{Comment: "tailcall"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, fb.tailBlock) fb.tailBlock.Preds = append(fb.tailBlock.Preds, fb.currentBlock) fb.currentBlock = nil return } } } var vals []SSAValue if s.Results != nil { if list, ok := s.Results.(*syntax.ListExpr); ok { for i, el := range list.ElemList { v := fb.buildExpr(el) if v != nil { vals = append(vals, v) } else { var zeroType Type if fb.fn.Signature != nil && fb.fn.Signature.Results != nil && int32(i) < int32(fb.fn.Signature.Results.Len()) { zeroType = fb.fn.Signature.Results.At(int32(i)).Typ } vals = append(vals, &SSAConst{Typ: zeroType, Val: nil}) } } } else { v := fb.buildExpr(s.Results) if v != nil { if v.SSAType() == nil { vals = append(vals, v) } else if tup, ok2 := v.SSAType().(*Tuple); ok2 && tup.Len() > 1 { for i := 0; i < tup.Len(); i++ { ext := &SSAExtract{Tuple: v, Index: i} ext.typ = tup.At(i).Typ ext.name = fb.nextName() fb.emit(ext) vals = append(vals, ext) } } else { vals = append(vals, v) } } } } else if len(fb.namedResults) > 0 { for _, nr := range fb.namedResults { elemType := nr.SSAType() if p, ok := elemType.(*Pointer); ok { elemType = p.Base } vals = append(vals, fb.emitLoad(nr, elemType)) } } if fb.fn.Signature != nil && fb.fn.Signature.Results != nil { for i, v := range vals { if i < fb.fn.Signature.Results.Len() { retType := fb.fn.Signature.Results.At(i).Typ if n, ok := retType.(*Named); ok && n != nil { u := n.Underlying() if u != nil && u != Type(n) { if _, isIface := u.(*TCInterface); isIface { if _, alreadyIface := SafeUnderlying(v.SSAType()).(*TCInterface); !alreadyIface { mi := &SSAMakeInterface{X: v} mi.typ = retType mi.name = fb.nextName() fb.emit(mi) vals[i] = mi continue } } } } vals[i] = fb.coerceToInterface(v, retType) } } } fb.emitReturn(vals, 0) } func (fb *ssaFuncBuilder) buildBranch(s *syntax.BranchStmt) { if fb.currentBlock == nil { return } switch s.Tok { case token.Break: if len(fb.loops) > 0 { var doneBlock *SSABasicBlock if s.Label != nil { for i := len(fb.loops) - 1; i >= 0; i-- { if fb.loops[i].label == s.Label.Value { doneBlock = fb.loops[i].done break } } } if doneBlock == nil { doneBlock = fb.loops[len(fb.loops)-1].done } if doneBlock != nil { fb.emit(&SSAJump{Comment: "break"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, doneBlock) doneBlock.Preds = append(doneBlock.Preds, fb.currentBlock) fb.currentBlock = nil } } case token.Continue: for i := len(fb.loops) - 1; i >= 0; i-- { postBlock := fb.loops[i].post if postBlock == nil { postBlock = fb.loops[i].body } if postBlock == nil { continue } fb.emit(&SSAJump{Comment: "continue"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, postBlock) postBlock.Preds = append(postBlock.Preds, fb.currentBlock) fb.currentBlock = nil break } case token.Goto: if s.Label != nil { target := fb.labelBlock(s.Label.Value) if target != nil { fb.emit(&SSAJump{Comment: "goto"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, target) target.Preds = append(target.Preds, fb.currentBlock) fb.currentBlock = nil } } case token.Return: fb.emitReturn(nil, 0) } } func (fb *ssaFuncBuilder) buildGoStmt(call *syntax.CallExpr) { fn := fb.buildExpr(call.Fun) if fn == nil { return } args := fb.buildArgs(call.ArgList) g := &SSAGo{Call: SSACallCommon{Value: fn, Args: args}} fb.emit(g) } func (fb *ssaFuncBuilder) buildDeferStmt(call *syntax.CallExpr) { fn := fb.buildExpr(call.Fun) if fn == nil { return } args := fb.buildArgs(call.ArgList) d := &SSADefer{Call: SSACallCommon{Value: fn, Args: args}} fb.emit(d) fb.deferred++ } // Expression builders. func (fb *ssaFuncBuilder) buildExpr(e syntax.Expr) (s SSAValue) { if e == nil || fb.currentBlock == nil { return nil } switch ev := e.(type) { case *syntax.Name: return fb.buildIdent(ev) case *syntax.BasicLit: return fb.buildLit(ev) case *syntax.Operation: return fb.buildOperation(ev) case *syntax.CallExpr: return fb.buildCall(ev) case *syntax.SelectorExpr: return fb.buildSelector(ev) case *syntax.IndexExpr: return fb.buildIndex(ev) case *syntax.SliceExpr: return fb.buildSlice(ev) case *syntax.AssertExpr: return fb.buildAssert(ev) case *syntax.CompositeLit: return fb.buildCompositeLit(ev) case *syntax.FuncLit: return fb.buildFuncLit(ev) case *syntax.ParenExpr: return fb.buildExpr(ev.X) case *syntax.ListExpr: var last SSAValue for _, el := range ev.ElemList { last = fb.buildExpr(el) } return last case *syntax.KeyValueExpr: return fb.buildExpr(ev.Value) } return nil } func (fb *ssaFuncBuilder) buildIdent(e *syntax.Name) (s SSAValue) { if e.Value == "_" || e.Value == "nil" { return &SSAConst{Typ: nil, Val: nil} } if fb.localConsts != nil { if c, ok := fb.localConsts[e.Value]; ok { return c } } obj := fb.lookupObject(e.Value) if obj == nil && fb.info != nil { obj = fb.info.Uses[e] } if obj == nil && fb.parent != nil { for p := fb.parent; p != nil; p = p.parent { for pobj := range p.vars { if ObjectName(pobj) == e.Value { fv := fb.getOrCreateFreeVar(e.Value, ObjectType(pobj)) return fb.emitLoad(fv, ObjectType(pobj)) } } if p.freeVarPtrs != nil { if pfv, ok := p.freeVarPtrs[e.Value]; ok { fv := fb.getOrCreateFreeVar(e.Value, pfv.typ) return fb.emitLoad(fv, pfv.typ) } } } } if obj == nil { if fb.fn.Pkg != nil { if fn, ok := fb.fn.Pkg.Members[e.Value].(*SSAFunction); ok { return fn } if g, ok := fb.fn.Pkg.Members[e.Value].(*SSAGlobal); ok { return fb.emitLoad(g, g.Typ) } if nc, ok := fb.fn.Pkg.Members[e.Value].(*SSANamedConst); ok { return nc.Value } } return fb.builtinValue(e.Value) } switch ob := obj.(type) { case *TCVar: if alloc, ok := fb.vars[ob]; ok { return fb.emitLoad(alloc, ob.Typ) } if fv, ok := fb.freeVarPtrs[e.Value]; ok { return fb.emitLoad(fv, ob.Typ) } for p := fb.parent; p != nil; p = p.parent { found := false if _, ok := p.vars[ob]; ok { found = true } if !found { for pobj := range p.vars { if ObjectName(pobj) == e.Value { found = true break } } } if !found && p.freeVarPtrs != nil { if _, ok := p.freeVarPtrs[e.Value]; ok { found = true } } if found { fv := fb.getOrCreateFreeVar(e.Value, ob.Typ) return fb.emitLoad(fv, ob.Typ) } } if g, ok := fb.fn.Pkg.Members[e.Value].(*SSAGlobal); ok { return fb.emitLoad(g, ob.Typ) } // dot-imported variable from another package if ob.Pkg != nil && ob.Pkg != fb.fn.Pkg.Pkg { impPkg := fb.ensureImportedSSAPackage(ob.Pkg) g, ok := impPkg.Members[e.Value].(*SSAGlobal) if !ok { g = &SSAGlobal{ Name: e.Value, Typ: NewPointer(ob.Typ), Pkg: impPkg, } impPkg.SetMember(e.Value, g) } return fb.emitLoad(g, ob.Typ) } return &SSAConst{Typ: ob.Typ, Val: nil} case *TCConst: return &SSAConst{Typ: ob.Typ, Val: localizeConstVal(ob.Val, ob.Typ)} case *TCFunc: fn, _ := fb.fn.Pkg.Members[e.Value].(*SSAFunction) if fn != nil { return fn } // dot-imported function from another package if ob.Pkg != nil && ob.Pkg != fb.fn.Pkg.Pkg { impPkg := fb.ensureImportedSSAPackage(ob.Pkg) fn = impPkg.Func(e.Value) if fn == nil { sig, _ := ob.Typ.(*Signature) fn = &SSAFunction{ Name: e.Value, Object: ob, Signature: sig, Pkg: impPkg, Prog: fb.fn.Prog, } impPkg.SetMember(e.Value, fn) } return fn } return &SSAConst{Typ: ob.Typ, Val: nil} case *TypeName: return nil case *Builtin: return &SSABuiltin{ID: ob.Id, name: ob.Name} } return nil } func (fb *ssaFuncBuilder) builtinValue(name string) (s SSAValue) { id, ok := SSABuiltinID(name) if !ok { return nil } return &SSABuiltin{ID: id, name: name} } func (fb *ssaFuncBuilder) buildLit(e *syntax.BasicLit) (s SSAValue) { cv := EvalBasicLitLocal(e) if cv == nil { return nil } switch e.Kind { case token.IntLit: return &SSAConst{Typ: Typ[UntypedInt], Val: cv} case token.FloatLit: return &SSAConst{Typ: Typ[UntypedFloat], Val: cv} case token.StringLit: return &SSAConst{Typ: Typ[UntypedString], Val: cv} case token.RuneLit: return &SSAConst{Typ: Typ[UntypedRune], Val: cv} } return nil } func (fb *ssaFuncBuilder) buildOperation(e *syntax.Operation) (s SSAValue) { if e.Y == nil { if e.Op == token.Add { return fb.buildExpr(e.X) } op2 := SyntaxOpToSSAOp(e.Op, true) if op2 == OpAnd { if name, ok := e.X.(*syntax.Name); ok { obj := fb.lookupObject(name.Value) if obj != nil { if alloc, ok2 := fb.vars[obj]; ok2 { return alloc } if g, ok2 := fb.fn.Pkg.Members[name.Value].(*SSAGlobal); ok2 { return g } } } if ie, ok := e.X.(*syntax.IndexExpr); ok { idx := fb.buildExpr(ie.Index) if idx == nil { return nil } var base SSAValue if sel, ok2 := ie.X.(*syntax.SelectorExpr); ok2 { base = fb.buildSelectorAddr(sel) } if base == nil { base = fb.buildExpr(ie.X) } if base == nil { return nil } // &s.field[i] where field is a slice: base is a pointer to // the slice header; load the header so IndexAddr indexes the // slice's data pointer, not the header itself. if pt, okp := SafeUnderlying(base.SSAType()).(*Pointer); okp && pt.Base != nil { pte := pt.Base pteu := SafeUnderlying(pte) isSl := false if _, ok2 := pteu.(*Slice); ok2 { isSl = true } else if bb, okb := pteu.(*Basic); okb && bb.Info&IsString != 0 { isSl = true } if isSl { base = fb.emitLoad(base, pte) } } ia := &SSAIndexAddr{X: base, Index: idx} ia.typ = NewPointer(SSAElemType(base.SSAType())) ia.name = fb.nextName() fb.emit(ia) return ia } if sel, ok := e.X.(*syntax.SelectorExpr); ok { addr := fb.buildSelectorAddr(sel) if addr != nil { return addr } } xv2 := fb.buildExpr(e.X) if xv2 == nil { return nil } a := &SSAAlloc{Heap: true} a.typ = NewPointer(xv2.SSAType()) a.name = fb.nextName() fb.emit(a) fb.emitStore(a, xv2) return a } xv3 := fb.buildExpr(e.X) if xv3 == nil { return nil } if op2 == OpArrow { u2 := &SSAUnOp{Op: OpArrow, X: xv3} u2.typ = SSAChanElemType(xv3.SSAType()) u2.name = fb.nextName() fb.emit(u2) return u2 } u := &SSAUnOp{Op: op2, X: xv3} if op2 == OpMul { if p, ok := xv3.SSAType().(*Pointer); ok { u.typ = p.Base } else { u.typ = xv3.SSAType() } } else { u.typ = xv3.SSAType() } u.name = fb.nextName() fb.emit(u) return u } op := SyntaxOpToSSAOp(e.Op, false) if op == OpLand || op == OpLor { xv4 := fb.buildExpr(e.X) if xv4 == nil { return nil } rhsBlock := fb.newBlock("sc.rhs") mergeBlock := fb.newBlock("sc.merge") entryBlock := fb.currentBlock fb.emit(&SSAIf{Cond: xv4}) if op == OpLand { fb.currentBlock.Succs = append(fb.currentBlock.Succs, rhsBlock, mergeBlock) } else { fb.currentBlock.Succs = append(fb.currentBlock.Succs, mergeBlock, rhsBlock) } rhsBlock.Preds = append(rhsBlock.Preds, entryBlock) mergeBlock.Preds = append(mergeBlock.Preds, entryBlock) fb.currentBlock = rhsBlock yv := fb.buildExpr(e.Y) if yv == nil { yv = &SSAConst{Typ: Typ[Bool], Val: &ConstBool{V:false}} } fb.emit(&SSAJump{Comment: "sc.merge"}) fb.currentBlock.Succs = append(fb.currentBlock.Succs, mergeBlock) mergeBlock.Preds = append(mergeBlock.Preds, fb.currentBlock) fb.currentBlock = mergeBlock var shortVal SSAValue if op == OpLand { shortVal = &SSAConst{Typ: Typ[Bool], Val: &ConstBool{V:false}} } else { shortVal = &SSAConst{Typ: Typ[Bool], Val: &ConstBool{V:true}} } phi := &SSAPhi{Edges: []SSAValue{shortVal, yv}, Comment: "sc"} phi.typ = Typ[Bool] phi.name = fb.nextName() fb.emit(phi) return phi } x := fb.buildExpr(e.X) y := fb.buildExpr(e.Y) if x == nil || y == nil { return nil } if (op == OpOr || op == OpAdd) && x.SSAType() != nil && y.SSAType() != nil { if IsRuneKind(x.SSAType()) && IsStringKind(y.SSAType()) || IsStringKind(x.SSAType()) && IsRuneKind(y.SSAType()) { mxutil.WriteStr(2, "compile error: cannot concatenate rune with string - convert to UTF-8 first\n") fb.fn.Prog.Errors = append(fb.fn.Prog.Errors, "cannot concatenate rune with string") } } if xc, ok := x.(*SSAConst); ok { if yc, ok2 := y.(*SSAConst); ok2 { if xc.Val != nil && yc.Val != nil { folded := EvalBinaryLocal(e.Op, xc.Val, yc.Val) if folded != nil { ct := xc.Typ if ct == nil { ct = yc.Typ } if _, isBool := folded.(*ConstBool); isBool { ct = Typ[Bool] } return &SSAConst{Typ: ct, Val: folded} } } } } b := &SSABinOp{Op: op, X: x, Y: y} b.name = fb.nextName() switch op { case OpEql, OpNeq, OpLss, OpLeq, OpGtr, OpGeq: b.typ = Typ[Bool] default: xT := x.SSAType() yT := y.SSAType() xUntyped := false if xB, ok := SafeUnderlying(xT).(*Basic); ok && xB.Info&IsUntyped != 0 { xUntyped = true } if xT != nil && !xUntyped { b.typ = xT } else if yT != nil { b.typ = yT } else { b.typ = xT } } fb.emit(b) return b } func (fb *ssaFuncBuilder) buildCall(e *syntax.CallExpr) (s SSAValue) { if ie, ok := e.Fun.(*syntax.IndexExpr); ok { if result, handled := fb.tryGenericCallFromIndex(ie, e.ArgList, e.HasDots); handled { return result } } if name, ok := e.Fun.(*syntax.Name); ok { obj := fb.lookupObject(name.Value) if tn, ok2 := obj.(*TypeName); ok2 { if _, isTP := tn.Typ.(*TypeParam); !isTP { args2 := fb.buildArgs(e.ArgList) if len(args2) == 1 && args2[0] != nil { conv := &SSAConvert{X: args2[0]} conv.typ = tn.Typ conv.name = fb.nextName() fb.emit(conv) return conv } return nil } } if result, handled := fb.tryGenericLocalCall(name.Value, e.ArgList, e.HasDots); handled { return result } } if convType := fb.resolveTypeAST(e.Fun); convType != nil { args3 := fb.buildArgs(e.ArgList) if len(args3) == 1 && args3[0] != nil { conv := &SSAConvert{X: args3[0]} conv.typ = convType conv.name = fb.nextName() fb.emit(conv) return conv } return nil } if sel, ok := e.Fun.(*syntax.SelectorExpr); ok { if name, ok2 := sel.X.(*syntax.Name); ok2 { obj := fb.lookupObject(name.Value) if pn, ok3 := obj.(*PkgName); ok3 { return fb.buildPkgCall(pn, sel.Sel.Value, e.ArgList, e.HasDots) } } recv := fb.buildExpr(sel.X) if recv != nil && recv.SSAType() != nil { if fn2, fixedRecv := fb.resolveMethodCallWithRecv(sel, recv); fn2 != nil { fixedRecv = fb.derefValueRecv(fn2, fixedRecv) args4 := fb.buildArgs(e.ArgList) if fn2.Signature != nil { if fn2.Signature.Variadic && !e.HasDots { args4 = fb.wrapVariadicArgs(args4, fn2.Signature) } args4 = fb.coerceArgsToInterface(args4, fn2.Signature) } allArgs := []SSAValue{:0:len(args4) + 1} allArgs = append(allArgs, fixedRecv) for _, ma := range args4 { allArgs = append(allArgs, ma) } var retType2 Type if fn2.Signature != nil { if fn2.Signature.Results != nil && fn2.Signature.Results.Len() == 1 { retType2 = fn2.Signature.Results.At(0).Typ } else if fn2.Signature.Results != nil && fn2.Signature.Results.Len() > 1 { retType2 = fn2.Signature.Results } } call2 := &SSACall{Call: SSACallCommon{Value: fn2, Args: allArgs}} call2.typ = retType2 call2.name = fb.nextName() fb.emit(call2) if retType2 == nil { return nil } return call2 } if inv := fb.buildIfaceMethodCall(sel, recv, e.ArgList); inv != nil { return inv } } } fn := fb.buildExpr(e.Fun) args := fb.buildArgs(e.ArgList) if fn == nil { return nil } if bi, ok := fn.(*SSABuiltin); ok { return fb.buildBuiltinCall(e, bi, args) } var retType Type if fn.SSAType() != nil { if sig, ok := SafeUnderlying(fn.SSAType()).(*Signature); ok { if sig.Results != nil && sig.Results.Len() == 1 { retType = sig.Results.At(0).Typ } else if sig.Results != nil && sig.Results.Len() > 1 { retType = sig.Results } } } else if ssaFn, ok := fn.(*SSAFunction); ok && ssaFn.Signature != nil { sig := ssaFn.Signature if sig.Results != nil && sig.Results.Len() == 1 { retType = sig.Results.At(0).Typ } else if sig.Results != nil && sig.Results.Len() > 1 { retType = sig.Results } } if fn.SSAType() != nil { if sig, ok := SafeUnderlying(fn.SSAType()).(*Signature); ok { if sig.Variadic && !e.HasDots { args = fb.wrapVariadicArgs(args, sig) } args = fb.coerceArgsToInterface(args, sig) } } call := &SSACall{Call: SSACallCommon{Value: fn, Args: args}} call.typ = retType call.name = fb.nextName() fb.emit(call) if retType == nil { return nil } return call } func (fb *ssaFuncBuilder) coerceArgsToInterface(args []SSAValue, sig *Signature) (ss []SSAValue) { if sig.Params == nil { return args } for i := 0; i < len(args) && i < sig.Params.Len(); i++ { paramType := sig.Params.At(i).Typ if paramType == nil || args[i] == nil || args[i].SSAType() == nil { continue } _, isIface := SafeUnderlying(paramType).(*TCInterface) if !isIface { continue } _, argIsIface := SafeUnderlying(args[i].SSAType()).(*TCInterface) if argIsIface { continue } mi := &SSAMakeInterface{X: args[i], IType: paramType} mi.typ = paramType mi.name = fb.nextName() fb.emit(mi) args[i] = mi } return args } func (fb *ssaFuncBuilder) wrapVariadicArgs(args []SSAValue, sig *Signature) (ss []SSAValue) { if sig.Params == nil { return args } nFixed := sig.Params.Len() - 1 if nFixed < 0 { nFixed = 0 } if nFixed > len(args) { nFixed = len(args) } nVariadic := len(args) - nFixed if nVariadic <= 0 { nilSlice := NewSSAConst(&ConstNil{}, sig.Params.At(sig.Params.Len()-1).Typ) result2 := []SSAValue{:nFixed:nFixed} copy(result2, args[:nFixed]) return append(result2, nilSlice) } sliceType := sig.Params.At(sig.Params.Len() - 1).Typ var elemType Type if sl, ok := SafeUnderlying(sliceType).(*Slice); ok { elemType = sl.Elem } // HasDots lost because closures can't write to captured named returns. // Detect spread: if exactly 1 variadic arg whose type matches the // variadic param's slice type, pass it through. This covers both // concrete T and interface{} variadics (e.g. fmt.Print -> fmt.Fprint). if nVariadic == 1 && elemType != nil { argT := args[nFixed].SSAType() if argT != nil && Identical(argT, sliceType) { return args } } lenVal := NewSSAConst(&ConstInt{V:int64(nVariadic)}, Typ[Int32]) ms := &SSAMakeSlice{Len: lenVal, Cap: lenVal} ms.typ = sliceType ms.name = fb.nextName() fb.emit(ms) for i := 0; i < nVariadic; i++ { idx := NewSSAConst(&ConstInt{V:int64(i)}, Typ[Int32]) ia := &SSAIndexAddr{X: ms, Index: idx} ia.typ = NewPointer(elemType) ia.name = fb.nextName() fb.emit(ia) v := args[nFixed+i] if elemType != nil { v = fb.coerceToInterface(v, elemType) } fb.emitStore(ia, v) } result := []SSAValue{:nFixed:nFixed} copy(result, args[:nFixed]) return append(result, ms) } func (fb *ssaFuncBuilder) buildArgs(argList []syntax.Expr) (ss []SSAValue) { args := []SSAValue{:0:len(argList)} for _, a := range argList { v := fb.buildExpr(a) if v != nil { args = append(args, v) } } if len(args) == 1 && args[0] != nil { if t, ok := args[0].SSAType().(*Tuple); ok && t.Len() > 1 { expanded := []SSAValue{:0:t.Len()} for i := int32(0); i < int32(t.Len()); i++ { ext := &SSAExtract{Tuple: args[0], Index: int32(i)} ext.typ = t.At(int32(i)).Typ ext.name = fb.nextName() fb.emit(ext) expanded = append(expanded, ext) } return expanded } } return args } func (fb *ssaFuncBuilder) buildBuiltinCall(e *syntax.CallExpr, b *SSABuiltin, args []SSAValue) (s SSAValue) { switch b.ID { case BuiltinLen, BuiltinCap: if b.ID == BuiltinLen && len(args) == 1 { if c, ok := args[0].(*SSAConst); ok { if cs, ok2 := c.Val.(*ConstStr); ok2 { return &SSAConst{Typ: Typ[Int32], Val: &ConstInt{V:int64(len(cs.S))}} } } } call2 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} call2.typ = Typ[Int32] call2.name = fb.nextName() fb.emit(call2) return call2 case BuiltinAppend: if len(args) > 1 { if sl, ok := SafeUnderlying(args[0].SSAType()).(*Slice); ok { if _, isIface := SafeUnderlying(sl.Elem).(*TCInterface); isIface { for i := 1; i < len(args); i++ { if e.HasDots && i == 1 { continue } if args[i] == nil || args[i].SSAType() == nil { continue } if _, argIface := SafeUnderlying(args[i].SSAType()).(*TCInterface); argIface { continue } mi := &SSAMakeInterface{X: args[i], IType: sl.Elem} mi.typ = sl.Elem mi.name = fb.nextName() fb.emit(mi) args[i] = mi } } } } call3 := &SSACall{Call: SSACallCommon{Value: b, Args: args, HasDots: e.HasDots}} if len(args) > 0 { call3.typ = args[0].SSAType() } call3.name = fb.nextName() fb.emit(call3) return call3 case BuiltinMake: if len(e.ArgList) == 0 { return nil } typ := fb.resolveType(e.ArgList[0]) return fb.emitMake(typ, args) case BuiltinNew: if len(e.ArgList) == 0 { return nil } typ := fb.resolveType(e.ArgList[0]) a := &SSAAlloc{Heap: true} a.typ = NewPointer(typ) a.name = fb.nextName() fb.emit(a) return a case BuiltinPanic: if len(args) > 0 { fb.emit(&SSAPanic{X: args[0]}) fb.currentBlock = nil } return nil case BuiltinClose, BuiltinDelete, BuiltinClear, BuiltinPrint, BuiltinPrintln: call4 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} call4.name = fb.nextName() fb.emit(call4) return nil case BuiltinCopy: call5 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} call5.typ = Typ[Int32] call5.name = fb.nextName() fb.emit(call5) return call5 case BuiltinRecover: call6 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} call6.typ = NewTCInterface(nil, nil) call6.name = fb.nextName() fb.emit(call6) return call6 case BuiltinSpawn: call7 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} call7.typ = NewTCChan(TCRecvOnly, Typ[TCString]) call7.name = fb.nextName() fb.emit(call7) return call7 case BuiltinMin, BuiltinMax: call8 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} if len(args) > 0 && args[0] != nil { call8.typ = args[0].SSAType() } else { call8.typ = Typ[Int32] } call8.name = fb.nextName() fb.emit(call8) return call8 } call9 := &SSACall{Call: SSACallCommon{Value: b, Args: args}} call9.name = fb.nextName() fb.emit(call9) return call9 } func (fb *ssaFuncBuilder) emitMake(typ Type, sizeArgs []SSAValue) (s SSAValue) { if typ == nil { return nil } switch u := SafeUnderlying(typ).(type) { case *Basic: if u.Kind == TCString { ms := &SSAMakeSlice{} ms.typ = typ ms.name = fb.nextName() if len(sizeArgs) > 0 { ms.Len = sizeArgs[0] } if len(sizeArgs) > 1 { ms.Cap = sizeArgs[1] } fb.emit(ms) return ms } case *Slice: ms := &SSAMakeSlice{} ms.typ = typ ms.name = fb.nextName() if len(sizeArgs) > 0 { ms.Len = sizeArgs[0] } if len(sizeArgs) > 1 { ms.Cap = sizeArgs[1] } fb.emit(ms) return ms case *TCMap: mm := &SSAMakeMap{} mm.typ = typ mm.name = fb.nextName() if len(sizeArgs) > 0 { mm.Reserve = sizeArgs[0] } fb.emit(mm) return mm case *TCChan: mc := &SSAMakeChan{} mc.typ = typ mc.name = fb.nextName() if len(sizeArgs) > 0 { mc.Size = sizeArgs[0] } fb.emit(mc) return mc } return nil } func (fb *ssaFuncBuilder) buildSelector(e *syntax.SelectorExpr) (s SSAValue) { if name, ok := e.X.(*syntax.Name); ok { obj := fb.lookupObject(name.Value) if pn, ok2 := obj.(*PkgName); ok2 { return fb.buildPkgMember(pn, e.Sel.Value) } } fa := fb.buildSelectorAddr(e) if fa != nil { elemType := fa.SSAType() if p, ok := SafeUnderlying(elemType).(*Pointer); ok { elemType = p.Base } return fb.emitLoad(fa, elemType) } recv := fb.buildExpr(e.X) if recv == nil { return nil } return fb.buildMethodValue(e, recv) } func (fb *ssaFuncBuilder) buildMethodValue(sel *syntax.SelectorExpr, recv SSAValue) (s SSAValue) { fn, fixedRecv := fb.resolveMethodCallWithRecv(sel, recv) if fn == nil || fn.Signature == nil { return nil } methodSig := fn.Signature var wrapperParams *Tuple if methodSig.Params != nil && methodSig.Params.Len() > 0 { vars := []*TCVar{:0:methodSig.Params.Len()} for i := 0; i < methodSig.Params.Len(); i++ { vars = append(vars, methodSig.Params.At(i)) } wrapperParams = NewTuple(vars...) } wrapperSig := NewSignature(nil, wrapperParams, methodSig.Results, methodSig.Variadic) name := fb.fn.Name | "__mval" | SSAItoa(len(fb.fn.AnonFuncs)+1) wrapper := &SSAFunction{ Name: name, Signature: wrapperSig, Pkg: fb.fn.Pkg, Prog: fb.fn.Prog, Parent: fb.fn, } fb.fn.AnonFuncs = append(fb.fn.AnonFuncs, wrapper) recvType := fixedRecv.SSAType() fv := &SSAFreeVar{name: "recv", typ: recvType, parent: wrapper} wrapper.FreeVars = append(wrapper.FreeVars, fv) if wrapperParams != nil { for i := 0; i < wrapperParams.Len(); i++ { v := wrapperParams.At(i) p := &SSAParameter{name: v.Name, typ: v.Typ, parent: wrapper} wrapper.Params = append(wrapper.Params, p) } } entry := NewSSABasicBlock(wrapper, "entry") args := []SSAValue{:0:1 + len(wrapper.Params)} args = append(args, fv) for _, p := range wrapper.Params { args = append(args, p) } var retType Type if methodSig.Results != nil && methodSig.Results.Len() == 1 { retType = methodSig.Results.At(0).Typ } else if methodSig.Results != nil && methodSig.Results.Len() > 1 { retType = methodSig.Results } call := &SSACall{Call: SSACallCommon{Value: fn, Args: args}} call.typ = retType call.name = "t1" call.setBlock(entry) entry.Instrs = append(entry.Instrs, call) ret := &SSAReturn{} if retType != nil { ret.Results = []SSAValue{call} } ret.setBlock(entry) entry.Instrs = append(entry.Instrs, ret) mc := &SSAMakeClosure{Fn: wrapper, Bindings: []SSAValue{fixedRecv}} mc.typ = wrapperSig mc.name = fb.nextName() fb.emit(mc) return mc } func (fb *ssaFuncBuilder) ensureImportedSSAPackage(imported *TCPackage) (s *SSAPackage) { impPkg := fb.fn.Prog.ImportedPackage(imported.Path) if impPkg == nil { impPkg = &SSAPackage{ Prog: fb.fn.Prog, Pkg: imported, Members: map[string]SSAMember{}, } fb.fn.Prog.imported[imported.Path] = impPkg fb.fn.Prog.packages[imported] = impPkg } return impPkg } func (fb *ssaFuncBuilder) buildPkgMember(pn *PkgName, memberName string) (s SSAValue) { imported := pn.Imported if imported == nil { return nil } obj := imported.Scope.Lookup(memberName) if obj == nil { return nil } // runtime.GOOS and runtime.GOARCH are declared as TCVar in the // skeleton but are target-dependent consts. Return them as SSAConst // so the SSA builder sees string values, not global loads. if imported.Path == "runtime" { if memberName == "GOOS" { return &SSAConst{Typ: Typ[TCString], Val: &ConstStr{S:fb.fn.Prog.TargetOS}} } if memberName == "GOARCH" { return &SSAConst{Typ: Typ[TCString], Val: &ConstStr{S:fb.fn.Prog.TargetArch}} } } switch ob := obj.(type) { case *TCConst: return &SSAConst{Typ: ob.Typ, Val: localizeConstVal(ob.Val, ob.Typ)} case *TCVar: impPkg := fb.ensureImportedSSAPackage(imported) g, ok := impPkg.Members[memberName].(*SSAGlobal) if !ok { g = &SSAGlobal{ Name: memberName, Typ: NewPointer(ob.Typ), Pkg: impPkg, } impPkg.SetMember(memberName, g) } return fb.emitLoad(g, ob.Typ) case *TCFunc: impPkg := fb.ensureImportedSSAPackage(imported) fn := impPkg.Func(memberName) if fn == nil { // Materialize the imported function for use as a value; the // direct-call path does the same lazily, but a func referenced // as an argument never goes through it. sig, _ := ob.Typ.(*Signature) fn = &SSAFunction{ Name: memberName, Object: ob, Signature: sig, Pkg: impPkg, Prog: fb.fn.Prog, } if isNoContextExtern(imported.Path, memberName) { fn.isExternC = true } impPkg.SetMember(memberName, fn) } return fn } return nil } func (fb *ssaFuncBuilder) buildSelectorAddr(e *syntax.SelectorExpr) (s SSAValue) { var addr SSAValue var structType Type if inner, ok := e.X.(*syntax.SelectorExpr); ok { addr = fb.buildSelectorAddr(inner) if addr == nil { return nil } structType = addr.SSAType() if p2, ok19 := SafeUnderlying(structType).(*Pointer); ok19 { structType = p2.Base } if p3, ok20 := SafeUnderlying(structType).(*Pointer); ok20 { ld := &SSAUnOp{Op: OpMul, X: addr} ld.typ = structType ld.name = fb.nextName() fb.emit(ld) addr = ld structType = p3.Base } } else if ie2, ok21 := e.X.(*syntax.IndexExpr); ok21 { x := fb.buildExpr(ie2.X) idx := fb.buildExpr(ie2.Index) if x == nil || idx == nil { return nil } ia := &SSAIndexAddr{X: x, Index: idx} elemT := SSAElemType(x.SSAType()) ia.typ = NewPointer(elemT) ia.name = fb.nextName() fb.emit(ia) addr = ia structType = elemT if _, isPtr := SafeUnderlying(elemT).(*Pointer); isPtr { ld := fb.emitLoad(ia, elemT) addr = ld structType = elemT } } else { x := fb.buildExpr(e.X) if x == nil || x.SSAType() == nil { if name2, ok22 := e.X.(*syntax.Name); ok22 { obj := fb.lookupObject(name2.Value) if pn2, ok23 := obj.(*PkgName); ok23 { return fb.buildPkgMemberAddr(pn2, e.Sel.Value) } } return nil } baseType := x.SSAType() addr = x if _, ok24 := SafeUnderlying(baseType).(*Pointer); !ok24 { found := false if name3, ok25 := e.X.(*syntax.Name); ok25 { obj := fb.lookupObject(name3.Value) if obj != nil { if alloc, ok2 := fb.vars[obj]; ok2 { addr = alloc baseType = NewPointer(baseType) found = true } else if fb.freeVarPtrs != nil { if fv2, ok26 := fb.freeVarPtrs[name3.Value]; ok26 { addr = fv2 baseType = NewPointer(baseType) found = true } } } } if !found { alloc := &SSAAlloc{Comment: "fieldaddr.tmp"} alloc.typ = NewPointer(baseType) alloc.name = fb.nextName() fb.emit(alloc) fb.emitStore(alloc, x) addr = alloc baseType = NewPointer(baseType) } } structType = baseType if p4, ok27 := SafeUnderlying(structType).(*Pointer); ok27 { structType = p4.Base } } fieldIdx := fb.fieldIndex(structType, e.Sel.Value) if fieldIdx < 0 { _ = structType } if fieldIdx >= 0 { fa := &SSAFieldAddr{X: addr, Field: fieldIdx} fa.typ = NewPointer(fb.fieldType(structType, fieldIdx)) fa.name = fb.nextName() fb.emit(fa) return fa } path := fb.findEmbeddedFieldPath(structType, e.Sel.Value) if len(path) >= 2 { cur := addr curType := structType for pi, idx := range path { fa := &SSAFieldAddr{X: cur, Field: idx} fa.typ = NewPointer(fb.fieldType(curType, idx)) fa.name = fb.nextName() fb.emit(fa) curType = fb.fieldType(curType, idx) if pi < len(path)-1 { if p, ok := SafeUnderlying(curType).(*Pointer); ok { ld := &SSAUnOp{Op: OpMul, X: fa} ld.typ = curType ld.name = fb.nextName() fb.emit(ld) cur = ld curType = p.Base continue } } cur = fa } return cur } return nil } func (fb *ssaFuncBuilder) buildIndex(e *syntax.IndexExpr) (s SSAValue) { x := fb.buildExpr(e.X) idx := fb.buildExpr(e.Index) if x == nil || idx == nil { return nil } if x.SSAType() == nil { return nil } switch t := SafeUnderlying(x.SSAType()).(type) { case *TCMap: idx = fb.coerceToInterface(idx, t.Key) l := &SSALookup{X: x, Index: idx} l.typ = t.Elem l.name = fb.nextName() fb.emit(l) return l case *Pointer: if arr, ok2 := SafeUnderlying(t.Base).(*Array); ok2 { ia2 := &SSAIndexAddr{X: x, Index: idx} ia2.typ = NewPointer(t.Base) ia2.name = fb.nextName() fb.emit(ia2) return fb.emitLoad(ia2, arr.Elem) } et := SSAElemType(x.SSAType()) ia := &SSAIndexAddr{X: x, Index: idx} ia.typ = NewPointer(et) ia.name = fb.nextName() fb.emit(ia) return fb.emitLoad(ia, et) default: et := SSAElemType(x.SSAType()) ia := &SSAIndexAddr{X: x, Index: idx} ia.typ = NewPointer(et) ia.name = fb.nextName() fb.emit(ia) return fb.emitLoad(ia, et) } } func (fb *ssaFuncBuilder) buildSlice(e *syntax.SliceExpr) (s SSAValue) { // Slicing an addressable array must alias its storage: arr[:] passed to // a writer mutates arr. Building the array VALUE would slice a copy. x := fb.buildArrayAddr(e.X) if x == nil { x = fb.buildExpr(e.X) } if x == nil { return nil } sl := &SSASlice{X: x} if len(e.Index) > 0 && e.Index[0] != nil { sl.Low = fb.buildExpr(e.Index[0]) } if len(e.Index) > 1 && e.Index[1] != nil { sl.High = fb.buildExpr(e.Index[1]) } if len(e.Index) > 2 && e.Index[2] != nil { sl.Max = fb.buildExpr(e.Index[2]) } sl.typ = SSASliceOf(x.SSAType()) sl.name = fb.nextName() fb.emit(sl) return sl } // buildArrayAddr returns the address of an addressable array-typed // expression, or nil if the expression is not an addressable array. func (fb *ssaFuncBuilder) buildArrayAddr(x syntax.Expr) (s SSAValue) { switch ex := x.(type) { case *syntax.ParenExpr: return fb.buildArrayAddr(ex.X) case *syntax.Operation: if ex.Y == nil && ex.Op == token.Mul { // (*p)[low:high] - p already points at the array p := fb.buildExpr(ex.X) if p != nil { if pt, ok := SafeUnderlying(p.SSAType()).(*Pointer); ok && pt.Base != nil { if _, isArr := SafeUnderlying(pt.Base).(*Array); isArr { return p } } } } case *syntax.Name: obj := fb.lookupObject(ex.Value) if obj == nil || ObjectType(obj) == nil { return nil } if _, isArr := SafeUnderlying(ObjectType(obj)).(*Array); !isArr { return nil } if alloc, ok := fb.vars[obj]; ok { return alloc } if fv, ok := fb.freeVarPtrs[ex.Value]; ok { return fv } if fb.fn.Pkg != nil { if g, ok := fb.fn.Pkg.Members[ex.Value].(*SSAGlobal); ok { return g } } case *syntax.SelectorExpr: // Skip package selectors: pkg.Name is not a field access. if name, ok := ex.X.(*syntax.Name); ok { if _, isPkg := fb.lookupObject(name.Value).(*PkgName); isPkg { return nil } } addr := fb.buildSelectorAddr(ex) if addr == nil { return nil } if pt, ok := SafeUnderlying(addr.SSAType()).(*Pointer); ok && pt.Base != nil { if _, isArr := SafeUnderlying(pt.Base).(*Array); isArr { return addr } } // Not an array field: the stray field-addr GEP is dead code. return nil case *syntax.IndexExpr: var base SSAValue if sel, ok2 := ex.X.(*syntax.SelectorExpr); ok2 { base = fb.buildSelectorAddr(sel) } if base == nil { base = fb.buildArrayAddr(ex.X) } if base == nil { base = fb.buildExpr(ex.X) } if base == nil { return nil } bt := base.SSAType() if pt, ok := SafeUnderlying(bt).(*Pointer); ok && pt.Base != nil { bt = pt.Base } elem := SSAElemType(bt) if elem == nil { return nil } if _, isArr := SafeUnderlying(elem).(*Array); !isArr { return nil } idx := fb.buildExpr(ex.Index) if idx == nil { return nil } ia := &SSAIndexAddr{X: base, Index: idx} ia.typ = NewPointer(elem) ia.name = fb.nextName() fb.emit(ia) return ia } return nil } func (fb *ssaFuncBuilder) buildAssert(e *syntax.AssertExpr) (s SSAValue) { x := fb.buildExpr(e.X) assertedType := fb.resolveType(e.Type) if x == nil { return nil } ta := &SSATypeAssert{X: x, AssertedType: assertedType, CommaOk: false} ta.typ = assertedType ta.name = fb.nextName() fb.emit(ta) return ta } func (fb *ssaFuncBuilder) buildCompositeLit(e *syntax.CompositeLit) (s SSAValue) { var typ Type if e.Type != nil { typ = fb.resolveType(e.Type) } if typ == nil { return nil } if sl, ok := SafeUnderlying(typ).(*Slice); ok { return fb.buildSliceLit(e, typ, sl) } if mt, isMap := SafeUnderlying(typ).(*TCMap); isMap { mm := &SSAMakeMap{} mm.typ = typ mm.name = fb.nextName() fb.emit(mm) for _, el := range e.ElemList { if kv, ok := el.(*syntax.KeyValueExpr); ok { k := fb.buildExpr(kv.Key) v := fb.buildExpr(kv.Value) if k != nil && v != nil { k = fb.coerceToInterface(k, mt.Key) v = fb.coerceToInterface(v, mt.Elem) fb.emit(&SSAMapUpdate{Map: mm, Key: k, Value: v}) } } } return mm } if ar, isArr := SafeUnderlying(typ).(*Array); isArr { return fb.buildArrayLit(e, typ, ar) } alloc := fb.emitAlloc(typ, 0) posIdx := 0 for _, el := range e.ElemList { if kv, ok := el.(*syntax.KeyValueExpr); ok { if _, ok2 := SafeUnderlying(typ).(*TCStruct); ok2 { idx := fb.fieldIndex(typ, kv.Key.(*syntax.Name).Value) if idx >= 0 { fa := &SSAFieldAddr{X: alloc, Field: idx} ft := fb.fieldType(typ, idx) fa.typ = NewPointer(ft) fa.name = fb.nextName() fb.emit(fa) v := fb.buildExpr(kv.Value) if v != nil { v = fb.coerceToInterface(v, ft) fb.emitStore(fa, v) } } } } else { v := fb.buildExpr(el) if v != nil { if _, ok2 := SafeUnderlying(typ).(*TCStruct); ok2 { ft := fb.fieldType(typ, posIdx) fa := &SSAFieldAddr{X: alloc, Field: posIdx} fa.typ = NewPointer(ft) fa.name = fb.nextName() fb.emit(fa) v = fb.coerceToInterface(v, ft) fb.emitStore(fa, v) } } posIdx++ } } return fb.emitLoad(alloc, typ) } func (fb *ssaFuncBuilder) buildArrayLit(e *syntax.CompositeLit, typ Type, ar *Array) (s SSAValue) { if ar.Len < 0 { ar = NewArray(ar.Elem, int64(len(e.ElemList))) typ = ar } alloc := fb.emitAlloc(typ, 0) elemTyp := ar.Elem for i, el := range e.ElemList { var expr syntax.Expr if kv, ok := el.(*syntax.KeyValueExpr); ok { expr = kv.Value } else { expr = el } var v SSAValue if cl, ok := expr.(*syntax.CompositeLit); ok && cl.Type == nil { v = fb.buildCompositeLitWithType(cl, elemTyp) } else { v = fb.buildExpr(expr) } if v == nil { continue } idx := &SSAConst{Typ: Typ[Int32], Val: &ConstInt{V:int64(i)}} ia := &SSAIndexAddr{X: alloc, Index: idx} ia.typ = NewPointer(elemTyp) ia.name = fb.nextName() fb.emit(ia) v = fb.coerceToInterface(v, elemTyp) fb.emitStore(ia, v) } return fb.emitLoad(alloc, typ) } func (fb *ssaFuncBuilder) buildCompositeLitWithType(e *syntax.CompositeLit, typ Type) (s SSAValue) { if sl, ok := SafeUnderlying(typ).(*Slice); ok { return fb.buildSliceLit(e, typ, sl) } if ar, isArr := SafeUnderlying(typ).(*Array); isArr { return fb.buildArrayLit(e, typ, ar) } alloc := fb.emitAlloc(typ, 0) posIdx := 0 for _, el := range e.ElemList { if kv, ok := el.(*syntax.KeyValueExpr); ok { if _, ok2 := SafeUnderlying(typ).(*TCStruct); ok2 { idx := fb.fieldIndex(typ, kv.Key.(*syntax.Name).Value) if idx >= 0 { ft := fb.fieldType(typ, idx) fa := &SSAFieldAddr{X: alloc, Field: idx} fa.typ = NewPointer(ft) fa.name = fb.nextName() fb.emit(fa) v := fb.buildExpr(kv.Value) if v != nil { v = fb.coerceToInterface(v, ft) fb.emitStore(fa, v) } } } } else { v := fb.buildExpr(el) if v != nil { if _, ok2 := SafeUnderlying(typ).(*TCStruct); ok2 { ft := fb.fieldType(typ, posIdx) fa := &SSAFieldAddr{X: alloc, Field: posIdx} fa.typ = NewPointer(ft) fa.name = fb.nextName() fb.emit(fa) v = fb.coerceToInterface(v, ft) fb.emitStore(fa, v) } } posIdx++ } } return fb.emitLoad(alloc, typ) } func (fb *ssaFuncBuilder) buildSliceLit(e *syntax.CompositeLit, typ Type, sl *Slice) (s SSAValue) { n := len(e.ElemList) nVal := &SSAConst{Typ: Typ[Int32], Val: &ConstInt{V:int64(n)}} ms := &SSAMakeSlice{} ms.typ = typ ms.name = fb.nextName() ms.Len = nVal ms.Cap = nVal fb.emit(ms) elemTyp := sl.Elem for i, el := range e.ElemList { var expr syntax.Expr if kv, ok := el.(*syntax.KeyValueExpr); ok { expr = kv.Value } else { expr = el } var v SSAValue if cl, ok := expr.(*syntax.CompositeLit); ok && cl.Type == nil { v = fb.buildCompositeLitWithType(cl, elemTyp) } else { v = fb.buildExpr(expr) } if v == nil { continue } idx := &SSAConst{Typ: Typ[Int32], Val: &ConstInt{V:int64(i)}} ia := &SSAIndexAddr{X: ms, Index: idx} ia.typ = NewPointer(elemTyp) ia.name = fb.nextName() fb.emit(ia) v = fb.coerceToInterface(v, elemTyp) fb.emitStore(ia, v) } return ms } func (fb *ssaFuncBuilder) buildFuncLit(e *syntax.FuncLit) (s SSAValue) { var sig *Signature if fb.info != nil { if tv, ok := fb.info.Types[e]; ok { sig, _ = tv.Type.(*Signature) } } if sig == nil && e.Type != nil { sig = fb.fn.Prog.ResolveFuncInline(e.Type, fb.fn.Pkg.Pkg.Scope) } name := fb.fn.Name | "__anon" | SSAItoa(len(fb.fn.AnonFuncs)+1) anon := &SSAFunction{ Name: name, Signature: sig, pos: 0, Pkg: fb.fn.Pkg, Prog: fb.fn.Prog, Parent: fb.fn, } fb.fn.AnonFuncs = append(fb.fn.AnonFuncs, anon) ab := newSSAFuncBuilder(anon, fb.info) ab.parent = fb // Closures inside monomorphized bodies need the same generic context as // their parent, or calls to source-package functions silently vanish. ab.typeSubst = fb.typeSubst ab.srcScope = fb.srcScope ab.fileScope = fb.fileScope d := &syntax.FuncDecl{ Name: &syntax.Name{Value: name}, Type: e.Type, Body: e.Body, } ab.buildBody(d) if len(anon.FreeVars) == 0 { return anon } bindings := []SSAValue{:0:len(anon.FreeVars)} for _, fv := range anon.FreeVars { obj := fb.lookupObject(fv.name) if obj != nil { if alloc, ok := fb.vars[obj]; ok { alloc.Heap = true bindings = append(bindings, alloc) } else if pfv2, ok28 := fb.freeVarPtrs[fv.name]; ok28 { bindings = append(bindings, pfv2) } else { bindings = append(bindings, &SSAConst{Typ: NewPointer(fv.typ), Val: nil}) } } else if pfv, ok := fb.freeVarPtrs[fv.name]; ok { bindings = append(bindings, pfv) } else { bindings = append(bindings, &SSAConst{Typ: NewPointer(fv.typ), Val: nil}) } } mc := &SSAMakeClosure{Fn: anon, Bindings: bindings} mc.typ = sig mc.name = fb.nextName() fb.emit(mc) return mc } func (fb *ssaFuncBuilder) saveVars() (m map[Object]*SSAAlloc) { saved := map[Object]*SSAAlloc{} for k, v := range fb.vars { saved[k] = v } return saved } func (fb *ssaFuncBuilder) removeVar(name string) { // Delete ALL matching Objects. A map iteration would be nondeterministic // if multiple Objects share the same name (scope shadowing), so collect // matches first, then delete. var matches []Object for o := range fb.vars { if ObjectName(o) == name { matches = append(matches, o) } } for _, o := range matches { delete(fb.vars, o) } } func (fb *ssaFuncBuilder) buildPkgMemberAddr(pn *PkgName, memberName string) (s SSAValue) { imported := pn.Imported if imported == nil { return nil } obj := imported.Scope.Lookup(memberName) if obj == nil { return nil } switch ob := obj.(type) { case *TCConst: return nil case *TCVar: impPkg := fb.ensureImportedSSAPackage(imported) g, ok := impPkg.Members[memberName].(*SSAGlobal) if !ok { g = &SSAGlobal{ Name: memberName, Typ: NewPointer(ob.Typ), Pkg: impPkg, } impPkg.SetMember(memberName, g) } return g } return nil } func (fb *ssaFuncBuilder) buildPkgCall(pn *PkgName, funcName string, argList []syntax.Expr, hasDots bool) (s SSAValue) { imported := pn.Imported if imported == nil { return nil } if imported.Path == "unsafe" && (funcName == "Slice" || funcName == "String") { // string and []byte are the same type: unsafe.String(ptr, len) is // unsafe.Slice with a byte element, which already yields string. return fb.buildUnsafeSlice(argList) } if imported.Path == "unsafe" && (funcName == "SliceData" || funcName == "StringData") { arg := fb.buildExpr(argList[0]) extract := &SSAExtract{Tuple: arg, Index: 0} extract.typ = Typ[UnsafePointer] extract.name = fb.nextName() extract.setBlock(fb.currentBlock) fb.currentBlock.Instrs = append(fb.currentBlock.Instrs, extract) return extract } if imported.Path == "unsafe" && funcName == "Add" { if len(argList) != 2 { return nil } ptr := fb.buildExpr(argList[0]) offset := fb.buildExpr(argList[1]) idx := &SSAIndexAddr{X: ptr, Index: offset} idx.typ = NewPointer(Typ[Uint8]) idx.name = fb.nextName() idx.setBlock(fb.currentBlock) fb.currentBlock.Instrs = append(fb.currentBlock.Instrs, idx) return idx } if imported.Path == "unsafe" && funcName == "Sizeof" { sz := int64(8) if len(argList) == 1 { arg := fb.buildExpr(argList[0]) if arg != nil && arg.SSAType() != nil { u := SafeUnderlying(arg.SSAType()) if b2, ok29 := u.(*Basic); ok29 { switch b2.Kind { case Bool, Int8, Uint8: sz = 1 case Int16, Uint16: sz = 2 case Int32, Uint32, Float32: sz = 4 case Int64, Uint64, Float64, UnsafePointer: sz = 8 } } if _, ok30 := u.(*Pointer); ok30 { sz = 8 } if st2, ok31 := u.(*TCStruct); ok31 { total := int64(0) for i := 0; i < st2.NumFields(); i++ { f := st2.Field(i) fsz := int64(8) fu := SafeUnderlying(f.Typ) if fb2, ok32 := fu.(*Basic); ok32 { switch fb2.Kind { case Bool, Int8, Uint8: fsz = 1 case Int16, Uint16: fsz = 2 case Int32, Uint32, Float32: fsz = 4 case Int64, Uint64, Float64, UnsafePointer: fsz = 8 } } if _, ok2 := fu.(*Pointer); ok2 { fsz = 8 } if ar, ok2 := fu.(*Array); ok2 { elemSz := int64(8) if eb, ok3 := SafeUnderlying(ar.Elem).(*Basic); ok3 { switch eb.Kind { case Bool, Int8, Uint8: elemSz = 1 case Int16, Uint16: elemSz = 2 case Int32, Uint32, Float32: elemSz = 4 case Int64, Uint64, Float64, UnsafePointer: elemSz = 8 } } fsz = elemSz * ar.Len } if fsz < 8 { total = total + fsz } else { if total%8 != 0 { total = (total + 7) & ^int64(7) } total = total + fsz } } if total%8 != 0 { total = (total + 7) & ^int64(7) } sz = total } if sl2, ok33 := u.(*Slice); ok33 { _ = sl2 sz = 24 } if ar2, ok34 := u.(*Array); ok34 { elemSz := int64(8) if eb, ok2 := SafeUnderlying(ar2.Elem).(*Basic); ok2 { switch eb.Kind { case Bool, Int8, Uint8: elemSz = 1 case Int16, Uint16: elemSz = 2 case Int32, Uint32, Float32: elemSz = 4 case Int64, Uint64, Float64, UnsafePointer: elemSz = 8 } } sz = elemSz * ar2.Len } } } return NewSSAConst(&ConstInt{V:sz}, Typ[Int64]) } if imported.Path == "unsafe" && funcName == "Alignof" { return NewSSAConst(&ConstInt{V:8}, Typ[Int64]) } if imported.Path == "unsafe" && funcName == "Offsetof" { return NewSSAConst(&ConstInt{V:0}, Typ[Int64]) } if result, handled := fb.tryGenericPkgCall(pn, funcName, argList, hasDots); handled { return result } obj := imported.Scope.Lookup(funcName) if obj == nil { // Fail loud: a dropped call compiles the caller to garbage // (zeroinitializer returns) and crashes far from the cause. fb.fn.Prog.Errors = append(fb.fn.Prog.Errors, "undefined: "|imported.Path|"."|funcName|" (called from "|fb.fn.Name|")") return nil } fn, ok := obj.(*TCFunc) if !ok { return nil } impPkg := fb.ensureImportedSSAPackage(imported) ssaFn := impPkg.Func(funcName) if ssaFn == nil { ssaFn = &SSAFunction{ Name: funcName, Object: fn, Signature: fn.Typ.(*Signature), Pkg: impPkg, Prog: fb.fn.Prog, } if isNoContextExtern(imported.Path, funcName) { ssaFn.isExternC = true } impPkg.SetMember(funcName, ssaFn) } args := fb.buildArgs(argList) var retType Type if ssaFn.Signature != nil { if ssaFn.Signature.Variadic && !hasDots { args = fb.wrapVariadicArgs(args, ssaFn.Signature) } args = fb.coerceArgsToInterface(args, ssaFn.Signature) if ssaFn.Signature.Results != nil && ssaFn.Signature.Results.Len() == 1 { retType = ssaFn.Signature.Results.At(0).Typ } else if ssaFn.Signature.Results != nil && ssaFn.Signature.Results.Len() > 1 { retType = ssaFn.Signature.Results } } call := &SSACall{Call: SSACallCommon{Value: ssaFn, Args: args}} call.typ = retType call.name = fb.nextName() fb.emit(call) if retType == nil { return nil } return call } func (fb *ssaFuncBuilder) buildUnsafeSlice(argList []syntax.Expr) (s SSAValue) { if len(argList) < 2 { return nil } ptrVal := fb.buildExpr(argList[0]) lenVal := fb.buildExpr(argList[1]) if ptrVal == nil || lenVal == nil { return nil } var elemType Type if pt, ok := SafeUnderlying(ptrVal.SSAType()).(*Pointer); ok { elemType = pt.Base } if elemType == nil { elemType = Typ[Uint8] } ms := &SSAMakeSlice{Len: lenVal, Cap: lenVal, Data: ptrVal} if b, ok := elemType.(*Basic); ok && b.Kind == Uint8 { ms.typ = Typ[TCString] } else { ms.typ = NewSlice(elemType) } ms.name = fb.nextName() fb.emit(ms) return ms } // Helpers. func (fb *ssaFuncBuilder) lookupObject(name string) (o Object) { for obj := range fb.vars { if ObjectName(obj) == name { return obj } } if fb.parent != nil { for obj := range fb.parent.vars { if ObjectName(obj) == name { return obj } } } if fb.srcScope != nil { if _, obj := fb.srcScope.LookupParent(name); obj != nil { return obj } } if fb.fileScope != nil { if _, obj := fb.fileScope.LookupParent(name); obj != nil { return obj } } if fb.fn.Pkg != nil { if _, obj := fb.fn.Pkg.Pkg.Scope.LookupParent(name); obj != nil { return obj } } if fb.info != nil { return nil } return nil } func (fb *ssaFuncBuilder) getOrCreateFreeVar(name string, typ Type) (s SSAValue) { for _, fv2 := range fb.fn.FreeVars { if fv2.name == name { return fv2 } } fv := &SSAFreeVar{ name: name, typ: typ, parent: fb.fn, } fb.fn.FreeVars = append(fb.fn.FreeVars, fv) if fb.freeVarPtrs == nil { fb.freeVarPtrs = map[string]*SSAFreeVar{} } fb.freeVarPtrs[name] = fv return fv } func (fb *ssaFuncBuilder) lookupVar(name string) (t *TCVar) { obj := fb.lookupObject(name) v, _ := obj.(*TCVar) return v } // derefValueRecv loads the receiver through the pointer when a value-receiver // method is called on a pointer: the definition takes the receiver by value // (an LLVM aggregate), so passing the raw pointer reinterprets pointer bits // as field data. func (fb *ssaFuncBuilder) derefValueRecv(fn *SSAFunction, recv SSAValue) (s SSAValue) { if fn == nil || recv == nil || fn.Signature == nil || fn.Signature.Recv == nil { return recv } // All receivers are ptr in Moxie. If the Signature says value receiver // and the caller has a pointer, pass it through (already ptr). // If the caller has a value, take its address. if _, recvIsPtr := SafeUnderlying(fn.Signature.Recv.Typ).(*Pointer); recvIsPtr { return recv } // Source-level value receiver - function now expects ptr. // If recv is already a pointer, pass through. if _, ok := SafeUnderlying(recv.SSAType()).(*Pointer); ok { return recv } // recv is a value - take its address by storing to an alloca. alloc := fb.emitAlloc(recv.SSAType(), 0) fb.emitStore(alloc, recv) return alloc } func (fb *ssaFuncBuilder) resolveMethodCallWithRecv(sel *syntax.SelectorExpr, recv SSAValue) (rfn *SSAFunction, rrecv SSAValue) { typ := recv.SSAType() if typ == nil { return nil, nil } isPtr := false if p5, ok35 := SafeUnderlying(typ).(*Pointer); ok35 { typ = p5.Base isPtr = true } if _, ok36 := SafeUnderlying(typ).(*TCInterface); ok36 { if named2, ok37 := typ.(*Named); !ok37 || named2.NumMethods() == 0 { return nil, nil } } // Handle Basic types (builtin stringers in runtime package) if basic, ok38 := typ.(*Basic); ok38 { typeName2 := basic.Name mangledName2 := typeName2 | "." | sel.Sel.Value extSym := "runtime." | mangledName2 for i := int32(0); i < basic.NumMethods(); i++ { m := basic.Method(i) if m.Name == sel.Sel.Value { fn3 := fb.fn.Pkg.ExternalFunc(mangledName2, m.Signature()) fn3.ExternalSymbol = extSym return fn3, recv } } resolved := UntypedToTyped(basic) if resolved == nil { return nil, nil } switch rt := resolved.(type) { case *Basic: typeName2 = rt.Name mangledName2 = typeName2 | "." | sel.Sel.Value extSym = "runtime." | mangledName2 for i := int32(0); i < rt.NumMethods(); i++ { m := rt.Method(i) if m.Name == sel.Sel.Value { fn4 := fb.fn.Pkg.ExternalFunc(mangledName2, m.Signature()) fn4.ExternalSymbol = extSym return fn4, recv } } return nil, nil case *Named: typ = rt default: return nil, nil } } named, ok := typ.(*Named) if !ok { return nil, nil } typeName := "" if named.Obj != nil { typeName = named.Obj.Name } if typeName == "" { return nil, nil } resolveNamed := named if resolveNamed.NumMethods() == 0 { // The Named type from typeSubst may be a bare copy without methods. // Look up the canonical type in ImportRegistry which has methods // attached from mxh loading. if resolveNamed.Obj != nil && resolveNamed.Obj.Pkg != nil { pkgPath := resolveNamed.Obj.Pkg.Path nm := resolveNamed.Obj.Name if ImportRegistry != nil { if pkg := ImportRegistry[pkgPath]; pkg != nil && pkg.Scope != nil { if obj := pkg.Scope.Lookup(nm); obj != nil { if regNamed, ok39 := ObjectType(obj).(*Named); ok39 && regNamed.NumMethods() > 0 { resolveNamed = regNamed typeName = nm } } } } } if resolveNamed.NumMethods() == 0 { cur := Type(resolveNamed) for depth := 0; depth < 10; depth++ { n2, ok40 := cur.(*Named) if !ok40 || n2 == nil { break } if n2.NumMethods() > 0 { resolveNamed = n2 if n2.Obj != nil && n2.Obj.Name != "" { typeName = n2.Obj.Name } break } raw := n2.Under if raw == nil || raw == cur { break } cur = raw } } } namedPkgPath := "" if resolveNamed.Obj != nil && resolveNamed.Obj.Pkg != nil { namedPkgPath = resolveNamed.Obj.Pkg.Path } samePackage := namedPkgPath != "" && namedPkgPath == fb.fn.Pkg.Pkg.Path mangledName := typeName | "." | sel.Sel.Value if !samePackage && namedPkgPath != "" { mangledName = namedPkgPath | "." | typeName | "." | sel.Sel.Value } var fn *SSAFunction if samePackage { fn, _ = fb.fn.Pkg.Members[mangledName].(*SSAFunction) } if fn == nil { for i := 0; i < resolveNamed.NumMethods(); i++ { m := resolveNamed.Method(i) if m.Name == sel.Sel.Value { if !samePackage && namedPkgPath != "" { // Imported method: register in the imported package so the // emitter uses the moxie convention (value receivers passed // by value, trailing ctx param). The externalSymbol path is // C-export ABI and drops the ctx argument. impPkg := fb.ensureImportedSSAPackage(resolveNamed.Obj.Pkg) fn = impPkg.ExternalFunc(typeName|"."|sel.Sel.Value, m.Signature()) } else { fn = fb.fn.Pkg.ExternalFunc(mangledName, m.Signature()) if resolveNamed.Obj != nil && resolveNamed.Obj.Pkg == nil { fn.ExternalSymbol = "runtime." | mangledName } } break } } } if fn != nil { if fn.Signature != nil && fn.Signature.Recv != nil { recvParam := fn.Signature.Recv _, recvIsPtr := SafeUnderlying(recvParam.Typ).(*Pointer) if recvIsPtr && !isPtr { if nameExpr, ok41 := sel.X.(*syntax.Name); ok41 { obj := fb.lookupObject(nameExpr.Value) if obj != nil { if alloc, ok3 := fb.vars[obj]; ok3 { recv = alloc } } } else if innerSel, ok42 := sel.X.(*syntax.SelectorExpr); ok42 { addr := fb.buildSelectorAddr(innerSel) if addr != nil { recv = addr } } } } return fn, recv } st, ok2 := SafeUnderlying(named).(*TCStruct) if !ok2 { return nil, nil } if !isPtr { fixed := false if innerSel, ok3 := sel.X.(*syntax.SelectorExpr); ok3 { addr := fb.buildSelectorAddr(innerSel) if addr != nil { recv = addr fixed = true } } if !fixed { if nameExpr, ok3 := sel.X.(*syntax.Name); ok3 { obj := fb.lookupObject(nameExpr.Value) if obj != nil { if a, ok4 := fb.vars[obj]; ok4 { recv = a fixed = true } } } } if !fixed { alloc := &SSAAlloc{} alloc.typ = NewPointer(recv.SSAType()) alloc.name = fb.nextName() fb.emit(alloc) storeOp := &SSAStore{Addr: alloc, Val: recv} fb.emit(storeOp) recv = alloc } } for i := 0; i < st.NumFields(); i++ { f := st.Field(i) if !f.Anonymous { continue } embedType := f.Typ embedNamed, ok3 := embedType.(*Named) if !ok3 { if p, ok4 := embedType.(*Pointer); ok4 { embedNamed, ok3 = p.Base.(*Named) } } if !ok3 || embedNamed.Obj == nil { continue } embedName := embedNamed.Obj.Name embedSamePkg := embedNamed.Obj.Pkg != nil && embedNamed.Obj.Pkg.Path == fb.fn.Pkg.Pkg.Path embedMangledName := embedName | "." | sel.Sel.Value if !embedSamePkg && embedNamed.Obj.Pkg != nil { embedMangledName = embedNamed.Obj.Pkg.Path | "." | embedName | "." | sel.Sel.Value } var embedFn *SSAFunction if embedSamePkg { embedFn, _ = fb.fn.Pkg.Members[embedMangledName].(*SSAFunction) } if embedFn == nil { for mi := int32(0); mi < int32(embedNamed.NumMethods()); mi++ { m := embedNamed.Method(int32(mi)) if m.Name == sel.Sel.Value { if !embedSamePkg && embedNamed.Obj.Pkg != nil { impPkg := fb.ensureImportedSSAPackage(embedNamed.Obj.Pkg) embedFn = impPkg.ExternalFunc(embedName|"."|sel.Sel.Value, m.Signature()) } else { embedFn = fb.fn.Pkg.ExternalFunc(embedMangledName, m.Signature()) } break } } } if embedFn != nil { fieldAddr := &SSAFieldAddr{ X: recv, Field: i, } fieldAddr.typ = NewPointer(embedType) fieldAddr.name = fb.nextName() fb.emit(fieldAddr) var embedRecv SSAValue = fieldAddr if _, isEmbedPtr := SafeUnderlying(embedType).(*Pointer); isEmbedPtr { ld := &SSAUnOp{Op: OpMul, X: fieldAddr} ld.typ = embedType ld.name = fb.nextName() fb.emit(ld) embedRecv = ld } return embedFn, embedRecv } embedSt, ok5 := SafeUnderlying(embedNamed).(*TCStruct) if !ok5 { continue } for j := 0; j < embedSt.NumFields(); j++ { f2 := embedSt.Field(j) if !f2.Anonymous { continue } embed2Type := f2.Typ embed2Named, ok6 := embed2Type.(*Named) if !ok6 { if p2, ok7 := embed2Type.(*Pointer); ok7 { embed2Named, ok6 = p2.Base.(*Named) } } if !ok6 || embed2Named.Obj == nil { continue } embed2Name := embed2Named.Obj.Name embed2SamePkg := embed2Named.Obj.Pkg != nil && embed2Named.Obj.Pkg.Path == fb.fn.Pkg.Pkg.Path embed2Mangled := embed2Name | "." | sel.Sel.Value if !embed2SamePkg && embed2Named.Obj.Pkg != nil { embed2Mangled = embed2Named.Obj.Pkg.Path | "." | embed2Name | "." | sel.Sel.Value } var embed2Fn *SSAFunction if embed2SamePkg { embed2Fn, _ = fb.fn.Pkg.Members[embed2Mangled].(*SSAFunction) } if embed2Fn == nil { for mi := int32(0); mi < int32(embed2Named.NumMethods()); mi++ { m := embed2Named.Method(int32(mi)) if m.Name == sel.Sel.Value { if !embed2SamePkg && embed2Named.Obj.Pkg != nil { impPkg := fb.ensureImportedSSAPackage(embed2Named.Obj.Pkg) embed2Fn = impPkg.ExternalFunc(embed2Name|"."|sel.Sel.Value, m.Signature()) } else { embed2Fn = fb.fn.Pkg.ExternalFunc(embed2Mangled, m.Signature()) } break } } } if embed2Fn == nil { continue } fieldAddr1 := &SSAFieldAddr{X: recv, Field: i} fieldAddr1.typ = NewPointer(embedType) fieldAddr1.name = fb.nextName() fb.emit(fieldAddr1) var cur1 SSAValue = fieldAddr1 curEmbedType := embedType if p5, ok8 := SafeUnderlying(embedType).(*Pointer); ok8 { ld := &SSAUnOp{Op: OpMul, X: fieldAddr1} ld.typ = embedType ld.name = fb.nextName() fb.emit(ld) cur1 = ld curEmbedType = p5.Base } _ = curEmbedType fieldAddr2 := &SSAFieldAddr{X: cur1, Field: j} fieldAddr2.typ = NewPointer(embed2Type) fieldAddr2.name = fb.nextName() fb.emit(fieldAddr2) var embedRecv2 SSAValue = fieldAddr2 if _, isEmbedPtr2 := SafeUnderlying(embed2Type).(*Pointer); isEmbedPtr2 { ld2 := &SSAUnOp{Op: OpMul, X: fieldAddr2} ld2.typ = embed2Type ld2.name = fb.nextName() fb.emit(ld2) embedRecv2 = ld2 } return embed2Fn, embedRecv2 } } return nil, nil } func (fb *ssaFuncBuilder) buildIfaceMethodCall(sel *syntax.SelectorExpr, recv SSAValue, argExprs []syntax.Expr) (s SSAValue) { typ := recv.SSAType() if p6, ok43 := SafeUnderlying(typ).(*Pointer); ok43 { typ = p6.Base } iface, ok := SafeUnderlying(typ).(*TCInterface) if !ok { return nil } var methodSig *Signature for i := 0; i < iface.NumMethods(); i++ { m := iface.Method(i) if m == nil { continue } if m.Name == sel.Sel.Value { methodSig = m.Sig break } } var retType Type if methodSig != nil && methodSig.Results != nil { if methodSig.Results.Len() == 1 { retType = methodSig.Results.At(0).Typ } else if methodSig.Results.Len() > 1 { retType = methodSig.Results } } args := fb.buildArgs(argExprs) inv := &SSAInvoke{ X: recv, MethodName: sel.Sel.Value, IfaceType: iface, Args: args, } inv.typ = retType inv.name = fb.nextName() fb.emit(inv) if retType == nil { return nil } return inv } func (fb *ssaFuncBuilder) resolveType(e syntax.Expr) (t Type) { if fb.info != nil { if tv, ok := fb.info.Types[e]; ok && tv.Type != nil { return tv.Type } } t = fb.resolveTypeAST(e) return t } func (fb *ssaFuncBuilder) resolveTypeAST(e syntax.Expr) (t Type) { if e == nil { return nil } switch ev := e.(type) { case *syntax.Name: if fb.typeSubst != nil { if t2, ok44 := fb.typeSubst[ev.Value]; ok44 { return t2 } } if t3, ok45 := fb.localTypes[ev.Value]; ok45 { return t3 } obj := fb.lookupObject(ev.Value) if tn, ok := obj.(*TypeName); ok { if tp, ok2 := tn.Typ.(*TypeParam); ok2 && fb.typeSubst != nil { if t4, ok46 := fb.typeSubst[tp.String()]; ok46 { return t4 } } return tn.Typ } case *syntax.Operation: if ev.Y == nil && ev.Op == token.Mul { base := fb.resolveTypeAST(ev.X) if base != nil { return NewPointer(base) } } case *syntax.SliceType: elem := fb.resolveTypeAST(ev.Elem) if elem != nil { if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) } case *syntax.ArrayType: elem := fb.resolveTypeAST(ev.Elem) if elem != nil { n := int64(-1) if lit, ok := ev.Len.(*syntax.BasicLit); ok { n = SSAParseInt64(lit.Value) } else if ev.Len != nil { cv := fb.fn.Prog.EvalConstExpr(ev.Len, fb.fn.Pkg.Pkg.Scope, -1) if cv == nil && fb.localConsts != nil { cv = fb.evalLocalConstExpr(ev.Len) } if ci2, ok47 := cv.(*ConstInt); ok47 { n = ci2.V } } return NewArray(elem, n) } case *syntax.MapType: key := fb.resolveTypeAST(ev.Key) val := fb.resolveTypeAST(ev.Value) if key != nil && val != nil { return NewTCMap(key, val) } case *syntax.StructType: return fb.resolveStructTypeAST(ev) case *syntax.ChanType: elem := fb.resolveTypeAST(ev.Elem) if elem != nil { dir := TCSendRecv if ev.Dir == syntax.SendOnly { dir = TCSendOnly } else if ev.Dir == syntax.RecvOnly { dir = TCRecvOnly } return NewTCChan(dir, elem) } case *syntax.InterfaceType: return fb.resolveInterfaceTypeAST(ev) case *syntax.FuncType: return fb.resolveFuncTypeAST(ev) case *syntax.SelectorExpr: if x, ok := ev.X.(*syntax.Name); ok { obj := fb.lookupObject(x.Value) if pn, ok2 := obj.(*PkgName); ok2 && pn.Imported != nil { sel := pn.Imported.Scope.Lookup(ev.Sel.Value) if tn, ok3 := sel.(*TypeName); ok3 { return tn.Typ } } } case *syntax.ParenExpr: return fb.resolveTypeAST(ev.X) } return nil } func (fb *ssaFuncBuilder) resolveInterfaceTypeAST(e *syntax.InterfaceType) (t *TCInterface) { var methods []*IfaceMethod for _, f := range e.MethodList { if f.Name == nil { continue } ft, ok := f.Type.(*syntax.FuncType) if !ok { continue } sig := fb.resolveFuncTypeAST(ft) if sig != nil { methods = append(methods, NewTCIfaceMethod(f.Name.Value, sig)) } } iface := NewTCInterface(methods, nil) iface.Complete() return iface } func (fb *ssaFuncBuilder) resolveStructTypeAST(e *syntax.StructType) (t *TCStruct) { var fields []*TCVar var tags []string for i, f := range e.FieldList { typ := fb.resolveTypeAST(f.Type) if typ == nil { return nil } name := "" if f.Name != nil { name = f.Name.Value } fields = append(fields, NewTCField(fb.fn.Pkg.Pkg, name, typ, f.Name == nil)) tag := "" if i < len(e.TagList) && e.TagList[i] != nil { tag = e.TagList[i].Value } tags = append(tags, tag) } return NewTCStruct(fields, tags) } func (fb *ssaFuncBuilder) resolveFuncTypeAST(ft *syntax.FuncType) (s *Signature) { if ft == nil { return nil } var params []*TCVar for _, p := range ft.ParamList { typ := fb.resolveTypeAST(p.Type) pname := "" if p.Name != nil { pname = p.Name.Value } params = append(params, NewTCVar(nil, pname, typ)) } var results []*TCVar for _, r := range ft.ResultList { typ := fb.resolveTypeAST(r.Type) rname := "" if r.Name != nil { rname = r.Name.Value } results = append(results, NewTCVar(nil, rname, typ)) } var pTuple, rTuple *Tuple if len(params) > 0 { pTuple = NewTuple(params...) } if len(results) > 0 { rTuple = NewTuple(results...) } return NewSignature(nil, pTuple, rTuple, false) } func (fb *ssaFuncBuilder) fieldIndex(t Type, name string) (n int32) { if t == nil { return -1 } if pt, ok := SafeUnderlying(t).(*Pointer); ok { t = pt.Base } if st, ok := SafeUnderlying(t).(*TCStruct); ok { for i := 0; i < st.NumFields(); i++ { if st.Field(i).Name == name { return i } } } return -1 } func (fb *ssaFuncBuilder) findEmbeddedField(t Type, name string) (embedIdx int32, innerIdx int32) { path := fb.findEmbeddedFieldPath(t, name) if len(path) < 2 { return -1, -1 } return path[0], path[1] } func (fb *ssaFuncBuilder) findEmbeddedFieldPath(t Type, name string) (ss []int32) { if t == nil { return nil } if pt2, ok48 := SafeUnderlying(t).(*Pointer); ok48 { t = pt2.Base } st, ok := SafeUnderlying(t).(*TCStruct) if !ok { return nil } for i := 0; i < st.NumFields(); i++ { f := st.Field(i) if !f.Anonymous { continue } embedType := f.Typ if pt3, ok49 := SafeUnderlying(embedType).(*Pointer); ok49 { embedType = pt3.Base } inner := fb.fieldIndex(embedType, name) if inner >= 0 { return []int32{i, inner} } sub := fb.findEmbeddedFieldPath(embedType, name) if len(sub) > 0 { return []int32{i} | sub } } return nil } func (fb *ssaFuncBuilder) fieldType(t Type, idx int32) (tv Type) { if t == nil { return nil } if pt, ok := SafeUnderlying(t).(*Pointer); ok { t = pt.Base } if st, ok := SafeUnderlying(t).(*TCStruct); ok { if idx >= 0 && idx < st.NumFields() { return fb.substTypeParams(st.Field(idx).Typ) } } return nil } // substTypeParams substitutes type parameters in t using fb.typeSubst. // Generic struct types are erased to their base Named inside monomorphized // bodies, so a field like newPoint func() P still carries the TypeParam; the // concrete binding lives in fb.typeSubst. func (fb *ssaFuncBuilder) substTypeParams(t Type) (tv Type) { if t == nil || fb.typeSubst == nil { return t } switch tt := t.(type) { case *TypeParam: if r, ok := fb.typeSubst[tt.String()]; ok { return r } case *Pointer: e := fb.substTypeParams(tt.Base) if e != tt.Base { return NewPointer(e) } case *Slice: e := fb.substTypeParams(tt.Elem) if e != tt.Elem { return NewSlice(e) } case *Signature: changed := false var params []*TCVar if tt.Params != nil { for i := 0; i < tt.Params.Len(); i++ { p := tt.Params.At(i) nt := fb.substTypeParams(p.Typ) if nt != p.Typ { changed = true params = append(params, NewTCVar(nil, p.Name, nt)) } else { params = append(params, p) } } } var results []*TCVar if tt.Results != nil { for i := 0; i < tt.Results.Len(); i++ { r := tt.Results.At(i) nt := fb.substTypeParams(r.Typ) if nt != r.Typ { changed = true results = append(results, NewTCVar(nil, r.Name, nt)) } else { results = append(results, r) } } } if changed { var pT *Tuple if len(params) > 0 { pT = NewTuple(params...) } var rT *Tuple if len(results) > 0 { rT = NewTuple(results...) } return NewSignature(nil, pT, rT, tt.Variadic) } } return t } func ssaExtractConst(v SSAValue) (s *SSAConst) { if c, ok := v.(*SSAConst); ok { return c } if conv, ok := v.(*SSAConvert); ok { if c, ok2 := conv.X.(*SSAConst); ok2 { return &SSAConst{Typ: conv.SSAType(), Val: c.Val} } } return nil } func ssaExprNames(e syntax.Expr) (ns []*syntax.Name) { if e == nil { return nil } if n, ok := e.(*syntax.Name); ok { return []*syntax.Name{n} } if l, ok := e.(*syntax.ListExpr); ok { var names []*syntax.Name for _, el := range l.ElemList { if n2, ok50 := el.(*syntax.Name); ok50 { names = append(names, n2) } } return names } return nil } func localizeConstVal(val ConstVal, typ Type) (c ConstVal) { if val == nil { return nil } return val } func mangleGenericName(baseName string, typeArgs []Type) (s string) { s = baseName | "__" for i, t := range typeArgs { if i > 0 { s = s | "_" } if t == nil { s = s | "nil" } else { ts := t.String() for j := 0; j < len(ts); j++ { c := ts[j] if (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') { s = s | string([]byte{c}) } else if c == '.' { s = s | "." } else { s = s | "_" } } } } return s } func ResolveTypeExprWithSubst(e syntax.Expr, typeSubst map[string]Type, pkgScope *Scope, prog *SSAProgram) (t Type) { if e == nil { return nil } switch ev := e.(type) { case *syntax.Name: if typeSubst != nil { if t5, ok51 := typeSubst[ev.Value]; ok51 { return t5 } } _, obj := Universe.LookupParent(ev.Value) if obj != nil { if tn, ok := obj.(*TypeName); ok { return tn.Typ } } if pkgScope != nil { tobj := pkgScope.Lookup(ev.Value) if tobj != nil { if tn, ok := tobj.(*TypeName); ok { return tn.Typ } } } if ImportRegistry != nil { for _, pkg := range ImportRegistry { if pkg == nil || pkg.Scope == nil { continue } tobj := pkg.Scope.Lookup(ev.Value) if tobj != nil { if tn, ok := tobj.(*TypeName); ok { return tn.Typ } } } } return nil case *syntax.DotsType: elem := ResolveTypeExprWithSubst(ev.Elem, typeSubst, pkgScope, prog) if elem != nil { if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) } case *syntax.SliceType: elem := ResolveTypeExprWithSubst(ev.Elem, typeSubst, pkgScope, prog) if elem != nil { if b, ok := elem.(*Basic); ok && b.Kind == Uint8 { return Typ[TCString] } return NewSlice(elem) } case *syntax.ArrayType: elem := ResolveTypeExprWithSubst(ev.Elem, typeSubst, pkgScope, prog) if elem != nil { n := int64(-1) if lit, ok := ev.Len.(*syntax.BasicLit); ok { n = SSAParseInt64(lit.Value) } else if ev.Len != nil && pkgScope != nil && prog != nil && prog.EvalConstExpr != nil { cv := prog.EvalConstExpr(ev.Len, pkgScope, -1) if ci3, ok52 := cv.(*ConstInt); ok52 { n = ci3.V } } return NewArray(elem, n) } case *syntax.Operation: if ev.Y == nil && ev.Op == token.Mul { base := ResolveTypeExprWithSubst(ev.X, typeSubst, pkgScope, prog) if base != nil { return NewPointer(base) } } case *syntax.MapType: key := ResolveTypeExprWithSubst(ev.Key, typeSubst, pkgScope, prog) val := ResolveTypeExprWithSubst(ev.Value, typeSubst, pkgScope, prog) if key != nil && val != nil { return NewTCMap(key, val) } case *syntax.ChanType: elem := ResolveTypeExprWithSubst(ev.Elem, typeSubst, pkgScope, prog) if elem != nil { dir := TCSendRecv if ev.Dir == syntax.SendOnly { dir = TCSendOnly } else if ev.Dir == syntax.RecvOnly { dir = TCRecvOnly } return NewTCChan(dir, elem) } case *syntax.InterfaceType: return resolveInterfaceFromAST(ev) case *syntax.FuncType: return resolveSignatureWithSubst(ev, typeSubst, pkgScope, prog) case *syntax.IndexExpr: // Generic instantiation Curve[P]: erase to the base named type. // Fields typed by the type param are substituted at use sites via // fb.typeSubst. return ResolveTypeExprWithSubst(ev.X, typeSubst, pkgScope, prog) case *syntax.SelectorExpr: if pkgName, ok := ev.X.(*syntax.Name); ok { if imp := cctx.universe.LookupByName(pkgName.Value); imp != nil { typeObj := imp.Scope.Lookup(ev.Sel.Value) if typeObj != nil { if tn, ok2 := typeObj.(*TypeName); ok2 { return tn.Typ } } } } } return nil } func resolveSignatureWithSubst(ft *syntax.FuncType, typeSubst map[string]Type, pkgScope *Scope, prog *SSAProgram) (s *Signature) { if ft == nil { return nil } var params []*TCVar variadic := false for _, p := range ft.ParamList { typ := ResolveTypeExprWithSubst(p.Type, typeSubst, pkgScope, prog) name := "" if p.Name != nil { name = p.Name.Value } params = append(params, NewTCVar(nil, name, typ)) } if len(ft.ParamList) > 0 { last := ft.ParamList[len(ft.ParamList)-1] if _, ok := last.Type.(*syntax.DotsType); ok { variadic = true } } var results []*TCVar for _, r := range ft.ResultList { typ := ResolveTypeExprWithSubst(r.Type, typeSubst, pkgScope, prog) name := "" if r.Name != nil { name = r.Name.Value } if typ == nil && r.Name != nil { if _, ok := r.Type.(*syntax.Name); ok { } } results = append(results, NewTCVar(nil, name, typ)) } var paramTuple *Tuple if len(params) > 0 { paramTuple = NewTuple(params...) } var resultTuple *Tuple if len(results) > 0 { resultTuple = NewTuple(results...) } return NewSignature(nil, paramTuple, resultTuple, variadic) } func matchTypeParam(paramType syntax.Expr, argType Type, tparams []*syntax.Field, typeMap map[string]Type) { if paramType == nil || argType == nil { return } switch pt := paramType.(type) { case *syntax.Name: for _, tp := range tparams { if tp.Name != nil && tp.Name.Value == pt.Value { typeMap[pt.Value] = argType return } } case *syntax.DotsType: matchTypeParam(pt.Elem, argType, tparams, typeMap) case *syntax.Operation: // pointer param AST: *T is Operation{Op: Mul, Y: nil} if pt.Y == nil && pt.Op == token.Mul { if p, ok := SafeUnderlying(argType).(*Pointer); ok { matchTypeParam(pt.X, p.Base, tparams, typeMap) } } case *syntax.SliceType: u := SafeUnderlying(argType) // In Moxie, string and []byte are the same type. Treat string as // []byte when matching against a slice type parameter. if b, ok := u.(*Basic); ok && b.Kind == TCString { u = NewSlice(Typ[Uint8]) } if sl, ok := u.(*Slice); ok { matchTypeParam(pt.Elem, sl.Elem, tparams, typeMap) } case *syntax.FuncType: sig, ok := SafeUnderlying(argType).(*Signature) if !ok { return } for i, p := range pt.ParamList { if sig.Params != nil && i < int32(sig.Params.Len()) { matchTypeParam(p.Type, sig.Params.At(int32(i)).Typ, tparams, typeMap) } } for i, r := range pt.ResultList { if sig.Results != nil && i < int32(sig.Results.Len()) { matchTypeParam(r.Type, sig.Results.At(int32(i)).Typ, tparams, typeMap) } } } } func inferFromConstraints(tparams []*syntax.Field, typeMap map[string]Type) { for _, tp := range tparams { if tp.Name == nil || tp.Type == nil { continue } knownType, ok := typeMap[tp.Name.Value] if !ok { continue } extractConstraintTypeParams(tp.Type, knownType, tparams, typeMap) } } func extractConstraintTypeParams(constraint syntax.Expr, concreteType Type, tparams []*syntax.Field, typeMap map[string]Type) { if constraint == nil || concreteType == nil { return } switch c := constraint.(type) { case *syntax.Operation: if c.Op == token.Tilde && c.Y == nil { extractConstraintTypeParams(c.X, concreteType, tparams, typeMap) } case *syntax.SliceType: // In Moxie, string and []byte are the same type. When the concrete // type is string (Basic), treat it as []byte for constraint matching. ct := concreteType u := SafeUnderlying(ct) if b, ok := u.(*Basic); ok && b.Kind == TCString { ct = NewSlice(Typ[Uint8]) u = ct } if sl, ok := u.(*Slice); ok { if name, ok2 := c.Elem.(*syntax.Name); ok2 { for _, tp := range tparams { if tp.Name != nil && tp.Name.Value == name.Value { if _, already := typeMap[name.Value]; !already { typeMap[name.Value] = sl.Elem } } } } } } } func inferTypeArgsFromFunc(fd *syntax.FuncDecl, args []SSAValue) (ts []Type) { typeMap := map[string]Type{} if fd.Type != nil { for i, param := range fd.Type.ParamList { if i >= len(args) || args[i] == nil || args[i].SSAType() == nil { continue } matchTypeParam(param.Type, args[i].SSAType(), fd.TParamList, typeMap) } } inferFromConstraints(fd.TParamList, typeMap) var result []Type for _, tp := range fd.TParamList { if tp.Name != nil { result = append(result, typeMap[tp.Name.Value]) } } return result } // inferTypeArgsFromArgASTs is a fallback for inferTypeArgsFromFunc when the // checker erased a generic instantiation (resolveGenericInst is a stub): the // checked type of an arg like ecdh.P256() is bare *Curve with no type args, // so type-level matching cannot bind P. Instead, unify the callee param's // type AST (*Curve[P]) against the argument EXPRESSION's declared type AST: // the result AST of the called function, or the enclosing function's param // AST (resolved through fb.typeSubst inside monomorphized bodies). func (fb *ssaFuncBuilder) inferTypeArgsFromArgASTs(fd *syntax.FuncDecl, argList []syntax.Expr, typeArgs []Type) (ts []Type) { anyNil := false for _, ta := range typeArgs { if ta == nil { anyNil = true } } if !anyNil { return typeArgs } typeMap := map[string]Type{} for i, tp := range fd.TParamList { if tp.Name != nil && i < len(typeArgs) && typeArgs[i] != nil { typeMap[tp.Name.Value] = typeArgs[i] } } if fd.Type != nil { for i, param := range fd.Type.ParamList { if i >= len(argList) { break } argAST, argSubst, argScope := fb.declaredTypeAST(argList[i]) if argAST == nil { continue } fb.unifyParamAST(param.Type, argAST, fd.TParamList, typeMap, argSubst, argScope) } } inferFromConstraints(fd.TParamList, typeMap) var result []Type for _, tp := range fd.TParamList { if tp.Name != nil { result = append(result, typeMap[tp.Name.Value]) } } return result } // declaredTypeAST returns the source-declared type AST of an argument // expression plus the subst map and scope needed to resolve concrete names // inside it. Two shapes matter: a call to a function whose FuncDecl AST is // recorded (its result type AST carries the un-erased generic instantiation), // and a bare name referring to a parameter of the enclosing function. func (fb *ssaFuncBuilder) declaredTypeAST(arg syntax.Expr) (e syntax.Expr, m map[string]Type, sc *Scope) { switch a := arg.(type) { case *syntax.CallExpr: path := "" fname := "" switch fun := a.Fun.(type) { case *syntax.SelectorExpr: if pkgName, ok53 := fun.X.(*syntax.Name); ok53 { if pn, ok2 := fb.lookupObject(pkgName.Value).(*PkgName); ok2 && pn.Imported != nil { path = pn.Imported.Path fname = fun.Sel.Value } } case *syntax.Name: path = fb.fn.Pkg.Pkg.Path fname = fun.Value if fb.srcScope != nil { for gPkg, gScope := range fb.fn.Prog.GenericPkgScopes { if gScope == fb.srcScope { path = gPkg break } } } } if fname == "" { return nil, nil, nil } callee, ok := fb.fn.Prog.GenericPkgLocalDecls[path|"."|fname] if !ok { return nil, nil, nil } if callee.Type == nil || len(callee.Type.ResultList) != 1 { return nil, nil, nil } var scope *Scope if fb.fn.Prog.GenericPkgScopes != nil { scope = fb.fn.Prog.GenericPkgScopes[path] } return callee.Type.ResultList[0].Type, map[string]Type{}, scope case *syntax.Name: // Walk the closure parent chain: a free variable captured by a // closure inside a monomorphized body is declared as a param of an // enclosing function, not of the closure itself. for cur := fb; cur != nil; cur = cur.parent { if cur.decl == nil || cur.decl.Type == nil { continue } for _, p := range cur.decl.Type.ParamList { if p.Name != nil && p.Name.Value == a.Value { return p.Type, cur.typeSubst, cur.srcScope } } } } return nil, nil, nil } // unifyParamAST structurally unifies a callee parameter type AST against an // argument's declared type AST, binding callee type params in typeMap. Leaf // exprs on the arg side are resolved with (argSubst, argScope). func (fb *ssaFuncBuilder) unifyParamAST(paramAST syntax.Expr, argAST syntax.Expr, tparams []*syntax.Field, typeMap map[string]Type, argSubst map[string]Type, argScope *Scope) { if paramAST == nil || argAST == nil { return } switch pt := paramAST.(type) { case *syntax.Name: for _, tp := range tparams { if tp.Name != nil && tp.Name.Value == pt.Value { if _, already := typeMap[pt.Value]; already { return } t := ResolveTypeExprWithSubst(argAST, argSubst, argScope, fb.fn.Prog) if t != nil { typeMap[pt.Value] = t } return } } case *syntax.Operation: if pt.Y == nil && pt.Op == token.Mul { if at, ok := argAST.(*syntax.Operation); ok && at.Y == nil && at.Op == token.Mul { fb.unifyParamAST(pt.X, at.X, tparams, typeMap, argSubst, argScope) } } case *syntax.IndexExpr: if at, ok := argAST.(*syntax.IndexExpr); ok { fb.unifyParamAST(pt.Index, at.Index, tparams, typeMap, argSubst, argScope) } case *syntax.ListExpr: if at, ok := argAST.(*syntax.ListExpr); ok { for i, el := range pt.ElemList { if i < len(at.ElemList) { fb.unifyParamAST(el, at.ElemList[i], tparams, typeMap, argSubst, argScope) } } } case *syntax.SliceType: if at, ok := argAST.(*syntax.SliceType); ok { fb.unifyParamAST(pt.Elem, at.Elem, tparams, typeMap, argSubst, argScope) } case *syntax.DotsType: fb.unifyParamAST(pt.Elem, argAST, tparams, typeMap, argSubst, argScope) } } // monomorphizeAndCall returns (value, handled). handled is true when the call // was emitted - the value is nil for void functions, so callers must NOT use // value-nilness to decide whether to fall through to a non-generic call path // (that double-emits the call). func (fb *ssaFuncBuilder) monomorphizeAndCall(fd *syntax.FuncDecl, baseName string, typeArgs []Type, args []SSAValue, targetPkg *SSAPackage, srcPkgPath string, hasDots bool) (s SSAValue, handled bool) { for _, ta := range typeArgs { if ta == nil { return nil, false } } mangledName := mangleGenericName(baseName, typeArgs) if srcPkgPath != "" { // Two generic funcs with the same name from different packages can // mangle identically (e.g. crypto/pbkdf2.Key and // crypto/internal/fips140/pbkdf2.Key, both instantiated at // hash.Hash); without the source package in the name the second // instantiation resolves to the first and recurses. pfx := "" for j := 0; j < len(srcPkgPath); j++ { c := srcPkgPath[j] if c == '/' { pfx = pfx | "_" } else { pfx = pfx | string([]byte{c}) } } mangledName = pfx | "." | mangledName } if fn5, ok54 := targetPkg.Members[mangledName].(*SSAFunction); ok54 { return fb.emitCallToSSAFunc(fn5, args, hasDots), true } typeSubst := map[string]Type{} for i, tp := range fd.TParamList { if tp.Name != nil && i < len(typeArgs) && typeArgs[i] != nil { typeSubst[tp.Name.Value] = typeArgs[i] } } var srcScope *Scope if fb.fn.Prog.GenericPkgScopes != nil { if sc, ok := fb.fn.Prog.GenericPkgScopes[srcPkgPath]; ok { srcScope = sc } } sig := resolveSignatureWithSubst(fd.Type, typeSubst, srcScope, fb.fn.Prog) if sig == nil { return nil, false } fn := &SSAFunction{ Name: mangledName, Signature: sig, Pkg: targetPkg, Prog: fb.fn.Prog, } targetPkg.SetMember(mangledName, fn) subFb := newSSAFuncBuilder(fn, fb.info) subFb.typeSubst = typeSubst subFb.srcScope = srcScope subFb.buildBody(fd) return fb.emitCallToSSAFunc(fn, args, hasDots), true } func (fb *ssaFuncBuilder) emitCallToSSAFunc(fn *SSAFunction, args []SSAValue, hasDots bool) (s SSAValue) { if fn.Signature != nil { if fn.Signature.Variadic && !hasDots { args = fb.wrapVariadicArgs(args, fn.Signature) } args = fb.coerceArgsToInterface(args, fn.Signature) } var retType Type if fn.Signature != nil && fn.Signature.Results != nil { if fn.Signature.Results.Len() == 1 { retType = fn.Signature.Results.At(0).Typ } else if fn.Signature.Results.Len() > 1 { retType = fn.Signature.Results } } call := &SSACall{Call: SSACallCommon{Value: fn, Args: args}} call.typ = retType call.name = fb.nextName() fb.emit(call) if retType == nil { return nil } return call } func (fb *ssaFuncBuilder) tryGenericLocalCall(funcName string, argList []syntax.Expr, hasDots bool) (s SSAValue, handled bool) { pkgPath := fb.fn.Pkg.Pkg.Path key := pkgPath | "." | funcName fd, ok := fb.fn.Prog.GenericFuncDecls[key] if !ok && fb.srcScope != nil { for gPkg, gScope := range fb.fn.Prog.GenericPkgScopes { if gScope == fb.srcScope { key = gPkg | "." | funcName fd, ok = fb.fn.Prog.GenericFuncDecls[key] if !ok && gPkg != fb.fn.Pkg.Pkg.Path { // Package-local non-generic helper called from a // monomorphized body in ANOTHER package: instantiate it // here, its symbol is unexported in the source package's // object. Within the source package itself the local // function is directly available. fd, ok = fb.fn.Prog.GenericPkgLocalDecls[key] } if ok { pkgPath = gPkg break } } } } if !ok { return nil, false } args := fb.buildArgs(argList) typeArgs := inferTypeArgsFromFunc(fd, args) typeArgs = fb.inferTypeArgsFromArgASTs(fd, argList, typeArgs) return fb.monomorphizeAndCall(fd, funcName, typeArgs, args, fb.fn.Pkg, pkgPath, hasDots) } func (fb *ssaFuncBuilder) tryGenericPkgCall(pn *PkgName, funcName string, argList []syntax.Expr, hasDots bool) (s SSAValue, handled bool) { imported := pn.Imported if imported == nil { return nil, false } key := imported.Path | "." | funcName fd, ok := fb.fn.Prog.GenericFuncDecls[key] if !ok { return nil, false } args := fb.buildArgs(argList) typeArgs := inferTypeArgsFromFunc(fd, args) typeArgs = fb.inferTypeArgsFromArgASTs(fd, argList, typeArgs) return fb.monomorphizeAndCall(fd, funcName, typeArgs, args, fb.fn.Pkg, imported.Path, hasDots) } func (fb *ssaFuncBuilder) tryGenericCallFromIndex(ie *syntax.IndexExpr, argList []syntax.Expr, hasDots bool) (s SSAValue, handled bool) { switch x := ie.X.(type) { case *syntax.Name: pkgPath := fb.fn.Pkg.Pkg.Path key := pkgPath | "." | x.Value fd, ok := fb.fn.Prog.GenericFuncDecls[key] if !ok && fb.srcScope != nil { for gPkg, gScope := range fb.fn.Prog.GenericPkgScopes { if gScope == fb.srcScope { key = gPkg | "." | x.Value fd, ok = fb.fn.Prog.GenericFuncDecls[key] if ok { pkgPath = gPkg break } } } } if !ok { return nil, false } typeArgs := fb.resolveExplicitTypeArgs(ie.Index, fd) args := fb.buildArgs(argList) return fb.monomorphizeAndCall(fd, x.Value, typeArgs, args, fb.fn.Pkg, pkgPath, hasDots) case *syntax.SelectorExpr: if pkgName, ok2 := x.X.(*syntax.Name); ok2 { obj := fb.lookupObject(pkgName.Value) if pn, ok3 := obj.(*PkgName); ok3 && pn.Imported != nil { key := pn.Imported.Path | "." | x.Sel.Value fd, ok4 := fb.fn.Prog.GenericFuncDecls[key] if !ok4 { return nil, false } typeArgs := fb.resolveExplicitTypeArgs(ie.Index, fd) args := fb.buildArgs(argList) return fb.monomorphizeAndCall(fd, x.Sel.Value, typeArgs, args, fb.fn.Pkg, pn.Imported.Path, hasDots) } } } return nil, false } func (fb *ssaFuncBuilder) resolveExplicitTypeArgs(indexExpr syntax.Expr, fd *syntax.FuncDecl) (ts []Type) { var typeExprs []syntax.Expr if le, ok := indexExpr.(*syntax.ListExpr); ok { typeExprs = le.ElemList } else { typeExprs = []syntax.Expr{indexExpr} } var result []Type for _, te := range typeExprs { t := fb.resolveTypeAST(te) result = append(result, t) } if len(result) < len(fd.TParamList) { typeMap := map[string]Type{} for i, tp := range fd.TParamList { if tp.Name != nil && i < len(result) && result[i] != nil { typeMap[tp.Name.Value] = result[i] } } inferFromConstraints(fd.TParamList, typeMap) result = nil for _, tp := range fd.TParamList { if tp.Name != nil { result = append(result, typeMap[tp.Name.Value]) } } } return result } func exprTypeName(e syntax.Expr) (s string) { switch ev := e.(type) { case *syntax.Name: return "Name(" | ev.Value | ")" case *syntax.BasicLit: return "BasicLit" case *syntax.SelectorExpr: if x, ok := ev.X.(*syntax.Name); ok { return "Selector(" | x.Value | "." | ev.Sel.Value | ")" } return "Selector" case *syntax.CallExpr: return "Call" case *syntax.Operation: return "Op" } return "unknown" } func resolveInterfaceFromAST(e *syntax.InterfaceType) (t *TCInterface) { var methods []*IfaceMethod if e.MethodList != nil { for _, f := range e.MethodList { if f.Name != nil { m := NewTCIfaceMethod(f.Name.Value, nil) methods = append(methods, m) } } } iface := NewTCInterface(methods, nil) iface.Complete() return iface }