diff --git a/cl/_mod/go.mod b/cl/_mod/go.mod deleted file mode 100644 index 87fb2ade..00000000 --- a/cl/_mod/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module foo - -go 1.20 - -require github.com/goplus/lib v0.5.2 diff --git a/cl/_mod/go.sum b/cl/_mod/go.sum deleted file mode 100644 index 8fad59c3..00000000 --- a/cl/_mod/go.sum +++ /dev/null @@ -1,2 +0,0 @@ -github.com/goplus/lib v0.5.2 h1:BUd3mUwTajDRBHVxMfS/y/hDJ6n/Pxwf6z7ikrOXvkE= -github.com/goplus/lib v0.5.2/go.mod h1:SgJv3oPqLLHCu0gcL46ejOP3x7/2ry2Jtxu7ta32kp0= diff --git a/cl/_mod/stub.go b/cl/_mod/stub.go deleted file mode 100644 index 1b5378c5..00000000 --- a/cl/_mod/stub.go +++ /dev/null @@ -1,3 +0,0 @@ -package foo - -import _ "github.com/goplus/lib/c" diff --git a/cl/_testmockc/function/in.h b/cl/_testmockc/function/in.h index 98a8f3dd..b58d01e0 100644 --- a/cl/_testmockc/function/in.h +++ b/cl/_testmockc/function/in.h @@ -1,5 +1,5 @@ unsigned f(int a); -void g(); +void _g(); signed int xprintf(const char* fmt, ...); diff --git a/cl/_testmockc/function/out.go b/cl/_testmockc/function/out.go index 14ac025a..7824fcd1 100644 --- a/cl/_testmockc/function/out.go +++ b/cl/_testmockc/function/out.go @@ -1,7 +1,15 @@ package foo -import "github.com/goplus/lib/c" +import ( + "github.com/goplus/lib/c" + _ "unsafe" +) -func f(a c.Int) c.Uint -func g() -func xprintf(fmt *c.Char, __llgo_va_list ...any) c.Int +//go:linkname F C.f +func F(a c.Int) c.Uint + +//go:linkname X_g C._g +func X_g() + +//go:linkname Xprintf C.xprintf +func Xprintf(fmt *c.Char, __llgo_va_list ...any) c.Int diff --git a/cl/blockctx.go b/cl/blockctx.go index 8f79adf8..a819963d 100644 --- a/cl/blockctx.go +++ b/cl/blockctx.go @@ -80,6 +80,17 @@ type blockCtx struct { fset *token.FileSet file *token.File c gogen.PkgRef + + nameLookup func(manglingName string) (archivePath string, ok bool) + + unsafeImported bool +} + +func (p *blockCtx) forceImportUnsafe() { + if !p.unsafeImported { + p.unsafeImported = true + p.pkg.ForceImport("unsafe") + } } func (p *blockCtx) initFile(file Source) { @@ -89,9 +100,23 @@ func (p *blockCtx) initFile(file Source) { } func (p *blockCtx) getPubName(pfnName *string) (rewritten bool) { - // TODO(xsw): - _ = pfnName + fnName := *pfnName + pubName := cPubName(fnName) + rewritten = fnName != pubName + if rewritten { + *pfnName = pubName + } return } +func cPubName(name string) string { + if r := name[0]; 'a' <= r && r <= 'z' { + r -= 'a' - 'A' + return string(r) + name[1:] + } else if r == '_' { + return "X" + name + } + return name +} + // ----------------------------------------------------------------------------- diff --git a/cl/cltest/cltest.go b/cl/cltest/cltest.go index e2c8fa64..cad9bfd9 100644 --- a/cl/cltest/cltest.go +++ b/cl/cltest/cltest.go @@ -25,8 +25,8 @@ import ( // ----------------------------------------------------------------------------- -// TestFromDir runs testFunc for each subdirectory of relDir. If sel is not empty, only subdirectories -// whose path contains sel will be tested. +// TestFromDir runs testFunc for each subdirectory of relDir. If sel is not empty, only +// subdirectories whose path contains sel will be tested. func TestFromDir(t *testing.T, sel, relDir string, testFunc func(t *testing.T, pkgDir string)) { dir, err := os.Getwd() if err != nil { @@ -54,8 +54,8 @@ func TestFromDir(t *testing.T, sel, relDir string, testFunc func(t *testing.T, p // ----------------------------------------------------------------------------- -// MockNameLookup is a mock implementation of the NameLookup function. It returns a fixed archive -// path and true for any input. +// MockNameLookup is a mock implementation of the NameLookup function. It returns a +// fixed archive path and true for any input. func MockNameLookup(manglingName string) (archivePath string, ok bool) { return "libfoo.a", true } diff --git a/cl/compile.go b/cl/compile.go index 1227d5b3..4bf179f0 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -17,6 +17,7 @@ package cl import ( + "go/ast" "go/token" "go/types" "log" @@ -55,7 +56,8 @@ type Package struct { pi *PkgInfo } -// Reused specifies to reuse the Package instance between processing multiple C/C++ header files. +// Reused specifies to reuse the Package instance between processing multiple C/C++ +// header files. type Reused struct { pkg Package } @@ -74,10 +76,12 @@ type Config struct { // Include specifies include searching directories. Include []string - // Reused specifies to reuse the Package instance between processing multiple C/C++ header files. + // Reused specifies to reuse the Package instance between processing multiple C/C++ + // header files. *Reused - // 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. + // 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. NameLookup func(manglingName string) (archivePath string, ok bool) } @@ -127,9 +131,9 @@ func loadFile(p *gogen.Package, conf *Config, file Source) (pi *PkgInfo, err err c := p.Import("github.com/goplus/lib/c") ctx := &blockCtx{ pkg: p, cb: p.CB(), fset: p.Fset, c: c, + nameLookup: conf.NameLookup, } ctx.initFile(file) - _ = conf clang.VisitChildren(file.TU.Cursor(), func(decl, parent clang.Cursor) clang.ChildVisitResult { compileDecl(ctx, decl) return clang.Continue @@ -150,52 +154,7 @@ func compileDecl(ctx *blockCtx, decl clang.Cursor) { case lc.CursorVarDecl: // compileVarDecl(ctx, decl, global) case lc.CursorTypedefDecl: - /* origName, pub := decl.Name, false - if global { - pub = ctx.getPubName(&decl.Name) - } - compileTypedef(ctx, decl, global, pub) - if pub { - substObj(ctx.pkg.Types, scope, origName, scope.Lookup(decl.Name)) - } - case ast.RecordDecl: - pub := false - name, suKind := ctx.getSuName(decl, decl.TagUsed) - origName := name - if global { - if suKind == suAnonymous { - // pub = true if this is a public typedef - pub = i+1 < n && isPubTypedef(ctx, node.Inner[i+1]) - } else { - pub = ctx.getPubName(&name) - if decl.CompleteDefinition && ctx.checkExists(name) { - continue - } - } - } - typ, del := compileStructOrUnion(ctx, name, decl, pub) - if suKind != suAnonymous { - if pub { - substObj(ctx.pkg.Types, scope, origName, scope.Lookup(name)) - } - break - } - ctx.unnameds[decl.ID] = unnamedType{typ: typ, del: del} - for i+1 < n { - next := node.Inner[i+1] - if next.Kind == ast.VarDecl { - if ret, ok := checkAnonymous(ctx, scope, typ, next); ok { - compileVarWith(ctx, ret, next) - i++ - continue - } - } - break - } - case ast.EmptyDecl: - case ast.StaticAssertDecl: - continue - */ + // TODO(xsw) case lc.CursorEnumDecl: // compileEnum(ctx, decl, global) default: @@ -205,6 +164,14 @@ func compileDecl(ctx *blockCtx, decl clang.Cursor) { // TODO(xsw): method support func compileFunc(ctx *blockCtx, fn clang.Cursor) { + manglingName := clang.Mangling(fn) + if _, ok := ctx.nameLookup(manglingName); !ok { + if debugCompileDecl { + log.Println("func", clang.String(fn), "- skipped") + } + return + } + fnName := clang.String(fn) if debugCompileDecl { log.Println("func", fnName, "-", clang.String(fn.Type())) @@ -234,93 +201,16 @@ func compileFunc(ctx *blockCtx, fn clang.Cursor) { if err != nil { log.Panicln("compileFunc:", fnName, err) } - // ctx.addExternFunc(fnName) + ctx.forceImportUnsafe() + f.SetComments(pkg, &ast.CommentGroup{ + List: []*ast.Comment{ + {Text: "\n//go:linkname " + fnName + " C." + manglingName[1:]}, + }, + }) if rewritten { scope := pkg.Types.Scope() substObj(pkg.Types, scope, origName, f) } - /* origName, rewritten := fnName, false - if !ctx.inHeader && fn.StorageClass == ast.Static { - fnName, rewritten = ctx.autoStaticName(origName), true - } else { - rewritten = ctx.getPubName(&fnName) - } - if body != nil { - if ctx.checkExists(fnName) { - return - } - isMain := false - if fnName == "main" && (results != nil || params != nil) { - fnName, isMain = "_cgo_main", true - } - f, err := pkg.NewFuncWith(ctx.goNodePos(fn), fnName, sig, nil) - if err != nil { - log.Panicln("compileFunc:", err) - } - if rewritten { // for fnName is a recursive function - scope := pkg.Types.Scope() - substObj(pkg.Types, scope, origName, f.Obj()) - rewritten = false - } - cb := f.BodyStart(pkg) - ctx.curfn = newFuncCtx(pkg, ctx.markComplicated(fnName, body), origName) - compileSub(ctx, body) - checkNeedReturn(ctx, body) - ctx.curfn = nil - cb.End() - if isMain { - var t *types.Var - var entryParams *types.Tuple - var entry = "main" - var testMain = ctx.testMain - if testMain { - entry = "TestMain" - testing := pkg.Import("testing") - t = pkg.NewParam(token.NoPos, "t", types.NewPointer(testing.Ref("T").Type())) - entryParams = types.NewTuple(t) - } - pkg.NewFunc(nil, entry, entryParams, nil, false).BodyStart(pkg) - if results != nil { - if testMain { - // if _cgo_ret := _cgo_main(); _cgo_ret != 0 { - // t.Fatal("exit status", _cgo_ret) - // } - cb.If().DefineVarStart(token.NoPos, retName) - } else { - // os.Exit(int(_cgo_main())) - cb.Val(pkg.Import("os").Ref("Exit")).Typ(types.Typ[types.Int]) - } - } - cb.Val(f.Obj()) - if params != nil { - panic("TODO: main func with params") - } - cb.Call(len(params)) - if results != nil { - if testMain { - cb.EndInit(1) - ret := cb.Scope().Lookup(retName) - cb.Val(ret).Val(0).BinaryOp(token.NEQ).Then(). - Val(t).MemberVal("Fatal").Val("exit status").Val(ret).Call(2).EndStmt(). - End() - } else { - cb.Call(1).Call(1) - } - } - cb.EndStmt().End() - } else { - delete(ctx.extfns, fnName) - } - } else if fn.IsUsed { - f := types.NewFunc(ctx.goNodePos(fn), pkg.Types, fnName, sig) - if pkg.Types.Scope().Insert(f) == nil { - ctx.addExternFunc(fnName) - } - } - if rewritten { - scope := pkg.Types.Scope() - substObj(pkg.Types, scope, origName, scope.Lookup(fnName)) - } */ } var ( @@ -341,7 +231,7 @@ func newParam(ctx *blockCtx, decl clang.Cursor, i c.Int) *types.Var { if declName != "" { avoidKeyword(&declName) } else { - declName = "__llcppg_param" + strconv.Itoa(int(i)+1) + declName = "_llcppg_param" + strconv.Itoa(int(i)+1) } return types.NewParam(goNodePos(ctx, decl), ctx.pkg.Types, declName, typ) } diff --git a/clang/mangling_addprefix.go b/clang/mangling_addprefix.go new file mode 100644 index 00000000..3cafcdd4 --- /dev/null +++ b/clang/mangling_addprefix.go @@ -0,0 +1,37 @@ +//go:build linux + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package clang + +import ( + "github.com/goplus/lib/c" +) + +// ----------------------------------------------------------------------------- + +/** + * Retrieve a name for the entity referenced by this cursor. + */ +func Mangling(fn Cursor) string { + m := fn.Mangling() + manglingName := c.GoString(m.CStr()) + m.Dispose() + return "_" + manglingName +} + +// ----------------------------------------------------------------------------- diff --git a/clang/mangling_normal.go b/clang/mangling_normal.go new file mode 100644 index 00000000..477de6d5 --- /dev/null +++ b/clang/mangling_normal.go @@ -0,0 +1,37 @@ +//go:build darwin || windows + +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package clang + +import ( + "github.com/goplus/lib/c" +) + +// ----------------------------------------------------------------------------- + +/** + * Retrieve a name for the entity referenced by this cursor. + */ +func Mangling(fn Cursor) string { + m := fn.Mangling() + manglingName := c.GoString(m.CStr()) + m.Dispose() + return manglingName +} + +// ----------------------------------------------------------------------------- diff --git a/lib/clang/clang.go b/lib/clang/clang.go index 45667d7f..f37ed8a9 100644 --- a/lib/clang/clang.go +++ b/lib/clang/clang.go @@ -2569,8 +2569,7 @@ func VisitChildren( type Visitor func(cursor, parent Cursor, clientData ClientData) ChildVisitResult /** - * Visitor invoked for each file in a translation unit - * (used with clang_getInclusions()). + * Visitor invoked for each file in a translation unit (used with clang_getInclusions()). * * This visitor function will be invoked by clang_getInclusions() for each * file included (either at the top-level or by \#include directives) within @@ -2585,7 +2584,7 @@ type InclusionVisitor func(included_file File, inclusion_stack *SourceLocation, /** * Visit the set of preprocessor inclusions in a translation unit. * The visitor function is called with the provided data for every included - * file. This does not include headers included by the PCH file (unless one + * file. This does not include headers included by the PCH file (unless one * is inspecting the inclusions in the PCH file itself). */ //go:linkname GetInclusions C.clang_getInclusions