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  }