cgo: supports c/go callback funcs

This commit is contained in:
Li Jie
2024-11-26 22:34:19 +08:00
parent 5380ffa471
commit 90763de93c
12 changed files with 162 additions and 84 deletions

View File

@@ -77,9 +77,26 @@ static void test_macros() {
#define MY_VERSION "1.0.0"
#define MY_CODE 0x12345678
void test_void() {
static void test_void() {
printf("test_void\n");
}
typedef int (*Cb)(int);
extern int go_callback(int);
extern int c_callback(int i);
static void test_callback(Cb cb) {
printf("test_callback, cb: %p, go_callback: %p, c_callback: %p\n", cb, go_callback, c_callback);
printf("test_callback, *cb: %p, *go_callback: %p, *c_callback: %p\n", *(void**)cb, *(void**)(go_callback), *(void**)(c_callback));
printf("cb result: %d\n", cb(123));
printf("done\n");
}
static void run_callback() {
test_callback(c_callback);
}
*/
import "C"
import (
@@ -90,6 +107,11 @@ import (
"github.com/goplus/llgo/_demo/cgofull/pymod2"
)
//export go_callback
func go_callback(i C.int) C.int {
return i + 1
}
func main() {
runPy()
f := &C.Foo{a: 1}
@@ -104,6 +126,16 @@ func main() {
fmt.Println(C.MY_VERSION)
fmt.Println(int(C.MY_CODE))
C.test_void()
println("call run_callback")
C.run_callback()
// test _Cgo_ptr and _cgoCheckResult
println("call with go_callback")
C.test_callback((C.Cb)(C.go_callback))
println("call with c_callback")
C.test_callback((C.Cb)(C.c_callback))
}
func runPy() {
@@ -112,4 +144,6 @@ func runPy() {
Run("print('Hello, Python!')")
C.PyObject_Print((*C.PyObject)(unsafe.Pointer(pymod1.Float(1.23))), C.stderr, 0)
C.PyObject_Print((*C.PyObject)(unsafe.Pointer(pymod2.Long(123))), C.stdout, 0)
// test _Cgo_use
C.PyObject_Print((*C.PyObject)(unsafe.Pointer(C.PyComplex_FromDoubles(C.double(1.23), C.double(4.56)))), C.stdout, 0)
}

View File

@@ -1,6 +1,12 @@
#include <stdio.h>
#include "foo.h"
void print_foo(Foo* f) {
void print_foo(Foo *f)
{
printf("print_foo: %d\n", f->a);
}
int c_callback(int i)
{
return i + 1;
}

View File

@@ -1,64 +0,0 @@
; ModuleID = 'main'
source_filename = "main"
%"github.com/goplus/llgo/internal/runtime.String" = type { ptr, i64 }
@"main.init$guard" = global i1 false, align 1
@0 = private unnamed_addr constant [5 x i8] c"hello", align 1
@__llgo_argc = global i32 0, align 4
@__llgo_argv = global ptr null, align 8
define i64 @main.Foo(%"github.com/goplus/llgo/internal/runtime.String" %0) {
_llgo_0:
%1 = extractvalue %"github.com/goplus/llgo/internal/runtime.String" %0, 1
ret i64 %1
}
define void @main.Test() {
_llgo_0:
br label %_llgo_3
_llgo_1: ; preds = %_llgo_3
%0 = alloca %"github.com/goplus/llgo/internal/runtime.String", align 8
%1 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %0, i32 0, i32 0
store ptr @0, ptr %1, align 8
%2 = getelementptr inbounds %"github.com/goplus/llgo/internal/runtime.String", ptr %0, i32 0, i32 1
store i64 5, ptr %2, align 4
%3 = load %"github.com/goplus/llgo/internal/runtime.String", ptr %0, align 8
%4 = call i64 @main.Foo(%"github.com/goplus/llgo/internal/runtime.String" %3)
%5 = add i64 %6, 1
br label %_llgo_3
_llgo_2: ; preds = %_llgo_3
ret void
_llgo_3: ; preds = %_llgo_1, %_llgo_0
%6 = phi i64 [ 0, %_llgo_0 ], [ %5, %_llgo_1 ]
%7 = icmp slt i64 %6, 1000000
br i1 %7, label %_llgo_1, label %_llgo_2
}
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
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define i32 @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()
call void @main.Test()
ret i32 0
}
declare void @"github.com/goplus/llgo/internal/runtime.init"()

View File

@@ -1,12 +1,23 @@
; ModuleID = 'main'
source_filename = "main"
%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr }
@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1
@main.format = global [10 x i8] zeroinitializer, align 1
@"main.init$guard" = global i1 false, align 1
@__llgo_argc = global i32 0, align 4
@__llgo_argv = global ptr null, align 8
define ptr @main._Cgo_ptr(ptr %0) {
_llgo_0:
ret ptr %0
}
declare void @runtime.cgoUse(%"github.com/goplus/llgo/internal/runtime.eface")
declare void @runtime.cgoCheckResult(%"github.com/goplus/llgo/internal/runtime.eface")
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"main.init$guard", align 1

View File

@@ -2,6 +2,7 @@
source_filename = "main"
%main.Foo = type { i32, i1 }
%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr }
@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1
@main.format = global [10 x i8] zeroinitializer, align 1
@@ -35,6 +36,15 @@ _llgo_0:
ret void
}
define ptr @main._Cgo_ptr(ptr %0) {
_llgo_0:
ret ptr %0
}
declare void @runtime.cgoUse(%"github.com/goplus/llgo/internal/runtime.eface")
declare void @runtime.cgoCheckResult(%"github.com/goplus/llgo/internal/runtime.eface")
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"main.init$guard", align 1

View File

@@ -1,6 +1,8 @@
; ModuleID = 'main'
source_filename = "main"
%"github.com/goplus/llgo/internal/runtime.eface" = type { ptr, ptr }
@"github.com/goplus/llgo/internal/runtime.cgoAlwaysFalse" = external global i1, align 1
@main.format = global [10 x i8] zeroinitializer, align 1
@"main.init$guard" = global i1 false, align 1
@@ -23,6 +25,15 @@ _llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}
define ptr @main._Cgo_ptr(ptr %0) {
_llgo_0:
ret ptr %0
}
declare void @runtime.cgoUse(%"github.com/goplus/llgo/internal/runtime.eface")
declare void @runtime.cgoCheckResult(%"github.com/goplus/llgo/internal/runtime.eface")
define void @main.init() {
_llgo_0:
%0 = load i1, ptr @"main.init$guard", align 1

View File

@@ -189,7 +189,7 @@ var (
argvTy = types.NewPointer(types.NewPointer(types.Typ[types.Int8]))
)
func isCgoCfuncOrCmacro(f *ssa.Function) bool {
func isCgoExternSymbol(f *ssa.Function) bool {
name := f.Name()
return isCgoCfunc(name) || isCgoCmacro(name)
}
@@ -202,6 +202,10 @@ func isCgoCmacro(name string) bool {
return strings.HasPrefix(name, "_Cmacro_")
}
func isCgoVar(name string) bool {
return strings.HasPrefix(name, "__cgo_")
}
func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) {
pkgTypes, name, ftype := p.funcName(f, true)
if ftype != goFunc {
@@ -255,7 +259,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun
fn.Inline(llssa.NoInline)
}
}
isCgo := isCgoCfuncOrCmacro(f)
isCgo := isCgoExternSymbol(f)
if nblk := len(f.Blocks); nblk > 0 {
p.cgoCalled = false
p.cgoArgs = nil
@@ -918,7 +922,17 @@ func (p *context) compileValue(b llssa.Builder, v ssa.Value) llssa.Expr {
}
return pyFn.Expr
case *ssa.Global:
varName := v.Name()
val := p.varOf(b, v)
if isCgoVar(varName) {
fname := p.fset.Position(v.Pos()).Filename
funcs, ok := p.cgoFuncs[fname]
if !ok {
funcs = make([]string, 0, 1)
}
funcs = append(funcs, val.Name())
p.cgoFuncs[fname] = funcs
}
if debugSymbols {
pos := p.fset.Position(v.Pos())
b.DIGlobal(val, v.Name(), pos)
@@ -1053,6 +1067,26 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
fn()
}
externs = ctx.cgoFuncs
// TODO(lijie): read export name
for _, funcs := range externs {
for _, funcName := range funcs {
if strings.Contains(funcName, ".__cgo_") {
goFnName := strings.Replace(funcName, ".__cgo_", ".", 1)
idx := strings.LastIndex(funcName, ".__cgo_")
cfuncName := funcName[idx+len(".__cgo_"):]
v := ret.VarOf(funcName)
if fn := ret.FuncOf(goFnName); fn != nil {
// TODO(lijie): naive go:export, need better way from comment
fn.SetName(cfuncName)
// Replace symbol instead of static linking
v.ReplaceAllUsesWith(fn.Expr)
} else if fn := ret.FuncOf(cfuncName); fn != nil {
// Replace symbol instead of static linking
v.ReplaceAllUsesWith(fn.Expr)
}
}
}
}
return
}

View File

@@ -448,7 +448,7 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin
if checkCgo(fname) && !cgoIgnored(fname) {
return nil, fname, llgoInstr
}
if isCgoCfuncOrCmacro(fn) {
if isCgoExternSymbol(fn) {
if _, ok := llgoInstrs[fname]; ok {
return nil, fname, llgoInstr
}

View File

@@ -525,6 +525,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoLdflags []string,
cl.SetDebug(0)
}
check(err)
aPkg.LPkg = ret
cgoLdflags, err = buildCgo(ctx, aPkg, aPkg.Package.Syntax, externs, verbose)
if needLLFile(ctx.mode) {
pkg.ExportFile += ".ll"
@@ -533,7 +534,6 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) (cgoLdflags []string,
fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile)
}
}
aPkg.LPkg = ret
return
}

