diff --git a/.github/workflows/llgo.yml b/.github/workflows/llgo.yml index 1f027509..56d1b905 100644 --- a/.github/workflows/llgo.yml +++ b/.github/workflows/llgo.yml @@ -125,6 +125,13 @@ jobs: export LLGO_FULL_RPATH=true bash .github/workflows/test_demo.sh + - name: Test C header generation + run: | + echo "Testing C header generation in different build modes..." + cd _demo/go/export + chmod +x test.sh + ./test.sh + - name: _xtool build tests run: | cd _xtool diff --git a/.gitignore b/.gitignore index c388e11d..6976dfda 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ *.dll *.so *.dylib +*.a test.db demo.ll diff --git a/_demo/go/export/.gitignore b/_demo/go/export/.gitignore new file mode 100644 index 00000000..a2750a6c --- /dev/null +++ b/_demo/go/export/.gitignore @@ -0,0 +1 @@ +libexport.h diff --git a/_demo/go/export/c/c.go b/_demo/go/export/c/c.go new file mode 100644 index 00000000..5063a769 --- /dev/null +++ b/_demo/go/export/c/c.go @@ -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 +} diff --git a/_demo/go/export/export.go b/_demo/go/export/export.go new file mode 100644 index 00000000..777595f6 --- /dev/null +++ b/_demo/go/export/export.go @@ -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") +} diff --git a/_demo/go/export/libexport.h.want b/_demo/go/export/libexport.h.want new file mode 100644 index 00000000..8f3f535a --- /dev/null +++ b/_demo/go/export/libexport.h.want @@ -0,0 +1,312 @@ +/* Code generated by llgo; DO NOT EDIT. */ + +#ifndef __LIBEXPORT_H_ +#define __LIBEXPORT_H_ + +#include +#include + +#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_ */ diff --git a/_demo/go/export/test.sh b/_demo/go/export/test.sh new file mode 100755 index 00000000..0f8795d6 --- /dev/null +++ b/_demo/go/export/test.sh @@ -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!" diff --git a/_demo/go/export/use/Makefile b/_demo/go/export/use/Makefile new file mode 100644 index 00000000..468bb8cd --- /dev/null +++ b/_demo/go/export/use/Makefile @@ -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" \ No newline at end of file diff --git a/_demo/go/export/use/main.c b/_demo/go/export/use/main.c new file mode 100644 index 00000000..4a5f0656 --- /dev/null +++ b/_demo/go/export/use/main.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#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; +} \ No newline at end of file diff --git a/cl/compile.go b/cl/compile.go index fc24b9c2..9521ff14 100644 --- a/cl/compile.go +++ b/cl/compile.go @@ -126,7 +126,6 @@ type context struct { cgoArgs []llssa.Expr cgoRet llssa.Expr cgoSymbols []string - cgoExports map[string]string } type pkgState byte @@ -1008,7 +1007,6 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ loaded: map[*types.Package]*pkgInfo{ types.Unsafe: {kind: PkgDeclOnly}, // TODO(xsw): PkgNoInit or PkgDeclOnly? }, - cgoExports: make(map[string]string), cgoSymbols: make([]string, 0, 128), } ctx.initPyModule() @@ -1044,12 +1042,6 @@ func NewPackageEx(prog llssa.Program, patches Patches, pkg *ssa.Package, files [ fn() } externs = ctx.cgoSymbols - for fnName, exportName := range ctx.cgoExports { - fn := ret.FuncOf(fnName) - if fn != nil { - fn.SetName(exportName) - } - } return } diff --git a/cl/import.go b/cl/import.go index 62318981..ea472819 100644 --- a/cl/import.go +++ b/cl/import.go @@ -183,7 +183,9 @@ func (p *context) initFiles(pkgPath string, files []*ast.File, cPkg bool) { if !p.initLinknameByDoc(decl.Doc, fullName, inPkgName, false) && cPkg { // package C (https://github.com/goplus/llgo/issues/1165) if decl.Recv == nil && token.IsExported(inPkgName) { - p.prog.SetLinkname(fullName, strings.TrimPrefix(inPkgName, "X")) + exportName := strings.TrimPrefix(inPkgName, "X") + p.prog.SetLinkname(fullName, exportName) + p.pkg.SetExport(fullName, exportName) } } case *ast.GenDecl: @@ -301,16 +303,19 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s directive = "//go:" ) if strings.HasPrefix(line, linkname) { - p.initLink(line, len(linkname), f) + p.initLink(line, len(linkname), false, f) return hasLinkname } else if strings.HasPrefix(line, llgolink2) { - p.initLink(line, len(llgolink2), f) + p.initLink(line, len(llgolink2), false, f) return hasLinkname } else if strings.HasPrefix(line, llgolink) { - p.initLink(line, len(llgolink), f) + p.initLink(line, len(llgolink), false, f) return hasLinkname } else if strings.HasPrefix(line, export) { - p.initCgoExport(line, len(export), f) + // rewrite //export FuncName to //export FuncName FuncName + funcName := strings.TrimSpace(line[len(export):]) + line = line + " " + funcName + p.initLink(line, len(export), true, f) return hasLinkname } else if strings.HasPrefix(line, directive) { // skip unknown annotation but continue to parse the next annotation @@ -319,23 +324,15 @@ func (p *context) initLinkname(line string, f func(inPkgName string) (fullName s return noDirective } -func (p *context) initCgoExport(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) { - name := strings.TrimSpace(line[prefix:]) - if fullName, _, ok := f(name); ok { - p.cgoExports[fullName] = name // TODO(xsw): why not use prog.SetLinkname? - } -} - -func (p *context) initLink(line string, prefix int, f func(inPkgName string) (fullName string, isVar, ok bool)) { +func (p *context) initLink(line string, prefix int, export bool, f func(inPkgName string) (fullName string, isVar, ok bool)) { text := strings.TrimSpace(line[prefix:]) if idx := strings.IndexByte(text, ' '); idx > 0 { inPkgName := text[:idx] - if fullName, isVar, ok := f(inPkgName); ok { + if fullName, _, ok := f(inPkgName); ok { link := strings.TrimLeft(text[idx+1:], " ") - if isVar || strings.Contains(link, ".") { // eg. C.printf, C.strlen, llgo.cstr - p.prog.SetLinkname(fullName, link) - } else { - p.prog.SetLinkname(fullName, "C."+link) + p.prog.SetLinkname(fullName, link) + if export { + p.pkg.SetExport(fullName, link) } } else { fmt.Fprintln(os.Stderr, "==>", line) @@ -523,6 +520,9 @@ func (p *context) funcName(fn *ssa.Function) (*types.Package, string, int) { if checkCgo(fname) && !cgoIgnored(fname) { return nil, fname, llgoInstr } + if strings.HasPrefix(fname, "_cgoexp_") { + return nil, fname, ignoredFunc + } if isCgoExternSymbol(fn) { if _, ok := llgoInstrs[fname]; ok { return nil, fname, llgoInstr diff --git a/cmd/internal/base/pass.go b/cmd/internal/base/pass.go index c04be72f..5b595cfa 100644 --- a/cmd/internal/base/pass.go +++ b/cmd/internal/base/pass.go @@ -75,7 +75,7 @@ func PassBuildFlags(cmd *Command) *PassArgs { p.Bool("a") p.Bool("linkshared", "race", "msan", "asan", "trimpath", "work") - p.Var("p", "asmflags", "compiler", "buildmode", + p.Var("p", "asmflags", "compiler", "gcflags", "gccgoflags", "installsuffix", "ldflags", "pkgdir", "toolexec", "buildvcs") return p diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go index 98d4748c..bcb79dfb 100644 --- a/cmd/internal/build/build.go +++ b/cmd/internal/build/build.go @@ -39,6 +39,7 @@ func init() { flags.AddCommonFlags(&Cmd.Flag) flags.AddBuildFlags(&Cmd.Flag) + flags.AddBuildModeFlags(&Cmd.Flag) flags.AddEmulatorFlags(&Cmd.Flag) flags.AddEmbeddedFlags(&Cmd.Flag) flags.AddOutputFlags(&Cmd.Flag) @@ -51,7 +52,10 @@ func runCmd(cmd *base.Command, args []string) { } conf := build.NewDefaultConf(build.ModeBuild) - flags.UpdateConfig(conf) + if err := flags.UpdateBuildConfig(conf); err != nil { + fmt.Fprintln(os.Stderr, err) + mockable.Exit(1) + } args = cmd.Flag.Args() diff --git a/cmd/internal/flags/flags.go b/cmd/internal/flags/flags.go index 0b27cf93..232edbd4 100644 --- a/cmd/internal/flags/flags.go +++ b/cmd/internal/flags/flags.go @@ -25,6 +25,7 @@ func AddOutputFlags(fs *flag.FlagSet) { var Verbose bool var BuildEnv string +var BuildMode string var Tags string var Target string var Emulator bool @@ -52,6 +53,10 @@ func AddBuildFlags(fs *flag.FlagSet) { } } +func AddBuildModeFlags(fs *flag.FlagSet) { + fs.StringVar(&BuildMode, "buildmode", "exe", "Build mode (exe, c-archive, c-shared)") +} + var Gen bool func AddEmulatorFlags(fs *flag.FlagSet) { @@ -68,12 +73,13 @@ func AddCmpTestFlags(fs *flag.FlagSet) { fs.BoolVar(&Gen, "gen", false, "Generate llgo.expect file") } -func UpdateConfig(conf *build.Config) { +func UpdateConfig(conf *build.Config) error { conf.Tags = Tags conf.Verbose = Verbose conf.Target = Target conf.Port = Port conf.BaudRate = BaudRate + switch conf.Mode { case build.ModeBuild: conf.OutFile = OutputFile @@ -99,4 +105,18 @@ func UpdateConfig(conf *build.Config) { conf.GenLL = GenLLFiles conf.ForceEspClang = ForceEspClang } + return nil +} + +func UpdateBuildConfig(conf *build.Config) error { + // First apply common config + if err := UpdateConfig(conf); err != nil { + return err + } + if err := build.ValidateBuildMode(BuildMode); err != nil { + return err + } + conf.BuildMode = build.BuildMode(BuildMode) + + return nil } diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go index 037c4c68..aab36797 100644 --- a/cmd/internal/install/install.go +++ b/cmd/internal/install/install.go @@ -47,7 +47,10 @@ func runCmd(cmd *base.Command, args []string) { } conf := build.NewDefaultConf(build.ModeInstall) - flags.UpdateConfig(conf) + if err := flags.UpdateConfig(conf); err != nil { + fmt.Fprintln(os.Stderr, err) + mockable.Exit(1) + } args = cmd.Flag.Args() _, err := build.Do(args, conf) diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go index 68b8ce83..f075fad7 100644 --- a/cmd/internal/run/run.go +++ b/cmd/internal/run/run.go @@ -76,7 +76,10 @@ func runCmdEx(cmd *base.Command, args []string, mode build.Mode) { } conf := build.NewDefaultConf(mode) - flags.UpdateConfig(conf) + if err := flags.UpdateConfig(conf); err != nil { + fmt.Fprintln(os.Stderr, err) + mockable.Exit(1) + } args = cmd.Flag.Args() args, runArgs, err := parseRunArgs(args) diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go index 0a8a5a05..f330db0d 100644 --- a/cmd/internal/test/test.go +++ b/cmd/internal/test/test.go @@ -7,6 +7,7 @@ import ( "github.com/goplus/llgo/cmd/internal/base" "github.com/goplus/llgo/cmd/internal/flags" "github.com/goplus/llgo/internal/build" + "github.com/goplus/llgo/internal/mockable" ) // llgo test @@ -30,12 +31,15 @@ func runCmd(cmd *base.Command, args []string) { } conf := build.NewDefaultConf(build.ModeTest) - flags.UpdateConfig(conf) + if err := flags.UpdateConfig(conf); err != nil { + fmt.Fprintln(os.Stderr, err) + mockable.Exit(1) + } args = cmd.Flag.Args() _, err := build.Do(args, conf) if err != nil { fmt.Fprintln(os.Stderr, err) - os.Exit(1) + mockable.Exit(1) } } diff --git a/internal/build/build.go b/internal/build/build.go index 6d85fbec..8676838c 100644 --- a/internal/build/build.go +++ b/internal/build/build.go @@ -43,6 +43,7 @@ import ( "github.com/goplus/llgo/internal/env" "github.com/goplus/llgo/internal/firmware" "github.com/goplus/llgo/internal/flash" + "github.com/goplus/llgo/internal/header" "github.com/goplus/llgo/internal/mockable" "github.com/goplus/llgo/internal/monitor" "github.com/goplus/llgo/internal/packages" @@ -66,6 +67,24 @@ const ( ModeGen ) +type BuildMode string + +const ( + BuildModeExe BuildMode = "exe" + BuildModeCArchive BuildMode = "c-archive" + BuildModeCShared BuildMode = "c-shared" +) + +// ValidateBuildMode checks if the build mode is valid +func ValidateBuildMode(mode string) error { + switch BuildMode(mode) { + case BuildModeExe, BuildModeCArchive, BuildModeCShared: + return nil + default: + return fmt.Errorf("invalid build mode %q, must be one of: exe, c-archive, c-shared", mode) + } +} + type AbiMode = cabi.Mode const ( @@ -104,6 +123,7 @@ type Config struct { BaudRate int // baudrate for serial communication RunArgs []string Mode Mode + BuildMode BuildMode // Build mode: exe, c-archive, c-shared AbiMode AbiMode GenExpect bool // only valid for ModeCmpTest Verbose bool @@ -136,11 +156,12 @@ func NewDefaultConf(mode Mode) *Config { goarch = runtime.GOARCH } conf := &Config{ - Goos: goos, - Goarch: goarch, - BinPath: bin, - Mode: mode, - AbiMode: cabi.ModeAllFunc, + Goos: goos, + Goarch: goarch, + BinPath: bin, + Mode: mode, + BuildMode: BuildModeExe, + AbiMode: cabi.ModeAllFunc, } return conf } @@ -156,23 +177,6 @@ func envGOPATH() (string, error) { return filepath.Join(home, "go"), nil } -func defaultAppExt(conf *Config) string { - if conf.Target != "" { - if strings.HasPrefix(conf.Target, "wasi") || strings.HasPrefix(conf.Target, "wasm") { - return ".wasm" - } - return ".elf" - } - - switch conf.Goos { - case "windows": - return ".exe" - case "wasi", "wasip1", "js": - return ".wasm" - } - return "" -} - // ----------------------------------------------------------------------------- const ( @@ -192,6 +196,9 @@ func Do(args []string, conf *Config) ([]Package, error) { if conf.AppExt == "" { conf.AppExt = defaultAppExt(conf) } + if conf.BuildMode == "" { + conf.BuildMode = BuildModeExe + } // Handle crosscompile configuration first to set correct GOOS/GOARCH forceEspClang := conf.ForceEspClang || conf.Target != "" export, err := crosscompile.Use(conf.Goos, conf.Goarch, conf.Target, IsWasiThreadsEnabled(), forceEspClang) @@ -361,12 +368,29 @@ func Do(args []string, conf *Config) ([]Package, error) { return nil, err } - // Link main package using the base output path + // Link main package using the output path from buildOutFmts err = linkMainPkg(ctx, pkg, allPkgs, global, outFmts.Out, verbose) if err != nil { return nil, err } + // Generate C headers for c-archive and c-shared modes before linking + if ctx.buildConf.BuildMode == BuildModeCArchive || ctx.buildConf.BuildMode == BuildModeCShared { + libname := strings.TrimSuffix(filepath.Base(outFmts.Out), conf.AppExt) + headerPath := filepath.Join(filepath.Dir(outFmts.Out), libname) + ".h" + pkgs := make([]llssa.Package, 0, len(allPkgs)) + for _, p := range allPkgs { + if p.LPkg != nil { + pkgs = append(pkgs, p.LPkg) + } + } + headerErr := header.GenHeaderFile(prog, pkgs, libname, headerPath, verbose) + if headerErr != nil { + return nil, headerErr + } + continue + } + envMap := outFmts.ToEnvMap() // Only convert formats when Target is specified @@ -755,6 +779,7 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l } } }) + // Generate main module file (needed for global variables even in library modes) entryObjFile, err := genMainModuleFile(ctx, llssa.PkgRuntime, pkg, needRuntime, needPyInit) if err != nil { return err @@ -794,13 +819,31 @@ func linkMainPkg(ctx *context, pkg *packages.Package, pkgs []*aPackage, global l } } - return linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose) + err = linkObjFiles(ctx, outputPath, objFiles, linkArgs, verbose) + if err != nil { + return err + } + + return nil } func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose bool) error { + // Handle c-archive mode differently - use ar tool instead of linker + if ctx.buildConf.BuildMode == BuildModeCArchive { + return createStaticArchive(ctx, app, objFiles, verbose) + } + buildArgs := []string{"-o", app} buildArgs = append(buildArgs, linkArgs...) + // Add build mode specific linker arguments + switch ctx.buildConf.BuildMode { + case BuildModeCShared: + buildArgs = append(buildArgs, "-shared", "-fPIC") + case BuildModeExe: + // Default executable mode, no additional flags needed + } + // Add common linker arguments based on target OS and architecture if IsDbgSymsEnabled() { buildArgs = append(buildArgs, "-gdwarf-4") @@ -813,6 +856,27 @@ func linkObjFiles(ctx *context, app string, objFiles, linkArgs []string, verbose return cmd.Link(buildArgs...) } +func createStaticArchive(ctx *context, archivePath string, objFiles []string, verbose bool) error { + // Use ar tool to create static archive + args := []string{"rcs", archivePath} + args = append(args, objFiles...) + + if verbose { + fmt.Fprintf(os.Stderr, "ar %s\n", strings.Join(args, " ")) + } + + cmd := exec.Command("ar", args...) + output, err := cmd.CombinedOutput() + if err != nil { + if verbose && len(output) > 0 { + fmt.Fprintf(os.Stderr, "ar output: %s\n", output) + } + return fmt.Errorf("ar command failed: %w", err) + } + + return nil +} + func isWasmTarget(goos string) bool { return slices.Contains([]string{"wasi", "js", "wasip1"}, goos) } @@ -889,7 +953,18 @@ define weak void @_start() { if !needStart(ctx) { startDefine = "" } - mainCode := fmt.Sprintf(`; ModuleID = 'main' + + var mainCode string + // For library modes (c-archive, c-shared), only generate global variables + if ctx.buildConf.BuildMode != BuildModeExe { + mainCode = `; ModuleID = 'main' +source_filename = "main" +@__llgo_argc = global i32 0, align 4 +@__llgo_argv = global ptr null, align 8 +` + } else { + // For executable mode, generate full main function + mainCode = fmt.Sprintf(`; ModuleID = 'main' source_filename = "main" %s @__llgo_argc = global i32 0, align 4 @@ -923,9 +998,10 @@ _llgo_0: ret i32 0 } `, declSizeT, stdioDecl, - pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath, - startDefine, mainDefine, stdioNobuf, - pyInit, rtInit, mainPkgPath, mainPkgPath) + pyInitDecl, rtInitDecl, mainPkgPath, mainPkgPath, + startDefine, mainDefine, stdioNobuf, + pyInit, rtInit, mainPkgPath, mainPkgPath) + } return exportObject(ctx, pkg.PkgPath+".main", pkg.ExportFile+"-main", []byte(mainCode)) } diff --git a/internal/build/outputs.go b/internal/build/outputs.go index 0fc4ab60..75e733ad 100644 --- a/internal/build/outputs.go +++ b/internal/build/outputs.go @@ -47,46 +47,94 @@ func setOutFmt(conf *Config, formatName string) { } // buildOutFmts creates OutFmtDetails based on package, configuration and multi-package status +// determineBaseNameAndDir extracts the base name and directory from configuration +func determineBaseNameAndDir(pkgName string, conf *Config, multiPkg bool) (baseName, dir string) { + switch conf.Mode { + case ModeInstall: + return pkgName, conf.BinPath + case ModeBuild: + if !multiPkg && conf.OutFile != "" { + dir = filepath.Dir(conf.OutFile) + baseName = strings.TrimSuffix(filepath.Base(conf.OutFile), conf.AppExt) + if dir == "." { + dir = "" + } + return baseName, dir + } + return pkgName, "" + } + // Other modes (run, test, etc.) + return pkgName, "" +} + +// applyPrefix applies build mode specific naming conventions +func applyPrefix(baseName string, buildMode BuildMode, target string, goos string) string { + // Determine the effective OS for naming conventions + effectiveGoos := goos + if target != "" { + // Embedded targets follow Linux conventions + effectiveGoos = "linux" + } + + switch buildMode { + case BuildModeCArchive: + // Static libraries: libname.a (add lib prefix if missing) + if !strings.HasPrefix(baseName, "lib") { + return "lib" + baseName + } + return baseName + + case BuildModeCShared: + // Shared libraries: libname.so/libname.dylib (add lib prefix if missing, except on Windows) + if effectiveGoos != "windows" && !strings.HasPrefix(baseName, "lib") { + return "lib" + baseName + } + return baseName + + case BuildModeExe: + // Executables: name or name.exe (no lib prefix) + if strings.HasPrefix(baseName, "lib") { + return strings.TrimPrefix(baseName, "lib") + } + return baseName + } + + return baseName +} + +// buildOutputPath creates the final output path from baseName, dir and other parameters +func buildOutputPath(baseName, dir string, conf *Config, multiPkg bool, appExt string) (string, error) { + baseName = applyPrefix(baseName, conf.BuildMode, conf.Target, conf.Goos) + + if dir != "" { + return filepath.Join(dir, baseName+appExt), nil + } else if (conf.Mode == ModeBuild && multiPkg) || (conf.Mode != ModeBuild && conf.Mode != ModeInstall) { + return genTempOutputFile(baseName, appExt) + } else { + return baseName + appExt, nil + } +} + func buildOutFmts(pkgName string, conf *Config, multiPkg bool, crossCompile *crosscompile.Export) (*OutFmtDetails, error) { details := &OutFmtDetails{} - var err error + + // Determine base name and directory + baseName, dir := determineBaseNameAndDir(pkgName, conf, multiPkg) + + // Build output path + outputPath, err := buildOutputPath(baseName, dir, conf, multiPkg, conf.AppExt) + if err != nil { + return nil, err + } + details.Out = outputPath + if conf.Target == "" { - // Native target - 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 - } - } + // Native target - we're done return details, nil } needRun := slices.Contains([]Mode{ModeRun, ModeTest, ModeCmpTest, ModeInstall}, conf.Mode) - if multiPkg { - details.Out, err = genTempOutputFile(pkgName, conf.AppExt) - if err != nil { - return nil, err - } - } else if conf.OutFile != "" { - base := strings.TrimSuffix(conf.OutFile, conf.AppExt) - details.Out = base + conf.AppExt - } else if conf.Mode == ModeBuild { - details.Out = pkgName + conf.AppExt - } else { - details.Out, err = genTempOutputFile(pkgName, conf.AppExt) - if err != nil { - return nil, err - } - } - // Check emulator format if emulator mode is enabled outFmt := "" if needRun { @@ -163,3 +211,39 @@ func (details *OutFmtDetails) ToEnvMap() map[string]string { return envMap } + +func defaultAppExt(conf *Config) string { + // Handle build mode specific extensions first + switch conf.BuildMode { + case BuildModeCArchive: + return ".a" + case BuildModeCShared: + switch conf.Goos { + case "windows": + return ".dll" + case "darwin": + return ".dylib" + default: + return ".so" + } + case BuildModeExe: + // For executable mode, handle target-specific logic + if conf.Target != "" { + if strings.HasPrefix(conf.Target, "wasi") || strings.HasPrefix(conf.Target, "wasm") { + return ".wasm" + } + return ".elf" + } + + switch conf.Goos { + case "windows": + return ".exe" + case "wasi", "wasip1", "js": + return ".wasm" + } + return "" + } + + // This should not be reached, but kept for safety + return "" +} diff --git a/internal/build/outputs_test.go b/internal/build/outputs_test.go index e76b33d3..f4f8bf7e 100644 --- a/internal/build/outputs_test.go +++ b/internal/build/outputs_test.go @@ -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) + } + }) + } +} diff --git a/internal/header/header.go b/internal/header/header.go new file mode 100644 index 00000000..4c88b47f --- /dev/null +++ b/internal/header/header.go @@ -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 +#include + +#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 +} diff --git a/internal/header/header_test.go b/internal/header/header_test.go new file mode 100644 index 00000000..d6035ab2 --- /dev/null +++ b/internal/header/header_test.go @@ -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 ", + "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) + } +} diff --git a/ssa/expr.go b/ssa/expr.go index b9dafca0..91235b9c 100644 --- a/ssa/expr.go +++ b/ssa/expr.go @@ -1344,7 +1344,7 @@ func (b Builder) PrintEx(ln bool, args ...Expr) (ret Expr) { // ----------------------------------------------------------------------------- func checkExpr(v Expr, t types.Type, b Builder) Expr { - if st, ok := t.Underlying().(*types.Struct); ok && isClosure(st) { + if st, ok := t.Underlying().(*types.Struct); ok && IsClosure(st) { if v.kind != vkClosure { return b.Pkg.closureStub(b, t, v) } diff --git a/ssa/package.go b/ssa/package.go index f60c3545..e90426d5 100644 --- a/ssa/package.go +++ b/ssa/package.go @@ -435,6 +435,7 @@ func (p Program) NewPackage(name, pkgPath string) Package { pyobjs: pyobjs, pymods: pymods, strs: strs, chkabi: chkabi, Prog: p, di: nil, cu: nil, glbDbgVars: glbDbgVars, + export: make(map[string]string), } ret.abi.Init(pkgPath) return ret @@ -693,6 +694,8 @@ type aPackage struct { NeedRuntime bool NeedPyInit bool + + export map[string]string // pkgPath.nameInPkg => exportname } type Package = *aPackage @@ -701,6 +704,14 @@ func (p Package) Module() llvm.Module { return p.mod } +func (p Package) SetExport(name, export string) { + p.export[name] = export +} + +func (p Package) ExportFuncs() map[string]string { + return p.export +} + func (p Package) rtFunc(fnName string) Expr { p.NeedRuntime = true fn := p.Prog.runtime().Scope().Lookup(fnName).(*types.Func) diff --git a/ssa/type.go b/ssa/type.go index 374df514..329b4e11 100644 --- a/ssa/type.go +++ b/ssa/type.go @@ -411,7 +411,7 @@ func (p Program) toLLVMNamedStruct(name string, raw *types.Struct) llvm.Type { func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind) { fields := p.toLLVMFields(raw) ret = p.ctx.StructType(fields, false) - if isClosure(raw) { + if IsClosure(raw) { kind = vkClosure } else { kind = vkStruct @@ -419,7 +419,7 @@ func (p Program) toLLVMStruct(raw *types.Struct) (ret llvm.Type, kind valueKind) return } -func isClosure(raw *types.Struct) bool { +func IsClosure(raw *types.Struct) bool { n := raw.NumFields() if n == 2 { f1, f2 := raw.Field(0), raw.Field(1) @@ -508,7 +508,7 @@ func (p Program) toNamed(raw *types.Named) Type { case *types.Struct: name := p.llvmNameOf(raw) kind := vkStruct - if isClosure(t) { + if IsClosure(t) { kind = vkClosure } return &aType{p.toLLVMNamedStruct(name, t), rawType{raw}, kind} diff --git a/ssa/type_cvt.go b/ssa/type_cvt.go index 0b8e8fb7..5ad18978 100644 --- a/ssa/type_cvt.go +++ b/ssa/type_cvt.go @@ -92,7 +92,7 @@ func (p goTypes) cvtType(typ types.Type) (raw types.Type, cvt bool) { return types.NewMap(key, elem), true } case *types.Struct: - if isClosure(t) { + if IsClosure(t) { return typ, false } return p.cvtStruct(t)