Merge pull request #1285 from cpunion/impl-build-mode

Implement llgo build mode support
This commit is contained in:
xushiwei
2025-09-14 10:30:47 +08:00
committed by GitHub
26 changed files with 3864 additions and 98 deletions

View File

@@ -125,6 +125,13 @@ jobs:
export LLGO_FULL_RPATH=true
bash .github/workflows/test_demo.sh
- name: Test C header generation
run: |
echo "Testing C header generation in different build modes..."
cd _demo/go/export
chmod +x test.sh
./test.sh
- name: _xtool build tests
run: |
cd _xtool

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
*.dll
*.so
*.dylib
*.a
test.db
demo.ll

1
_demo/go/export/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
libexport.h

29
_demo/go/export/c/c.go Normal file
View File

@@ -0,0 +1,29 @@
package C
// XType - struct for export.go to use
type XType struct {
ID int32 `json:"id"`
Name string `json:"name"`
Value float64 `json:"value"`
Flag bool `json:"flag"`
}
func XAdd(a, b int) int {
return a + b
}
func Sub(a, b int64) int64 {
return a - b
}
func sub(a, b uint32) uint32 {
return a - b
}
func Xmul(a, b float32) float32 {
return a * b
}
func Concat(a, b string) string {
return a + b
}

674
_demo/go/export/export.go Normal file
View File

