diff --git a/cl/_testpy/callpy/out.ll b/cl/_testpy/callpy/out.ll index 001dd685..d3892453 100644 --- a/cl/_testpy/callpy/out.ll +++ b/cl/_testpy/callpy/out.ll @@ -4,8 +4,8 @@ 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 +@__llgo_py.math.sqrt = linkonce global ptr null +@__llgo_py.os.getcwd = linkonce global ptr null @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 @@ -26,13 +26,14 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0 define void @main(i32 %0, ptr %1) { _llgo_0: + call void @Py_Initialize() 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) + %3 = call ptr @PyObject_CallOneArg(ptr @__llgo_py.math.sqrt, ptr %2) + %4 = call ptr @PyObject_CallNoArgs(ptr @__llgo_py.os.getcwd) %5 = call double @PyFloat_AsDouble() %6 = call i32 (ptr, ...) @printf(ptr @0, double %5) %7 = call ptr @PyBytes_AsString() @@ -50,10 +51,12 @@ declare ptr @PyFloat_FromDouble(double) declare ptr @PyObject_CallOneArg(ptr, ptr) -declare ptr @PyObject_CallNoArg(ptr) +declare ptr @PyObject_CallNoArgs(ptr) declare double @PyFloat_AsDouble() declare i32 @printf(ptr, ...) declare ptr @PyBytes_AsString() + +declare void @Py_Initialize() diff --git a/cl/_testpy/math/out.ll b/cl/_testpy/math/out.ll index f5feead3..4fd8d274 100644 --- a/cl/_testpy/math/out.ll +++ b/cl/_testpy/math/out.ll @@ -1,7 +1,7 @@ ; ModuleID = 'math' source_filename = "math" -@__llgo_py.math.sqrt = external global ptr +@__llgo_py.math.sqrt = linkonce global ptr null @"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 diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index 4b21cb04..436b272e 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -159,6 +159,11 @@ func TestCompileEx(t *testing.T, src any, fname, expected string) { if err != nil { t.Fatal("cl.NewPackage failed:", err) } + + if prog.NeedPyInit() { // call PyInit if needed + ret.PyInit() + } + if v := ret.String(); v != expected { t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected) } diff --git a/cl/compile.go b/cl/compile.go index eddf4aea..83db6653 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -285,13 +285,18 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun // 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) (aFn llssa.Function, pyFn llssa.PyFunction, ftype int) { - _, name, ftype := p.funcName(fn, false) + pkgTypes, name, ftype := p.funcName(fn, false) switch ftype { case pyFunc: - pkg := p.pkg - if pyFn = pkg.PyFuncOf(name); pyFn == nil { - pyFn = pkg.NewPyFunc(name, fn.Signature) + if kind, mod := pkgKindByScope(pkgTypes.Scope()); kind == PkgPyModule { + pkg := p.pkg + fnName := pysymPrefix + mod + "." + name + if pyFn = pkg.PyFuncOf(fnName); pyFn == nil { + pyFn = pkg.NewPyFunc(fnName, fn.Signature) + return + } } + ftype = ignoredFunc case llgoInstr: switch name { case "cstr": @@ -371,8 +376,7 @@ const ( ) func callRuntimeInit(b llssa.Builder, pkg llssa.Package) { - sig := types.NewSignatureType(nil, nil, nil, nil, nil, false) - fn := pkg.NewFunc(RuntimeInit, sig, llssa.InC) // don't need to convert runtime.init + fn := pkg.NewFunc(RuntimeInit, llssa.NoArgsNoRet, llssa.InC) // don't need to convert runtime.init b.Call(fn.Expr) } diff --git a/cl/compile_test.go b/cl/compile_test.go index 1af95938..14ad7339 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -33,7 +33,7 @@ func TestFromTestpy(t *testing.T) { } func TestFromTestlibc(t *testing.T) { - cltest.FromDir(t, "", "./_testlibc", false) + cltest.FromDir(t, "", "./_testlibc", true) } func TestFromTestrt(t *testing.T) { diff --git a/cl/import.go b/cl/import.go index 4f42f2c3..0afc1c0b 100644 --- a/cl/import.go +++ b/cl/import.go @@ -214,7 +214,7 @@ func (p *context) initLink(line string, prefix int, f func(inPkgName string) (fu } else { panic(line + ": no specified call convention. eg. //go:linkname Printf C.printf") } - } else { + } else if c := inPkgName[0]; c >= 'A' && c <= 'Z' { fmt.Fprintln(os.Stderr, "==>", line) fmt.Fprintf(os.Stderr, "llgo: linkname %s not found and ignored\n", inPkgName) } diff --git a/internal/build/build.go b/internal/build/build.go index 2e18e97f..38ccc5a8 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -160,12 +160,22 @@ func Do(args []string, conf *Config) { } } -func setNeedRuntime(pkg *packages.Package) { - pkg.ID = "" // just use pkg.Module to mark it needs runtime +func setNeedRuntimeOrPyInit(pkg *packages.Package, needRuntime, needPyInit bool) { + v := []byte{'0', '0'} + if needRuntime { + v[0] = '1' + } + if needPyInit { + v[1] = '1' + } + pkg.ID = string(v) // just use pkg.ID to mark it needs runtime } -func isNeedRuntime(pkg *packages.Package) bool { - return pkg.ID == "" +func isNeedRuntimeOrPyInit(pkg *packages.Package) (needRuntime, needPyInit bool) { + if len(pkg.ID) == 2 { + return pkg.ID[0] == '1', pkg.ID[1] == '1' + } + return } func buildAllPkgs(prog llssa.Program, initial []*packages.Package, mode Mode, verbose bool) (pkgs []*aPackage) { @@ -204,9 +214,7 @@ func buildAllPkgs(prog llssa.Program, initial []*packages.Package, mode Mode, ve pkg.ExportFile = command default: buildPkg(prog, aPkg, mode, verbose) - if prog.NeedRuntime() { - setNeedRuntime(pkg) - } + setNeedRuntimeOrPyInit(pkg, prog.NeedRuntime(), prog.NeedPyInit()) } } return @@ -225,27 +233,43 @@ func linkMainPkg(pkg *packages.Package, pkgs []*aPackage, runtimeFiles []string, args[1] = app args[2] = "-Wno-override-module" needRuntime := false + needPyInit := false packages.Visit([]*packages.Package{pkg}, nil, func(p *packages.Package) { if p.ExportFile != "" && !isRuntimePkg(p.PkgPath) { // skip packages that only contain declarations args = appendLinkFiles(args, p.ExportFile) + need1, need2 := isNeedRuntimeOrPyInit(p) if !needRuntime { - needRuntime = isNeedRuntime(p) + needRuntime = need1 + } + if !needPyInit { + needPyInit = need2 } } }) + + var aPkg *aPackage + for _, v := range pkgs { + if v.Package == pkg { // found this package + aPkg = v + break + } + } + + dirty := false if needRuntime && runtimeFiles != nil { args = append(args, runtimeFiles...) } else { - for _, aPkg := range pkgs { - if aPkg.Package == pkg { // make empty runtime.init if no runtime needed - lpkg := aPkg.LPkg - lpkg.FuncOf(cl.RuntimeInit).MakeBody(1).Return() - if needLLFile(mode) { - os.WriteFile(pkg.ExportFile, []byte(lpkg.String()), 0644) - } - break - } - } + dirty = true + fn := aPkg.LPkg.FuncOf(cl.RuntimeInit) + fn.MakeBody(1).Return() + } + if needPyInit { + dirty = aPkg.LPkg.PyInit() + } + + if dirty && needLLFile(mode) { + lpkg := aPkg.LPkg + os.WriteFile(pkg.ExportFile, []byte(lpkg.String()), 0644) } if verbose || mode != ModeRun { diff --git a/internal/llgen/llgen.go b/internal/llgen/llgen.go index a9e70e63..801a81f0 100644 --- a/internal/llgen/llgen.go +++ b/internal/llgen/llgen.go @@ -79,6 +79,10 @@ func Gen(pkgPath, inFile string, src any) string { ret, err := cl.NewPackage(prog, ssaPkg, files) check(err) + if prog.NeedPyInit() { // call PyInit if needed + ret.PyInit() + } + return ret.String() } diff --git a/internal/llgen/llgenf.go b/internal/llgen/llgenf.go index b7eeddaf..64a06f55 100644 --- a/internal/llgen/llgenf.go +++ b/internal/llgen/llgenf.go @@ -82,6 +82,10 @@ func GenFrom(fileOrPkg string) string { ret, err := cl.NewPackage(prog, ssaPkg, pkg.Syntax) check(err) + if prog.NeedPyInit() { // call PyInit if needed + ret.PyInit() + } + return ret.String() } diff --git a/py/math/llgo_autogen.lla b/py/math/llgo_autogen.lla new file mode 100644 index 00000000..1daa2e16 Binary files /dev/null and b/py/math/llgo_autogen.lla differ diff --git a/py/os/llgo_autogen.lla b/py/os/llgo_autogen.lla new file mode 100644 index 00000000..b018d58d Binary files /dev/null and b/py/os/llgo_autogen.lla differ diff --git a/ssa/decl.go b/ssa/decl.go index b89243fa..f23d23a4 100644 --- a/ssa/decl.go +++ b/ssa/decl.go @@ -298,7 +298,11 @@ 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) + prog := p.Prog + prog.needPyInit = true + obj := p.NewVar(name, prog.PyObjectPtrPtr().RawType(), InC) + obj.Init(prog.Null(obj.Type)) + obj.impl.SetLinkage(llvm.LinkOnceAnyLinkage) ty := &aType{obj.ll, rawType{sig}, vkPyFunc} expr := Expr{obj.impl, ty} ret := &aPyFunction{expr, obj} diff --git a/ssa/package.go b/ssa/package.go index b0fbb0e0..cc730c5c 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -138,7 +138,7 @@ type aProgram struct { pyObjPPtr Type pyImpTy *types.Signature - callNoArg *types.Signature + callNoArgs *types.Signature callOneArg *types.Signature needRuntime bool @@ -195,6 +195,11 @@ func (p Program) NeedRuntime() bool { return p.needRuntime } +// NeedPyInit returns if the current package needs Python initialization. +func (p Program) NeedPyInit() bool { + return p.needPyInit +} + func (p Program) runtime() *types.Package { if p.rt == nil { p.rt = p.rtget() @@ -263,8 +268,11 @@ func (p Program) NewPackage(name, pkgPath string) Package { fns := make(map[string]Function) stubs := make(map[string]Function) pyfns := make(map[string]PyFunction) + pymods := make(map[string]Global) p.needRuntime = false - return &aPackage{mod, gbls, fns, stubs, pyfns, p} + // Don't need reset p.needPyInit here + // p.needPyInit = false + return &aPackage{mod, gbls, fns, stubs, pyfns, pymods, p} } // PyObjectPtrPtr returns the **py.Object type. @@ -364,12 +372,13 @@ func (p Program) Float64() Type { // initializer) and "init#%d", the nth declared init function, // and unspecified other things too. type aPackage struct { - mod llvm.Module - vars map[string]Global - fns map[string]Function - stubs map[string]Function - pyfns map[string]PyFunction - Prog Program + mod llvm.Module + vars map[string]Global + fns map[string]Function + stubs map[string]Function + pyfns map[string]PyFunction + pymods map[string]Global + Prog Program } type Package = *aPackage @@ -486,14 +495,14 @@ func (p Program) tyImportPyModule() *types.Signature { return p.pyImpTy } -func (p Program) tyCallNoArg() *types.Signature { - if p.callNoArg == nil { +func (p Program) tyCallNoArgs() *types.Signature { + if p.callNoArgs == 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) + p.callNoArgs = types.NewSignatureType(nil, nil, nil, params, params, false) } - return p.callNoArg + return p.callNoArgs } func (p Program) tyCallOneArg() *types.Signature { @@ -507,23 +516,38 @@ func (p Program) tyCallOneArg() *types.Signature { 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)) +// PyInit initializes Python for a main package. +func (p Package) PyInit() bool { + if fn := p.FuncOf("main"); fn != nil { + b := fn.NewBuilder() + b.SetBlockEx(fn.Block(0), AtStart).CallPyInit() + b.Dispose() + return true + } + return false } // NewPyModVar creates a new global variable for a Python module. func (p Package) NewPyModVar(name string) Global { + if v, ok := p.pymods[name]; ok { + return v + } 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) + p.pymods[name] = g return g } +// 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)) +} + func (b Builder) pyCall(fn Expr, args []Expr) (ret Expr) { prog := b.Prog pkg := b.Func.Pkg @@ -532,7 +556,7 @@ func (b Builder) pyCall(fn Expr, args []Expr) (ret Expr) { n := params.Len() switch n { case 0: - call := pkg.pyFunc("PyObject_CallNoArg", prog.tyCallNoArg()) + call := pkg.pyFunc("PyObject_CallNoArgs", prog.tyCallNoArgs()) ret = b.Call(call, fn) case 1: call := pkg.pyFunc("PyObject_CallOneArg", prog.tyCallOneArg()) @@ -543,4 +567,14 @@ func (b Builder) pyCall(fn Expr, args []Expr) (ret Expr) { return } +// CallPyInit calls Py_Initialize. +func (b Builder) CallPyInit() (ret Expr) { + fn := b.Func.Pkg.pyFunc("Py_Initialize", NoArgsNoRet) + return b.Call(fn) +} + +var ( + NoArgsNoRet = types.NewSignatureType(nil, nil, nil, nil, nil, false) +) + // ----------------------------------------------------------------------------- diff --git a/ssa/ssa_test.go b/ssa/ssa_test.go index 17b7ccf5..ef60f7af 100644 --- a/ssa/ssa_test.go +++ b/ssa/ssa_test.go @@ -25,6 +25,28 @@ import ( "github.com/goplus/llvm" ) +func TestSetBlock(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Log("SetBlock: no error?") + } + }() + fn := &aFunction{} + b := &aBuilder{Func: fn} + b.SetBlock(&aBasicBlock{}) +} + +func TestSetBlockEx(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Log("SetBlockEx: no error?") + } + }() + fn := &aFunction{} + b := &aBuilder{Func: fn} + b.SetBlockEx(&aBasicBlock{fn: fn}, -1) +} + func TestSetPython(t *testing.T) { prog := NewProgram(nil) typ := types.NewPackage("foo", "foo") @@ -125,6 +147,10 @@ func TestPyFunc(t *testing.T) { if pkg.NewPyFunc("a", sig) != a { t.Fatal("NewPyFunc(a) failed") } + foo := pkg.NewPyModVar("foo") + if pkg.NewPyModVar("foo") != foo { + t.Fatal("NewPyModVar(foo) failed") + } } func TestVar(t *testing.T) { diff --git a/ssa/stmt_builder.go b/ssa/stmt_builder.go index bb1744b8..817d6524 100644 --- a/ssa/stmt_builder.go +++ b/ssa/stmt_builder.go @@ -57,15 +57,40 @@ type aBuilder struct { // Builder represents a builder for creating instructions in a function. type Builder = *aBuilder -// SetBlock sets the current block to the specified basic block. +// Dispose disposes of the builder. +func (b Builder) Dispose() { + b.impl.Dispose() +} + +// SetBlock means SetBlockEx(blk, AtEnd). func (b Builder) SetBlock(blk BasicBlock) Builder { - if b.Func != blk.fn { - panic("mismatched function") - } if debugInstr { log.Printf("Block _llgo_%v:\n", blk.idx) } - b.impl.SetInsertPointAtEnd(blk.impl) + b.SetBlockEx(blk, AtEnd) + return b +} + +type InsertPoint int + +const ( + AtEnd InsertPoint = iota + AtStart +) + +// SetBlockEx sets blk as current basic block and pos as its insert point. +func (b Builder) SetBlockEx(blk BasicBlock, pos InsertPoint) Builder { + if b.Func != blk.fn { + panic("mismatched function") + } + switch pos { + case AtEnd: + b.impl.SetInsertPointAtEnd(blk.impl) + case AtStart: + b.impl.SetInsertPointBefore(blk.impl.FirstInstruction()) + default: + panic("SetBlockEx: invalid pos") + } return b }