diff --git a/cl/_testpy/callpy/in.go b/cl/_testpy/callpy/in.go index 21ec21ab..12424d62 100644 --- a/cl/_testpy/callpy/in.go +++ b/cl/_testpy/callpy/in.go @@ -4,9 +4,12 @@ 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 index e69de29b..001dd685 100644 --- a/cl/_testpy/callpy/out.ll +++ 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/compile.go b/cl/compile.go index de93ed5c..fd96cbdf 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -557,7 +557,8 @@ func (p *context) compileInstrOrValue(b llssa.Builder, iv instrOrValue, asValue args := p.compileValues(b, args, kind) ret = b.Call(aFn.Expr, args...) case pyFunc: - log.Panicln("pyFunc:", pyFn) + args := p.compileValues(b, args, kind) + ret = b.Call(pyFn.Expr, args...) case llgoCstr: ret = cstr(b, args) case llgoAdvance: diff --git a/cl/compile_test.go b/cl/compile_test.go index e95916db..1af95938 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -29,7 +29,7 @@ func testCompile(t *testing.T, src, expected string) { } func TestFromTestpy(t *testing.T) { - cltest.FromDir(t, "callpy", "./_testpy", false) + cltest.FromDir(t, "", "./_testpy", false) } func TestFromTestlibc(t *testing.T) { 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 1fbb7716..1cfa7a38 100644 --- a/py/float.go +++ b/py/float.go @@ -29,4 +29,4 @@ func Float(v float64) *Object func FloatFromSring(v *Object) *Object // llgo:link (*Object).Float64 C.PyFloat_AsDouble -func (o *Object) Float64() float64 { panic("unreachable") } +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/expr.go b/ssa/expr.go index 7d2715d6..72e478dd 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -1176,7 +1176,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() @@ -1191,11 +1190,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 2f5decdc..cb3691b3 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -136,7 +136,9 @@ type aProgram struct { pyObjPtr Type pyObjPPtr Type - pyImpTy *types.Signature + pyImpTy *types.Signature + callNoArg *types.Signature + callOneArg *types.Signature needRuntime bool needPyInit bool @@ -385,7 +387,7 @@ func (p Package) rtFunc(fnName string) Expr { return p.NewFunc(name, sig, InGo).Expr } -func (p Package) cpyFunc(fullName string, sig *types.Signature) Expr { +func (p Package) pyFunc(fullName string, sig *types.Signature) Expr { p.Prog.needPyInit = true return p.NewFunc(fullName, sig, InC).Expr } @@ -483,10 +485,31 @@ func (p Program) tyImportPyModule() *types.Signature { 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.cpyFunc("PyImport_ImportModule", b.Prog.tyImportPyModule()) + fnImp := pkg.pyFunc("PyImport_ImportModule", b.Prog.tyImportPyModule()) return b.Call(fnImp, b.CStr(path)) } @@ -500,4 +523,23 @@ func (p Package) NewPyModVar(name string) Global { 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 +} + // -----------------------------------------------------------------------------