From 21e7aca52741ef85e0d953ba78d6607a23b19c1c Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 16 Sep 2026 08:20:06 +0800 Subject: [PATCH 1/5] cl Config: defaultNameLookup --- cl/compile.go | 28 +++++++++++++++++++++------- cl/compile_test.go | 2 +- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index 523314d2..b9d8ec80 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -63,30 +63,33 @@ const ( // Config specifies the configuration for compiling header files. type Config struct { - // Fset provides source position information for syntax trees and types. + // Fset provides source position information for syntax trees and types (optional). // If Fset is nil, Load will use a new fileset, but preserve Fset's value. Fset *token.FileSet - // An Importer resolves import paths to Packages. + // An Importer resolves import paths to Packages (optional). Importer types.Importer // LLGoPackage specifies the value of the LLGoPackage constant in the generated - // Go package. + // Go package (optional). LLGoPackage string - // Language specifies the programming language of the header file. + // Language specifies the programming language of the header file (default LanguageC). Language Language // CFlags specifies the compiler flags to be used when compiling the wrapper file. + // If not specified, llcppg will skip wrapping inline functions/methods. CFlags string // NameLookup looks up the archive path for a given mangling name. It returns the - // archive path and a boolean indicating whether the lookup was successful. + // archive path and a boolean indicating whether the lookup was successful. If not + // specified, llcppg use a default lookup function that returns an empty archivePath + // and true (it means any mangling name is considered found). NameLookup func(manglingName string) (archivePath string, ok bool) // PresumedFiles specifies the list of files that are presumed to be included in the // compilation. This is used to determine which files are considered part of the - // package being compiled. + // package being compiled (optional). PresumedFiles []string } @@ -100,6 +103,9 @@ const ( // using the provided configuration and translation unit. func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, files ...string) (ret Package, err error) { interp := &nodeInterp{} + if conf == nil { + conf = &Config{} + } confGox := &gogen.Config{ Fset: conf.Fset, Importer: conf.Importer, @@ -123,9 +129,13 @@ func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, } c := pkg.Import("github.com/goplus/lib/c") + nameLookup := conf.NameLookup + if nameLookup == nil { + nameLookup = defaultNameLookup + } ctx := &pkgCtx{ pkg: pkg, cb: pkg.CB(), llgo: llgo, fset: pkg.Fset, tu: tu, c: c, - lang: conf.Language, cflags: conf.CFlags, nameLookup: conf.NameLookup, + lang: conf.Language, cflags: conf.CFlags, nameLookup: nameLookup, methods: make(map[string]*classMethod), } ctx.initFiles(files) @@ -134,6 +144,10 @@ func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, return } +func defaultNameLookup(manglingName string) (archivePath string, ok bool) { + return "", true +} + // ----------------------------------------------------------------------------- func loadFiles(ctx *pkgCtx) { diff --git a/cl/compile_test.go b/cl/compile_test.go index 91cf20e7..3b3107db 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -71,7 +71,7 @@ func testFromDir(t *testing.T, sel, relDir string, lang cl.Language) { LLGoPackage: conf.LLGoPackage, Language: lang, CFlags: conf.CFlags, - NameLookup: cltest.MockNameLookup, + NameLookup: nil, }, u, filename) if err != nil { t.Error("cl.NewPackage:", err) From c9dbbf33924437fdfce1c2de8d86b0d87612484a Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 16 Sep 2026 08:33:44 +0800 Subject: [PATCH 2/5] cl: class ctor/dtor --- cl/_testcpp/ctor_dtor/in.h | 7 +++++++ cl/_testcpp/ctor_dtor/out.go | 20 ++++++++++++++++++++ cl/class.go | 11 ++++++++++- cl/ctx.go | 3 +-- cl/func.go | 3 ++- 5 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 cl/_testcpp/ctor_dtor/in.h create mode 100644 cl/_testcpp/ctor_dtor/out.go diff --git a/cl/_testcpp/ctor_dtor/in.h b/cl/_testcpp/ctor_dtor/in.h new file mode 100644 index 00000000..b1055b16 --- /dev/null +++ b/cl/_testcpp/ctor_dtor/in.h @@ -0,0 +1,7 @@ +class bar +{ +public: + bar(int a); + bar(); + ~bar(); +}; diff --git a/cl/_testcpp/ctor_dtor/out.go b/cl/_testcpp/ctor_dtor/out.go new file mode 100644 index 00000000..ae1be811 --- /dev/null +++ b/cl/_testcpp/ctor_dtor/out.go @@ -0,0 +1,20 @@ +package foo + +import "github.com/goplus/lib/c" + +const XGoPackage = true + +type Bar struct { +} + +// llgo:link (*Bar).XGo_Ctor__1 C._ZN3barC1Ei +func (this *Bar) XGo_Ctor__1(a c.Int) { +} + +// llgo:link (*Bar).XGo_Ctor__0 C._ZN3barC1Ev +func (this *Bar) XGo_Ctor__0() { +} + +// llgo:link (*Bar).XGo_Dtor C._ZN3barD1Ev +func (this *Bar) XGo_Dtor() { +} diff --git a/cl/class.go b/cl/class.go index 6b175d1f..69ae85a8 100644 --- a/cl/class.go +++ b/cl/class.go @@ -84,7 +84,16 @@ func loadClass(ctx *pkgCtx, cls clang.Cursor, defaultInPublic bool) { func loadClassMember(ctx *pkgCtx, pkg *types.Package, cls *classCtx, decl clang.Cursor) { switch decl.Kind { case lc.CursorCXXMethod, lc.CursorConstructor, lc.CursorDestructor: - obj := cls.addObject(decl) + var name string + switch decl.Kind { + default: + name = clang.String(decl) + case lc.CursorConstructor: + name = "XGo_Ctor" + case lc.CursorDestructor: + name = "XGo_Dtor" + } + obj := cls.addObject(name, decl) manglingName := clang.Mangling(decl) isPublic := cls.inPublic method := &classMethod{obj: obj, manglingName: manglingName, isPublic: isPublic} diff --git a/cl/ctx.go b/cl/ctx.go index e3641d0f..1544670d 100644 --- a/cl/ctx.go +++ b/cl/ctx.go @@ -208,8 +208,7 @@ type scopeCtx struct { overloads map[string]*overloads // name => overload items } -func (p *scopeCtx) addObject(decl clang.Cursor) *object { - name := clang.String(decl) +func (p *scopeCtx) addObject(name string, decl clang.Cursor) *object { obj := &object{ name: name, decl: decl, diff --git a/cl/func.go b/cl/func.go index f965a985..344d7e03 100644 --- a/cl/func.go +++ b/cl/func.go @@ -30,7 +30,8 @@ import ( // ----------------------------------------------------------------------------- func loadGlobalFunc(ctx *pkgCtx, scope *scopeCtx, decl clang.Cursor) { - obj := scope.addObject(decl) + name := clang.String(decl) + obj := scope.addObject(name, decl) ctx.compiles = append(ctx.compiles, func(ctx *pkgCtx) { compileFuncOrMethod(ctx, decl, obj, nil) }) From c2783fbe6ea56e36ab878252a26e31c7cbb0727a Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 16 Sep 2026 08:44:33 +0800 Subject: [PATCH 3/5] cl: NewPackage doc --- cl/compile.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cl/compile.go b/cl/compile.go index b9d8ec80..1f102a0b 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -61,7 +61,7 @@ const ( // ----------------------------------------------------------------------------- -// Config specifies the configuration for compiling header files. +// Config specifies the configuration for llcppg. type Config struct { // Fset provides source position information for syntax trees and types (optional). // If Fset is nil, Load will use a new fileset, but preserve Fset's value. @@ -99,8 +99,8 @@ const ( headerGoFile = "llcppg.i.go" ) -// NewPackage creates a new Package instance for the given package path and name, -// using the provided configuration and translation unit. +// NewPackage loads a translation unit and generates a Go package with the given package +// path, name and configuration. func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, files ...string) (ret Package, err error) { interp := &nodeInterp{} if conf == nil { @@ -140,6 +140,7 @@ func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, } ctx.initFiles(files) loadFiles(ctx) + ctx.compile() ret.Package = pkg return } @@ -159,7 +160,6 @@ func loadFiles(ctx *pkgCtx) { return clang.Continue }) scope.reorder() - ctx.compile() } func loadDecl(ctx *pkgCtx, scope *scopeCtx, decl clang.Cursor) { From 5fa65facb28e7e03ec3dce1ba0c0547148de3159 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 16 Sep 2026 08:48:14 +0800 Subject: [PATCH 4/5] cl doc: small fix --- cl/class.go | 4 ++-- cl/compile.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cl/class.go b/cl/class.go index 69ae85a8..ed70c819 100644 --- a/cl/class.go +++ b/cl/class.go @@ -86,12 +86,12 @@ func loadClassMember(ctx *pkgCtx, pkg *types.Package, cls *classCtx, decl clang. case lc.CursorCXXMethod, lc.CursorConstructor, lc.CursorDestructor: var name string switch decl.Kind { - default: - name = clang.String(decl) case lc.CursorConstructor: name = "XGo_Ctor" case lc.CursorDestructor: name = "XGo_Dtor" + default: + name = clang.String(decl) } obj := cls.addObject(name, decl) manglingName := clang.Mangling(decl) diff --git a/cl/compile.go b/cl/compile.go index 1f102a0b..168d5507 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -83,7 +83,7 @@ type Config struct { // NameLookup looks up the archive path for a given mangling name. It returns the // archive path and a boolean indicating whether the lookup was successful. If not - // specified, llcppg use a default lookup function that returns an empty archivePath + // specified, llcppg uses a default lookup function that returns an empty archivePath // and true (it means any mangling name is considered found). NameLookup func(manglingName string) (archivePath string, ok bool) From 5e14ffeb83bf6c8c42b88d31991cedf8ca56bdf5 Mon Sep 17 00:00:00 2001 From: xushiwei Date: Wed, 16 Sep 2026 10:40:32 +0800 Subject: [PATCH 5/5] cl wrapInlineFunc: write c func prototype --- .gitignore | 1 + cl/_testc/inline/out.go | 2 +- cl/_testc/inline/wrap.c | 5 ++ cl/_testcpp/inline/out.go | 2 +- cl/_testcpp/inline/wrap.cpp | 5 ++ cl/compile.go | 2 + cl/compile_test.go | 12 ++++- cl/ctx.go | 2 +- cl/func.go | 2 +- cl/wrap.go | 93 +++++++++++++++++++++++++++++++++---- 10 files changed, 112 insertions(+), 14 deletions(-) create mode 100644 cl/_testc/inline/wrap.c create mode 100644 cl/_testcpp/inline/wrap.cpp diff --git a/.gitignore b/.gitignore index 94e38187..457404ce 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,7 @@ *.so *.dylib *.DS_Store +*.txt _temp/ _c2go/ diff --git a/cl/_testc/inline/out.go b/cl/_testc/inline/out.go index 52368dea..d18c6072 100644 --- a/cl/_testc/inline/out.go +++ b/cl/_testc/inline/out.go @@ -7,7 +7,7 @@ import ( const ( LLGoPackage = "link: -L/path/foo -lfoo" - LLGoFiles = "-I/path/foo/include: _wrap/foo.c" + LLGoFiles = "-I/path/foo/include: _wrap/llcppg.c" ) //go:linkname Add C._llcppg_add diff --git a/cl/_testc/inline/wrap.c b/cl/_testc/inline/wrap.c new file mode 100644 index 00000000..8d0c030e --- /dev/null +++ b/cl/_testc/inline/wrap.c @@ -0,0 +1,5 @@ +int _llcppg_add(int a, int b) { +} + +int _llcppg_mul(int a, int b) { +} diff --git a/cl/_testcpp/inline/out.go b/cl/_testcpp/inline/out.go index 843e8e73..f623af05 100644 --- a/cl/_testcpp/inline/out.go +++ b/cl/_testcpp/inline/out.go @@ -4,7 +4,7 @@ import "github.com/goplus/lib/c" const ( LLGoPackage = "link: -L/path/foo -lfoo" - LLGoFiles = "-I/path/foo/include: _wrap/foo.cpp" + LLGoFiles = "-I/path/foo/include: _wrap/llcppg.cpp" ) type Bar struct { diff --git a/cl/_testcpp/inline/wrap.cpp b/cl/_testcpp/inline/wrap.cpp new file mode 100644 index 00000000..eb67aeff --- /dev/null +++ b/cl/_testcpp/inline/wrap.cpp @@ -0,0 +1,5 @@ +unsigned int _llcppg__ZN3bar1fEi(int a) { +} + +void _llcppg__ZN3bar2_gEv() { +} diff --git a/cl/compile.go b/cl/compile.go index 168d5507..ad7fe05d 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -47,6 +47,7 @@ func SetDebug(flags int) { // Package represents a generated Go package. type Package struct { *gogen.Package + Wrap *WrapFile } // ----------------------------------------------------------------------------- @@ -142,6 +143,7 @@ func NewPackage(pkgPath, pkgName string, conf *Config, tu clang.TranslationUnit, loadFiles(ctx) ctx.compile() ret.Package = pkg + ret.Wrap = ctx.wrap return } diff --git a/cl/compile_test.go b/cl/compile_test.go index 3b3107db..1f1c2d99 100644 --- a/cl/compile_test.go +++ b/cl/compile_test.go @@ -52,7 +52,7 @@ func testGenGo(t *testing.T, pkg *gogen.Package, dir string, exp any) { if err != nil { t.Fatal("gogen.WriteTo failed:", err) } - testDiff(t, dir, "/result.txt", &b, exp) + testDiff(t, dir, "/out.txt", &b, exp) } func testFromDir(t *testing.T, sel, relDir string, lang cl.Language) { @@ -79,9 +79,19 @@ func testFromDir(t *testing.T, sel, relDir string, lang cl.Language) { } exp, _ := os.ReadFile(pkgDir + "/out.go") testGenGo(t, pkg.Package, pkgDir, exp) + wrapFile := "/wrap" + langExts[lang] + wrap, _ := os.ReadFile(pkgDir + wrapFile) + if pkg.Wrap != nil { + testDiff(t, pkgDir, wrapFile+".txt", &pkg.Wrap.Content, wrap) + } }) } +var langExts = [...]string{ + cl.LanguageC: ".c", + cl.LanguageCXX: ".cpp", +} + func TestC(t *testing.T) { testFromDir(t, "", "./_testc", cl.LanguageC) } diff --git a/cl/ctx.go b/cl/ctx.go index 1544670d..3779879b 100644 --- a/cl/ctx.go +++ b/cl/ctx.go @@ -90,7 +90,7 @@ type pkgCtx struct { pkg *gogen.Package cb *gogen.CodeBuilder llgo *gogen.ConstDefs - wrap *wrapFile + wrap *WrapFile fset *token.FileSet tu clang.TranslationUnit c gogen.PkgRef diff --git a/cl/func.go b/cl/func.go index 344d7e03..50a24add 100644 --- a/cl/func.go +++ b/cl/func.go @@ -47,7 +47,7 @@ func compileFuncOrMethod(ctx *pkgCtx, fn clang.Cursor, obj *object, typNamed *ty } return } - manglingName = wrapInlineFunc(ctx, manglingName, origName, fn) + manglingName = wrapInlineFunc(ctx, manglingName, fn) } else if _, ok := ctx.nameLookup(manglingName); !ok { if debugCompileDecl { log.Println("func", origName, "- skipped") diff --git a/cl/wrap.go b/cl/wrap.go index 02cd37ba..8685a6be 100644 --- a/cl/wrap.go +++ b/cl/wrap.go @@ -17,15 +17,20 @@ package cl import ( + "bytes" "go/token" "github.com/goplus/gogen" + "github.com/goplus/lib/c" "github.com/goplus/llcppg/clang" + lc "github.com/goplus/llcppg/lib/clang" ) // ----------------------------------------------------------------------------- -type wrapFile struct { +type WrapFile struct { + Filename string + Content bytes.Buffer } var langExts = [...]string{ @@ -33,26 +38,96 @@ var langExts = [...]string{ LanguageCXX: ".cpp", } -func newWrapFile(ctx *pkgCtx) *wrapFile { +func newWrapFile(ctx *pkgCtx) *WrapFile { ext := langExts[ctx.lang] - filename := "_wrap/" + ctx.pkg.Types.Name() + ext + filename := "_wrap/llcppg" + ext llgoFiles := ctx.cflags + ": " + filename ctx.llgo.New(func(cb *gogen.CodeBuilder) int { cb.Val(llgoFiles) return 1 }, 0, token.NoPos, nil, "LLGoFiles") - return &wrapFile{} + return &WrapFile{Filename: filename} } -func wrapInlineFunc(ctx *pkgCtx, manglingName, origName string, fn clang.Cursor) string { - if ctx.wrap == nil { +func wrapInlineFunc(ctx *pkgCtx, manglingName string, fn clang.Cursor) string { + first := ctx.wrap == nil + if first { ctx.wrap = newWrapFile(ctx) } + w := &ctx.wrap.Content + if !first { + w.WriteByte('\n') + } wrapName := "_llcppg_" + manglingName - // TODO(xsw): wrap inline func - _ = fn - _ = origName + writeFunc(w, wrapName, fn) return wrapName } // ----------------------------------------------------------------------------- + +type writerT = bytes.Buffer + +func writeFunc(b *writerT, name string, fn clang.Cursor) { + writeFuncProto(b, name, fn) + b.WriteString(` { +} +`) +} + +func writeFuncProto(out *writerT, name string, fn clang.Cursor) { + var b writerT + b.WriteString(name) + b.WriteByte('(') + for i := range c.Uint(fn.NumArguments()) { + if i > 0 { + b.WriteString(", ") + } + arg := fn.Argument(i) + argName := clang.String(arg) + writeParam(&b, arg.Type(), argName) + } + b.WriteByte(')') + writeParam(out, fn.ResultType(), b.String()) +} + +func writeParam(b *writerT, typ lc.Type, name string) { + tderef, lvl := deref(typ) + if tderef.Kind == lc.TypeFunctionProto { + writeFuncParam(b, tderef, lvl, name) + return + } + b.WriteString(clang.String(typ)) + b.WriteByte(' ') + b.WriteString(name) +} + +func writeFuncParam(out *writerT, fn lc.Type, lvl int, name string) { + var b writerT + b.WriteByte('(') + for range lvl { + b.WriteByte('*') + } + b.WriteString(name) + b.WriteByte(')') + b.WriteString("(") + for i := range c.Uint(fn.NumArgTypes()) { + if i > 0 { + b.WriteString(", ") + } + arg := fn.ArgType(i) + writeParam(&b, arg, "") + } + b.WriteString(")") + writeParam(out, fn.ResultType(), b.String()) +} + +func deref(typ lc.Type) (lc.Type, int) { + n := 0 + for typ.Kind == lc.TypePointer { + typ = typ.PointeeType() + n++ + } + return typ, n +} + +// -----------------------------------------------------------------------------