@@ -0,0 +1,674 @@
package main
import (
"unsafe"
C "github.com/goplus/llgo/_demo/go/export/c"
)
// assert helper function for testing
func assert[T comparable](got, expected T, message string) {
if got != expected {
println("ASSERTION FAILED:", message)
println(" Expected:", expected)
println(" Got: ", got)
panic("assertion failed: " + message)
}
println("✓", message)
}
// Small struct
type SmallStruct struct {
ID int8 `json:"id"`
Flag bool `json:"flag"`
}
// Large struct
type LargeStruct struct {
ID int64 `json:"id"`
Name string `json:"name"`
Values [10]float64 `json:"values"`
Metadata map[string]int `json:"metadata"`
Children []SmallStruct `json:"children"`
Extra1 int32 `json:"extra1"`
Extra2 uint64 `json:"extra2"`
Extra3 float32 `json:"extra3"`
Extra4 bool `json:"extra4"`
Extra5 uintptr `json:"extra5"`
}
// Self-referential struct
type Node struct {
Data int `json:"data"`
Next *Node `json:"next"`
}
// Named types
type MyInt int
type MyString string
// Function types for callbacks
//
//llgo:type C
type IntCallback func(int) int
//llgo:type C
type StringCallback func(string) string
//llgo:type C
type VoidCallback func()
// Complex struct with mixed arrays and slices
type ComplexData struct {
Matrix [3][4]int32 `json:"matrix"` // 2D array
Slices [][]string `json:"slices"` // slice of slices - commented out
IntArray [5]int `json:"int_array"` // 1D array
DataList []float64 `json:"data_list"` // slice - commented out
}
//export HelloWorld
func HelloWorld() {
println("Hello, World!")
}
// Functions with small struct parameters and return values
//export CreateSmallStruct
func CreateSmallStruct(id int8, flag bool) SmallStruct {
return SmallStruct{ID: id, Flag: flag}
}
//export ProcessSmallStruct
func ProcessSmallStruct(s SmallStruct) SmallStruct {
s.ID += 1
s.Flag = !s.Flag
return s
}
//export ProcessSmallStructPtr
func ProcessSmallStructPtr(s *SmallStruct) *SmallStruct {
if s != nil {
s.ID *= 2
s.Flag = !s.Flag
}
return s
}
// Functions with large struct parameters and return values
//export CreateLargeStruct
func CreateLargeStruct(id int64, name string) LargeStruct {
return LargeStruct{
ID: id,
Name: name,
Values: [10]float64{1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 10.0},
Metadata: map[string]int{"count": 42, "size": 100},
Children: []SmallStruct{{ID: 1, Flag: true}, {ID: 2, Flag: false}},
Extra1: 12345,
Extra2: 67890,
Extra3: 3.14,
Extra4: true,
Extra5: 0x1000,
}
}
//export ProcessLargeStruct
func ProcessLargeStruct(ls LargeStruct) int64 {
total := ls.ID + int64(len(ls.Name))
for _, v := range ls.Values {
total += int64(v)
}
total += int64(len(ls.Children))
total += int64(ls.Extra1) + int64(ls.Extra2) + int64(ls.Extra3)
if ls.Extra4 {
total += 1000
}
total += int64(ls.Extra5)
return total
}
//export ProcessLargeStructPtr
func ProcessLargeStructPtr(ls *LargeStruct) *LargeStruct {
if ls != nil {
ls.ID += 100
ls.Name = "processed_" + ls.Name
ls.Extra1 *= 2
ls.Extra4 = !ls.Extra4
}
return ls
}
// Functions with self-referential struct
//export CreateNode
func CreateNode(data int) *Node {
return &Node{Data: data, Next: nil}
}
//export LinkNodes
func LinkNodes(first, second *Node) int {
if first != nil && second != nil {
first.Next = second
return first.Data + second.Data // Return sum for verification
}
if first != nil {
return first.Data + 1000 // Return data + offset if only first exists
}
return 2000 // Return fixed value if both are nil
}
//export TraverseNodes
func TraverseNodes(head *Node) int {
count := 0
current := head
for current != nil {
count++
current = current.Next
if count > 100 { // Safety check
break
}
}
return count
}
// Functions covering all basic types
//export ProcessBool
func ProcessBool(b bool) bool {
return !b
}
//export ProcessInt8
func ProcessInt8(x int8) int8 {
return x + 1
}
//export ProcessUint8
func ProcessUint8(x uint8) uint8 {
return x + 1
}
//export ProcessInt16
func ProcessInt16(x int16) int16 {
return x * 2
}
//export ProcessUint16
func ProcessUint16(x uint16) uint16 {
return x * 2
}
//export ProcessInt32
func ProcessInt32(x int32) int32 {
return x * 3
}
//export ProcessUint32
func ProcessUint32(x uint32) uint32 {
return x * 3
}
//export ProcessInt64
func ProcessInt64(x int64) int64 {
return x * 4
}
//export ProcessUint64
func ProcessUint64(x uint64) uint64 {
return x * 4
}
//export ProcessInt
func ProcessInt(x int) int {
return x * 11
}
//export ProcessUint
func ProcessUint(x uint) uint {
return x * 21
}
//export ProcessUintptr
func ProcessUintptr(x uintptr) uintptr {
return x + 300
}
//export ProcessFloat32
func ProcessFloat32(x float32) float32 {
return x * 1.5
}
//export ProcessFloat64
func ProcessFloat64(x float64) float64 {
return x * 2.5
}
//export ProcessString
func ProcessString(s string) string {
return "processed_" + s
}
//export ProcessUnsafePointer
func ProcessUnsafePointer(p unsafe.Pointer) unsafe.Pointer {
return p
}
// Functions with named types
//export ProcessMyInt
func ProcessMyInt(x MyInt) MyInt {
return x * 10
}
//export ProcessMyString
func ProcessMyString(s MyString) MyString {
return MyString("modified_" + string(s))
}
// Functions with arrays, slices, maps, channels
//export ProcessIntArray
func ProcessIntArray(arr [5]int) int {
total := 0
for _, v := range arr {
total += v
}
return total
}
//export CreateComplexData
func CreateComplexData() ComplexData {
return ComplexData{
Matrix: [3][4]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}},
Slices: [][]string{{"helo"}},
IntArray: [5]int{10, 20, 30, 40, 50},
DataList: []float64{1.0},
}
}
//export ProcessComplexData
func ProcessComplexData(data ComplexData) int32 {
// Sum all matrix elements
var sum int32
for i := 0; i < 3; i++ {
for j := 0; j < 4; j++ {
sum += data.Matrix[i][j]
}
}
return sum
}
// Functions with multidimensional arrays as parameters and return values
//export ProcessMatrix2D
func ProcessMatrix2D(matrix [3][4]int32) int32 {
var sum int32
for i := 0; i < 3; i++ {
for j := 0; j < 4; j++ {
sum += matrix[i][j]
}
}
return sum
}
//export CreateMatrix1D
func CreateMatrix1D() [4]int32 {
return [4]int32{1, 2, 3, 4}
}
//export CreateMatrix2D
func CreateMatrix2D() [3][4]int32 {
return [3][4]int32{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}
}
//export ProcessMatrix3D
func ProcessMatrix3D(cube [2][3][4]uint8) uint32 {
var sum uint32
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 4; k++ {
sum += uint32(cube[i][j][k])
}
}
}
return sum
}
//export CreateMatrix3D
func CreateMatrix3D() [2][3][4]uint8 {
var cube [2][3][4]uint8
val := uint8(1)
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
for k := 0; k < 4; k++ {
cube[i][j][k] = val
val++
}
}
}
return cube
}
//export ProcessGrid5x4
func ProcessGrid5x4(grid [5][4]float64) float64 {
var sum float64
for i := 0; i < 5; i++ {
for j := 0; j < 4; j++ {
sum += grid[i][j]
}
}
return sum
}
//export CreateGrid5x4
func CreateGrid5x4() [5][4]float64 {
var grid [5][4]float64
val := 1.0
for i := 0; i < 5; i++ {
for j := 0; j < 4; j++ {
grid[i][j] = val
val += 0.5
}
}
return grid
}
//export ProcessIntSlice
func ProcessIntSlice(slice []int) int {
total := 0
for _, v := range slice {
total += v
}
return total
}
//export ProcessStringMap
func ProcessStringMap(m map[string]int) int {
total := 0
for _, v := range m {
total += v
}
return total
}
//export ProcessIntChannel
func ProcessIntChannel(ch chan int) int {
select {
case val := <-ch:
return val
default:
return -1
}
}
// Functions with function callbacks
//export ProcessWithIntCallback
func ProcessWithIntCallback(x int, callback IntCallback) int {
if callback != nil {
return callback(x)
}
return x
}
//export ProcessWithStringCallback
func ProcessWithStringCallback(s string, callback StringCallback) string {
if callback != nil {
return callback(s)
}
return s
}
//export ProcessWithVoidCallback
func ProcessWithVoidCallback(callback VoidCallback) int {
if callback != nil {
callback()
return 123 // Return non-zero to indicate callback was called
}
return 456 // Return different value if callback is nil
}
//export ProcessThreeUnnamedParams
func ProcessThreeUnnamedParams(a int, s string, b bool) float64 {
result := float64(a) + float64(len(s))
if b {
result *= 1.5
}
return result
}
// Functions with interface
//export ProcessInterface
func ProcessInterface(i interface{}) int {
switch v := i.(type) {
case int:
return v + 100
case string:
return len(v) * 10
default:
return 999 // Non-zero default to avoid false positives
}
}
// Functions with various parameter counts
//export NoParams
func NoParams() int {
return 42
}
//export OneParam
func OneParam(x int) int {
return x * 2
}
//export TwoParams
func TwoParams(a int, b string) string {
return string(rune(a)) + b
}
//export ThreeParams
func ThreeParams(a int32, b float64, c bool) float64 {
result := float64(a) + b
if c {
result *= 2
}
return result
}
//export MultipleParams
func MultipleParams(a int8, b uint16, c int32, d uint64, e float32, f float64, g string, h bool) string {
result := g + "_" + string(rune('A'+a)) + string(rune('0'+b%10)) + string(rune('0'+c%10))
if h {
result += "_true"
}
return result + "_" + string(rune('0'+int(d%10))) + "_" + string(rune('0'+int(e)%10)) + "_" + string(rune('0'+int(f)%10))
}
//export NoParamNames
func NoParamNames(int8, int16, bool) int32 {
return 789 // Return non-zero value for testing, params are unnamed by design
}
// Functions returning no value
//export NoReturn
func NoReturn(message string) {
println("Message:", message)
}
// Functions using XType from c package
//export CreateXType
func CreateXType(id int32, name string, value float64, flag bool) C.XType {
return C.XType{
ID: id,
Name: name,
Value: value,
Flag: flag,
}
}
//export ProcessXType
func ProcessXType(x C.XType) C.XType {
x.ID += 100
x.Name = "processed_" + x.Name
x.Value *= 2.0
x.Flag = !x.Flag
return x
}
//export ProcessXTypePtr
func ProcessXTypePtr(x *C.XType) *C.XType {
if x != nil {
x.ID *= 2
x.Name = "ptr_" + x.Name
x.Value += 10.0
x.Flag = !x.Flag
}
return x
}
func main() {
println("=== Export Demo ===")
// Test small struct
small := CreateSmallStruct(5, true)
assert(small.ID, int8(5), "CreateSmallStruct ID should be 5")
assert(small.Flag, true, "CreateSmallStruct Flag should be true")
println("Small struct:", small.ID, small.Flag)
processed := ProcessSmallStruct(small)
assert(processed.ID, int8(6), "ProcessSmallStruct should increment ID to 6")
assert(processed.Flag, false, "ProcessSmallStruct should flip Flag to false")
println("Processed small:", processed.ID, processed.Flag)
// Test large struct
large := CreateLargeStruct(12345, "test")
assert(large.ID, int64(12345), "CreateLargeStruct ID should be 12345")
assert(large.Name, "test", "CreateLargeStruct Name should be 'test'")
println("Large struct ID:", large.ID, "Name:", large.Name)
total := ProcessLargeStruct(large)
// Expected calculation:
// ID: 12345, Name len: 4, Values: 1+2+3+4+5+6+7+8+9+10=55, Children len: 2
// Extra1: 12345, Extra2: 67890, Extra3: 3, Extra4: +1000, Extra5: 4096
expectedTotal := int64(12345 + 4 + 55 + 2 + 12345 + 67890 + 3 + 1000 + 4096)
assert(total, expectedTotal, "ProcessLargeStruct total should match expected calculation")
println("Large struct total:", total)
// Test self-referential struct
node1 := CreateNode(100)
node2 := CreateNode(200)
linkResult := LinkNodes(node1, node2)
assert(linkResult, 300, "LinkNodes should return sum of node data (100 + 200)")
count := TraverseNodes(node1)
assert(count, 2, "TraverseNodes should count 2 linked nodes")
println("Node count:", count)
// Test basic types with assertions
assert(ProcessBool(true), false, "ProcessBool(true) should return false")
assert(ProcessInt8(10), int8(11), "ProcessInt8(10) should return 11")
f32Result := ProcessFloat32(3.14)
// Float comparison with tolerance
if f32Result < 4.7 || f32Result > 4.72 {
println("ASSERTION FAILED: ProcessFloat32(3.14) should return ~4.71, got:", f32Result)
panic("float assertion failed")
}
println("✓ ProcessFloat32(3.14) returns ~4.71")
assert(ProcessString("hello"), "processed_hello", "ProcessString should prepend 'processed_'")
println("Bool:", ProcessBool(true))
println("Int8:", ProcessInt8(10))
println("Float32:", ProcessFloat32(3.14))
println("String:", ProcessString("hello"))
// Test named types
myInt := ProcessMyInt(MyInt(42))
assert(myInt, MyInt(420), "ProcessMyInt(42) should return 420")
println("MyInt:", int(myInt))
myStr := ProcessMyString(MyString("world"))
assert(myStr, MyString("modified_world"), "ProcessMyString should prepend 'modified_'")
println("MyString:", string(myStr))
// Test collections
arr := [5]int{1, 2, 3, 4, 5}
arrSum := ProcessIntArray(arr)
assert(arrSum, 15, "ProcessIntArray([1,2,3,4,5]) should return 15")
println("Array sum:", arrSum)
slice := []int{10, 20, 30}
sliceSum := ProcessIntSlice(slice)
assert(sliceSum, 60, "ProcessIntSlice([10,20,30]) should return 60")
println("Slice sum:", sliceSum)
m := make(map[string]int)
m["a"] = 100
m["b"] = 200
mapSum := ProcessStringMap(m)
assert(mapSum, 300, "ProcessStringMap({'a':100,'b':200}) should return 300")
println("Map sum:", mapSum)
// Test multidimensional arrays
matrix2d := CreateMatrix2D()
matrix2dSum := ProcessMatrix2D(matrix2d)
assert(matrix2dSum, int32(78), "ProcessMatrix2D should return 78 (sum of 1+2+...+12)")
println("Matrix2D sum:", matrix2dSum)
matrix3d := CreateMatrix3D()
matrix3dSum := ProcessMatrix3D(matrix3d)
assert(matrix3dSum, uint32(300), "ProcessMatrix3D should return 300")
println("Matrix3D sum:", matrix3dSum)
grid5x4 := CreateGrid5x4()
gridSum := ProcessGrid5x4(grid5x4)
assert(gridSum, 115.0, "ProcessGrid5x4 should return 115.0")
println("Grid5x4 sum:", gridSum)
// Test complex data with multidimensional arrays
complexData := CreateComplexData()
complexSum := ProcessComplexData(complexData)
assert(complexSum, int32(78), "ProcessComplexData should return 78")
println("ComplexData matrix sum:", complexSum)
// Test various parameter counts
assert(NoParams(), 42, "NoParams should return 42")
assert(OneParam(5), 10, "OneParam(5) should return 10")
assert(TwoParams(65, "_test"), "A_test", "TwoParams should return 'A_test'")
assert(ThreeParams(10, 2.5, true), 25.0, "ThreeParams should return 25.0")
assert(NoParamNames(1, 2, false), int32(789), "NoParamNames should return 789")
println("NoParams:", NoParams())
println("OneParam:", OneParam(5))
println("TwoParams:", TwoParams(65, "_test"))
println("ThreeParams:", ThreeParams(10, 2.5, true))
println("MultipleParams:", MultipleParams(1, 2, 3, 4, 5.0, 6.0, "result", true))
println("NoParamNames:", NoParamNames(1, 2, false))
// Test XType from c package
xtype := CreateXType(42, "test", 3.14, true)
println("XType:", xtype.ID, xtype.Name, xtype.Value, xtype.Flag)
processedX := ProcessXType(xtype)
println("Processed XType:", processedX.ID, processedX.Name, processedX.Value, processedX.Flag)
ptrX := ProcessXTypePtr(&xtype)
if ptrX != nil {
println("Ptr XType:", ptrX.ID, ptrX.Name, ptrX.Value, ptrX.Flag)
}
// Test callback functions
intResult := ProcessWithIntCallback(10, func(x int) int { return x * 3 })
println("IntCallback result:", intResult)
stringResult := ProcessWithStringCallback("hello", func(s string) string { return s + "_callback" })
println("StringCallback result:", stringResult)
ProcessWithVoidCallback(func() { println("VoidCallback executed") })
NoReturn("demo completed")
}

View File

