Skip to content

Commit 5edc2b9

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

5 files changed

Lines changed: 222 additions & 124 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: 178 additions & 19 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 field types. To preserve type information, llcppg automatically generates named function types and references them in the field and following this naming convention:
10198
```
10299
LLGO_<namespaces>_<typename>_<nested_field_typename>_<fieldname>
103100
```
@@ -141,7 +138,8 @@ type Hooks struct {
141138
FreeFn LLGO_A_Hooks_FreeFn
142139
}
143140
```
144-
with nested struct's function pointer
141+
in nested struct
142+
145143
```c
146144
struct Foo {
147145
struct {
@@ -206,7 +204,6 @@ char matrix[3][4]; // In function parameter becomes **c.Char
206204
char field[3][4]; // In struct field becomes [3][4]c.Char
207205
```
208206

209-
210207
#### Name Mapping Rules
211208

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

0 commit comments

Comments
 (0)