Merge pull request #1167 from xushiwei/q

Write a C package in Go
This commit is contained in:
xushiwei
2025-06-23 22:51:41 +08:00
committed by GitHub
10 changed files with 102 additions and 42 deletions

1
.github/codecov.yml vendored
View File

@@ -2,6 +2,7 @@ coverage:
ignore: ignore:
- "chore" - "chore"
- "cmd" - "cmd"
- "cl/cltest"
- "internal/build" - "internal/build"
- "internal/llgen" - "internal/llgen"
- "internal/mockable" - "internal/mockable"

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ stories*.bin
.DS_Store .DS_Store
err.log err.log
numpy.txt numpy.txt
result.txt
_go/ _go/
_runtime/ _runtime/

13
cl/_testdata/cpkg/in.go Normal file
View File

@@ -0,0 +1,13 @@
package C
func Xadd(a, b int) int {
return add(a, b)
}
func Double(x float64) float64 {
return 2 * x
}
func add(a, b int) int {
return a + b
}

35
cl/_testdata/cpkg/out.ll Normal file
View File

@@ -0,0 +1,35 @@
; ModuleID = 'github.com/goplus/llgo/cl/_testdata/cpkg'
source_filename = "github.com/goplus/llgo/cl/_testdata/cpkg"
@"github.com/goplus/llgo/cl/_testdata/cpkg.init$guard" = global i1 false, align 1
define double @Double(double %0) {
_llgo_0:
%1 = fmul double 2.000000e+00, %0
ret double %1
}
define i64 @add(i64 %0, i64 %1) {
_llgo_0:
%2 = call i64 @"github.com/goplus/llgo/cl/_testdata/cpkg.add"(i64 %0, i64 %1)
ret i64 %2
}
define i64 @"github.com/goplus/llgo/cl/_testdata/cpkg.add"(i64 %0, i64 %1) {
_llgo_0:
%2 = add i64 %0, %1
ret i64 %2
}
define void @"github.com/goplus/llgo/cl/_testdata/cpkg.init"() {
_llgo_0:
%0 = load i1, ptr @"github.com/goplus/llgo/cl/_testdata/cpkg.init$guard", align 1
br i1 %0, label %_llgo_2, label %_llgo_1
_llgo_1: ; preds = %_llgo_0
store i1 true, ptr @"github.com/goplus/llgo/cl/_testdata/cpkg.init$guard", align 1
br label %_llgo_2
_llgo_2: ; preds = %_llgo_1, %_llgo_0
ret void
}

View File

@@ -18,6 +18,7 @@ package cltest
import ( import (
"archive/zip" "archive/zip"
"bytes"
"go/ast" "go/ast"
"go/parser" "go/parser"
"go/token" "go/token"
@@ -35,6 +36,7 @@ import (
"github.com/goplus/llgo/cl" "github.com/goplus/llgo/cl"
"github.com/goplus/llgo/internal/llgen" "github.com/goplus/llgo/internal/llgen"
"github.com/goplus/llgo/ssa/ssatest" "github.com/goplus/llgo/ssa/ssatest"
"github.com/qiniu/x/test"
"golang.org/x/tools/go/ssa" "golang.org/x/tools/go/ssa"
"golang.org/x/tools/go/ssa/ssautil" "golang.org/x/tools/go/ssa/ssautil"
@@ -112,14 +114,13 @@ func testFrom(t *testing.T, pkgDir, sel string) {
return return
} }
log.Println("Parsing", pkgDir) log.Println("Parsing", pkgDir)
v := llgen.GenFrom(pkgDir)
out := pkgDir + "/out.ll" out := pkgDir + "/out.ll"
b, err := os.ReadFile(out) b, _ := os.ReadFile(out)
if err != nil { if !bytes.Equal(b, []byte{';'}) { // expected == ";" means skipping out.ll
t.Fatal("ReadFile failed:", err) if test.Diff(t, pkgDir+"/result.txt", []byte(v), b) {
t.Fatal("llgen.GenFrom: unexpect result")
} }
expected := string(b)
if v := llgen.GenFrom(pkgDir); v != expected && expected != ";" { // expected == ";" means skipping out.ll
t.Fatalf("\n==> got:\n%s\n==> expected:\n%s\n", v, expected)
} }
} }

View File