@@ -0,0 +1,312 @@
/* Code generated by llgo; DO NOT EDIT. */
#ifndef __LIBEXPORT_H_
#define __LIBEXPORT_H_
#include <stdint.h>
#include <stdbool.h>
#ifdef __cplusplus
extern "C" {
#endif
// 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;
typedef struct {
int32_t Matrix[3][4];
GoSlice Slices;
intptr_t IntArray[5];
GoSlice DataList;
} main_ComplexData;
typedef struct {
double data[5][4];
} Array_double_5_4;
typedef struct {
int8_t ID;
_Bool Flag;
} main_SmallStruct;
typedef struct {
int64_t ID;
GoString Name;
double Values[10];
GoMap Metadata;
GoSlice Children;
int32_t Extra1;
uint64_t Extra2;
float Extra3;
_Bool Extra4;
uintptr_t Extra5;
} main_LargeStruct;
typedef struct {
int32_t data[4];
} Array_int32_t_4;
typedef struct {
int32_t data[3][4];
} Array_int32_t_3_4;
typedef struct {
uint8_t data[2][3][4];
} Array_uint8_t_2_3_4;
typedef struct main_Node main_Node;
struct main_Node {
intptr_t Data;
main_Node* Next;
};
typedef struct {
int32_t ID;
GoString Name;
double Value;
_Bool Flag;
} C_XType;
typedef intptr_t main_MyInt;
typedef GoString main_MyString;
typedef intptr_t (*main_IntCallback)(intptr_t);
typedef GoString (*main_StringCallback)(GoString);
typedef void (*main_VoidCallback)(void);
GoString
Concat(GoString a, GoString b);
int64_t
Sub(int64_t a, int64_t b);
intptr_t
Add(intptr_t a, intptr_t b);
float
mul(float a, float b);
void
github_com_goplus_llgo__demo_go_export_c_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/_demo/go/export/c.init")
main_ComplexData
CreateComplexData(void);
Array_double_5_4
CreateGrid5x4(void);
main_LargeStruct
CreateLargeStruct(int64_t id, GoString name);
Array_int32_t_4
CreateMatrix1D(void);
Array_int32_t_3_4
CreateMatrix2D(void);
Array_uint8_t_2_3_4
CreateMatrix3D(void);
main_Node*
CreateNode(intptr_t data);
main_SmallStruct
CreateSmallStruct(int8_t id, _Bool flag);
C_XType
CreateXType(int32_t id, GoString name, double value, _Bool flag);
void
HelloWorld(void);
intptr_t
LinkNodes(main_Node* first, main_Node* second);
GoString
MultipleParams(int8_t a, uint16_t b, int32_t c, uint64_t d, float e, double f, GoString g, _Bool h);
int32_t
NoParamNames(int8_t, int16_t, _Bool);
intptr_t
NoParams(void);
void
NoReturn(GoString message);
intptr_t
OneParam(intptr_t x);
_Bool
ProcessBool(_Bool b);
int32_t
ProcessComplexData(main_ComplexData data);
float
ProcessFloat32(float x);
double
ProcessFloat64(double x);
double
ProcessGrid5x4(double grid[5][4]);
intptr_t
ProcessInt(intptr_t x);
int16_t
ProcessInt16(int16_t x);
int32_t
ProcessInt32(int32_t x);
int64_t
ProcessInt64(int64_t x);
int8_t
ProcessInt8(int8_t x);
intptr_t
ProcessIntArray(intptr_t* arr);
intptr_t
ProcessIntChannel(GoChan ch);
intptr_t
ProcessIntSlice(GoSlice slice);
intptr_t
ProcessInterface(GoInterface i);
int64_t
ProcessLargeStruct(main_LargeStruct ls);
main_LargeStruct*
ProcessLargeStructPtr(main_LargeStruct* ls);
int32_t
ProcessMatrix2D(int32_t matrix[3][4]);
uint32_t
ProcessMatrix3D(uint8_t cube[2][3][4]);
main_MyInt
ProcessMyInt(main_MyInt x);
main_MyString
ProcessMyString(main_MyString s);
main_SmallStruct
ProcessSmallStruct(main_SmallStruct s);
main_SmallStruct*
ProcessSmallStructPtr(main_SmallStruct* s);
GoString
ProcessString(GoString s);
intptr_t
ProcessStringMap(GoMap m);
double
ProcessThreeUnnamedParams(intptr_t a, GoString s, _Bool b);
uintptr_t
ProcessUint(uintptr_t x);
uint16_t
ProcessUint16(uint16_t x);
uint32_t
ProcessUint32(uint32_t x);
uint64_t
ProcessUint64(uint64_t x);
uint8_t
ProcessUint8(uint8_t x);
uintptr_t
ProcessUintptr(uintptr_t x);
void*
ProcessUnsafePointer(void* p);
intptr_t
ProcessWithIntCallback(intptr_t x, main_IntCallback callback);
GoString
ProcessWithStringCallback(GoString s, main_StringCallback callback);
intptr_t
ProcessWithVoidCallback(main_VoidCallback callback);
C_XType
ProcessXType(C_XType x);
C_XType*
ProcessXTypePtr(C_XType* x);
double
ThreeParams(int32_t a, double b, _Bool c);
intptr_t
TraverseNodes(main_Node* head);
GoString
TwoParams(intptr_t a, GoString b);
void
github_com_goplus_llgo__demo_go_export_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/_demo/go/export.init")
void
github_com_goplus_llgo_runtime_abi_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/abi.init")
void
github_com_goplus_llgo_runtime_internal_clite_bdwgc_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/bdwgc.init")
void
github_com_goplus_llgo_runtime_internal_clite_bitcast_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/bitcast.init")
void
github_com_goplus_llgo_runtime_internal_clite_debug_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/debug.init")
void
github_com_goplus_llgo_runtime_internal_clite_pthread_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/pthread.init")
void
github_com_goplus_llgo_runtime_internal_clite_pthread_sync_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/clite/pthread/sync.init")
void
github_com_goplus_llgo_runtime_internal_runtime_goarch_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime/goarch.init")
void
github_com_goplus_llgo_runtime_internal_runtime_math_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime/math.init")
void
github_com_goplus_llgo_runtime_internal_runtime_init(void) GO_SYMBOL_RENAME("github.com/goplus/llgo/runtime/internal/runtime.init")
#ifdef __cplusplus
}
#endif
#endif /* __LIBEXPORT_H_ */

278
_demo/go/export/test.sh Executable file
View File

@@ -0,0 +1,278 @@
#!/bin/bash
# Test script for C header generation in different build modes
# This script tests the header generation functionality with various buildmode options
set -e # Exit on any error
# Get script directory
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${GREEN}[INFO]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARN]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# Function to check if file exists and is not empty
check_file() {
local file="$1"
local description="$2"
if [[ -f "$file" ]]; then
if [[ -s "$file" ]]; then
print_status "$description exists and is not empty"
return 0
else
print_error "$description exists but is empty"
return 1
fi
else
print_error "$description does not exist"
return 1
fi
}
# Function to compare header with expected content
compare_header() {
local header_file="$1"
local expected_file="$2"
local test_name="$3"
if [[ -f "$expected_file" ]]; then
if diff -q "$header_file" "$expected_file" >/dev/null 2>&1; then
print_status "$test_name: Header content matches expected"
return 0
else
print_warning "$test_name: Header content differs from expected"
print_warning "Run 'diff $header_file $expected_file' to see differences"
return 1
fi
else
print_warning "$test_name: No expected file found at $expected_file"
print_status "Generated header content:"
echo "--- START OF HEADER ---"
cat "$header_file"
echo "--- END OF HEADER ---"
return 0
fi
}
# Function to cleanup generated files
cleanup() {
local files=("$@")
for file in "${files[@]}"; do
if [[ -f "$file" ]]; then
rm -f "$file"
print_status "Cleaned up $file"
fi
done
}
# Check if llgo.sh exists
LLGO_SCRIPT="../../../llgo.sh"
if [[ ! -f "$LLGO_SCRIPT" ]]; then
print_error "llgo.sh not found at $LLGO_SCRIPT"
exit 1
fi
print_status "Starting C header generation tests..."
print_status "Working directory: $SCRIPT_DIR"
echo ""
# Test 1: c-shared mode
print_status "=== Test 2: Building with -buildmode c-shared ==="
if $LLGO_SCRIPT build -buildmode c-shared -o export .; then
print_status "Build succeeded"
# Check generated files (different extensions on different platforms)
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
check_file "libexport.dylib" "Dynamic library (libexport.dylib)"
SHARED_LIB="libexport.dylib"
else
# Linux and others
check_file "libexport.so" "Dynamic library (libexport.so)"
SHARED_LIB="libexport.so"
fi
check_file "libexport.h" "C header (libexport.h)"
# Compare with expected header if it exists
if [[ -f "libexport.h" ]]; then
compare_header "libexport.h" "libexport.h.want" "c-shared"
fi
# Test C demo with shared library
print_status "=== Testing C demo with shared library ==="
if cd use; then
if LINK_TYPE=shared make clean && LINK_TYPE=shared make; then
print_status "C demo build succeeded with shared library"
if LINK_TYPE=shared make run; then
print_status "C demo execution succeeded with shared library"
else
print_warning "C demo execution failed with shared library"
fi
else
print_warning "C demo build failed with shared library"
fi
cd ..
else
print_error "Failed to enter use directory"
fi
# Cleanup
cleanup "$SHARED_LIB" "libexport.h"
else
print_error "Build failed for c-shared mode"
fi
# Test 2: c-archive mode
print_status "=== Test 1: Building with -buildmode c-archive ==="
if $LLGO_SCRIPT build -buildmode c-archive -o export .; then
print_status "Build succeeded"
# Check generated files
check_file "libexport.a" "Static library (libexport.a)"
check_file "libexport.h" "C header (libexport.h)"
# Compare with expected header if it exists
if [[ -f "libexport.h" ]]; then
compare_header "libexport.h" "libexport.h.want" "c-archive"
fi
# Test C demo with static library
print_status "=== Testing C demo with static library ==="
if cd use; then
if make clean && make; then
print_status "C demo build succeeded with static library"
if make run; then
print_status "C demo execution succeeded with static library"
else
print_warning "C demo execution failed with static library"
fi
else
print_warning "C demo build failed with static library"
fi
cd ..
else
print_error "Failed to enter use directory"
fi
# # Cleanup
# cleanup "libexport.a" "libexport.h"
else
print_error "Build failed for c-archive mode"
fi
echo ""
# TODO(lijie): Uncomment if https://github.com/goplus/llgo/pull/1268 merged
# # Test 3: ESP32 target with c-archive mode
# print_status "=== Test 3: Building with -target esp32 -buildmode c-archive ==="
# if $LLGO_SCRIPT build -target esp32 -buildmode c-archive -o export .; then
# print_status "Build succeeded"
# # Check generated files
# check_file "libexport.a" "Static library for ESP32 (libexport.a)"
# check_file "libexport.h" "C header for ESP32 (libexport.h)"
# # Compare with expected header if it exists
# if [[ -f "libexport.h" ]]; then
# compare_header "libexport.h" "libexport.h.want" "esp32-c-archive"
# fi
# # Don't cleanup ESP32 files - keep them for inspection
# print_status "ESP32 build files kept for inspection"
# else
# print_error "Build failed for ESP32 target"
# fi
# echo ""
# Test 3: Go export demo execution
print_status "=== Test 3: Running Go export demo ==="
if go run export.go > /tmp/go_export_output.log 2>&1; then
print_status "Go export demo execution succeeded"
# Check if output contains expected success indicators
if grep -q "✓" /tmp/go_export_output.log; then
SUCCESS_COUNT=$(grep -c "✓" /tmp/go_export_output.log)
print_status "All $SUCCESS_COUNT assertions passed in Go export demo"
else
print_warning "No assertion markers found in Go export demo output"
fi
# Show key output lines
print_status "Go export demo output summary:"
if grep -q "ASSERTION FAILED" /tmp/go_export_output.log; then
print_error "Found assertion failures in Go export demo"
grep "ASSERTION FAILED" /tmp/go_export_output.log
else
print_status " ✅ No assertion failures detected"
echo " 📊 First few lines of output:"
head -5 /tmp/go_export_output.log | sed 's/^/ /'
echo " 📊 Last few lines of output:"
tail -5 /tmp/go_export_output.log | sed 's/^/ /'
fi
else
print_error "Go export demo execution failed"
print_error "Error output:"
cat /tmp/go_export_output.log | sed 's/^/ /'
fi
# Cleanup temporary file
rm -f /tmp/go_export_output.log
echo ""
# Final summary
print_status "=== Test Summary ==="
if [[ -f "libexport.a" ]] && [[ -f "libexport.h" ]]; then
print_status "All tests completed successfully:"
print_status " ✅ Go export demo execution with assertions"
print_status " ✅ C header generation (c-archive and c-shared modes)"
print_status " ✅ C demo compilation and execution"
print_status " ✅ Cross-platform symbol renaming"
print_status " ✅ Init function export and calling"
print_status " ✅ Function callback types with proper typedef syntax"
print_status " ✅ Multidimensional array parameter handling"
print_status ""
print_status "Final files available:"
print_status " - libexport.a (static library)"
print_status " - libexport.h (C header file)"
print_status " - use/main.out (C demo executable)"
echo ""
echo "==================="
else
print_error "Some tests may have failed. Check the output above."
fi
# Show file sizes for reference
if [[ -f "libexport.a" ]]; then
SIZE=$(wc -c < libexport.a)
print_status "Static library size: $SIZE bytes"
fi
if [[ -f "libexport.h" ]]; then
LINES=$(wc -l < libexport.h)
print_status "Header file lines: $LINES"
fi
print_status "C header generation and demo tests completed!"

