diff --git a/.gitignore b/.gitignore index 52681010..94e38187 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,7 @@ *.DS_Store _temp/ +_c2go/ *.log # Test binary, built with `go test -c` diff --git a/cl/blockctx.go b/cl/blockctx.go new file mode 100644 index 00000000..023e5ce0 --- /dev/null +++ b/cl/blockctx.go @@ -0,0 +1,61 @@ +/* + * 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 cl + +import ( + "go/token" + + "github.com/goplus/gogen" + "github.com/goplus/llcppg/clang" +) + +// ----------------------------------------------------------------------------- + +type blockCtx struct { + pkg *gogen.Package + cb *gogen.CodeBuilder + fset *token.FileSet +} + +/* +func (ctx *blockCtx) goNode(v clang.Cursor) ast.Node { + if rg := v.Range; rg != nil && ctx.file != nil { + base := ctx.file.Base() + pos := token.Pos(int(rg.Begin.Offset) + base) + end := token.Pos(int(rg.End.Offset) + rg.End.TokLen + base) + return &node{pos: pos, end: end, ctx: ctx} + } + return nil +} +*/ + +func (ctx *blockCtx) goNodePos(v clang.Cursor) token.Pos { + /* if rg := v.Range; rg != nil && ctx.file != nil { + base := ctx.file.Base() + return token.Pos(int(rg.Begin.Offset) + base) + } + return token.NoPos */ + panic("todo") +} + +func (p *blockCtx) getPubName(pfnName *string) (rewritten bool) { + // TODO(xsw): + _ = pfnName + return +} + +// ----------------------------------------------------------------------------- diff --git a/cl/compile.go b/cl/compile.go index e359794f..4ac5d846 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -17,13 +17,48 @@ package cl import ( + "go/ast" + "go/token" "go/types" + "log" + "strconv" "github.com/goplus/gogen" "github.com/goplus/lib/c" "github.com/goplus/llcppg/clang" + lc "github.com/goplus/llcppg/lib/clang" ) +const ( + DbgFlagCompileDecl = 1 << iota + DbgFlagLoadDeps + DbgFlagAll = DbgFlagCompileDecl | DbgFlagLoadDeps +) + +var ( + debugCompileDecl bool + debugLoadDeps bool +) + +func SetDebug(flags int) { + debugCompileDecl = (flags & DbgFlagCompileDecl) != 0 + debugLoadDeps = (flags & DbgFlagLoadDeps) != 0 +} + +// ----------------------------------------------------------------------------- + +type nodeInterp struct { + fset *token.FileSet +} + +func (p *nodeInterp) Position(start token.Pos) token.Position { + return p.fset.Position(start) +} + +func (p *nodeInterp) LoadExpr(v ast.Node) string { + panic("todo") +} + // ----------------------------------------------------------------------------- type PkgInfo struct { @@ -44,6 +79,10 @@ type Reused struct { // Config specifies the configuration for compiling C/C++ header files. type Config struct { + // Fset provides source position information for syntax trees and types. + // 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. Importer types.Importer @@ -64,10 +103,254 @@ type Source struct { // ----------------------------------------------------------------------------- +const ( + headerGoFile = "llcppg_header.i.go" +) + // NewPackage creates a new Package instance for the specified package path and name, using // the provided Source and Config. -func NewPackage(pkgPath, pkgName string, src Source, conf *Config) (pkg Package, err error) { - panic("todo") +func NewPackage(pkgPath, pkgName string, file Source, conf *Config) (pkg Package, err error) { + if reused := conf.Reused; reused != nil && reused.pkg.Package != nil { + pkg = reused.pkg + } else { + interp := &nodeInterp{} + confGox := &gogen.Config{ + Fset: conf.Fset, + Importer: conf.Importer, + LoadNamed: nil, + HandleErr: nil, + NewBuiltin: nil, + NodeInterpreter: interp, + CanImplicitCast: nil, + DefaultGoFile: headerGoFile, + } + pkg.Package = gogen.NewPackage(pkgPath, pkgName, confGox) + interp.fset = pkg.Fset + } + pkg.SetRedeclarable(true) + pkg.pi, err = loadFile(pkg.Package, conf, file) + return +} + +// ----------------------------------------------------------------------------- + +func loadFile(p *gogen.Package, conf *Config, file Source) (pi *PkgInfo, err error) { + ctx := &blockCtx{ + pkg: p, cb: p.CB(), fset: p.Fset, + } + _ = conf + clang.VisitChildren(file.Cursor(), func(decl, parent clang.Cursor) clang.ChildVisitResult { + compileDecl(ctx, decl) + return clang.Continue + }) + return +} + +func compileDecl(ctx *blockCtx, decl clang.Cursor) { + /* if global { + ctx.logFile(decl) + if decl.IsImplicit || ctx.inDepPkg { + continue + } + } */ + switch decl.Kind { + case lc.CursorFunctionDecl: + compileFunc(ctx, decl) + 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 + */ + case lc.CursorEnumDecl: + // compileEnum(ctx, decl, global) + default: + log.Panicln("compileDecl: unknown kind =", decl.Kind) + } +} + +// TODO(xsw): method support +func compileFunc(ctx *blockCtx, fn clang.Cursor) { + fnName := clang.String(fn) + if debugCompileDecl { + log.Println("func", fnName, "-", clang.String(fn.Type())) + } + origName := fnName + rewritten := ctx.getPubName(&fnName) + n := fn.NumArguments() + var params []*types.Var + var results *types.Tuple + for i := range n { + item := fn.Argument(c.Uint(i)) + param := newParam(ctx, item, i) + params = append(params, param) + } + variadic := fn.IsVariadic() != 0 + if variadic { + params = append(params, newVariadicParam(ctx)) + } + pkg := ctx.pkg + retType := fn.ResultType() // TODO(xsw): return void + tyRet := toType(ctx, retType, flagRetType) + results = types.NewTuple(pkg.NewParam(token.NoPos, "", tyRet, false)) + sig := types.NewSignatureType(nil, nil, nil, types.NewTuple(params...), results, variadic) + f := types.NewFunc(ctx.goNodePos(fn), pkg.Types, fnName, sig) + if old := pkg.Types.Scope().Insert(f); old != nil { + log.Panicln("Go func", fnName, "redefined") + } + // ctx.addExternFunc(fnName) + 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 ( + tyValist types.Type = types.NewSlice(gogen.TyAny) +) + +func newVariadicParam(ctx *blockCtx) *types.Var { + return types.NewParam(token.NoPos, ctx.pkg.Types, "__llgo_va_list", tyValist) +} + +func newParam(ctx *blockCtx, decl clang.Cursor, i c.Int) *types.Var { + declName := clang.String(decl) + declTyp := decl.Type() + if debugCompileDecl { + log.Println(" => param", declName, "-", clang.String(declTyp)) + } + typ := toType(ctx, declTyp, flagIsParam) + if declName != "" { + avoidKeyword(&declName) + } else { + declName = "__llcppg_param" + strconv.Itoa(int(i)+1) + } + return types.NewParam(ctx.goNodePos(decl), ctx.pkg.Types, declName, typ) } // ----------------------------------------------------------------------------- diff --git a/cl/compile_test.go b/cl/compile_test.go new file mode 100644 index 00000000..6089f847 --- /dev/null +++ b/cl/compile_test.go @@ -0,0 +1,60 @@ +/* + * 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 cl + +import ( + "os" + "path" + "strings" + "testing" +) + +// ----------------------------------------------------------------------------- + +func DoTestFromDir(t *testing.T, sel, relDir string, testFunc func(t *testing.T, pkgDir string)) { + dir, err := os.Getwd() + if err != nil { + t.Fatal("Getwd failed:", err) + } + dir = path.Join(dir, relDir) + fis, err := os.ReadDir(dir) + if err != nil { + t.Fatal("ReadDir failed:", err) + } + for _, fi := range fis { + name := fi.Name() + if strings.HasPrefix(name, "_") { + continue + } + t.Run(name, func(t *testing.T) { + pkgDir := dir + "/" + name + if sel != "" && !strings.Contains(pkgDir, sel) { + return + } + testFunc(t, pkgDir) + }) + } +} + +// ----------------------------------------------------------------------------- +/* +func testFromDir(t *testing.T, sel, relDir string) { + DoTestFromDir(t, sel, relDir, func(t *testing.T, pkgDir string) { + }) +} +*/ +// ----------------------------------------------------------------------------- diff --git a/cl/type_and_var.go b/cl/type_and_var.go new file mode 100644 index 00000000..48bf8cd7 --- /dev/null +++ b/cl/type_and_var.go @@ -0,0 +1,65 @@ +/* + * 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 cl + +import ( + "go/token" + "go/types" + "log" + + "github.com/goplus/gogen" + lc "github.com/goplus/llcppg/lib/clang" +) + +// ----------------------------------------------------------------------------- + +const ( + flagIsParam = 1 << iota + flagIsStructField + flagIsExtern + flagIsTypedef + flagRetType +) + +func toType(ctx *blockCtx, typ lc.Type, flags int) types.Type { + panic("todo") +} + +// ----------------------------------------------------------------------------- + +func substObj(pkg *types.Package, scope *types.Scope, origName string, real types.Object) { + old := scope.Insert(gogen.NewSubst(token.NoPos, pkg, origName, real)) + if old != nil { + if t, ok := old.Type().(*gogen.TySubst); ok { + t.Real = real + } else { + log.Panicln(origName, "redefined") + } + } +} + +// ----------------------------------------------------------------------------- + +func avoidKeyword(name *string) { + switch *name { + case "map", "type", "range", "chan", "var", "func", "go", "select", + "defer", "package", "import", "interface", "fallthrough": + *name += "_" + } +} + +// ----------------------------------------------------------------------------- diff --git a/lib/clang/clang.go b/lib/clang/clang.go index 50715d50..1f11fa03 100644 --- a/lib/clang/clang.go +++ b/lib/clang/clang.go @@ -1890,7 +1890,7 @@ func (t Type) IsConstQualifiedType() (ret c.Uint) { } /** - * Determine whether a CXCursor that is a macro, is + * Determine whether a CXCursor that is a macro, is * function like. */ // llgo:link Cursor.IsMacroFunctionLike C.clang_Cursor_isMacroFunctionLike @@ -1899,7 +1899,7 @@ func (c Cursor) IsMacroFunctionLike() (ret c.Uint) { } /** - * Determine whether a CXCursor that is a macro, is a + * Determine whether a CXCursor that is a macro, is a * builtin one. */ // llgo:link Cursor.IsMacroBuiltin C.clang_Cursor_isMacroBuiltin @@ -1908,7 +1908,7 @@ func (c Cursor) IsMacroBuiltin() (ret c.Uint) { } /** - * Determine whether a CXCursor that is a function declaration, is an + * Determine whether a CXCursor that is a function declaration, is an * inline declaration. */ // llgo:link Cursor.IsFunctionInlined C.clang_Cursor_isFunctionInlined @@ -2274,74 +2274,75 @@ func (c Cursor) Mangling() (ret String) { /** * Determine if a C++ constructor is a converting constructor. */ -// llgo:link Cursor.IsConvertingConstructor C.clang_CXXConstructor_isConvertingConstructor -func (c Cursor) IsConvertingConstructor() (ret c.Uint) { +// llgo:link Cursor.CXXConstructorIsConvertingConstructor C.clang_CXXConstructor_isConvertingConstructor +func (c Cursor) CXXConstructorIsConvertingConstructor() (ret c.Uint) { return } /** * Determine if a C++ constructor is a copy constructor. */ -// llgo:link Cursor.IsCopyConstructor C.clang_CXXConstructor_isCopyConstructor -func (c Cursor) IsCopyConstructor() (ret c.Uint) { +// llgo:link Cursor.CXXConstructorIsCopyConstructor C.clang_CXXConstructor_isCopyConstructor +func (c Cursor) CXXConstructorIsCopyConstructor() (ret c.Uint) { return } /** * Determine if a C++ constructor is the default constructor. */ -// llgo:link Cursor.IsDefaultConstructor C.clang_CXXConstructor_isDefaultConstructor -func (c Cursor) IsDefaultConstructor() (ret c.Uint) { +// llgo:link Cursor.CXXConstructorIsDefaultConstructor C.clang_CXXConstructor_isDefaultConstructor +func (c Cursor) CXXConstructorIsDefaultConstructor() (ret c.Uint) { return } /** * Determine if a C++ constructor is a move constructor. */ -// llgo:link Cursor.IsMoveConstructor C.clang_CXXConstructor_isMoveConstructor -func (c Cursor) IsMoveConstructor() (ret c.Uint) { +// llgo:link Cursor.CXXConstructorIsMoveConstructor C.clang_CXXConstructor_isMoveConstructor +func (c Cursor) CXXConstructorIsMoveConstructor() (ret c.Uint) { return } /** - * Determine if a C++ field is declared 'mutable'. + * Determine if a C++ method is declared '= default'. */ -// llgo:link Cursor.IsMutable C.clang_CXXField_isMutable -func (c Cursor) IsMutable() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsDefaulted C.clang_CXXMethod_isDefaulted +func (c Cursor) CXXMethodIsDefaulted() (ret c.Uint) { return } /** - * Determine if a C++ method is declared '= default'. + * Determine if a C++ method is declared '= delete'. */ -// llgo:link Cursor.IsDefaulted C.clang_CXXMethod_isDefaulted -func (c Cursor) IsDefaulted() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsDeleted C.clang_CXXMethod_isDeleted +func (c Cursor) CXXMethodIsDeleted() (ret c.Uint) { return } /** - * Determine if a C++ method is declared '= delete'. + * Determine if a C++ member function or member function template is + * pure virtual. */ -// llgo:link Cursor.IsDeleted C.clang_CXXMethod_isDeleted -func (c Cursor) IsDeleted() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsPureVirtual C.clang_CXXMethod_isPureVirtual +func (c Cursor) CXXMethodIsPureVirtual() (ret c.Uint) { return } /** * Determine if a C++ member function or member function template is - * pure virtual. + * declared 'static'. */ -// llgo:link Cursor.IsPureVirtual C.clang_CXXMethod_isPureVirtual -func (c Cursor) IsPureVirtual() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsStatic C.clang_CXXMethod_isStatic +func (c Cursor) CXXMethodIsStatic() (ret c.Uint) { return } /** * Determine if a C++ member function or member function template is - * declared 'static'. + * declared 'const'. */ -// llgo:link Cursor.IsStatic C.clang_CXXMethod_isStatic -func (c Cursor) IsStatic() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsConst C.clang_CXXMethod_isConst +func (c Cursor) CXXMethodIsConst() (ret c.Uint) { return } @@ -2350,8 +2351,8 @@ func (c Cursor) IsStatic() (ret c.Uint) { * explicitly declared 'virtual' or if it overrides a virtual method from * one of the base classes. */ -// llgo:link Cursor.IsVirtual C.clang_CXXMethod_isVirtual -func (c Cursor) IsVirtual() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsVirtual C.clang_CXXMethod_isVirtual +func (c Cursor) CXXMethodIsVirtual() (ret c.Uint) { return } @@ -2378,8 +2379,8 @@ func (c Cursor) IsVirtual() (ret c.Uint) { * * Is not. */ -// llgo:link Cursor.IsCopyAssignmentOperator C.clang_CXXMethod_isCopyAssignmentOperator -func (c Cursor) IsCopyAssignmentOperator() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsCopyAssignmentOperator C.clang_CXXMethod_isCopyAssignmentOperator +func (c Cursor) CXXMethodIsCopyAssignmentOperator() (ret c.Uint) { return } @@ -2406,8 +2407,8 @@ func (c Cursor) IsCopyAssignmentOperator() (ret c.Uint) { * * Is not. */ -// llgo:link Cursor.IsMoveAssignmentOperator C.clang_CXXMethod_isMoveAssignmentOperator -func (c Cursor) IsMoveAssignmentOperator() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsMoveAssignmentOperator C.clang_CXXMethod_isMoveAssignmentOperator +func (c Cursor) CXXMethodIsMoveAssignmentOperator() (ret c.Uint) { return } @@ -2454,34 +2455,33 @@ func (c Cursor) IsMoveAssignmentOperator() (ret c.Uint) { * This function will return 0 for the constructor and 1 for * the conversion function. */ -// llgo:link Cursor.IsExplicit C.clang_CXXMethod_isExplicit -func (c Cursor) IsExplicit() (ret c.Uint) { +// llgo:link Cursor.CXXMethodIsExplicit C.clang_CXXMethod_isExplicit +func (c Cursor) CXXMethodIsExplicit() (ret c.Uint) { return } /** - * Determine if a C++ record is abstract, i.e. whether a class or struct - * has a pure virtual member function. + * Determine if a C++ field is declared 'mutable'. */ -// llgo:link Cursor.IsAbstract C.clang_CXXRecord_isAbstract -func (c Cursor) IsAbstract() (ret c.Uint) { +// llgo:link Cursor.CXXFieldIsMutable C.clang_CXXField_isMutable +func (c Cursor) CXXFieldIsMutable() (ret c.Uint) { return } /** - * Determine if an enum declaration refers to a scoped enum. + * Determine if a C++ record is abstract, i.e. whether a class or struct + * has a pure virtual member function. */ -// llgo:link Cursor.IsScoped C.clang_EnumDecl_isScoped -func (c Cursor) IsScoped() (ret c.Uint) { +// llgo:link Cursor.CXXRecordIsAbstract C.clang_CXXRecord_isAbstract +func (c Cursor) CXXRecordIsAbstract() (ret c.Uint) { return } /** - * Determine if a C++ member function or member function template is - * declared 'const'. + * Determine if an enum declaration refers to a scoped enum. */ -// llgo:link Cursor.IsConst C.clang_CXXMethod_isConst -func (c Cursor) IsConst() (ret c.Uint) { +// llgo:link Cursor.EnumDeclIsScoped C.clang_EnumDecl_isScoped +func (c Cursor) EnumDeclIsScoped() (ret c.Uint) { return }