// 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. /* Translate a .cgo file into a .c file. A .cgo file is a combination of a limited form of Go with C. */ /* package PACKAGENAME {# line} func NAME([NAME TYPE { , NAME TYPE }]) [(NAME TYPE { , NAME TYPE })] \{ C code with proper brace nesting \} */ /* We generate C code which implements the function such that it can be called from Go and executes the C code. */ #include #include #include #include #include #include #include #include /* Whether we're emitting for gcc */ static int gcc; /* File and line number */ static const char *file; static unsigned int lineno; /* List of names and types. */ struct params { struct params *next; char *name; char *type; }; /* index into type_table */ enum { Bool, Float, Int, Uint, Uintptr, String, Slice, }; static struct { char *name; int size; } type_table[] = { /* variable sized first, for easy replacement */ /* order matches enum above */ /* default is 32-bit architecture sizes */ "bool", 1, "float", 4, "int", 4, "uint", 4, "uintptr", 4, "String", 8, "Slice", 12, /* fixed size */ "float32", 4, "float64", 8, "byte", 1, "int8", 1, "uint8", 1, "int16", 2, "uint16", 2, "int32", 4, "uint32", 4, "int64", 8, "uint64", 8, NULL, }; /* Fixed structure alignment (non-gcc only) */ int structround = 4; /* Unexpected EOF. */ static void bad_eof(void) { fprintf(stderr, "%s:%u: unexpected EOF\n", file, lineno); exit(1); } /* Out of memory. */ static void bad_mem(void) { fprintf(stderr, "%s:%u: out of memory\n", file, lineno); exit(1); } /* Allocate memory without fail. */ static void * xmalloc(unsigned int size) { void *ret = malloc(size); if (ret == NULL) bad_mem(); return ret; } /* Reallocate memory without fail. */ static void* xrealloc(void *buf, unsigned int size) { void *ret = realloc(buf, size); if (ret == NULL) bad_mem(); return ret; } /* Free a list of parameters. */ static void free_params(struct params *p) { while (p != NULL) { struct params *next; next = p->next; free(p->name); free(p->type); free(p); p = next; } } /* Read a character, tracking lineno. */ static int getchar_update_lineno(void) { int c; c = getchar(); if (c == '\n') ++lineno; return c; } /* Read a character, giving an error on EOF, tracking lineno. */ static int getchar_no_eof(void) { int c; c = getchar_update_lineno(); if (c == EOF) bad_eof(); return c; } /* Read a character, skipping comments. */ static int getchar_skipping_comments(void) { int c; while (1) { c = getchar_update_lineno(); if (c != '/') return c; c = getchar(); if (c == '/') { do { c = getchar_update_lineno(); } while (c != EOF && c != '\n'); return c; } else if (c == '*') { while (1) { c = getchar_update_lineno(); if (c == EOF) return EOF; if (c == '*') { do { c = getchar_update_lineno(); } while (c == '*'); if (c == '/') break; } } } else { ungetc(c, stdin); return '/'; } } } /* Read and return a token. Tokens are delimited by whitespace or by [(),{}]. The latter are all returned as single characters. */ static char * read_token(void) { int c; char *buf; unsigned int alc, off; const char* delims = "(),{}"; while (1) { c = getchar_skipping_comments(); if (c == EOF) return NULL; if (!isspace(c)) break; } alc = 16; buf = xmalloc(alc + 1); off = 0; if (strchr(delims, c) != NULL) { buf[off] = c; ++off; } else { while (1) { if (off >= alc) { alc *= 2; buf = xrealloc(buf, alc + 1); } buf[off] = c; ++off; c = getchar_skipping_comments(); if (c == EOF) break; if (isspace(c) || strchr(delims, c) != NULL) { if (c == '\n') lineno--; ungetc(c, stdin); break; } } } buf[off] = '\0'; return buf; } /* Read a token, giving an error on EOF. */ static char * read_token_no_eof(void) { char *token = read_token(); if (token == NULL) bad_eof(); return token; } /* Read the package clause, and return the package name. */ static char * read_package(void) { char *token; token = read_token_no_eof(); if (strcmp(token, "package") != 0) { fprintf(stderr, "%s:%u: expected \"package\", got \"%s\"\n", file, lineno, token); exit(1); } return read_token_no_eof(); } /* Read and copy preprocessor lines. */ static void read_preprocessor_lines(void) { while (1) { int c; do { c = getchar_skipping_comments(); } while (isspace(c)); if (c != '#') { ungetc(c, stdin); break; } putchar(c); do { c = getchar_update_lineno(); putchar(c); } while (c != '\n'); } } /* Read a type in Go syntax and return a type in C syntax. We only permit basic types and pointers. */ static char * read_type(void) { char *p, *op, *q; int pointer_count; unsigned int len; p = read_token_no_eof(); if (*p != '*') return p; op = p; pointer_count = 0; while (*p == '*') { ++pointer_count; ++p; } len = strlen(p); q = xmalloc(len + pointer_count + 1); memcpy(q, p, len); while (pointer_count > 0) { q[len] = '*'; ++len; --pointer_count; } q[len] = '\0'; free(op); return q; } /* Return the size of the given type. */ static int type_size(char *p) { int i; if(p[strlen(p)-1] == '*') return type_table[Uintptr].size; for(i=0; type_table[i].name; i++) if(strcmp(type_table[i].name, p) == 0) return type_table[i].size; fprintf(stderr, "%s:%u: unknown type %s\n", file, lineno, p); exit(1); return 0; } /* Read a list of parameters. Each parameter is a name and a type. The list ends with a ')'. We have already read the '('. */ static struct params * read_params(int *poffset) { char *token; struct params *ret, **pp, *p; int offset, size, rnd; ret = NULL; pp = &ret; token = read_token_no_eof(); offset = 0; if (strcmp(token, ")") != 0) { while (1) { p = xmalloc(sizeof(struct params)); p->name = token; p->type = read_type(); p->next = NULL; *pp = p; pp = &p->next; size = type_size(p->type); rnd = size; if(rnd > structround) rnd = structround; if(offset%rnd) offset += rnd - offset%rnd; offset += size; token = read_token_no_eof(); if (strcmp(token, ",") != 0) break; token = read_token_no_eof(); } } if (strcmp(token, ")") != 0) { fprintf(stderr, "%s:%u: expected '('\n", file, lineno); exit(1); } if (poffset != NULL) *poffset = offset; return ret; } /* Read a function header. This reads up to and including the initial '{' character. Returns 1 if it read a header, 0 at EOF. */ static int read_func_header(char **name, struct params **params, int *paramwid, struct params **rets) { int lastline; char *token; lastline = -1; while (1) { token = read_token(); if (token == NULL) return 0; if (strcmp(token, "func") == 0) { if(lastline != -1) printf("\n"); break; } if (lastline != lineno) { if (lastline == lineno-1) printf("\n"); else printf("\n#line %d \"%s\"\n", lineno, file); lastline = lineno; } printf("%s ", token); } *name = read_token_no_eof(); token = read_token(); if (token == NULL || strcmp(token, "(") != 0) { fprintf(stderr, "%s:%u: expected \"(\"\n", file, lineno); exit(1); } *params = read_params(paramwid); token = read_token(); if (token == NULL || strcmp(token, "(") != 0) *rets = NULL; else { *rets = read_params(NULL); token = read_token(); } if (token == NULL || strcmp(token, "{") != 0) { fprintf(stderr, "%s:%u: expected \"{\"\n", file, lineno); exit(1); } return 1; } /* Write out parameters. */ static void write_params(struct params *params, int *first) { struct params *p; for (p = params; p != NULL; p = p->next) { if (*first) *first = 0; else printf(", "); printf("%s %s", p->type, p->name); } } /* Write a 6g function header. */ static void write_6g_func_header(char *package, char *name, struct params *params, int paramwid, struct params *rets) { int first, n; printf("void\n%s·%s(", package, name); first = 1; write_params(params, &first); /* insert padding to align output struct */ if(rets != NULL && paramwid%structround != 0) { n = structround - paramwid%structround; if(n & 1) printf(", uint8"); if(n & 2) printf(", uint16"); if(n & 4) printf(", uint32"); } write_params(rets, &first); printf(")\n{\n"); } /* Write a 6g function trailer. */ static void write_6g_func_trailer(struct params *rets) { struct params *p; for (p = rets; p != NULL; p = p->next) printf("\tFLUSH(&%s);\n", p->name); printf("}\n"); } /* Define the gcc function return type if necessary. */ static void define_gcc_return_type(char *package, char *name, struct params *rets) { struct params *p; if (rets == NULL || rets->next == NULL) return; printf("struct %s_%s_ret {\n", package, name); for (p = rets; p != NULL; p = p->next) printf(" %s %s;\n", p->type, p->name); printf("};\n"); } /* Write out the gcc function return type. */ static void write_gcc_return_type(char *package, char *name, struct params *rets) { if (rets == NULL) printf("void"); else if (rets->next == NULL) printf("%s", rets->type); else printf("struct %s_%s_ret", package, name); } /* Write out a gcc function header. */ static void write_gcc_func_header(char *package, char *name, struct params *params, struct params *rets) { int first; struct params *p; define_gcc_return_type(package, name, rets); write_gcc_return_type(package, name, rets); printf(" %s_%s(", package, name); first = 1; write_params(params, &first); printf(") asm (\"%s.%s\");\n", package, name); write_gcc_return_type(package, name, rets); printf(" %s_%s(", package, name); first = 1; write_params(params, &first); printf(")\n{\n"); for (p = rets; p != NULL; p = p->next) printf(" %s %s;\n", p->type, p->name); } /* Write out a gcc function trailer. */ static void write_gcc_func_trailer(char *package, char *name, struct params *rets) { if (rets == NULL) ; else if (rets->next == NULL) printf("return %s;\n", rets->name); else { struct params *p; printf(" {\n struct %s_%s_ret __ret;\n", package, name); for (p = rets; p != NULL; p = p->next) printf(" __ret.%s = %s;\n", p->name, p->name); printf(" return __ret;\n }\n"); } printf("}\n"); } /* Write out a function header. */ static void write_func_header(char *package, char *name, struct params *params, int paramwid, struct params *rets) { if (gcc) write_gcc_func_header(package, name, params, rets); else write_6g_func_header(package, name, params, paramwid, rets); printf("#line %d \"%s\"\n", lineno, file); } /* Write out a function trailer. */ static void write_func_trailer(char *package, char *name, struct params *rets) { if (gcc) write_gcc_func_trailer(package, name, rets); else write_6g_func_trailer(rets); } /* Read and write the body of the function, ending in an unnested } (which is read but not written). */ static void copy_body(void) { int nesting = 0; while (1) { int c; c = getchar_no_eof(); if (c == '}' && nesting == 0) return; putchar(c); switch (c) { default: break; case '{': ++nesting; break; case '}': --nesting; break; case '/': c = getchar_update_lineno(); putchar(c); if (c == '/') { do { c = getchar_no_eof(); putchar(c); } while (c != '\n'); } else if (c == '*') { while (1) { c = getchar_no_eof(); putchar(c); if (c == '*') { do { c = getchar_no_eof(); putchar(c); } while (c == '*'); if (c == '/') break; } } } break; case '"': case '\'': { int delim = c; do { c = getchar_no_eof(); putchar(c); if (c == '\\') { c = getchar_no_eof(); putchar(c); c = '\0'; } } while (c != delim); } break; } } } /* Process the entire file. */ static void process_file(void) { char *package, *name; struct params *params, *rets; int paramwid; package = read_package(); read_preprocessor_lines(); while (read_func_header(&name, ¶ms, ¶mwid, &rets)) { write_func_header(package, name, params, paramwid, rets); copy_body(); write_func_trailer(package, name, rets); free(name); free_params(params); free_params(rets); } free(package); } static void usage(void) { fprintf(stderr, "Usage: cgo2c [--6g | --gc] [file]\n"); exit(1); } void main(int argc, char **argv) { char *goarch; while(argc > 1 && argv[1][0] == '-') { if(strcmp(argv[1], "-") == 0) break; if(strcmp(argv[1], "--6g") == 0) gcc = 0; else if(strcmp(argv[1], "--gcc") == 0) gcc = 1; else usage(); argc--; argv++; } if(argc <= 1 || strcmp(argv[1], "-") == 0) { file = ""; process_file(); exits(nil); } if(argc > 2) usage(); file = argv[1]; if(freopen(file, "r", stdin) == 0) { fprintf(stderr, "open %s: %r\n", file); exits("open"); } if(!gcc) { // 6g etc; update size table goarch = getenv("GOARCH"); if(goarch != NULL && strcmp(goarch, "amd64") == 0) { type_table[Uintptr].size = 8; type_table[String].size = 16; type_table[Slice].size = 8+4+4; structround = 8; } } process_file(); exits(nil); }