Skip to content

Commit ff94ed7

Browse files
committed
docs(design):simplify desc for anony func type autogen named func type
2 parents 2467df6 + 4580427 commit ff94ed7

5 files changed

Lines changed: 220 additions & 123 deletions

File tree

.github/workflows/end2end.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ jobs:
5252
- name: Test End2End
5353
working-directory: _cmptest
5454
run: |
55-
go test -v .
55+
# increase the test timeout to avoid conan installation timeout occasionally
56+
go test -v -timeout 30m .
5657
5758
- name: Upload Logs to Artifacts
5859
uses: actions/upload-artifact@v4

_cmptest/llcppgend_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ const llcppgGoVersion = "1.20.14"
2020
var (
2121
// avoid conan install race condition
2222
conanInstallMutex sync.Mutex
23-
// avoid llgo run race condition
24-
llgoRunMutex sync.Mutex
2523
)
2624

2725
type testCase struct {
@@ -234,9 +232,10 @@ func runDemos(t *testing.T, logFile *os.File, demosPath string, pkgname, pkgpath
234232
t.Fatal(err)
235233
}
236234

237-
// only can lock out of loop,will got ld64.lld: error: undefined symbol: math.Float32bits
238-
llgoRunMutex.Lock()
239-
defer llgoRunMutex.Unlock()
235+
llgoRunTempDir, err := os.MkdirTemp("", "llgo-run")
236+
if err != nil {
237+
t.Fatal(err)
238+
}
240239

241240
for _, demo := range demos {
242241
if !demo.IsDir() {
@@ -246,6 +245,8 @@ func runDemos(t *testing.T, logFile *os.File, demosPath string, pkgname, pkgpath
246245
demoCmd := command(logFile, demoPath, "llgo", "run", ".")
247246
demoCmd.Env = append(demoCmd.Env, llgoEnv()...)
248247
demoCmd.Env = append(demoCmd.Env, pcPathEnv(pcPath)...)
248+
demoCmd.Env = append(demoCmd.Env, tempDirEnv(llgoRunTempDir)...)
249+
249250
err = demoCmd.Run()
250251
if err != nil {
251252
t.Fatal(err)
@@ -280,6 +281,16 @@ func goVerEnv() string {
280281
return fmt.Sprintf("GOTOOLCHAIN=go%s", llcppgGoVersion)
281282
}
282283

284+
func tempDirEnv(tempDir string) []string {
285+
return []string{
286+
fmt.Sprintf("TMPDIR=%s", tempDir),
287+
fmt.Sprintf("TEMP=%s", tempDir),
288+
fmt.Sprintf("TMP=%s", tempDir),
289+
fmt.Sprintf("GOTMPDIR=%s", tempDir),
290+
fmt.Sprintf("GOCACHE=%s", tempDir),
291+
}
292+
}
293+
283294
func copyFile(src, dst string) error {
284295
srcFile, err := os.Open(src)
285296
if err != nil {

cmd/llcppgtest/demo/demo.go

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import (
1313
llcppg "github.com/goplus/llcppg/config"
1414
)
1515

16-
var llgoRunMu sync.Mutex
17-
1816
var mkdirTempLazily = sync.OnceValue(func() string {
1917
if env := os.Getenv("LLCPPG_TEST_LOG_DIR"); env != "" {
2018
return env
@@ -137,11 +135,6 @@ func RunGenPkgDemo(demoRoot string, confDir string) error {
137135
return fmt.Errorf("%s: go fmt failed in %s: %w", demoPkgName, genPkgDir, err)
138136
}
139137

140-
if err = runCommand(tempLog, genPkgDir, "llgo", "build", "."); err != nil {
141-
return fmt.Errorf("%s: llgo build failed in %s: %w", demoPkgName, genPkgDir, err)
142-
}
143-
fmt.Printf("%s: llgo build success\n", demoPkgName)
144-
145138
demosPath := filepath.Join(demoRoot, "demo")
146139
// init mods to test package,because the demo is dependent on the gen pkg
147140
if err = runCommand(tempLog, demoRoot, "go", "mod", "init", "demo"); err != nil {
@@ -164,15 +157,22 @@ func RunGenPkgDemo(demoRoot string, confDir string) error {
164157
return fmt.Errorf("%s: failed to read demo directory: %v", demoPkgName, err)
165158
}
166159

167-
// start to test demos via llgo run
168-
// to avoid potential racy, we must grab the lock
169-
llgoRunMu.Lock()
170-
defer llgoRunMu.Unlock()
160+
llgoRunTempDir, err := os.MkdirTemp("", "llgo-run")
161+
if err != nil {
162+
return err
163+
}
171164

172165
for _, demo := range demos {
173166
if demo.IsDir() {
174167
fmt.Printf("%s: Running demo: %s\n", demoPkgName, demo.Name())
175-
if demoErr := runCommand(tempLog, filepath.Join(demosPath, demo.Name()), "llgo", "run", "."); demoErr != nil {
168+
169+
// avoid racy
170+
if demoErr := runCommandWithTempDir(
171+
tempLog,
172+
filepath.Join(demosPath, demo.Name()),
173+
llgoRunTempDir,
174+
"llgo", "run", "-v", ".",
175+
); demoErr != nil {
176176
return fmt.Errorf("%s: failed to run demo: %s: %w", demoPkgName, demo.Name(), demoErr)
177177
}
178178
}
@@ -255,3 +255,17 @@ func runCommand(logFile *os.File, dir, command string, args ...string) error {
255255
cmd.Stderr = logFile
256256
return cmd.Run()
257257
}
258+
259+
func runCommandWithTempDir(logFile *os.File, dir, tempDir string, command string, args ...string) error {
260+
cmd := exec.Command(command, args...)
261+
cmd.Dir = dir
262+
cmd.Stdout = logFile
263+
cmd.Stderr = logFile
264+
cmd.Env = append(os.Environ(), fmt.Sprintf("TMPDIR=%s", tempDir))
265+
cmd.Env = append(cmd.Env, fmt.Sprintf("TEMP=%s", tempDir))
266+
cmd.Env = append(cmd.Env, fmt.Sprintf("TMP=%s", tempDir))
267+
cmd.Env = append(cmd.Env, fmt.Sprintf("GOTMPDIR=%s", tempDir))
268+
cmd.Env = append(cmd.Env, fmt.Sprintf("GOCACHE=%s", tempDir))
269+
270+
return cmd.Run()
271+
}

doc/en/dev/llcppg.md

Lines changed: 176 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ typedef int (*CallBack)(void *L);
5555
type CallBack func(c.Pointer) c.Int
5656
```
5757

58-
For function pointer types referenced in function signatures, the type is replaced with the converted Go function type.
58+
For function pointer types referenced in function signatures & struct fields, the type is replaced with the converted Go function type.
5959

6060
```c
6161
void exec(void *L, CallBack cb);
@@ -65,6 +65,17 @@ void exec(void *L, CallBack cb);
6565
func Exec(L c.Pointer, cb CallBack)
6666
```
6767

68+
```c
69+
typedef struct Stream {
70+
CallBack cb;
71+
} Stream;
72+
```
73+
```go
74+
type Stream struct {
75+
Cb CallBack
76+
}
77+
```
78+
6879
For cases where a parameter in a function signature is an anonymous function pointer (meaning it does not reference a pre-defined function pointer type), it is mapped to the corresponding Go function type.
6980

7081
```c
@@ -83,21 +94,7 @@ func (recv_ *Sqlite3) Exec(sql *c.Char, callback func(c.Pointer, c.Int, **c.Char
8394
}
8495
```
8596

86-
For struct fields that are named function pointer types, the field type is replaced with a `c.Pointer` for description.
87-
88-
```c
89-
typedef struct Stream {
90-
CallBack cb;
91-
} Stream;
92-
```
93-
```go
94-
type Stream struct {
95-
Cb c.Pointer
96-
}
97-
```
98-
Due to the characteristics of LLGo, an anonymous function type cannot be directly declared as a Field Type. So to preserve as much information as possible, we need to use a `c.Pointer` to describe the field type.
99-
100-
For anonymous function pointer types, llgo will build a public function type for them and reference it,and ensure that the anonymous type is unique, the naming rule for the corresponding type will be:
97+
LLGo cannot use anonymous function types directly as struct field types. To preserve type information, llcppg automatically generates named function types and references them in the struct fields and following this naming convention:
10198
```
10299
LLGO_<namespaces>_<typename>_<nested_field_typename>_<fieldname>
103100
```
@@ -206,7 +203,6 @@ char matrix[3][4]; // In function parameter becomes **c.Char
206203
char field[3][4]; // In struct field becomes [3][4]c.Char
207204
```
208205

209-
210206
#### Name Mapping Rules
211207

212208
The llcppg system converts C/C++ type names to Go-compatible identifiers following specific transformation rules. These rules ensure generated Go code follows Go naming conventions while maintaining clarity and avoiding conflicts.
@@ -506,4 +502,166 @@ linux amd64 `t1_linux_amd64.go` `t2_linux_amd64.go`
506502
```go
507503
// +build linux,amd64
508504
package xxx
509-
```
505+
```
506+
507+
## Usage
508+
509+
```sh
510+
llcppg [config-file]
511+
```
512+
513+
If `config-file` is not specified, a `llcppg.cfg` file is used in current directory. The configuration file format is as follows:
514+
515+
```json
516+
{
517+
"name": "inih",
518+
"cflags": "$(pkg-config --cflags inireader)",
519+
"include": [
520+
"INIReader.h",
521+
"AnotherHeaderFile.h"
522+
],
523+
"libs": "$(pkg-config --libs inireader)",
524+
"trimPrefixes": ["Ini", "INI"],
525+
"cplusplus":true,
526+
"deps":["c","github.com/..../third"],
527+
"mix":false
528+
}
529+
```
530+
531+
## Process Steps
532+
533+
The llcppg tool orchestrates a three-stage pipeline that automatically generates Go bindings for C/C++ libraries by coordinating symbol table generation, signature extraction, and Go code generation components.
534+
535+
1. llcppsymg: Generate symbol table for a C/C++ library
536+
2. llcppsigfetch: Fetch information of C/C++ symbols
537+
3. gogensig: Generate a Go package by information of symbols
538+
539+
### llcppsymg
540+
541+
```sh
542+
llcppsymg config-file
543+
llcppsymg - # read config from stdin
544+
```
545+
546+
llcppsymg is the symbol table generator in the llcppg toolchain, responsible for analyzing C/C++ dynamic libraries and header files to generate symbol mapping tables. Its main functions are:
547+
548+
1. Parse dynamic library symbols: Extract exported symbols from libraries using the nm tool
549+
2. Parse header file declarations: Analyze C/C++ header files using libclang for function declarations
550+
3. Find intersection: Match library symbols with header declarations and then generate symbol table named `llcppg.symb.json`.
551+
552+
#### Symbol Table
553+
554+
This symbol table determines whether the function appears in the generated Go code、its actual name and if it is a method. Its file format is as follows:
555+
556+
```json
557+
[
558+
{
559+
"mangle": "cJSON_Delete",
560+
"c++": "cJSON_Delete(cJSON *)",
561+
"go": "(*CJSON).Delete"
562+
},
563+
]
564+
```
565+
566+
* mangle: mangled name of function
567+
* c++: C/C++ function prototype declaration string
568+
* go: corresponding Go function or method name, during the process, llcppg will automatically check if the current function can be a method
569+
1. When go is "-", the function is ignored (not generated)
570+
2. When go is a valid function name, the function name will be named as the mangle
571+
3. When go is `(*Type).MethodName` or `Type.MethodName`, the function will be generated as a method with Receiver as Type/*Type, and Name as MethodName
572+
573+
#### Custom Symbol Table generation
574+
575+
Specify function mapping behavior in `llcppg.cfg` by config the `symMap` field:
576+
```json
577+
{
578+
"symMap":{
579+
"mangle":"<goFuncName> | <.goMethodName> | -"
580+
}
581+
}
582+
```
583+
`mangle` is the symbol name of the function. For the value of `mangle`, you can customize it as:
584+
1. `goFuncName` - generates a regular function named `goFuncName`
585+
2. `.goMethodName` - generates a method named `goMethodName` (if it doesn't meet the rules for generating a method, it will be generated as a regular function)
586+
3. `-` - completely ignore this function
587+
588+
For example, to convert `(*CJSON).PrintUnformatted` from a method to a function, you can use follow config:
589+
590+
```json
591+
{
592+
"symMap":{
593+
"cJSON_PrintUnformatted":"PrintUnformatted"
594+
}
595+
}
596+
```
597+
and the `llcppg.symb.json` will be:
598+
```json
599+
[
600+
{
601+
"mangle": "cJSON_PrintUnformatted",
602+
"c++": "cJSON_PrintUnformatted(cJSON *)",
603+
"go": "PrintUnformatted"
604+
}
605+
]
606+
```
607+
608+
### llcppsigfetch
609+
610+
llcppsigfetch is a tool that extracts type information and function signatures from C/C++ header files. It uses Clang & Libclang to parse C/C++ header files and outputs a JSON-formatted package information structure.
611+
612+
```sh
613+
llcppsigfetch config-file
614+
llcppsigfetch - # read config from stdin
615+
```
616+
617+
* Preprocesses C/C++ header files
618+
* Creates translation units using libclang and traverses the preprocessed header file to extract ast info.
619+
620+
#### Output:
621+
622+
The output is a `pkg-info` structure that contains comprehensive package information needed for Go code generation. This `pkg-info` consists of two main components:
623+
624+
* File: Contains the AST with decls, includes, and macros.
625+
* FileMap: Maps file paths to file types, where FileType indicates file classification (interface, implementation, or third-party files)
626+
627+
```json
628+
{
629+
"File": {
630+
"decls": [],
631+
"includes": [],
632+
"macros": []
633+
},
634+
"FileMap": {
635+
"usr/include/sys/_types/_rsize_t.h": {
636+
"FileType": 3
637+
},
638+
"/opt/homebrew/include/lua/lua.h": {
639+
"FileType": 1
640+
},
641+
"/opt/homebrew/include/lua/luaconf.h": {
642+
"FileType": 2
643+
}
644+
}
645+
}
646+
```
647+
648+
### gogensig
649+
650+
gogensig is the final component in the pipeline, responsible for converting C/C++ type declarations and function signatures into Go code. It reads the `pkg-info` structure generated by llcppsigfetch.
651+
652+
```sh
653+
gogensig pkg-info-file
654+
gogensig - # read pkg-info-file from stdin
655+
```
656+
657+
#### Function Generation
658+
During execution, gogensig only generates functions whose corresponding mangle exists in llcppg.symb.json, determining whether to generate functions/methods with specified Go names by parsing the go field corresponding to the mangle.
659+
660+
1. Regular function format: "FunctionName"
661+
* Generates regular functions, using `//go:linkname` annotation
662+
2. Pointer receiver method format: "(*TypeName).MethodName"
663+
* Generates methods with pointer receivers, using `// llgo:link` annotation
664+
3. Value receiver method format: "TypeName.MethodName"
665+
* Generates methods with value receivers, using `// llgo:link` annotation
666+
4. Ignore function format: "-"
667+
* Completely ignores the function, generates no code

0 commit comments

Comments
 (0)