View File

@@ -0,0 +1,84 @@
# Makefile for C demo using Go exported library
# Use LINK_TYPE environment variable to choose library type:
# LINK_TYPE=static - Link with static library (default)
# LINK_TYPE=shared - Link with shared library
CC = clang
CFLAGS = -Wall -Wextra -std=c99
INCLUDES = -I..
TARGET = main.out
SOURCES = main.c
HEADER = ../libexport.h
# Default to static linking
LINK_TYPE ?= static
# Platform detection
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
SHARED_EXT = dylib
PLATFORM_LIBS =
else
SHARED_EXT = so
PLATFORM_LIBS = $(shell pkg-config --libs libunwind 2>/dev/null || echo -lunwind)
endif
# Library and flags based on link type
ifeq ($(LINK_TYPE),shared)
BUILDMODE = c-shared
LIBRARY = ../libexport.$(SHARED_EXT)
LDFLAGS = -L.. -lexport -lpthread -lm $(shell pkg-config --libs bdw-gc || echo -lgc) $(PLATFORM_LIBS)
BUILD_MSG = "Building Go shared library..."
LINK_MSG = "Linking with shared library..."
else
BUILDMODE = c-archive
LIBRARY = ../libexport.a
LDFLAGS = $(LIBRARY) -lpthread -lm $(shell pkg-config --libs bdw-gc || echo -lgc) $(PLATFORM_LIBS)
BUILD_MSG = "Building Go static library..."
LINK_MSG = "Linking with static library..."
endif
.PHONY: all clean run build-go
all: build-go $(TARGET)
# Build the Go library first
build-go:
@echo $(BUILD_MSG)
cd .. && ../../../llgo.sh build -buildmode $(BUILDMODE) -o export .
# Build the C executable
$(TARGET): $(SOURCES) $(LIBRARY) $(HEADER)
@echo $(LINK_MSG)
$(CC) $(CFLAGS) $(INCLUDES) -o $(TARGET) $(SOURCES) $(LDFLAGS)
# Run the executable
run: $(TARGET)
@echo "Running C demo..."
ifeq ($(LINK_TYPE),shared)
@echo "Setting library path for shared library..."
LD_LIBRARY_PATH=.. DYLD_LIBRARY_PATH=.. ./$(TARGET)
else
./$(TARGET)
endif
# Clean build artifacts
clean:
rm -f $(TARGET)
rm -f ../libexport.a ../libexport.h ../libexport.so ../libexport.dylib
# Help target
help:
@echo "Available targets:"
@echo " all - Build Go library and C executable"
@echo " build-go - Build only the Go library"
@echo " run - Build and run the C demo"
@echo " clean - Clean all build artifacts"
@echo " help - Show this help message"
@echo ""
@echo "Environment variables:"
@echo " LINK_TYPE - Library type: 'static' (default) or 'shared'"
@echo ""
@echo "Examples:"
@echo " make run # Use static library"
@echo " LINK_TYPE=shared make run # Use shared library"

237
_demo/go/export/use/main.c Normal file
View File

