summaryrefslogtreecommitdiffstats
path: root/src/cmd/cgo/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/cgo/main.go')
-rw-r--r--src/cmd/cgo/main.go501
1 files changed, 501 insertions, 0 deletions
diff --git a/src/cmd/cgo/main.go b/src/cmd/cgo/main.go
new file mode 100644
index 0000000..78020ae
--- /dev/null
+++ b/src/cmd/cgo/main.go
@@ -0,0 +1,501 @@
+// 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.
+
+// Cgo; see doc.go for an overview.
+
+// TODO(rsc):
+// Emit correct line number annotations.
+// Make gc understand the annotations.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/ast"
+ "go/printer"
+ "go/token"
+ "internal/buildcfg"
+ "io"
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "sort"
+ "strings"
+
+ "cmd/internal/edit"
+ "cmd/internal/notsha256"
+ "cmd/internal/objabi"
+)
+
+// A Package collects information about the package we're going to write.
+type Package struct {
+ PackageName string // name of package
+ PackagePath string
+ PtrSize int64
+ IntSize int64
+ GccOptions []string
+ GccIsClang bool
+ CgoFlags map[string][]string // #cgo flags (CFLAGS, LDFLAGS)
+ Written map[string]bool
+ Name map[string]*Name // accumulated Name from Files
+ ExpFunc []*ExpFunc // accumulated ExpFunc from Files
+ Decl []ast.Decl
+ GoFiles []string // list of Go files
+ GccFiles []string // list of gcc output files
+ Preamble string // collected preamble for _cgo_export.h
+ typedefs map[string]bool // type names that appear in the types of the objects we're interested in
+ typedefList []typedefInfo
+}
+
+// A typedefInfo is an element on Package.typedefList: a typedef name
+// and the position where it was required.
+type typedefInfo struct {
+ typedef string
+ pos token.Pos
+}
+
+// A File collects information about a single Go input file.
+type File struct {
+ AST *ast.File // parsed AST
+ Comments []*ast.CommentGroup // comments from file
+ Package string // Package name
+ Preamble string // C preamble (doc comment on import "C")
+ Ref []*Ref // all references to C.xxx in AST
+ Calls []*Call // all calls to C.xxx in AST
+ ExpFunc []*ExpFunc // exported functions for this file
+ Name map[string]*Name // map from Go name to Name
+ NamePos map[*Name]token.Pos // map from Name to position of the first reference
+ Edit *edit.Buffer
+}
+
+func (f *File) offset(p token.Pos) int {
+ return fset.Position(p).Offset
+}
+
+func nameKeys(m map[string]*Name) []string {
+ var ks []string
+ for k := range m {
+ ks = append(ks, k)
+ }
+ sort.Strings(ks)
+ return ks
+}
+
+// A Call refers to a call of a C.xxx function in the AST.
+type Call struct {
+ Call *ast.CallExpr
+ Deferred bool
+ Done bool
+}
+
+// A Ref refers to an expression of the form C.xxx in the AST.
+type Ref struct {
+ Name *Name
+ Expr *ast.Expr
+ Context astContext
+ Done bool
+}
+
+func (r *Ref) Pos() token.Pos {
+ return (*r.Expr).Pos()
+}
+
+var nameKinds = []string{"iconst", "fconst", "sconst", "type", "var", "fpvar", "func", "macro", "not-type"}
+
+// A Name collects information about C.xxx.
+type Name struct {
+ Go string // name used in Go referring to package C
+ Mangle string // name used in generated Go
+ C string // name used in C
+ Define string // #define expansion
+ Kind string // one of the nameKinds
+ Type *Type // the type of xxx
+ FuncType *FuncType
+ AddError bool
+ Const string // constant definition
+}
+
+// IsVar reports whether Kind is either "var" or "fpvar"
+func (n *Name) IsVar() bool {
+ return n.Kind == "var" || n.Kind == "fpvar"
+}
+
+// IsConst reports whether Kind is either "iconst", "fconst" or "sconst"
+func (n *Name) IsConst() bool {
+ return strings.HasSuffix(n.Kind, "const")
+}
+
+// An ExpFunc is an exported function, callable from C.
+// Such functions are identified in the Go input file
+// by doc comments containing the line //export ExpName
+type ExpFunc struct {
+ Func *ast.FuncDecl
+ ExpName string // name to use from C
+ Doc string
+}
+
+// A TypeRepr contains the string representation of a type.
+type TypeRepr struct {
+ Repr string
+ FormatArgs []interface{}
+}
+
+// A Type collects information about a type in both the C and Go worlds.
+type Type struct {
+ Size int64
+ Align int64
+ C *TypeRepr
+ Go ast.Expr
+ EnumValues map[string]int64
+ Typedef string
+ BadPointer bool // this pointer type should be represented as a uintptr (deprecated)
+}
+
+// A FuncType collects information about a function type in both the C and Go worlds.
+type FuncType struct {
+ Params []*Type
+ Result *Type
+ Go *ast.FuncType
+}
+
+func usage() {
+ fmt.Fprint(os.Stderr, "usage: cgo -- [compiler options] file.go ...\n")
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+var ptrSizeMap = map[string]int64{
+ "386": 4,
+ "alpha": 8,
+ "amd64": 8,
+ "arm": 4,
+ "arm64": 8,
+ "loong64": 8,
+ "m68k": 4,
+ "mips": 4,
+ "mipsle": 4,
+ "mips64": 8,
+ "mips64le": 8,
+ "nios2": 4,
+ "ppc": 4,
+ "ppc64": 8,
+ "ppc64le": 8,
+ "riscv": 4,
+ "riscv64": 8,
+ "s390": 4,
+ "s390x": 8,
+ "sh": 4,
+ "shbe": 4,
+ "sparc": 4,
+ "sparc64": 8,
+}
+
+var intSizeMap = map[string]int64{
+ "386": 4,
+ "alpha": 8,
+ "amd64": 8,
+ "arm": 4,
+ "arm64": 8,
+ "loong64": 8,
+ "m68k": 4,
+ "mips": 4,
+ "mipsle": 4,
+ "mips64": 8,
+ "mips64le": 8,
+ "nios2": 4,
+ "ppc": 4,
+ "ppc64": 8,
+ "ppc64le": 8,
+ "riscv": 4,
+ "riscv64": 8,
+ "s390": 4,
+ "s390x": 8,
+ "sh": 4,
+ "shbe": 4,
+ "sparc": 4,
+ "sparc64": 8,
+}
+
+var cPrefix string
+
+var fset = token.NewFileSet()
+
+var dynobj = flag.String("dynimport", "", "if non-empty, print dynamic import data for that file")
+var dynout = flag.String("dynout", "", "write -dynimport output to this file")
+var dynpackage = flag.String("dynpackage", "main", "set Go package for -dynimport output")
+var dynlinker = flag.Bool("dynlinker", false, "record dynamic linker information in -dynimport mode")
+
+// This flag is for bootstrapping a new Go implementation,
+// to generate Go types that match the data layout and
+// constant values used in the host's C libraries and system calls.
+var godefs = flag.Bool("godefs", false, "for bootstrap: write Go definitions for C file to standard output")
+
+var srcDir = flag.String("srcdir", "", "source directory")
+var objDir = flag.String("objdir", "", "object directory")
+var importPath = flag.String("importpath", "", "import path of package being built (for comments in generated files)")
+var exportHeader = flag.String("exportheader", "", "where to write export header if any exported functions")
+
+var gccgo = flag.Bool("gccgo", false, "generate files for use with gccgo")
+var gccgoprefix = flag.String("gccgoprefix", "", "-fgo-prefix option used with gccgo")
+var gccgopkgpath = flag.String("gccgopkgpath", "", "-fgo-pkgpath option used with gccgo")
+var gccgoMangler func(string) string
+var gccgoDefineCgoIncomplete = flag.Bool("gccgo_define_cgoincomplete", false, "define cgo.Incomplete for older gccgo/GoLLVM")
+var importRuntimeCgo = flag.Bool("import_runtime_cgo", true, "import runtime/cgo in generated code")
+var importSyscall = flag.Bool("import_syscall", true, "import syscall in generated code")
+var trimpath = flag.String("trimpath", "", "applies supplied rewrites or trims prefixes to recorded source file paths")
+
+var goarch, goos, gomips, gomips64 string
+var gccBaseCmd []string
+
+func main() {
+ objabi.AddVersionFlag() // -V
+ objabi.Flagparse(usage)
+
+ if *gccgoDefineCgoIncomplete {
+ if !*gccgo {
+ fmt.Fprintf(os.Stderr, "cgo: -gccgo_define_cgoincomplete without -gccgo\n")
+ os.Exit(2)
+ }
+ incomplete = "_cgopackage_Incomplete"
+ }
+
+ if *dynobj != "" {
+ // cgo -dynimport is essentially a separate helper command
+ // built into the cgo binary. It scans a gcc-produced executable
+ // and dumps information about the imported symbols and the
+ // imported libraries. The 'go build' rules for cgo prepare an
+ // appropriate executable and then use its import information
+ // instead of needing to make the linkers duplicate all the
+ // specialized knowledge gcc has about where to look for imported
+ // symbols and which ones to use.
+ dynimport(*dynobj)
+ return
+ }
+
+ if *godefs {
+ // Generating definitions pulled from header files,
+ // to be checked into Go repositories.
+ // Line numbers are just noise.
+ conf.Mode &^= printer.SourcePos
+ }
+
+ args := flag.Args()
+ if len(args) < 1 {
+ usage()
+ }
+
+ // Find first arg that looks like a go file and assume everything before
+ // that are options to pass to gcc.
+ var i int
+ for i = len(args); i > 0; i-- {
+ if !strings.HasSuffix(args[i-1], ".go") {
+ break
+ }
+ }
+ if i == len(args) {
+ usage()
+ }
+
+ // Save original command line arguments for the godefs generated comment. Relative file
+ // paths in os.Args will be rewritten to absolute file paths in the loop below.
+ osArgs := make([]string, len(os.Args))
+ copy(osArgs, os.Args[:])
+ goFiles := args[i:]
+
+ for _, arg := range args[:i] {
+ if arg == "-fsanitize=thread" {
+ tsanProlog = yesTsanProlog
+ }
+ if arg == "-fsanitize=memory" {
+ msanProlog = yesMsanProlog
+ }
+ }
+
+ p := newPackage(args[:i])
+
+ // We need a C compiler to be available. Check this.
+ var err error
+ gccBaseCmd, err = checkGCCBaseCmd()
+ if err != nil {
+ fatalf("%v", err)
+ os.Exit(2)
+ }
+
+ // Record CGO_LDFLAGS from the environment for external linking.
+ if ldflags := os.Getenv("CGO_LDFLAGS"); ldflags != "" {
+ args, err := splitQuoted(ldflags)
+ if err != nil {
+ fatalf("bad CGO_LDFLAGS: %q (%s)", ldflags, err)
+ }
+ p.addToFlag("LDFLAGS", args)
+ }
+
+ // Need a unique prefix for the global C symbols that
+ // we use to coordinate between gcc and ourselves.
+ // We already put _cgo_ at the beginning, so the main
+ // concern is other cgo wrappers for the same functions.
+ // Use the beginning of the notsha256 of the input to disambiguate.
+ h := notsha256.New()
+ io.WriteString(h, *importPath)
+ fs := make([]*File, len(goFiles))
+ for i, input := range goFiles {
+ if *srcDir != "" {
+ input = filepath.Join(*srcDir, input)
+ }
+
+ // Create absolute path for file, so that it will be used in error
+ // messages and recorded in debug line number information.
+ // This matches the rest of the toolchain. See golang.org/issue/5122.
+ if aname, err := filepath.Abs(input); err == nil {
+ input = aname
+ }
+
+ b, err := os.ReadFile(input)
+ if err != nil {
+ fatalf("%s", err)
+ }
+ if _, err = h.Write(b); err != nil {
+ fatalf("%s", err)
+ }
+
+ // Apply trimpath to the file path. The path won't be read from after this point.
+ input, _ = objabi.ApplyRewrites(input, *trimpath)
+ if strings.ContainsAny(input, "\r\n") {
+ // ParseGo, (*Package).writeOutput, and printer.Fprint in SourcePos mode
+ // all emit line directives, which don't permit newlines in the file path.
+ // Bail early if we see anything newline-like in the trimmed path.
+ fatalf("input path contains newline character: %q", input)
+ }
+ goFiles[i] = input
+
+ f := new(File)
+ f.Edit = edit.NewBuffer(b)
+ f.ParseGo(input, b)
+ f.DiscardCgoDirectives()
+ fs[i] = f
+ }
+
+ cPrefix = fmt.Sprintf("_%x", h.Sum(nil)[0:6])
+
+ if *objDir == "" {
+ // make sure that _obj directory exists, so that we can write
+ // all the output files there.
+ os.Mkdir("_obj", 0777)
+ *objDir = "_obj"
+ }
+ *objDir += string(filepath.Separator)
+
+ for i, input := range goFiles {
+ f := fs[i]
+ p.Translate(f)
+ for _, cref := range f.Ref {
+ switch cref.Context {
+ case ctxCall, ctxCall2:
+ if cref.Name.Kind != "type" {
+ break
+ }
+ old := *cref.Expr
+ *cref.Expr = cref.Name.Type.Go
+ f.Edit.Replace(f.offset(old.Pos()), f.offset(old.End()), gofmt(cref.Name.Type.Go))
+ }
+ }
+ if nerrors > 0 {
+ os.Exit(2)
+ }
+ p.PackagePath = f.Package
+ p.Record(f)
+ if *godefs {
+ os.Stdout.WriteString(p.godefs(f, osArgs))
+ } else {
+ p.writeOutput(f, input)
+ }
+ }
+
+ if !*godefs {
+ p.writeDefs()
+ }
+ if nerrors > 0 {
+ os.Exit(2)
+ }
+}
+
+// newPackage returns a new Package that will invoke
+// gcc with the additional arguments specified in args.
+func newPackage(args []string) *Package {
+ goarch = runtime.GOARCH
+ if s := os.Getenv("GOARCH"); s != "" {
+ goarch = s
+ }
+ goos = runtime.GOOS
+ if s := os.Getenv("GOOS"); s != "" {
+ goos = s
+ }
+ buildcfg.Check()
+ gomips = buildcfg.GOMIPS
+ gomips64 = buildcfg.GOMIPS64
+ ptrSize := ptrSizeMap[goarch]
+ if ptrSize == 0 {
+ fatalf("unknown ptrSize for $GOARCH %q", goarch)
+ }
+ intSize := intSizeMap[goarch]
+ if intSize == 0 {
+ fatalf("unknown intSize for $GOARCH %q", goarch)
+ }
+
+ // Reset locale variables so gcc emits English errors [sic].
+ os.Setenv("LANG", "en_US.UTF-8")
+ os.Setenv("LC_ALL", "C")
+
+ p := &Package{
+ PtrSize: ptrSize,
+ IntSize: intSize,
+ CgoFlags: make(map[string][]string),
+ Written: make(map[string]bool),
+ }
+ p.addToFlag("CFLAGS", args)
+ return p
+}
+
+// Record what needs to be recorded about f.
+func (p *Package) Record(f *File) {
+ if p.PackageName == "" {
+ p.PackageName = f.Package
+ } else if p.PackageName != f.Package {
+ error_(token.NoPos, "inconsistent package names: %s, %s", p.PackageName, f.Package)
+ }
+
+ if p.Name == nil {
+ p.Name = f.Name
+ } else {
+ for k, v := range f.Name {
+ if p.Name[k] == nil {
+ p.Name[k] = v
+ } else if p.incompleteTypedef(p.Name[k].Type) {
+ p.Name[k] = v
+ } else if p.incompleteTypedef(v.Type) {
+ // Nothing to do.
+ } else if _, ok := nameToC[k]; ok {
+ // Names we predefine may appear inconsistent
+ // if some files typedef them and some don't.
+ // Issue 26743.
+ } else if !reflect.DeepEqual(p.Name[k], v) {
+ error_(token.NoPos, "inconsistent definitions for C.%s", fixGo(k))
+ }
+ }
+ }
+
+ if f.ExpFunc != nil {
+ p.ExpFunc = append(p.ExpFunc, f.ExpFunc...)
+ p.Preamble += "\n" + f.Preamble
+ }
+ p.Decl = append(p.Decl, f.AST.Decls...)
+}
+
+// incompleteTypedef reports whether t appears to be an incomplete
+// typedef definition.
+func (p *Package) incompleteTypedef(t *Type) bool {
+ return t == nil || (t.Size == 0 && t.Align == -1)
+}