@@ -213,15 +213,8 @@ func isCgoVar(name string) bool {
} }
func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) { func (p *context) compileFuncDecl(pkg llssa.Package, f *ssa.Function) (llssa.Function, llssa.PyObjRef, int) {
pkgTypes, name, ftype := p.funcName(f, true) pkgTypes, name, ftype := p.funcName(f)
if ftype != goFunc { if ftype != goFunc {
/*
if ftype == pyFunc {
// TODO(xsw): pyMod == ""
fnName := pysymPrefix + p.pyMod + "." + name
return nil, pkg.NewPyFunc(fnName, f.Signature, call), pyFunc
}
*/
return nil, nil, ignoredFunc return nil, nil, ignoredFunc
} }
sig := f.Signature sig := f.Signature
@@ -1012,7 +1005,7 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
cgoSymbols: make([]string, 0, 128), cgoSymbols: make([]string, 0, 128),
} }
ctx.initPyModule() ctx.initPyModule()
ctx.initFiles(pkgPath, files) ctx.initFiles(pkgPath, files, pkgName == "C")
ctx.prog.SetPatch(ctx.patchType) ctx.prog.SetPatch(ctx.patchType)
ret.SetPatch(ctx.patchType) ret.SetPatch(ctx.patchType)
ret.SetResolveLinkname(ctx.resolveLinkname) ret.SetResolveLinkname(ctx.resolveLinkname)

View File

