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/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/_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/class.go b/cl/class.go index 6b175d1f..ed70c819 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 { + 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) isPublic := cls.inPublic method := &classMethod{obj: obj, manglingName: manglingName, isPublic: isPublic} diff --git a/cl/compile.go b/cl/compile.go index 523314d2..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 } // ----------------------------------------------------------------------------- @@ -61,32 +62,35 @@ 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. + // 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 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) // 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 } @@ -96,10 +100,13 @@ 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 { + conf = &Config{} + } confGox := &gogen.Config{ Fset: conf.Fset, Importer: conf.Importer, @@ -123,17 +130,27 @@ 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) loadFiles(ctx) + ctx.compile() ret.Package = pkg + ret.Wrap = ctx.wrap return } +func defaultNameLookup(manglingName string) (archivePath string, ok bool) { + return "", true +} + // ----------------------------------------------------------------------------- func loadFiles(ctx *pkgCtx) { @@ -145,7 +162,6 @@ func loadFiles(ctx *pkgCtx) { return clang.Continue }) scope.reorder() - ctx.compile() } func loadDecl(ctx *pkgCtx, scope *scopeCtx, decl clang.Cursor) { diff --git a/cl/compile_test.go b/cl/compile_test.go index 91cf20e7..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) { @@ -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) @@ -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 e3641d0f..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 @@ -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..50a24add 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) }) @@ -46,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 +} + +// -----------------------------------------------------------------------------