Files
llgo/chore/_xtool/llcppsymg/llcppsymg.go

263 lines
6.8 KiB
Go
Raw Normal View History

2024-07-25 18:46:40 +08:00
/*
* 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 main
import (
2024-07-26 10:31:20 +08:00
"bufio"
"bytes"
"encoding/json"
"errors"
2024-07-25 18:46:40 +08:00
"fmt"
"io"
"os"
2024-07-26 10:31:20 +08:00
"os/exec"
"path/filepath"
"strconv"
"strings"
2024-07-26 14:37:26 +08:00
2024-07-29 15:09:54 +08:00
"github.com/goplus/llgo/c"
"github.com/goplus/llgo/c/cjson"
"github.com/goplus/llgo/chore/_xtool/llcppsymg/parse"
2024-07-26 14:37:26 +08:00
"github.com/goplus/llgo/chore/llcppg/types"
2024-07-29 17:21:32 +08:00
"github.com/goplus/llgo/cpp/llvm"
2024-07-25 18:46:40 +08:00
)
func main() {
cfgFile := "llcppg.cfg"
2024-07-26 16:13:20 +08:00
if len(os.Args) > 1 {
cfgFile = os.Args[1]
}
2024-07-25 18:46:40 +08:00
var data []byte
var err error
if cfgFile == "-" {
data, err = io.ReadAll(os.Stdin)
} else {
data, err = os.ReadFile(cfgFile)
}
check(err)
2024-07-29 15:09:54 +08:00
config, err := getConf(data)
if err != nil {
fmt.Fprintln(os.Stderr, "Failed to parse config file:", cfgFile)
}
2024-07-26 10:31:20 +08:00
symbols, err := parseDylibSymbols(config.Libs)
2024-07-29 17:21:32 +08:00
2024-07-26 10:31:20 +08:00
check(err)
filepaths := generateHeaderFilePath(config.CFlags, config.Include)
astInfos, err := parse.ParseHeaderFile(filepaths)
2024-07-26 10:31:20 +08:00
check(err)
symbolInfo := getCommonSymbols(symbols, astInfos, config.TrimPrefixes)
2024-07-26 10:31:20 +08:00
jsonData, err := json.MarshalIndent(symbolInfo, "", " ")
check(err)
fileName := "llcppg.symb.json"
2024-07-26 16:13:20 +08:00
err = os.WriteFile(fileName, jsonData, 0644)
2024-07-26 10:31:20 +08:00
check(err)
2024-07-25 18:46:40 +08:00
2024-07-29 10:52:10 +08:00
absJSONPath, err := filepath.Abs(fileName)
check(err)
config.JSONPath = absJSONPath
updatedCfgData, err := json.MarshalIndent(config, "", " ")
check(err)
err = os.WriteFile(cfgFile, updatedCfgData, 0644)
check(err)
2024-07-25 18:46:40 +08:00
}
func check(err error) {
if err != nil {
panic(err)
}
}
2024-07-26 10:31:20 +08:00
2024-07-29 15:09:54 +08:00
func getConf(data []byte) (config types.Config, err error) {
conf := cjson.ParseBytes(data)
defer conf.Delete()
if conf == nil {
return config, errors.New("failed to parse config")
2024-07-29 15:09:54 +08:00
}
config.Name = c.GoString(conf.GetItem("name").GetStringValue())
config.CFlags = c.GoString(conf.GetItem("cflags").GetStringValue())
config.Libs = c.GoString(conf.GetItem("libs").GetStringValue())
config.Include = make([]string, conf.GetItem("include").GetArraySize())
for i := range config.Include {
config.Include[i] = c.GoString(conf.GetItem("include").GetArrayItem(c.Int(i)).GetStringValue())
}
config.TrimPrefixes = make([]string, conf.GetItem("trimPrefixes").GetArraySize())
for i := range config.TrimPrefixes {
config.TrimPrefixes[i] = c.GoString(conf.GetItem("trimPrefixes").GetArrayItem(c.Int(i)).GetStringValue())
}
return
}
2024-07-29 11:21:36 +08:00
func parseDylibSymbols(lib string) ([]types.CPPSymbol, error) {
2024-07-26 10:31:20 +08:00
dylibPath, _ := generateDylibPath(lib)
nmCmd := exec.Command("nm", "-gU", dylibPath)
2024-07-29 17:21:32 +08:00
nmOutput, err := nmCmd.Output() // maybe lock
2024-07-26 10:31:20 +08:00
if err != nil {
return nil, errors.New("failed to execute nm command")
}
symbols := parseNmOutput(nmOutput)
for i, sym := range symbols {
decodedName, err := decodeSymbolName(sym.Name)
if err != nil {
return nil, err
}
symbols[i].Name = decodedName
}
return symbols, nil
}
func generateDylibPath(lib string) (string, error) {
2024-07-29 15:09:54 +08:00
output := lib
2024-07-26 10:31:20 +08:00
libPath := ""
libName := ""
for _, part := range strings.Fields(string(output)) {
if strings.HasPrefix(part, "-L") {
2024-07-26 16:13:20 +08:00
libPath = part[2:]
2024-07-26 10:31:20 +08:00
} else if strings.HasPrefix(part, "-l") {
2024-07-26 16:13:20 +08:00
libName = part[2:]
2024-07-26 10:31:20 +08:00
}
}
if libPath == "" || libName == "" {
return "", fmt.Errorf("failed to parse pkg-config output: %s", output)
}
dylibPath := filepath.Join(libPath, "lib"+libName+".dylib")
return dylibPath, nil
}
2024-07-29 11:21:36 +08:00
func parseNmOutput(output []byte) []types.CPPSymbol {
2024-07-26 10:31:20 +08:00
scanner := bufio.NewScanner(bytes.NewReader(output))
2024-07-29 11:21:36 +08:00
var symbols []types.CPPSymbol
2024-07-26 10:31:20 +08:00
for scanner.Scan() {
line := scanner.Text()
fields := strings.Fields(line)
if len(fields) < 3 {
continue
}
symbolName := fields[2]
// Check if the symbol name starts with an underscore and remove it if present
2024-07-29 11:21:36 +08:00
symbolName = strings.TrimPrefix(symbolName, "_")
symbols = append(symbols, types.CPPSymbol{
2024-07-26 10:31:20 +08:00
Symbol: symbolName,
Type: fields[1],
Name: fields[2],
})
}
return symbols
}
func decodeSymbolName(symbolName string) (string, error) {
2024-07-29 17:21:32 +08:00
llvm.ItaniumDemangle(symbolName, true)
demangleName := c.GoString(llvm.ItaniumDemangle(symbolName, true))
decodedName := strings.TrimSpace(string(demangleName))
2024-07-26 10:31:20 +08:00
decodedName = strings.ReplaceAll(decodedName, "std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const", "std::string")
return decodedName, nil
}
func generateHeaderFilePath(cflags string, files []string) []string {
2024-07-29 15:09:54 +08:00
prefixPath := cflags
2024-07-29 11:21:36 +08:00
prefixPath = strings.TrimPrefix(prefixPath, "-I")
2024-07-26 10:31:20 +08:00
var includePaths []string
for _, file := range files {
includePaths = append(includePaths, filepath.Join(prefixPath, "/"+file))
}
return includePaths
}
2024-07-29 11:21:36 +08:00
func getCommonSymbols(dylibSymbols []types.CPPSymbol, astInfoList []types.ASTInformation, prefix []string) []types.SymbolInfo {
var commonSymbols []types.SymbolInfo
2024-07-26 10:31:20 +08:00
functionNameMap := make(map[string]int)
for _, astInfo := range astInfoList {
for _, dylibSym := range dylibSymbols {
if dylibSym.Symbol == astInfo.Symbol {
cppName := generateCPPName(astInfo)
functionNameMap[cppName]++
2024-07-29 11:21:36 +08:00
symbolInfo := types.SymbolInfo{
2024-07-26 10:31:20 +08:00
Mangle: dylibSym.Symbol,
CPP: cppName,
2024-07-26 15:00:06 +08:00
Go: generateMangle(astInfo, functionNameMap[cppName], prefix),
2024-07-26 10:31:20 +08:00
}
commonSymbols = append(commonSymbols, symbolInfo)
break
}
}
}
return commonSymbols
}
2024-07-29 11:21:36 +08:00
func generateCPPName(astInfo types.ASTInformation) string {
2024-07-26 10:31:20 +08:00
cppName := astInfo.Name
if astInfo.Class != "" {
cppName = astInfo.Class + "::" + astInfo.Name
}
return cppName
}
2024-07-29 11:21:36 +08:00
func generateMangle(astInfo types.ASTInformation, count int, prefixes []string) string {
2024-07-26 15:00:06 +08:00
astInfo.Class = removePrefix(astInfo.Class, prefixes)
astInfo.Name = removePrefix(astInfo.Name, prefixes)
2024-07-26 10:31:20 +08:00
res := ""
if astInfo.Class != "" {
if astInfo.Class == astInfo.Name {
res = "(*" + astInfo.Class + ")." + "Init"
if count > 1 {
res += "__" + strconv.Itoa(count-1)
}
} else if astInfo.Name == "~"+astInfo.Class {
res = "(*" + astInfo.Class + ")." + "Dispose"
if count > 1 {
res += "__" + strconv.Itoa(count-1)
}
} else {
2024-07-26 14:37:26 +08:00
res = "(*" + astInfo.Class + ")." + astInfo.Name
if count > 1 {
res += "__" + strconv.Itoa(count-1)
}
2024-07-26 10:31:20 +08:00
}
} else {
res = astInfo.Name
2024-07-26 14:37:26 +08:00
if count > 1 {
2024-07-26 10:31:20 +08:00
res += "__" + strconv.Itoa(count-1)
}
}
return res
}
2024-07-26 15:00:06 +08:00
func removePrefix(str string, prefixes []string) string {
for _, prefix := range prefixes {
if strings.HasPrefix(str, prefix) {
return strings.TrimPrefix(str, prefix)
}
}
return str
}