mirror of
https://github.com/golang/go
synced 2024-10-04 11:21:21 -06:00
5a9ad8b9e3
Specifically: * lib/godoc: - provide file set (FSet) argument to formatters where needed * src/cmd: - cgo, ebnflint, godoc, gofmt, goinstall: provide file set (fset) where needed - godoc: remove local binary search with sort.Search (change by rsc), extract file set for formatters * src/pkg: - exp/eval: remove embedded token.Position fields from nodes and replace with named token.Pos fields; add corresponding Pos() accessor methods - go/token: added file.Line(), changed signature of File.Position() * test/fixedbugs/: - bug206.go: change test to not rely on token.Pos details * added various extra comments * Runs all.bash * gofmt formats all of src, misc w/o changes * godoc runs * performance: - The new version of godoc consumes about the same space after indexing has completed, but indexing is half the speed. Significant space savings are expected from smaller ASTs, but since they are thrown away after a file has been indexed, this is not visible anymore. The slower indexing time is due to the much more expensive computation of line information. However, with the new compressed position information, indexing can be rewritten and simplified. Furthermore, computing the line info can be done more efficiently. New godoc, immediately after indexing completed (best of three runs): PID COMMAND %CPU TIME #TH #PRTS #MREGS RPRVT RSHRD RSIZE VSIZE 44381 godoc 0.0% 0:38.00 4 19 149 145M 184K 148M 176M 2010/12/03 17:58:35 index updated (39.231s, 18505 unique words, 386387 spots) 2010/12/03 17:58:35 bytes=90858456 footprint=199182584 2010/12/03 17:58:36 bytes=47858568 footprint=167295224 Old godoc, immediately after indexing completed (best of three runs): PID COMMAND %CPU TIME #TH #PRTS #MREGS RPRVT RSHRD RSIZE VSIZE 23167 godoc 0.0% 0:22.02 4 17 132 129M 184K 132M 173M 2010/12/03 14:51:32 index updated (24.892s, 18765 unique words, 393830 spots) 2010/12/03 14:51:32 bytes=66404528 footprint=163907832 2010/12/03 14:51:32 bytes=46282224 footprint=163907832 The different numbers for unique words/spots stem from the fact the the two workspaces are not exactly identical. The new godoc maintains a large file set data structure during indexing which (probably) is the reason for the larger heap (90858456 vs 66404528) before garbage collection. R=rsc, r CC=golang-dev https://golang.org/cl/3050041
1058 lines
27 KiB
Go
1058 lines
27 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// Annotate Ref in Prog with C types by parsing gcc debug output.
|
|
// Conversion of debug output to Go types.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"debug/dwarf"
|
|
"debug/elf"
|
|
"debug/macho"
|
|
"debug/pe"
|
|
"flag"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/parser"
|
|
"go/token"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var debugDefine = flag.Bool("debug-define", false, "print relevant #defines")
|
|
var debugGcc = flag.Bool("debug-gcc", false, "print gcc invocations")
|
|
|
|
var nameToC = map[string]string{
|
|
"schar": "signed char",
|
|
"uchar": "unsigned char",
|
|
"ushort": "unsigned short",
|
|
"uint": "unsigned int",
|
|
"ulong": "unsigned long",
|
|
"longlong": "long long",
|
|
"ulonglong": "unsigned long long",
|
|
}
|
|
|
|
// cname returns the C name to use for C.s.
|
|
// The expansions are listed in nameToC and also
|
|
// struct_foo becomes "struct foo", and similarly for
|
|
// union and enum.
|
|
func cname(s string) string {
|
|
if t, ok := nameToC[s]; ok {
|
|
return t
|
|
}
|
|
|
|
if strings.HasPrefix(s, "struct_") {
|
|
return "struct " + s[len("struct_"):]
|
|
}
|
|
if strings.HasPrefix(s, "union_") {
|
|
return "union " + s[len("union_"):]
|
|
}
|
|
if strings.HasPrefix(s, "enum_") {
|
|
return "enum " + s[len("enum_"):]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// Translate rewrites f.AST, the original Go input, to remove
|
|
// references to the imported package C, replacing them with
|
|
// references to the equivalent Go types, functions, and variables.
|
|
func (p *Package) Translate(f *File) {
|
|
for _, cref := range f.Ref {
|
|
// Convert C.ulong to C.unsigned long, etc.
|
|
cref.Name.C = cname(cref.Name.Go)
|
|
}
|
|
p.loadDefines(f)
|
|
needType := p.guessKinds(f)
|
|
if len(needType) > 0 {
|
|
p.loadDWARF(f, needType)
|
|
}
|
|
p.rewriteRef(f)
|
|
}
|
|
|
|
// loadDefines coerces gcc into spitting out the #defines in use
|
|
// in the file f and saves relevant renamings in f.Name[name].Define.
|
|
func (p *Package) loadDefines(f *File) {
|
|
var b bytes.Buffer
|
|
b.WriteString(builtinProlog)
|
|
b.WriteString(f.Preamble)
|
|
stdout := p.gccDefines(b.Bytes())
|
|
|
|
for _, line := range strings.Split(stdout, "\n", -1) {
|
|
if len(line) < 9 || line[0:7] != "#define" {
|
|
continue
|
|
}
|
|
|
|
line = strings.TrimSpace(line[8:])
|
|
|
|
var key, val string
|
|
spaceIndex := strings.Index(line, " ")
|
|
tabIndex := strings.Index(line, "\t")
|
|
|
|
if spaceIndex == -1 && tabIndex == -1 {
|
|
continue
|
|
} else if tabIndex == -1 || (spaceIndex != -1 && spaceIndex < tabIndex) {
|
|
key = line[0:spaceIndex]
|
|
val = strings.TrimSpace(line[spaceIndex:])
|
|
} else {
|
|
key = line[0:tabIndex]
|
|
val = strings.TrimSpace(line[tabIndex:])
|
|
}
|
|
|
|
if n := f.Name[key]; n != nil {
|
|
if *debugDefine {
|
|
fmt.Fprintf(os.Stderr, "#define %s %s\n", key, val)
|
|
}
|
|
n.Define = val
|
|
}
|
|
}
|
|
}
|
|
|
|
// guessKinds tricks gcc into revealing the kind of each
|
|
// name xxx for the references C.xxx in the Go input.
|
|
// The kind is either a constant, type, or variable.
|
|
func (p *Package) guessKinds(f *File) []*Name {
|
|
// Coerce gcc into telling us whether each name is
|
|
// a type, a value, or undeclared. We compile a function
|
|
// containing the line:
|
|
// name;
|
|
// If name is a type, gcc will print:
|
|
// cgo-test:2: warning: useless type name in empty declaration
|
|
// If name is a value, gcc will print
|
|
// cgo-test:2: warning: statement with no effect
|
|
// If name is undeclared, gcc will print
|
|
// cgo-test:2: error: 'name' undeclared (first use in this function)
|
|
// A line number directive causes the line number to
|
|
// correspond to the index in the names array.
|
|
//
|
|
// The line also has an enum declaration:
|
|
// name; enum { _cgo_enum_1 = name };
|
|
// If name is not a constant, gcc will print:
|
|
// cgo-test:4: error: enumerator value for '_cgo_enum_4' is not an integer constant
|
|
// we assume lines without that error are constants.
|
|
|
|
// Make list of names that need sniffing, type lookup.
|
|
toSniff := make([]*Name, 0, len(f.Name))
|
|
needType := make([]*Name, 0, len(f.Name))
|
|
|
|
for _, n := range f.Name {
|
|
// If we've already found this name as a #define
|
|
// and we can translate it as a constant value, do so.
|
|
if n.Define != "" {
|
|
ok := false
|
|
if _, err := strconv.Atoi(n.Define); err == nil {
|
|
ok = true
|
|
} else if n.Define[0] == '"' || n.Define[0] == '\'' {
|
|
_, err := parser.ParseExpr(fset, "", n.Define)
|
|
if err == nil {
|
|
ok = true
|
|
}
|
|
}
|
|
if ok {
|
|
n.Kind = "const"
|
|
n.Const = n.Define
|
|
continue
|
|
}
|
|
|
|
if isName(n.Define) {
|
|
n.C = n.Define
|
|
}
|
|
}
|
|
|
|
// If this is a struct, union, or enum type name,
|
|
// record the kind but also that we need type information.
|
|
if strings.HasPrefix(n.C, "struct ") || strings.HasPrefix(n.C, "union ") || strings.HasPrefix(n.C, "enum ") {
|
|
n.Kind = "type"
|
|
i := len(needType)
|
|
needType = needType[0 : i+1]
|
|
needType[i] = n
|
|
continue
|
|
}
|
|
|
|
i := len(toSniff)
|
|
toSniff = toSniff[0 : i+1]
|
|
toSniff[i] = n
|
|
}
|
|
|
|
if len(toSniff) == 0 {
|
|
return needType
|
|
}
|
|
|
|
var b bytes.Buffer
|
|
b.WriteString(builtinProlog)
|
|
b.WriteString(f.Preamble)
|
|
b.WriteString("void f(void) {\n")
|
|
b.WriteString("#line 0 \"cgo-test\"\n")
|
|
for i, n := range toSniff {
|
|
fmt.Fprintf(&b, "%s; enum { _cgo_enum_%d = %s }; /* cgo-test:%d */\n", n.C, i, n.C, i)
|
|
}
|
|
b.WriteString("}\n")
|
|
stderr := p.gccErrors(b.Bytes())
|
|
if stderr == "" {
|
|
fatal("gcc produced no output\non input:\n%s", b.Bytes())
|
|
}
|
|
|
|
names := make([]*Name, len(toSniff))
|
|
copy(names, toSniff)
|
|
|
|
isConst := make([]bool, len(toSniff))
|
|
for i := range isConst {
|
|
isConst[i] = true // until proven otherwise
|
|
}
|
|
|
|
for _, line := range strings.Split(stderr, "\n", -1) {
|
|
if len(line) < 9 || line[0:9] != "cgo-test:" {
|
|
if len(line) > 8 && line[0:8] == "<stdin>:" {
|
|
fatal("gcc produced unexpected output:\n%s\non input:\n%s", line, b.Bytes())
|
|
}
|
|
continue
|
|
}
|
|
line = line[9:]
|
|
colon := strings.Index(line, ":")
|
|
if colon < 0 {
|
|
continue
|
|
}
|
|
i, err := strconv.Atoi(line[0:colon])
|
|
if err != nil {
|
|
continue
|
|
}
|
|
what := ""
|
|
switch {
|
|
default:
|
|
continue
|
|
case strings.Contains(line, ": useless type name in empty declaration"):
|
|
what = "type"
|
|
isConst[i] = false
|
|
case strings.Contains(line, ": statement with no effect"):
|
|
what = "not-type" // const or func or var
|
|
case strings.Contains(line, "undeclared"):
|
|
error(token.NoPos, "%s", strings.TrimSpace(line[colon+1:]))
|
|
case strings.Contains(line, "is not an integer constant"):
|
|
isConst[i] = false
|
|
continue
|
|
}
|
|
n := toSniff[i]
|
|
if n == nil {
|
|
continue
|
|
}
|
|
toSniff[i] = nil
|
|
n.Kind = what
|
|
|
|
j := len(needType)
|
|
needType = needType[0 : j+1]
|
|
needType[j] = n
|
|
}
|
|
for i, b := range isConst {
|
|
if b {
|
|
names[i].Kind = "const"
|
|
}
|
|
}
|
|
for _, n := range toSniff {
|
|
if n == nil {
|
|
continue
|
|
}
|
|
if n.Kind != "" {
|
|
continue
|
|
}
|
|
error(token.NoPos, "could not determine kind of name for C.%s", n.Go)
|
|
}
|
|
if nerrors > 0 {
|
|
fatal("unresolved names")
|
|
}
|
|
return needType
|
|
}
|
|
|
|
// loadDWARF parses the DWARF debug information generated
|
|
// by gcc to learn the details of the constants, variables, and types
|
|
// being referred to as C.xxx.
|
|
func (p *Package) loadDWARF(f *File, names []*Name) {
|
|
// Extract the types from the DWARF section of an object
|
|
// from a well-formed C program. Gcc only generates DWARF info
|
|
// for symbols in the object file, so it is not enough to print the
|
|
// preamble and hope the symbols we care about will be there.
|
|
// Instead, emit
|
|
// typeof(names[i]) *__cgo__i;
|
|
// for each entry in names and then dereference the type we
|
|
// learn for __cgo__i.
|
|
var b bytes.Buffer
|
|
b.WriteString(builtinProlog)
|
|
b.WriteString(f.Preamble)
|
|
for i, n := range names {
|
|
fmt.Fprintf(&b, "typeof(%s) *__cgo__%d;\n", n.C, i)
|
|
if n.Kind == "const" {
|
|
fmt.Fprintf(&b, "enum { __cgo_enum__%d = %s };\n", i, n.C)
|
|
}
|
|
}
|
|
d := p.gccDebug(b.Bytes())
|
|
|
|
// Scan DWARF info for top-level TagVariable entries with AttrName __cgo__i.
|
|
types := make([]dwarf.Type, len(names))
|
|
enums := make([]dwarf.Offset, len(names))
|
|
nameToIndex := make(map[*Name]int)
|
|
for i, n := range names {
|
|
nameToIndex[n] = i
|
|
}
|
|
r := d.Reader()
|
|
for {
|
|
e, err := r.Next()
|
|
if err != nil {
|
|
fatal("reading DWARF entry: %s", err)
|
|
}
|
|
if e == nil {
|
|
break
|
|
}
|
|
switch e.Tag {
|
|
case dwarf.TagEnumerationType:
|
|
offset := e.Offset
|
|
for {
|
|
e, err := r.Next()
|
|
if err != nil {
|
|
fatal("reading DWARF entry: %s", err)
|
|
}
|
|
if e.Tag == 0 {
|
|
break
|
|
}
|
|
if e.Tag == dwarf.TagEnumerator {
|
|
entryName := e.Val(dwarf.AttrName).(string)
|
|
if strings.HasPrefix(entryName, "__cgo_enum__") {
|
|
n, _ := strconv.Atoi(entryName[len("__cgo_enum__"):])
|
|
if 0 <= n && n < len(names) {
|
|
enums[n] = offset
|
|
}
|
|
}
|
|
}
|
|
}
|
|
case dwarf.TagVariable:
|
|
name, _ := e.Val(dwarf.AttrName).(string)
|
|
typOff, _ := e.Val(dwarf.AttrType).(dwarf.Offset)
|
|
if name == "" || typOff == 0 {
|
|
fatal("malformed DWARF TagVariable entry")
|
|
}
|
|
if !strings.HasPrefix(name, "__cgo__") {
|
|
break
|
|
}
|
|
typ, err := d.Type(typOff)
|
|
if err != nil {
|
|
fatal("loading DWARF type: %s", err)
|
|
}
|
|
t, ok := typ.(*dwarf.PtrType)
|
|
if !ok || t == nil {
|
|
fatal("internal error: %s has non-pointer type", name)
|
|
}
|
|
i, err := strconv.Atoi(name[7:])
|
|
if err != nil {
|
|
fatal("malformed __cgo__ name: %s", name)
|
|
}
|
|
if enums[i] != 0 {
|
|
t, err := d.Type(enums[i])
|
|
if err != nil {
|
|
fatal("loading DWARF type: %s", err)
|
|
}
|
|
types[i] = t
|
|
} else {
|
|
types[i] = t.Type
|
|
}
|
|
}
|
|
if e.Tag != dwarf.TagCompileUnit {
|
|
r.SkipChildren()
|
|
}
|
|
}
|
|
|
|
// Record types and typedef information.
|
|
var conv typeConv
|
|
conv.Init(p.PtrSize)
|
|
for i, n := range names {
|
|
f, fok := types[i].(*dwarf.FuncType)
|
|
if n.Kind != "type" && fok {
|
|
n.Kind = "func"
|
|
n.FuncType = conv.FuncType(f)
|
|
} else {
|
|
n.Type = conv.Type(types[i])
|
|
if enums[i] != 0 && n.Type.EnumValues != nil {
|
|
n.Kind = "const"
|
|
n.Const = strconv.Itoa64(n.Type.EnumValues[fmt.Sprintf("__cgo_enum__%d", i)])
|
|
}
|
|
}
|
|
}
|
|
f.Typedef = conv.typedef
|
|
}
|
|
|
|
// rewriteRef rewrites all the C.xxx references in f.AST to refer to the
|
|
// Go equivalents, now that we have figured out the meaning of all
|
|
// the xxx.
|
|
func (p *Package) rewriteRef(f *File) {
|
|
// Assign mangled names.
|
|
for _, n := range f.Name {
|
|
if n.Kind == "not-type" {
|
|
n.Kind = "var"
|
|
}
|
|
if n.Mangle == "" {
|
|
n.Mangle = "_C" + n.Kind + "_" + n.Go
|
|
}
|
|
}
|
|
|
|
// Now that we have all the name types filled in,
|
|
// scan through the Refs to identify the ones that
|
|
// are trying to do a ,err call. Also check that
|
|
// functions are only used in calls.
|
|
for _, r := range f.Ref {
|
|
var expr ast.Expr = ast.NewIdent(r.Name.Mangle) // default
|
|
switch r.Context {
|
|
case "call", "call2":
|
|
if r.Name.Kind != "func" {
|
|
if r.Name.Kind == "type" {
|
|
r.Context = "type"
|
|
expr = r.Name.Type.Go
|
|
break
|
|
}
|
|
error(r.Pos(), "call of non-function C.%s", r.Name.Go)
|
|
break
|
|
}
|
|
if r.Context == "call2" {
|
|
if r.Name.FuncType.Result == nil {
|
|
error(r.Pos(), "assignment count mismatch: 2 = 0")
|
|
}
|
|
// Invent new Name for the two-result function.
|
|
n := f.Name["2"+r.Name.Go]
|
|
if n == nil {
|
|
n = new(Name)
|
|
*n = *r.Name
|
|
n.AddError = true
|
|
n.Mangle = "_C2func_" + n.Go
|
|
f.Name["2"+r.Name.Go] = n
|
|
}
|
|
expr = ast.NewIdent(n.Mangle)
|
|
r.Name = n
|
|
break
|
|
}
|
|
case "expr":
|
|
if r.Name.Kind == "func" {
|
|
error(r.Pos(), "must call C.%s", r.Name.Go)
|
|
}
|
|
if r.Name.Kind == "type" {
|
|
// Okay - might be new(T)
|
|
expr = r.Name.Type.Go
|
|
}
|
|
if r.Name.Kind == "var" {
|
|
expr = &ast.StarExpr{X: expr}
|
|
}
|
|
|
|
case "type":
|
|
if r.Name.Kind != "type" {
|
|
error(r.Pos(), "expression C.%s used as type", r.Name.Go)
|
|
} else {
|
|
expr = r.Name.Type.Go
|
|
}
|
|
default:
|
|
if r.Name.Kind == "func" {
|
|
error(r.Pos(), "must call C.%s", r.Name.Go)
|
|
}
|
|
}
|
|
*r.Expr = expr
|
|
}
|
|
}
|
|
|
|
// gccName returns the name of the compiler to run. Use $GCC if set in
|
|
// the environment, otherwise just "gcc".
|
|
|
|
func (p *Package) gccName() (ret string) {
|
|
if ret = os.Getenv("GCC"); ret == "" {
|
|
ret = "gcc"
|
|
}
|
|
return
|
|
}
|
|
|
|
// gccMachine returns the gcc -m flag to use, either "-m32" or "-m64".
|
|
func (p *Package) gccMachine() string {
|
|
if p.PtrSize == 8 {
|
|
return "-m64"
|
|
}
|
|
return "-m32"
|
|
}
|
|
|
|
const gccTmp = "_cgo_.o"
|
|
|
|
// gccCmd returns the gcc command line to use for compiling
|
|
// the input.
|
|
func (p *Package) gccCmd() []string {
|
|
return []string{
|
|
p.gccName(),
|
|
p.gccMachine(),
|
|
"-Wall", // many warnings
|
|
"-Werror", // warnings are errors
|
|
"-o" + gccTmp, // write object to tmp
|
|
"-gdwarf-2", // generate DWARF v2 debugging symbols
|
|
"-fno-eliminate-unused-debug-types", // gets rid of e.g. untyped enum otherwise
|
|
"-c", // do not link
|
|
"-xc", // input language is C
|
|
"-", // read input from standard input
|
|
}
|
|
}
|
|
|
|
// gccDebug runs gcc -gdwarf-2 over the C program stdin and
|
|
// returns the corresponding DWARF data and any messages
|
|
// printed to standard error.
|
|
func (p *Package) gccDebug(stdin []byte) *dwarf.Data {
|
|
runGcc(stdin, append(p.gccCmd(), p.GccOptions...))
|
|
|
|
// Try to parse f as ELF and Mach-O and hope one works.
|
|
var f interface {
|
|
DWARF() (*dwarf.Data, os.Error)
|
|
}
|
|
var err os.Error
|
|
if f, err = elf.Open(gccTmp); err != nil {
|
|
if f, err = macho.Open(gccTmp); err != nil {
|
|
if f, err = pe.Open(gccTmp); err != nil {
|
|
fatal("cannot parse gcc output %s as ELF or Mach-O or PE object", gccTmp)
|
|
}
|
|
}
|
|
}
|
|
|
|
d, err := f.DWARF()
|
|
if err != nil {
|
|
fatal("cannot load DWARF debug information from %s: %s", gccTmp, err)
|
|
}
|
|
return d
|
|
}
|
|
|
|
// gccDefines runs gcc -E -dM -xc - over the C program stdin
|
|
// and returns the corresponding standard output, which is the
|
|
// #defines that gcc encountered while processing the input
|
|
// and its included files.
|
|
func (p *Package) gccDefines(stdin []byte) string {
|
|
base := []string{p.gccName(), p.gccMachine(), "-E", "-dM", "-xc", "-"}
|
|
stdout, _ := runGcc(stdin, append(base, p.GccOptions...))
|
|
return stdout
|
|
}
|
|
|
|
// gccErrors runs gcc over the C program stdin and returns
|
|
// the errors that gcc prints. That is, this function expects
|
|
// gcc to fail.
|
|
func (p *Package) gccErrors(stdin []byte) string {
|
|
// TODO(rsc): require failure
|
|
args := append(p.gccCmd(), p.GccOptions...)
|
|
if *debugGcc {
|
|
fmt.Fprintf(os.Stderr, "$ %s <<EOF\n", strings.Join(args, " "))
|
|
os.Stderr.Write(stdin)
|
|
fmt.Fprint(os.Stderr, "EOF\n")
|
|
}
|
|
stdout, stderr, _ := run(stdin, args)
|
|
if *debugGcc {
|
|
os.Stderr.Write(stdout)
|
|
os.Stderr.Write(stderr)
|
|
}
|
|
return string(stderr)
|
|
}
|
|
|
|
// runGcc runs the gcc command line args with stdin on standard input.
|
|
// If the command exits with a non-zero exit status, runGcc prints
|
|
// details about what was run and exits.
|
|
// Otherwise runGcc returns the data written to standard output and standard error.
|
|
// Note that for some of the uses we expect useful data back
|
|
// on standard error, but for those uses gcc must still exit 0.
|
|
func runGcc(stdin []byte, args []string) (string, string) {
|
|
if *debugGcc {
|
|
fmt.Fprintf(os.Stderr, "$ %s <<EOF\n", strings.Join(args, " "))
|
|
os.Stderr.Write(stdin)
|
|
fmt.Fprint(os.Stderr, "EOF\n")
|
|
}
|
|
stdout, stderr, ok := run(stdin, args)
|
|
if *debugGcc {
|
|
os.Stderr.Write(stdout)
|
|
os.Stderr.Write(stderr)
|
|
}
|
|
if !ok {
|
|
fmt.Fprint(os.Stderr, "Error running gcc:\n")
|
|
fmt.Fprintf(os.Stderr, "$ %s <<EOF\n", strings.Join(args, " "))
|
|
os.Stderr.Write(stdin)
|
|
fmt.Fprint(os.Stderr, "EOF\n")
|
|
os.Stderr.Write(stderr)
|
|
os.Exit(2)
|
|
}
|
|
return string(stdout), string(stderr)
|
|
}
|
|
|
|
// A typeConv is a translator from dwarf types to Go types
|
|
// with equivalent memory layout.
|
|
type typeConv struct {
|
|
// Cache of already-translated or in-progress types.
|
|
m map[dwarf.Type]*Type
|
|
typedef map[string]ast.Expr
|
|
|
|
// Predeclared types.
|
|
bool ast.Expr
|
|
byte ast.Expr // denotes padding
|
|
int8, int16, int32, int64 ast.Expr
|
|
uint8, uint16, uint32, uint64, uintptr ast.Expr
|
|
float32, float64 ast.Expr
|
|
void ast.Expr
|
|
unsafePointer ast.Expr
|
|
string ast.Expr
|
|
|
|
ptrSize int64
|
|
|
|
tagGen int
|
|
}
|
|
|
|
func (c *typeConv) Init(ptrSize int64) {
|
|
c.ptrSize = ptrSize
|
|
c.m = make(map[dwarf.Type]*Type)
|
|
c.typedef = make(map[string]ast.Expr)
|
|
c.bool = c.Ident("bool")
|
|
c.byte = c.Ident("byte")
|
|
c.int8 = c.Ident("int8")
|
|
c.int16 = c.Ident("int16")
|
|
c.int32 = c.Ident("int32")
|
|
c.int64 = c.Ident("int64")
|
|
c.uint8 = c.Ident("uint8")
|
|
c.uint16 = c.Ident("uint16")
|
|
c.uint32 = c.Ident("uint32")
|
|
c.uint64 = c.Ident("uint64")
|
|
c.uintptr = c.Ident("uintptr")
|
|
c.float32 = c.Ident("float32")
|
|
c.float64 = c.Ident("float64")
|
|
c.unsafePointer = c.Ident("unsafe.Pointer")
|
|
c.void = c.Ident("void")
|
|
c.string = c.Ident("string")
|
|
}
|
|
|
|
// base strips away qualifiers and typedefs to get the underlying type
|
|
func base(dt dwarf.Type) dwarf.Type {
|
|
for {
|
|
if d, ok := dt.(*dwarf.QualType); ok {
|
|
dt = d.Type
|
|
continue
|
|
}
|
|
if d, ok := dt.(*dwarf.TypedefType); ok {
|
|
dt = d.Type
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
return dt
|
|
}
|
|
|
|
// Map from dwarf text names to aliases we use in package "C".
|
|
var dwarfToName = map[string]string{
|
|
"long int": "long",
|
|
"long unsigned int": "ulong",
|
|
"unsigned int": "uint",
|
|
"short unsigned int": "ushort",
|
|
"short int": "short",
|
|
"long long int": "longlong",
|
|
"long long unsigned int": "ulonglong",
|
|
"signed char": "schar",
|
|
}
|
|
|
|
// Type returns a *Type with the same memory layout as
|
|
// dtype when used as the type of a variable or a struct field.
|
|
func (c *typeConv) Type(dtype dwarf.Type) *Type {
|
|
if t, ok := c.m[dtype]; ok {
|
|
if t.Go == nil {
|
|
fatal("type conversion loop at %s", dtype)
|
|
}
|
|
return t
|
|
}
|
|
|
|
t := new(Type)
|
|
t.Size = dtype.Size()
|
|
t.Align = -1
|
|
t.C = dtype.Common().Name
|
|
t.EnumValues = nil
|
|
c.m[dtype] = t
|
|
|
|
if t.Size < 0 {
|
|
// Unsized types are [0]byte
|
|
t.Size = 0
|
|
t.Go = c.Opaque(0)
|
|
if t.C == "" {
|
|
t.C = "void"
|
|
}
|
|
return t
|
|
}
|
|
|
|
switch dt := dtype.(type) {
|
|
default:
|
|
fatal("unexpected type: %s", dtype)
|
|
|
|
case *dwarf.AddrType:
|
|
if t.Size != c.ptrSize {
|
|
fatal("unexpected: %d-byte address type - %s", t.Size, dtype)
|
|
}
|
|
t.Go = c.uintptr
|
|
t.Align = t.Size
|
|
|
|
case *dwarf.ArrayType:
|
|
if dt.StrideBitSize > 0 {
|
|
// Cannot represent bit-sized elements in Go.
|
|
t.Go = c.Opaque(t.Size)
|
|
break
|
|
}
|
|
gt := &ast.ArrayType{
|
|
Len: c.intExpr(dt.Count),
|
|
}
|
|
t.Go = gt // publish before recursive call
|
|
sub := c.Type(dt.Type)
|
|
t.Align = sub.Align
|
|
gt.Elt = sub.Go
|
|
t.C = fmt.Sprintf("typeof(%s[%d])", sub.C, dt.Count)
|
|
|
|
case *dwarf.BoolType:
|
|
t.Go = c.bool
|
|
t.Align = c.ptrSize
|
|
|
|
case *dwarf.CharType:
|
|
if t.Size != 1 {
|
|
fatal("unexpected: %d-byte char type - %s", t.Size, dtype)
|
|
}
|
|
t.Go = c.int8
|
|
t.Align = 1
|
|
|
|
case *dwarf.EnumType:
|
|
switch t.Size {
|
|
default:
|
|
fatal("unexpected: %d-byte enum type - %s", t.Size, dtype)
|
|
case 1:
|
|
t.Go = c.uint8
|
|
case 2:
|
|
t.Go = c.uint16
|
|
case 4:
|
|
t.Go = c.uint32
|
|
case 8:
|
|
t.Go = c.uint64
|
|
}
|
|
if t.Align = t.Size; t.Align >= c.ptrSize {
|
|
t.Align = c.ptrSize
|
|
}
|
|
t.C = "enum " + dt.EnumName
|
|
t.EnumValues = make(map[string]int64)
|
|
for _, ev := range dt.Val {
|
|
t.EnumValues[ev.Name] = ev.Val
|
|
}
|
|
|
|
case *dwarf.FloatType:
|
|
switch t.Size {
|
|
default:
|
|
fatal("unexpected: %d-byte float type - %s", t.Size, dtype)
|
|
case 4:
|
|
t.Go = c.float32
|
|
case 8:
|
|
t.Go = c.float64
|
|
}
|
|
if t.Align = t.Size; t.Align >= c.ptrSize {
|
|
t.Align = c.ptrSize
|
|
}
|
|
|
|
case *dwarf.FuncType:
|
|
// No attempt at translation: would enable calls
|
|
// directly between worlds, but we need to moderate those.
|
|
t.Go = c.uintptr
|
|
t.Align = c.ptrSize
|
|
|
|
case *dwarf.IntType:
|
|
if dt.BitSize > 0 {
|
|
fatal("unexpected: %d-bit int type - %s", dt.BitSize, dtype)
|
|
}
|
|
switch t.Size {
|
|
default:
|
|
fatal("unexpected: %d-byte int type - %s", t.Size, dtype)
|
|
case 1:
|
|
t.Go = c.int8
|
|
case 2:
|
|
t.Go = c.int16
|
|
case 4:
|
|
t.Go = c.int32
|
|
case 8:
|
|
t.Go = c.int64
|
|
}
|
|
if t.Align = t.Size; t.Align >= c.ptrSize {
|
|
t.Align = c.ptrSize
|
|
}
|
|
|
|
case *dwarf.PtrType:
|
|
t.Align = c.ptrSize
|
|
|
|
// Translate void* as unsafe.Pointer
|
|
if _, ok := base(dt.Type).(*dwarf.VoidType); ok {
|
|
t.Go = c.unsafePointer
|
|
t.C = "void*"
|
|
break
|
|
}
|
|
|
|
gt := &ast.StarExpr{}
|
|
t.Go = gt // publish before recursive call
|
|
sub := c.Type(dt.Type)
|
|
gt.X = sub.Go
|
|
t.C = sub.C + "*"
|
|
|
|
case *dwarf.QualType:
|
|
// Ignore qualifier.
|
|
t = c.Type(dt.Type)
|
|
c.m[dtype] = t
|
|
return t
|
|
|
|
case *dwarf.StructType:
|
|
// Convert to Go struct, being careful about alignment.
|
|
// Have to give it a name to simulate C "struct foo" references.
|
|
tag := dt.StructName
|
|
if tag == "" {
|
|
tag = "__" + strconv.Itoa(c.tagGen)
|
|
c.tagGen++
|
|
} else if t.C == "" {
|
|
t.C = dt.Kind + " " + tag
|
|
}
|
|
name := c.Ident("_Ctype_" + dt.Kind + "_" + tag)
|
|
t.Go = name // publish before recursive calls
|
|
switch dt.Kind {
|
|
case "union", "class":
|
|
c.typedef[name.Name] = c.Opaque(t.Size)
|
|
if t.C == "" {
|
|
t.C = fmt.Sprintf("typeof(unsigned char[%d])", t.Size)
|
|
}
|
|
case "struct":
|
|
g, csyntax, align := c.Struct(dt)
|
|
if t.C == "" {
|
|
t.C = csyntax
|
|
}
|
|
t.Align = align
|
|
c.typedef[name.Name] = g
|
|
}
|
|
|
|
case *dwarf.TypedefType:
|
|
// Record typedef for printing.
|
|
if dt.Name == "_GoString_" {
|
|
// Special C name for Go string type.
|
|
// Knows string layout used by compilers: pointer plus length,
|
|
// which rounds up to 2 pointers after alignment.
|
|
t.Go = c.string
|
|
t.Size = c.ptrSize * 2
|
|
t.Align = c.ptrSize
|
|
break
|
|
}
|
|
name := c.Ident("_Ctypedef_" + dt.Name)
|
|
t.Go = name // publish before recursive call
|
|
sub := c.Type(dt.Type)
|
|
t.Size = sub.Size
|
|
t.Align = sub.Align
|
|
if _, ok := c.typedef[name.Name]; !ok {
|
|
c.typedef[name.Name] = sub.Go
|
|
}
|
|
|
|
case *dwarf.UcharType:
|
|
if t.Size != 1 {
|
|
fatal("unexpected: %d-byte uchar type - %s", t.Size, dtype)
|
|
}
|
|
t.Go = c.uint8
|
|
t.Align = 1
|
|
|
|
case *dwarf.UintType:
|
|
if dt.BitSize > 0 {
|
|
fatal("unexpected: %d-bit uint type - %s", dt.BitSize, dtype)
|
|
}
|
|
switch t.Size {
|
|
default:
|
|
fatal("unexpected: %d-byte uint type - %s", t.Size, dtype)
|
|
case 1:
|
|
t.Go = c.uint8
|
|
case 2:
|
|
t.Go = c.uint16
|
|
case 4:
|
|
t.Go = c.uint32
|
|
case 8:
|
|
t.Go = c.uint64
|
|
}
|
|
if t.Align = t.Size; t.Align >= c.ptrSize {
|
|
t.Align = c.ptrSize
|
|
}
|
|
|
|
case *dwarf.VoidType:
|
|
t.Go = c.void
|
|
t.C = "void"
|
|
}
|
|
|
|
switch dtype.(type) {
|
|
case *dwarf.AddrType, *dwarf.BoolType, *dwarf.CharType, *dwarf.IntType, *dwarf.FloatType, *dwarf.UcharType, *dwarf.UintType:
|
|
s := dtype.Common().Name
|
|
if s != "" {
|
|
if ss, ok := dwarfToName[s]; ok {
|
|
s = ss
|
|
}
|
|
s = strings.Join(strings.Split(s, " ", -1), "") // strip spaces
|
|
name := c.Ident("_Ctype_" + s)
|
|
c.typedef[name.Name] = t.Go
|
|
t.Go = name
|
|
}
|
|
}
|
|
|
|
if t.C == "" {
|
|
fatal("internal error: did not create C name for %s", dtype)
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
// FuncArg returns a Go type with the same memory layout as
|
|
// dtype when used as the type of a C function argument.
|
|
func (c *typeConv) FuncArg(dtype dwarf.Type) *Type {
|
|
t := c.Type(dtype)
|
|
switch dt := dtype.(type) {
|
|
case *dwarf.ArrayType:
|
|
// Arrays are passed implicitly as pointers in C.
|
|
// In Go, we must be explicit.
|
|
return &Type{
|
|
Size: c.ptrSize,
|
|
Align: c.ptrSize,
|
|
Go: &ast.StarExpr{X: t.Go},
|
|
C: t.C + "*",
|
|
}
|
|
case *dwarf.TypedefType:
|
|
// C has much more relaxed rules than Go for
|
|
// implicit type conversions. When the parameter
|
|
// is type T defined as *X, simulate a little of the
|
|
// laxness of C by making the argument *X instead of T.
|
|
if ptr, ok := base(dt.Type).(*dwarf.PtrType); ok {
|
|
// Unless the typedef happens to point to void* since
|
|
// Go has special rules around using unsafe.Pointer.
|
|
if _, void := base(ptr.Type).(*dwarf.VoidType); !void {
|
|
return c.Type(ptr)
|
|
}
|
|
}
|
|
}
|
|
return t
|
|
}
|
|
|
|
// FuncType returns the Go type analogous to dtype.
|
|
// There is no guarantee about matching memory layout.
|
|
func (c *typeConv) FuncType(dtype *dwarf.FuncType) *FuncType {
|
|
p := make([]*Type, len(dtype.ParamType))
|
|
gp := make([]*ast.Field, len(dtype.ParamType))
|
|
for i, f := range dtype.ParamType {
|
|
// gcc's DWARF generator outputs a single DotDotDotType parameter for
|
|
// function pointers that specify no parameters (e.g. void
|
|
// (*__cgo_0)()). Treat this special case as void. This case is
|
|
// invalid according to ISO C anyway (i.e. void (*__cgo_1)(...) is not
|
|
// legal).
|
|
if _, ok := f.(*dwarf.DotDotDotType); ok && i == 0 {
|
|
p, gp = nil, nil
|
|
break
|
|
}
|
|
p[i] = c.FuncArg(f)
|
|
gp[i] = &ast.Field{Type: p[i].Go}
|
|
}
|
|
var r *Type
|
|
var gr []*ast.Field
|
|
if _, ok := dtype.ReturnType.(*dwarf.VoidType); !ok && dtype.ReturnType != nil {
|
|
r = c.Type(dtype.ReturnType)
|
|
gr = []*ast.Field{&ast.Field{Type: r.Go}}
|
|
}
|
|
return &FuncType{
|
|
Params: p,
|
|
Result: r,
|
|
Go: &ast.FuncType{
|
|
Params: &ast.FieldList{List: gp},
|
|
Results: &ast.FieldList{List: gr},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Identifier
|
|
func (c *typeConv) Ident(s string) *ast.Ident {
|
|
return ast.NewIdent(s)
|
|
}
|
|
|
|
// Opaque type of n bytes.
|
|
func (c *typeConv) Opaque(n int64) ast.Expr {
|
|
return &ast.ArrayType{
|
|
Len: c.intExpr(n),
|
|
Elt: c.byte,
|
|
}
|
|
}
|
|
|
|
// Expr for integer n.
|
|
func (c *typeConv) intExpr(n int64) ast.Expr {
|
|
return &ast.BasicLit{
|
|
Kind: token.INT,
|
|
Value: []byte(strconv.Itoa64(n)),
|
|
}
|
|
}
|
|
|
|
// Add padding of given size to fld.
|
|
func (c *typeConv) pad(fld []*ast.Field, size int64) []*ast.Field {
|
|
n := len(fld)
|
|
fld = fld[0 : n+1]
|
|
fld[n] = &ast.Field{Names: []*ast.Ident{c.Ident("_")}, Type: c.Opaque(size)}
|
|
return fld
|
|
}
|
|
|
|
// Struct conversion: return Go and (6g) C syntax for type.
|
|
func (c *typeConv) Struct(dt *dwarf.StructType) (expr *ast.StructType, csyntax string, align int64) {
|
|
var buf bytes.Buffer
|
|
buf.WriteString("struct {")
|
|
fld := make([]*ast.Field, 0, 2*len(dt.Field)+1) // enough for padding around every field
|
|
off := int64(0)
|
|
|
|
// Rename struct fields that happen to be named Go keywords into
|
|
// _{keyword}. Create a map from C ident -> Go ident. The Go ident will
|
|
// be mangled. Any existing identifier that already has the same name on
|
|
// the C-side will cause the Go-mangled version to be prefixed with _.
|
|
// (e.g. in a struct with fields '_type' and 'type', the latter would be
|
|
// rendered as '__type' in Go).
|
|
ident := make(map[string]string)
|
|
used := make(map[string]bool)
|
|
for _, f := range dt.Field {
|
|
ident[f.Name] = f.Name
|
|
used[f.Name] = true
|
|
}
|
|
for cid, goid := range ident {
|
|
if token.Lookup([]byte(goid)).IsKeyword() {
|
|
// Avoid keyword
|
|
goid = "_" + goid
|
|
|
|
// Also avoid existing fields
|
|
for _, exist := used[goid]; exist; _, exist = used[goid] {
|
|
goid = "_" + goid
|
|
}
|
|
|
|
used[goid] = true
|
|
ident[cid] = goid
|
|
}
|
|
}
|
|
|
|
for _, f := range dt.Field {
|
|
if f.BitSize > 0 && f.BitSize != f.ByteSize*8 {
|
|
continue
|
|
}
|
|
if f.ByteOffset > off {
|
|
fld = c.pad(fld, f.ByteOffset-off)
|
|
off = f.ByteOffset
|
|
}
|
|
t := c.Type(f.Type)
|
|
n := len(fld)
|
|
fld = fld[0 : n+1]
|
|
|
|
fld[n] = &ast.Field{Names: []*ast.Ident{c.Ident(ident[f.Name])}, Type: t.Go}
|
|
off += t.Size
|
|
buf.WriteString(t.C)
|
|
buf.WriteString(" ")
|
|
buf.WriteString(f.Name)
|
|
buf.WriteString("; ")
|
|
if t.Align > align {
|
|
align = t.Align
|
|
}
|
|
}
|
|
if off < dt.ByteSize {
|
|
fld = c.pad(fld, dt.ByteSize-off)
|
|
off = dt.ByteSize
|
|
}
|
|
if off != dt.ByteSize {
|
|
fatal("struct size calculation error")
|
|
}
|
|
buf.WriteString("}")
|
|
csyntax = buf.String()
|
|
expr = &ast.StructType{Fields: &ast.FieldList{List: fld}}
|
|
return
|
|
}
|