Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*.so
*.dylib
*.DS_Store
*.txt

_temp/
_c2go/
Expand Down
2 changes: 1 addition & 1 deletion cl/_testc/inline/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions cl/_testc/inline/wrap.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
int _llcppg_add(int a, int b) {
}

int _llcppg_mul(int a, int b) {
}
7 changes: 7 additions & 0 deletions cl/_testcpp/ctor_dtor/in.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class bar
{
public:
bar(int a);
bar();
~bar();
};
20 changes: 20 additions & 0 deletions cl/_testcpp/ctor_dtor/out.go
Original file line number Diff line number Diff line change
@@ -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() {
}
2 changes: 1 addition & 1 deletion cl/_testcpp/inline/out.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions cl/_testcpp/inline/wrap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
unsigned int _llcppg__ZN3bar1fEi(int a) {
}

void _llcppg__ZN3bar2_gEv() {
}
11 changes: 10 additions & 1 deletion cl/class.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor readability: this inner switch decl.Kind lists default first, which is legal but unconventional. Since the outer switch already constrains the kind to method/constructor/destructor, default here really means "CXXMethod" — consider putting default last and labeling it, e.g.:

switch decl.Kind {
case lc.CursorConstructor:
	name = ctorName
case lc.CursorDestructor:
	name = dtorName
default: // lc.CursorCXXMethod
	name = clang.String(decl)
}

The "XGo_Ctor"/"XGo_Dtor" literals also effectively form part of the generated-code contract; promoting them to named constants would give a single source of truth.

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}
Expand Down
38 changes: 27 additions & 11 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ func SetDebug(flags int) {
// Package represents a generated Go package.
type Package struct {
*gogen.Package
Wrap *WrapFile
}

// -----------------------------------------------------------------------------
Expand All @@ -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
}

Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -145,7 +162,6 @@ func loadFiles(ctx *pkgCtx) {
return clang.Continue
})
scope.reorder()
ctx.compile()
}

func loadDecl(ctx *pkgCtx, scope *scopeCtx, decl clang.Cursor) {
Expand Down
14 changes: 12 additions & 2 deletions cl/compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -71,17 +71,27 @@ func testFromDir(t *testing.T, sel, relDir string, lang cl.Language) {
LLGoPackage: conf.LLGoPackage,
Language: lang,
CFlags: conf.CFlags,
NameLookup: cltest.MockNameLookup,
NameLookup: nil,
Comment thread
xushiwei marked this conversation as resolved.
}, u, filename)
if err != nil {
t.Error("cl.NewPackage:", err)
return
}
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)
}
Expand Down
5 changes: 2 additions & 3 deletions cl/ctx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
5 changes: 3 additions & 2 deletions cl/func.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
Expand All @@ -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")
Expand Down
93 changes: 84 additions & 9 deletions cl/wrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,42 +17,117 @@
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{
LanguageC: ".c",
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
}

// -----------------------------------------------------------------------------
Loading