export c header file for build library

This commit is contained in:
Li Jie
2025-09-11 14:07:58 +08:00
parent 729e5772a0
commit 3605eeeef7
5 changed files with 1465 additions and 45 deletions

View File

@@ -183,7 +183,9 @@ func (p *context) initFiles(pkgPath string, files []*ast.File, cPkg bool) {
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"))
exportName := strings.TrimPrefix(inPkgName, "X")
p.prog.SetLinkname(fullName, exportName)
p.pkg.SetExport(fullName, exportName)
}
}
case *ast.GenDecl:
@@ -301,19 +303,19 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
directive = "//go:"
)
if strings.HasPrefix(line, linkname) {
p.initLink(line, len(linkname), f)
p.initLink(line, len(linkname), false, f)
return hasLinkname
} else if strings.HasPrefix(line, llgolink2) {
p.initLink(line, len(llgolink2), f)
p.initLink(line, len(llgolink2), false, f)
return hasLinkname
} else if strings.HasPrefix(line, llgolink) {
p.initLink(line, len(llgolink), f)
p.initLink(line, len(llgolink), false, f)
return hasLinkname
} else if strings.HasPrefix(line, export) {
// rewrite //export FuncName to //export FuncName FuncName
funcName := strings.TrimSpace(line[len(export):])
line = line + " " + funcName
p.initLink(line, len(export), f)
p.initLink(line, len(export), true, f)
return hasLinkname
} else if strings.HasPrefix(line, directive) {
// skip unknown annotation but continue to parse the next annotation
@@ -322,13 +324,16 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
return noDirective
}
func (p *context) initLink(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) {
func (p *context) initLink(line string, prefix int, export bool, f func(inPkgName string) (fullName string, isVar, ok bool)) {
text := strings.TrimSpace(line[prefix:])
if idx := strings.IndexByte(text, ' '); idx > 0 {
inPkgName := text[:idx]
if fullName, _, ok := f(inPkgName); ok {
link := strings.TrimLeft(text[idx+1:], " ")
p.prog.SetLinkname(fullName, link)
if export {
p.pkg.SetExport(fullName, link)
}
} else {
fmt.Fprintln(os.Stderr, "==>", line)
fmt.Fprintf(os.Stderr, "llgo: linkname %s not found and ignored\n", inPkgName)

View File

@@ -43,6 +43,7 @@ import (
"github.com/goplus/llgo/internal/env"
"github.com/goplus/llgo/internal/firmware"
"github.com/goplus/llgo/internal/flash"
"github.com/goplus/llgo/internal/header"
"github.com/goplus/llgo/internal/mockable"
"github.com/goplus/llgo/internal/monitor"
"github.com/goplus/llgo/internal/packages"
@@ -375,7 +376,15 @@ func Do(args []string, conf *Config) ([]Package, error) {
// Generate C headers for c-archive and c-shared modes before linking
if ctx.buildConf.BuildMode == BuildModeCArchive || ctx.buildConf.BuildMode == BuildModeCShared {
headerErr := generateCHeader(ctx, pkg, outFmts.Out, verbose)
libname := strings.TrimSuffix(filepath.Base(outFmts.Out), conf.AppExt)
headerPath := filepath.Join(filepath.Dir(outFmts.Out), libname) + ".h"
pkgs := make([]llssa.Package, 0, len(allPkgs))
for _, p := range allPkgs {
if p.LPkg != nil {
pkgs = append(pkgs, p.LPkg)
}
}
headerErr := header.GenHeaderFile(prog, pkgs, libname, headerPath, verbose)
if headerErr != nil {
return nil, headerErr
}
@@ -818,44 +827,6 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
return nil
}
// TODO(lijie): export C header from function list of the pkg
func generateCHeader(ctx *context, pkg *packages.Package, outputPath string, verbose bool) error {
// Determine header file path
headerPath := strings.TrimSuffix(outputPath, filepath.Ext(outputPath)) + ".h"
// Generate header content
headerContent := fmt.Sprintf(`/* Code generated by llgo; DO NOT EDIT. */
#ifndef __%s_H_
#define __%s_H_
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* __%s_H_ */
`,
strings.ToUpper(strings.ReplaceAll(pkg.Name, "-", "_")),
strings.ToUpper(strings.ReplaceAll(pkg.Name, "-", "_")),
strings.ToUpper(strings.ReplaceAll(pkg.Name, "-", "_")))
// Write header file
err := os.WriteFile(headerPath, []byte(headerContent), 0644)
if err != nil {
return fmt.Errorf("failed to write header file %s: %w", headerPath, err)
}
if verbose {
fmt.Fprintf(os.Stderr, "Generated C header: %s\n", headerPath)
}
return nil
}
func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error {
// Handle c-archive mode differently - use ar tool instead of linker
if ctx.buildConf.BuildMode == BuildModeCArchive {

642
internal/header/header.go Normal file
View File

@@ -0,0 +1,642 @@
/*
* Copyright (c) 2024 The GoPlus Authors (goplus.org). 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 header
import (
"bytes"
"fmt"
"go/types"
"io"
"os"
"sort"
"strings"
"github.com/goplus/llgo/ssa"
)
// cheaderWriter handles C header generation with type definition management
type cheaderWriter struct {
p ssa.Program
typeBuf *bytes.Buffer // buffer for type definitions
funcBuf *bytes.Buffer // buffer for function declarations
declaredTypes map[string]bool // track declared types to avoid duplicates
}
// newCHeaderWriter creates a new C header writer
func newCHeaderWriter(p ssa.Program) *cheaderWriter {
return &cheaderWriter{
p: p,
typeBuf: &bytes.Buffer{},
funcBuf: &bytes.Buffer{},
declaredTypes: make(map[string]bool),
}
}
// writeTypedef writes a C typedef for the given Go type if not already declared
func (hw *cheaderWriter) writeTypedef(t types.Type) error {
return hw.writeTypedefRecursive(t, make(map[string]bool))
}
// writeTypedefRecursive writes typedefs recursively, handling dependencies
func (hw *cheaderWriter) writeTypedefRecursive(t types.Type, visiting map[string]bool) error {
// Handle container types that only need element processing
switch typ := t.(type) {
case *types.Array:
return hw.writeTypedefRecursive(typ.Elem(), visiting)
case *types.Slice:
return hw.writeTypedefRecursive(typ.Elem(), visiting)
case *types.Map:
if err := hw.writeTypedefRecursive(typ.Key(), visiting); err != nil {
return err
}
return hw.writeTypedefRecursive(typ.Elem(), visiting)
case *types.Chan:
return hw.writeTypedefRecursive(typ.Elem(), visiting)
}
cType := hw.goCTypeName(t)
if cType == "" || hw.declaredTypes[cType] {
return nil
}
// Prevent infinite recursion for self-referential types
if visiting[cType] {
return nil
}
visiting[cType] = true
defer delete(visiting, cType)
// Process dependent types for complex types
if err := hw.processDependentTypes(t, visiting); err != nil {
return err
}
// Then write the typedef for this type
typedef := hw.generateTypedef(t)
if typedef != "" {
fmt.Fprintln(hw.typeBuf, typedef)
// Add empty line after each type definition
fmt.Fprintln(hw.typeBuf)
hw.declaredTypes[cType] = true
}
return nil
}
// processDependentTypes processes dependent types for composite types
func (hw *cheaderWriter) processDependentTypes(t types.Type, visiting map[string]bool) error {
switch typ := t.(type) {
case *types.Pointer:
return hw.writeTypedefRecursive(typ.Elem(), visiting)
case *types.Struct:
// For anonymous structs, handle field dependencies
for i := 0; i < typ.NumFields(); i++ {
field := typ.Field(i)
if err := hw.writeTypedefRecursive(field.Type(), visiting); err != nil {
return err
}
}
case *types.Named:
// For named types, handle the underlying type dependencies
underlying := typ.Underlying()
if structType, ok := underlying.(*types.Struct); ok {
// For named struct types, handle field dependencies directly
for i := 0; i < structType.NumFields(); i++ {
field := structType.Field(i)
if err := hw.writeTypedefRecursive(field.Type(), visiting); err != nil {
return err
}
}
} else {
// For other named types, handle the underlying type
return hw.writeTypedefRecursive(underlying, visiting)
}
case *types.Signature:
return hw.processSignatureTypes(typ, visiting)
}
return nil
}
// processSignatureTypes processes function signature parameter and result types
func (hw *cheaderWriter) processSignatureTypes(sig *types.Signature, visiting map[string]bool) error {
// Handle function parameters
if sig.Params() != nil {
for i := 0; i < sig.Params().Len(); i++ {
param := sig.Params().At(i)
if err := hw.writeTypedefRecursive(param.Type(), visiting); err != nil {
return err
}
}
}
// Handle function results
if sig.Results() != nil {
for i := 0; i < sig.Results().Len(); i++ {
result := sig.Results().At(i)
if err := hw.writeTypedefRecursive(result.Type(), visiting); err != nil {
return err
}
}
}
return nil
return nil
}
// goCTypeName returns the C type name for a Go type
func (hw *cheaderWriter) goCTypeName(t types.Type) string {
switch typ := t.(type) {
case *types.Basic:
switch typ.Kind() {
case types.Invalid:
return ""
case types.Bool:
return "_Bool"
case types.Int8:
return "int8_t"
case types.Uint8:
return "uint8_t"
case types.Int16:
return "int16_t"
case types.Uint16:
return "uint16_t"
case types.Int32:
return "int32_t"
case types.Uint32:
return "uint32_t"
case types.Int64:
return "int64_t"
case types.Uint64:
return "uint64_t"
case types.Int:
return "intptr_t"
case types.Uint:
return "uintptr_t"
case types.Uintptr:
return "uintptr_t"
case types.Float32:
return "float"
case types.Float64:
return "double"
case types.Complex64:
return "GoComplex64"
case types.Complex128:
return "GoComplex128"
case types.String:
return "GoString"
case types.UnsafePointer:
return "void*"
}
case *types.Pointer:
elemType := hw.goCTypeName(typ.Elem())
if elemType == "" {
return "void*"
}
return elemType + "*"
case *types.Slice:
return "GoSlice"
case *types.Array:
// For arrays, we return just the element type
// The array size will be handled in field generation
return hw.goCTypeName(typ.Elem())
case *types.Map:
return "GoMap"
case *types.Chan:
return "GoChan"
case *types.Interface:
return "GoInterface"
case *types.Struct:
// For anonymous structs, generate a descriptive name
var fields []string
for i := 0; i < typ.NumFields(); i++ {
field := typ.Field(i)
fieldType := hw.goCTypeName(field.Type())
fields = append(fields, fmt.Sprintf("%s_%s", fieldType, field.Name()))
}
return fmt.Sprintf("struct_%s", strings.Join(fields, "_"))
case *types.Named:
// For named types, always use the named type
pkg := typ.Obj().Pkg()
return fmt.Sprintf("%s_%s", pkg.Name(), typ.Obj().Name())
case *types.Signature:
// Function types are represented as function pointers in C
// For simplicity, we use void* to represent function pointers
return "void*"
}
panic(fmt.Errorf("unsupported type: %v", t))
}
// generateTypedef generates C typedef declaration for complex types
func (hw *cheaderWriter) generateTypedef(t types.Type) string {
switch typ := t.(type) {
case *types.Struct:
// Only generate typedef for anonymous structs
return hw.generateStructTypedef(typ)
case *types.Named:
underlying := typ.Underlying()
if structType, ok := underlying.(*types.Struct); ok {
// For named struct types, generate the typedef directly
return hw.generateNamedStructTypedef(typ, structType)
}
// For other named types, create a typedef to the underlying type
underlyingCType := hw.goCTypeName(underlying)
if underlyingCType != "" {
cTypeName := hw.goCTypeName(typ)
return fmt.Sprintf("typedef %s %s;", underlyingCType, cTypeName)
}
}
return ""
}
// generateReturnType generates C return type, converting arrays to struct wrappers
func (hw *cheaderWriter) generateReturnType(retType types.Type) string {
switch typ := retType.(type) {
case *types.Array:
// For array return values, generate a struct wrapper
return hw.ensureArrayStruct(typ)
default:
// For non-array types, use regular type conversion
return hw.goCTypeName(retType)
}
}
// ensureArrayStruct generates array struct name and ensures its typedef is declared
func (hw *cheaderWriter) ensureArrayStruct(arr *types.Array) string {
// Generate struct name
var dimensions []int64
baseType := types.Type(arr)
// Traverse all array dimensions
for {
if a, ok := baseType.(*types.Array); ok {
dimensions = append(dimensions, a.Len())
baseType = a.Elem()
} else {
break
}
}
// Get base element type
elemType := hw.goCTypeName(baseType)
// Generate struct name: Array_int32_t_4 for [4]int32, Array_int32_t_3_4 for [3][4]int32
var name strings.Builder
name.WriteString("Array_")
name.WriteString(strings.ReplaceAll(elemType, "*", "_ptr"))
for _, dim := range dimensions {
name.WriteString(fmt.Sprintf("_%d", dim))
}
structName := name.String()
// Ensure typedef is declared
if !hw.declaredTypes[structName] {
hw.declaredTypes[structName] = true
// Generate field declaration for the array
fieldDecl := hw.generateFieldDeclaration(arr, "data")
// Write the typedef
typedef := fmt.Sprintf("typedef struct {\n%s\n} %s;", fieldDecl, structName)
fmt.Fprintf(hw.typeBuf, "%s\n\n", typedef)
}
return structName
}
// generateFieldDeclaration generates C field declaration with correct array syntax
func (hw *cheaderWriter) generateFieldDeclaration(fieldType types.Type, fieldName string) string {
switch fieldType.(type) {
case *types.Array:
// Handle multidimensional arrays by collecting all dimensions
var dimensions []int64
baseType := fieldType
// Traverse all array dimensions
for {
if arr, ok := baseType.(*types.Array); ok {
dimensions = append(dimensions, arr.Len())
baseType = arr.Elem()
} else {
break
}
}
// Get base element type
elemType := hw.goCTypeName(baseType)
// Build array dimensions string [d1][d2][d3]...
var dimStr strings.Builder
for _, dim := range dimensions {
dimStr.WriteString(fmt.Sprintf("[%d]", dim))
}
return fmt.Sprintf(" %s %s%s;", elemType, fieldName, dimStr.String())
default:
cType := hw.goCTypeName(fieldType)
return fmt.Sprintf(" %s %s;", cType, fieldName)
}
}
// generateStructTypedef generates typedef for anonymous struct
func (hw *cheaderWriter) generateStructTypedef(s *types.Struct) string {
// Generate descriptive type name inline
var nameFields []string
var declFields []string
for i := 0; i < s.NumFields(); i++ {
field := s.Field(i)
fieldType := hw.goCTypeName(field.Type())
nameFields = append(nameFields, fmt.Sprintf("%s_%s", fieldType, field.Name()))
declFields = append(declFields, hw.generateFieldDeclaration(field.Type(), field.Name()))
}
typeName := fmt.Sprintf("struct_%s", strings.Join(nameFields, "_"))
return fmt.Sprintf("typedef struct {\n%s\n} %s;", strings.Join(declFields, "\n"), typeName)
}
// generateNamedStructTypedef generates typedef for named struct
func (hw *cheaderWriter) generateNamedStructTypedef(named *types.Named, s *types.Struct) string {
typeName := hw.goCTypeName(named)
// Check if this is a self-referential struct
needsForwardDecl := hw.needsForwardDeclaration(s, typeName)
var result string
if needsForwardDecl {
// Add forward declaration
result = fmt.Sprintf("typedef struct %s %s;\n", typeName, typeName)
}
var fields []string
for i := 0; i < s.NumFields(); i++ {
field := s.Field(i)
fields = append(fields, hw.generateFieldDeclaration(field.Type(), field.Name()))
}
if needsForwardDecl {
// Use struct tag in definition
result += fmt.Sprintf("struct %s {\n%s\n};", typeName, strings.Join(fields, "\n"))
} else {
result = fmt.Sprintf("typedef struct {\n%s\n} %s;", strings.Join(fields, "\n"), typeName)
}
return result
}
// needsForwardDeclaration checks if a struct needs forward declaration due to self-reference
func (hw *cheaderWriter) needsForwardDeclaration(s *types.Struct, typeName string) bool {
for i := 0; i < s.NumFields(); i++ {
field := s.Field(i)
if hw.typeReferencesSelf(field.Type(), typeName) {
return true
}
}
return false
}
// typeReferencesSelf checks if a type references the given type name
func (hw *cheaderWriter) typeReferencesSelf(t types.Type, selfTypeName string) bool {
switch typ := t.(type) {
case *types.Pointer:
elemTypeName := hw.goCTypeName(typ.Elem())
return elemTypeName == selfTypeName
case *types.Slice:
elemTypeName := hw.goCTypeName(typ.Elem())
return elemTypeName == selfTypeName
case *types.Array:
elemTypeName := hw.goCTypeName(typ.Elem())
return elemTypeName == selfTypeName
case *types.Named:
return hw.goCTypeName(typ) == selfTypeName
}
return false
}
// writeFunctionDecl writes C function declaration for exported Go function
// fullName: the C function name to display in header
// linkName: the actual Go function name for linking
func (hw *cheaderWriter) writeFunctionDecl(fullName, linkName string, fn ssa.Function) error {
if fn.IsNil() {
return nil
}
// Get Go signature from LLVM function type
goType := fn.Type.RawType()
sig, ok := goType.(*types.Signature)
if !ok {
return fmt.Errorf("function %s does not have signature type", fullName)
}
// Generate return type
var returnType string
if sig.Results().Len() == 0 {
returnType = "void"
} else if sig.Results().Len() == 1 {
retType := sig.Results().At(0).Type()
if err := hw.writeTypedef(retType); err != nil {
return err
}
returnType = hw.generateReturnType(retType)
} else {
return fmt.Errorf("function %s has more than one result", fullName)
}
// Generate parameters
var params []string
for i := 0; i < sig.Params().Len(); i++ {
param := sig.Params().At(i)
paramType := param.Type()
if err := hw.writeTypedef(paramType); err != nil {
return err
}
paramName := param.Name()
if paramName == "" {
paramName = fmt.Sprintf("param%d", i)
}
// Use generateFieldDeclaration logic for consistent parameter syntax
paramDecl := hw.generateFieldDeclaration(paramType, paramName)
// Remove the leading spaces and semicolon to get just the declaration
paramDecl = strings.TrimSpace(paramDecl)
paramDecl = strings.TrimSuffix(paramDecl, ";")
params = append(params, paramDecl)
}
paramStr := strings.Join(params, ", ")
if paramStr == "" {
paramStr = "void"
}
// Write function declaration with return type on separate line for normal functions
fmt.Fprintln(hw.funcBuf, returnType)
// Generate function declaration using cross-platform macro when names differ
var funcDecl string
if fullName != linkName {
funcDecl = fmt.Sprintf("%s(%s) GO_SYMBOL_RENAME(\"%s\")", fullName, paramStr, linkName)
} else {
funcDecl = fmt.Sprintf("%s(%s);", fullName, paramStr)
}
fmt.Fprintln(hw.funcBuf, funcDecl)
// Add empty line after each function declaration
fmt.Fprintln(hw.funcBuf)
return nil
}
// writeCommonIncludes writes common C header includes and Go runtime type definitions
func (hw *cheaderWriter) writeCommonIncludes() error {
includes := `
// Platform-specific symbol renaming macro
#ifdef __APPLE__
#define GO_SYMBOL_RENAME(go_name) __asm("_" go_name);
#else
#define GO_SYMBOL_RENAME(go_name) __asm(go_name);
#endif
// Go runtime types
typedef struct { const char *p; intptr_t n; } GoString;
typedef struct { void *data; intptr_t len; intptr_t cap; } GoSlice;
typedef struct { void *data; } GoMap;
typedef struct { void *data; } GoChan;
typedef struct { void *data; void *type; } GoInterface;
typedef struct { float real; float imag; } GoComplex64;
typedef struct { double real; double imag; } GoComplex128;
`
if _, err := hw.typeBuf.WriteString(includes); err != nil {
return err
}
return nil
}
// writeTo writes all generated content to the output writer
func (hw *cheaderWriter) writeTo(w io.Writer) error {
// Write type definitions first
if hw.typeBuf.Len() > 0 {
if _, err := hw.typeBuf.WriteTo(w); err != nil {
return err
}
}
// Then write function declarations
if hw.funcBuf.Len() > 0 {
if _, err := hw.funcBuf.WriteTo(w); err != nil {
return err
}
}
return nil
}
func genHeader(p ssa.Program, pkgs []ssa.Package, w io.Writer) error {
hw := newCHeaderWriter(p)
// Write common header includes and type definitions
if err := hw.writeCommonIncludes(); err != nil {
return err
}
// Mark predefined Go types as declared
hw.declaredTypes["GoString"] = true
hw.declaredTypes["GoSlice"] = true
hw.declaredTypes["GoMap"] = true
hw.declaredTypes["GoChan"] = true
hw.declaredTypes["GoInterface"] = true
hw.declaredTypes["GoComplex64"] = true
hw.declaredTypes["GoComplex128"] = true
// Process all exported functions
for _, pkg := range pkgs {
exports := pkg.ExportFuncs()
// Sort functions for testing
exportNames := make([]string, 0, len(exports))
for name := range exports {
exportNames = append(exportNames, name)
}
sort.Strings(exportNames)
for _, name := range exportNames { // name is goName
link := exports[name] // link is cName
fn := pkg.FuncOf(link)
if fn == nil {
continue
}
// Write function declaration with proper C types
if err := hw.writeFunctionDecl(link, link, fn); err != nil {
return fmt.Errorf("failed to write declaration for function %s: %w", name, err)
}
}
initFnName := pkg.Path() + ".init"
initFn := pkg.FuncOf(initFnName)
if initFn != nil {
// Generate C-compatible function name (replace . and / with _)
cInitFnName := strings.ReplaceAll(strings.ReplaceAll(initFnName, ".", "_"), "/", "_")
if err := hw.writeFunctionDecl(cInitFnName, initFnName, initFn); err != nil {
return fmt.Errorf("failed to write declaration for function %s: %w", initFnName, err)
}
}
}
// Write all content to output in the correct order
return hw.writeTo(w)
}
func GenHeaderFile(p ssa.Program, pkgs []ssa.Package, libName, headerPath string, verbose bool) error {
// Write header file
w, err := os.Create(headerPath)
if err != nil {
return fmt.Errorf("failed to write header file %s: %w", headerPath, err)
}
defer w.Close()
if verbose {
fmt.Fprintf(os.Stderr, "Generated C header: %s\n", headerPath)
}
headerIdent := strings.ToUpper(strings.ReplaceAll(libName, "-", "_"))
headerContent := fmt.Sprintf(`/* Code generated by llgo; DO NOT EDIT. */
#ifndef __%s_H_
#define __%s_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
`, headerIdent, headerIdent)
w.Write([]byte(headerContent))
if err = genHeader(p, pkgs, w); err != nil {
return fmt.Errorf("failed to generate header content for %s: %w", libName, err)
}
footerContent := fmt.Sprintf(`
#ifdef __cplusplus
}
#endif
#endif /* __%s_H_ */
`, headerIdent)
_, err = w.Write([]byte(footerContent))
return err
}

View File

@@ -0,0 +1,791 @@
//go:build !llgo
// +build !llgo
package header
import (
"bytes"
"go/token"
"go/types"
"os"
"strings"
"testing"
"github.com/goplus/gogen/packages"
"github.com/goplus/llgo/ssa"
"github.com/goplus/llvm"
)
func init() {
llvm.InitializeAllTargets()
llvm.InitializeAllTargetMCs()
llvm.InitializeAllTargetInfos()
llvm.InitializeAllAsmParsers()
llvm.InitializeAllAsmPrinters()
}
func TestGenCHeaderExport(t *testing.T) {
prog := ssa.NewProgram(nil)
prog.SetRuntime(func() *types.Package {
fset := token.NewFileSet()
imp := packages.NewImporter(fset)
pkg, _ := imp.Import(ssa.PkgRuntime)
return pkg
})
// Define main package and the 'Foo' type within it
mainPkgPath := "github.com/goplus/llgo/test_buildmode/main"
mainTypesPkg := types.NewPackage(mainPkgPath, "main")
fooFields := []*types.Var{
types.NewField(token.NoPos, mainTypesPkg, "a", types.Typ[types.Int], false),
types.NewField(token.NoPos, mainTypesPkg, "b", types.Typ[types.Float64], false),
}
fooStruct := types.NewStruct(fooFields, nil)
fooTypeName := types.NewTypeName(token.NoPos, mainTypesPkg, "Foo", nil)
fooNamed := types.NewNamed(fooTypeName, fooStruct, nil)
mainTypesPkg.Scope().Insert(fooTypeName)
// Create SSA package for main
mainPkg := prog.NewPackage("main", mainPkgPath)
// Define exported functions in mainPkg
mainPkg.NewFunc("HelloWorld", types.NewSignatureType(nil, nil, nil, nil, nil, false), ssa.InGo)
useFooPtrParams := types.NewTuple(types.NewVar(token.NoPos, nil, "f", types.NewPointer(fooNamed)))
useFooPtrResults := types.NewTuple(types.NewVar(token.NoPos, nil, "", fooNamed))
useFooPtrSig := types.NewSignatureType(nil, nil, nil, useFooPtrParams, useFooPtrResults, false)
mainPkg.NewFunc("UseFooPtr", useFooPtrSig, ssa.InGo)
useFooParams := types.NewTuple(types.NewVar(token.NoPos, nil, "f", fooNamed))
useFooSig := types.NewSignatureType(nil, nil, nil, useFooParams, useFooPtrResults, false)
mainPkg.NewFunc("UseFoo", useFooSig, ssa.InGo)
// Set exports for main
mainPkg.SetExport("HelloWorld", "HelloWorld")
mainPkg.SetExport("UseFooPtr", "UseFooPtr")
mainPkg.SetExport("UseFoo", "UseFoo")
// Create package C
cPkgPath := "github.com/goplus/llgo/test_buildmode/bar"
cPkg := prog.NewPackage("C", cPkgPath)
addParams := types.NewTuple(
types.NewVar(token.NoPos, nil, "a", types.Typ[types.Int]),
types.NewVar(token.NoPos, nil, "b", types.Typ[types.Int]))
addResults := types.NewTuple(types.NewVar(token.NoPos, nil, "", types.Typ[types.Int]))
addSig := types.NewSignatureType(nil, nil, nil, addParams, addResults, false)
cPkg.NewFunc("Add", addSig, ssa.InGo)
cPkg.NewFunc("Sub", addSig, ssa.InGo)
cPkg.SetExport("XAdd", "Add")
cPkg.SetExport("XSub", "Sub")
// Generate header
libname := "testbuild"
headerPath := os.TempDir() + "/testbuild.h"
err := GenHeaderFile(prog, []ssa.Package{mainPkg, cPkg}, libname, headerPath, true)
if err != nil {
t.Fatal(err)
}
data, err := os.ReadFile(headerPath)
if err != nil {
t.Fatal(err)
}
required := []string{
"/* Code generated by llgo; DO NOT EDIT. */",
"#ifndef __TESTBUILD_H_",
"#include <stdbool.h>",
"typedef struct { const char *p; intptr_t n; } GoString;",
"typedef struct {\n intptr_t a;\n double b;\n} main_Foo;",
"void\nHelloWorld(void);",
"main_Foo\nUseFooPtr(main_Foo* f);",
"main_Foo\nUseFoo(main_Foo f);",
"intptr_t\nAdd(intptr_t a, intptr_t b);",
"intptr_t\nSub(intptr_t a, intptr_t b);",
"#endif /* __TESTBUILD_H_ */",
}
got := string(data)
for _, sub := range required {
if !strings.Contains(got, sub) {
t.Fatalf("Generated content: %s\n", got)
t.Fatalf("Generated header is missing expected content:\n%s", sub)
}
}
}
func TestCheaderWriterTypes(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test complex integration scenarios only - basic types are covered by TestGoCTypeName
tests := []struct {
name string
goType types.Type
expected string
}{
{
name: "named struct",
goType: func() types.Type {
pkg := types.NewPackage("main", "main")
s := types.NewStruct([]*types.Var{types.NewField(0, nil, "f1", types.Typ[types.Int], false)}, nil)
return types.NewNamed(types.NewTypeName(0, pkg, "MyStruct", nil), s, nil)
}(),
expected: "typedef struct {\n intptr_t f1;\n} main_MyStruct;\n\n",
},
{
name: "struct with array field",
goType: func() types.Type {
arrayType := types.NewArray(types.Typ[types.Float64], 10)
return types.NewStruct([]*types.Var{
types.NewField(0, nil, "Values", arrayType, false),
}, nil)
}(),
expected: "typedef struct {\n double Values[10];\n} struct_double_Values;\n\n",
},
{
name: "struct with multidimensional array",
goType: func() types.Type {
// Create a 2D array: [4][3]int32
innerArrayType := types.NewArray(types.Typ[types.Int32], 3)
outerArrayType := types.NewArray(innerArrayType, 4)
return types.NewStruct([]*types.Var{
types.NewField(0, nil, "Matrix", outerArrayType, false),
}, nil)
}(),
expected: "typedef struct {\n int32_t Matrix[4][3];\n} struct_int32_t_Matrix;\n\n",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
hw.typeBuf.Reset()
hw.declaredTypes = make(map[string]bool) // Reset declared types for each run
// Mark predefined Go types as declared (same as in genHeader)
hw.declaredTypes["GoString"] = true
hw.declaredTypes["GoSlice"] = true
hw.declaredTypes["GoMap"] = true
hw.declaredTypes["GoChan"] = true
hw.declaredTypes["GoInterface"] = true
hw.declaredTypes["GoComplex64"] = true
hw.declaredTypes["GoComplex128"] = true
if err := hw.writeTypedef(tt.goType); err != nil {
t.Fatalf("writeTypedef() error = %v", err)
}
got := hw.typeBuf.String()
if got != tt.expected {
t.Errorf("writeTypedef() got = %q, want %q", got, tt.expected)
}
})
}
}
// Test for goCTypeName function to cover all basic types
func TestGoCTypeName(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
tests := []struct {
name string
goType types.Type
expected string
}{
{name: "bool", goType: types.Typ[types.Bool], expected: "_Bool"},
{name: "int8", goType: types.Typ[types.Int8], expected: "int8_t"},
{name: "uint8", goType: types.Typ[types.Uint8], expected: "uint8_t"},
{name: "int16", goType: types.Typ[types.Int16], expected: "int16_t"},
{name: "uint16", goType: types.Typ[types.Uint16], expected: "uint16_t"},
{name: "int32", goType: types.Typ[types.Int32], expected: "int32_t"},
{name: "uint32", goType: types.Typ[types.Uint32], expected: "uint32_t"},
{name: "int64", goType: types.Typ[types.Int64], expected: "int64_t"},
{name: "uint64", goType: types.Typ[types.Uint64], expected: "uint64_t"},
{name: "int", goType: types.Typ[types.Int], expected: "intptr_t"},
{name: "uint", goType: types.Typ[types.Uint], expected: "uintptr_t"},
{name: "uintptr", goType: types.Typ[types.Uintptr], expected: "uintptr_t"},
{name: "float32", goType: types.Typ[types.Float32], expected: "float"},
{name: "float64", goType: types.Typ[types.Float64], expected: "double"},
{name: "complex64", goType: types.Typ[types.Complex64], expected: "GoComplex64"},
{name: "complex128", goType: types.Typ[types.Complex128], expected: "GoComplex128"},
{name: "string", goType: types.Typ[types.String], expected: "GoString"},
{name: "unsafe pointer", goType: types.Typ[types.UnsafePointer], expected: "void*"},
{name: "slice", goType: types.NewSlice(types.Typ[types.Int]), expected: "GoSlice"},
{name: "map", goType: types.NewMap(types.Typ[types.String], types.Typ[types.Int]), expected: "GoMap"},
{name: "chan", goType: types.NewChan(types.SendRecv, types.Typ[types.Int]), expected: "GoChan"},
{name: "interface", goType: types.NewInterfaceType(nil, nil), expected: "GoInterface"},
{
name: "array",
goType: types.NewArray(types.Typ[types.Int], 5),
expected: "intptr_t",
},
{
name: "pointer to int",
goType: types.NewPointer(types.Typ[types.Int]),
expected: "intptr_t*",
},
{
name: "pointer to unknown type",
goType: types.NewPointer(types.Typ[types.Invalid]),
expected: "void*",
},
{
name: "array of unknown type",
goType: types.NewArray(types.Typ[types.Invalid], 3),
expected: "",
},
{
name: "signature type",
goType: types.NewSignature(nil, nil, nil, false),
expected: "void*",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hw.goCTypeName(tt.goType)
if got != tt.expected {
t.Errorf("goCTypeName() = %q, want %q", got, tt.expected)
}
})
}
}
// Test typeReferencesSelf function
func TestTypeReferencesSelf(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create a named type for testing
nodeTypeName := types.NewTypeName(0, pkg, "Node", nil)
nodeStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "data", types.Typ[types.Int], false),
}, nil)
namedNode := types.NewNamed(nodeTypeName, nodeStruct, nil)
tests := []struct {
name string
typ types.Type
selfTypeName string
expected bool
}{
{
name: "pointer to self",
typ: types.NewPointer(namedNode),
selfTypeName: "test_Node",
expected: true,
},
{
name: "slice of self",
typ: types.NewSlice(namedNode),
selfTypeName: "test_Node",
expected: true,
},
{
name: "array of self",
typ: types.NewArray(namedNode, 5),
selfTypeName: "test_Node",
expected: true,
},
{
name: "named type self",
typ: namedNode,
selfTypeName: "test_Node",
expected: true,
},
{
name: "basic type not self",
typ: types.Typ[types.Int],
selfTypeName: "test_Node",
expected: false,
},
{
name: "different named type",
typ: namedNode,
selfTypeName: "other_Type",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hw.typeReferencesSelf(tt.typ, tt.selfTypeName)
if got != tt.expected {
t.Errorf("typeReferencesSelf() = %v, want %v", got, tt.expected)
}
})
}
}
// Test array struct generation functions
func TestArrayStructGeneration(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test ensureArrayStruct
arrayType := types.NewArray(types.Typ[types.Int32], 5)
name := hw.ensureArrayStruct(arrayType)
expectedName := "Array_int32_t_5"
if name != expectedName {
t.Errorf("ensureArrayStruct() = %q, want %q", name, expectedName)
}
// Test that typedef was generated
output := hw.typeBuf.String()
if !strings.Contains(output, "typedef struct") {
t.Error("ensureArrayStruct should generate typedef")
}
if !strings.Contains(output, "int32_t data[5]") {
t.Error("ensureArrayStruct should generate correct array field")
}
// Test duplicate prevention
hw.typeBuf.Reset()
name2 := hw.ensureArrayStruct(arrayType) // Call again
if name2 != name {
t.Errorf("ensureArrayStruct should return same name for same type")
}
duplicateOutput := hw.typeBuf.String()
if duplicateOutput != "" {
t.Error("ensureArrayStruct should not generate duplicate typedef")
}
}
// Test generateReturnType function
func TestGenerateReturnType(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test basic type
basicRet := hw.generateReturnType(types.Typ[types.Int32])
if basicRet != "int32_t" {
t.Errorf("generateReturnType(int32) = %q, want %q", basicRet, "int32_t")
}
// Test array type (should generate struct wrapper)
arrayType := types.NewArray(types.Typ[types.Float64], 3)
arrayRet := hw.generateReturnType(arrayType)
expectedArrayRet := "Array_double_3"
if arrayRet != expectedArrayRet {
t.Errorf("generateReturnType(array) = %q, want %q", arrayRet, expectedArrayRet)
}
}
// Test generateTypedef function
func TestGenerateTypedef(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Test named struct
structType := types.NewStruct([]*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
}, nil)
namedType := types.NewNamed(types.NewTypeName(0, pkg, "TestStruct", nil), structType, nil)
typedef := hw.generateTypedef(namedType)
if !strings.Contains(typedef, "typedef struct") {
t.Error("generateTypedef should generate typedef for named struct")
}
// Test named basic type
namedInt := types.NewNamed(types.NewTypeName(0, pkg, "MyInt", nil), types.Typ[types.Int], nil)
typedef2 := hw.generateTypedef(namedInt)
if !strings.Contains(typedef2, "typedef intptr_t test_MyInt") {
t.Error("generateTypedef should generate typedef for named basic type")
}
}
// Test complex nested structures and dependencies
func TestComplexNestedStructures(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Create a complex nested structure
pkg := types.NewPackage("test", "test")
// Inner struct
innerStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
}, nil)
// Named inner struct
innerTypeName := types.NewTypeName(0, pkg, "InnerStruct", nil)
namedInner := types.NewNamed(innerTypeName, innerStruct, nil)
// Outer struct with inner struct field
outerStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "inner", namedInner, false),
types.NewField(0, nil, "ptr", types.NewPointer(namedInner), false),
types.NewField(0, nil, "slice", types.NewSlice(namedInner), false),
}, nil)
outerTypeName := types.NewTypeName(0, pkg, "OuterStruct", nil)
namedOuter := types.NewNamed(outerTypeName, outerStruct, nil)
// Test writeTypedef for complex structure
err := hw.writeTypedef(namedOuter)
if err != nil {
t.Fatalf("writeTypedef() error = %v", err)
}
output := hw.typeBuf.String()
// Should contain both inner and outer struct definitions
if !strings.Contains(output, "test_InnerStruct") {
t.Error("Expected inner struct typedef")
}
if !strings.Contains(output, "test_OuterStruct") {
t.Error("Expected outer struct typedef")
}
}
// Test goCTypeName with more type cases
// Test processDependentTypes for error paths and edge cases
func TestProcessDependentTypesEdgeCases(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test signature type dependency (function parameters and results)
params := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
results := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String]))
sigType := types.NewSignatureType(nil, nil, nil, params, results, false)
err := hw.processDependentTypes(sigType, make(map[string]bool))
if err != nil {
t.Errorf("processDependentTypes(signature) error = %v", err)
}
// Test processSignatureTypes directly
err = hw.processSignatureTypes(sigType, make(map[string]bool))
if err != nil {
t.Errorf("processSignatureTypes error = %v", err)
}
// Test Map type - this should trigger the Map case in writeTypedefRecursive
mapType := types.NewMap(types.Typ[types.String], types.Typ[types.Int])
err = hw.writeTypedefRecursive(mapType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(map) error = %v", err)
}
// Test Chan type - this should trigger the Chan case in writeTypedefRecursive
chanType := types.NewChan(types.SendRecv, types.Typ[types.Bool])
err = hw.writeTypedefRecursive(chanType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(chan) error = %v", err)
}
// Test Map with complex types to trigger both key and value processing
struct1 := types.NewStruct([]*types.Var{
types.NewField(0, nil, "key", types.Typ[types.String], false),
}, nil)
struct2 := types.NewStruct([]*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
}, nil)
complexMapType := types.NewMap(struct1, struct2)
err = hw.writeTypedefRecursive(complexMapType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(complex map) error = %v", err)
}
// Test function signature with no parameters (edge case)
noParamsSig := types.NewSignatureType(nil, nil, nil, nil, results, false)
err = hw.processSignatureTypes(noParamsSig, make(map[string]bool))
if err != nil {
t.Errorf("processSignatureTypes(no params) error = %v", err)
}
// Test function signature with no results (edge case)
noResultsSig := types.NewSignatureType(nil, nil, nil, params, nil, false)
err = hw.processSignatureTypes(noResultsSig, make(map[string]bool))
if err != nil {
t.Errorf("processSignatureTypes(no results) error = %v", err)
}
}
// Test generateNamedStructTypedef with forward declaration
func TestGenerateNamedStructTypedefWithForwardDecl(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create a self-referential struct that needs forward declaration
nodeName := types.NewTypeName(0, pkg, "Node", nil)
nodeNamed := types.NewNamed(nodeName, nil, nil)
// Create fields including a pointer to itself
fields := []*types.Var{
types.NewField(0, nil, "value", types.Typ[types.Int], false),
types.NewField(0, nil, "next", types.NewPointer(nodeNamed), false),
}
nodeStruct := types.NewStruct(fields, nil)
nodeNamed.SetUnderlying(nodeStruct)
// Test generateNamedStructTypedef
result := hw.generateNamedStructTypedef(nodeNamed, nodeStruct)
// Should contain forward declaration (with package prefix)
if !strings.Contains(result, "typedef struct test_Node test_Node;") {
t.Errorf("Expected forward declaration in result: %s", result)
}
// Should contain the actual struct definition
if !strings.Contains(result, "struct test_Node {") {
t.Errorf("Expected struct definition in result: %s", result)
}
}
// Test self-referential structures to ensure no infinite recursion
func TestSelfReferentialStructure(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create a self-referential struct: Node with a pointer to itself
nodeTypeName := types.NewTypeName(0, pkg, "Node", nil)
nodeStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "data", types.Typ[types.Int], false),
}, nil)
namedNode := types.NewNamed(nodeTypeName, nodeStruct, nil)
// Add a self-referential field after creating the named type
nodeStructWithPtr := types.NewStruct([]*types.Var{
types.NewField(0, nil, "data", types.Typ[types.Int], false),
types.NewField(0, nil, "next", types.NewPointer(namedNode), false),
}, nil)
// Create a new named type with the updated struct
nodeTypeNameFinal := types.NewTypeName(0, pkg, "SelfRefNode", nil)
namedNodeFinal := types.NewNamed(nodeTypeNameFinal, nodeStructWithPtr, nil)
// This should not cause infinite recursion
err := hw.writeTypedef(namedNodeFinal)
if err != nil {
t.Fatalf("writeTypedef() error = %v", err)
}
output := hw.typeBuf.String()
if !strings.Contains(output, "test_SelfRefNode") {
t.Error("Expected self-referential struct typedef")
}
}
// Test function signature dependencies
func TestFunctionSignatureDependencies(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
pkg := types.NewPackage("test", "test")
// Create struct type for function parameters
paramStruct := types.NewStruct([]*types.Var{
types.NewField(0, nil, "id", types.Typ[types.Int], false),
}, nil)
paramTypeName := types.NewTypeName(0, pkg, "ParamStruct", nil)
namedParam := types.NewNamed(paramTypeName, paramStruct, nil)
// Create function signature with struct parameters and return values
params := types.NewTuple(
types.NewVar(0, nil, "input", namedParam),
types.NewVar(0, nil, "count", types.Typ[types.Int]),
)
results := types.NewTuple(
types.NewVar(0, nil, "output", namedParam),
)
funcSig := types.NewSignatureType(nil, nil, nil, params, results, false)
// Test that function signature dependencies are processed
err := hw.processDependentTypes(funcSig, make(map[string]bool))
if err != nil {
t.Fatalf("processDependentTypes() error = %v", err)
}
// Test named basic type alias (should trigger the "else" branch in processDependentTypes)
namedInt := types.NewNamed(types.NewTypeName(0, pkg, "MyInt", nil), types.Typ[types.Int], nil)
err = hw.processDependentTypes(namedInt, make(map[string]bool))
if err != nil {
t.Errorf("processDependentTypes(named int) error = %v", err)
}
// Test duplicate type prevention - mark type as already declared
hw2 := newCHeaderWriter(prog)
hw2.declaredTypes["test_DuplicateType"] = true
duplicateType := types.NewNamed(
types.NewTypeName(0, pkg, "DuplicateType", nil),
types.Typ[types.Int],
nil,
)
err = hw2.writeTypedefRecursive(duplicateType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(duplicate) error = %v", err)
}
if strings.Contains(hw2.typeBuf.String(), "DuplicateType") {
t.Error("Should not generate typedef for already declared type")
}
// Test visiting map to prevent infinite recursion
visiting := make(map[string]bool)
visiting["test_MyInt"] = true // Mark as visiting
err = hw.writeTypedefRecursive(namedInt, visiting)
if err != nil {
t.Errorf("writeTypedefRecursive(already visiting) error = %v", err)
}
// Test invalid type (should trigger cType == "" path)
invalidType := types.Typ[types.Invalid]
err = hw.writeTypedefRecursive(invalidType, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(invalid) error = %v", err)
}
}
// Test error conditions and edge cases
func TestEdgeCasesAndErrorConditions(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Test with properly initialized but empty function - skip nil test since it causes panic
// This would require creating a complete Function object which is complex
// Test writeCommonIncludes
hw.typeBuf.Reset()
err := hw.writeCommonIncludes()
if err != nil {
t.Fatalf("writeCommonIncludes() error = %v", err)
}
output := hw.typeBuf.String()
expectedIncludes := []string{
"GoString",
"GoSlice",
"GoMap",
"GoChan",
"GoInterface",
}
for _, expected := range expectedIncludes {
if !strings.Contains(output, expected) {
t.Errorf("Expected %s in common includes", expected)
}
}
}
// Test writeTo function
func TestWriteTo(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
// Add some content to both buffers
hw.typeBuf.WriteString("typedef struct { int x; } TestStruct;\n")
hw.funcBuf.WriteString("void TestFunction(void);\n")
var output bytes.Buffer
err := hw.writeTo(&output)
if err != nil {
t.Fatalf("writeTo() error = %v", err)
}
got := output.String()
if !strings.Contains(got, "TestStruct") {
t.Error("writeTo() should write type definitions")
}
if !strings.Contains(got, "TestFunction") {
t.Error("writeTo() should write function declarations")
}
}
// Test genHeader function
func TestGenHeader(t *testing.T) {
prog := ssa.NewProgram(nil)
// Create a mock package
pkg := prog.NewPackage("", "testpkg")
var output bytes.Buffer
err := genHeader(prog, []ssa.Package{pkg}, &output)
if err != nil {
t.Fatalf("genHeader() error = %v", err)
}
got := output.String()
if !strings.Contains(got, "GoString") {
t.Error("genHeader() should include Go runtime types")
}
}
// Test GenCHeader function
func TestGenCHeader(t *testing.T) {
prog := ssa.NewProgram(nil)
// Create a mock package
pkg := prog.NewPackage("", "testpkg")
// Create a temp file for output
tmpfile, err := os.CreateTemp("", "test_header_*.h")
if err != nil {
t.Fatalf("Failed to create temp file: %v", err)
}
defer os.Remove(tmpfile.Name())
defer tmpfile.Close()
err = GenHeaderFile(prog, []ssa.Package{pkg}, "testlib", tmpfile.Name(), false)
if err != nil {
t.Fatalf("GenCHeader() error = %v", err)
}
// Read the file and verify content
content, err := os.ReadFile(tmpfile.Name())
if err != nil {
t.Fatalf("Failed to read generated file: %v", err)
}
got := string(content)
if !strings.Contains(got, "#ifndef") {
t.Error("GenCHeader() should generate header guards")
}
if !strings.Contains(got, "GoString") {
t.Error("GenCHeader() should include Go runtime types")
}
}
// Test genHeader with init function coverage
func TestGenHeaderWithInitFunction(t *testing.T) {
prog := ssa.NewProgram(nil)
// Create a package
pkgPath := "github.com/test/mypackage"
pkg := prog.NewPackage("", pkgPath)
// Create an init function signature: func()
initSig := types.NewSignature(nil, types.NewTuple(), types.NewTuple(), false)
// Create the init function with the expected name format
initFnName := pkgPath + ".init"
_ = pkg.NewFunc(initFnName, initSig, ssa.InGo)
// Test genHeader which should now detect the init function
var output bytes.Buffer
err := genHeader(prog, []ssa.Package{pkg}, &output)
if err != nil {
t.Fatalf("genHeader() error = %v", err)
}
got := output.String()
// Should contain Go runtime types
if !strings.Contains(got, "GoString") {
t.Error("genHeader() should include Go runtime types")
}
// Should contain the init function declaration with C-compatible name
expectedInitName := "github_com_test_mypackage_init"
if !strings.Contains(got, expectedInitName) {
t.Errorf("genHeader() should include init function declaration with name %s, got: %s", expectedInitName, got)
}
}

View File

@@ -435,6 +435,7 @@ func (p Program) NewPackage(name, pkgPath string) Package {
pyobjs: pyobjs, pymods: pymods, strs: strs,
chkabi: chkabi, Prog: p,
di: nil, cu: nil, glbDbgVars: glbDbgVars,
export: make(map[string]string),
}
ret.abi.Init(pkgPath)
return ret
@@ -693,6 +694,8 @@ type aPackage struct {
NeedRuntime bool
NeedPyInit bool
export map[string]string // pkgPath.nameInPkg => exportname
}
type Package = *aPackage
@@ -701,6 +704,14 @@ func (p Package) Module() llvm.Module {
return p.mod
}
func (p Package) SetExport(name, export string) {
p.export[name] = export
}
func (p Package) ExportFuncs() map[string]string {
return p.export
}
func (p Package) rtFunc(fnName string) Expr {
p.NeedRuntime = true
fn := p.Prog.runtime().Scope().Lookup(fnName).(*types.Func)