View File

@@ -21,6 +21,7 @@ import (
"fmt"
"go/ast"
"go/token"
"go/types"
"os"
"os/exec"
"path/filepath"
@@ -29,6 +30,7 @@ import (
"github.com/goplus/llgo/internal/buildtags"
"github.com/goplus/llgo/internal/safesplit"
llssa "github.com/goplus/llgo/ssa"
)
type cgoDecl struct {
@@ -93,12 +95,21 @@ func buildCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string
mallocFix := false
for _, symbols := range externs {
for _, symbolName := range symbols {
if m := re.FindStringSubmatch(symbolName); len(m) > 0 {
cgoSymbols[symbolName] = m[3]
pkgPrefix := m[1]
lastPart := symbolName
lastDot := strings.LastIndex(symbolName, ".")
if lastDot != -1 {
lastPart = symbolName[lastDot+1:]
}
if strings.HasPrefix(lastPart, "__cgo_") {
// func ptr var: main.__cgo_func_name
cgoSymbols[symbolName] = lastPart
} else if m := re.FindStringSubmatch(symbolName); len(m) > 0 {
prefix := m[1] // _cgo_hash_(Cfunc|Cmacro)_
name := m[3] // remaining part
cgoSymbols[symbolName] = name
// fix missing _cgo_9113e32b6599_Cfunc__Cmalloc
if !mallocFix && m[2] == "Cfunc" {
mallocName := pkgPrefix + "_Cmalloc"
mallocName := prefix + "_Cmalloc"
cgoSymbols[mallocName] = "_Cmalloc"
mallocFix = true
}
@@ -113,7 +124,7 @@ func buildCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string
tmpName := tmpFile.Name()
defer os.Remove(tmpName)
code := cgoHeader + "\n\n" + preamble.src
externDecls, err := genExternDeclsByClang(code, cflags, cgoSymbols)
externDecls, err := genExternDeclsByClang(pkg, code, cflags, cgoSymbols)
if err != nil {
return nil, err
}
@@ -137,7 +148,7 @@ type clangASTNode struct {
Inner []clangASTNode `json:"inner,omitempty"`
}
func genExternDeclsByClang(src string, cflags []string, cgoSymbols map[string]string) (string, error) {
func genExternDeclsByClang(pkg *aPackage, src string, cflags []string, cgoSymbols map[string]string) (string, error) {
tmpSrc, err := os.CreateTemp("", "cgo-src-*.c")
if err != nil {
return "", err
@@ -158,25 +169,37 @@ func genExternDeclsByClang(src string, cflags []string, cgoSymbols map[string]st
b := strings.Builder{}
var toRemove []string
for cgoName, symbolName := range cgoSymbols {
if symbolNames[symbolName] {
b.WriteString(fmt.Sprintf("void* %s = (void*)%s;\n", cgoName, symbolName))
if strings.HasPrefix(symbolName, "__cgo_") {
cfuncName := symbolName[len("__cgo_"):]
cfn := pkg.LPkg.NewFunc(cfuncName, types.NewSignature(nil, nil, nil, false), llssa.InC)
cgoVar := pkg.LPkg.VarOf(cgoName)
cgoVar.ReplaceAllUsesWith(cfn.Expr)
toRemove = append(toRemove, cgoName)
} else if macroNames[symbolName] {
} else {
usePtr := ""
if symbolNames[symbolName] {
usePtr = "*"
} else if !macroNames[symbolName] {
continue
}
/* template:
typeof(stdout) _cgo_1574167f3838_Cmacro_stdout;
typeof(fputs)* _cgo_1574167f3838_Cfunc_fputs;
__attribute__((constructor))
static void _init__cgo_1574167f3838_Cmacro_stdout() {
_cgo_1574167f3838_Cmacro_stdout = stdout;
static void _init__cgo_1574167f3838_Cfunc_fputs() {
_cgo_1574167f3838_Cfunc_fputs = fputs;
}*/
b.WriteString(fmt.Sprintf(`
typeof(%s) %s;
typeof(%s)%s %s;
__attribute__((constructor))
static void _init_%s() {
%s = %s;
}
`, symbolName, cgoName, cgoName, cgoName, symbolName))
`,
symbolName, usePtr, cgoName,
cgoName,
cgoName, symbolName))
toRemove = append(toRemove, cgoName)
}
}

View File

@@ -117,6 +117,10 @@ func (g Global) InitNil() {
g.impl.SetInitializer(llvm.ConstNull(g.impl.GlobalValueType()))
}
func (g Global) ReplaceAllUsesWith(v Expr) {
g.impl.ReplaceAllUsesWith(v.impl)
}
// -----------------------------------------------------------------------------
// Function represents the parameters, results, and code of a function

View File

@@ -59,6 +59,15 @@ func (v Expr) SetOrdering(ordering AtomicOrdering) Expr {
return v
}
func (v Expr) SetName(alias string) Expr {
v.impl.SetName(alias)
return v
}
func (v Expr) Name() string {
return v.impl.Name()
}
// -----------------------------------------------------------------------------
type builtinTy struct {