From 2d75c55d3690e9bc1771c4b7237910388d114a8a Mon Sep 17 00:00:00 2001 From: xushiwei Date: Mon, 22 Apr 2024 17:49:21 +0800 Subject: [PATCH 1/2] cl: funcOf; use gogen/packages.Importer --- cl/_testdata/_importpkg/in.go | 9 +++++++++ cl/_testdata/_importpkg/out.ll | 0 cl/compile.go | 21 ++++++++++++++------- cl/compile_test.go | 8 +++++--- cl/internal/stdio/printf.go | 4 ++-- go.mod | 2 +- ssa/ssa_test.go | 9 +++++++++ 7 files changed, 40 insertions(+), 13 deletions(-) create mode 100644 cl/_testdata/_importpkg/in.go create mode 100644 cl/_testdata/_importpkg/out.ll diff --git a/cl/_testdata/_importpkg/in.go b/cl/_testdata/_importpkg/in.go new file mode 100644 index 00000000..f9c1c830 --- /dev/null +++ b/cl/_testdata/_importpkg/in.go @@ -0,0 +1,9 @@ +package main + +import "github.com/goplus/llgo/cl/internal/stdio" + +var hello = [...]int8{'H', 'e', 'l', 'l', 'o', '\n', 0} + +func main() { + stdio.Printf(&hello[0]) +} diff --git a/cl/_testdata/_importpkg/out.ll b/cl/_testdata/_importpkg/out.ll new file mode 100644 index 00000000..e69de29b diff --git a/cl/compile.go b/cl/compile.go index f3e761ae..3822a98a 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -59,14 +59,15 @@ const ( ) func funcKind(vfn ssa.Value) int { - if fn, ok := vfn.(*ssa.Function); ok { - n := len(fn.Params) + if fn, ok := vfn.(*ssa.Function); ok && fn.Signature.Recv() == nil { + params := fn.Signature.Params() + n := params.Len() if n == 0 { if fn.Name() == "init" && fn.Pkg.Pkg.Path() == "unsafe" { return fnUnsafeInit } } else { - last := fn.Params[n-1] + last := params.At(n - 1) if last.Name() == llssa.NameValist { return fnHasVArg } @@ -135,6 +136,15 @@ func (p *context) funcName(fn *ssa.Function) string { return name } +func (p *context) funcOf(fn *ssa.Function) llssa.Function { + pkg := p.pkg + name := p.funcName(fn) + if ret := pkg.FuncOf(name); ret != nil { + return ret + } + return pkg.NewFunc(name, fn.Signature) // TODO: support linkname +} + func (p *context) compileType(pkg llssa.Package, member *ssa.Type) { panic("todo") } @@ -279,10 +289,7 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { } } case *ssa.Function: - if v.Pkg != p.goPkg { - panic("todo") - } - fn := p.pkg.FuncOf(p.funcName(v)) + fn := p.funcOf(v) return fn.Expr case *ssa.Global: if v.Pkg != p.goPkg { diff --git a/cl/compile_test.go b/cl/compile_test.go index 30a2a6b5..0d7657dc 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -18,7 +18,6 @@ package cl import ( "go/ast" - "go/importer" "go/parser" "go/token" "go/types" @@ -28,9 +27,11 @@ import ( "strings" "testing" - llssa "github.com/goplus/llgo/ssa" + "github.com/goplus/gogen/packages" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" + + llssa "github.com/goplus/llgo/ssa" ) func TestFromTestdata(t *testing.T) { @@ -88,8 +89,9 @@ func testCompileEx(t *testing.T, src any, fname, expected string) { files := []*ast.File{f} name := f.Name.Name pkg := types.NewPackage(name, name) + imp := packages.NewImporter(fset) foo, _, err := ssautil.BuildPackage( - &types.Config{Importer: importer.Default()}, fset, pkg, files, ssa.SanityCheckFunctions) + &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions) if err != nil { t.Fatal("BuildPackage failed:", err) } diff --git a/cl/internal/stdio/printf.go b/cl/internal/stdio/printf.go index 9887fa93..c8ec0b5e 100644 --- a/cl/internal/stdio/printf.go +++ b/cl/internal/stdio/printf.go @@ -2,5 +2,5 @@ package stdio import _ "unsafe" -//go:linkname printf printf -func printf(format *int8, __llgo_va_list ...any) +//go:linkname Printf printf +func Printf(format *int8, __llgo_va_list ...any) diff --git a/go.mod b/go.mod index 176d938d..03475463 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/aykevl/go-wasm v0.0.1 + github.com/goplus/gogen v1.15.2 github.com/goplus/gop v1.2.6 github.com/goplus/llvm v0.7.1-0.20240420180312-6230a4ea7a47 github.com/qiniu/x v1.13.10 @@ -11,7 +12,6 @@ require ( ) require ( - github.com/goplus/gogen v1.15.2 // indirect github.com/goplus/mod v0.13.10 // indirect golang.org/x/mod v0.17.0 // indirect ) diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 1d7c83c2..7978b223 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -87,6 +87,9 @@ func TestNamedStruct(t *testing.T) { prog := NewProgram(nil) pkg := prog.NewPackage("bar", "foo/bar") pkg.NewVar("a", empty) + if pkg.VarOf("a") == nil { + t.Fatal("VarOf failed") + } assertPkg(t, pkg, `; ModuleID = 'foo/bar' source_filename = "foo/bar" @@ -102,6 +105,12 @@ func TestDeclFunc(t *testing.T) { params := types.NewTuple(types.NewVar(0, nil, "a", types.Typ[types.Int])) sig := types.NewSignatureType(nil, nil, nil, params, nil, false) pkg.NewFunc("fn", sig) + if pkg.FuncOf("fn") == nil { + t.Fatal("FuncOf failed") + } + if prog.retType(sig) != prog.Void() { + t.Fatal("retType failed") + } assertPkg(t, pkg, `; ModuleID = 'foo/bar' source_filename = "foo/bar" From 72084b5648ea4464b0ffb2badce9acc52c80123f Mon Sep 17 00:00:00 2001 From: xushiwei Date: Mon, 22 Apr 2024 20:09:23 +0800 Subject: [PATCH 2/2] cl: _testdata/importpkg --- cl/_testdata/_importpkg/out.ll | 0 cl/_testdata/{_importpkg => importpkg}/in.go | 0 cl/_testdata/importpkg/out.ll | 37 +++++ cl/compile.go | 99 ++++--------- cl/compile_test.go | 4 +- cl/import.go | 140 +++++++++++++++++++ cl/internal/stdio/printf.go | 4 + internal/llgen/llgen.go | 7 +- 8 files changed, 212 insertions(+), 79 deletions(-) delete mode 100644 cl/_testdata/_importpkg/out.ll rename cl/_testdata/{_importpkg => importpkg}/in.go (100%) create mode 100644 cl/_testdata/importpkg/out.ll create mode 100644 cl/import.go diff --git a/cl/_testdata/_importpkg/out.ll b/cl/_testdata/_importpkg/out.ll deleted file mode 100644 index e69de29b..00000000 diff --git a/cl/_testdata/_importpkg/in.go b/cl/_testdata/importpkg/in.go similarity index 100% rename from cl/_testdata/_importpkg/in.go rename to cl/_testdata/importpkg/in.go diff --git a/cl/_testdata/importpkg/out.ll b/cl/_testdata/importpkg/out.ll new file mode 100644 index 00000000..ddd41201 --- /dev/null +++ b/cl/_testdata/importpkg/out.ll @@ -0,0 +1,37 @@ +; ModuleID = 'main' +source_filename = "main" + +@"init$guard" = global ptr null +@hello = global ptr null + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"init$guard", align 1 + call void @"github.com/goplus/llgo/cl/internal/stdio.init"() + store i8 72, ptr @hello, align 1 + store i8 101, ptr getelementptr inbounds (i8, ptr @hello, i64 1), align 1 + store i8 108, ptr getelementptr inbounds (i8, ptr @hello, i64 2), align 1 + store i8 108, ptr getelementptr inbounds (i8, ptr @hello, i64 3), align 1 + store i8 111, ptr getelementptr inbounds (i8, ptr @hello, i64 4), align 1 + store i8 10, ptr getelementptr inbounds (i8, ptr @hello, i64 5), align 1 + store i8 0, ptr getelementptr inbounds (i8, ptr @hello, i64 6), align 1 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define void @main() { +_llgo_0: + call void @main.init() + call void (ptr, ...) @printf(ptr @hello) + ret void +} + +declare void @"github.com/goplus/llgo/cl/internal/stdio.init"() + +declare void @printf(ptr, ...) diff --git a/cl/compile.go b/cl/compile.go index 3822a98a..a15fa4ed 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -19,11 +19,12 @@ package cl import ( "fmt" "go/ast" + "go/types" "log" "os" "sort" - "strings" + "github.com/goplus/gop/token" llssa "github.com/goplus/llgo/ssa" "golang.org/x/tools/go/ssa" ) @@ -76,73 +77,25 @@ func funcKind(vfn ssa.Value) int { return fnNormal } -func funcName(fn *ssa.Function) string { - ret := fn.Pkg.Pkg.Path() + "." + fn.Name() - if ret == "main.main" { - ret = "main" - } - return ret -} - // ----------------------------------------------------------------------------- +type none = struct{} + type instrAndValue interface { ssa.Instruction ssa.Value } type context struct { - prog llssa.Program - pkg llssa.Package - fn llssa.Function - goPkg *ssa.Package - link map[string]string - bvals map[ssa.Value]llssa.Expr // block values - inits []func() -} - -func (p *context) initFiles(pkgPath string, files []*ast.File) { - for _, file := range files { - for _, decl := range file.Decls { - if decl, ok := decl.(*ast.FuncDecl); ok { - if decl.Recv == nil && decl.Doc != nil { - p.initLinkname(pkgPath, decl.Doc) - } - } - } - } -} - -func (p *context) initLinkname(pkgPath string, doc *ast.CommentGroup) { - const ( - linkname = "//go:linkname " - ) - for _, c := range doc.List { - if strings.HasPrefix(c.Text, linkname) { - text := strings.TrimSpace(c.Text[len(linkname):]) - if idx := strings.IndexByte(text, ' '); idx > 0 { - name := pkgPath + "." + text[:idx] - p.link[name] = strings.TrimLeft(text[idx+1:], " ") - } - } - } -} - -func (p *context) funcName(fn *ssa.Function) string { - name := funcName(fn) - if v, ok := p.link[name]; ok { - return v - } - return name -} - -func (p *context) funcOf(fn *ssa.Function) llssa.Function { - pkg := p.pkg - name := p.funcName(fn) - if ret := pkg.FuncOf(name); ret != nil { - return ret - } - return pkg.NewFunc(name, fn.Signature) // TODO: support linkname + prog llssa.Program + pkg llssa.Package + fn llssa.Function + fset *token.FileSet + goPkg *ssa.Package + link map[string]string // pkgPath.nameInPkg => linkname + loaded map[*types.Package]none // loaded packages + bvals map[ssa.Value]llssa.Expr // block values + inits []func() } func (p *context) compileType(pkg llssa.Package, member *ssa.Type) { @@ -160,7 +113,7 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) { } func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) { - name := p.funcName(f) + name := p.funcName(f.Pkg.Pkg, f) if debugInstr { log.Println("==> NewFunc", name) } @@ -329,20 +282,16 @@ func (p *context) compileValues(b llssa.Builder, vals []ssa.Value, hasVArg int) // ----------------------------------------------------------------------------- -type GoPackage struct { - SSA *ssa.Package - Files []*ast.File -} - // NewPackage compiles a Go package to LLVM IR package. -func NewPackage(prog llssa.Program, in *GoPackage) (ret llssa.Package, err error) { +func NewPackage( + prog llssa.Program, + fset *token.FileSet, pkg *ssa.Package, files []*ast.File) (ret llssa.Package, err error) { + type namedMember struct { name string val ssa.Member } - pkg := in.SSA - // Sort by position, so that the order of the functions in the IR matches // the order of functions in the source file. This is useful for testing, // for example. @@ -360,12 +309,14 @@ func NewPackage(prog llssa.Program, in *GoPackage) (ret llssa.Package, err error ret = prog.NewPackage(pkgTypes.Name(), pkgTypes.Path()) ctx := &context{ - prog: prog, - pkg: ret, - goPkg: pkg, - link: make(map[string]string), + prog: prog, + pkg: ret, + fset: fset, + goPkg: pkg, + link: make(map[string]string), + loaded: make(map[*types.Package]none), } - ctx.initFiles(pkgTypes.Path(), in.Files) + ctx.initFiles(pkgTypes.Path(), files) for _, m := range members { member := m.val switch member := member.(type) { diff --git a/cl/compile_test.go b/cl/compile_test.go index 0d7657dc..49c6650d 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -35,7 +35,7 @@ import ( ) func TestFromTestdata(t *testing.T) { - testFromDir(t, "", "./_testdata") + testFromDir(t, "importpkg", "./_testdata") } func init() { @@ -97,7 +97,7 @@ func testCompileEx(t *testing.T, src any, fname, expected string) { } foo.WriteTo(os.Stderr) prog := llssa.NewProgram(nil) - ret, err := NewPackage(prog, &GoPackage{SSA: foo, Files: files}) + ret, err := NewPackage(prog, fset, foo, files) if err != nil { t.Fatal("cl.NewPackage failed:", err) } diff --git a/cl/import.go b/cl/import.go new file mode 100644 index 00000000..c9746a64 --- /dev/null +++ b/cl/import.go @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cl + +import ( + "bytes" + "go/ast" + "go/token" + "go/types" + "os" + "strings" + + llssa "github.com/goplus/llgo/ssa" + "golang.org/x/tools/go/ssa" +) + +type contentLines = [][]byte +type contentMap = map[string]contentLines + +func contentOf(m contentMap, file string) (lines contentLines, err error) { + if v, ok := m[file]; ok { + return v, nil + } + b, err := os.ReadFile(file) + if err == nil { + lines = bytes.Split(b, []byte{'\n'}) + m[file] = lines + } + return +} + +func (p *context) importPkg(pkg *types.Package) { + scope := pkg.Scope() + if scope.Lookup("LLGoPackage") == nil { + return + } + fset := p.fset + names := scope.Names() + contents := make(contentMap) + pkgPath := pkg.Path() + for _, name := range names { + if token.IsExported(name) { + obj := scope.Lookup(name) + if obj, ok := obj.(*types.Func); ok { + if pos := obj.Pos(); pos != token.NoPos { + f := fset.File(pos) + if fp := f.PositionFor(pos, false); fp.Line > 2 { + lines, err := contentOf(contents, fp.Filename) + if err != nil { + panic(err) + } + if i := fp.Line - 2; i < len(lines) { + line := string(lines[i]) + p.initLinkname(pkgPath, line) + } + } + } + } + } + } +} + +func (p *context) initFiles(pkgPath string, files []*ast.File) { + for _, file := range files { + for _, decl := range file.Decls { + if decl, ok := decl.(*ast.FuncDecl); ok { + if decl.Recv == nil { + if doc := decl.Doc; doc != nil { + if n := len(doc.List); n > 0 { + line := doc.List[n-1].Text + p.initLinkname(pkgPath, line) + } + } + } + } + } + } +} + +func (p *context) initLinkname(pkgPath, line string) { + const ( + linkname = "//go:linkname " + ) + if strings.HasPrefix(line, linkname) { + text := strings.TrimSpace(line[len(linkname):]) + if idx := strings.IndexByte(text, ' '); idx > 0 { + name := pkgPath + "." + text[:idx] + link := strings.TrimLeft(text[idx+1:], " ") + p.link[name] = link + } + } +} + +func fullName(pkg *types.Package, name string) string { + return pkg.Path() + "." + name +} + +func funcName(pkg *types.Package, fn *ssa.Function) string { + ret := fullName(pkg, fn.Name()) + if ret == "main.main" { + ret = "main" + } + return ret +} + +func (p *context) funcName(pkg *types.Package, fn *ssa.Function) string { + name := funcName(pkg, fn) + if v, ok := p.link[name]; ok { + return v + } + return name +} + +func (p *context) funcOf(fn *ssa.Function) llssa.Function { + pkgTypes := fn.Pkg.Pkg + if _, ok := p.loaded[pkgTypes]; !ok { + p.loaded[pkgTypes] = none{} + p.importPkg(pkgTypes) + } + pkg := p.pkg + name := p.funcName(pkgTypes, fn) + if ret := pkg.FuncOf(name); ret != nil { + return ret + } + return pkg.NewFunc(name, fn.Signature) +} diff --git a/cl/internal/stdio/printf.go b/cl/internal/stdio/printf.go index c8ec0b5e..38fffa6f 100644 --- a/cl/internal/stdio/printf.go +++ b/cl/internal/stdio/printf.go @@ -2,5 +2,9 @@ package stdio import _ "unsafe" +const ( + LLGoPackage = true +) + //go:linkname Printf printf func Printf(format *int8, __llgo_va_list ...any) diff --git a/internal/llgen/llgen.go b/internal/llgen/llgen.go index 4f91aa4e..c9acab14 100644 --- a/internal/llgen/llgen.go +++ b/internal/llgen/llgen.go @@ -18,12 +18,12 @@ package llgen import ( "go/ast" - "go/importer" "go/parser" "go/token" "go/types" "os" + "github.com/goplus/gogen/packages" "github.com/goplus/llgo/cl" "golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa/ssautil" @@ -51,14 +51,15 @@ func Gen(inFile string, src any) string { files := []*ast.File{f} name := f.Name.Name pkg := types.NewPackage(name, name) + imp := packages.NewImporter(fset) ssaPkg, _, err := ssautil.BuildPackage( - &types.Config{Importer: importer.Default()}, fset, pkg, files, ssa.SanityCheckFunctions) + &types.Config{Importer: imp}, fset, pkg, files, ssa.SanityCheckFunctions) check(err) ssaPkg.WriteTo(os.Stderr) prog := llssa.NewProgram(nil) - ret, err := cl.NewPackage(prog, &cl.GoPackage{SSA: ssaPkg, Files: files}) + ret, err := cl.NewPackage(prog, fset, ssaPkg, files) check(err) return ret.String()