@@ -0,0 +1,237 @@
#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <assert.h>
#include "../libexport.h"
int main() {
printf("=== C Export Demo ===\n");
fflush(stdout); // Force output
// Initialize packages - call init functions first
github_com_goplus_llgo__demo_go_export_c_init();
github_com_goplus_llgo__demo_go_export_init();
// Test HelloWorld
HelloWorld();
printf("\n");
// Test small struct
main_SmallStruct small = CreateSmallStruct(5, 1); // 1 for true
printf("Small struct: %d %d\n", small.ID, small.Flag);
main_SmallStruct processed = ProcessSmallStruct(small);
printf("Processed small: %d %d\n", processed.ID, processed.Flag);
main_SmallStruct* ptrSmall = ProcessSmallStructPtr(&small);
if (ptrSmall != NULL) {
printf("Ptr small: %d %d\n", ptrSmall->ID, ptrSmall->Flag);
}
// Test large struct - create GoString for name parameter
GoString name = {"test_large", 10}; // name and length
main_LargeStruct large = CreateLargeStruct(12345, name);
printf("Large struct ID: %" PRId64 "\n", large.ID);
int64_t total = ProcessLargeStruct(large);
printf("Large struct total: %" PRId64 "\n", total);
main_LargeStruct* ptrLarge = ProcessLargeStructPtr(&large);
if (ptrLarge != NULL) {
printf("Ptr large ID: %" PRId64 "\n", ptrLarge->ID);
}
// Test self-referential struct
main_Node* node1 = CreateNode(100);
main_Node* node2 = CreateNode(200);
int link_result = LinkNodes(node1, node2);
assert(link_result == 300); // LinkNodes returns 100 + 200 = 300
printf("LinkNodes result: %d\n", link_result);
int count = TraverseNodes(node1);
assert(count == 2); // Should traverse 2 nodes
printf("Node count: %d\n", count);
// Test basic types with assertions
assert(ProcessBool(1) == 0); // ProcessBool(true) returns !true = false
printf("Bool: %d\n", ProcessBool(1));
assert(ProcessInt8(10) == 11); // ProcessInt8(x) returns x + 1
printf("Int8: %d\n", ProcessInt8(10));
assert(ProcessUint8(10) == 11); // ProcessUint8(x) returns x + 1
printf("Uint8: %d\n", ProcessUint8(10));
assert(ProcessInt16(10) == 20); // ProcessInt16(x) returns x * 2
printf("Int16: %d\n", ProcessInt16(10));
assert(ProcessUint16(10) == 20); // ProcessUint16(x) returns x * 2
printf("Uint16: %d\n", ProcessUint16(10));
assert(ProcessInt32(10) == 30); // ProcessInt32(x) returns x * 3
printf("Int32: %d\n", ProcessInt32(10));
assert(ProcessUint32(10) == 30); // ProcessUint32(x) returns x * 3
printf("Uint32: %u\n", ProcessUint32(10));
assert(ProcessInt64(10) == 40); // ProcessInt64(x) returns x * 4
printf("Int64: %" PRId64 "\n", ProcessInt64(10));
assert(ProcessUint64(10) == 40); // ProcessUint64(x) returns x * 4
printf("Uint64: %" PRIu64 "\n", ProcessUint64(10));
assert(ProcessInt(10) == 110); // ProcessInt(x) returns x * 11
printf("Int: %ld\n", ProcessInt(10));
assert(ProcessUint(10) == 210); // ProcessUint(x) returns x * 21
printf("Uint: %lu\n", ProcessUint(10));
assert(ProcessUintptr(0x1000) == 4396); // ProcessUintptr(x) returns x + 300 = 4096 + 300
printf("Uintptr: %lu\n", ProcessUintptr(0x1000));
// Float comparisons with tolerance
float f32_result = ProcessFloat32(3.14f);
assert(f32_result > 4.7f && f32_result < 4.72f); // ProcessFloat32(x) returns x * 1.5 ≈ 4.71
printf("Float32: %f\n", f32_result);
double f64_result = ProcessFloat64(3.14);
assert(f64_result > 7.84 && f64_result < 7.86); // ProcessFloat64(x) returns x * 2.5 ≈ 7.85
printf("Float64: %f\n", f64_result);
// Test unsafe pointer
int test_val = 42;
void* ptr_result = ProcessUnsafePointer(&test_val);
printf("UnsafePointer: %p\n", ptr_result);
// Test named types
main_MyInt myInt = ProcessMyInt(42);
printf("MyInt: %ld\n", (long)myInt);
// Test arrays
intptr_t arr[5] = {1, 2, 3, 4, 5};
printf("Array sum: %ld\n", ProcessIntArray(arr));
// Test complex data with multidimensional arrays
main_ComplexData complex = CreateComplexData();
printf("Complex data matrix sum: %" PRId32 "\n", ProcessComplexData(complex));
// Test interface - this is more complex in C, we'll skip for now
printf("Interface test skipped (complex in C)\n");
// Test various parameter counts
assert(NoParams() == 42); // NoParams() always returns 42
printf("NoParams: %ld\n", NoParams());
assert(OneParam(5) == 10); // OneParam(x) returns x * 2
printf("OneParam: %ld\n", OneParam(5));
assert(ThreeParams(10, 2.5, 1) == 25.0); // ThreeParams calculates result
printf("ThreeParams: %f\n", ThreeParams(10, 2.5, 1)); // 1 for true
// Test ProcessThreeUnnamedParams - now uses all parameters
GoString test_str = {"hello", 5};
double unnamed_result = ProcessThreeUnnamedParams(10, test_str, 1);
assert(unnamed_result == 22.5); // (10 + 5) * 1.5 = 22.5
printf("ProcessThreeUnnamedParams: %f\n", unnamed_result);
// Test ProcessWithVoidCallback - now returns int
int void_callback_result = ProcessWithVoidCallback(NULL);
assert(void_callback_result == 456); // Returns 456 when callback is nil
printf("ProcessWithVoidCallback(NULL): %d\n", void_callback_result);
// Test NoParamNames - function with unnamed parameters
int32_t no_names_result = NoParamNames(5, 10, 0);
assert(no_names_result == 789); // Returns fixed value 789
printf("NoParamNames: %d\n", no_names_result);
// Test XType from c package - create GoString for name parameter
GoString xname = {"test_x", 6}; // name and length
C_XType xtype = CreateXType(42, xname, 3.14, 1); // 1 for true
printf("XType: %d %f %d\n", xtype.ID, xtype.Value, xtype.Flag);
C_XType processedX = ProcessXType(xtype);
printf("Processed XType: %d %f %d\n", processedX.ID, processedX.Value, processedX.Flag);
C_XType* ptrX = ProcessXTypePtr(&xtype);
if (ptrX != NULL) {
printf("Ptr XType: %d %f %d\n", ptrX->ID, ptrX->Value, ptrX->Flag);
}
// Test multidimensional arrays
printf("\n=== Multidimensional Array Tests ===\n");
// Create and test 2D matrix [3][4]
// Note: CreateMatrix2D returns [3][4]int32, but function returns need special handling in C
printf("Testing 2D matrix functions...\n");
// Create a test 2D matrix [3][4]int32
int32_t test_matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int32_t matrix_sum = ProcessMatrix2D(test_matrix);
assert(matrix_sum == 78); // Sum of 1+2+3+...+12 = 78
printf("Matrix2D sum: %d\n", matrix_sum);
// Create a test 3D cube [2][3][4]uint8
uint8_t test_cube[2][3][4];
uint8_t val = 1;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 4; k++) {
test_cube[i][j][k] = val++;
}
}
}
uint32_t cube_sum = ProcessMatrix3D(test_cube);
assert(cube_sum == 300); // Sum of 1+2+3+...+24 = 300
printf("Matrix3D (cube) sum: %u\n", cube_sum);
// Create a test 5x4 grid [5][4]double
double test_grid[5][4];
double grid_val = 1.0;
for (int i = 0; i < 5; i++) {
for (int j = 0; j < 4; j++) {
test_grid[i][j] = grid_val;
grid_val += 0.5;
}
}
double grid_sum = ProcessGrid5x4(test_grid);
assert(grid_sum == 115.0); // Sum of 1.0+1.5+2.0+...+10.5 = 115.0
printf("Grid5x4 sum: %f\n", grid_sum);
// Test functions that return multidimensional arrays (as multi-level pointers)
printf("\n=== Testing Return Value Functions ===\n");
// Test CreateMatrix1D() which returns Array_int32_t_4
printf("About to call CreateMatrix1D()...\n");
fflush(stdout);
Array_int32_t_4 matrix1d = CreateMatrix1D();
printf("CreateMatrix1D() call completed\n");
printf("CreateMatrix1D() returned struct, first element: %d\n", matrix1d.data[0]);
// Test CreateMatrix2D() which returns Array_int32_t_3_4
printf("About to call CreateMatrix2D()...\n");
fflush(stdout);
Array_int32_t_3_4 matrix2d = CreateMatrix2D();
printf("CreateMatrix2D() call completed\n");
printf("CreateMatrix2D() returned struct, first element: %d\n", matrix2d.data[0][0]);
// Test CreateMatrix3D() which returns Array_uint8_t_2_3_4
Array_uint8_t_2_3_4 cube = CreateMatrix3D();
printf("CreateMatrix3D() returned struct, first element: %u\n", cube.data[0][0][0]);
// Test CreateGrid5x4() which returns Array_double_5_4
Array_double_5_4 grid = CreateGrid5x4();
printf("CreateGrid5x4() returned struct, first element: %f\n", grid.data[0][0]);
// Test NoReturn function
// Note: This function takes a string parameter which is complex to pass from C
// We'll skip it for now or pass a simple string if the binding allows
printf("NoReturn test skipped (string parameter)\n");
printf("C demo completed!\n");
return 0;
}

View File

