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 @@ -10,6 +10,7 @@
*.DS_Store

_temp/
_c2go/
*.log

# Test binary, built with `go test -c`
Expand Down
61 changes: 61 additions & 0 deletions cl/blockctx.go
Original file line number Diff line number Diff line change
@@ -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
}

// -----------------------------------------------------------------------------
287 changes: 285 additions & 2 deletions cl/compile.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.

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.

This doc comment is inaccurate and self-contradictory: there is no Load function in this package (the entry point is NewPackage), and "If Fset is nil ... preserve Fset's value" cannot be true when Fset is nil. NewPackage passes conf.Fset to gogen and never writes it back. Suggest: // If Fset is nil, NewPackage will create a new fileset.

Fset *token.FileSet

// An Importer resolves import paths to Packages.
Importer types.Importer

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

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.

The exact argument count is already known from n := fn.NumArguments(), so the append loop below reallocates the backing array as it grows. Preallocate to avoid the copies:

params := make([]*types.Var, 0, n+1) // +1 for the optional variadic param

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))

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.

Void return produces an invalid signature. results is unconditionally built as a single unnamed result: for a C function returning void, toType will yield a void tyRet and this generates a Go func with one unnamed result rather than no results. The author already flagged this with // TODO(xsw): return void (line 236); worth resolving before toType is implemented, since void-returning functions are common and would otherwise emit broken output.

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")

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.

NewPackage/loadFile carry an err error return, but this path (and compileDecl's log.Panicln("compileDecl: unknown kind ...") at line 211, and substObj) crash the process instead of propagating errors. Duplicate symbols and unhandled declaration kinds are input-controlled, so any header with a currently-unhandled top-level kind aborts. Consider deciding the error strategy — propagate err vs. panic-and-recover at the top level — since the public API advertises error.

}
// 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)
}

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