diff --git a/cl/_testdata/fncall/in.go b/cl/_testdata/fncall/in.go new file mode 100644 index 00000000..71243587 --- /dev/null +++ b/cl/_testdata/fncall/in.go @@ -0,0 +1,12 @@ +package fncall + +func max(a, b int) int { + if a > b { + return a + } + return b +} + +func Foo() int { + return max(1, 2) +} diff --git a/cl/_testdata/fncall/out.ll b/cl/_testdata/fncall/out.ll new file mode 100644 index 00000000..d7443220 --- /dev/null +++ b/cl/_testdata/fncall/out.ll @@ -0,0 +1,35 @@ +; ModuleID = 'fncall' +source_filename = "fncall" + +@"init$guard" = external global ptr + +define void @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 + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define i64 @max(i64 %0, i64 %1) { +_llgo_0: + %2 = icmp sgt i64 %0, %1 + br i1 %2, label %_llgo_1, label %_llgo_2 + +_llgo_1: ; preds = %_llgo_0 + ret i64 %0 + +_llgo_2: ; preds = %_llgo_0 + ret i64 %1 +} + +define i64 @Foo() { +_llgo_0: + %0 = call i64 @max(i64 1, i64 2) + ret i64 %0 +} diff --git a/cl/compile.go b/cl/compile.go index 04064580..bcffef30 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -35,11 +35,16 @@ type instrAndValue interface { } type context struct { - prog llssa.Program - pkg llssa.Package - fn llssa.Function - glbs map[*ssa.Global]llssa.Global - vals map[ssa.Value]llssa.Expr + prog llssa.Program + pkg llssa.Package + fn llssa.Function + fns map[*ssa.Function]llssa.Function + glbs map[*ssa.Global]llssa.Global + bvals map[ssa.Value]llssa.Expr // block values +} + +func (p *context) compileType(pkg llssa.Package, member *ssa.Type) { + panic("todo") } // Global variable. @@ -52,12 +57,17 @@ func (p *context) compileGlobal(pkg llssa.Package, gbl *ssa.Global) llssa.Global return g } -func (p *context) compileType(pkg llssa.Package, member *ssa.Type) { - panic("todo") +func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) llssa.Function { + if fn, ok := p.fns[f]; ok { + return fn + } + fn := p.doCompileFunc(pkg, f) + p.fns[f] = fn + return fn } -func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) { - fn := pkg.NewFunc(f.Name(), f.Signature) +func (p *context) doCompileFunc(pkg llssa.Package, f *ssa.Function) (fn llssa.Function) { + fn = pkg.NewFunc(f.Name(), f.Signature) p.fn = fn defer func() { p.fn = nil @@ -71,12 +81,13 @@ func (p *context) compileFunc(pkg llssa.Package, f *ssa.Function) { for _, block := range f.Blocks { p.compileBlock(b, block) } + return } func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock) llssa.BasicBlock { ret := p.fn.Block(block.Index) b.SetBlock(ret) - p.vals = make(map[ssa.Value]llssa.Expr) + p.bvals = make(map[ssa.Value]llssa.Expr) for _, instr := range block.Instrs { p.compileInstr(b, instr) } @@ -84,17 +95,26 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock) llssa.Bas } func (p *context) compileInstrAndValue(b llssa.Builder, iv instrAndValue) (ret llssa.Expr) { - if v, ok := p.vals[iv]; ok { + if v, ok := p.bvals[iv]; ok { return v } switch v := iv.(type) { + case *ssa.Call: + call := v.Call + fn := p.compileValue(b, call.Value) + args := p.compileValues(b, call.Args) + ret = b.Call(fn, args...) + case *ssa.BinOp: + x := p.compileValue(b, v.X) + y := p.compileValue(b, v.Y) + ret = b.BinOp(v.Op, x, y) case *ssa.UnOp: x := p.compileValue(b, v.X) ret = b.UnOp(v.Op, x) default: panic(fmt.Sprintf("compileInstrAndValue: unknown instr - %T\n", iv)) } - p.vals[iv] = ret + p.bvals[iv] = ret return ret } @@ -139,14 +159,31 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { return p.compileInstrAndValue(b, iv) } switch v := v.(type) { + case *ssa.Parameter: + fn := v.Parent() + for idx, param := range fn.Params { + if param == v { + return p.fn.Param(idx) + } + } + case *ssa.Function: + fn := p.compileFunc(p.pkg, v) + return fn.Expr case *ssa.Global: g := p.compileGlobal(p.pkg, v) return g.Expr case *ssa.Const: return b.Const(v.Value, v.Type()) - default: - panic(fmt.Sprintf("compileValue: unknown value - %T\n", v)) } + panic(fmt.Sprintf("compileValue: unknown value - %T\n", v)) +} + +func (p *context) compileValues(b llssa.Builder, vals []ssa.Value) []llssa.Expr { + ret := make([]llssa.Expr, len(vals)) + for i, v := range vals { + ret[i] = p.compileValue(b, v) + } + return ret } // ----------------------------------------------------------------------------- @@ -177,6 +214,7 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, conf *Config) (ret llssa.P ctx := &context{ prog: prog, pkg: ret, + fns: make(map[*ssa.Function]llssa.Function), glbs: make(map[*ssa.Global]llssa.Global), } for _, m := range members { diff --git a/cl/compile_test.go b/cl/compile_test.go index 4850405f..54e9023e 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -22,7 +22,10 @@ import ( "go/parser" "go/token" "go/types" + "log" "os" + "path" + "strings" "testing" llssa "github.com/goplus/llgo/ssa" @@ -30,14 +33,55 @@ import ( "golang.org/x/tools/go/ssa/ssautil" ) -func testCompile(t *testing.T, src, expected string) { +func TestFromTestdata(t *testing.T) { + testFromDir(t, "", "./_testdata") +} + +func testFromDir(t *testing.T, sel, relDir string) { + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + dir = path.Join(dir, relDir) + fis, err := os.ReadDir(dir) + if err != nil { + t.Fatal("ReadDir failed:", err) + } + for _, fi := range fis { + name := fi.Name() + if !fi.IsDir() || strings.HasPrefix(name, "_") { + continue + } + t.Run(name, func(t *testing.T) { + testFrom(t, dir+"/"+name, sel) + }) + } +} + +func testFrom(t *testing.T, pkgDir, sel string) { + if sel != "" && !strings.Contains(pkgDir, sel) { + return + } + log.Println("Parsing", pkgDir) + in := pkgDir + "/in.go" + out := pkgDir + "/out.ll" + expected, err := os.ReadFile(out) + if err != nil { + t.Fatal("ReadFile failed:", err) + } + testCompileEx(t, nil, in, string(expected)) +} + +func testCompileEx(t *testing.T, src any, fname, expected string) { + t.Helper() fset := token.NewFileSet() - f, err := parser.ParseFile(fset, "foo.go", src, parser.ParseComments) + f, err := parser.ParseFile(fset, fname, src, parser.ParseComments) if err != nil { t.Fatal("ParseFile failed:", err) } files := []*ast.File{f} - pkg := types.NewPackage("foo", "foo") + name := f.Name.Name + pkg := types.NewPackage(name, name) foo, _, err := ssautil.BuildPackage( &types.Config{Importer: importer.Default()}, fset, pkg, files, ssa.SanityCheckFunctions) if err != nil { @@ -59,6 +103,11 @@ func testCompile(t *testing.T, src, expected string) { } } +func testCompile(t *testing.T, src, expected string) { + t.Helper() + testCompileEx(t, src, "foo.go", expected) +} + func TestVar(t *testing.T) { testCompile(t, `package foo