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 export LLGO_FULL_RPATH=true
bash .github/workflows/test_demo.sh 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 - name: _xtool build tests
run: | run: |
cd _xtool cd _xtool

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
*.dll *.dll
*.so *.so
*.dylib *.dylib
*.a
test.db test.db
demo.ll 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 cgoArgs []llssa.Expr
cgoRet llssa.Expr cgoRet llssa.Expr
cgoSymbols []string cgoSymbols []string
cgoExports map[string]string
} }
type pkgState byte type pkgState byte
@@ -1008,7 +1007,6 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
loaded: map[*types.Package]*pkgInfo{ loaded: map[*types.Package]*pkgInfo{
types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly? types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly?
}, },
cgoExports: make(map[string]string),
cgoSymbols: make([]string, 0, 128), cgoSymbols: make([]string, 0, 128),
} }
ctx.initPyModule() ctx.initPyModule()
@@ -1044,12 +1042,6 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [
fn() fn()
} }
externs = ctx.cgoSymbols externs = ctx.cgoSymbols
for fnName, exportName := range ctx.cgoExports {
fn := ret.FuncOf(fnName)
if fn != nil {
fn.SetName(exportName)
}
}
return 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 { if !p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false) && cPkg {
// package C (https://github.com/goplus/llgo/issues/1165) // package C (https://github.com/goplus/llgo/issues/1165)
if decl.Recv == nil && token.IsExported(inPkgName) { 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: case *ast.GenDecl:
@@ -301,16 +303,19 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
directive = "//go:" directive = "//go:"
) )
if strings.HasPrefix(line, linkname) { if strings.HasPrefix(line, linkname) {
p.initLink(line, len(linkname), f) p.initLink(line, len(linkname), false, f)
return hasLinkname return hasLinkname
} else if strings.HasPrefix(line, llgolink2) { } else if strings.HasPrefix(line, llgolink2) {
p.initLink(line, len(llgolink2), f) p.initLink(line, len(llgolink2), false, f)
return hasLinkname return hasLinkname
} else if strings.HasPrefix(line, llgolink) { } else if strings.HasPrefix(line, llgolink) {
p.initLink(line, len(llgolink), f) p.initLink(line, len(llgolink), false, f)
return hasLinkname return hasLinkname
} else if strings.HasPrefix(line, export) { } 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 return hasLinkname
} else if strings.HasPrefix(line, directive) { } else if strings.HasPrefix(line, directive) {
// skip unknown annotation but continue to parse the next annotation // skip unknown annotation but continue to parse the next annotation
@@ -319,23 +324,15 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s
return noDirective return noDirective
} }
func (p *context) initCgoExport(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)) {
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)) {
text := strings.TrimSpace(line[prefix:]) text := strings.TrimSpace(line[prefix:])
if idx := strings.IndexByte(text, ' '); idx > 0 { if idx := strings.IndexByte(text, ' '); idx > 0 {
inPkgName := text[:idx] inPkgName := text[:idx]
if fullName, isVar, ok := f(inPkgName); ok { if fullName, _, ok := f(inPkgName); ok {
link := strings.TrimLeft(text[idx+1:], " ") link := strings.TrimLeft(text[idx+1:], " ")
if isVar || strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr p.prog.SetLinkname(fullName, link)
p.prog.SetLinkname(fullName, link) if export {
} else { p.pkg.SetExport(fullName, link)
p.prog.SetLinkname(fullName, "C."+link)
} }
} else { } else {
fmt.Fprintln(os.Stderr, "==>", line) 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) { if checkCgo(fname) && !cgoIgnored(fname) {
return nil, fname, llgoInstr return nil, fname, llgoInstr
} }
if strings.HasPrefix(fname, "_cgoexp_") {
return nil, fname, ignoredFunc
}
if isCgoExternSymbol(fn) { if isCgoExternSymbol(fn) {
if _, ok := llgoInstrs[fname]; ok { if _, ok := llgoInstrs[fname]; ok {
return nil, fname, llgoInstr return nil, fname, llgoInstr

View File

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

View File

@@ -39,6 +39,7 @@ func init() {
flags.AddCommonFlags(&Cmd.Flag) flags.AddCommonFlags(&Cmd.Flag)
flags.AddBuildFlags(&Cmd.Flag) flags.AddBuildFlags(&Cmd.Flag)
flags.AddBuildModeFlags(&Cmd.Flag)
flags.AddEmulatorFlags(&Cmd.Flag) flags.AddEmulatorFlags(&Cmd.Flag)
flags.AddEmbeddedFlags(&Cmd.Flag) flags.AddEmbeddedFlags(&Cmd.Flag)
flags.AddOutputFlags(&Cmd.Flag) flags.AddOutputFlags(&Cmd.Flag)
@@ -51,7 +52,10 @@ func runCmd(cmd *base.Command, args []string) {
} }
conf := build.NewDefaultConf(build.ModeBuild) 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() args = cmd.Flag.Args()

View File

@@ -25,6 +25,7 @@ func AddOutputFlags(fs *flag.FlagSet) {
var Verbose bool var Verbose bool
var BuildEnv string var BuildEnv string
var BuildMode string
var Tags string var Tags string
var Target string var Target string
var Emulator bool 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 var Gen bool
func AddEmulatorFlags(fs *flag.FlagSet) { func AddEmulatorFlags(fs *flag.FlagSet) {
@@ -68,12 +73,13 @@ func AddCmpTestFlags(fs *flag.FlagSet) {
fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file") fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file")
} }
func UpdateConfig(conf *build.Config) { func UpdateConfig(conf *build.Config) error {
conf.Tags = Tags conf.Tags = Tags
conf.Verbose = Verbose conf.Verbose = Verbose
conf.Target = Target conf.Target = Target
conf.Port = Port conf.Port = Port
conf.BaudRate = BaudRate conf.BaudRate = BaudRate
switch conf.Mode { switch conf.Mode {
case build.ModeBuild: case build.ModeBuild:
conf.OutFile = OutputFile conf.OutFile = OutputFile
@@ -99,4 +105,18 @@ func UpdateConfig(conf *build.Config) {
conf.GenLL = GenLLFiles conf.GenLL = GenLLFiles
conf.ForceEspClang = ForceEspClang 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) 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() args = cmd.Flag.Args()
_, err := build.Do(args, conf) _, 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) 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 = cmd.Flag.Args()
args, runArgs, err := parseRunArgs(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/base"
"github.com/goplus/llgo/cmd/internal/flags" "github.com/goplus/llgo/cmd/internal/flags"
"github.com/goplus/llgo/internal/build" "github.com/goplus/llgo/internal/build"
"github.com/goplus/llgo/internal/mockable"
) )
// llgo test // llgo test
@@ -30,12 +31,15 @@ func runCmd(cmd *base.Command, args []string) {
} }
conf := build.NewDefaultConf(build.ModeTest) 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() args = cmd.Flag.Args()
_, err := build.Do(args, conf) _, err := build.Do(args, conf)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) 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/env"
"github.com/goplus/llgo/internal/firmware" "github.com/goplus/llgo/internal/firmware"
"github.com/goplus/llgo/internal/flash" "github.com/goplus/llgo/internal/flash"
"github.com/goplus/llgo/internal/header"
"github.com/goplus/llgo/internal/mockable" "github.com/goplus/llgo/internal/mockable"
"github.com/goplus/llgo/internal/monitor" "github.com/goplus/llgo/internal/monitor"
"github.com/goplus/llgo/internal/packages" "github.com/goplus/llgo/internal/packages"
@@ -66,6 +67,24 @@ const (
ModeGen 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 type AbiMode = cabi.Mode
const ( const (
@@ -104,6 +123,7 @@ type Config struct {
BaudRate int // baudrate for serial communication BaudRate int // baudrate for serial communication
RunArgs []string RunArgs []string
Mode Mode Mode Mode
BuildMode BuildMode // Build mode: exe, c-archive, c-shared
AbiMode AbiMode AbiMode AbiMode
GenExpect bool // only valid for ModeCmpTest GenExpect bool // only valid for ModeCmpTest
Verbose bool Verbose bool
@@ -136,11 +156,12 @@ func NewDefaultConf(mode Mode) *Config {
goarch = runtime.GOARCH goarch = runtime.GOARCH
} }
conf := &Config{ conf := &Config{
Goos: goos, Goos: goos,
Goarch: goarch, Goarch: goarch,
BinPath: bin, BinPath: bin,
Mode: mode, Mode: mode,
AbiMode: cabi.ModeAllFunc, BuildMode: BuildModeExe,
AbiMode: cabi.ModeAllFunc,
} }
return conf return conf
} }
@@ -156,23 +177,6 @@ func envGOPATH() (string, error) {
return filepath.Join(home, "go"), nil 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 ( const (
@@ -192,6 +196,9 @@ func Do(args []string, conf *Config) ([]Package, error) {
if conf.AppExt == "" { if conf.AppExt == "" {
conf.AppExt = defaultAppExt(conf) conf.AppExt = defaultAppExt(conf)
} }
if conf.BuildMode == "" {
conf.BuildMode = BuildModeExe
}
// Handle crosscompile configuration first to set correct GOOS/GOARCH // Handle crosscompile configuration first to set correct GOOS/GOARCH
forceEspClang := conf.ForceEspClang || conf.Target != "" forceEspClang := conf.ForceEspClang || conf.Target != ""
export, err := crosscompile.Use(conf.Goos, conf.Goarch, conf.Target, IsWasiThreadsEnabled(), forceEspClang) 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 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) err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose)
if err != nil { if err != nil {
return nil, err 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() envMap := outFmts.ToEnvMap()
// Only convert formats when Target is specified // 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) entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit)
if err != nil { if err != nil {
return err 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 { 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 := []string{"-o", app}
buildArgs = append(buildArgs, linkArgs...) 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 // Add common linker arguments based on target OS and architecture
if IsDbgSymsEnabled() { if IsDbgSymsEnabled() {
buildArgs = append(buildArgs, "-gdwarf-4") buildArgs = append(buildArgs, "-gdwarf-4")
@@ -813,6 +856,27 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose
return cmd.Link(buildArgs...) 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 { func isWasmTarget(goos string) bool {
return slices.Contains([]string{"wasi", "js", "wasip1"}, goos) return slices.Contains([]string{"wasi", "js", "wasip1"}, goos)
} }
@@ -889,7 +953,18 @@ define weak void @_start() {
if !needStart(ctx) { if !needStart(ctx) {
startDefine = "" 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" source_filename = "main"
%s %s
@__llgo_argc = global i32 0, align 4 @__llgo_argc = global i32 0, align 4
@@ -923,9 +998,10 @@ _llgo_0:
ret i32 0 ret i32 0
} }
`, declSizeT, stdioDecl, `, declSizeT, stdioDecl,
pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath, pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath,
startDefine, mainDefine, stdioNobuf, startDefine, mainDefine, stdioNobuf,
pyInit, rtInit, mainPkgPath, mainPkgPath) pyInit, rtInit, mainPkgPath, mainPkgPath)
}
return exportObject(ctx, pkg.PkgPath+".main", pkg.ExportFile+"-main", []byte(mainCode)) 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 // 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) { func buildOutFmts(pkgName string, conf *Config, multiPkg bool, crossCompile *crosscompile.Export) (*OutFmtDetails, error) {
details := &OutFmtDetails{} details := &OutFmtDetails{}
var err error
// 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 == "" { if conf.Target == "" {
// Native target // Native target - we're done
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)
if err != nil {
return nil, err
}
}
return details, nil return details, nil
} }
needRun := slices.Contains([]Mode{ModeRun, ModeTest, ModeCmpTest, ModeInstall}, conf.Mode) 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 // Check emulator format if emulator mode is enabled
outFmt := "" outFmt := ""
if needRun { if needRun {
@@ -163,3 +211,39 @@ func (details *OutFmtDetails) ToEnvMap() map[string]string {
return envMap 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 { 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 { if v.kind != vkClosure {
return b.Pkg.closureStub(b, t, v) 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, pyobjs: pyobjs, pymods: pymods, strs: strs,
chkabi: chkabi, Prog: p, chkabi: chkabi, Prog: p,
di: nil, cu: nil, glbDbgVars: glbDbgVars, di: nil, cu: nil, glbDbgVars: glbDbgVars,
export: make(map[string]string),
} }
ret.abi.Init(pkgPath) ret.abi.Init(pkgPath)
return ret return ret
@@ -693,6 +694,8 @@ type aPackage struct {
NeedRuntime bool NeedRuntime bool
NeedPyInit bool NeedPyInit bool
export map[string]string // pkgPath.nameInPkg => exportname
} }
type Package = *aPackage type Package = *aPackage
@@ -701,6 +704,14 @@ func (p Package) Module() llvm.Module {
return p.mod 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 { func (p Package) rtFunc(fnName string) Expr {
p.NeedRuntime = true p.NeedRuntime = true
fn := p.Prog.runtime().Scope().Lookup(fnName).(*types.Func) 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) { func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind) {
fields := p.toLLVMFields(raw) fields := p.toLLVMFields(raw)
ret = p.ctx.StructType(fields, false) ret = p.ctx.StructType(fields, false)
if isClosure(raw) { if IsClosure(raw) {
kind = vkClosure kind = vkClosure
} else { } else {
kind = vkStruct kind = vkStruct
@@ -419,7 +419,7 @@ func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind)
return return
} }
func isClosure(raw *types.Struct) bool { func IsClosure(raw *types.Struct) bool {
n := raw.NumFields() n := raw.NumFields()
if n == 2 { if n == 2 {
f1, f2 := raw.Field(0), raw.Field(1) f1, f2 := raw.Field(0), raw.Field(1)
@@ -508,7 +508,7 @@ func (p Program) toNamed(raw *types.Named) Type {
case *types.Struct: case *types.Struct:
name := p.llvmNameOf(raw) name := p.llvmNameOf(raw)
kind := vkStruct kind := vkStruct
if isClosure(t) { if IsClosure(t) {
kind = vkClosure kind = vkClosure
} }
return &aType{p.toLLVMNamedStruct(name, t), rawType{raw}, kind} 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 return types.NewMap(key, elem), true
} }
case *types.Struct: case *types.Struct:
if isClosure(t) { if IsClosure(t) {
return typ, false return typ, false
} }
return p.cvtStruct(t) return p.cvtStruct(t)