@@ -174,13 +174,18 @@ start:
syms.initLinknames(p) syms.initLinknames(p)
} }
func (p *context) initFiles(pkgPath string, files []*ast.File) { func (p *context) initFiles(pkgPath string, files []*ast.File, cPkg bool) {
for _, file := range files { for _, file := range files {
for _, decl := range file.Decls { for _, decl := range file.Decls {
switch decl := decl.(type) { switch decl := decl.(type) {
case *ast.FuncDecl: case *ast.FuncDecl:
fullName, inPkgName := astFuncName(pkgPath, decl) fullName, inPkgName := astFuncName(pkgPath, decl)
p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false) if !p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false) && cPkg {
// package C (https://github.com/goplus/llgo/issues/1165)
if decl.Recv == nil && token.IsExported(inPkgName) {
p.prog.SetLinkname(fullName, strings.TrimPrefix(inPkgName, "X"))
}
}
case *ast.GenDecl: case *ast.GenDecl:
switch decl.Tok { switch decl.Tok {
case token.VAR: case token.VAR:
@@ -266,51 +271,58 @@ func (p *context) collectSkip(line string, prefix int) {
} }
} }
func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName string, isVar bool) { func (p *context) initLinknameByDoc(doc *ast.CommentGroup, fullName, inPkgName string, isVar bool) bool {
if doc != nil { if doc != nil {
for n := len(doc.List) - 1; n >= 0; n-- { for n := len(doc.List) - 1; n >= 0; n-- {
line := doc.List[n].Text line := doc.List[n].Text
found := p.initLinkname(line, func(name string) (_ string, _, ok bool) { ret := p.initLinkname(line, func(name string) (_ string, _, ok bool) {
return fullName, isVar, name == inPkgName return fullName, isVar, name == inPkgName
}) })
if !found { if ret != unknownDirective {
break return ret == hasLinkname
} }
} }
} }
return false
} }
func (p *context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) bool { const (
noDirective = iota
hasLinkname
unknownDirective = -1
)
func (p *context) initLinkname(line string, f func(inPkgName string) (fullName string, isVar, ok bool)) int {
const ( const (
linkname = "//go:linkname " linkname = "//go:linkname "
llgolink = "//llgo:link " llgolink = "//llgo:link "
llgolink2 = "// llgo:link " llgolink2 = "// llgo:link "
exportName = "//export " export = "//export "
directive = "//go:" directive = "//go:"
) )
if strings.HasPrefix(line, linkname) { if strings.HasPrefix(line, linkname) {
p.initLink(line, len(linkname), f) p.initLink(line, len(linkname), f)
return true return hasLinkname
} else if strings.HasPrefix(line, llgolink2) { } else if strings.HasPrefix(line, llgolink2) {
p.initLink(line, len(llgolink2), f) p.initLink(line, len(llgolink2), f)
return true return hasLinkname
} else if strings.HasPrefix(line, llgolink) { } else if strings.HasPrefix(line, llgolink) {
p.initLink(line, len(llgolink), f) p.initLink(line, len(llgolink), f)
return true return hasLinkname
} else if strings.HasPrefix(line, exportName) { } else if strings.HasPrefix(line, export) {
p.initCgoExport(line, len(exportName), f) p.initCgoExport(line, len(export), f)
return true return hasLinkname
} else if strings.HasPrefix(line, directive) { } else if strings.HasPrefix(line, directive) {
// skip unknown annotation but continue to parse the next annotation // skip unknown annotation but continue to parse the next annotation
return true return unknownDirective
} }
return false return noDirective
} }
func (p *context) initCgoExport(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) { func (p *context) initCgoExport(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) {
name := strings.TrimSpace(line[prefix:]) name := strings.TrimSpace(line[prefix:])
if fullName, _, ok := f(name); ok { if fullName, _, ok := f(name); ok {
p.cgoExports[fullName] = name p.cgoExports[fullName] = name // TODO(xsw): why not use prog.SetLinkname?
} }
} }
@@ -497,7 +509,7 @@ const (
llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin) llgoAtomicOpLast = llgoAtomicOpBase + int(llssa.OpUMin)
) )
func (p *context) funcName(fn *ssa.Function, ignore bool) (*types.Package, string, int) { func (p *context) funcName(fn *ssa.Function) (*types.Package, string, int) {
var pkg *types.Package var pkg *types.Package
var orgName string var orgName string
if origin := fn.Origin(); origin != nil { if origin := fn.Origin(); origin != nil {

View File

@@ -334,7 +334,7 @@ var llgoInstrs = map[string]int{
// funcOf returns a function by name and set ftype = goFunc, cFunc, etc. // funcOf returns a function by name and set ftype = goFunc, cFunc, etc.
// or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc. // or returns nil and set ftype = llgoCstr, llgoAlloca, llgoUnreachable, etc.
func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObjRef, ftype int) { func (p *context) funcOf(fn *ssa.Function) (aFn llssa.Function, pyFn llssa.PyObjRef, ftype int) {
pkgTypes, name, ftype := p.funcName(fn, false) pkgTypes, name, ftype := p.funcName(fn)
switch ftype { switch ftype {
case pyFunc: case pyFunc:
if kind, mod := pkgKindByScope(pkgTypes.Scope()); kind == PkgPyModule { if kind, mod := pkgKindByScope(pkgTypes.Scope()); kind == PkgPyModule {

View File

@@ -184,7 +184,7 @@ func Do(args []string, conf *Config) ([]Package, error) {
prog := llssa.NewProgram(target) prog := llssa.NewProgram(target)
sizes := func(sizes types.Sizes, compiler, arch string) types.Sizes { sizes := func(sizes types.Sizes, compiler, arch string) types.Sizes {
if arch == "wasm" { if arch == "wasm" {
sizes = &types.StdSizes{4, 4} sizes = &types.StdSizes{WordSize: 4, MaxAlign: 4}
} }
return prog.TypeSizes(sizes) return prog.TypeSizes(sizes)
} }
@@ -803,7 +803,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile) fmt.Fprintf(os.Stderr, "==> Export %s: %s\n", aPkg.PkgPath, pkg.ExportFile)
} }
if IsCheckEnable() { if IsCheckEnable() {
if err, msg := llcCheck(ctx.env, pkg.ExportFile); err != nil { if msg, err := llcCheck(ctx.env, pkg.ExportFile); err != nil {
fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", pkg.PkgPath, pkg.ExportFile, msg) fmt.Fprintf(os.Stderr, "==> lcc %v: %v\n%v\n", pkg.PkgPath, pkg.ExportFile, msg)
} }
} }
@@ -811,7 +811,7 @@ func buildPkg(ctx *context, aPkg *aPackage, verbose bool) error {
return nil return nil
} }
func llcCheck(env *llvm.Env, exportFile string) (err error, msg string) { func llcCheck(env *llvm.Env, exportFile string) (msg string, err error) {
bin := filepath.Join(env.BinDir(), "llc") bin := filepath.Join(env.BinDir(), "llc")
cmd := exec.Command(bin, "-filetype=null", exportFile) cmd := exec.Command(bin, "-filetype=null", exportFile)
var buf bytes.Buffer var buf bytes.Buffer
@@ -904,6 +904,7 @@ func createSSAPkg(prog *ssa.Program, p *packages.Package, verbose bool) *ssa.Pac
return pkgSSA return pkgSSA
} }
/*
var ( var (
// TODO(xsw): complete build flags // TODO(xsw): complete build flags
buildFlags = map[string]bool{ buildFlags = map[string]bool{
@@ -922,6 +923,7 @@ var (
"-ldflags": true, // --ldflags 'flag list': arguments to pass on each go tool link invocation "-ldflags": true, // --ldflags 'flag list': arguments to pass on each go tool link invocation
} }
) )
*/
const llgoDebug = "LLGO_DEBUG" const llgoDebug = "LLGO_DEBUG"
const llgoDbgSyms = "LLGO_DEBUG_SYMBOLS" const llgoDbgSyms = "LLGO_DEBUG_SYMBOLS"

View File

@@ -26,12 +26,14 @@ import (
"github.com/goplus/llgo/internal/packages" "github.com/goplus/llgo/internal/packages"
) )
/*
var ( var (
// TODO(xsw): complete clean flags // TODO(xsw): complete clean flags
cleanFlags = map[string]bool{ cleanFlags = map[string]bool{
"-v": false, // -v: print the paths of packages as they are clean "-v": false, // -v: print the paths of packages as they are clean
} }
) )
*/
func Clean(patterns []string, conf *Config) { func Clean(patterns []string, conf *Config) {
if conf.Goos == "" { if conf.Goos == "" {