@@ -126,7 +126,6 @@ type context struct {
cgoArgs []llssa.Expr
cgoRet llssa.Expr
cgoSymbols []string
cgoExports map[string]string
}
type pkgState byte
@@ -1008,7 +1007,6 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
loaded: map[*types.Package]*pkgInfo{
types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly?
},
cgoExports: make(map[string]string),
cgoSymbols: make([]string, 0, 128),
}
ctx.initPyModule()
@@ -1044,12 +1042,6 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
fn()
}
externs = ctx.cgoSymbols
for fnName, exportName := range ctx.cgoExports {
fn := ret.FuncOf(fnName)
if fn != nil {
fn.SetName(exportName)
}
}
return
}

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,16 +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) {
p.initCgoExport(line, len(export), f)
// rewrite //export FuncName to //export FuncName FuncName
funcName := strings.TrimSpace(line[len(export):])
line = line + " " + funcName
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
@@ -319,23 +324,15 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
return noDirective
}
func (p *context) initCgoExport(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) {
name := strings.TrimSpace(line[prefix:])
if fullName, _, ok := f(name); ok {
p.cgoExports[fullName] = name // TODO(xsw): why not use prog.SetLinkname?
}
}
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, isVar, ok := f(inPkgName); ok {
if fullName, _, ok := f(inPkgName); ok {
link := strings.TrimLeft(text[idx+1:], " ")
if isVar || strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr
p.prog.SetLinkname(fullName, link)
} else {
p.prog.SetLinkname(fullName, "C."+link)
if export {
p.pkg.SetExport(fullName, link)
}
} else {
fmt.Fprintln(os.Stderr, "==>", line)
@@ -523,6 +520,9 @@ func (p *context) funcName(fn *ssa.Function) (*types.Package, string, int) {
if checkCgo(fname) && !cgoIgnored(fname) {
return nil, fname, llgoInstr
}
if strings.HasPrefix(fname, "_cgoexp_") {
return nil, fname, ignoredFunc
}
if isCgoExternSymbol(fn) {
if _, ok := llgoInstrs[fname]; ok {
return nil, fname, llgoInstr

View File

@@ -75,7 +75,7 @@ func PassBuildFlags(cmd *Command) *PassArgs {
p.Bool("a")
p.Bool("linkshared", "race", "msan", "asan",
"trimpath", "work")
p.Var("p", "asmflags", "compiler", "buildmode",
p.Var("p", "asmflags", "compiler",
"gcflags", "gccgoflags", "installsuffix",
"ldflags", "pkgdir", "toolexec", "buildvcs")
return p

View File

@@ -39,6 +39,7 @@ func init() {
flags.AddCommonFlags(&Cmd.Flag)
flags.AddBuildFlags(&Cmd.Flag)
flags.AddBuildModeFlags(&Cmd.Flag)
flags.AddEmulatorFlags(&Cmd.Flag)
flags.AddEmbeddedFlags(&Cmd.Flag)
flags.AddOutputFlags(&Cmd.Flag)
@@ -51,7 +52,10 @@ func runCmd(cmd *base.Command, args []string) {
}
conf := build.NewDefaultConf(build.ModeBuild)
flags.UpdateConfig(conf)
if err := flags.UpdateBuildConfig(conf); err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
args = cmd.Flag.Args()

View File

@@ -25,6 +25,7 @@ func AddOutputFlags(fs *flag.FlagSet) {
var Verbose bool
var BuildEnv string
var BuildMode string
var Tags string
var Target string
var Emulator bool
@@ -52,6 +53,10 @@ func AddBuildFlags(fs *flag.FlagSet) {
}
}
func AddBuildModeFlags(fs *flag.FlagSet) {
fs.StringVar(&BuildMode, "buildmode", "exe", "Build mode (exe, c-archive, c-shared)")
}
var Gen bool
func AddEmulatorFlags(fs *flag.FlagSet) {
@@ -68,12 +73,13 @@ func AddCmpTestFlags(fs *flag.FlagSet) {
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
}
func UpdateConfig(conf *build.Config) {
func UpdateConfig(conf *build.Config) error {
conf.Tags = Tags
conf.Verbose = Verbose
conf.Target = Target
conf.Port = Port
conf.BaudRate = BaudRate
switch conf.Mode {
case build.ModeBuild:
conf.OutFile = OutputFile
@@ -99,4 +105,18 @@ func UpdateConfig(conf *build.Config) {
conf.GenLL = GenLLFiles
conf.ForceEspClang = ForceEspClang
}
return nil
}
func UpdateBuildConfig(conf *build.Config) error {
// First apply common config
if err := UpdateConfig(conf); err != nil {
return err
}
if err := build.ValidateBuildMode(BuildMode); err != nil {
return err
}
conf.BuildMode = build.BuildMode(BuildMode)
return nil
}

View File

@@ -47,7 +47,10 @@ func runCmd(cmd *base.Command, args []string) {
}
conf := build.NewDefaultConf(build.ModeInstall)
flags.UpdateConfig(conf)
if err := flags.UpdateConfig(conf); err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
args = cmd.Flag.Args()
_, err := build.Do(args, conf)

View File

@@ -76,7 +76,10 @@ func runCmdEx(cmd *base.Command, args []string, mode build.Mode) {
}
conf := build.NewDefaultConf(mode)
flags.UpdateConfig(conf)
if err := flags.UpdateConfig(conf); err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
args = cmd.Flag.Args()
args, runArgs, err := parseRunArgs(args)

View File

@@ -7,6 +7,7 @@ import (
"github.com/goplus/llgo/cmd/internal/base"
"github.com/goplus/llgo/cmd/internal/flags"
"github.com/goplus/llgo/internal/build"
"github.com/goplus/llgo/internal/mockable"
)
// llgo test
@@ -30,12 +31,15 @@ func runCmd(cmd *base.Command, args []string) {
}
conf := build.NewDefaultConf(build.ModeTest)
flags.UpdateConfig(conf)
if err := flags.UpdateConfig(conf); err != nil {
fmt.Fprintln(os.Stderr, err)
mockable.Exit(1)
}
args = cmd.Flag.Args()
_, err := build.Do(args, conf)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
mockable.Exit(1)
}
}

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"
@@ -66,6 +67,24 @@ const (
ModeGen
)
type BuildMode string
const (
BuildModeExe BuildMode = "exe"
BuildModeCArchive BuildMode = "c-archive"
BuildModeCShared BuildMode = "c-shared"
)
// ValidateBuildMode checks if the build mode is valid
func ValidateBuildMode(mode string) error {
switch BuildMode(mode) {
case BuildModeExe, BuildModeCArchive, BuildModeCShared:
return nil
default:
return fmt.Errorf("invalid build mode %q, must be one of: exe, c-archive, c-shared", mode)
}
}
type AbiMode = cabi.Mode
const (
@@ -104,6 +123,7 @@ type Config struct {
BaudRate int // baudrate for serial communication
RunArgs []string
Mode Mode
BuildMode BuildMode // Build mode: exe, c-archive, c-shared
AbiMode AbiMode
GenExpect bool // only valid for ModeCmpTest
Verbose bool
@@ -140,6 +160,7 @@ func NewDefaultConf(mode Mode) *Config {
Goarch: goarch,
BinPath: bin,
Mode: mode,
BuildMode: BuildModeExe,
AbiMode: cabi.ModeAllFunc,
}
return conf
@@ -156,23 +177,6 @@ func envGOPATH() (string, error) {
return filepath.Join(home, "go"), nil
}
func defaultAppExt(conf *Config) string {
if conf.Target != "" {
if strings.HasPrefix(conf.Target, "wasi") || strings.HasPrefix(conf.Target, "wasm") {
return ".wasm"
}
return ".elf"
}
switch conf.Goos {
case "windows":
return ".exe"
case "wasi", "wasip1", "js":
return ".wasm"
}
return ""
}
// -----------------------------------------------------------------------------
const (
@@ -192,6 +196,9 @@ func Do(args []string, conf *Config) ([]Package, error) {
if conf.AppExt == "" {
conf.AppExt = defaultAppExt(conf)
}
if conf.BuildMode == "" {
conf.BuildMode = BuildModeExe
}
// Handle crosscompile configuration first to set correct GOOS/GOARCH
forceEspClang := conf.ForceEspClang || conf.Target != ""
export, err := crosscompile.Use(conf.Goos, conf.Goarch, conf.Target, IsWasiThreadsEnabled(), forceEspClang)
@@ -361,12 +368,29 @@ func Do(args []string, conf *Config) ([]Package, error) {
return nil, err
}
// Link main package using the base output path
// Link main package using the output path from buildOutFmts
err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
if err != nil {
return nil, err
}
// Generate C headers for c-archive and c-shared modes before linking
if ctx.buildConf.BuildMode == BuildModeCArchive || ctx.buildConf.BuildMode == BuildModeCShared {
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
}
continue
}
envMap := outFmts.ToEnvMap()
// Only convert formats when Target is specified
@@ -755,6 +779,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
}
}
})
// Generate main module file (needed for global variables even in library modes)
entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit)
if err != nil {
return err
@@ -794,13 +819,31 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l
}
}
return linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose)
err = linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose)
if err != nil {
return err
}
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 {
return createStaticArchive(ctx, app, objFiles, verbose)
}
buildArgs := []string{"-o", app}
buildArgs = append(buildArgs, linkArgs...)
// Add build mode specific linker arguments
switch ctx.buildConf.BuildMode {
case BuildModeCShared:
buildArgs = append(buildArgs, "-shared", "-fPIC")
case BuildModeExe:
// Default executable mode, no additional flags needed
}
// Add common linker arguments based on target OS and architecture
if IsDbgSymsEnabled() {
buildArgs = append(buildArgs, "-gdwarf-4")
@@ -813,6 +856,27 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
return cmd.Link(buildArgs...)
}
func createStaticArchive(ctx *context, archivePath string, objFiles []string, verbose bool) error {
// Use ar tool to create static archive
args := []string{"rcs", archivePath}
args = append(args, objFiles...)
if verbose {
fmt.Fprintf(os.Stderr, "ar %s\n", strings.Join(args, " "))
}
cmd := exec.Command("ar", args...)
output, err := cmd.CombinedOutput()
if err != nil {
if verbose && len(output) > 0 {
fmt.Fprintf(os.Stderr, "ar output: %s\n", output)
}
return fmt.Errorf("ar command failed: %w", err)
}
return nil
}
func isWasmTarget(goos string) bool {
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
}
@@ -889,7 +953,18 @@ define weak void @_start() {
if !needStart(ctx) {
startDefine = ""
}
mainCode := fmt.Sprintf(`; ModuleID = 'main'
var mainCode string
// For library modes (c-archive, c-shared), only generate global variables
if ctx.buildConf.BuildMode != BuildModeExe {
mainCode = `; ModuleID = 'main'
source_filename = "main"
@__llgo_argc = global i32 0, align 4
@__llgo_argv = global ptr null, align 8
`
} else {
// For executable mode, generate full main function
mainCode = fmt.Sprintf(`; ModuleID = 'main'
source_filename = "main"
%s
@__llgo_argc = global i32 0, align 4
@@ -926,6 +1001,7 @@ _llgo_0:
pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath,
startDefine, mainDefine, stdioNobuf,
pyInit, rtInit, mainPkgPath, mainPkgPath)
}
return exportObject(ctx, pkg.PkgPath+".main", pkg.ExportFile+"-main", []byte(mainCode))
}

View File

@@ -47,46 +47,94 @@ func setOutFmt(conf *Config, formatName string) {
}
// buildOutFmts creates OutFmtDetails based on package, configuration and multi-package status
// determineBaseNameAndDir extracts the base name and directory from configuration
func determineBaseNameAndDir(pkgName string, conf *Config, multiPkg bool) (baseName, dir string) {
switch conf.Mode {
case ModeInstall:
return pkgName, conf.BinPath
case ModeBuild:
if !multiPkg && conf.OutFile != "" {
dir = filepath.Dir(conf.OutFile)
baseName = strings.TrimSuffix(filepath.Base(conf.OutFile), conf.AppExt)
if dir == "." {
dir = ""
}
return baseName, dir
}
return pkgName, ""
}
// Other modes (run, test, etc.)
return pkgName, ""
}
// applyPrefix applies build mode specific naming conventions
func applyPrefix(baseName string, buildMode BuildMode, target string, goos string) string {
// Determine the effective OS for naming conventions
effectiveGoos := goos
if target != "" {
// Embedded targets follow Linux conventions
effectiveGoos = "linux"
}
switch buildMode {
case BuildModeCArchive:
// Static libraries: libname.a (add lib prefix if missing)
if !strings.HasPrefix(baseName, "lib") {
return "lib" + baseName
}
return baseName
case BuildModeCShared:
// Shared libraries: libname.so/libname.dylib (add lib prefix if missing, except on Windows)
if effectiveGoos != "windows" && !strings.HasPrefix(baseName, "lib") {
return "lib" + baseName
}
return baseName
case BuildModeExe:
// Executables: name or name.exe (no lib prefix)
if strings.HasPrefix(baseName, "lib") {
return strings.TrimPrefix(baseName, "lib")
}
return baseName
}
return baseName
}
// buildOutputPath creates the final output path from baseName, dir and other parameters
func buildOutputPath(baseName, dir string, conf *Config, multiPkg bool, appExt string) (string, error) {
baseName = applyPrefix(baseName, conf.BuildMode, conf.Target, conf.Goos)
if dir != "" {
return filepath.Join(dir, baseName+appExt), nil
} else if (conf.Mode == ModeBuild && multiPkg) || (conf.Mode != ModeBuild && conf.Mode != ModeInstall) {
return genTempOutputFile(baseName, appExt)
} else {
return baseName + appExt, nil
}
}
func buildOutFmts(pkgName string, conf *Config, multiPkg bool, crossCompile *crosscompile.Export) (*OutFmtDetails, error) {
details := &OutFmtDetails{}
var err error
if conf.Target == "" {
// Native target
if conf.Mode == ModeInstall {
details.Out = filepath.Join(conf.BinPath, pkgName+conf.AppExt)
} else if conf.Mode == ModeBuild && !multiPkg && conf.OutFile != "" {
base := strings.TrimSuffix(conf.OutFile, conf.AppExt)
details.Out = base + conf.AppExt
} else if conf.Mode == ModeBuild && !multiPkg {
details.Out = pkgName + conf.AppExt
} else {
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
// Determine base name and directory
baseName, dir := determineBaseNameAndDir(pkgName, conf, multiPkg)
// Build output path
outputPath, err := buildOutputPath(baseName, dir, conf, multiPkg, conf.AppExt)
if err != nil {
return nil, err
}
}
details.Out = outputPath
if conf.Target == "" {
// Native target - we're done
return details, nil
}
needRun := slices.Contains([]Mode{ModeRun, ModeTest, ModeCmpTest, ModeInstall}, conf.Mode)
if multiPkg {
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
if err != nil {
return nil, err
}
} else if conf.OutFile != "" {
base := strings.TrimSuffix(conf.OutFile, conf.AppExt)
details.Out = base + conf.AppExt
} else if conf.Mode == ModeBuild {
details.Out = pkgName + conf.AppExt
} else {
details.Out, err = genTempOutputFile(pkgName, conf.AppExt)
if err != nil {
return nil, err
}
}
// Check emulator format if emulator mode is enabled
outFmt := ""
if needRun {
@@ -163,3 +211,39 @@ func (details *OutFmtDetails) ToEnvMap() map[string]string {
return envMap
}
func defaultAppExt(conf *Config) string {
// Handle build mode specific extensions first
switch conf.BuildMode {
case BuildModeCArchive:
return ".a"
case BuildModeCShared:
switch conf.Goos {
case "windows":
return ".dll"
case "darwin":
return ".dylib"
default:
return ".so"
}
case BuildModeExe:
// For executable mode, handle target-specific logic
if conf.Target != "" {
if strings.HasPrefix(conf.Target, "wasi") || strings.HasPrefix(conf.Target, "wasm") {
return ".wasm"
}
return ".elf"
}
switch conf.Goos {
case "windows":
return ".exe"
case "wasi", "wasip1", "js":
return ".wasm"
}
return ""
}
// This should not be reached, but kept for safety
return ""
}

View File

@@ -364,3 +364,241 @@ func TestBuildOutFmtsNativeTarget(t *testing.T) {
})
}
}
func TestBuildOutFmtsBuildModes(t *testing.T) {
tests := []struct {
name string
pkgName string
buildMode BuildMode
outFile string
mode Mode
target string
goos string
appExt string
expectedOut string
}{
// C-Archive tests
{
name: "c_archive_build_linux",
pkgName: "mylib",
buildMode: BuildModeCArchive,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".a",
expectedOut: "libmylib.a",
},
{
name: "c_archive_build_with_outfile",
pkgName: "mylib",
buildMode: BuildModeCArchive,
outFile: "custom.a",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".a",
expectedOut: "libcustom.a",
},
{
name: "c_archive_build_with_path",
pkgName: "mylib",
buildMode: BuildModeCArchive,
outFile: "build/custom.a",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".a",
expectedOut: "build/libcustom.a",
},
// C-Shared tests
{
name: "c_shared_build_linux",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: ".so",
expectedOut: "libmylib.so",
},
{
name: "c_shared_build_windows",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "",
goos: "windows",
appExt: ".dll",
expectedOut: "mylib.dll",
},
{
name: "c_shared_build_darwin",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "",
goos: "darwin",
appExt: ".dylib",
expectedOut: "libmylib.dylib",
},
{
name: "c_shared_embedded_target",
pkgName: "mylib",
buildMode: BuildModeCShared,
outFile: "",
mode: ModeBuild,
target: "rp2040",
goos: "windows",
appExt: ".so", // embedded follows linux rules
expectedOut: "libmylib.so",
},
// Executable tests
{
name: "exe_build_linux",
pkgName: "myapp",
buildMode: BuildModeExe,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: "",
expectedOut: "myapp",
},
{
name: "exe_build_windows",
pkgName: "myapp",
buildMode: BuildModeExe,
outFile: "",
mode: ModeBuild,
target: "",
goos: "windows",
appExt: ".exe",
expectedOut: "myapp.exe",
},
{
name: "exe_remove_lib_prefix",
pkgName: "libmyapp",
buildMode: BuildModeExe,
outFile: "",
mode: ModeBuild,
target: "",
goos: "linux",
appExt: "",
expectedOut: "myapp",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
conf := &Config{
Mode: tt.mode,
BuildMode: tt.buildMode,
Target: tt.target,
Goos: tt.goos,
OutFile: tt.outFile,
AppExt: tt.appExt,
}
crossCompile := &crosscompile.Export{}
result, err := buildOutFmts(tt.pkgName, conf, false, crossCompile)
if err != nil {
t.Fatalf("buildOutFmts failed: %v", err)
}
if result.Out != tt.expectedOut {
t.Errorf("buildOutFmts(%q, buildMode=%v, target=%q, goos=%q) = %q, want %q",
tt.pkgName, tt.buildMode, tt.target, tt.goos, result.Out, tt.expectedOut)
}
})
}
}
func TestApplyBuildModeNaming(t *testing.T) {
tests := []struct {
name string
baseName string
buildMode BuildMode
target string
goos string
expected string
}{
// Executable tests
{
name: "exe_linux",
baseName: "myapp",
buildMode: BuildModeExe,
target: "",
goos: "linux",
expected: "myapp",
},
{
name: "exe_remove_lib_prefix",
baseName: "libmyapp",
buildMode: BuildModeExe,
target: "",
goos: "linux",
expected: "myapp",
},
// C-Archive tests
{
name: "c_archive_linux",
baseName: "mylib",
buildMode: BuildModeCArchive,
target: "",
goos: "linux",
expected: "libmylib",
},
{
name: "c_archive_existing_prefix",
baseName: "libmylib",
buildMode: BuildModeCArchive,
target: "",
goos: "linux",
expected: "libmylib",
},
// C-Shared tests
{
name: "c_shared_linux",
baseName: "mylib",
buildMode: BuildModeCShared,
target: "",
goos: "linux",
expected: "libmylib",
},
{
name: "c_shared_windows",
baseName: "mylib",
buildMode: BuildModeCShared,
target: "",
goos: "windows",
expected: "mylib", // Windows doesn't use lib prefix
},
{
name: "c_shared_embedded_rp2040",
baseName: "mylib",
buildMode: BuildModeCShared,
target: "rp2040",
goos: "darwin",
expected: "libmylib", // embedded follows linux rules
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := applyPrefix(tt.baseName, tt.buildMode, tt.target, tt.goos)
if result != tt.expected {
t.Errorf("applyBuildModeNaming(%q, %v, %q, %q) = %q, want %q",
tt.baseName, tt.buildMode, tt.target, tt.goos, result, tt.expected)
}
})
}
}

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

@@ -0,0 +1,770 @@
/*
* 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 {
if ssa.IsClosure(structType) {
return fmt.Errorf("closure type %s can't export to C header", typ.Obj().Name())
}
// 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
}
// 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:
if ssa.IsClosure(typ) {
panic("closure type can't export to C header")
}
// 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
// Generate proper function pointer syntax
return hw.generateFunctionPointerType(typ)
}
panic(fmt.Errorf("unsupported type: %v", t))
}
// generateFunctionPointerType generates C function pointer type for Go function signatures
func (hw *cheaderWriter) generateFunctionPointerType(sig *types.Signature) string {
// Generate return type
var returnType string
results := sig.Results()
if results == nil || results.Len() == 0 {
returnType = "void"
} else if results.Len() == 1 {
returnType = hw.goCTypeName(results.At(0).Type())
} else {
panic("multiple return values can't export to C header")
}
// Generate parameter types
var paramTypes []string
params := sig.Params()
if params == nil || params.Len() == 0 {
paramTypes = []string{"void"}
} else {
for i := 0; i < params.Len(); i++ {
paramType := hw.goCTypeName(params.At(i).Type())
paramTypes = append(paramTypes, paramType)
}
}
// Return function pointer type: returnType (*)(paramType1, paramType2, ...)
return fmt.Sprintf("%s (*)(%s)", returnType, strings.Join(paramTypes, ", "))
}
// 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)
}
cTypeName := hw.goCTypeName(typ)
// Special handling for function types
if sig, ok := underlying.(*types.Signature); ok {
// Generate return type
var returnType string
results := sig.Results()
if results == nil || results.Len() == 0 {
returnType = "void"
} else if results.Len() == 1 {
returnType = hw.goCTypeName(results.At(0).Type())
} else {
panic("multiple return values can't export to C header")
}
// Generate parameter types
var paramTypes []string
params := sig.Params()
if params == nil || params.Len() == 0 {
paramTypes = []string{"void"}
} else {
for i := 0; i < params.Len(); i++ {
paramType := hw.goCTypeName(params.At(i).Type())
paramTypes = append(paramTypes, paramType)
}
}
// Generate proper function pointer typedef: typedef returnType (*typeName)(params);
return fmt.Sprintf("typedef %s (*%s)(%s);", returnType, cTypeName, strings.Join(paramTypes, ", "))
}
// For other named types, create a typedef to the underlying type
underlyingCType := hw.goCTypeName(underlying)
if underlyingCType != "" {
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
}
// generateParameterDeclaration generates C parameter declaration for function parameters
func (hw *cheaderWriter) generateParameterDeclaration(paramType types.Type, paramName string) string {
var cType string
switch typ := paramType.(type) {
case *types.Array:
// Handle multidimensional arrays by collecting all dimensions
var dimensions []int64
baseType := types.Type(typ)
// 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)
// For parameters, preserve all array dimensions
// In C, array parameters need special handling for syntax
cType = elemType
// Store dimensions for later use with parameter name
var dimStr strings.Builder
for _, dim := range dimensions {
dimStr.WriteString(fmt.Sprintf("[%d]", dim))
}
// For single dimension, we can use pointer syntax
if len(dimensions) == 1 {
cType = elemType + "*"
} else {
// For multi-dimensional, we need to handle it when adding parameter name
// Store the dimension info in a special way
cType = elemType + "ARRAY_DIMS" + dimStr.String()
}
case *types.Pointer:
pointeeType := hw.goCTypeName(typ.Elem())
cType = pointeeType + "*"
default:
// Regular types
cType = hw.goCTypeName(paramType)
}
// Handle special array dimension syntax
if strings.Contains(cType, "ARRAY_DIMS") {
parts := strings.Split(cType, "ARRAY_DIMS")
elemType := parts[0]
dimStr := parts[1]
if paramName == "" {
// For unnamed parameters, keep dimension info: type[dim1][dim2]
return elemType + dimStr
}
// For named parameters, use proper array syntax: type name[dim1][dim2]
return elemType + " " + paramName + dimStr
}
if paramName == "" {
return cType
}
return cType + " " + paramName
}
// 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()
// Generate parameter declaration
paramDecl := hw.generateParameterDeclaration(paramType, paramName)
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 {
return fmt.Errorf("function %s not found", link)
}
// 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,938 @@
//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 (*)(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 function type (callback) parameters - IntCallback
intCallbackParams := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
intCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
intCallbackSig := types.NewSignatureType(nil, nil, nil, intCallbackParams, intCallbackResults, false)
err = hw.writeTypedefRecursive(intCallbackSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(IntCallback) error = %v", err)
}
// Test function type (callback) parameters - StringCallback
stringCallbackParams := types.NewTuple(types.NewVar(0, nil, "s", types.Typ[types.String]))
stringCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.String]))
stringCallbackSig := types.NewSignatureType(nil, nil, nil, stringCallbackParams, stringCallbackResults, false)
err = hw.writeTypedefRecursive(stringCallbackSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(StringCallback) error = %v", err)
}
// Test function type (callback) parameters - VoidCallback
voidCallbackSig := types.NewSignatureType(nil, nil, nil, nil, nil, false)
err = hw.writeTypedefRecursive(voidCallbackSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(VoidCallback) error = %v", err)
}
// Test Named function type - this should trigger the function typedef generation
pkg := types.NewPackage("test", "test")
callbackParams := types.NewTuple(types.NewVar(0, nil, "x", types.Typ[types.Int]))
callbackSig := types.NewSignatureType(nil, nil, nil, callbackParams, nil, false)
callbackTypeName := types.NewTypeName(0, pkg, "Callback", nil)
namedCallback := types.NewNamed(callbackTypeName, callbackSig, nil)
// Test Named function type with no parameters - NoParamCallback func() int
noParamCallbackResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Int]))
noParamCallbackSig := types.NewSignatureType(nil, nil, nil, nil, noParamCallbackResults, false)
noParamCallbackTypeName := types.NewTypeName(0, pkg, "NoParamCallback", nil)
namedNoParamCallback := types.NewNamed(noParamCallbackTypeName, noParamCallbackSig, nil)
err = hw.writeTypedef(namedCallback)
if err != nil {
t.Errorf("writeTypedef(named function) error = %v", err)
}
err = hw.writeTypedef(namedNoParamCallback)
if err != nil {
t.Errorf("writeTypedef(no param callback) error = %v", err)
}
// Verify the generated typedef contains function pointer syntax
output := hw.typeBuf.String()
if !strings.Contains(output, "test_Callback") {
t.Errorf("Expected named function typedef in output")
}
if !strings.Contains(output, "(*test_Callback)") {
t.Errorf("Expected function pointer syntax in typedef: %s", output)
}
if !strings.Contains(output, "test_NoParamCallback") {
t.Errorf("Expected no-param callback typedef in output")
}
if !strings.Contains(output, "(*test_NoParamCallback)(void)") {
t.Errorf("Expected no-param function pointer syntax in typedef: %s", output)
}
// Test function signature with unnamed parameters (like //export ProcessThreeUnnamedParams)
unnamedParams := types.NewTuple(
types.NewVar(0, nil, "", types.Typ[types.Int]),
types.NewVar(0, nil, "", types.Typ[types.String]),
types.NewVar(0, nil, "", types.Typ[types.Bool]),
)
unnamedResults := types.NewTuple(types.NewVar(0, nil, "", types.Typ[types.Float64]))
unnamedSig := types.NewSignatureType(nil, nil, nil, unnamedParams, unnamedResults, false)
err = hw.writeTypedefRecursive(unnamedSig, make(map[string]bool))
if err != nil {
t.Errorf("writeTypedefRecursive(unnamed params) error = %v", err)
}
}
// Test generateParameterDeclaration function
func TestGenerateParameterDeclaration(t *testing.T) {
prog := ssa.NewProgram(nil)
hw := newCHeaderWriter(prog)
tests := []struct {
name string
paramType types.Type
paramName string
expected string
}{
{
name: "basic type with name",
paramType: types.Typ[types.Int],
paramName: "x",
expected: "intptr_t x",
},
{
name: "basic type without name",
paramType: types.Typ[types.Int],
paramName: "",
expected: "intptr_t",
},
{
name: "array type with name",
paramType: types.NewArray(types.Typ[types.Int], 5),
paramName: "arr",
expected: "intptr_t* arr",
},
{
name: "array type without name",
paramType: types.NewArray(types.Typ[types.Int], 5),
paramName: "",
expected: "intptr_t*",
},
{
name: "multidimensional array with name",
paramType: types.NewArray(types.NewArray(types.Typ[types.Int], 4), 3),
paramName: "matrix",
expected: "intptr_t matrix[3][4]",
},
{
name: "multidimensional array without name",
paramType: types.NewArray(types.NewArray(types.Typ[types.Int], 4), 3),
paramName: "",
expected: "intptr_t[3][4]",
},
{
name: "pointer type with name",
paramType: types.NewPointer(types.Typ[types.Int]),
paramName: "ptr",
expected: "intptr_t* ptr",
},
{
name: "pointer type without name",
paramType: types.NewPointer(types.Typ[types.Int]),
paramName: "",
expected: "intptr_t*",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := hw.generateParameterDeclaration(tt.paramType, tt.paramName)
if got != tt.expected {
t.Errorf("generateParameterDeclaration() = %q, want %q", got, tt.expected)
}
})
}
}
// 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

@@ -1344,7 +1344,7 @@ func (b Builder) PrintEx(ln bool, args ...Expr) (ret Expr) {
// -----------------------------------------------------------------------------
func checkExpr(v Expr, t types.Type, b Builder) Expr {
if st, ok := t.Underlying().(*types.Struct); ok && isClosure(st) {
if st, ok := t.Underlying().(*types.Struct); ok && IsClosure(st) {
if v.kind != vkClosure {
return b.Pkg.closureStub(b, t, v)
}

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)

View File

@@ -411,7 +411,7 @@ func (p Program) toLLVMNamedStruct(name string, raw *types.Struct) llvm.Type {
func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind) {
fields := p.toLLVMFields(raw)
ret = p.ctx.StructType(fields, false)
if isClosure(raw) {
if IsClosure(raw) {
kind = vkClosure
} else {
kind = vkStruct
@@ -419,7 +419,7 @@ func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind)
return
}
func isClosure(raw *types.Struct) bool {
func IsClosure(raw *types.Struct) bool {
n := raw.NumFields()
if n == 2 {
f1, f2 := raw.Field(0), raw.Field(1)
@@ -508,7 +508,7 @@ func (p Program) toNamed(raw *types.Named) Type {
case *types.Struct:
name := p.llvmNameOf(raw)
kind := vkStruct
if isClosure(t) {
if IsClosure(t) {
kind = vkClosure
}
return &aType{p.toLLVMNamedStruct(name, t), rawType{raw}, kind}

View File

@@ -92,7 +92,7 @@ func (p goTypes) cvtType(typ types.Type) (raw types.Type, cvt bool) {
return types.NewMap(key, elem), true
}
case *types.Struct:
if isClosure(t) {
if IsClosure(t) {
return typ, false
}
return p.cvtStruct(t)