github.com/goplus/igop@v0.17.0/visit.go (about) 1 /* 2 * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package igop 18 19 import ( 20 "fmt" 21 "go/token" 22 "go/types" 23 "log" 24 "reflect" 25 "strings" 26 27 "github.com/goplus/igop/load" 28 "github.com/visualfc/xtype" 29 "golang.org/x/tools/go/ssa" 30 ) 31 32 const ( 33 fnBase = 100 34 ) 35 36 func checkPackages(intp *Interp, pkgs []*ssa.Package) (err error) { 37 if intp.ctx.Mode&DisableRecover == 0 { 38 defer func() { 39 if v := recover(); v != nil { 40 err = v.(error) 41 } 42 }() 43 } 44 visit := visitor{ 45 intp: intp, 46 prog: intp.mainpkg.Prog, 47 pkgs: make(map[*ssa.Package]bool), 48 seen: make(map[*ssa.Function]bool), 49 base: fnBase, 50 } 51 for _, pkg := range pkgs { 52 visit.pkgs[pkg] = true 53 } 54 visit.program() 55 return 56 } 57 58 type visitor struct { 59 intp *Interp 60 prog *ssa.Program 61 pkgs map[*ssa.Package]bool 62 seen map[*ssa.Function]bool 63 base int 64 } 65 66 func (visit *visitor) program() { 67 chks := make(map[string]bool) 68 chks[""] = true // anonymous struct embed named type 69 for pkg := range visit.pkgs { 70 chks[pkg.Pkg.Path()] = true 71 for _, mem := range pkg.Members { 72 if fn, ok := mem.(*ssa.Function); ok { 73 visit.function(fn) 74 } 75 } 76 } 77 isExtern := func(typ reflect.Type) bool { 78 if typ.Kind() == reflect.Ptr { 79 typ = typ.Elem() 80 } 81 return !chks[typ.PkgPath()] 82 } 83 for _, T := range visit.prog.RuntimeTypes() { 84 typ := visit.intp.preToType(T) 85 // skip extern type 86 if isExtern(typ) { 87 continue 88 } 89 mmap := make(map[string]*ssa.Function) 90 mset := visit.prog.MethodSets.MethodSet(T) 91 for i, n := 0, mset.Len(); i < n; i++ { 92 sel := mset.At(i) 93 obj := sel.Obj() 94 // skip embed extern type method 95 if pkg := obj.Pkg(); pkg != nil && !chks[pkg.Path()] { 96 continue 97 } 98 fn := visit.prog.MethodValue(sel) 99 mmap[obj.Name()] = fn 100 visit.function(fn) 101 } 102 visit.intp.msets[typ] = mmap 103 } 104 } 105 106 func (visit *visitor) findLinkSym(fn *ssa.Function) (*load.LinkSym, bool) { 107 if sp, ok := visit.intp.ctx.pkgs[fn.Pkg.Pkg.Path()]; ok { 108 for _, link := range sp.Links { 109 if link.Name == fn.Name() { 110 return link, true 111 } 112 } 113 } 114 return nil, false 115 } 116 117 func (visit *visitor) findFunction(sym *load.LinkSym) *ssa.Function { 118 for pkg := range visit.pkgs { 119 if pkg.Pkg.Path() == sym.Linkname.PkgPath { 120 if typ := sym.Linkname.Recv; typ != "" { 121 var star bool 122 if typ[0] == '*' { 123 star = true 124 typ = typ[1:] 125 } 126 if obj := pkg.Pkg.Scope().Lookup(typ); obj != nil { 127 t := obj.Type() 128 if star { 129 t = types.NewPointer(t) 130 } 131 return visit.prog.LookupMethod(t, pkg.Pkg, sym.Linkname.Method) 132 } 133 } 134 return pkg.Func(sym.Linkname.Name) 135 } 136 } 137 return nil 138 } 139 140 func wrapMethodType(sig *types.Signature) *types.Signature { 141 params := sig.Params() 142 n := params.Len() 143 list := make([]*types.Var, n+1) 144 list[0] = sig.Recv() 145 for i := 0; i < n; i++ { 146 list[i+1] = params.At(i) 147 } 148 return types.NewSignature(nil, types.NewTuple(list...), sig.Results(), sig.Variadic()) 149 } 150 151 func (visit *visitor) findLinkFunc(sym *load.LinkSym) (ext reflect.Value, ok bool) { 152 ext, ok = findExternLinkFunc(visit.intp, &sym.Linkname) 153 if ok { 154 return 155 } 156 if link := visit.findFunction(sym); link != nil { 157 visit.function(link) 158 sig := link.Signature 159 if sig.Recv() != nil { 160 sig = wrapMethodType(sig) 161 } 162 typ := visit.intp.preToType(sig) 163 pfn := visit.intp.loadFunction(link) 164 ext = pfn.makeFunction(typ, nil) 165 ok = true 166 } 167 return 168 } 169 170 func (visit *visitor) function(fn *ssa.Function) { 171 if visit.seen[fn] { 172 return 173 } 174 if hasTypeParam(fn.Type()) { 175 return 176 } 177 visit.seen[fn] = true 178 fnPath := fn.String() 179 if f, ok := visit.intp.ctx.override[fnPath]; ok && 180 visit.intp.preToType(fn.Type()) == f.Type() { 181 fn.Blocks = nil 182 return 183 } 184 if fn.Blocks == nil { 185 if _, ok := visit.pkgs[fn.Pkg]; ok { 186 if _, ok = findExternFunc(visit.intp, fn); !ok { 187 if sym, ok := visit.findLinkSym(fn); ok { 188 if ext, ok := visit.findLinkFunc(sym); ok { 189 typ := visit.intp.preToType(fn.Type()) 190 ftyp := ext.Type() 191 if typ != ftyp { 192 ext = xtype.ConvertFunc(ext, xtype.TypeOfType(typ)) 193 } 194 visit.intp.ctx.override[fnPath] = ext 195 return 196 } 197 } 198 if visit.intp.ctx.Mode&EnableNoStrict != 0 { 199 typ := visit.intp.preToType(fn.Type()) 200 numOut := typ.NumOut() 201 if numOut == 0 { 202 visit.intp.ctx.override[fnPath] = reflect.MakeFunc(typ, func(args []reflect.Value) (results []reflect.Value) { 203 return 204 }) 205 } else { 206 visit.intp.ctx.override[fnPath] = reflect.MakeFunc(typ, func(args []reflect.Value) (results []reflect.Value) { 207 results = make([]reflect.Value, numOut) 208 for i := 0; i < numOut; i++ { 209 results[i] = reflect.New(typ.Out(i)).Elem() 210 } 211 return 212 }) 213 } 214 println(fmt.Sprintf("igop warning: %v: %v missing function body", visit.intp.ctx.FileSet.Position(fn.Pos()), fnPath)) 215 return 216 } 217 panic(fmt.Errorf("%v: %v missing function body", visit.intp.ctx.FileSet.Position(fn.Pos()), fnPath)) 218 } 219 } 220 return 221 } 222 if len(fn.TypeArgs()) != 0 { 223 visit.intp.record.EnterInstance(fn) 224 defer visit.intp.record.LeaveInstance(fn) 225 } 226 visit.intp.loadType(fn.Type()) 227 for _, alloc := range fn.Locals { 228 visit.intp.loadType(alloc.Type()) 229 visit.intp.loadType(deref(alloc.Type())) 230 } 231 pfn := visit.intp.loadFunction(fn) 232 for _, p := range fn.Params { 233 pfn.regIndex(p) 234 } 235 for _, p := range fn.FreeVars { 236 pfn.regIndex(p) 237 } 238 var buf [32]*ssa.Value // avoid alloc in common case 239 for _, b := range fn.Blocks { 240 Instrs := make([]func(*frame), len(b.Instrs)) 241 ssaInstrs := make([]ssa.Instruction, len(b.Instrs)) 242 var index int 243 n := len(b.Instrs) 244 for i := 0; i < n; i++ { 245 instr := b.Instrs[i] 246 ops := instr.Operands(buf[:0]) 247 switch instr := instr.(type) { 248 case *ssa.Alloc: 249 visit.intp.loadType(instr.Type()) 250 visit.intp.loadType(deref(instr.Type())) 251 case *ssa.Next: 252 // skip *ssa.opaqueType: iter 253 ops = nil 254 case *ssa.Extract: 255 // skip 256 ops = nil 257 case *ssa.TypeAssert: 258 visit.intp.loadType(instr.AssertedType) 259 case *ssa.MakeChan: 260 visit.intp.loadType(instr.Type()) 261 case *ssa.MakeMap: 262 visit.intp.loadType(instr.Type()) 263 case *ssa.MakeSlice: 264 visit.intp.loadType(instr.Type()) 265 case *ssa.SliceToArrayPointer: 266 visit.intp.loadType(instr.Type()) 267 case *ssa.Convert: 268 visit.intp.loadType(instr.Type()) 269 case *ssa.ChangeType: 270 visit.intp.loadType(instr.Type()) 271 case *ssa.MakeInterface: 272 visit.intp.loadType(instr.Type()) 273 } 274 for _, op := range ops { 275 switch v := (*op).(type) { 276 case *ssa.Function: 277 visit.function(v) 278 case nil: 279 // skip 280 default: 281 visit.intp.loadType(v.Type()) 282 } 283 } 284 pfn.makeInstr = instr 285 ifn := makeInstr(visit.intp, pfn, instr) 286 if ifn == nil { 287 continue 288 } 289 if visit.intp.ctx.evalMode && fn.String() == "main.init" { 290 if visit.intp.ctx.evalInit == nil { 291 visit.intp.ctx.evalInit = make(map[string]bool) 292 } 293 if call, ok := instr.(*ssa.Call); ok { 294 key := call.String() 295 if strings.HasPrefix(key, "init#") { 296 if visit.intp.ctx.evalInit[key] { 297 ifn = func(fr *frame) {} 298 } else { 299 visit.intp.ctx.evalInit[key] = true 300 } 301 } 302 } 303 } 304 if visit.intp.ctx.evalCallFn != nil { 305 if call, ok := instr.(*ssa.Call); ok { 306 ir := pfn.regIndex(call) 307 results := call.Call.Signature().Results() 308 ofn := ifn 309 switch results.Len() { 310 case 0: 311 ifn = func(fr *frame) { 312 ofn(fr) 313 visit.intp.ctx.evalCallFn(visit.intp, call) 314 } 315 case 1: 316 ifn = func(fr *frame) { 317 ofn(fr) 318 visit.intp.ctx.evalCallFn(visit.intp, call, fr.reg(ir)) 319 } 320 default: 321 ifn = func(fr *frame) { 322 ofn(fr) 323 r := fr.reg(ir).(tuple) 324 visit.intp.ctx.evalCallFn(visit.intp, call, r...) 325 } 326 } 327 } 328 } 329 if visit.intp.ctx.Mode&EnableTracing != 0 { 330 ofn := ifn 331 ifn = func(fr *frame) { 332 if v, ok := instr.(ssa.Value); ok { 333 log.Printf("\t%-20T %v = %-40v\t%v\n", instr, v.Name(), instr, v.Type()) 334 } else { 335 log.Printf("\t%-20T %v\n", instr, instr) 336 } 337 ofn(fr) 338 } 339 if index == 0 { 340 ofn := ifn 341 bi := b.Index 342 common := b.Comment 343 ifn = func(fr *frame) { 344 log.Printf(".%v %v\n", bi, common) 345 ofn(fr) 346 } 347 } 348 if index == 0 && b.Index == 0 { 349 ofn := ifn 350 ifn = func(fr *frame) { 351 log.Printf("Entering %v%v.", fr.pfn.Fn, loc(fr.interp.ctx.FileSet, fr.pfn.Fn.Pos())) 352 ofn(fr) 353 } 354 } 355 if _, ok := instr.(*ssa.Return); ok { 356 ofn := ifn 357 ifn = func(fr *frame) { 358 ofn(fr) 359 var caller ssa.Instruction 360 if fr.caller != nil { 361 caller = fr.caller.pfn.InstrForPC(fr.caller.ipc - 1) 362 } 363 if caller == nil { 364 log.Printf("Leaving %v.\n", fr.pfn.Fn) 365 } else { 366 log.Printf("Leaving %v, resuming %v call %v%v.\n", 367 fr.pfn.Fn, fr.caller.pfn.Fn, caller, loc(fr.interp.ctx.FileSet, caller.Pos())) 368 } 369 } 370 } 371 } 372 Instrs[index] = ifn 373 ssaInstrs[index] = instr 374 index++ 375 } 376 offset := len(pfn.Instrs) 377 pfn.Blocks = append(pfn.Blocks, offset) 378 pfn.Instrs = append(pfn.Instrs, Instrs[:index]...) 379 pfn.ssaInstrs = append(pfn.ssaInstrs, ssaInstrs[:index]...) 380 if b == fn.Recover && visit.intp.ctx.Mode&DisableRecover == 0 { 381 pfn.Recover = pfn.Instrs[offset:] 382 } 383 } 384 pfn.makeInstr = nil 385 pfn.base = visit.base 386 visit.base += len(pfn.ssaInstrs) + 2 387 pfn.initPool() 388 } 389 390 func loc(fset *token.FileSet, pos token.Pos) string { 391 if pos == token.NoPos { 392 return "" 393 } 394 return " at " + fset.Position(pos).String() 395 }