From 8991455da27157f32ab712d3ea4e94471f503912 Mon Sep 17 00:00:00 2001 From: visualfc Date: Fri, 19 Jul 2024 21:40:27 +0800 Subject: [PATCH] build: add cl.Context for linkname --- cl/builtin_test.go | 10 ++- cl/cltest/cltest.go | 8 +- cl/compile.go | 46 +++++++--- cl/import.go | 186 ++++++++++++--------------------------- internal/build/build.go | 33 +++++-- internal/llgen/llgenf.go | 21 ++++- ssa/package.go | 2 +- ssa/type.go | 2 +- ssa/type_cvt.go | 8 +- 9 files changed, 147 insertions(+), 169 deletions(-) diff --git a/cl/builtin_test.go b/cl/builtin_test.go index e2b0fce2c..c04b9cf81 100644 --- a/cl/builtin_test.go +++ b/cl/builtin_test.go @@ -40,10 +40,10 @@ func TestToBackground(t *testing.T) { } func TestCollectSkipNames(t *testing.T) { - ctx := &context{skips: make(map[string]none)} - ctx.collectSkipNames("//llgo:skipall") - ctx.collectSkipNames("//llgo:skip") - ctx.collectSkipNames("//llgo:skip abs") + ctx := &Context{} + ctx.collectSkipNames("pkg", "//llgo:skipall") + ctx.collectSkipNames("pkg", "//llgo:skip") + ctx.collectSkipNames("pkg", "//llgo:skip abs") } func TestReplaceGoName(t *testing.T) { @@ -236,6 +236,7 @@ func TestIgnoreName(t *testing.T) { func TestErrImport(t *testing.T) { var ctx context + ctx.Context = NewContext(nil) pkg := types.NewPackage("foo", "foo") ctx.importPkg(pkg, nil) @@ -249,6 +250,7 @@ func TestErrImport(t *testing.T) { func TestErrInitLinkname(t *testing.T) { var ctx context + ctx.Context = NewContext(nil) ctx.initLinkname("//llgo:link abc", func(name string) (string, bool, bool) { return "", false, false }) diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 5d4c2913c..db830d8eb 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -119,12 +119,8 @@ func testFrom(t *testing.T, pkgDir, sel string, byLLGen bool) { t.Fatal("ReadFile failed:", err) } expected := string(b) - if byLLGen { - if v := llgen.GenFrom(in); v != expected && expected != ";" { // expected == ";" means skipping out.ll - t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected) - } - } else { - TestCompileEx(t, nil, in, expected) + if v := llgen.GenFrom(in); v != expected && expected != ";" { // expected == ";" means skipping out.ll + t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected) } } diff --git a/cl/compile.go b/cl/compile.go index 113dfc1b3..36c4e9528 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -25,6 +25,7 @@ import ( "log" "os" "sort" + "sync" "github.com/goplus/llgo/cl/blocks" "github.com/goplus/llgo/internal/typepatch" @@ -79,8 +80,30 @@ type pkgInfo struct { type none = struct{} +type Context struct { + prog llssa.Program + link sync.Map // pkgPath.nameInPkg => linkname + skipLines sync.Map // pkgPath => skip lines + patches Patches + kinds map[string]int +} + +func NewContext(prog llssa.Program) *Context { + return &Context{ + prog: prog, + kinds: map[string]int{ + "runtime/cgo": PkgDeclOnly, + "unsafe": PkgDeclOnly, + }, + } +} + +func (p *Context) SetPatches(patches Patches) { + p.patches = patches +} + type context struct { - prog llssa.Program + *Context pkg llssa.Package fn llssa.Function fset *token.FileSet @@ -88,13 +111,11 @@ type context struct { goTyps *types.Package goPkg *ssa.Package pyMod string - link map[string]string // pkgPath.nameInPkg => linkname skips map[string]none loaded map[*types.Package]*pkgInfo // loaded packages bvals map[ssa.Value]llssa.Expr // block values vargs map[*ssa.Alloc][]llssa.Expr // varargs - patches Patches blkInfos []blocks.Info inits []func() @@ -774,16 +795,19 @@ type Patches = map[string]Patch // NewPackage compiles a Go package to LLVM IR package. func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) { - return NewPackageEx(prog, nil, pkg, files) + bctx := NewContext(prog) + bctx.ParsePkgSyntax(pkg.Pkg, files, false) + return NewPackageEx(bctx, pkg) } // NewPackageEx compiles a Go package to LLVM IR package. -func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) { +func NewPackageEx(bctx *Context, pkg *ssa.Package) (ret llssa.Package, err error) { + prog := bctx.prog pkgProg := pkg.Prog pkgTypes := pkg.Pkg oldTypes := pkgTypes pkgName, pkgPath := pkgTypes.Name(), llssa.PathOf(pkgTypes) - patch, hasPatch := patches[pkgPath] + patch, hasPatch := bctx.patches[pkgPath] if hasPatch { pkgTypes = patch.Types pkg.Pkg = pkgTypes @@ -795,14 +819,12 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ ret = prog.NewPackage(pkgName, pkgPath) ctx := &context{ - prog: prog, + Context: bctx, pkg: ret, fset: pkgProg.Fset, goProg: pkgProg, goTyps: pkgTypes, goPkg: pkg, - patches: patches, - link: make(map[string]string), skips: make(map[string]none), vargs: make(map[*ssa.Alloc][]llssa.Expr), loaded: map[*types.Package]*pkgInfo{ @@ -810,10 +832,14 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ }, } ctx.initPyModule() - ctx.initFiles(pkgPath, files) ret.SetPatch(ctx.patchType) if hasPatch { + if v, ok := bctx.skipLines.Load(pkgPath); ok { + for _, line := range v.([]string) { + ctx.parseSkip(line) + } + } skips := ctx.skips typepatch.Merge(pkgTypes, oldTypes, skips, ctx.skipall) ctx.skips = nil diff --git a/cl/import.go b/cl/import.go index 6729b7cf6..65424723c 100644 --- a/cl/import.go +++ b/cl/import.go @@ -17,7 +17,6 @@ package cl import ( - "bytes" "fmt" "go/ast" "go/constant" @@ -33,56 +32,6 @@ import ( // ----------------------------------------------------------------------------- -type symInfo struct { - file string - fullName string - isVar bool -} - -type pkgSymInfo struct { - files map[string][]byte // file => content - syms map[string]symInfo // name => isVar -} - -func newPkgSymInfo() *pkgSymInfo { - return &pkgSymInfo{ - files: make(map[string][]byte), - syms: make(map[string]symInfo), - } -} - -func (p *pkgSymInfo) addSym(fset *token.FileSet, pos token.Pos, fullName, inPkgName string, isVar bool) { - f := fset.File(pos) - if fp := f.Position(pos); fp.Line > 2 { - file := fp.Filename - if _, ok := p.files[file]; !ok { - b, err := os.ReadFile(file) - if err == nil { - p.files[file] = b - } - } - p.syms[inPkgName] = symInfo{file, fullName, isVar} - } -} - -func (p *pkgSymInfo) initLinknames(ctx *context) { - sep := []byte{'\n'} - commentPrefix := []byte{'/', '/'} - for file, b := range p.files { - lines := bytes.Split(b, sep) - for _, line := range lines { - if bytes.HasPrefix(line, commentPrefix) { - ctx.initLinkname(string(line), func(inPkgName string) (fullName string, isVar, ok bool) { - if sym, ok := p.syms[inPkgName]; ok && file == sym.file { - return sym.fullName, sym.isVar, true - } - return - }) - } - } - } -} - // PkgKindOf returns the kind of a package. func PkgKindOf(pkg *types.Package) (int, string) { scope := pkg.Scope() @@ -127,8 +76,12 @@ func pkgKindByScope(scope *types.Scope) (int, string) { return PkgNormal, "" } -func (p *context) importPkg(pkg *types.Package, i *pkgInfo) { +func (p *Context) importPkg(pkg *types.Package, i *pkgInfo) { pkgPath := llssa.PathOf(pkg) + if kind, ok := p.kinds[pkgPath]; ok { + i.kind = kind + return + } scope := pkg.Scope() kind, _ := pkgKindByScope(scope) if kind == PkgNormal { @@ -136,67 +89,24 @@ func (p *context) importPkg(pkg *types.Package, i *pkgInfo) { pkg = patch.Alt.Pkg scope = pkg.Scope() if kind, _ = pkgKindByScope(scope); kind != PkgNormal { - goto start + goto end } } return } -start: +end: i.kind = kind - fset := p.fset - names := scope.Names() - syms := newPkgSymInfo() - for _, name := range names { - obj := scope.Lookup(name) - switch obj := obj.(type) { - case *types.Func: - if pos := obj.Pos(); pos != token.NoPos { - fullName, inPkgName := typesFuncName(pkgPath, obj) - syms.addSym(fset, pos, fullName, inPkgName, false) - } - case *types.TypeName: - if !obj.IsAlias() { - if t, ok := obj.Type().(*types.Named); ok { - for i, n := 0, t.NumMethods(); i < n; i++ { - fn := t.Method(i) - fullName, inPkgName := typesFuncName(pkgPath, fn) - syms.addSym(fset, fn.Pos(), fullName, inPkgName, false) - } - } - } - case *types.Var: - if pos := obj.Pos(); pos != token.NoPos { - syms.addSym(fset, pos, pkgPath+"."+name, name, true) - } - } - } - syms.initLinknames(p) + p.kinds[pkgPath] = kind } -func (p *context) initFiles(pkgPath string, files []*ast.File) { - for _, file := range files { - for _, decl := range file.Decls { - switch decl := decl.(type) { - case *ast.FuncDecl: - fullName, inPkgName := astFuncName(pkgPath, decl) - p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false) - case *ast.GenDecl: - switch decl.Tok { - case token.VAR: - if len(decl.Specs) == 1 { - if names := decl.Specs[0].(*ast.ValueSpec).Names; len(names) == 1 { - inPkgName := names[0].Name - p.initLinknameByDoc(decl.Doc, pkgPath+"."+inPkgName, inPkgName, true) - } - } - case token.IMPORT: - if doc := decl.Doc; doc != nil { - if n := len(doc.List); n > 0 { - line := doc.List[n-1].Text - p.collectSkipNames(line) - } - } - } +func (p *context) parseSkip(line string) { + if line == "all" { + p.skipall = true + } else if len(line) > 0 && line[0] == ' ' { + names := strings.Split(line[1:], " ") + for _, name := range names { + if name != "" { + p.skips[name] = none{} } } } @@ -204,36 +114,27 @@ func (p *context) initFiles(pkgPath string, files []*ast.File) { // llgo:skip symbol1 symbol2 ... // llgo:skipall -func (p *context) collectSkipNames(line string) { +func (p *Context) collectSkipNames(pkgPath string, line string) { const ( skip = "//llgo:skip" skip2 = "// llgo:skip" ) if strings.HasPrefix(line, skip2) { - p.collectSkip(line, len(skip2)) + p.collectSkip(pkgPath, line, len(skip2)) } else if strings.HasPrefix(line, skip) { - p.collectSkip(line, len(skip)) + p.collectSkip(pkgPath, line, len(skip)) } } -func (p *context) collectSkip(line string, prefix int) { - line = line[prefix:] - if line == "all" { - p.skipall = true - return - } - if len(line) == 0 || line[0] != ' ' { - return - } - names := strings.Split(line[1:], " ") - for _, name := range names { - if name != "" { - p.skips[name] = none{} - } +func (p *Context) collectSkip(pkgPath, line string, prefix int) { + if v, ok := p.skipLines.Load(pkgPath); ok { + p.skipLines.Store(pkgPath, append(v.([]string), line[prefix:])) + } else { + p.skipLines.Store(pkgPath, []string{line[prefix:]}) } } -func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName string, isVar bool) { +func (p *Context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName string, isVar bool) { if doc != nil { if n := len(doc.List); n > 0 { line := doc.List[n-1].Text @@ -244,7 +145,7 @@ func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName s } } -func (p *context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) { +func (p *Context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) { const ( linkname = "//go:linkname " llgolink = "//llgo:link " @@ -259,14 +160,14 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s } } -func (p *context) initLink(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) { +func (p *Context) initLink(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) { text := strings.TrimSpace(line[prefix:]) if idx := strings.IndexByte(text, ' '); idx > 0 { inPkgName := text[:idx] if fullName, isVar, ok := f(inPkgName); ok { link := strings.TrimLeft(text[idx+1:], " ") if isVar || strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr - p.link[fullName] = link + p.link.Store(fullName, link) } else { panic(line + ": no specified call convention. eg. //go:linkname Printf C.printf") } @@ -434,7 +335,8 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin return nil, orgName, ignoredFunc } } - if v, ok := p.link[orgName]; ok { + if v, ok := p.link.Load(orgName); ok { + v := v.(string) if strings.HasPrefix(v, "C.") { return nil, v[2:], cFunc } @@ -458,7 +360,8 @@ const ( func (p *context) varName(pkg *types.Package, v *ssa.Global) (vName string, vtype int, define bool) { name := llssa.FullName(pkg, v.Name()) - if v, ok := p.link[name]; ok { + if v, ok := p.link.Load(name); ok { + v := v.(string) if pos := strings.IndexByte(v, '.'); pos >= 0 { if pos == 2 && v[0] == 'p' && v[1] == 'y' { return v[3:], pyVar, false @@ -513,14 +416,35 @@ func (p *context) initPyModule() { } // ParsePkgSyntax parses AST of a package to check llgo:type in type declaration. -func ParsePkgSyntax(prog llssa.Program, pkg *types.Package, files []*ast.File) { +func (p *Context) ParsePkgSyntax(pkg *types.Package, files []*ast.File, patch bool) { + pkgPath := llssa.PathOf(pkg) for _, file := range files { for _, decl := range file.Decls { switch decl := decl.(type) { + case *ast.FuncDecl: + fullName, inPkgName := astFuncName(pkgPath, decl) + p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false) case *ast.GenDecl: switch decl.Tok { + case token.VAR: + if len(decl.Specs) == 1 { + if names := decl.Specs[0].(*ast.ValueSpec).Names; len(names) == 1 { + inPkgName := names[0].Name + p.initLinknameByDoc(decl.Doc, pkgPath+"."+inPkgName, inPkgName, true) + } + } case token.TYPE: - handleTypeDecl(prog, pkg, decl) + handleTypeDecl(p.prog, pkg, decl) + case token.IMPORT: + if !patch { + continue + } + if doc := decl.Doc; doc != nil { + if n := len(doc.List); n > 0 { + line := doc.List[n-1].Text + p.collectSkipNames(pkgPath, line) + } + } } } } diff --git a/internal/build/build.go b/internal/build/build.go index 668a307ce..747988407 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -143,12 +143,13 @@ func Do(args []string, conf *Config) { prog := llssa.NewProgram(target) sizes := prog.TypeSizes + bctx := cl.NewContext(prog) dedup := packages.NewDeduper() dedup.SetPreload(func(pkg *types.Package, files []*ast.File) { - if canSkipToBuild(pkg.Path()) { + if canSkipToSyntax(pkg.Path()) { return } - cl.ParsePkgSyntax(prog, pkg, files) + bctx.ParsePkgSyntax(pkg, files, strings.HasPrefix(pkg.Path(), altPkgPathPrefix)) }) if patterns == nil { @@ -187,11 +188,12 @@ func Do(args []string, conf *Config) { progSSA := ssa.NewProgram(initial[0].Fset, ssaBuildMode) patches := make(cl.Patches, len(altPkgPaths)) altSSAPkgs(progSSA, patches, altPkgs[1:], verbose) + bctx.SetPatches(patches) env := llvm.New("") os.Setenv("PATH", env.BinDir()+":"+os.Getenv("PATH")) // TODO(xsw): check windows - ctx := &context{env, progSSA, prog, dedup, patches, make(map[string]none), initial, mode, 0} + ctx := &context{env, progSSA, prog, dedup, bctx, make(map[string]none), initial, mode, 0} pkgs := buildAllPkgs(ctx, initial, verbose) var llFiles []string @@ -242,7 +244,7 @@ type context struct { progSSA *ssa.Program prog llssa.Program dedup packages.Deduper - patches cl.Patches + bctx *cl.Context built map[string]none initial []*packages.Package mode Mode @@ -477,17 +479,14 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) { pkg.ExportFile = "" return } - var syntax = pkg.Syntax - if altPkg := aPkg.AltPkg; altPkg != nil { - syntax = append(syntax, altPkg.Syntax...) - } + showDetail := verbose && pkgExists(ctx.initial, pkg) if showDetail { llssa.SetDebug(llssa.DbgFlagAll) cl.SetDebug(cl.DbgFlagAll) } - ret, err := cl.NewPackageEx(ctx.prog, ctx.patches, aPkg.SSA, syntax) + ret, err := cl.NewPackageEx(ctx.bctx, aPkg.SSA) if showDetail { llssa.SetDebug(0) cl.SetDebug(0) @@ -725,6 +724,20 @@ func pkgExists(initial []*packages.Package, pkg *packages.Package) bool { return false } +func canSkipToSyntax(pkgPath string) bool { + switch pkgPath { + case "runtime": + return true + case "runtime/cgo": + return true + case "unsafe": + return true + default: + return strings.HasPrefix(pkgPath, "internal/") || + strings.HasPrefix(pkgPath, "runtime/internal/") + } +} + func canSkipToBuild(pkgPath string) bool { if _, ok := hasAltPkg[pkgPath]; ok { return false @@ -732,6 +745,8 @@ func canSkipToBuild(pkgPath string) bool { switch pkgPath { case "unsafe": return true + case "runtime/cgo": + return true default: return strings.HasPrefix(pkgPath, "internal/") || strings.HasPrefix(pkgPath, "runtime/internal/") diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index f9cc49799..2f23a177a 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -82,8 +82,12 @@ func genFrom(fileOrPkg string, pkgPath string) string { } return path }) + bctx := cl.NewContext(prog) dedup.SetPreload(func(pkg *types.Package, files []*ast.File) { - cl.ParsePkgSyntax(prog, pkg, files) + if canSkipToSyntax(pkg.Path()) { + return + } + bctx.ParsePkgSyntax(pkg, files, false) }) initial, err := packages.LoadEx(dedup, prog.TypeSizes, cfg, fileOrPkg) @@ -91,7 +95,6 @@ func genFrom(fileOrPkg string, pkgPath string) string { _, pkgs := ssautil.AllPackages(initial, ssa.SanityCheckFunctions|ssa.InstantiateGenerics) - pkg := initial[0] ssaPkg := pkgs[0] ssaPkg.Build() @@ -101,7 +104,7 @@ func genFrom(fileOrPkg string, pkgPath string) string { ssaPkg.WriteTo(os.Stderr) } - ret, err := cl.NewPackage(prog, ssaPkg, pkg.Syntax) + ret, err := cl.NewPackageEx(bctx, ssaPkg) check(err) if prog.NeedPyInit { // call PyInit if needed @@ -111,6 +114,18 @@ func genFrom(fileOrPkg string, pkgPath string) string { return ret.String() } +func canSkipToSyntax(pkgPath string) bool { + switch pkgPath { + case "unsafe": + return true + case "runtime/cgo": + return true + default: + return strings.HasPrefix(pkgPath, "internal/") || + strings.HasPrefix(pkgPath, "runtime/internal/") + } +} + func DoFile(fileOrPkg, outFile string) { ret := GenFrom(fileOrPkg) err := os.WriteFile(outFile, []byte(ret), 0644) diff --git a/ssa/package.go b/ssa/package.go index 02b0e360b..98205d9cc 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -241,7 +241,7 @@ func (p Program) SetRuntime(runtime any) { } func (p Program) SetTypeBackground(fullName string, bg Background) { - p.gocvt.typbg[fullName] = bg + p.gocvt.typbg.Store(fullName, bg) } func (p Program) runtime() *types.Package { diff --git a/ssa/type.go b/ssa/type.go index 5e2dcf88c..3043c2966 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -126,7 +126,7 @@ func (p *goProgram) extraSize(typ types.Type, ptrSize int64) (ret int64) { retry: switch t := typ.(type) { case *types.Named: - if p.gocvt.typbg[t.String()] == InC { + if v, ok := p.gocvt.typbg.Load(t.String()); ok && v.(Background) == InC { return 0 } typ = t.Underlying() diff --git a/ssa/type_cvt.go b/ssa/type_cvt.go index 03d0e07eb..10275c4e4 100644 --- a/ssa/type_cvt.go +++ b/ssa/type_cvt.go @@ -20,6 +20,7 @@ import ( "fmt" "go/token" "go/types" + "sync" "unsafe" ) @@ -27,13 +28,12 @@ import ( type goTypes struct { typs map[unsafe.Pointer]unsafe.Pointer - typbg map[string]Background + typbg sync.Map } func newGoTypes() goTypes { typs := make(map[unsafe.Pointer]unsafe.Pointer) - typbk := make(map[string]Background) - return goTypes{typs, typbk} + return goTypes{typs: typs} } type Background int @@ -95,7 +95,7 @@ func (p goTypes) cvtType(typ types.Type) (raw types.Type, cvt bool) { case *types.Struct: return p.cvtStruct(t) case *types.Named: - if p.typbg[t.String()] == InC { + if v, ok := p.typbg.Load(t.String()); ok && v.(Background) == InC { break } return p.cvtNamed(t)