Commit 0432f289 authored by Russ Cox's avatar Russ Cox

cgo: various bug fixes

* remember #defined names, so that C.stdout can refer
  to the real name (on OS X) __stdoutp.
* better handling of #defined constant expressions
* allow n, err = C.strtol("asdf", 0, 123) to get errno as os.Error
* write all output files to current directory
* don't require gcc output if there was no input

Fixes #533.
Fixes #709.
Fixes #756.

R=r
CC=dho, golang-dev, iant
https://golang.org/cl/1734047
parent e8fcf600
......@@ -6,7 +6,9 @@ include ../../../src/Make.$(GOARCH)
TARG=stdio
CGOFILES=\
file.go
align.go\
file.go\
test.go\
CLEANFILES+=hello fib chain run.out
......
package stdio
/*
#include <stdio.h>
typedef unsigned char Uint8;
typedef unsigned short Uint16;
typedef enum {
MOD1 = 0x0000,
MODX = 0x8000
} SDLMod;
typedef enum {
A = 1,
B = 322,
SDLK_LAST
} SDLKey;
typedef struct SDL_keysym {
Uint8 scancode;
SDLKey sym;
SDLMod mod;
Uint16 unicode;
} SDL_keysym;
typedef struct SDL_KeyboardEvent {
Uint8 typ;
Uint8 which;
Uint8 state;
SDL_keysym keysym;
} SDL_KeyboardEvent;
void makeEvent(SDL_KeyboardEvent *event) {
unsigned char *p;
int i;
p = (unsigned char*)event;
for (i=0; i<sizeof *event; i++) {
p[i] = i;
}
}
int same(SDL_KeyboardEvent* e, Uint8 typ, Uint8 which, Uint8 state, Uint8 scan, SDLKey sym, SDLMod mod, Uint16 uni) {
return e->typ == typ && e->which == which && e->state == state && e->keysym.scancode == scan && e->keysym.sym == sym && e->keysym.mod == mod && e->keysym.unicode == uni;
}
void cTest(SDL_KeyboardEvent *event) {
printf("C: %#x %#x %#x %#x %#x %#x %#x\n", event->typ, event->which, event->state,
event->keysym.scancode, event->keysym.sym, event->keysym.mod, event->keysym.unicode);
fflush(stdout);
}
*/
import "C"
import (
"fmt"
"syscall"
)
func TestAlign() {
if syscall.ARCH == "amd64" {
// alignment is known to be broken on amd64.
// http://code.google.com/p/go/issues/detail?id=609
return
}
var evt C.SDL_KeyboardEvent
C.makeEvent(&evt)
if C.same(&evt, evt.typ, evt.which, evt.state, evt.keysym.scancode, evt.keysym.sym, evt.keysym.mod, evt.keysym.unicode) == 0 {
fmt.Println("*** bad alignment")
C.cTest(&evt)
fmt.Printf("Go: %#x %#x %#x %#x %#x %#x %#x\n",
evt.typ, evt.which, evt.state, evt.keysym.scancode,
evt.keysym.sym, evt.keysym.mod, evt.keysym.unicode)
fmt.Println(evt)
}
}
......@@ -22,7 +22,7 @@ func link(left chan<- int, right <-chan int) {
runtime.LockOSThread()
for {
v := <-right
stdio.Puts(strconv.Itoa(v))
stdio.Stdout.WriteString(strconv.Itoa(v) + "\n")
left <- 1+v
}
}
......@@ -38,6 +38,6 @@ func main() {
for i := 0; i < R; i++ {
right <- 0
x := <-leftmost
stdio.Puts(strconv.Itoa(x))
stdio.Stdout.WriteString(strconv.Itoa(x) + "\n")
}
}
......@@ -26,7 +26,7 @@ func fibber(c, out chan int64, i int64) {
}
for {
j := <-c
stdio.Puts(strconv.Itoa64(j))
stdio.Stdout.WriteString(strconv.Itoa64(j) + "\n")
out <- j
<-out
i += j
......
......@@ -10,33 +10,31 @@ see ../gmp/gmp.go.
package stdio
// TODO(rsc): Remove fflushstdout when C.fflush(C.stdout) works in cgo.
/*
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
void fflushstdout(void) { fflush(stdout); }
char* greeting = "hello, world";
*/
import "C"
import "unsafe"
/*
type File C.FILE
var Stdout = (*File)(C.stdout)
var Stderr = (*File)(C.stderr)
func (f *File) WriteString(s string) {
p := C.CString(s);
C.fputs(p, (*C.FILE)(f));
C.free(p);
}
*/
func Puts(s string) {
p := C.CString(s)
C.puts(p)
C.fputs(p, (*C.FILE)(f))
C.free(unsafe.Pointer(p))
C.fflushstdout()
f.Flush()
}
func (f *File) Flush() {
C.fflush((*C.FILE)(f))
}
var Greeting = C.GoString(C.greeting)
......@@ -4,9 +4,26 @@
package main
import "stdio"
import (
"os"
"stdio"
)
func main() {
// stdio.Stdout.WriteString("hello, world\n");
stdio.Puts("hello, world")
stdio.Stdout.WriteString(stdio.Greeting + "\n")
l := stdio.Atol("123")
if l != 123 {
println("Atol 123: ", l)
panic("bad atol")
}
n, err := stdio.Strtol("asdf", 123)
if n != 0 || err != os.EINVAL {
println("Strtol: ", n, err)
panic("bad atoi2")
}
stdio.TestAlign()
stdio.TestEnum()
}
// Copyright 2010 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.
// This file contains test cases for cgo.
package stdio
/*
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <errno.h>
#define SHIFT(x, y) ((x)<<(y))
#define KILO SHIFT(1, 10)
enum {
Enum1 = 1,
Enum2 = 2,
};
*/
import "C"
import (
"os"
"unsafe"
)
const EINVAL = C.EINVAL /* test #define */
var KILO = C.KILO
func Size(name string) (int64, os.Error) {
var st C.struct_stat
p := C.CString(name)
_, err := C.stat(p, &st)
C.free(unsafe.Pointer(p))
if err != nil {
return 0, err
}
return int64(C.ulong(st.st_size)), nil
}
func Strtol(s string, base int) (int, os.Error) {
p := C.CString(s)
n, err := C.strtol(p, nil, C.int(base))
C.free(unsafe.Pointer(p))
return int(n), err
}
func Atol(s string) int {
p := C.CString(s)
n := C.atol(p)
C.free(unsafe.Pointer(p))
return int(n)
}
func TestEnum() {
if C.Enum1 != 1 || C.Enum2 != 2 {
println("bad enum", C.Enum1, C.Enum2)
}
}
func TestAtol() {
l := Atol("123")
if l != 123 {
println("Atol 123: ", l)
panic("bad atol")
}
}
func TestErrno() {
n, err := Strtol("asdf", 123)
if n != 0 || err != os.EINVAL {
println("Strtol: ", n, err)
panic("bad atoi2")
}
}
var (
uint = (C.uint)(0)
ulong C.ulong
char C.char
)
func Test() {
TestAlign()
TestAtol()
TestEnum()
TestErrno()
}
This diff is collapsed.
......@@ -23,6 +23,19 @@ the package. For example:
// #include <errno.h>
import "C"
C identifiers or field names that are keywords in Go can be
accessed by prefixing them with an underscore: if x points at
a C struct with a field named "type", x._type accesses the field.
To access a struct, union, or enum type directly, prefix it with
struct_, union_, or enum_, as in C.struct_stat.
Any C function that returns a value may be called in a multiple
assignment context to retrieve both the return value and the
C errno variable as an os.Error. For example:
n, err := C.atoi("abc")
Cgo transforms the input file into four output files: two Go source
files, a C file for 6c (or 8c or 5c), and a C file for gcc.
......
This diff is collapsed.
......@@ -11,13 +11,91 @@
package main
import (
"flag"
"fmt"
"go/ast"
"go/token"
"os"
"reflect"
"strings"
)
func usage() { fmt.Fprint(os.Stderr, "usage: cgo [compiler options] file.go ...\n") }
// A Package collects information about the package we're going to write.
type Package struct {
PackageName string // name of package
PackagePath string
PtrSize int64
GccOptions []string
Written map[string]bool
Name map[string]*Name // accumulated Name from Files
Typedef map[string]ast.Expr // accumulated Typedef from Files
ExpFunc []*ExpFunc // accumulated ExpFunc from Files
Decl []ast.Decl
}
// A File collects information about a single Go input file.
type File struct {
AST *ast.File // parsed AST
Package string // Package name
Preamble string // C preamble (doc comment on import "C")
Ref []*Ref // all references to C.xxx in AST
ExpFunc []*ExpFunc // exported functions for this file
Name map[string]*Name // map from Go name to Name
Typedef map[string]ast.Expr // translations of all necessary types from C
}
// A Ref refers to an expression of the form C.xxx in the AST.
type Ref struct {
Name *Name
Expr *ast.Expr
Context string // "type", "expr", "call", or "call2"
}
func (r *Ref) Pos() token.Position {
return (*r.Expr).Pos()
}
// 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 // "const", "type", "var", "func", "not-type"
Type *Type // the type of xxx
FuncType *FuncType
AddError bool
Const string // constant definition
}
// A 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
}
// A Type collects information about a type in both the C and Go worlds.
type Type struct {
Size int64
Align int64
C string
Go ast.Expr
EnumValues map[string]int64
}
// 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")
os.Exit(2)
}
var ptrSizeMap = map[string]int64{
"386": 4,
......@@ -25,43 +103,34 @@ var ptrSizeMap = map[string]int64{
"arm": 4,
}
var expandName = 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",
}
func main() {
args := os.Args
if len(args) < 2 {
flag.Usage = usage
flag.Parse()
args := flag.Args()
if len(args) < 1 {
usage()
os.Exit(2)
}
// 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) - 1; i > 0; i-- {
if !strings.HasSuffix(args[i], ".go") {
for i = len(args); i > 0; i-- {
if !strings.HasSuffix(args[i-1], ".go") {
break
}
}
i += 1
gccOptions, goFiles := args[1:i], args[i:]
if i == len(args) {
usage()
}
gccOptions, goFiles := args[0:i], args[i:]
arch := os.Getenv("GOARCH")
if arch == "" {
fatal("$GOARCH is not set")
}
ptrSize, ok := ptrSizeMap[arch]
if !ok {
fatal("unknown architecture %s", arch)
ptrSize := ptrSizeMap[arch]
if ptrSize == 0 {
fatal("unknown $GOARCH %q", arch)
}
// Clear locale variables so gcc emits English errors [sic].
......@@ -69,75 +138,88 @@ func main() {
os.Setenv("LC_ALL", "C")
os.Setenv("LC_CTYPE", "C")
p := new(Prog)
p.PtrSize = ptrSize
p.GccOptions = gccOptions
p.Vardef = make(map[string]*Type)
p.Funcdef = make(map[string]*FuncType)
p.Enumdef = make(map[string]int64)
p.Constdef = make(map[string]string)
p.OutDefs = make(map[string]bool)
p := &Package{
PtrSize: ptrSize,
GccOptions: gccOptions,
Written: make(map[string]bool),
}
for _, input := range goFiles {
// Reset p.Preamble so that we don't end up with conflicting headers / defines
p.Preamble = builtinProlog
openProg(input, p)
for _, cref := range p.Crefs {
// Convert C.ulong to C.unsigned long, etc.
if expand, ok := expandName[cref.Name]; ok {
cref.Name = expand
}
}
p.loadDebugInfo()
for _, cref := range p.Crefs {
f := new(File)
// Reset f.Preamble so that we don't end up with conflicting headers / defines
f.Preamble = ""
f.ReadGo(input)
p.Translate(f)
for _, cref := range f.Ref {
switch cref.Context {
case "const":
// This came from a #define and we'll output it later.
*cref.Expr = ast.NewIdent(cref.Name)
break
case "call":
if !cref.TypeName {
// Is an actual function call.
pos := (*cref.Expr).Pos()
*cref.Expr = &ast.Ident{Position: pos, Obj: ast.NewObj(ast.Err, pos, "_C_"+cref.Name)}
p.Funcdef[cref.Name] = cref.FuncType
break
}
*cref.Expr = cref.Type.Go
case "expr":
if cref.TypeName {
error((*cref.Expr).Pos(), "type C.%s used as expression", cref.Name)
}
// If the expression refers to an enumerated value, then
// place the identifier for the value and add it to Enumdef so
// it will be declared as a constant in the later stage.
if cref.Type.EnumValues != nil {
*cref.Expr = ast.NewIdent(cref.Name)
p.Enumdef[cref.Name] = cref.Type.EnumValues[cref.Name]
case "call", "call2":
if cref.Name.Kind != "type" {
break
}
// Reference to C variable.
// We declare a pointer and arrange to have it filled in.
*cref.Expr = &ast.StarExpr{X: ast.NewIdent("_C_" + cref.Name)}
p.Vardef[cref.Name] = cref.Type
case "type":
if !cref.TypeName {
error((*cref.Expr).Pos(), "expression C.%s used as type", cref.Name)
}
*cref.Expr = cref.Type.Go
*cref.Expr = cref.Name.Type.Go
}
}
if nerrors > 0 {
os.Exit(2)
}
pkg := p.Package
pkg := f.Package
if dir := os.Getenv("CGOPKGPATH"); dir != "" {
pkg = dir + "/" + pkg
}
p.PackagePath = pkg
p.writeOutput(input)
p.writeOutput(f, input)
p.Record(f)
}
p.writeDefs()
}
// 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(noPos, "inconsistent package names: %s, %s", p.PackageName, f.Package)
}
if p.Typedef == nil {
p.Typedef = f.Typedef
} else {
for k, v := range f.Typedef {
if p.Typedef[k] == nil {
p.Typedef[k] = v
} else if !reflect.DeepEqual(p.Typedef[k], v) {
error(noPos, "inconsistent definitions for C type %s", k)
}
}
}
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 !reflect.DeepEqual(p.Name[k], v) {
error(noPos, "inconsistent definitions for C.%s", k)
}
}
}
if len(f.ExpFunc) > 0 {
n := len(p.ExpFunc)
ef := make([]*ExpFunc, n+len(f.ExpFunc))
copy(ef, p.ExpFunc)
copy(ef[n:], f.ExpFunc)
p.ExpFunc = ef
}
if len(f.AST.Decls) > 0 {
n := len(p.Decl)
d := make([]ast.Decl, n+len(f.AST.Decls))
copy(d, p.Decl)
copy(d[n:], f.AST.Decls)
p.Decl = d
}
}
This diff is collapsed.
......@@ -12,16 +12,6 @@ import (
"os"
)
// A ByteReaderAt implements io.ReadAt using a slice of bytes.
type ByteReaderAt []byte
func (r ByteReaderAt) ReadAt(p []byte, off int64) (n int, err os.Error) {
if off >= int64(len(r)) || off < 0 {
return 0, os.EOF
}
return copy(p, r[off:]), nil
}
// run runs the command argv, feeding in stdin on standard input.
// It returns the output to standard output and standard error.
// ok indicates whether the command exited successfully.
......@@ -92,3 +82,38 @@ func error(pos token.Position, msg string, args ...interface{}) {
fmt.Fprintf(os.Stderr, msg, args)
fmt.Fprintf(os.Stderr, "\n")
}
// isName returns true if s is a valid C identifier
func isName(s string) bool {
for i, v := range s {
if v != '_' && (v < 'A' || v > 'Z') && (v < 'a' || v > 'z') && (v < '0' || v > '9') {
return false
}
if i == 0 && '0' <= v && v <= '9' {
return false
}
}
return s != ""
}
func creat(name string) *os.File {
f, err := os.Open(name, os.O_WRONLY|os.O_CREAT|os.O_TRUNC, 0666)
if err != nil {
fatal("%s", err)
}
return f
}
func slashToUnderscore(c int) int {
if c == '/' {
c = '_'
}
return c
}
func concat(a, b []string) []string {
c := make([]string, len(a)+len(b))
copy(c, a)
copy(c[len(a):], b)
return c
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment