Skip to content

Commit 621f05c

Browse files
authored
Merge pull request #566 from luoliwoshang/llcppg/symg/header
[wip] llcppg/symg
2 parents daf97ea + 9afe26f commit 621f05c

File tree

5 files changed

+509
-5
lines changed

5 files changed

+509
-5
lines changed

c/cjson/cjson.go

+12
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,18 @@ func (o *JSON) PrintUnformatted() *c.Char { return nil }
134134
// llgo:link (*JSON).PrintBuffered C.cJSON_PrintBuffered
135135
func (o *JSON) PrintBuffered(prebuffer c.Int, fmt c.Int) *c.Char { return nil }
136136

137+
// llgo:link (*JSON).GetObjectItemCaseSensitive C.cJSON_GetObjectItemCaseSensitive
138+
func (o *JSON) GetObjectItemCaseSensitive(key *c.Char) *JSON { return nil }
139+
140+
// llgo:link (*JSON).GetArraySize C.cJSON_GetArraySize
141+
func (o *JSON) GetArraySize() c.Int { return 0 }
142+
143+
// llgo:link (*JSON).GetArrayItem C.cJSON_GetArrayItem
144+
func (o *JSON) GetArrayItem(index c.Int) *JSON { return nil }
145+
146+
// llgo:link (*JSON).GetStringValue C.cJSON_GetStringValue
147+
func (o *JSON) GetStringValue() *c.Char { return nil }
148+
137149
//go:linkname Free C.cJSON_free
138150
func Free(ptr unsafe.Pointer)
139151

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package config
2+
3+
import (
4+
"errors"
5+
"unsafe"
6+
7+
"github.com/goplus/llgo/c"
8+
"github.com/goplus/llgo/c/cjson"
9+
"github.com/goplus/llgo/chore/llcppg/types"
10+
)
11+
12+
type Conf struct {
13+
*cjson.JSON
14+
*types.Config
15+
}
16+
17+
func GetConf(data []byte) (Conf, error) {
18+
parsedConf := cjson.ParseBytes(data)
19+
if parsedConf == nil {
20+
return Conf{}, errors.New("failed to parse config")
21+
}
22+
23+
config := &types.Config{
24+
Name: GetStringItem(parsedConf, "name", ""),
25+
CFlags: GetStringItem(parsedConf, "cflags", ""),
26+
Libs: GetStringItem(parsedConf, "libs", ""),
27+
Include: GetStringArrayItem(parsedConf, "include"),
28+
TrimPrefixes: GetStringArrayItem(parsedConf, "trimPrefixes"),
29+
}
30+
31+
return Conf{
32+
JSON: parsedConf,
33+
Config: config,
34+
}, nil
35+
}
36+
37+
func GetString(obj *cjson.JSON) (value string) {
38+
str := obj.GetStringValue()
39+
return unsafe.String((*byte)(unsafe.Pointer(str)), c.Strlen(str))
40+
}
41+
42+
func GetStringItem(obj *cjson.JSON, key string, defval string) (value string) {
43+
item := obj.GetObjectItemCaseSensitive(c.AllocaCStr(key))
44+
if item == nil {
45+
return defval
46+
}
47+
return GetString(item)
48+
}
49+
50+
func GetStringArrayItem(obj *cjson.JSON, key string) (value []string) {
51+
item := obj.GetObjectItemCaseSensitive(c.AllocaCStr(key))
52+
if item == nil {
53+
return
54+
}
55+
value = make([]string, item.GetArraySize())
56+
for i := range value {
57+
value[i] = GetString(item.GetArrayItem(c.Int(i)))
58+
}
59+
return
60+
}

chore/_xtool/llcppsymg/llcppsymg.go

+253-5
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,22 @@
1717
package main
1818

1919
import (
20+
"errors"
2021
"fmt"
2122
"io"
2223
"os"
24+
"path/filepath"
25+
"strconv"
26+
"strings"
27+
"unsafe"
2328

2429
"github.com/goplus/llgo/c"
2530
"github.com/goplus/llgo/c/cjson"
31+
"github.com/goplus/llgo/chore/_xtool/llcppsymg/config"
32+
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
33+
"github.com/goplus/llgo/chore/llcppg/types"
34+
"github.com/goplus/llgo/cpp/llvm"
35+
"github.com/goplus/llgo/xtool/nm"
2636
)
2737

