From d6c527f662bc5d2d2246c92672883578a6aee236 Mon Sep 17 00:00:00 2001 From: Li Jie Date: Sun, 24 Nov 2024 22:45:25 +0800 Subject: [PATCH] cgo: support macros --- _demo/cgomacro/cgomacro.go | 31 ++++++++++ cl/compile.go | 60 ++++++++++++-------- cl/import.go | 5 +- cl/instr.go | 8 --- internal/build/cgo.go | 113 +++++++++++++++++++++++++++---------- 5 files changed, 153 insertions(+), 64 deletions(-) create mode 100644 _demo/cgomacro/cgomacro.go diff --git a/_demo/cgomacro/cgomacro.go b/_demo/cgomacro/cgomacro.go new file mode 100644 index 00000000..21bb8737 --- /dev/null +++ b/_demo/cgomacro/cgomacro.go @@ -0,0 +1,31 @@ +package main + +/* +#cgo pkg-config: python3-embed +#include +#include + +void test_stdout() { + printf("stdout ptr: %p\n", stdout); + fputs("outputs to stdout\n", stdout); +} +*/ +import "C" +import ( + "unsafe" + + "github.com/goplus/llgo/c" +) + +func main() { + C.test_stdout() + C.fputs((*C.char)(unsafe.Pointer(c.Str("hello\n"))), C.stdout) + C.Py_Initialize() + defer C.Py_Finalize() + C.PyObject_Print(C.Py_True, C.stdout, 0) + C.fputs((*C.char)(unsafe.Pointer(c.Str("\n"))), C.stdout) + C.PyObject_Print(C.Py_False, C.stdout, 0) + C.fputs((*C.char)(unsafe.Pointer(c.Str("\n"))), C.stdout) + C.PyObject_Print(C.Py_None, C.stdout, 0) + C.fputs((*C.char)(unsafe.Pointer(c.Str("\n"))), C.stdout) +} diff --git a/cl/compile.go b/cl/compile.go index f4949b74..8c9eca46 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -189,8 +189,17 @@ var ( argvTy = types.NewPointer(types.NewPointer(types.Typ[types.Int8])) ) -func isCgoCfunc(f *ssa.Function) bool { - return strings.HasPrefix(f.Name(), "_Cfunc_") +func isCgoCfuncOrCmacro(f *ssa.Function) bool { + name := f.Name() + return isCgoCfunc(name) || isCgoCmacro(name) +} + +func isCgoCfunc(name string) bool { + return strings.HasPrefix(name, "_Cfunc_") +} + +func isCgoCmacro(name string) bool { + return strings.HasPrefix(name, "_Cmacro_") } func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) { @@ -246,10 +255,11 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun fn.Inline(llssa.NoInline) } } + isCgo := isCgoCfuncOrCmacro(f) if nblk := len(f.Blocks); nblk > 0 { p.cgoCalled = false p.cgoArgs = nil - if isCgoCfunc(f) { + if isCgo { fn.MakeBlocks(1) } else { fn.MakeBlocks(nblk) // to set fn.HasBody() = true @@ -278,7 +288,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun } p.bvals = make(map[ssa.Value]llssa.Expr) off := make([]int, len(f.Blocks)) - if isCgoCfunc(f) { + if isCgo { p.cgoArgs = make([]llssa.Expr, len(f.Params)) for i, param := range f.Params { p.cgoArgs[i] = p.compileValue(b, param) @@ -295,7 +305,7 @@ func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Fun doMainInit := (i == 0 && name == "main") doModInit := (i == 1 && isInit) p.compileBlock(b, block, off[i], doMainInit, doModInit) - if isCgoCfunc(f) { + if isCgo { // just process first block for performance break } @@ -409,36 +419,46 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do if funcs, ok := p.cgoFuncs[fname]; ok { cgoFuncs = funcs } + fnName := block.Parent().Name() cgoReturned := false + isCgoCfunc := isCgoCfunc(fnName) + isCgoCmacro := isCgoCmacro(fnName) for i, instr := range instrs { if i == 1 && doModInit && p.state == pkgInPatch { // in patch package but no pkgFNoOldInit initFnNameOld := initFnNameOfHasPatch(p.fn.Name()) fnOld := pkg.NewFunc(initFnNameOld, llssa.NoArgsNoRet, llssa.InC) b.Call(fnOld.Expr) } - if isCgoCfunc(block.Parent()) { + if isCgoCfunc || isCgoCmacro { switch instr := instr.(type) { case *ssa.Alloc: // return value allocation p.compileInstr(b, instr) case *ssa.UnOp: // load cgo function pointer - if instr.Op == token.MUL && strings.HasPrefix(instr.X.Name(), "_cgo_") { - cgoFuncs = append(cgoFuncs, instr.X.Name()) + varName := instr.X.Name() + if instr.Op == token.MUL && strings.HasPrefix(varName, "_cgo_") { + cgoFuncs = append(cgoFuncs, varName) p.cgoFuncs[fname] = cgoFuncs p.compileInstr(b, instr) } case *ssa.Call: - // call c function - p.compileInstr(b, instr) - p.cgoCalled = true + if isCgoCmacro { + p.cgoRet = p.compileValue(b, instr.Call.Args[0]) + p.cgoCalled = true + } else { + // call c function + p.compileInstr(b, instr) + p.cgoCalled = true + } case *ssa.Return: // return cgo function result - if len(instr.Results) > 0 { - b.Return(p.cgoRet) - } else { - b.Return(llssa.Nil) + if isCgoCmacro { + ty := p.type_(instr.Results[0].Type(), llssa.InGo) + p.cgoRet.Type = p.prog.Pointer(ty) + p.cgoRet = b.Load(p.cgoRet) } + b.Return(p.cgoRet) cgoReturned = true } } else { @@ -446,18 +466,14 @@ func (p *context) compileBlock(b llssa.Builder, block *ssa.BasicBlock, n int, do } } // is cgo cfunc but not return yet, some funcs has multiple blocks - if isCgoCfunc(block.Parent()) && !cgoReturned { + if (isCgoCfunc || isCgoCmacro) && !cgoReturned { if !p.cgoCalled { panic("cgo cfunc not called") } for _, block := range block.Parent().Blocks { for _, instr := range block.Instrs { - if instr, ok := instr.(*ssa.Return); ok { - if len(instr.Results) > 0 { - b.Return(p.cgoRet) - } else { - b.Return(llssa.Nil) - } + if _, ok := instr.(*ssa.Return); ok { + b.Return(p.cgoRet) goto end } } diff --git a/cl/import.go b/cl/import.go index fd241849..8c1f7d3b 100644 --- a/cl/import.go +++ b/cl/import.go @@ -421,7 +421,6 @@ const ( llgoCgoCMalloc = llgoCgoBase + 0x5 llgoCgoCheckPointer = llgoCgoBase + 0x6 llgoCgoCgocall = llgoCgoBase + 0x7 - llgoCgoUse = llgoCgoBase + 0x8 llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin) ) @@ -438,7 +437,7 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin if checkCgo(fname) { return nil, fname, llgoInstr } - if isCgoCfunc(fn) { + if isCgoCfuncOrCmacro(fn) { if _, ok := llgoInstrs[fname]; ok { return nil, fname, llgoInstr } @@ -450,7 +449,7 @@ func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, strin } p.ensureLoaded(pkg) orgName = funcName(pkg, fn, false) - if ignore && ignoreName(orgName) || checkCgo(fn.Name()) { + if ignore && ignoreName(orgName) { return nil, orgName, ignoredFunc } } diff --git a/cl/instr.go b/cl/instr.go index 6ae03ea7..e9c9effa 100644 --- a/cl/instr.go +++ b/cl/instr.go @@ -130,11 +130,6 @@ func (p *context) cgoCgocall(b llssa.Builder, args []ssa.Value) (ret llssa.Expr) return p.cgoRet } -// func _Cgo_use(v any) -func (p *context) cgoUse(b llssa.Builder, args []ssa.Value) { - // don't need to do anything -} - // ----------------------------------------------------------------------------- // func index(arr *T, idx int) T @@ -324,7 +319,6 @@ var llgoInstrs = map[string]int{ "_Cfunc__CMalloc": llgoCgoCMalloc, "_cgoCheckPointer": llgoCgoCheckPointer, "_cgo_runtime_cgocall": llgoCgoCgocall, - "_Cgo_use": llgoCgoUse, } // funcOf returns a function by name and set ftype = goFunc, cFunc, etc. @@ -468,8 +462,6 @@ func (p *context) call(b llssa.Builder, act llssa.DoAction, call *ssa.CallCommon p.cgoCheckPointer(b, args) case llgoCgoCgocall: p.cgoCgocall(b, args) - case llgoCgoUse: - p.cgoUse(b, args) case llgoAdvance: ret = p.advance(b, args) case llgoIndex: diff --git a/internal/build/cgo.go b/internal/build/cgo.go index 9615a73b..ddbeb376 100644 --- a/internal/build/cgo.go +++ b/internal/build/cgo.go @@ -88,18 +88,18 @@ func buildCgo(ctx *context, pkg *aPackage, files []*ast.File, externs map[string cgoLdflags = append(cgoLdflags, linkFile) }, verbose) } - re := regexp.MustCompile(`^(_cgo_[^_]+_Cfunc_)(.*)$`) - cgoFuncs := make(map[string]string) + re := regexp.MustCompile(`^(_cgo_[^_]+_(Cfunc|Cmacro)_)(.*)$`) + cgoSymbols := make(map[string]string) mallocFix := false - for _, funcs := range externs { - for _, funcName := range funcs { - if m := re.FindStringSubmatch(funcName); len(m) > 0 { - cgoFuncs[funcName] = m[2] + for _, symbols := range externs { + for _, symbolName := range symbols { + if m := re.FindStringSubmatch(symbolName); len(m) > 0 { + cgoSymbols[symbolName] = m[3] + pkgPrefix := m[1] // fix missing _cgo_9113e32b6599_Cfunc__Cmalloc - if !mallocFix { - pkgPrefix := m[1] + if !mallocFix && m[2] == "Cfunc" { mallocName := pkgPrefix + "_Cmalloc" - cgoFuncs[mallocName] = "_Cmalloc" + cgoSymbols[mallocName] = "_Cmalloc" mallocFix = true } } @@ -113,7 +113,10 @@ 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 := genExternDeclsByClang(code, cflags, cgoFuncs) + externDecls, err := genExternDeclsByClang(code, cflags, cgoSymbols) + if err != nil { + return nil, err + } if err = os.WriteFile(tmpName, []byte(code+"\n\n"+externDecls), 0644); err != nil { return nil, err } @@ -134,42 +137,90 @@ type clangASTNode struct { Inner []clangASTNode `json:"inner,omitempty"` } -func genExternDeclsByClang(src string, cflags []string, cgoFuncs map[string]string) string { +func genExternDeclsByClang(src string, cflags []string, cgoSymbols map[string]string) (string, error) { tmpSrc, err := os.CreateTemp("", "cgo-src-*.c") if err != nil { - return "" + return "", err } defer os.Remove(tmpSrc.Name()) if err := os.WriteFile(tmpSrc.Name(), []byte(src), 0644); err != nil { - return "" + return "", err } - args := append([]string{"-Xclang", "-ast-dump=json", "-fsyntax-only"}, cflags...) - args = append(args, tmpSrc.Name()) - cmd := exec.Command("clang", args...) - output, err := cmd.Output() - if err != nil { - return "" + symbolNames := make(map[string]bool) + if err := getFuncNames(tmpSrc.Name(), cflags, symbolNames); err != nil { + return "", err } - var astRoot clangASTNode - if err := json.Unmarshal(output, &astRoot); err != nil { - return "" + macroNames := make(map[string]bool) + if err := getMacroNames(tmpSrc.Name(), cflags, macroNames); err != nil { + return "", err } - funcNames := make(map[string]bool) - extractFuncNames(&astRoot, funcNames) - b := strings.Builder{} var toRemove []string - for cgoFunc, funcName := range cgoFuncs { - if funcNames[funcName] { - b.WriteString(fmt.Sprintf("void* %s = (void*)%s;\n", cgoFunc, funcName)) - toRemove = append(toRemove, cgoFunc) + for cgoName, symbolName := range cgoSymbols { + if symbolNames[symbolName] { + b.WriteString(fmt.Sprintf("void* %s = (void*)%s;\n", cgoName, symbolName)) + toRemove = append(toRemove, cgoName) + } else if macroNames[symbolName] { + /* template: + typeof(stdout) _cgo_1574167f3838_Cmacro_stdout; + + __attribute__((constructor)) + static void _init__cgo_1574167f3838_Cmacro_stdout() { + _cgo_1574167f3838_Cmacro_stdout = stdout; + }*/ + b.WriteString(fmt.Sprintf(` +typeof(%s) %s; + +__attribute__((constructor)) +static void _init_%s() { + %s = %s; +} +`, symbolName, cgoName, cgoName, cgoName, symbolName)) + toRemove = append(toRemove, cgoName) } } for _, funcName := range toRemove { - delete(cgoFuncs, funcName) + delete(cgoSymbols, funcName) } - return b.String() + return b.String(), nil +} + +func getMacroNames(file string, cflags []string, macroNames map[string]bool) error { + args := append([]string{"-dM", "-E"}, cflags...) + args = append(args, file) + cmd := exec.Command("clang", args...) + output, err := cmd.Output() + if err != nil { + return err + } + for _, line := range strings.Split(string(output), "\n") { + if strings.HasPrefix(line, "#define ") { + define := strings.TrimPrefix(line, "#define ") + parts := strings.SplitN(define, " ", 2) + if len(parts) > 1 { + macroNames[parts[0]] = true + } + } + } + return nil +} + +func getFuncNames(file string, cflags []string, symbolNames map[string]bool) error { + args := append([]string{"-Xclang", "-ast-dump=json", "-fsyntax-only"}, cflags...) + args = append(args, file) + cmd := exec.Command("clang", args...) + output, err := cmd.Output() + if err != nil { + return err + } + var astRoot clangASTNode + if err := json.Unmarshal(output, &astRoot); err != nil { + return err + } + + extractFuncNames(&astRoot, symbolNames) + return nil } func extractFuncNames(node *clangASTNode, funcNames map[string]bool) {