diff --git a/README.md b/README.md index 344b8ed3..8e2d9c59 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,8 @@ The `_demo` directory contains our demos (it start with `_` to prevent the `go` And the `py/_demo` directory contains python related demos: * [hellopy](py/_demo/hellopy/hello.go): link Python to Go and say `Hello world` -* [clpy](py/_demo/clpy/cleval.go): compile Python code and eval. -* [callpy](py/_demo/callpy/call.go): call Python standard library function `math.sqrt`. +* [clpy](py/_demo/clpy/cleval.go): compile Python code and eval +* [callpy](py/_demo/callpy/call.go): call Python standard library function `math.sqrt` ### How to run demos diff --git a/_pydemo/callpy/callpy.go b/_pydemo/callpy/callpy.go index 8900a9bb..21ec21ab 100644 --- a/_pydemo/callpy/callpy.go +++ b/_pydemo/callpy/callpy.go @@ -8,5 +8,5 @@ import ( func main() { x := math.Sqrt(py.Float(2)) - c.Printf(c.Str("sqrt(2) = %f\n"), x.FloatAsDouble()) + c.Printf(c.Str("sqrt(2) = %f\n"), x.Float64()) } diff --git a/cl/_testpy/callpy/in.go b/cl/_testpy/callpy/in.go new file mode 100644 index 00000000..12424d62 --- /dev/null +++ b/cl/_testpy/callpy/in.go @@ -0,0 +1,15 @@ +package main + +import ( + "github.com/goplus/llgo/c" + "github.com/goplus/llgo/py" + "github.com/goplus/llgo/py/math" + "github.com/goplus/llgo/py/os" +) + +func main() { + x := math.Sqrt(py.Float(2)) + wd := os.Getcwd() + c.Printf(c.Str("sqrt(2) = %f\n"), x.Float64()) + c.Printf(c.Str("cwd = %s\n"), wd.CStr()) +} diff --git a/cl/_testpy/callpy/out.ll b/cl/_testpy/callpy/out.ll new file mode 100644 index 00000000..001dd685 --- /dev/null +++ b/cl/_testpy/callpy/out.ll @@ -0,0 +1,59 @@ +; ModuleID = 'main' +source_filename = "main" + +@"main.init$guard" = global ptr null +@__llgo_argc = global ptr null +@__llgo_argv = global ptr null +@sqrt = external global ptr +@getcwd = external global ptr +@0 = private unnamed_addr constant [14 x i8] c"sqrt(2) = %f\0A\00", align 1 +@1 = private unnamed_addr constant [10 x i8] c"cwd = %s\0A\00", align 1 + +define void @main.init() { +_llgo_0: + %0 = load i1, ptr @"main.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"main.init$guard", align 1 + call void @"github.com/goplus/llgo/py/math.init"() + call void @"github.com/goplus/llgo/py/os.init"() + br label %_llgo_2 + +_llgo_2: ; preds = %_llgo_1, %_llgo_0 + ret void +} + +define void @main(i32 %0, ptr %1) { +_llgo_0: + store i32 %0, ptr @__llgo_argc, align 4 + store ptr %1, ptr @__llgo_argv, align 8 + call void @"github.com/goplus/llgo/internal/runtime.init"() + call void @main.init() + %2 = call ptr @PyFloat_FromDouble(double 2.000000e+00) + %3 = call ptr @PyObject_CallOneArg(ptr @sqrt, ptr %2) + %4 = call ptr @PyObject_CallNoArg(ptr @getcwd) + %5 = call double @PyFloat_AsDouble() + %6 = call i32 (ptr, ...) @printf(ptr @0, double %5) + %7 = call ptr @PyBytes_AsString() + %8 = call i32 (ptr, ...) @printf(ptr @1, ptr %7) + ret void +} + +declare void @"github.com/goplus/llgo/py/math.init"() + +declare void @"github.com/goplus/llgo/py/os.init"() + +declare void @"github.com/goplus/llgo/internal/runtime.init"() + +declare ptr @PyFloat_FromDouble(double) + +declare ptr @PyObject_CallOneArg(ptr, ptr) + +declare ptr @PyObject_CallNoArg(ptr) + +declare double @PyFloat_AsDouble() + +declare i32 @printf(ptr, ...) + +declare ptr @PyBytes_AsString() diff --git a/cl/_testpy/math/in.go b/cl/_testpy/math/in.go new file mode 100644 index 00000000..5d6936f9 --- /dev/null +++ b/cl/_testpy/math/in.go @@ -0,0 +1,14 @@ +package math + +import ( + _ "unsafe" + + "github.com/goplus/llgo/py" +) + +const ( + LLGoPackage = "py.math" +) + +//go:linkname Sqrt py.sqrt +func Sqrt(x *py.Object) *py.Object diff --git a/cl/_testpy/math/out.ll b/cl/_testpy/math/out.ll new file mode 100644 index 00000000..f5feead3 --- /dev/null +++ b/cl/_testpy/math/out.ll @@ -0,0 +1,29 @@ +; ModuleID = 'math' +source_filename = "math" + +@__llgo_py.math.sqrt = external global ptr +@"math.init$guard" = global ptr null +@__llgo_py.math = linkonce global ptr null +@0 = private unnamed_addr constant [5 x i8] c"math\00", align 1 + +define void @math.init() { +_llgo_0: + %0 = load i1, ptr @"math.init$guard", align 1 + br i1 %0, label %_llgo_2, label %_llgo_1 + +_llgo_1: ; preds = %_llgo_0 + store i1 true, ptr @"math.init$guard", align 1 + %1 = load ptr, ptr @__llgo_py.math, align 8 + %2 = icmp ne ptr %1, null + br i1 %2, label %_llgo_2, label %_llgo_3 + +_llgo_2: ; preds = %_llgo_3, %_llgo_1, %_llgo_0 + ret void + +_llgo_3: ; preds = %_llgo_1 + %3 = call ptr @PyImport_ImportModule(ptr @0) + store ptr %3, ptr @__llgo_py.math, align 8 + br label %_llgo_2 +} + +declare ptr @PyImport_ImportModule(ptr) diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 65816930..4b21cb04 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -147,6 +147,13 @@ func TestCompileEx(t *testing.T, src any, fname, expected string) { } return rt }) + prog.SetPython(func() *types.Package { + rt, err := imp.Import(llssa.PkgPython) + if err != nil { + t.Fatal("load python failed:", err) + } + return rt + }) ret, err := cl.NewPackage(prog, foo, files) if err != nil { diff --git a/cl/compile.go b/cl/compile.go index 9f9bb2db..eddf4aea 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -143,6 +143,7 @@ type context struct { goProg *ssa.Program goTyps *types.Package goPkg *ssa.Package + pyMod string link map[string]string // pkgPath.nameInPkg => linkname loaded map[*types.Package]*pkgInfo // loaded packages bvals map[ssa.Value]llssa.Expr // block values @@ -210,14 +211,19 @@ var ( argvTy = types.NewPointer(types.NewPointer(types.Typ[types.Int8])) ) -func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) llssa.Function { +func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyFunction, int) { pkgTypes, name, ftype := p.funcName(f, true) if ftype != goFunc { - return nil + if ftype == pyFunc { + // TODO(xsw): pyMod == "" + fnName := pysymPrefix + p.pyMod + "." + name + return nil, pkg.NewPyFunc(fnName, f.Signature), pyFunc + } + return nil, nil, ignoredFunc } fn := pkg.FuncOf(name) if fn != nil && fn.HasBody() { - return fn + return fn, nil, goFunc } var sig = f.Signature @@ -244,6 +250,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) llssa.Func } if nblk := len(f.Blocks); nblk > 0 { fn.MakeBlocks(nblk) // to set fn.HasBody() = true + isPyMod := p.pyMod != "" p.inits = append(p.inits, func() { p.fn = fn defer func() { @@ -263,21 +270,29 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) llssa.Func off[i] = p.compilePhis(b, block) } for i, block := range f.Blocks { - p.compileBlock(b, block, off[i], i == 0 && name == "main") + doInit := (i == 0 && name == "main") + doPyModInit := (isPyMod && i == 1 && f.Name() == "init" && sig.Recv() == nil) + p.compileBlock(b, block, off[i], doInit, doPyModInit) } for _, phi := range p.phis { phi() } }) } - return fn + return fn, nil, goFunc } // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. // or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc. -func (p *context) funcOf(fn *ssa.Function) (ret llssa.Function, ftype int) { +func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyFunction, ftype int) { _, name, ftype := p.funcName(fn, false) - if ftype == llgoInstr { + switch ftype { + case pyFunc: + pkg := p.pkg + if pyFn = pkg.PyFuncOf(name); pyFn == nil { + pyFn = pkg.NewPyFunc(name, fn.Signature) + } + case llgoInstr: switch name { case "cstr": ftype = llgoCstr @@ -294,35 +309,60 @@ func (p *context) funcOf(fn *ssa.Function) (ret llssa.Function, ftype int) { default: panic("unknown llgo instruction: " + name) } - } else { + default: pkg := p.pkg - if ret = pkg.FuncOf(name); ret == nil && len(fn.FreeVars) == 0 { + if aFn = pkg.FuncOf(name); aFn == nil { + if len(fn.FreeVars) > 0 { + return nil, nil, ignoredFunc + } sig := fn.Signature - ret = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false) + aFn = pkg.NewFuncEx(name, sig, llssa.Background(ftype), false) } } return } -func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, doInit bool) llssa.BasicBlock { - ret := p.fn.Block(block.Index) +func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, doInit, pyModInit bool) llssa.BasicBlock { + var last int + var instrs []ssa.Instruction + var ret = p.fn.Block(block.Index) b.SetBlock(ret) - if doInit { - prog := p.prog - pkg := p.pkg - fn := p.fn - argc := pkg.NewVar("__llgo_argc", types.NewPointer(types.Typ[types.Int32]), llssa.InC) - argv := pkg.NewVar("__llgo_argv", types.NewPointer(argvTy), llssa.InC) - argc.Init(prog.Null(argc.Type)) - argv.Init(prog.Null(argv.Type)) - b.Store(argc.Expr, fn.Param(0)) - b.Store(argv.Expr, fn.Param(1)) - callRuntimeInit(b, pkg) - b.Call(pkg.FuncOf("main.init").Expr) + if pyModInit { + last = len(block.Instrs) - 1 + instrs = block.Instrs[n:last] + } else { + instrs = block.Instrs[n:] + if doInit { + prog := p.prog + pkg := p.pkg + fn := p.fn + argc := pkg.NewVar("__llgo_argc", types.NewPointer(types.Typ[types.Int32]), llssa.InC) + argv := pkg.NewVar("__llgo_argv", types.NewPointer(argvTy), llssa.InC) + argc.Init(prog.Null(argc.Type)) + argv.Init(prog.Null(argv.Type)) + b.Store(argc.Expr, fn.Param(0)) + b.Store(argv.Expr, fn.Param(1)) + callRuntimeInit(b, pkg) + b.Call(pkg.FuncOf("main.init").Expr) + } } - for _, instr := range block.Instrs[n:] { + for _, instr := range instrs { p.compileInstr(b, instr) } + if pyModInit { + jump := block.Instrs[last].(*ssa.Jump) + jumpTo := p.jumpTo(jump) + modPath := p.pyMod + modName := pysymPrefix + modPath + modPtr := p.pkg.NewPyModVar(modName).Expr + mod := b.Load(modPtr) + cond := b.BinOp(token.NEQ, mod, b.Prog.Null(mod.Type)) + newBlk := p.fn.MakeBlock() + b.If(cond, jumpTo, newBlk) + b.SetBlock(newBlk) + b.Store(modPtr, b.ImportPyMod(modPath)) + b.Jump(jumpTo) + } return ret } @@ -507,11 +547,14 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue ret = b.BuiltinCall(fn, args...) } case *ssa.Function: - fn, ftype := p.compileFunction(cv) + aFn, pyFn, ftype := p.compileFunction(cv) switch ftype { case goFunc, cFunc: args := p.compileValues(b, args, kind) - ret = b.Call(fn.Expr, args...) + ret = b.Call(aFn.Expr, args...) + case pyFunc: + args := p.compileValues(b, args, kind) + ret = b.Call(pyFn.Expr, args...) case llgoCstr: ret = cstr(b, args) case llgoAdvance: @@ -643,6 +686,12 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue return ret } +func (p *context) jumpTo(v *ssa.Jump) llssa.BasicBlock { + fn := p.fn + succs := v.Block().Succs + return fn.Block(succs[0].Index) +} + func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { if iv, ok := instr.(instrOrValue); ok { p.compileInstrOrValue(b, iv, false) @@ -666,9 +715,7 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { val := p.compileValue(b, v.Val) b.Store(ptr, val) case *ssa.Jump: - fn := p.fn - succs := v.Block().Succs - jmpb := fn.Block(succs[0].Index) + jmpb := p.jumpTo(v) b.Jump(jmpb) case *ssa.Return: var results []llssa.Expr @@ -699,12 +746,13 @@ func (p *context) compileInstr(b llssa.Builder, instr ssa.Instruction) { } } -func (p *context) compileFunction(v *ssa.Function) (llssa.Function, int) { +func (p *context) compileFunction(v *ssa.Function) (goFn llssa.Function, pyFn llssa.PyFunction, kind int) { // v.Pkg == nil: means auto generated function? if v.Pkg == p.goPkg || v.Pkg == nil { // function in this package - if fn := p.compileFuncDecl(p.pkg, v); fn != nil { - return fn, goFunc + goFn, pyFn, kind = p.compileFuncDecl(p.pkg, v) + if kind != ignoredFunc { + return } } return p.funcOf(v) @@ -723,8 +771,11 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr { } } case *ssa.Function: - fn, _ := p.compileFunction(v) - return fn.Expr + aFn, pyFn, _ := p.compileFunction(v) + if aFn != nil { + return aFn.Expr + } + return pyFn.Expr case *ssa.Global: g := p.varOf(v) return g.Expr @@ -807,6 +858,7 @@ func NewPackage(prog llssa.Program, pkg *ssa.Package, files []*ast.File) (ret ll types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly? }, } + ctx.initPyModule() ctx.initFiles(pkgPath, files) for _, m := range members { member := m.val diff --git a/cl/compile_test.go b/cl/compile_test.go index 76be7e7a..1af95938 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -28,6 +28,10 @@ func testCompile(t *testing.T, src, expected string) { cltest.TestCompileEx(t, src, "foo.go", expected) } +func TestFromTestpy(t *testing.T) { + cltest.FromDir(t, "", "./_testpy", false) +} + func TestFromTestlibc(t *testing.T) { cltest.FromDir(t, "", "./_testlibc", false) } diff --git a/cl/import.go b/cl/import.go index dde42752..4f42f2c3 100644 --- a/cl/import.go +++ b/cl/import.go @@ -30,6 +30,8 @@ import ( "golang.org/x/tools/go/ssa" ) +// ----------------------------------------------------------------------------- + type symInfo struct { file string fullName string @@ -305,6 +307,7 @@ const ( ignoredFunc = iota goFunc = int(llssa.InGo) cFunc = int(llssa.InC) + pyFunc = int(llssa.InPython) llgoInstr = -1 llgoInstrBase = 0x80 @@ -339,6 +342,9 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin if strings.HasPrefix(v, "C.") { return nil, v[2:], cFunc } + if strings.HasPrefix(v, "py.") { + return pkg, v[3:], pyFunc + } if strings.HasPrefix(v, "llgo.") { return nil, v[5:], llgoInstr } @@ -391,3 +397,17 @@ func pkgKindByPath(pkgPath string) int { } return PkgNormal } + +// ----------------------------------------------------------------------------- + +const ( + pysymPrefix = "__llgo_py." +) + +func (p *context) initPyModule() { + if kind, mod := pkgKindByScope(p.goTyps.Scope()); kind == PkgPyModule { + p.pyMod = mod + } +} + +// ----------------------------------------------------------------------------- diff --git a/internal/build/build.go b/internal/build/build.go index 9a7644d8..2e18e97f 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -120,18 +120,31 @@ func Do(args []string, conf *Config) { cl.SetDebug(cl.DbgFlagAll) } + var needRt bool var rt []*packages.Package prog := llssa.NewProgram(nil) + load := func() []*packages.Package { + if rt == nil { + var err error + rt, err = packages.Load(cfg, llssa.PkgRuntime, llssa.PkgPython) + check(err) + } + return rt + } prog.SetRuntime(func() *types.Package { - rt, err = packages.Load(cfg, llssa.PkgRuntime) - check(err) + needRt = true + rt := load() return rt[0].Types }) + prog.SetPython(func() *types.Package { + rt := load() + return rt[1].Types + }) pkgs := buildAllPkgs(prog, initial, mode, verbose) var runtimeFiles []string - if rt != nil { + if needRt { runtimeFiles = allLinkFiles(rt) } if mode != ModeBuild { diff --git a/internal/llgen/llgen.go b/internal/llgen/llgen.go index fafcb959..a9e70e63 100644 --- a/internal/llgen/llgen.go +++ b/internal/llgen/llgen.go @@ -30,6 +30,7 @@ import ( "golang.org/x/tools/go/ssa/ssautil" llssa "github.com/goplus/llgo/ssa" + cpackages "golang.org/x/tools/go/packages" ) func Init() { @@ -71,11 +72,10 @@ func Gen(pkgPath, inFile string, src any) string { } prog := llssa.NewProgram(nil) - prog.SetRuntime(func() *types.Package { - rt, err := imp.Import(llssa.PkgRuntime) - check(err) - return rt + initRtAndPy(prog, &cpackages.Config{ + Mode: loadSyntax | cpackages.NeedDeps, }) + ret, err := cl.NewPackage(prog, ssaPkg, files) check(err) diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index 985ceaec..b7eeddaf 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -38,6 +38,27 @@ const ( loadSyntax = loadTypes | packages.NeedSyntax | packages.NeedTypesInfo ) +func initRtAndPy(prog llssa.Program, cfg *packages.Config) { + var pkgRtAndPy []*packages.Package + load := func() []*packages.Package { + if pkgRtAndPy == nil { + var err error + pkgRtAndPy, err = packages.Load(cfg, llssa.PkgRuntime, llssa.PkgPython) + check(err) + } + return pkgRtAndPy + } + + prog.SetRuntime(func() *types.Package { + rt := load() + return rt[0].Types + }) + prog.SetPython(func() *types.Package { + rt := load() + return rt[1].Types + }) +} + func GenFrom(fileOrPkg string) string { cfg := &packages.Config{ Mode: loadSyntax | packages.NeedDeps, @@ -52,11 +73,7 @@ func GenFrom(fileOrPkg string) string { ssaPkg.Build() prog := llssa.NewProgram(nil) - prog.SetRuntime(func() *types.Package { - rt, err := packages.Load(cfg, llssa.PkgRuntime) - check(err) - return rt[0].Types - }) + initRtAndPy(prog, cfg) if Verbose { ssaPkg.WriteTo(os.Stderr) diff --git a/internal/pyimport/llgo_autogen.lla b/internal/pyimport/llgo_autogen.lla deleted file mode 100644 index d839012f..00000000 Binary files a/internal/pyimport/llgo_autogen.lla and /dev/null differ diff --git a/internal/pyimport/pyimport.go b/internal/pyimport/pyimport.go index 69f96fad..72c23de7 100644 --- a/internal/pyimport/pyimport.go +++ b/internal/pyimport/pyimport.go @@ -23,10 +23,16 @@ import ( "github.com/goplus/llgo/py" ) +const ( + LLGoPackage = "decl" +) + +/* func init() { py.Initialize() py.SetProgramName(*c.Argv) } +*/ //go:linkname Module C.PyImport_ImportModule func Module(name *c.Char) *py.Module diff --git a/py/_demo/callpy/call.go b/py/_demo/callpy/call.go index 58904c9c..0198b8c4 100644 --- a/py/_demo/callpy/call.go +++ b/py/_demo/callpy/call.go @@ -11,7 +11,7 @@ func main() { math := py.ImportModule(c.Str("math")) sqrt := math.GetAttrString(c.Str("sqrt")) sqrt2 := sqrt.CallOneArg(py.Float(2)) - c.Printf(c.Str("sqrt(2) = %f\n"), sqrt2.FloatAsDouble()) + c.Printf(c.Str("sqrt(2) = %f\n"), sqrt2.Float64()) sqrt2.DecRef() sqrt.DecRef() math.DecRef() diff --git a/py/bytes.go b/py/bytes.go new file mode 100644 index 00000000..cd6e257a --- /dev/null +++ b/py/bytes.go @@ -0,0 +1,46 @@ +/* + * 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 py + +import ( + _ "unsafe" + + "github.com/goplus/llgo/c" +) + +// https://docs.python.org/3/c-api/bytes.html + +// String returns a new bytes object from a C string. +// +//go:linkname StringFrom C.PyBytes_FromString +func StringFrom(s *c.Char) *Object + +//go:linkname stringFromStringAndSize C.PyBytes_FromStringAndSize +func stringFromStringAndSize(s *c.Char, size uintptr) *Object + +// String returns a new bytes object from a Go string. +func String(s string) *Object { + return stringFromStringAndSize(c.GoStringData(s), uintptr(len(s))) +} + +// CStr returns the content of a bytes object as a C string. +// +// llgo:link (*Object).CStr C.PyBytes_AsString +func (o *Object) CStr() *c.Char { return nil } + +// llgo:link (*Object).Strlen C.PyBytes_Size +func (o *Object) Strlen() uintptr { return 0 } diff --git a/py/float.go b/py/float.go index a4916225..1cfa7a38 100644 --- a/py/float.go +++ b/py/float.go @@ -28,5 +28,5 @@ func Float(v float64) *Object //go:linkname FloatFromSring C.PyFloat_FromString func FloatFromSring(v *Object) *Object -// llgo:link (*Object).FloatAsDouble C.PyFloat_AsDouble -func (o *Object) FloatAsDouble() float64 { panic("unreachable") } +// llgo:link (*Object).Float64 C.PyFloat_AsDouble +func (o *Object) Float64() float64 { return 0 } diff --git a/py/module.go b/py/module.go index 64c1a8a8..8dddecbf 100644 --- a/py/module.go +++ b/py/module.go @@ -53,6 +53,6 @@ func Import(name *Object) *Module // manipulate a module’s __dict__. // // llgo:link (*Module).GetDict C.PyModule_GetDict -func (m *Module) GetDict() *Object { panic("unreachable") } +func (m *Module) GetDict() *Object { return nil } // ----------------------------------------------------------------------------- diff --git a/py/object.go b/py/object.go index 35c798e8..cb973653 100644 --- a/py/object.go +++ b/py/object.go @@ -36,7 +36,7 @@ type Object struct { func BuildValue(format *c.Char, __llgo_va_list ...any) *Object // llgo:link (*Object).DecRef C.Py_DecRef -func (o *Object) DecRef() { panic("unreachable") } +func (o *Object) DecRef() {} // ----------------------------------------------------------------------------- @@ -44,10 +44,10 @@ func (o *Object) DecRef() { panic("unreachable") } // or nil on failure. This is the equivalent of the Python expression o.attrName. // // llgo:link (*Object).GetAttr C.PyObject_GetAttr -func (o *Object) GetAttr(attrName *Object) *Object { panic("unreachable") } +func (o *Object) GetAttr(attrName *Object) *Object { return nil } // llgo:link (*Object).GetAttrString C.PyObject_GetAttrString -func (o *Object) GetAttrString(attrName *c.Char) *Object { panic("unreachable") } +func (o *Object) GetAttrString(attrName *c.Char) *Object { return nil } // ----------------------------------------------------------------------------- @@ -55,7 +55,7 @@ func (o *Object) GetAttrString(attrName *c.Char) *Object { panic("unreachable") // 0 otherwise. This function always succeeds. // // llgo:link (*Object).Callable C.PyCallable_Check -func (o *Object) Callable() int { panic("unreachable") } +func (o *Object) Callable() c.Int { return 0 } // Call a callable Python object o, with arguments given by the tuple args, and // named arguments given by the dictionary kwargs. @@ -69,7 +69,7 @@ func (o *Object) Callable() int { panic("unreachable") } // This is the equivalent of the Python expression: o(*args, **kwargs). // // llgo:link (*Object).Call C.PyObject_Call -func (o *Object) Call(args, kwargs *Object) *Object { panic("unreachable") } +func (o *Object) Call(args, kwargs *Object) *Object { return nil } // Call a callable Python object callable without any arguments. It is the most // efficient way to call a callable Python object without any argument. @@ -78,7 +78,7 @@ func (o *Object) Call(args, kwargs *Object) *Object { panic("unreachable") } // on failure. // // llgo:link (*Object).CallNoArgs C.PyObject_CallNoArgs -func (o *Object) CallNoArgs() *Object { panic("unreachable") } +func (o *Object) CallNoArgs() *Object { return nil } // Call a callable Python object callable with exactly 1 positional argument arg // and no keyword arguments. @@ -87,7 +87,7 @@ func (o *Object) CallNoArgs() *Object { panic("unreachable") } // on failure. // // llgo:link (*Object).CallOneArg C.PyObject_CallOneArg -func (o *Object) CallOneArg(arg *Object) *Object { panic("unreachable") } +func (o *Object) CallOneArg(arg *Object) *Object { return nil } // Call a callable Python object o, with arguments given by the tuple args. If no // arguments are needed, then args can be nil. @@ -98,7 +98,7 @@ func (o *Object) CallOneArg(arg *Object) *Object { panic("unreachable") } // This is the equivalent of the Python expression: o(*args). // // llgo:link (*Object).CallObject C.PyObject_CallObject -func (o *Object) CallObject(callable, args *Object) *Object { panic("unreachable") } +func (o *Object) CallObject(callable, args *Object) *Object { return nil } // Call a callable Python object o, with a variable number of C arguments. The C // arguments are described using a py.BuildValue style format string. The format @@ -113,7 +113,7 @@ func (o *Object) CallObject(callable, args *Object) *Object { panic("unreachable // faster alternative. // // llgo:link (*Object).CallFunction C.PyObject_CallFunction -func (o *Object) CallFunction(format *c.Char, __llgo_va_list ...any) *Object { panic("unreachable") } +func (o *Object) CallFunction(format *c.Char, __llgo_va_list ...any) *Object { return nil } // Call a callable Python object o, with a variable number of PyObject* arguments. // The arguments are provided as a variable number of parameters followed by nil. @@ -124,7 +124,7 @@ func (o *Object) CallFunction(format *c.Char, __llgo_va_list ...any) *Object { p // This is the equivalent of the Python expression: o(arg1, arg2, ...). // // llgo:link (*Object).CallFunctionObjArgs C.PyObject_CallFunctionObjArgs -func (o *Object) CallFunctionObjArgs(__llgo_va_list ...any) *Object { panic("unreachable") } +func (o *Object) CallFunctionObjArgs(__llgo_va_list ...any) *Object { return nil } // llgo:link (*Object).CallMethod C.PyObject_CallMethod func (o *Object) CallMethod(name *c.Char, format *c.Char, __llgo_va_list ...any) *Object { @@ -132,27 +132,27 @@ func (o *Object) CallMethod(name *c.Char, format *c.Char, __llgo_va_list ...any) } // llgo:link (*Object).CallMethodObjArgs C.PyObject_CallMethodObjArgs -func (o *Object) CallMethodObjArgs(name *Object, __llgo_va_list ...any) *Object { panic("unreachable") } +func (o *Object) CallMethodObjArgs(name *Object, __llgo_va_list ...any) *Object { return nil } // llgo:link (*Object).CallMethodNoArgs C.PyObject_CallMethodNoArgs -func (o *Object) CallMethodNoArgs(name *Object) *Object { panic("unreachable") } +func (o *Object) CallMethodNoArgs(name *Object) *Object { return nil } // llgo:link (*Object).CallMethodOneArg C.PyObject_CallMethodOneArg -func (o *Object) CallMethodOneArg(name, arg *Object) *Object { panic("unreachable") } +func (o *Object) CallMethodOneArg(name, arg *Object) *Object { return nil } // llgo:link (*Object).Vectorcall C.PyObject_Vectorcall func (o *Object) Vectorcall(args **Object, nargs uintptr, kwnames *Object) *Object { - panic("unreachable") + return nil } // llgo:link (*Object).VectorcallDict C.PyObject_VectorcallDict func (o *Object) VectorcallDict(args **Object, nargs uintptr, kwdict *Object) *Object { - panic("unreachable") + return nil } // llgo:link (*Object).VectorcallMethod C.PyObject_VectorcallMethod func (o *Object) VectorcallMethod(name *Object, args **Object, nargs uintptr, kwnames *Object) *Object { - panic("unreachable") + return nil } // ----------------------------------------------------------------------------- diff --git a/py/os/os.go b/py/os/os.go new file mode 100644 index 00000000..8a421dac --- /dev/null +++ b/py/os/os.go @@ -0,0 +1,30 @@ +/* + * 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 os + +import ( + _ "unsafe" + + "github.com/goplus/llgo/py" +) + +const ( + LLGoPackage = "py.os" +) + +//go:linkname Getcwd py.getcwd +func Getcwd() *py.Object diff --git a/ssa/cl_test.go b/ssa/cl_test.go index 0a9d8493..4d79ec49 100644 --- a/ssa/cl_test.go +++ b/ssa/cl_test.go @@ -22,6 +22,10 @@ import ( "github.com/goplus/llgo/cl/cltest" ) +func TestFromTestpy(t *testing.T) { + cltest.FromDir(t, "", "../cl/_testpy", false) +} + func TestFromTestrt(t *testing.T) { cltest.FromDir(t, "", "../cl/_testrt", true) } diff --git a/ssa/decl.go b/ssa/decl.go index af4603b2..b89243fa 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -18,6 +18,7 @@ package ssa import ( "go/types" + "log" "strconv" "github.com/goplus/llvm" @@ -68,6 +69,23 @@ type aGlobal struct { // variable. type Global = *aGlobal +// NewVar creates a new global variable. +func (p Package) NewVar(name string, typ types.Type, bg Background) Global { + if v, ok := p.vars[name]; ok { + return v + } + t := p.Prog.Type(typ, bg) + gbl := llvm.AddGlobal(p.mod, t.ll, name) + ret := &aGlobal{Expr{gbl, t}} + p.vars[name] = ret + return ret +} + +// VarOf returns a global variable by name. +func (p Package) VarOf(name string) Global { + return p.vars[name] +} + // Init initializes the global variable with the given value. func (g Global) Init(v Expr) { g.impl.SetInitializer(v.impl) @@ -140,6 +158,31 @@ type aFunction struct { // Function represents a function or method. type Function = *aFunction +// NewFunc creates a new function. +func (p Package) NewFunc(name string, sig *types.Signature, bg Background) Function { + return p.NewFuncEx(name, sig, bg, false) +} + +// NewFuncEx creates a new function. +func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars bool) Function { + if v, ok := p.fns[name]; ok { + return v + } + t := p.Prog.FuncDecl(sig, bg) + if debugInstr { + log.Println("NewFunc", name, t.raw.Type, "hasFreeVars:", hasFreeVars) + } + fn := llvm.AddFunction(p.mod, name, t.ll) + ret := newFunction(fn, t, p, p.Prog, hasFreeVars) + p.fns[name] = ret + return ret +} + +// FuncOf returns a function by name. +func (p Package) FuncOf(name string) Function { + return p.fns[name] +} + func newFunction(fn llvm.Value, t Type, pkg Package, prog Program, hasFreeVars bool) Function { params, hasVArg := newParams(t, prog) base := 0 @@ -212,22 +255,60 @@ func (p Function) MakeBody(nblk int) Builder { // MakeBlocks creates nblk basic blocks for the function. func (p Function) MakeBlocks(nblk int) []BasicBlock { - if p.blks == nil { + n := len(p.blks) + if n == 0 { p.blks = make([]BasicBlock, 0, nblk) } - n := len(p.blks) - f := p.impl for i := 0; i < nblk; i++ { - label := "_llgo_" + strconv.Itoa(i) - blk := llvm.AddBasicBlock(f, label) - p.blks = append(p.blks, &aBasicBlock{blk, p, n + i}) + p.addBlock(n + i) } return p.blks[n:] } +func (p Function) addBlock(idx int) BasicBlock { + label := "_llgo_" + strconv.Itoa(idx) + blk := llvm.AddBasicBlock(p.impl, label) + ret := &aBasicBlock{blk, p, idx} + p.blks = append(p.blks, ret) + return ret +} + +// MakeBlock creates a new basic block for the function. +func (p Function) MakeBlock() BasicBlock { + return p.addBlock(len(p.blks)) +} + // Block returns the ith basic block of the function. func (p Function) Block(idx int) BasicBlock { return p.blks[idx] } // ----------------------------------------------------------------------------- + +type aPyFunction struct { + Expr + Obj Global +} + +// PyFunction represents a python function. +type PyFunction = *aPyFunction + +// NewPyFunc creates a new python function. +func (p Package) NewPyFunc(name string, sig *types.Signature) PyFunction { + if v, ok := p.pyfns[name]; ok { + return v + } + obj := p.NewVar(name, p.Prog.PyObjectPtrPtr().RawType(), InC) + ty := &aType{obj.ll, rawType{sig}, vkPyFunc} + expr := Expr{obj.impl, ty} + ret := &aPyFunction{expr, obj} + p.pyfns[name] = ret + return ret +} + +// PyFuncOf returns a function by name. +func (p Package) PyFuncOf(name string) PyFunction { + return p.pyfns[name] +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/expr.go b/ssa/expr.go index 193b8f49..5b4801eb 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -1204,7 +1204,6 @@ func (b Builder) InlineCall(fn Expr, args ...Expr) (ret Expr) { // t4 = t3() // t7 = invoke t5.Println(...t6) func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { - prog := b.Prog if debugInstr { var b bytes.Buffer name := fn.impl.Name() @@ -1219,11 +1218,16 @@ func (b Builder) Call(fn Expr, args ...Expr) (ret Expr) { } log.Println(b.String()) } + var kind = fn.kind + if kind == vkPyFunc { + return b.pyCall(fn, args) + } var ll llvm.Type var data Expr var sig *types.Signature + var prog = b.Prog var raw = fn.raw.Type - switch fn.kind { + switch kind { case vkClosure: data = b.Field(fn, 1) fn = b.Field(fn, 0) diff --git a/ssa/package.go b/ssa/package.go index 1ee6b8cf..b0fbb0e0 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -19,13 +19,13 @@ package ssa import ( "go/token" "go/types" - "log" "github.com/goplus/llvm" "golang.org/x/tools/go/types/typeutil" ) const ( + PkgPython = "github.com/goplus/llgo/py" PkgRuntime = "github.com/goplus/llgo/internal/runtime" ) @@ -103,6 +103,9 @@ type aProgram struct { rt *types.Package rtget func() *types.Package + py *types.Package + pyget func() *types.Package + target *Target td llvm.TargetData // tm llvm.TargetMachine @@ -131,8 +134,15 @@ type aProgram struct { uintptrTy Type intTy Type f64Ty Type + pyObjPtr Type + pyObjPPtr Type + + pyImpTy *types.Signature + callNoArg *types.Signature + callOneArg *types.Signature needRuntime bool + needPyInit bool } // A Program presents a program. @@ -158,6 +168,17 @@ func NewProgram(target *Target) Program { return &aProgram{ctx: ctx, gocvt: newGoTypes(), target: target, td: td, named: make(map[string]llvm.Type)} } +// SetPython sets the Python package. +// Its type can be *types.Package or func() *types.Package. +func (p Program) SetPython(py any) { + switch v := py.(type) { + case *types.Package: + p.py = v + case func() *types.Package: + p.pyget = v + } +} + // SetRuntime sets the runtime. // Its type can be *types.Package or func() *types.Package. func (p Program) SetRuntime(runtime any) { @@ -182,12 +203,25 @@ func (p Program) runtime() *types.Package { return p.rt } +func (p Program) python() *types.Package { + if p.py == nil { + p.py = p.pyget() + } + return p.py +} + func (p Program) rtNamed(name string) *types.Named { t := p.runtime().Scope().Lookup(name).Type().(*types.Named) t, _ = p.gocvt.cvtNamed(t) return t } +func (p Program) pyNamed(name string) *types.Named { + // TODO(xsw): does python type need to convert? + t := p.python().Scope().Lookup(name).Type().(*types.Named) + return t +} + func (p Program) rtType(name string) Type { return p.rawType(p.rtNamed(name)) } @@ -228,8 +262,26 @@ func (p Program) NewPackage(name, pkgPath string) Package { gbls := make(map[string]Global) fns := make(map[string]Function) stubs := make(map[string]Function) + pyfns := make(map[string]PyFunction) p.needRuntime = false - return &aPackage{mod, gbls, fns, stubs, p} + return &aPackage{mod, gbls, fns, stubs, pyfns, p} +} + +// PyObjectPtrPtr returns the **py.Object type. +func (p Program) PyObjectPtrPtr() Type { + if p.pyObjPPtr == nil { + p.pyObjPPtr = p.Pointer(p.PyObjectPtr()) + } + return p.pyObjPPtr +} + +// PyObjectPtr returns the *py.Object type. +func (p Program) PyObjectPtr() Type { + if p.pyObjPtr == nil { + objPtr := types.NewPointer(p.pyNamed("Object")) + p.pyObjPtr = p.rawType(objPtr) + } + return p.pyObjPtr } // Void returns void type. @@ -316,6 +368,7 @@ type aPackage struct { vars map[string]Global fns map[string]Function stubs map[string]Function + pyfns map[string]PyFunction Prog Program } @@ -328,40 +381,6 @@ func (p Package) NewConst(name string, val constant.Value) NamedConst { } */ -// NewVar creates a new global variable. -func (p Package) NewVar(name string, typ types.Type, bg Background) Global { - t := p.Prog.Type(typ, bg) - gbl := llvm.AddGlobal(p.mod, t.ll, name) - ret := &aGlobal{Expr{gbl, t}} - p.vars[name] = ret - return ret -} - -// VarOf returns a global variable by name. -func (p Package) VarOf(name string) Global { - return p.vars[name] -} - -// NewFunc creates a new function. -func (p Package) NewFunc(name string, sig *types.Signature, bg Background) Function { - return p.NewFuncEx(name, sig, bg, false) -} - -// NewFuncEx creates a new function. -func (p Package) NewFuncEx(name string, sig *types.Signature, bg Background, hasFreeVars bool) Function { - if v, ok := p.fns[name]; ok { - return v - } - t := p.Prog.FuncDecl(sig, bg) - if debugInstr { - log.Println("NewFunc", name, t.raw.Type, "hasFreeVars:", hasFreeVars) - } - fn := llvm.AddFunction(p.mod, name, t.ll) - ret := newFunction(fn, t, p, p.Prog, hasFreeVars) - p.fns[name] = ret - return ret -} - func (p Package) rtFunc(fnName string) Expr { fn := p.Prog.runtime().Scope().Lookup(fnName).(*types.Func) name := FullName(fn.Pkg(), fnName) @@ -369,6 +388,11 @@ func (p Package) rtFunc(fnName string) Expr { return p.NewFunc(name, sig, InGo).Expr } +func (p Package) pyFunc(fullName string, sig *types.Signature) Expr { + p.Prog.needPyInit = true + return p.NewFunc(fullName, sig, InC).Expr +} + func (p Package) closureStub(b Builder, t *types.Struct, v Expr) Expr { name := v.impl.Name() prog := b.Prog @@ -402,11 +426,6 @@ func (p Package) closureStub(b Builder, t *types.Struct, v Expr) Expr { return b.aggregateValue(prog.rawType(t), v.impl, nilVal) } -// FuncOf returns a function by name. -func (p Package) FuncOf(name string) Function { - return p.fns[name] -} - // ----------------------------------------------------------------------------- // String returns a string representation of the package. @@ -455,3 +474,73 @@ func (p *Package) WriteFile(file string) (err error) { */ // ----------------------------------------------------------------------------- + +func (p Program) tyImportPyModule() *types.Signature { + if p.pyImpTy == nil { + charPtr := types.NewPointer(types.Typ[types.Int8]) + objPtr := p.PyObjectPtr().raw.Type + params := types.NewTuple(types.NewParam(token.NoPos, nil, "", charPtr)) + results := types.NewTuple(types.NewParam(token.NoPos, nil, "", objPtr)) + p.pyImpTy = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.pyImpTy +} + +func (p Program) tyCallNoArg() *types.Signature { + if p.callNoArg == nil { + objPtr := p.PyObjectPtr().raw.Type + paramObjPtr := types.NewParam(token.NoPos, nil, "", objPtr) + params := types.NewTuple(paramObjPtr) + p.callNoArg = types.NewSignatureType(nil, nil, nil, params, params, false) + } + return p.callNoArg +} + +func (p Program) tyCallOneArg() *types.Signature { + if p.callOneArg == nil { + objPtr := p.PyObjectPtr().raw.Type + paramObjPtr := types.NewParam(token.NoPos, nil, "", objPtr) + params := types.NewTuple(paramObjPtr, paramObjPtr) + results := types.NewTuple(paramObjPtr) + p.callOneArg = types.NewSignatureType(nil, nil, nil, params, results, false) + } + return p.callOneArg +} + +// ImportPyMod imports a Python module. +func (b Builder) ImportPyMod(path string) Expr { + pkg := b.Func.Pkg + fnImp := pkg.pyFunc("PyImport_ImportModule", b.Prog.tyImportPyModule()) + return b.Call(fnImp, b.CStr(path)) +} + +// NewPyModVar creates a new global variable for a Python module. +func (p Package) NewPyModVar(name string) Global { + prog := p.Prog + objPtr := prog.PyObjectPtrPtr().raw.Type + g := p.NewVar(name, objPtr, InC) + g.Init(prog.Null(g.Type)) + g.impl.SetLinkage(llvm.LinkOnceAnyLinkage) + return g +} + +func (b Builder) pyCall(fn Expr, args []Expr) (ret Expr) { + prog := b.Prog + pkg := b.Func.Pkg + sig := fn.raw.Type.(*types.Signature) + params := sig.Params() + n := params.Len() + switch n { + case 0: + call := pkg.pyFunc("PyObject_CallNoArg", prog.tyCallNoArg()) + ret = b.Call(call, fn) + case 1: + call := pkg.pyFunc("PyObject_CallOneArg", prog.tyCallOneArg()) + ret = b.Call(call, fn, args[0]) + default: + panic("todo") + } + return +} + +// ----------------------------------------------------------------------------- diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index cc9d8621..17b7ccf5 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -25,6 +25,12 @@ import ( "github.com/goplus/llvm" ) +func TestSetPython(t *testing.T) { + prog := NewProgram(nil) + typ := types.NewPackage("foo", "foo") + prog.SetPython(typ) +} + func TestClosureCtx(t *testing.T) { defer func() { if r := recover(); r == nil { @@ -106,10 +112,28 @@ func assertPkg(t *testing.T, p Package, expected string) { } } +func TestPyFunc(t *testing.T) { + prog := NewProgram(nil) + py := types.NewPackage("foo", "foo") + o := types.NewTypeName(0, py, "Object", nil) + types.NewNamed(o, types.Typ[types.Int], nil) + py.Scope().Insert(o) + prog.SetPython(py) + pkg := prog.NewPackage("bar", "foo/bar") + sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) + a := pkg.NewPyFunc("a", sig) + if pkg.NewPyFunc("a", sig) != a { + t.Fatal("NewPyFunc(a) failed") + } +} + func TestVar(t *testing.T) { prog := NewProgram(nil) pkg := prog.NewPackage("bar", "foo/bar") a := pkg.NewVar("a", types.Typ[types.Int], InGo) + if pkg.NewVar("a", types.Typ[types.Int], InGo) != a { + t.Fatal("NewVar(a) failed") + } a.Init(prog.Val(100)) b := pkg.NewVar("b", types.Typ[types.Int], InGo) b.Init(a.Expr) diff --git a/ssa/type.go b/ssa/type.go index 0c3e35ea..936c9b21 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -43,6 +43,7 @@ const ( vkFuncDecl vkFuncPtr vkClosure + vkPyFunc vkTuple vkPhisExpr = -1 ) diff --git a/ssa/type_cvt.go b/ssa/type_cvt.go index e9cbe538..8346577e 100644 --- a/ssa/type_cvt.go +++ b/ssa/type_cvt.go @@ -42,6 +42,7 @@ const ( inUnknown Background = iota InGo InC + InPython ) // Type convert a Go/C type into raw type.