From 2adb671d186febfe9610f8d8ac8ba296b79d2c90 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 30 Aug 2016 03:27:43 +0000 Subject: [PATCH] compiler: add -fgo-c-header=FILE to create a C header The new -fgo-c-header=FILE option will write a C header file defining all the struct types and numeric const values in package scope. This will be used when building the Go runtime package (libgo/go/runtime) to generate a C header file that may be included by the C code in the C runtime package (libgo/runtime). This will ensure that the Go code and C code are working with the same data structures as we convert the runtime from C to Go to upgrade to the current GC runtime, notably the concurrent garbage collector. Reviewed-on: https://go-review.googlesource.com/28000 * lang.opt (fgo-c-header, fgo-compiling-runtime): New options. * go-c.h (struct go_create_gogo_args): Define. (go_create_gogo): Change declaration to take struct pointer. * go-lang.c (go_c_header): New static variable. (go_langhook_init): Update call to go_create_gogo. * gccgo.texi (Invoking gccgo): Document -fgo-c-header and -fgo-compiling-runtime. From-SVN: r239852 --- gcc/go/ChangeLog | 10 ++ gcc/go/gccgo.texi | 11 ++ gcc/go/go-c.h | 20 ++- gcc/go/go-lang.c | 20 ++- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/go.cc | 38 +++--- gcc/go/gofrontend/gogo.cc | 127 +++++++++++++++++ gcc/go/gofrontend/gogo.h | 24 ++++ gcc/go/gofrontend/types.cc | 272 +++++++++++++++++++++++++++++++++++++ gcc/go/gofrontend/types.h | 20 +++ gcc/go/lang.opt | 8 ++ 11 files changed, 525 insertions(+), 27 deletions(-) diff --git a/gcc/go/ChangeLog b/gcc/go/ChangeLog index dbe3943c2e7..2052a977029 100644 --- a/gcc/go/ChangeLog +++ b/gcc/go/ChangeLog @@ -1,3 +1,13 @@ +2016-08-29 Ian Lance Taylor + + * lang.opt (fgo-c-header, fgo-compiling-runtime): New options. + * go-c.h (struct go_create_gogo_args): Define. + (go_create_gogo): Change declaration to take struct pointer. + * go-lang.c (go_c_header): New static variable. + (go_langhook_init): Update call to go_create_gogo. + * gccgo.texi (Invoking gccgo): Document -fgo-c-header and + -fgo-compiling-runtime. + 2016-08-09 Ian Lance Taylor * gccgo.texi (Invoking gccgo): Document -fgo-optimize-allocs and diff --git a/gcc/go/gccgo.texi b/gcc/go/gccgo.texi index 6544b7981a5..04ce3e996c7 100644 --- a/gcc/go/gccgo.texi +++ b/gcc/go/gccgo.texi @@ -239,6 +239,17 @@ heap when possible. In the future this may be the default. Output escape analysis debugging information. Larger values of @var{n} generate more information. +@item -fgo-c-header=@var{file} +@cindex @option{-fgo-c-header} +Write top-level named Go struct definitions to @var{file} as C code. +This is used when compiling the runtime package. + +@item -fgo-compiling-runtime +@cindex @option{-fgo-compiling-runtime} +Apply special rules for compiling the runtime package. Implicit +memory allocation is forbidden. Some additional compiler directives +are supported. + @item -g @cindex @option{-g for gccgo} This is the standard @command{gcc} option (@pxref{Debugging Options, , diff --git a/gcc/go/go-c.h b/gcc/go/go-c.h index 690d6db7ab9..a96812d3cb5 100644 --- a/gcc/go/go-c.h +++ b/gcc/go/go-c.h @@ -31,11 +31,21 @@ extern int go_enable_optimize (const char*); extern void go_add_search_path (const char*); -extern void go_create_gogo (int int_type_size, int pointer_size, - const char* pkgpath, const char *prefix, - const char *relative_import_path, - bool check_divide_zero, bool check_divide_overflow, - int debug_escape_level); +struct go_create_gogo_args +{ + int int_type_size; + int pointer_size; + const char* pkgpath; + const char *prefix; + const char *relative_import_path; + const char *c_header; + bool check_divide_by_zero; + bool check_divide_overflow; + bool compiling_runtime; + int debug_escape_level; +}; + +extern void go_create_gogo (const struct go_create_gogo_args*); extern void go_parse_input_files (const char**, unsigned int, bool only_check_syntax, diff --git a/gcc/go/go-lang.c b/gcc/go/go-lang.c index 570f5e06e8d..88667e0c9b4 100644 --- a/gcc/go/go-lang.c +++ b/gcc/go/go-lang.c @@ -83,6 +83,7 @@ struct GTY(()) language_function static const char *go_pkgpath = NULL; static const char *go_prefix = NULL; static const char *go_relative_import_path = NULL; +static const char *go_c_header = NULL; /* Language hooks. */ @@ -99,9 +100,18 @@ go_langhook_init (void) to, e.g., unsigned_char_type_node) but before calling build_common_builtin_nodes (because it calls, indirectly, go_type_for_size). */ - go_create_gogo (INT_TYPE_SIZE, POINTER_SIZE, go_pkgpath, go_prefix, - go_relative_import_path, go_check_divide_zero, - go_check_divide_overflow, go_debug_escape_level); + struct go_create_gogo_args args; + args.int_type_size = INT_TYPE_SIZE; + args.pointer_size = POINTER_SIZE; + args.pkgpath = go_pkgpath; + args.prefix = go_prefix; + args.relative_import_path = go_relative_import_path; + args.c_header = go_c_header; + args.check_divide_by_zero = go_check_divide_zero; + args.check_divide_overflow = go_check_divide_overflow; + args.compiling_runtime = go_compiling_runtime; + args.debug_escape_level = go_debug_escape_level; + go_create_gogo (&args); build_common_builtin_nodes (); @@ -247,6 +257,10 @@ go_langhook_handle_option ( go_relative_import_path = arg; break; + case OPT_fgo_c_header_: + go_c_header = arg; + break; + default: /* Just return 1 to indicate that the option is valid. */ break; diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index f73d5dd51ac..e92aee31835 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -0e505f5d191182abd8beb9b4c8232174bc116f97 +9c91e7eeb404b5b639cd6e80e2a38da948bb35ec The first line of this file holds the git revision number of the last merge done from the gofrontend repository. diff --git a/gcc/go/gofrontend/go.cc b/gcc/go/gofrontend/go.cc index ee87141efd2..d0f523bdfe8 100644 --- a/gcc/go/gofrontend/go.cc +++ b/gcc/go/gofrontend/go.cc @@ -20,27 +20,29 @@ static Gogo* gogo; GO_EXTERN_C void -go_create_gogo(int int_type_size, int pointer_size, const char *pkgpath, - const char *prefix, const char *relative_import_path, - bool check_divide_by_zero, bool check_divide_overflow, - int debug_escape_level) +go_create_gogo(const struct go_create_gogo_args* args) { go_assert(::gogo == NULL); Linemap* linemap = go_get_linemap(); - ::gogo = new Gogo(go_get_backend(), linemap, int_type_size, pointer_size); - - if (pkgpath != NULL) - ::gogo->set_pkgpath(pkgpath); - else if (prefix != NULL) - ::gogo->set_prefix(prefix); - - if (relative_import_path != NULL) - ::gogo->set_relative_import_path(relative_import_path); - if (check_divide_by_zero) - ::gogo->set_check_divide_by_zero(check_divide_by_zero); - if (check_divide_overflow) - ::gogo->set_check_divide_overflow(check_divide_overflow); - ::gogo->set_debug_escape_level(debug_escape_level); + ::gogo = new Gogo(go_get_backend(), linemap, args->int_type_size, + args->pointer_size); + + if (args->pkgpath != NULL) + ::gogo->set_pkgpath(args->pkgpath); + else if (args->prefix != NULL) + ::gogo->set_prefix(args->prefix); + + if (args->relative_import_path != NULL) + ::gogo->set_relative_import_path(args->relative_import_path); + if (args->check_divide_by_zero) + ::gogo->set_check_divide_by_zero(args->check_divide_by_zero); + if (args->check_divide_overflow) + ::gogo->set_check_divide_overflow(args->check_divide_overflow); + if (args->compiling_runtime) + ::gogo->set_compiling_runtime(args->compiling_runtime); + if (args->c_header != NULL) + ::gogo->set_c_header(args->c_header); + ::gogo->set_debug_escape_level(args->debug_escape_level); } // Parse the input files. diff --git a/gcc/go/gofrontend/gogo.cc b/gcc/go/gofrontend/gogo.cc index e1ebd6524d7..3b7ecd3491f 100644 --- a/gcc/go/gofrontend/gogo.cc +++ b/gcc/go/gofrontend/gogo.cc @@ -6,6 +6,8 @@ #include "go-system.h" +#include + #include "filenames.h" #include "go-c.h" @@ -4429,6 +4431,131 @@ Gogo::do_exports() : ""), this->imported_init_fns_, this->package_->bindings()); + + if (!this->c_header_.empty() && !saw_errors()) + this->write_c_header(); +} + +// Write the top level named struct types in C format to a C header +// file. This is used when building the runtime package, to share +// struct definitions between C and Go. + +void +Gogo::write_c_header() +{ + std::ofstream out; + out.open(this->c_header_.c_str()); + if (out.fail()) + { + error("cannot open %s: %m", this->c_header_.c_str()); + return; + } + + std::list types; + Bindings* top = this->package_->bindings(); + for (Bindings::const_definitions_iterator p = top->begin_definitions(); + p != top->end_definitions(); + ++p) + { + Named_object* no = *p; + if (no->is_type() && no->type_value()->struct_type() != NULL) + types.push_back(no); + if (no->is_const() && no->const_value()->type()->integer_type() != NULL) + { + Numeric_constant nc; + unsigned long val; + if (no->const_value()->expr()->numeric_constant_value(&nc) + && nc.to_unsigned_long(&val) == Numeric_constant::NC_UL_VALID) + { + out << "#define " << no->message_name() << ' ' << val + << std::endl; + } + } + } + + std::vector written; + int loop = 0; + while (!types.empty()) + { + Named_object* no = types.front(); + types.pop_front(); + + std::vector requires; + std::vector declare; + if (!no->type_value()->struct_type()->can_write_to_c_header(&requires, + &declare)) + continue; + + bool ok = true; + for (std::vector::const_iterator pr + = requires.begin(); + pr != requires.end() && ok; + ++pr) + { + for (std::list::const_iterator pt = types.begin(); + pt != types.end() && ok; + ++pt) + if (*pr == *pt) + ok = false; + } + if (!ok) + { + ++loop; + if (loop > 10000) + { + // This should be impossible since the code parsed and + // type checked. + go_unreachable(); + } + + types.push_back(no); + continue; + } + + for (std::vector::const_iterator pd + = declare.begin(); + pd != declare.end(); + ++pd) + { + if (*pd == no) + continue; + + std::vector drequires; + std::vector ddeclare; + if (!(*pd)->type_value()->struct_type()-> + can_write_to_c_header(&drequires, &ddeclare)) + continue; + + bool done = false; + for (std::vector::const_iterator pw + = written.begin(); + pw != written.end(); + ++pw) + { + if (*pw == *pd) + { + done = true; + break; + } + } + if (!done) + { + out << std::endl; + out << "struct " << (*pd)->message_name() << ";" << std::endl; + written.push_back(*pd); + } + } + + out << std::endl; + out << "struct " << no->message_name() << " {" << std::endl; + no->type_value()->struct_type()->write_to_c_header(out); + out << "};" << std::endl; + written.push_back(no); + } + + out.close(); + if (out.fail()) + error("error writing to %s: %m", this->c_header_.c_str()); } // Find the blocks in order to convert named types defined in blocks. diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index 0c45443d3f1..fe9322c0be6 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -250,6 +250,12 @@ class Gogo set_relative_import_path(const std::string& s) { this->relative_import_path_ = s; } + // Set the C header file to write. This is used for the runtime + // package. + void + set_c_header(const std::string& s) + { this->c_header_ = s; } + // Return whether to check for division by zero in binary operations. bool check_divide_by_zero() const @@ -270,6 +276,16 @@ class Gogo set_check_divide_overflow(bool b) { this->check_divide_overflow_ = b; } + // Return whether we are compiling the runtime package. + bool + compiling_runtime() const + { return this->compiling_runtime_; } + + // Set whether we are compiling the runtime package. + void + set_compiling_runtime(bool b) + { this->compiling_runtime_ = b; } + // Return the level of escape analysis debug information to emit. int debug_escape_level() const @@ -731,6 +747,9 @@ class Gogo const Bindings* current_bindings() const; + void + write_c_header(); + // Get the decl for the magic initialization function. Named_object* initialization_function_decl(); @@ -841,12 +860,17 @@ class Gogo // The relative import path, from the -fgo-relative-import-path // option. std::string relative_import_path_; + // The C header file to write, from the -fgo-c-header option. + std::string c_header_; // Whether or not to check for division by zero, from the // -fgo-check-divide-zero option. bool check_divide_by_zero_; // Whether or not to check for division overflow, from the // -fgo-check-divide-overflow option. bool check_divide_overflow_; + // Whether we are compiling the runtime package, from the + // -fgo-compiling-runtime option. + bool compiling_runtime_; // The level of escape analysis debug information to emit, from the // -fgo-debug-escape option. int debug_escape_level_; diff --git a/gcc/go/gofrontend/types.cc b/gcc/go/gofrontend/types.cc index 1b764c0d251..d91180a0497 100644 --- a/gcc/go/gofrontend/types.cc +++ b/gcc/go/gofrontend/types.cc @@ -6,6 +6,8 @@ #include "go-system.h" +#include + #include "go-c.h" #include "gogo.h" #include "operator.h" @@ -5680,6 +5682,276 @@ Struct_type::do_import(Import* imp) return Type::make_struct_type(fields, imp->location()); } +// Whether we can write this struct type to a C header file. +// We can't if any of the fields are structs defined in a different package. + +bool +Struct_type::can_write_to_c_header( + std::vector* requires, + std::vector* declare) const +{ + const Struct_field_list* fields = this->fields_; + if (fields == NULL || fields->empty()) + return false; + for (Struct_field_list::const_iterator p = fields->begin(); + p != fields->end(); + ++p) + { + if (p->is_anonymous()) + return false; + if (!this->can_write_type_to_c_header(p->type(), requires, declare)) + return false; + } + return true; +} + +// Whether we can write the type T to a C header file. + +bool +Struct_type::can_write_type_to_c_header( + const Type* t, + std::vector* requires, + std::vector* declare) const +{ + t = t->forwarded(); + switch (t->classification()) + { + case TYPE_ERROR: + case TYPE_FORWARD: + return false; + + case TYPE_VOID: + case TYPE_BOOLEAN: + case TYPE_INTEGER: + case TYPE_FLOAT: + case TYPE_COMPLEX: + case TYPE_STRING: + case TYPE_FUNCTION: + case TYPE_MAP: + case TYPE_CHANNEL: + case TYPE_INTERFACE: + return true; + + case TYPE_POINTER: + if (t->points_to()->named_type() != NULL + && t->points_to()->struct_type() != NULL) + declare->push_back(t->points_to()->named_type()->named_object()); + return true; + + case TYPE_STRUCT: + return t->struct_type()->can_write_to_c_header(requires, declare); + + case TYPE_ARRAY: + if (t->is_slice_type()) + return true; + return this->can_write_type_to_c_header(t->array_type()->element_type(), + requires, declare); + + case TYPE_NAMED: + { + const Named_object* no = t->named_type()->named_object(); + if (no->package() != NULL) + { + if (t->is_unsafe_pointer_type()) + return true; + return false; + } + if (t->struct_type() != NULL) + { + requires->push_back(no); + return t->struct_type()->can_write_to_c_header(requires, declare); + } + return this->can_write_type_to_c_header(t->base(), requires, declare); + } + + case TYPE_CALL_MULTIPLE_RESULT: + case TYPE_NIL: + case TYPE_SINK: + default: + go_unreachable(); + } +} + +// Write this struct to a C header file. + +void +Struct_type::write_to_c_header(std::ostream& os) const +{ + const Struct_field_list* fields = this->fields_; + for (Struct_field_list::const_iterator p = fields->begin(); + p != fields->end(); + ++p) + { + os << '\t'; + this->write_field_to_c_header(os, p->field_name(), p->type()); + os << ';' << std::endl; + } +} + +// Write the type of a struct field to a C header file. + +void +Struct_type::write_field_to_c_header(std::ostream& os, const std::string& name, + const Type *t) const +{ + bool print_name = true; + t = t->forwarded(); + switch (t->classification()) + { + case TYPE_VOID: + os << "void"; + break; + + case TYPE_BOOLEAN: + os << "_Bool"; + break; + + case TYPE_INTEGER: + { + const Integer_type* it = t->integer_type(); + if (it->is_unsigned()) + os << 'u'; + os << "int" << it->bits() << "_t"; + } + break; + + case TYPE_FLOAT: + switch (t->float_type()->bits()) + { + case 32: + os << "float"; + break; + case 64: + os << "double"; + break; + default: + go_unreachable(); + } + break; + + case TYPE_COMPLEX: + switch (t->complex_type()->bits()) + { + case 64: + os << "float _Complex"; + break; + case 128: + os << "double _Complex"; + break; + default: + go_unreachable(); + } + break; + + case TYPE_STRING: + os << "String"; + break; + + case TYPE_FUNCTION: + os << "FuncVal"; + break; + + case TYPE_POINTER: + { + std::vector requires; + std::vector declare; + if (!this->can_write_type_to_c_header(t->points_to(), &requires, + &declare)) + os << "void*"; + else + { + this->write_field_to_c_header(os, "", t->points_to()); + os << '*'; + } + } + break; + + case TYPE_MAP: + os << "Map*"; + break; + + case TYPE_CHANNEL: + os << "Chan*"; + break; + + case TYPE_INTERFACE: + if (t->interface_type()->is_empty()) + os << "Eface"; + else + os << "Iface"; + break; + + case TYPE_STRUCT: + os << "struct {" << std::endl; + t->struct_type()->write_to_c_header(os); + os << "\t}"; + break; + + case TYPE_ARRAY: + if (t->is_slice_type()) + os << "Slice"; + else + { + const Type *ele = t; + std::vector array_types; + while (ele->array_type() != NULL && !ele->is_slice_type()) + { + array_types.push_back(ele); + ele = ele->array_type()->element_type(); + } + this->write_field_to_c_header(os, "", ele); + os << ' ' << Gogo::message_name(name); + print_name = false; + while (!array_types.empty()) + { + ele = array_types.back(); + array_types.pop_back(); + os << '['; + Numeric_constant nc; + if (!ele->array_type()->length()->numeric_constant_value(&nc)) + go_unreachable(); + mpz_t val; + if (!nc.to_int(&val)) + go_unreachable(); + char* s = mpz_get_str(NULL, 10, val); + os << s; + free(s); + mpz_clear(val); + os << ']'; + } + } + break; + + case TYPE_NAMED: + { + const Named_object* no = t->named_type()->named_object(); + if (t->struct_type() != NULL) + os << "struct " << no->message_name(); + else if (t->is_unsafe_pointer_type()) + os << "void*"; + else if (t == Type::lookup_integer_type("uintptr")) + os << "uintptr_t"; + else + { + this->write_field_to_c_header(os, name, t->base()); + print_name = false; + } + } + break; + + case TYPE_ERROR: + case TYPE_FORWARD: + case TYPE_CALL_MULTIPLE_RESULT: + case TYPE_NIL: + case TYPE_SINK: + default: + go_unreachable(); + } + + if (print_name && !name.empty()) + os << ' ' << Gogo::message_name(name); +} + // Make a struct type. Struct_type* diff --git a/gcc/go/gofrontend/types.h b/gcc/go/gofrontend/types.h index 682f61145d2..5de49ae3534 100644 --- a/gcc/go/gofrontend/types.h +++ b/gcc/go/gofrontend/types.h @@ -7,6 +7,8 @@ #ifndef GO_TYPES_H #define GO_TYPES_H +#include + #include "go-linemap.h" #include "escape.h" @@ -2329,6 +2331,16 @@ class Struct_type : public Type void write_equal_function(Gogo*, Named_type*); + // Whether we can write this type to a C header file, to implement + // -fgo-c-header. + bool + can_write_to_c_header(std::vector*, + std::vector*) const; + + // Write this type to a C header file, to implement -fgo-c-header. + void + write_to_c_header(std::ostream&) const; + protected: int do_traverse(Traverse*); @@ -2364,6 +2376,14 @@ class Struct_type : public Type do_export(Export*) const; private: + bool + can_write_type_to_c_header(const Type*, + std::vector*, + std::vector*) const; + + void + write_field_to_c_header(std::ostream&, const std::string&, const Type*) const; + // Used to merge method sets of identical unnamed structs. typedef Unordered_map_hash(Struct_type*, Struct_type*, Type_hash_identical, Type_identical) Identical_structs; diff --git a/gcc/go/lang.opt b/gcc/go/lang.opt index 7de9386f92a..503fc7dfd23 100644 --- a/gcc/go/lang.opt +++ b/gcc/go/lang.opt @@ -37,6 +37,10 @@ Wall Go ; Documented in c.opt +fgo-c-header= +Go Joined RejectNegative +-fgo-c-header= Write Go struct definitions to file as C code. + fgo-check-divide-zero Go Var(go_check_divide_zero) Init(1) Add explicit checks for division by zero. @@ -45,6 +49,10 @@ fgo-check-divide-overflow Go Var(go_check_divide_overflow) Init(1) Add explicit checks for division overflow in INT_MIN / -1. +fgo-compiling-runtime +Go Var(go_compiling_runtime) Init(0) +Apply special rules for compiling runtime package. + fgo-dump- Go Joined RejectNegative -fgo-dump- Dump Go frontend internal information. -- 2.30.2