2838
func main() {
@@ -40,18 +50,256 @@ func main() {
4050
}
4151
check(err)
4252

43-
conf := cjson.ParseBytes(data)
44-
if conf == nil {
53+
conf, err := config.GetConf(data)
54+
check(err)
55+
defer conf.Delete()
56+
57+
if err != nil {
4558
fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile)
46-
os.Exit(1)
4759
}
48-
defer conf.Delete()
60+
symbols, err := parseDylibSymbols(conf.Libs)
4961

50-
c.Printf(c.Str("%s"), conf.Print())
62+
check(err)
63+
64+
filepaths := generateHeaderFilePath(conf.CFlags, conf.Include)
65+
astInfos, err := parse.ParseHeaderFile(filepaths)
66+
check(err)
67+
68+
symbolInfo := getCommonSymbols(symbols, astInfos, conf.TrimPrefixes)
69+
70+
err = genSymbolTableFile(symbolInfo)
71+
check(err)
5172
}
5273

5374
func check(err error) {
5475
if err != nil {
5576
panic(err)
5677
}
5778
}
79+
80+
func parseDylibSymbols(lib string) ([]types.CPPSymbol, error) {
81+
dylibPath, err := generateDylibPath(lib)
82+
if err != nil {
83+
return nil, errors.New("failed to generate dylib path")
84+
}
85+
86+
files, err := nm.New("").List(dylibPath)
87+
if err != nil {
88+
return nil, errors.New("failed to list symbols in dylib")
89+
}
90+
91+
var symbols []types.CPPSymbol
92+
93+
for _, file := range files {
94+
for _, sym := range file.Symbols {
95+
demangleName := decodeSymbolName(sym.Name)
96+
symbols = append(symbols, types.CPPSymbol{
97+
Symbol: sym,
98+
DemangleName: demangleName,
99+
})
100+
}
101+
}
102+
103+
return symbols, nil
104+
}
105+
106+
func generateDylibPath(lib string) (string, error) {
107+
output := lib
108+
libPath := ""
109+
libName := ""
110+
for _, part := range strings.Fields(string(output)) {
111+
if strings.HasPrefix(part, "-L") {
112+
libPath = part[2:]
113+
} else if strings.HasPrefix(part, "-l") {
114+
libName = part[2:]
115+
}
116+
}
117+
118+
if libPath == "" || libName == "" {
119+
return "", fmt.Errorf("failed to parse pkg-config output: %s", output)
120+
}
121+
122+
dylibPath := filepath.Join(libPath, "lib"+libName+".dylib")
123+
return dylibPath, nil
124+
}
125+
126+
func decodeSymbolName(symbolName string) string {
127+
if symbolName == "" {
128+
return ""
129+
}
130+
131+
demangled := llvm.ItaniumDemangle(symbolName, true)
132+
if demangled == nil {
133+
return symbolName
134+
}
135+
defer c.Free(unsafe.Pointer(demangled))
136+
137+
demangleName := c.GoString(demangled)
138+
if demangleName == "" {
139+
return symbolName
140+
}
141+
142+
decodedName := strings.TrimSpace(demangleName)
143+
decodedName = strings.ReplaceAll(decodedName,
144+
"std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const",
145+
"std::string")
146+
147+
return decodedName
148+
}
149+
150+
func generateHeaderFilePath(cflags string, files []string) []string {
151+
prefixPath := cflags
152+
prefixPath = strings.TrimPrefix(prefixPath, "-I")
153+
var includePaths []string
154+
for _, file := range files {
155+
includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file))
156+
}
157+
return includePaths
158+
}
159+
160+
func getCommonSymbols(dylibSymbols []types.CPPSymbol, astInfoList []types.ASTInformation, prefix []string) []types.SymbolInfo {
161+
var commonSymbols []types.SymbolInfo
162+
functionNameMap := make(map[string]int)
163+
164+
for _, astInfo := range astInfoList {
165+
for _, dylibSym := range dylibSymbols {
166+
if strings.TrimPrefix(dylibSym.Name, "_") == astInfo.Symbol {
167+
cppName := generateCPPName(astInfo)
168+
functionNameMap[cppName]++
169+
symbolInfo := types.SymbolInfo{
170+
Mangle: strings.TrimPrefix(dylibSym.Name, "_"),
171+
CPP: cppName,
172+
Go: generateMangle(astInfo, functionNameMap[cppName], prefix),
173+
}
174+
commonSymbols = append(commonSymbols, symbolInfo)
175+
break
176+
}
177+
}
178+
}
179+
180+
return commonSymbols
181+
}
182+
183+
func generateCPPName(astInfo types.ASTInformation) string {
184+
cppName := astInfo.Name
185+
if astInfo.Class != "" {
186+
cppName = astInfo.Class + "::" + astInfo.Name
187+
}
188+
return cppName
189+
}
190+
191+
func generateMangle(astInfo types.ASTInformation, count int, prefixes []string) string {
192+
astInfo.Class = removePrefix(astInfo.Class, prefixes)
193+
astInfo.Name = removePrefix(astInfo.Name, prefixes)
194+
res := ""
195+
if astInfo.Class != "" {
196+
if astInfo.Class == astInfo.Name {
197+
res = "(*" + astInfo.Class + ")." + "Init"
198+
if count > 1 {
199+
res += "__" + strconv.Itoa(count-1)
200+
}
201+
} else if astInfo.Name == "~"+astInfo.Class {
202+
res = "(*" + astInfo.Class + ")." + "Dispose"
203+
if count > 1 {
204+
res += "__" + strconv.Itoa(count-1)
205+
}
206+
} else {
207+
res = "(*" + astInfo.Class + ")." + astInfo.Name
208+
if count > 1 {
209+
res += "__" + strconv.Itoa(count-1)
210+
}
211+
}
212+
} else {
213+
res = astInfo.Name
214+
if count > 1 {
215+
res += "__" + strconv.Itoa(count-1)
216+
}
217+
}
218+
return res
219+
}
220+
221+
func removePrefix(str string, prefixes []string) string {
222+
for _, prefix := range prefixes {
223+
if strings.HasPrefix(str, prefix) {
224+
return strings.TrimPrefix(str, prefix)
225+
}
226+
}
227+
return str
228+
}
229+
230+
func genSymbolTableFile(symbolInfos []types.SymbolInfo) error {
231+
// keep open follow code block can run successfully
232+
for i := range symbolInfos {
233+
println("symbol", symbolInfos[i].Go)
234+
}
235+
236+
fileName := "llcppg.symb.json"
237+
existingSymbols, err := readExistingSymbolTable(fileName)
238+
if err != nil {
239+
return err
240+
}
241+
242+
for i := range symbolInfos {
243+
if existingSymbol, exists := existingSymbols[symbolInfos[i].Mangle]; exists {
244+
symbolInfos[i].Go = existingSymbol.Go
245+
}
246+
}
247+
248+
root := cjson.Array()
249+
defer root.Delete()
250+
251+
for _, symbol := range symbolInfos {
252+
item := cjson.Object()
253+
item.SetItem(c.Str("mangle"), cjson.String(c.AllocaCStr(symbol.Mangle)))
254+
item.SetItem(c.Str("c++"), cjson.String(c.AllocaCStr(symbol.CPP)))
255+
item.SetItem(c.Str("go"), cjson.String(c.AllocaCStr(symbol.Go)))
256+
root.AddItem(item)
257+
}
258+
259+
cStr := root.Print()
260+
if cStr == nil {
261+
return errors.New("symbol table is empty")
262+
}
263+
defer c.Free(unsafe.Pointer(cStr))
264+
265+
data := unsafe.Slice((*byte)(unsafe.Pointer(cStr)), c.Strlen(cStr))
266+
267+
if err := os.WriteFile(fileName, data, 0644); err != nil {
268+
return errors.New("failed to write symbol table file")
269+
}
270+
return nil
271+
}
272+
func readExistingSymbolTable(fileName string) (map[string]types.SymbolInfo, error) {
273+
existingSymbols := make(map[string]types.SymbolInfo)
274+
275+
if _, err := os.Stat(fileName); err != nil {
276+
return existingSymbols, nil
277+
}
278+
279+
data, err := os.ReadFile(fileName)
280+
if err != nil {
281+
return nil, errors.New("failed to read symbol table file")
282+
}
283+
284+
parsedJSON := cjson.ParseBytes(data)
285+
if parsedJSON == nil {
286+
return nil, errors.New("failed to parse JSON")
287+
}
288+
289+
arraySize := parsedJSON.GetArraySize()
290+
291+
for i := 0; i < int(arraySize); i++ {
292+
item := parsedJSON.GetArrayItem(c.Int(i))
293+
if item == nil {
294+
continue
295+
}
296+
symbol := types.SymbolInfo{
297+
Mangle: config.GetStringItem(item, "mangle", ""),
298+
CPP: config.GetStringItem(item, "c++", ""),
299+
Go: config.GetStringItem(item, "go", ""),
300+
}
301+
existingSymbols[symbol.Mangle] = symbol
302+
}
303+
304+
return existingSymbols, nil
305+
}

0 commit comments

Comments
 (0)