github.com/goplus/llgo@v0.8.3/cl/import.go (about)

     1  /*
     2   * Copyright (c) 2024 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 cl
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"go/ast"
    23  	"go/constant"
    24  	"go/token"
    25  	"go/types"
    26  	"os"
    27  	"strings"
    28  
    29  	llssa "github.com/goplus/llgo/ssa"
    30  	"golang.org/x/tools/go/ssa"
    31  )
    32  
    33  // -----------------------------------------------------------------------------
    34  
    35  type symInfo struct {
    36  	file     string
    37  	fullName string
    38  	isVar    bool
    39  }
    40  
    41  type pkgSymInfo struct {
    42  	files map[string][]byte  // file => content
    43  	syms  map[string]symInfo // name => isVar
    44  }
    45  
    46  func newPkgSymInfo() *pkgSymInfo {
    47  	return &pkgSymInfo{
    48  		files: make(map[string][]byte),
    49  		syms:  make(map[string]symInfo),
    50  	}
    51  }
    52  
    53  func (p *pkgSymInfo) addSym(fset *token.FileSet, pos token.Pos, fullName, inPkgName string, isVar bool) {
    54  	f := fset.File(pos)
    55  	if fp := f.Position(pos); fp.Line > 2 {
    56  		file := fp.Filename
    57  		if _, ok := p.files[file]; !ok {
    58  			b, err := os.ReadFile(file)
    59  			if err == nil {
    60  				p.files[file] = b
    61  			}
    62  		}
    63  		p.syms[inPkgName] = symInfo{file, fullName, isVar}
    64  	}
    65  }
    66  
    67  func (p *pkgSymInfo) initLinknames(ctx *context) {
    68  	for file, b := range p.files {
    69  		lines := bytes.Split(b, []byte{'\n'})
    70  		for _, line := range lines {
    71  			ctx.initLinkname(string(line), func(inPkgName string) (fullName string, isVar, ok bool) {
    72  				if sym, ok := p.syms[inPkgName]; ok && file == sym.file {
    73  					return sym.fullName, sym.isVar, true
    74  				}
    75  				return
    76  			})
    77  		}
    78  	}
    79  }
    80  
    81  // PkgKindOf returns the kind of a package.
    82  func PkgKindOf(pkg *types.Package) (int, string) {
    83  	scope := pkg.Scope()
    84  	kind, param := pkgKindByScope(scope)
    85  	if kind == PkgNormal {
    86  		kind = pkgKindByPath(pkg.Path())
    87  	}
    88  	return kind, param
    89  }
    90  
    91  // decl: a package that only contains declarations
    92  // noinit: a package that does not need to be initialized
    93  func pkgKind(v string) (int, string) {
    94  	switch v {
    95  	case "link":
    96  		return PkgLinkIR, ""
    97  	case "decl":
    98  		return PkgDeclOnly, ""
    99  	case "noinit":
   100  		return PkgNoInit, ""
   101  	default:
   102  		// case "link:bc":
   103  		//	return PkgLinkBitCode
   104  		if strings.HasPrefix(v, "link:") { // "link: <libpath>"
   105  			return PkgLinkExtern, v[5:]
   106  		} else if strings.HasPrefix(v, "py.") { // "py.<module>"
   107  			return PkgPyModule, v[3:]
   108  		}
   109  	}
   110  	return PkgLLGo, ""
   111  }
   112  
   113  func pkgKindByScope(scope *types.Scope) (int, string) {
   114  	if v, ok := scope.Lookup("LLGoPackage").(*types.Const); ok {
   115  		if v := v.Val(); v.Kind() == constant.String {
   116  			return pkgKind(constant.StringVal(v))
   117  		}
   118  		return PkgLLGo, ""
   119  	}
   120  	return PkgNormal, ""
   121  }
   122  
   123  func (p *context) importPkg(pkg *types.Package, i *pkgInfo) {
   124  	scope := pkg.Scope()
   125  	kind, _ := pkgKindByScope(scope)
   126  	if kind == PkgNormal {
   127  		return
   128  	}
   129  	i.kind = kind
   130  	fset := p.fset
   131  	pkgPath := llssa.PathOf(pkg)
   132  	names := scope.Names()
   133  	syms := newPkgSymInfo()
   134  	for _, name := range names {
   135  		obj := scope.Lookup(name)
   136  		switch obj := obj.(type) {
   137  		case *types.Func:
   138  			if pos := obj.Pos(); pos != token.NoPos {
   139  				fullName, inPkgName := typesFuncName(pkgPath, obj)
   140  				syms.addSym(fset, pos, fullName, inPkgName, false)
   141  			}
   142  		case *types.TypeName:
   143  			if !obj.IsAlias() {
   144  				if t, ok := obj.Type().(*types.Named); ok {
   145  					for i, n := 0, t.NumMethods(); i < n; i++ {
   146  						fn := t.Method(i)
   147  						fullName, inPkgName := typesFuncName(pkgPath, fn)
   148  						syms.addSym(fset, fn.Pos(), fullName, inPkgName, false)
   149  					}
   150  				}
   151  			}
   152  		case *types.Var:
   153  			if pos := obj.Pos(); pos != token.NoPos {
   154  				syms.addSym(fset, pos, pkgPath+"."+name, name, true)
   155  			}
   156  		}
   157  	}
   158  	syms.initLinknames(p)
   159  }
   160  
   161  func (p *context) initFiles(pkgPath string, files []*ast.File) {
   162  	for _, file := range files {
   163  		for _, decl := range file.Decls {
   164  			switch decl := decl.(type) {
   165  			case *ast.FuncDecl:
   166  				fullName, inPkgName := astFuncName(pkgPath, decl)
   167  				p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false)
   168  			case *ast.GenDecl:
   169  				if decl.Tok == token.VAR && len(decl.Specs) == 1 {
   170  					if names := decl.Specs[0].(*ast.ValueSpec).Names; len(names) == 1 {
   171  						inPkgName := names[0].Name
   172  						p.initLinknameByDoc(decl.Doc, pkgPath+"."+inPkgName, inPkgName, true)
   173  					}
   174  				}
   175  			}
   176  		}
   177  	}
   178  }
   179  
   180  func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName string, isVar bool) {
   181  	if doc != nil {
   182  		if n := len(doc.List); n > 0 {
   183  			line := doc.List[n-1].Text
   184  			p.initLinkname(line, func(name string) (_ string, _, ok bool) {
   185  				return fullName, isVar, name == inPkgName
   186  			})
   187  		}
   188  	}
   189  }
   190  
   191  func (p *context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) {
   192  	const (
   193  		linkname  = "//go:linkname "
   194  		llgolink  = "//llgo:link "
   195  		llgolink2 = "// llgo:link "
   196  	)
   197  	if strings.HasPrefix(line, linkname) {
   198  		p.initLink(line, len(linkname), f)
   199  	} else if strings.HasPrefix(line, llgolink2) {
   200  		p.initLink(line, len(llgolink2), f)
   201  	} else if strings.HasPrefix(line, llgolink) {
   202  		p.initLink(line, len(llgolink), f)
   203  	}
   204  }
   205  
   206  func (p *context) initLink(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) {
   207  	text := strings.TrimSpace(line[prefix:])
   208  	if idx := strings.IndexByte(text, ' '); idx > 0 {
   209  		inPkgName := text[:idx]
   210  		if fullName, isVar, ok := f(inPkgName); ok {
   211  			link := strings.TrimLeft(text[idx+1:], " ")
   212  			if isVar || strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr
   213  				p.link[fullName] = link
   214  			} else {
   215  				panic(line + ": no specified call convention. eg. //go:linkname Printf C.printf")
   216  			}
   217  		} else if c := inPkgName[0]; c >= 'A' && c <= 'Z' {
   218  			fmt.Fprintln(os.Stderr, "==>", line)
   219  			fmt.Fprintf(os.Stderr, "llgo: linkname %s not found and ignored\n", inPkgName)
   220  		}
   221  	}
   222  }
   223  
   224  func recvTypeName(t ast.Expr) string {
   225  	switch t := t.(type) {
   226  	case *ast.Ident:
   227  		return t.Name
   228  	case *ast.IndexExpr:
   229  		return trecvTypeName(t.X, t.Index)
   230  	case *ast.IndexListExpr:
   231  		return trecvTypeName(t.X, t.Indices...)
   232  	}
   233  	panic("unreachable")
   234  }
   235  
   236  // TODO(xsw): support generic type
   237  func trecvTypeName(t ast.Expr, indices ...ast.Expr) string {
   238  	_ = indices
   239  	return t.(*ast.Ident).Name
   240  }
   241  
   242  // inPkgName:
   243  // - func: name
   244  // - method: (T).name, (*T).name
   245  // fullName:
   246  // - func: pkg.name
   247  // - method: (pkg.T).name, (*pkg.T).name
   248  func astFuncName(pkgPath string, fn *ast.FuncDecl) (fullName, inPkgName string) {
   249  	name := fn.Name.Name
   250  	if recv := fn.Recv; recv != nil && len(recv.List) == 1 {
   251  		tPrefix := "("
   252  		t := recv.List[0].Type
   253  		if tp, ok := t.(*ast.StarExpr); ok {
   254  			t, tPrefix = tp.X, "(*"
   255  		}
   256  		tSuffix := recvTypeName(t) + ")." + name
   257  		return tPrefix + pkgPath + "." + tSuffix, tPrefix + tSuffix
   258  	}
   259  	return pkgPath + "." + name, name
   260  }
   261  
   262  func typesFuncName(pkgPath string, fn *types.Func) (fullName, inPkgName string) {
   263  	sig := fn.Type().(*types.Signature)
   264  	name := fn.Name()
   265  	if recv := sig.Recv(); recv != nil {
   266  		tPrefix := "("
   267  		t := recv.Type()
   268  		if tp, ok := t.(*types.Pointer); ok {
   269  			t, tPrefix = tp.Elem(), "(*"
   270  		}
   271  		tSuffix := t.(*types.Named).Obj().Name() + ")." + name
   272  		return tPrefix + pkgPath + "." + tSuffix, tPrefix + tSuffix
   273  	}
   274  	return pkgPath + "." + name, name
   275  }
   276  
   277  // TODO(xsw): may can use typesFuncName
   278  // fullName:
   279  // - func: pkg.name
   280  // - method: (pkg.T).name, (*pkg.T).name
   281  func funcName(pkg *types.Package, fn *ssa.Function) string {
   282  	sig := fn.Signature
   283  	name := fn.Name()
   284  	if recv := sig.Recv(); recv != nil {
   285  		var tName string
   286  		t := recv.Type()
   287  		if tp, ok := t.(*types.Pointer); ok {
   288  			t, tName = tp.Elem(), "*"
   289  		}
   290  		tName += llssa.NameOf(t.(*types.Named))
   291  		return "(" + tName + ")." + name
   292  	}
   293  	ret := llssa.FullName(pkg, name)
   294  	if ret == "main.main" {
   295  		ret = "main"
   296  	}
   297  	return ret
   298  }
   299  
   300  func checkCgo(fnName string) bool {
   301  	return len(fnName) > 4 && fnName[0] == '_' && fnName[2] == 'g' && fnName[3] == 'o' &&
   302  		(fnName[1] == 'C' || fnName[1] == 'c') &&
   303  		(fnName[4] == '_' || strings.HasPrefix(fnName[4:], "Check"))
   304  }
   305  
   306  const (
   307  	ignoredFunc = iota
   308  	goFunc      = int(llssa.InGo)
   309  	cFunc       = int(llssa.InC)
   310  	pyFunc      = int(llssa.InPython)
   311  	llgoInstr   = -1
   312  
   313  	llgoInstrBase   = 0x80
   314  	llgoUnreachable = llgoInstrBase + 0
   315  	llgoCstr        = llgoInstrBase + 1
   316  	llgoAlloca      = llgoInstrBase + 2
   317  	llgoAllocaCStr  = llgoInstrBase + 3
   318  	llgoAdvance     = llgoInstrBase + 4
   319  	llgoIndex       = llgoInstrBase + 5
   320  	llgoStringData  = llgoInstrBase + 6
   321  	llgoPyList      = llgoInstrBase + 7
   322  )
   323  
   324  func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) {
   325  	var pkg *types.Package
   326  	var orgName string
   327  	if origin := fn.Origin(); origin != nil {
   328  		pkg = origin.Pkg.Pkg
   329  		p.ensureLoaded(pkg)
   330  		orgName = funcName(pkg, origin)
   331  	} else {
   332  		if fnPkg := fn.Pkg; fnPkg != nil {
   333  			pkg = fnPkg.Pkg
   334  		} else {
   335  			pkg = p.goTyps
   336  		}
   337  		p.ensureLoaded(pkg)
   338  		orgName = funcName(pkg, fn)
   339  		if ignore && ignoreName(orgName) || checkCgo(fn.Name()) {
   340  			return nil, orgName, ignoredFunc
   341  		}
   342  	}
   343  	if v, ok := p.link[orgName]; ok {
   344  		if strings.HasPrefix(v, "C.") {
   345  			return nil, v[2:], cFunc
   346  		}
   347  		if strings.HasPrefix(v, "py.") {
   348  			return pkg, v[3:], pyFunc
   349  		}
   350  		if strings.HasPrefix(v, "llgo.") {
   351  			return nil, v[5:], llgoInstr
   352  		}
   353  		return pkg, v, goFunc
   354  	}
   355  	return pkg, funcName(pkg, fn), goFunc
   356  }
   357  
   358  const (
   359  	ignoredVar = iota
   360  	goVar      = int(llssa.InGo)
   361  	cVar       = int(llssa.InC)
   362  	pyVar      = int(llssa.InPython)
   363  )
   364  
   365  func (p *context) varName(pkg *types.Package, v *ssa.Global) (vName string, vtype int) {
   366  	name := llssa.FullName(pkg, v.Name())
   367  	if v, ok := p.link[name]; ok {
   368  		if strings.HasPrefix(v, "py.") {
   369  			return v[3:], pyVar
   370  		}
   371  		return v, cVar
   372  	}
   373  	return name, goVar
   374  }
   375  
   376  func (p *context) varOf(b llssa.Builder, v *ssa.Global) llssa.Expr {
   377  	pkgTypes := p.ensureLoaded(v.Pkg.Pkg)
   378  	pkg := p.pkg
   379  	name, vtype := p.varName(pkgTypes, v)
   380  	if vtype == pyVar {
   381  		if kind, mod := pkgKindByScope(pkgTypes.Scope()); kind == PkgPyModule {
   382  			return b.PyNewVar(pysymPrefix+mod, name).Expr
   383  		}
   384  		panic("unreachable")
   385  	}
   386  	ret := pkg.VarOf(name)
   387  	if ret == nil {
   388  		ret = pkg.NewVar(name, v.Type(), llssa.Background(vtype))
   389  	}
   390  	return ret.Expr
   391  }
   392  
   393  func (p *context) ensureLoaded(pkgTypes *types.Package) *types.Package {
   394  	if p.goTyps != pkgTypes {
   395  		if _, ok := p.loaded[pkgTypes]; !ok {
   396  			i := &pkgInfo{
   397  				kind: pkgKindByPath(pkgTypes.Path()),
   398  			}
   399  			p.loaded[pkgTypes] = i
   400  			p.importPkg(pkgTypes, i)
   401  		}
   402  	}
   403  	return pkgTypes
   404  }
   405  
   406  func pkgKindByPath(pkgPath string) int {
   407  	switch pkgPath {
   408  	case "syscall", "runtime/cgo", "unsafe":
   409  		return PkgDeclOnly
   410  	}
   411  	return PkgNormal
   412  }
   413  
   414  // -----------------------------------------------------------------------------
   415  
   416  const (
   417  	pysymPrefix = "__llgo_py."
   418  )
   419  
   420  func (p *context) initPyModule() {
   421  	if kind, mod := pkgKindByScope(p.goTyps.Scope()); kind == PkgPyModule {
   422  		p.pyMod = mod
   423  	}
   424  }
   425  
   426  // -----------------------------------------------------------------------------