From 4880b994d63ffb014a5a547239e9cd8402bb014a Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 24 Jan 2018 23:50:09 +0000 Subject: [PATCH] compiler: rationalize external symbol names Encode all external symbol names using only ASCII alphanumeric characters, underscore, and dot. Use a scheme that can be reliably demangled to a somewhat readable version as described in the long comment in names.cc. A minor cleanup discovered during this was that we were treating function types as different if one had a NULL parameters_ field and another has a non-NULL parameters_ field that has no parameters. This worked because we mangled them slightly differently. We now mangle them the same, so we treat them as equal, as we should anyhow. Reviewed-on: https://go-review.googlesource.com/89555 * go.go-torture/execute/names-1.go: New test. From-SVN: r257033 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/escape.cc | 53 +- gcc/go/gofrontend/expressions.cc | 39 +- gcc/go/gofrontend/go-encode-id.cc | 84 +- gcc/go/gofrontend/go-encode-id.h | 14 +- gcc/go/gofrontend/gogo.cc | 10 +- gcc/go/gofrontend/gogo.h | 29 +- gcc/go/gofrontend/lex.cc | 9 +- gcc/go/gofrontend/names.cc | 778 ++++++++++++------ gcc/go/gofrontend/statements.cc | 2 +- gcc/go/gofrontend/types.cc | 32 +- gcc/testsuite/ChangeLog | 4 + .../go.go-torture/execute/names-1.go | 195 +++++ libgo/go/runtime/crash_test.go | 4 +- libgo/go/runtime/panic.go | 2 +- libgo/go/runtime/pprof/pprof_test.go | 2 +- libgo/go/syscall/wait.c | 18 +- libgo/runtime/go-callers.c | 11 +- libgo/runtime/go-unsafe-pointer.c | 12 +- 19 files changed, 924 insertions(+), 376 deletions(-) create mode 100644 gcc/testsuite/go.go-torture/execute/names-1.go diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index a100cab84fe..0430961f7bc 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -3488a401e50835de5de5c4f153772ac2798d0e71 +0bbc03f81c862fb35be3edee9824698a7892a17e 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/escape.cc b/gcc/go/gofrontend/escape.cc index 4f95945e230..e15e85f3082 100644 --- a/gcc/go/gofrontend/escape.cc +++ b/gcc/go/gofrontend/escape.cc @@ -686,42 +686,33 @@ debug_function_name(Named_object* fn) if (!fn->is_function()) return Gogo::unpack_hidden_name(fn->name()); - if (fn->func_value()->enclosing() == NULL) + + std::string fnname = Gogo::unpack_hidden_name(fn->name()); + if (fn->func_value()->is_method()) { - std::string fnname = Gogo::unpack_hidden_name(fn->name()); - if (fn->func_value()->is_method()) - { - // Methods in gc compiler are named "T.m" or "(*T).m" where - // T is the receiver type. Add the receiver here. - Type* rt = fn->func_value()->type()->receiver()->type(); - switch (rt->classification()) - { - case Type::TYPE_NAMED: - fnname = rt->named_type()->name() + "." + fnname; - break; + // Methods in gc compiler are named "T.m" or "(*T).m" where + // T is the receiver type. Add the receiver here. + Type* rt = fn->func_value()->type()->receiver()->type(); + switch (rt->classification()) + { + case Type::TYPE_NAMED: + fnname = rt->named_type()->name() + "." + fnname; + break; - case Type::TYPE_POINTER: - { - Named_type* nt = rt->points_to()->named_type(); - if (nt != NULL) - fnname = "(*" + nt->name() + ")." + fnname; - break; - } + case Type::TYPE_POINTER: + { + Named_type* nt = rt->points_to()->named_type(); + if (nt != NULL) + fnname = "(*" + nt->name() + ")." + fnname; + break; + } - default: - break; - } - } - return fnname; + default: + break; + } } - // Closures are named ".$nested#" where # is a global counter. Add outer - // function name for better distinguishing. This is also closer to what - // gc compiler prints, "outer.func#". - Named_object* enclosing = fn->func_value()->enclosing(); - std::string name = Gogo::unpack_hidden_name(fn->name()); - std::string outer_name = Gogo::unpack_hidden_name(enclosing->name()); - return outer_name + "." + name; + return fnname; } // Return the name of the current function. diff --git a/gcc/go/gofrontend/expressions.cc b/gcc/go/gofrontend/expressions.cc index 47be82f823e..0701cb04518 100644 --- a/gcc/go/gofrontend/expressions.cc +++ b/gcc/go/gofrontend/expressions.cc @@ -1310,6 +1310,16 @@ Func_descriptor_expression::do_get_backend(Translate_context* context) && Linemap::is_predeclared_location(no->location())) is_descriptor = true; + // The runtime package implements some functions defined in the + // syscall package. Let the syscall package define the descriptor + // in this case. + if (gogo->compiling_runtime() + && gogo->package_name() == "runtime" + && no->is_function() + && !no->func_value()->asm_name().empty() + && no->func_value()->asm_name().compare(0, 8, "syscall.") == 0) + is_descriptor = true; + Btype* btype = this->type()->get_backend(gogo); Bvariable* bvar; @@ -6845,7 +6855,8 @@ Bound_method_expression::create_thunk(Gogo* gogo, const Method* method, if (orig_fntype == NULL || !orig_fntype->is_method()) { - ins.first->second = Named_object::make_erroneous_name(Gogo::thunk_name()); + ins.first->second = + Named_object::make_erroneous_name(gogo->thunk_name()); return ins.first->second; } @@ -6853,8 +6864,8 @@ Bound_method_expression::create_thunk(Gogo* gogo, const Method* method, // The type here is wrong--it should be the C function type. But it // doesn't really matter. Type* vt = Type::make_pointer_type(Type::make_void_type()); - sfl->push_back(Struct_field(Typed_identifier("fn.0", vt, loc))); - sfl->push_back(Struct_field(Typed_identifier("val.1", + sfl->push_back(Struct_field(Typed_identifier("fn", vt, loc))); + sfl->push_back(Struct_field(Typed_identifier("val", orig_fntype->receiver()->type(), loc))); Struct_type* st = Type::make_struct_type(sfl, loc); @@ -6863,7 +6874,7 @@ Bound_method_expression::create_thunk(Gogo* gogo, const Method* method, Function_type* new_fntype = orig_fntype->copy_with_names(); - std::string thunk_name = Gogo::thunk_name(); + std::string thunk_name = gogo->thunk_name(); Named_object* new_no = gogo->start_function(thunk_name, new_fntype, false, loc); @@ -7009,10 +7020,10 @@ Bound_method_expression::do_flatten(Gogo* gogo, Named_object*, // away with this. Struct_field_list* fields = new Struct_field_list(); - fields->push_back(Struct_field(Typed_identifier("fn.0", + fields->push_back(Struct_field(Typed_identifier("fn", thunk->func_value()->type(), loc))); - fields->push_back(Struct_field(Typed_identifier("val.1", val->type(), loc))); + fields->push_back(Struct_field(Typed_identifier("val", val->type(), loc))); Struct_type* st = Type::make_struct_type(fields, loc); st->set_is_struct_incomparable(); @@ -11889,25 +11900,25 @@ Interface_field_reference_expression::create_thunk(Gogo* gogo, const Typed_identifier* method_id = type->find_method(name); if (method_id == NULL) - return Named_object::make_erroneous_name(Gogo::thunk_name()); + return Named_object::make_erroneous_name(gogo->thunk_name()); Function_type* orig_fntype = method_id->type()->function_type(); if (orig_fntype == NULL) - return Named_object::make_erroneous_name(Gogo::thunk_name()); + return Named_object::make_erroneous_name(gogo->thunk_name()); Struct_field_list* sfl = new Struct_field_list(); // The type here is wrong--it should be the C function type. But it // doesn't really matter. Type* vt = Type::make_pointer_type(Type::make_void_type()); - sfl->push_back(Struct_field(Typed_identifier("fn.0", vt, loc))); - sfl->push_back(Struct_field(Typed_identifier("val.1", type, loc))); + sfl->push_back(Struct_field(Typed_identifier("fn", vt, loc))); + sfl->push_back(Struct_field(Typed_identifier("val", type, loc))); Struct_type* st = Type::make_struct_type(sfl, loc); st->set_is_struct_incomparable(); Type* closure_type = Type::make_pointer_type(st); Function_type* new_fntype = orig_fntype->copy_with_names(); - std::string thunk_name = Gogo::thunk_name(); + std::string thunk_name = gogo->thunk_name(); Named_object* new_no = gogo->start_function(thunk_name, new_fntype, false, loc); @@ -11995,10 +12006,10 @@ Interface_field_reference_expression::do_get_backend(Translate_context* context) Location loc = this->location(); Struct_field_list* fields = new Struct_field_list(); - fields->push_back(Struct_field(Typed_identifier("fn.0", + fields->push_back(Struct_field(Typed_identifier("fn", thunk->func_value()->type(), loc))); - fields->push_back(Struct_field(Typed_identifier("val.1", + fields->push_back(Struct_field(Typed_identifier("val", this->expr_->type(), loc))); Struct_type* st = Type::make_struct_type(fields, loc); @@ -12247,7 +12258,7 @@ Selector_expression::lower_method_expression(Gogo* gogo) return f; } - Named_object* no = gogo->start_function(Gogo::thunk_name(), fntype, false, + Named_object* no = gogo->start_function(gogo->thunk_name(), fntype, false, location); Named_object* vno = gogo->lookup(receiver_name, NULL); diff --git a/gcc/go/gofrontend/go-encode-id.cc b/gcc/go/gofrontend/go-encode-id.cc index 978f20823d6..ec5c37fc40e 100644 --- a/gcc/go/gofrontend/go-encode-id.cc +++ b/gcc/go/gofrontend/go-encode-id.cc @@ -4,11 +4,16 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +#include "go-system.h" + +#include "gogo.h" #include "go-location.h" #include "go-linemap.h" #include "go-encode-id.h" +#include "lex.h" -// Return whether the character c is OK to use in the assembler. +// Return whether the character c is OK to use in the assembler. We +// only permit ASCII alphanumeric characters, underscore, and dot. static bool char_needs_encoding(char c) @@ -27,7 +32,7 @@ char_needs_encoding(char c) case 'y': case 'z': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': - case '_': case '.': case '$': case '/': + case '_': case '.': return false; default: return true; @@ -77,11 +82,25 @@ fetch_utf8_char(const char* p, unsigned int* pc) return len; } -// Encode an identifier using ASCII characters. +// Encode an identifier using ASCII characters. The encoding is +// described in detail near the end of the long comment at the start +// of names.cc. Short version: translate all non-ASCII-alphanumeric +// characters into ..uXXXX or ..UXXXXXXXX. std::string go_encode_id(const std::string &id) { + if (Lex::is_invalid_identifier(id)) + { + go_assert(saw_errors()); + return id; + } + + // The encoding is only unambiguous if the input string does not + // contain ..u or ..U. + go_assert(id.find("..u") == std::string::npos); + go_assert(id.find("..U") == std::string::npos); + std::string ret; const char* p = id.c_str(); const char* pend = p + id.length(); @@ -89,16 +108,25 @@ go_encode_id(const std::string &id) { unsigned int c; size_t len = fetch_utf8_char(p, &c); - if (len == 1 && !char_needs_encoding(c)) - ret += c; + if (len == 1) + { + // At this point we should only be seeing alphanumerics or + // underscore or dot. + go_assert(!char_needs_encoding(c)); + ret += c; + } + else if (c < 0x10000) + { + char buf[16]; + snprintf(buf, sizeof buf, "..u%04x", c); + ret += buf; + } else - { - ret += "$U"; - char buf[30]; - snprintf(buf, sizeof buf, "%x", c); - ret += buf; - ret += "$"; - } + { + char buf[16]; + snprintf(buf, sizeof buf, "..U%08x", c); + ret += buf; + } p += len; } return ret; @@ -111,3 +139,35 @@ go_selectively_encode_id(const std::string &id) return go_encode_id(id); return std::string(); } + +// Encode a struct field tag. This is only used when we need to +// create a type descriptor for an anonymous struct type with field +// tags. This mangling is applied before go_encode_id. We skip +// alphanumerics and underscore, replace every other single byte +// character with .xNN, and leave larger UTF-8 characters for +// go_encode_id. + +std::string +go_mangle_struct_tag(const std::string& tag) +{ + std::string ret; + const char* p = tag.c_str(); + const char* pend = p + tag.length(); + while (p < pend) + { + unsigned int c; + size_t len = fetch_utf8_char(p, &c); + if (len > 1) + ret.append(p, len); + else if (!char_needs_encoding(c) && c != '.') + ret += c; + else + { + char buf[16]; + snprintf(buf, sizeof buf, ".x%02x", c); + ret += buf; + } + p += len; + } + return ret; +} diff --git a/gcc/go/gofrontend/go-encode-id.h b/gcc/go/gofrontend/go-encode-id.h index b95d97dd1ba..ec81b63a182 100644 --- a/gcc/go/gofrontend/go-encode-id.h +++ b/gcc/go/gofrontend/go-encode-id.h @@ -9,10 +9,9 @@ #include "backend.h" -// Given an identifier corresponding to a function or variable, -// this helper returns TRUE if the identifier needs special -// encoding to be used as an ASM name (symbol), FALSE if the name -// is OK as is. +// Given an identifier that will appear in assembly code, this helper +// returns TRUE if the identifier needs special encoding to be used as +// an ASM name, FALSE if the name is OK as is. extern bool go_id_needs_encoding(const std::string& str); @@ -22,9 +21,12 @@ extern std::string go_encode_id(const std::string &id); // Returns the empty string if the specified name needs encoding, -// otherwise invokes go_encode_id() on the name and returns the -// result. +// otherwise invokes go_encode_id on the name and returns the result. extern std::string go_selectively_encode_id(const std::string &id); +// Encodes a struct tag that appears in a type literal encoding. +extern std::string +go_mangle_struct_tag(const std::string& tag); + #endif // !defined(GO_ENCODE_ID_H) diff --git a/gcc/go/gofrontend/gogo.cc b/gcc/go/gofrontend/gogo.cc index e8bc7aea9bf..83be6176ae7 100644 --- a/gcc/go/gofrontend/gogo.cc +++ b/gcc/go/gofrontend/gogo.cc @@ -1758,7 +1758,7 @@ Gogo::start_function(const std::string& name, Function_type* type, else { // Invent a name for a nested function. - nested_name = this->nested_function_name(); + nested_name = this->nested_function_name(enclosing); pname = &nested_name; } @@ -4821,9 +4821,9 @@ Function::Function(Function_type* type, Named_object* enclosing, Block* block, : type_(type), enclosing_(enclosing), results_(NULL), closure_var_(NULL), block_(block), location_(location), labels_(), local_type_count_(0), descriptor_(NULL), fndecl_(NULL), defer_stack_(NULL), - pragmas_(0), is_sink_(false), results_are_named_(false), - is_unnamed_type_stub_method_(false), calls_recover_(false), - is_recover_thunk_(false), has_recover_thunk_(false), + pragmas_(0), nested_functions_(0), is_sink_(false), + results_are_named_(false), is_unnamed_type_stub_method_(false), + calls_recover_(false), is_recover_thunk_(false), has_recover_thunk_(false), calls_defer_retaddr_(false), is_type_specific_function_(false), in_unique_section_(false) { @@ -4948,7 +4948,7 @@ Function::set_closure_type() // The first field of a closure is always a pointer to the function // code. Type* voidptr_type = Type::make_pointer_type(Type::make_void_type()); - st->push_field(Struct_field(Typed_identifier(".$f", voidptr_type, + st->push_field(Struct_field(Typed_identifier(".f", voidptr_type, this->location_))); unsigned int index = 1; diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index f7f8d602bd1..5135042763d 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -801,7 +801,7 @@ class Gogo // Return the name to use for a generated stub method. std::string - stub_method_name(const std::string& method_name); + stub_method_name(const Package*, const std::string& method_name); // Return the names of the hash and equality functions for TYPE. void @@ -826,7 +826,7 @@ class Gogo // Return a name to use for a thunk function. A thunk function is // one we create during the compilation, for a go statement or a // defer statement or a method expression. - static std::string + std::string thunk_name(); // Return whether an object is a thunk. @@ -838,8 +838,8 @@ class Gogo init_function_name(); // Return the name to use for a nested function. - static std::string - nested_function_name(); + std::string + nested_function_name(Named_object* enclosing); // Return the name to use for a sink funciton. std::string @@ -887,6 +887,12 @@ class Gogo std::string interface_method_table_name(Interface_type*, Type*, bool is_pointer); + // Return whether NAME is a special name that can not be passed to + // unpack_hidden_name. This is needed because various special names + // use "..SUFFIX", but unpack_hidden_name just looks for '.'. + static bool + is_special_name(const std::string& name); + private: // During parsing, we keep a stack of functions. Each function on // the stack is one that we are currently parsing. For each @@ -1233,6 +1239,11 @@ class Function results_are_named() const { return this->results_are_named_; } + // Return the assembler name. + const std::string& + asm_name() const + { return this->asm_name_; } + // Set the assembler name. void set_asm_name(const std::string& asm_name) @@ -1250,6 +1261,14 @@ class Function this->pragmas_ = pragmas; } + // Return the index to use for a nested function. + unsigned int + next_nested_function_index() + { + ++this->nested_functions_; + return this->nested_functions_; + } + // Whether this method should not be included in the type // descriptor. bool @@ -1510,6 +1529,8 @@ class Function Temporary_statement* defer_stack_; // Pragmas for this function. This is a set of GOPRAGMA bits. unsigned int pragmas_; + // Number of nested functions defined within this function. + unsigned int nested_functions_; // True if this function is sink-named. No code is generated. bool is_sink_ : 1; // True if the result variables are named. diff --git a/gcc/go/gofrontend/lex.cc b/gcc/go/gofrontend/lex.cc index e9f11c2c1c0..560f5f9d34d 100644 --- a/gcc/go/gofrontend/lex.cc +++ b/gcc/go/gofrontend/lex.cc @@ -2761,16 +2761,19 @@ bool Lex::is_exported_name(const std::string& name) { unsigned char c = name[0]; - if (c != '$') + if (c != '.') return c >= 'A' && c <= 'Z'; else { const char* p = name.data(); size_t len = name.length(); - if (len < 2 || p[1] != 'U') + if (len < 4 || p[1] != '.' || (p[2] != 'u' && p[2] != 'U')) return false; unsigned int ci = 0; - for (size_t i = 2; i < len && p[i] != '$'; ++i) + size_t want = (p[2] == 'u' ? 4 : 8); + if (len < want + 3) + return false; + for (size_t i = 3; i < want; ++i) { c = p[i]; if (!Lex::is_hex_digit(c)) diff --git a/gcc/go/gofrontend/names.cc b/gcc/go/gofrontend/names.cc index 5feea124443..eb18f81cc16 100644 --- a/gcc/go/gofrontend/names.cc +++ b/gcc/go/gofrontend/names.cc @@ -15,6 +15,190 @@ // assembly code. This is not used for names that appear only in the // debug info. +// Our external names contain only ASCII alphanumeric characters, +// underscore, and dot. (According to the GCC sources, dot is not +// permitted in assembler symbols on VxWorks and MMIX. We will not +// support those systems.) Go names can not contain dot, so we rely +// on using dot to encode Unicode characters, and to separate Go +// symbols by package, and so forth. We assume that none of the +// non-Go symbols in the final link will contain a dot, so we don't +// worry about conflicts. +// +// We first describe the basic symbol names, used to represent Go +// functions and variables. These never start with a dot, never end +// with a dot, never contain two consecutive dots, and never contain a +// dot followed by a digit. +// +// The external name for a normal Go symbol NAME, a function or +// variable, is simply "PKGPATH.NAME". Note that NAME is not the +// packed form used for the "hidden" name internally in the compiler; +// it is the name that appears in the source code. PKGPATH is the +// -fgo-pkgpath option as adjusted by Gogo::pkgpath_for_symbol. Note +// that PKGPATH can not contain a dot and neither can NAME. Also, +// NAME may not begin with a digit. NAME may require further encoding +// for non-ASCII characters as described below, but until that +// encoding these symbols contain exactly one dot, and they do not +// start with a dot. +// +// The external name for a method NAME for a named type TYPE is +// "PKGPATH.TYPE.NAME". Unlike the gc compiler, the external name +// does not indicate whether this is a pointer method or a value +// method; a named type can not have both a pointer and value method +// with the same name, so there is no ambiguity. PKGPATH is the +// package path of the package in which TYPE is defined. Here none of +// PKGPATH, TYPE, or NAME can be empty or contain a dot, and neither +// TYPE nor NAME may begin with a digit. Before encoding these names +// contain exactly two dots, not consecutive, and they do not start +// with a dot. +// +// It's uncommon, but the use of type literals with embedded fields +// can cause us to have methods on unnamed types. The external names +// for these are also PKGPATH.TYPE.NAME, where TYPE is an +// approximately readable version of the type literal, described +// below. As the type literal encoding always contains multiple dots, +// these names always contain more than two dots. Although the type +// literal encoding contains dots, neither PKGPATH nor NAME can +// contain a dot, and neither TYPE nor NAME can begin with a digit. +// The effect is that PKGPATH is always the portion of the name before +// the first dot and NAME is always the portion after the last dot. +// There is no ambiguity as long as encoded type literals are +// unambiguous. +// +// Also uncommon is an external name that must refer to a named type +// defined within a function. While such a type can not have methods +// itself, it can pick up embedded methods, and those methods need +// names. These are treated as a kind of type literal written as, +// before type literal encoding, FNNAME.TYPENAME(INDEX) or, for a +// method, TYPE.MNAME.TYPENAME(INDEX). INDEX is the index of that +// named type within the function, as a single function can have +// multiple types with the same name. This is unambiguous as +// parentheses can not appear in a type literal in this form (they can +// only appear in interface method declarations). +// +// That is the end of the list of basic names. The remaining names +// exist for special purposes, and are differentiated from the basic +// names by containing two consecutive dots. +// +// The hash function for a type is treated as a method whose name is +// ".hash". That is, the method name begins with a dot. The effect +// is that there will be two consecutive dots in the name; the name +// will always end with "..hash". +// +// Similarly the equality function for a type is treated as a method +// whose name is ".eq". +// +// The function descriptor for a function is the same as the name of +// the function with an added suffix "..f". +// +// A thunk for a go or defer statement is treated as a function whose +// name is ".thunkNN" where NN is a sequence of digits (these +// functions are never globally visible). Thus the final name of a +// thunk will be PKGPATH..thunkNN. +// +// An init function is treated as a function whose name is ".initNN" +// where NN is a sequence of digits (these functions are never +// globally visible). Thus the final name of an init function will be +// PKGPATH..initNN. +// +// A nested function is given the name of outermost enclosing function +// or method with an added suffix "..funcNN" where NN is a sequence of +// digits. Note that the function descriptor of a nested function, if +// needed, will end with "..funcNN..f". +// +// A recover thunk is the same as the name of the function with an +// added suffix "..r". +// +// The name of a type descriptor for a named type is PKGPATH.TYPE..d. +// +// The name of a type descriptor for an unnamed type is type..TYPE. +// That is, the string "type.." followed by the type literal encoding. +// These names are common symbols, in the linker's sense of the word +// common: in the final executable there is only one instance of the +// type descriptor for a given unnamed type. The type literal +// encoding can never start with a digit or with 'u' or 'U'. +// +// The name of the GC symbol for a named type is PKGPATH.TYPE..g. +// +// The name of the GC symbol for an unnamed type is typeg..TYPE. +// These are common symbols. +// +// The name of a ptrmask symbol is gcbits..B32 where B32 is an +// encoding of the ptrmask bits using only ASCII letters without 'u' +// or 'U'. These are common symbols. +// +// An interface method table for assigning the non-interface type TYPE +// to the interface type ITYPE is named imt..ITYPE..TYPE. If ITYPE or +// TYPE is a named type, they are written as PKGPATH.TYPE. Otherwise +// they are written as a type literal. An interface method table for +// a pointer method set uses pimt instead of imt. +// +// The names of composite literal initializers, including the GC root +// variable, are not referenced. They must not conflict with any C +// language names, but the names are otherwise unimportant. They are +// named "go..CNN" where NN is a sequence of digits. The names do not +// include the PKGPATH. +// +// The map zero value, a common symbol that represents the zero value +// of a map, is named simply "go..zerovalue". The name does not +// include the PKGPATH. +// +// The import function for the main package is referenced by C code, +// and is named __go_init_main. For other packages it is +// PKGPATH..import. +// +// The type literal encoding is essentially a single line version of +// the type literal, such as "struct { pkgpath.i int; J int }". In +// this representation unexported names use their pkgpath, exported +// names omit it. +// +// The type literal encoding is not quite valid Go, as some aspects of +// compiler generated types can not be represented. For example, +// incomparable struct types have an extra field "{x}". Struct tags +// are quoted inside curly braces, rather than introduce an encoding +// for quotes. Struct tags can contain any character, so any single +// byte Unicode character that is not alphanumeric or underscore is +// replaced with .xNN where NN is the hex encoding. +// +// There is a simple encoding for glue characters in type literals: +// .0 - ' ' +// .1 - '*' +// .2 - ';' +// .3 - ',' +// .4 - '{' +// .5 - '}' +// .6 - '[' +// .7 - ']' +// .8 - '(' +// .9 - ')' +// This is unambiguous as, although the type literal can contain a dot +// as shown above, those dots are always followed by a name and names +// can not begin with a digit. A dot is always followed by a name or +// a digit, and a type literal can neither start nor end with a dot, +// so this never introduces consecutive dots. +// +// Struct tags can contain any character, so they need special +// treatment. Alphanumerics, underscores, and Unicode characters that +// require more than a single byte are left alone (Unicode characters +// will be encoded later, as described below). Other single bytes +// characters are replace with .xNN where NN is the hex encoding. +// +// Since Go identifiers can contain Unicode characters, we must encode +// them into ASCII. We do this last, after the name is generated as +// described above and after type literals are encoded. To make the +// encoding unambiguous, we introduce it with two consecutive dots. +// This is followed by the letter u and four hex digits or the letter +// U and eight digits, just as in the language only using ..u and ..U +// instead of \u and \U. Since before this encoding names can never +// contain consecutive dots followed by 'u' or 'U', and after this +// encoding "..u" and "..U" are followed by a known number of +// characters, this is unambiguous. +// +// Demangling these names is straightforward: +// - replace ..uXXXX with a unicode character +// - replace ..UXXXXXXXX with a unicode character +// - replace .D, where D is a digit, with the character from the above +// That will get you as close as possible to a readable name. + // Return the assembler name to use for an exported function, a // method, or a function/method declaration. This is not called if // the function has been given an explicit name via a magic //extern @@ -27,30 +211,20 @@ std::string Gogo::function_asm_name(const std::string& go_name, const Package* package, const Type* rtype) { - std::string ret = (package == NULL - ? this->pkgpath_symbol() - : package->pkgpath_symbol()); - - if (rtype != NULL - && Gogo::is_hidden_name(go_name) - && Gogo::hidden_name_pkgpath(go_name) != this->pkgpath()) - { - // This is a method created for an unexported method of an - // imported embedded type. Use the pkgpath of the imported - // package. - std::string p = Gogo::hidden_name_pkgpath(go_name); - ret = this->pkgpath_symbol_for_package(p); - } - - ret.append(1, '.'); - ret.append(Gogo::unpack_hidden_name(go_name)); - + std::string ret; if (rtype != NULL) - { - ret.append(1, '.'); - ret.append(rtype->mangled_name(this)); - } - + ret = rtype->mangled_name(this); + else if (package == NULL) + ret = this->pkgpath_symbol(); + else + ret = package->pkgpath_symbol(); + ret.push_back('.'); + // Check for special names that will break if we use + // Gogo::unpack_hidden_name. + if (Gogo::is_special_name(go_name)) + ret.append(go_name); + else + ret.append(Gogo::unpack_hidden_name(go_name)); return go_encode_id(ret); } @@ -60,41 +234,45 @@ Gogo::function_asm_name(const std::string& go_name, const Package* package, std::string Gogo::function_descriptor_name(Named_object* no) { - std::string var_name; - if (no->is_function_declaration() - && !no->func_declaration_value()->asm_name().empty() - && Linemap::is_predeclared_location(no->location())) - { - if (no->func_declaration_value()->asm_name().substr(0, 8) != "runtime.") - var_name = no->func_declaration_value()->asm_name() + "_descriptor"; - else - var_name = no->func_declaration_value()->asm_name() + "$descriptor"; - } - else - { - if (no->package() == NULL) - var_name = this->pkgpath_symbol(); - else - var_name = no->package()->pkgpath_symbol(); - var_name.push_back('.'); - var_name.append(Gogo::unpack_hidden_name(no->name())); - var_name.append("$descriptor"); - } - return var_name; + if (no->is_function() && !no->func_value()->asm_name().empty()) + return no->func_value()->asm_name() + "..f"; + else if (no->is_function_declaration() + && !no->func_declaration_value()->asm_name().empty()) + return no->func_declaration_value()->asm_name() + "..f"; + std::string ret = this->function_asm_name(no->name(), no->package(), NULL); + ret.append("..f"); + return ret; } // Return the name to use for a generated stub method. MNAME is the -// method name. These functions are globally visible. Note that this -// is the function name that corresponds to the name used for the -// method in Go source code, if this stub method were written in Go. -// The assembler name will be generated by Gogo::function_asm_name, -// and because this is a method that name will include the receiver -// type. +// method name. PACKAGE is the package where the type that needs this +// stub method is defined. These functions are globally visible. +// Note that this is the function name that corresponds to the name +// used for the method in Go source code, if this stub method were +// written in Go. The assembler name will be generated by +// Gogo::function_asm_name, and because this is a method that name +// will include the receiver type. std::string -Gogo::stub_method_name(const std::string& mname) +Gogo::stub_method_name(const Package* package, const std::string& mname) { - return mname + "$stub"; + if (!Gogo::is_hidden_name(mname)) + return mname + "..stub"; + + const std::string& ppkgpath(package == NULL + ? this->pkgpath() + : package->pkgpath()); + std::string mpkgpath = Gogo::hidden_name_pkgpath(mname); + if (mpkgpath == ppkgpath) + return Gogo::unpack_hidden_name(mname) + "..stub"; + + // We are creating a stub method for an unexported method of an + // imported embedded type. We need to disambiguate the method name. + std::string ret = this->pkgpath_symbol_for_package(mpkgpath); + ret.push_back('.'); + ret.append(Gogo::unpack_hidden_name(mname)); + ret.append("..stub"); + return ret; } // Return the names of the hash and equality functions for TYPE. If @@ -106,48 +284,12 @@ Gogo::specific_type_function_names(const Type* type, const Named_type* name, std::string *hash_name, std::string *equal_name) { - std::string base_name; - if (name == NULL) - { - // Mangled names can have '.' if they happen to refer to named - // types in some way. That's fine if this is simply a named - // type, but otherwise it will confuse the code that builds - // function identifiers. Remove '.' when necessary. - base_name = type->mangled_name(this); - size_t i; - while ((i = base_name.find('.')) != std::string::npos) - base_name[i] = '$'; - base_name = this->pack_hidden_name(base_name, false); - } - else - { - // This name is already hidden or not as appropriate. - base_name = name->name(); - unsigned int index; - const Named_object* in_function = name->in_function(&index); - if (in_function != NULL) - { - base_name.append(1, '$'); - const Typed_identifier* rcvr = - in_function->func_value()->type()->receiver(); - if (rcvr != NULL) - { - Named_type* rcvr_type = rcvr->type()->deref()->named_type(); - base_name.append(Gogo::unpack_hidden_name(rcvr_type->name())); - base_name.append(1, '$'); - } - base_name.append(Gogo::unpack_hidden_name(in_function->name())); - if (index > 0) - { - char buf[30]; - snprintf(buf, sizeof buf, "%u", index); - base_name += '$'; - base_name += buf; - } - } - } - *hash_name = base_name + "$hash"; - *equal_name = base_name + "$equal"; + const Type* rtype = type; + if (name != NULL) + rtype = name; + std::string tname = rtype->mangled_name(this); + *hash_name = tname + "..hash"; + *equal_name = tname + "..eq"; } // Return the assembler name to use for a global variable. GO_NAME is @@ -158,10 +300,12 @@ Gogo::specific_type_function_names(const Type* type, const Named_type* name, std::string Gogo::global_var_asm_name(const std::string& go_name, const Package* package) { - std::string ret = (package != NULL - ? package->pkgpath_symbol() - : this->pkgpath_symbol()); - ret.push_back('.'); + std::string ret; + if (package == NULL) + ret = this->pkgpath_symbol(); + else + ret = package->pkgpath_symbol(); + ret.append(1, '.'); ret.append(Gogo::unpack_hidden_name(go_name)); return go_encode_id(ret); } @@ -172,9 +316,10 @@ Gogo::global_var_asm_name(const std::string& go_name, const Package* package) std::string Gogo::erroneous_name() { + go_assert(saw_errors()); static int erroneous_count; char name[50]; - snprintf(name, sizeof name, "$erroneous%d", erroneous_count); + snprintf(name, sizeof name, ".erroneous%d", erroneous_count); ++erroneous_count; return name; } @@ -184,7 +329,7 @@ Gogo::erroneous_name() bool Gogo::is_erroneous_name(const std::string& name) { - return name.compare(0, 10, "$erroneous") == 0; + return name.compare(0, 10, ".erroneous") == 0; } // Return a name for a thunk object. @@ -194,9 +339,10 @@ Gogo::thunk_name() { static int thunk_count; char thunk_name[50]; - snprintf(thunk_name, sizeof thunk_name, "$thunk%d", thunk_count); + snprintf(thunk_name, sizeof thunk_name, "..thunk%d", thunk_count); ++thunk_count; - return thunk_name; + std::string ret = this->pkgpath_symbol(); + return ret + thunk_name; } // Return whether a function is a thunk. @@ -204,7 +350,14 @@ Gogo::thunk_name() bool Gogo::is_thunk(const Named_object* no) { - return no->name().compare(0, 6, "$thunk") == 0; + const std::string& name(no->name()); + size_t i = name.find("..thunk"); + if (i == std::string::npos) + return false; + for (i += 7; i < name.size(); ++i) + if (name[i] < '0' || name[i] > '9') + return false; + return true; } // Return the name to use for an init function. There can be multiple @@ -215,21 +368,50 @@ Gogo::init_function_name() { static int init_count; char buf[30]; - snprintf(buf, sizeof buf, ".$init%d", init_count); + snprintf(buf, sizeof buf, "..init%d", init_count); ++init_count; - return buf; + std::string ret = this->pkgpath_symbol(); + return ret + buf; } // Return the name to use for a nested function. std::string -Gogo::nested_function_name() +Gogo::nested_function_name(Named_object* enclosing) { - static int nested_count; + std::string prefix; + unsigned int index; + if (enclosing == NULL) + { + // A function literal at top level, as in + // var f = func() {} + static unsigned int toplevel_index; + ++toplevel_index; + index = toplevel_index; + prefix = ".go"; + } + else + { + while (true) + { + Named_object* parent = enclosing->func_value()->enclosing(); + if (parent == NULL) + break; + enclosing = parent; + } + const Typed_identifier* rcvr = + enclosing->func_value()->type()->receiver(); + if (rcvr != NULL) + { + prefix = rcvr->type()->mangled_name(this); + prefix.push_back('.'); + } + prefix.append(Gogo::unpack_hidden_name(enclosing->name())); + index = enclosing->func_value()->next_nested_function_index(); + } char buf[30]; - snprintf(buf, sizeof buf, ".$nested%d", nested_count); - ++nested_count; - return buf; + snprintf(buf, sizeof buf, "..func%u", index); + return prefix + buf; } // Return the name to use for a sink function, a function whose name @@ -241,7 +423,7 @@ Gogo::sink_function_name() { static int sink_count; char buf[30]; - snprintf(buf, sizeof buf, ".$sink%d", sink_count); + snprintf(buf, sizeof buf, ".sink%d", sink_count); ++sink_count; return buf; } @@ -255,7 +437,7 @@ Gogo::redefined_function_name() { static int redefinition_count; char buf[30]; - snprintf(buf, sizeof buf, ".$redefined%d", redefinition_count); + snprintf(buf, sizeof buf, ".redefined%d", redefinition_count); ++redefinition_count; return buf; } @@ -266,13 +448,17 @@ Gogo::redefined_function_name() std::string Gogo::recover_thunk_name(const std::string& name, const Type* rtype) { - std::string ret(name); + std::string ret; if (rtype != NULL) { - ret.push_back('$'); - ret.append(rtype->mangled_name(this)); + ret = rtype->mangled_name(this); + ret.append(1, '.'); } - ret.append("$recover"); + if (Gogo::is_special_name(name)) + ret.append(name); + else + ret.append(Gogo::unpack_hidden_name(name)); + ret.append("..r"); return ret; } @@ -284,7 +470,7 @@ Gogo::recover_thunk_name(const std::string& name, const Type* rtype) std::string Gogo::gc_root_name() { - return "gc0"; + return "go..C0"; } // Return the name to use for a composite literal or string @@ -296,8 +482,8 @@ Gogo::initializer_name() { static unsigned int counter; char buf[30]; - snprintf(buf, sizeof buf, "C%u", counter); ++counter; + snprintf(buf, sizeof buf, "go..C%u", counter); return buf; } @@ -307,7 +493,7 @@ Gogo::initializer_name() std::string Gogo::map_zero_value_name() { - return "go$zerovalue"; + return "go..zerovalue"; } // Return the name to use for the import control function. @@ -343,11 +529,50 @@ Type::mangled_name(Gogo* gogo) const { std::string ret; - // The do_mangled_name virtual function should set RET to the - // mangled name. For a composite type it should append a code for - // the composition and then call do_mangled_name on the components. + // The do_mangled_name virtual function will set RET to the mangled + // name before glue character mapping. this->do_mangled_name(gogo, &ret); + // Type descriptor names and interface method table names use a ".." + // before the mangled name of a type, so to avoid ambiguity the + // mangled name must not start with 'u' or 'U' or a digit. + go_assert((ret[0] < '0' || ret[0] > '9') && ret[0] != ' '); + if (ret[0] == 'u' || ret[0] == 'U') + ret = " " + ret; + + // Map glue characters as described above. + + // The mapping is only unambiguous if there is no .DIGIT in the + // string, so check that. + for (size_t i = ret.find('.'); + i != std::string::npos; + i = ret.find('.', i + 1)) + { + if (i + 1 < ret.size()) + { + char c = ret[i + 1]; + go_assert(c < '0' || c > '9'); + } + } + + // The order of these characters is the replacement code. + const char * const replace = " *;,{}[]()"; + + const size_t rlen = strlen(replace); + char buf[2]; + buf[0] = '.'; + for (size_t ri = 0; ri < rlen; ++ri) + { + buf[1] = '0' + ri; + while (true) + { + size_t i = ret.find(replace[ri]); + if (i == std::string::npos) + break; + ret.replace(i, 1, buf, 2); + } + } + return ret; } @@ -357,27 +582,27 @@ Type::mangled_name(Gogo* gogo) const void Error_type::do_mangled_name(Gogo*, std::string* ret) const { - ret->push_back('E'); + ret->append("{error}"); } void Void_type::do_mangled_name(Gogo*, std::string* ret) const { - ret->push_back('v'); + ret->append("{void}"); } void Boolean_type::do_mangled_name(Gogo*, std::string* ret) const { - ret->push_back('b'); + ret->append("bool"); } void Integer_type::do_mangled_name(Gogo*, std::string* ret) const { char buf[100]; - snprintf(buf, sizeof buf, "i%s%s%de", - this->is_abstract_ ? "a" : "", + snprintf(buf, sizeof buf, "%s%si%d", + this->is_abstract_ ? "{abstract}" : "", this->is_unsigned_ ? "u" : "", this->bits_); ret->append(buf); @@ -387,8 +612,8 @@ void Float_type::do_mangled_name(Gogo*, std::string* ret) const { char buf[100]; - snprintf(buf, sizeof buf, "f%s%de", - this->is_abstract_ ? "a" : "", + snprintf(buf, sizeof buf, "%sfloat%d", + this->is_abstract_ ? "{abstract}" : "", this->bits_); ret->append(buf); } @@ -397,8 +622,8 @@ void Complex_type::do_mangled_name(Gogo*, std::string* ret) const { char buf[100]; - snprintf(buf, sizeof buf, "c%s%de", - this->is_abstract_ ? "a" : "", + snprintf(buf, sizeof buf, "%sc%d", + this->is_abstract_ ? "{abstract}" : "", this->bits_); ret->append(buf); } @@ -406,83 +631,103 @@ Complex_type::do_mangled_name(Gogo*, std::string* ret) const void String_type::do_mangled_name(Gogo*, std::string* ret) const { - ret->push_back('z'); + ret->append("string"); } void Function_type::do_mangled_name(Gogo* gogo, std::string* ret) const { - ret->push_back('F'); + ret->append("func"); if (this->receiver_ != NULL) { - ret->push_back('m'); + ret->push_back('('); this->append_mangled_name(this->receiver_->type(), gogo, ret); + ret->append(")"); } + ret->push_back('('); const Typed_identifier_list* params = this->parameters(); if (params != NULL) { - ret->push_back('p'); + bool first = true; for (Typed_identifier_list::const_iterator p = params->begin(); p != params->end(); ++p) - this->append_mangled_name(p->type(), gogo, ret); - if (this->is_varargs_) - ret->push_back('V'); - ret->push_back('e'); + { + if (first) + first = false; + else + ret->push_back(','); + if (this->is_varargs_ && p + 1 == params->end()) + { + // We can't use "..." here because the mangled name + // might start with 'u' or 'U', which would be ambiguous + // with the encoding of Unicode characters. + ret->append(",,,"); + } + this->append_mangled_name(p->type(), gogo, ret); + } } + ret->push_back(')'); + ret->push_back('('); const Typed_identifier_list* results = this->results(); if (results != NULL) { - ret->push_back('r'); + bool first = true; for (Typed_identifier_list::const_iterator p = results->begin(); p != results->end(); ++p) - this->append_mangled_name(p->type(), gogo, ret); - ret->push_back('e'); + { + if (first) + first = false; + else + ret->append(","); + this->append_mangled_name(p->type(), gogo, ret); + } } - - ret->push_back('e'); + ret->push_back(')'); } void Pointer_type::do_mangled_name(Gogo* gogo, std::string* ret) const { - ret->push_back('p'); + ret->push_back('*'); this->append_mangled_name(this->to_type_, gogo, ret); } void Nil_type::do_mangled_name(Gogo*, std::string* ret) const { - ret->push_back('n'); + ret->append("{nil}"); } void Struct_type::do_mangled_name(Gogo* gogo, std::string* ret) const { - ret->push_back('S'); + ret->append("struct{"); + + if (this->is_struct_incomparable_) + ret->append("{x}"); const Struct_field_list* fields = this->fields_; if (fields != NULL) { + bool first = true; for (Struct_field_list::const_iterator p = fields->begin(); p != fields->end(); ++p) { - if (p->is_anonymous()) - ret->append("0_"); + if (first) + first = false; else - { - - std::string n(Gogo::mangle_possibly_hidden_name(p->field_name())); - char buf[20]; - snprintf(buf, sizeof buf, "%u_", - static_cast(n.length())); - ret->append(buf); - ret->append(n); + ret->push_back(';'); + + if (!p->is_anonymous()) + { + ret->append(Gogo::mangle_possibly_hidden_name(p->field_name())); + ret->push_back(' '); } // For an anonymous field with an alias type, the field name @@ -493,44 +738,26 @@ Struct_type::do_mangled_name(Gogo* gogo, std::string* ret) const p->type()->named_type()->append_mangled_type_name(gogo, true, ret); else this->append_mangled_name(p->type(), gogo, ret); + if (p->has_tag()) { - const std::string& tag(p->tag()); - std::string out; - for (std::string::const_iterator p = tag.begin(); - p != tag.end(); - ++p) - { - if (ISALNUM(*p) || *p == '_') - out.push_back(*p); - else - { - char buf[20]; - snprintf(buf, sizeof buf, ".%x.", - static_cast(*p)); - out.append(buf); - } - } - char buf[20]; - snprintf(buf, sizeof buf, "T%u_", - static_cast(out.length())); - ret->append(buf); - ret->append(out); + // Use curly braces around a struct tag, since they are + // unambiguous here and we have no encoding for + // quotation marks. + ret->push_back('{'); + ret->append(go_mangle_struct_tag(p->tag())); + ret->push_back('}'); } } } - if (this->is_struct_incomparable_) - ret->push_back('x'); - - ret->push_back('e'); + ret->push_back('}'); } void Array_type::do_mangled_name(Gogo* gogo, std::string* ret) const { - ret->push_back('A'); - this->append_mangled_name(this->element_type_, gogo, ret); + ret->push_back('['); if (this->length_ != NULL) { Numeric_constant nc; @@ -550,30 +777,31 @@ Array_type::do_mangled_name(Gogo* gogo, std::string* ret) const free(s); mpz_clear(val); if (this->is_array_incomparable_) - ret->push_back('x'); + ret->append("x"); } - ret->push_back('e'); + ret->push_back(']'); + this->append_mangled_name(this->element_type_, gogo, ret); } void Map_type::do_mangled_name(Gogo* gogo, std::string* ret) const { - ret->push_back('M'); + ret->append("map["); this->append_mangled_name(this->key_type_, gogo, ret); - ret->append("__"); + ret->push_back(']'); this->append_mangled_name(this->val_type_, gogo, ret); } void Channel_type::do_mangled_name(Gogo* gogo, std::string* ret) const { - ret->push_back('C'); + if (!this->may_send_) + ret->append("{}"); + ret->append("chan"); + if (!this->may_receive_) + ret->append("{}"); + ret->push_back(' '); this->append_mangled_name(this->element_type_, gogo, ret); - if (this->may_send_) - ret->push_back('s'); - if (this->may_receive_) - ret->push_back('r'); - ret->push_back('e'); } void @@ -581,31 +809,34 @@ Interface_type::do_mangled_name(Gogo* gogo, std::string* ret) const { go_assert(this->methods_are_finalized_); - ret->push_back('I'); + ret->append("interface{"); const Typed_identifier_list* methods = this->all_methods_; if (methods != NULL && !this->seen_) { this->seen_ = true; + bool first = true; for (Typed_identifier_list::const_iterator p = methods->begin(); p != methods->end(); ++p) { + if (first) + first = false; + else + ret->push_back(';'); + if (!p->name().empty()) { - std::string n(Gogo::mangle_possibly_hidden_name(p->name())); - char buf[20]; - snprintf(buf, sizeof buf, "%u_", - static_cast(n.length())); - ret->append(buf); - ret->append(n); + ret->append(Gogo::mangle_possibly_hidden_name(p->name())); + ret->push_back(' '); } + this->append_mangled_name(p->type(), gogo, ret); } this->seen_ = false; } - ret->push_back('e'); + ret->push_back('}'); } void @@ -622,18 +853,12 @@ Forward_declaration_type::do_mangled_name(Gogo* gogo, std::string* ret) const else { const Named_object* no = this->named_object(); - std::string name; if (no->package() == NULL) - name = gogo->pkgpath_symbol(); + ret->append(gogo->pkgpath_symbol()); else - name = no->package()->pkgpath_symbol(); - name += '.'; - name += Gogo::unpack_hidden_name(no->name()); - char buf[20]; - snprintf(buf, sizeof buf, "N%u_", - static_cast(name.length())); - ret->append(buf); - ret->append(name); + ret->append(no->package()->pkgpath_symbol()); + ret->push_back('.'); + ret->append(Gogo::unpack_hidden_name(no->name())); } } @@ -662,37 +887,44 @@ Named_type::append_mangled_type_name(Gogo* gogo, bool use_alias, go_assert(this->in_function_ == NULL); else { - const std::string& pkgpath(no->package() == NULL - ? gogo->pkgpath_symbol() - : no->package()->pkgpath_symbol()); - name = pkgpath; - name.append(1, '.'); if (this->in_function_ != NULL) { const Typed_identifier* rcvr = this->in_function_->func_value()->type()->receiver(); if (rcvr != NULL) { - Named_type* rcvr_type = rcvr->type()->deref()->named_type(); - name.append(Gogo::unpack_hidden_name(rcvr_type->name())); - name.append(1, '.'); - } - name.append(Gogo::unpack_hidden_name(this->in_function_->name())); - name.append(1, '$'); - if (this->in_function_index_ > 0) - { - char buf[30]; - snprintf(buf, sizeof buf, "%u", this->in_function_index_); - name.append(buf); - name.append(1, '$'); + std::string m = rcvr->type()->mangled_name(gogo); + // Turn a leading ".1" back into "*" since we are going + // to type-mangle this name again. + if (m.compare(0, 2, ".1") == 0) + m = "*" + m.substr(2); + ret->append(m); } + else if (this->in_function_->package() == NULL) + ret->append(gogo->pkgpath_symbol()); + else + ret->append(this->in_function_->package()->pkgpath_symbol()); + ret->push_back('.'); + ret->append(Gogo::unpack_hidden_name(this->in_function_->name())); + } + else + { + if (no->package() == NULL) + ret->append(gogo->pkgpath_symbol()); + else + ret->append(no->package()->pkgpath_symbol()); } + ret->push_back('.'); + } + + ret->append(Gogo::unpack_hidden_name(no->name())); + + if (this->in_function_ != NULL && this->in_function_index_ > 0) + { + char buf[30]; + snprintf(buf, sizeof buf, "..i%u", this->in_function_index_); + ret->append(buf); } - name.append(Gogo::unpack_hidden_name(no->name())); - char buf[20]; - snprintf(buf, sizeof buf, "N%u_", static_cast(name.length())); - ret->append(buf); - ret->append(name); } // Return the name for the type descriptor symbol for TYPE. This can @@ -704,50 +936,53 @@ Gogo::type_descriptor_name(Type* type, Named_type* nt) { // The type descriptor symbol for the unsafe.Pointer type is defined // in libgo/runtime/go-unsafe-pointer.c, so just use a reference to - // that symbol. + // that symbol for all unsafe pointer types. if (type->is_unsafe_pointer_type()) - return "__go_tdn_unsafe.Pointer"; + return "unsafe.Pointer..d"; if (nt == NULL) - return "__go_td_" + type->mangled_name(this); + return "type.." + type->mangled_name(this); + std::string ret; Named_object* no = nt->named_object(); unsigned int index; const Named_object* in_function = nt->in_function(&index); - std::string ret = "__go_tdn_"; if (nt->is_builtin()) go_assert(in_function == NULL); else { - const std::string& pkgpath(no->package() == NULL - ? this->pkgpath_symbol() - : no->package()->pkgpath_symbol()); - ret.append(pkgpath); - ret.append(1, '.'); if (in_function != NULL) { const Typed_identifier* rcvr = in_function->func_value()->type()->receiver(); if (rcvr != NULL) - { - Named_type* rcvr_type = rcvr->type()->deref()->named_type(); - ret.append(Gogo::unpack_hidden_name(rcvr_type->name())); - ret.append(1, '.'); - } + ret.append(rcvr->type()->mangled_name(this)); + else if (in_function->package() == NULL) + ret.append(this->pkgpath_symbol()); + else + ret.append(in_function->package()->pkgpath_symbol()); + ret.push_back('.'); ret.append(Gogo::unpack_hidden_name(in_function->name())); - ret.append(1, '.'); - if (index > 0) - { - char buf[30]; - snprintf(buf, sizeof buf, "%u", index); - ret.append(buf); - ret.append(1, '.'); - } + ret.push_back('.'); } + + if (no->package() == NULL) + ret.append(this->pkgpath_symbol()); + else + ret.append(no->package()->pkgpath_symbol()); + ret.push_back('.'); + } + + ret.append(Gogo::mangle_possibly_hidden_name(no->name())); + + if (in_function != NULL && index > 0) + { + char buf[30]; + snprintf(buf, sizeof buf, "..i%u", index); + ret.append(buf); } - std::string mname(Gogo::mangle_possibly_hidden_name(no->name())); - ret.append(mname); + ret.append("..d"); return ret; } @@ -761,11 +996,11 @@ Gogo::type_descriptor_name(Type* type, Named_type* nt) std::string Gogo::gc_symbol_name(Type* type) { - return this->type_descriptor_name(type, type->named_type()) + "$gc"; + return this->type_descriptor_name(type, type->named_type()) + "..g"; } // Return the name for a ptrmask variable. PTRMASK_SYM_NAME is a -// base64 string encoding the ptrmask (as returned by Ptrmask::symname +// base32 string encoding the ptrmask (as returned by Ptrmask::symname // in types.cc). This name is used to intialize the gcdata field of a // type descriptor. These names are globally visible. (Note that // some type descriptors will initialize the gcdata field with a name @@ -774,7 +1009,7 @@ Gogo::gc_symbol_name(Type* type) std::string Gogo::ptrmask_symbol_name(const std::string& ptrmask_sym_name) { - return "runtime.gcbits." + ptrmask_sym_name; + return "gcbits.." + ptrmask_sym_name; } // Return the name to use for an interface method table used for the @@ -786,8 +1021,25 @@ std::string Gogo::interface_method_table_name(Interface_type* itype, Type* type, bool is_pointer) { - return ((is_pointer ? "__go_pimt__" : "__go_imt_") + return ((is_pointer ? "pimt.." : "imt..") + itype->mangled_name(this) - + "__" + + ".." + type->mangled_name(this)); } + +// Return whether NAME is a special name that can not be passed to +// unpack_hidden_name. This is needed because various special names +// use "..SUFFIX", but unpack_hidden_name just looks for '.'. + +bool +Gogo::is_special_name(const std::string& name) +{ + return (name.find("..hash") != std::string::npos + || name.find("..eq") != std::string::npos + || name.find("..stub") != std::string::npos + || name.find("..func") != std::string::npos + || name.find("..r") != std::string::npos + || name.find("..init") != std::string::npos + || name.find("..thunk") != std::string::npos + || name.find("..import") != std::string::npos); +} diff --git a/gcc/go/gofrontend/statements.cc b/gcc/go/gofrontend/statements.cc index 1c079bb40c7..094966592f6 100644 --- a/gcc/go/gofrontend/statements.cc +++ b/gcc/go/gofrontend/statements.cc @@ -2120,7 +2120,7 @@ Thunk_statement::simplify_statement(Gogo* gogo, Named_object* function, fn = Expression::make_temporary_reference(fn_temp, location); } - std::string thunk_name = Gogo::thunk_name(); + std::string thunk_name = gogo->thunk_name(); // Build the thunk. this->build_thunk(gogo, thunk_name); diff --git a/gcc/go/gofrontend/types.cc b/gcc/go/gofrontend/types.cc index 85273bf55d2..28eef8e9ed9 100644 --- a/gcc/go/gofrontend/types.cc +++ b/gcc/go/gofrontend/types.cc @@ -2654,14 +2654,14 @@ Ptrmask::set_from(Gogo* gogo, Type* type, int64_t ptrsize, int64_t offset) // Return a symbol name for this ptrmask. This is used to coalesce // identical ptrmasks, which are common. The symbol name must use // only characters that are valid in symbols. It's nice if it's -// short. We convert it to a base64 string. +// short. We convert it to a string that uses only 32 characters, +// avoiding digits and u and U. std::string Ptrmask::symname() const { - const char chars[65] = - "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_."; - go_assert(chars[64] == '\0'); + const char chars[33] = "abcdefghijklmnopqrstvwxyzABCDEFG"; + go_assert(chars[32] == '\0'); std::string ret; unsigned int b = 0; int remaining = 0; @@ -2671,18 +2671,18 @@ Ptrmask::symname() const { b |= *p << remaining; remaining += 8; - while (remaining >= 6) + while (remaining >= 5) { - ret += chars[b & 0x3f]; - b >>= 6; - remaining -= 6; + ret += chars[b & 0x1f]; + b >>= 5; + remaining -= 5; } } while (remaining > 0) { - ret += chars[b & 0x3f]; - b >>= 6; - remaining -= 6; + ret += chars[b & 0x1f]; + b >>= 5; + remaining -= 5; } return ret; } @@ -4447,7 +4447,11 @@ Function_type::is_identical(const Function_type* t, bool ignore_receiver, } const Typed_identifier_list* parms1 = this->parameters(); + if (parms1 != NULL && parms1->empty()) + parms1 = NULL; const Typed_identifier_list* parms2 = t->parameters(); + if (parms2 != NULL && parms2->empty()) + parms2 = NULL; if ((parms1 != NULL) != (parms2 != NULL)) { if (reason != NULL) @@ -4492,7 +4496,11 @@ Function_type::is_identical(const Function_type* t, bool ignore_receiver, } const Typed_identifier_list* results1 = this->results(); + if (results1 != NULL && results1->empty()) + results1 = NULL; const Typed_identifier_list* results2 = t->results(); + if (results2 != NULL && results2->empty()) + results2 = NULL; if ((results1 != NULL) != (results2 != NULL)) { if (reason != NULL) @@ -11144,7 +11152,7 @@ Type::build_stub_methods(Gogo* gogo, const Type* type, const Methods* methods, package = NULL; else package = type->named_type()->named_object()->package(); - std::string stub_name = gogo->stub_method_name(name); + std::string stub_name = gogo->stub_method_name(package, name); Named_object* stub; if (package != NULL) stub = Named_object::make_function_declaration(stub_name, package, diff --git a/gcc/testsuite/ChangeLog b/gcc/testsuite/ChangeLog index 2c637fe5d89..bdc01f7cfc1 100644 --- a/gcc/testsuite/ChangeLog +++ b/gcc/testsuite/ChangeLog @@ -1,3 +1,7 @@ +2018-01-24 Ian Lance Taylor + + * go.go-torture/execute/names-1.go: New test. + 2018-01-19 Jeff Law PR target/83994 diff --git a/gcc/testsuite/go.go-torture/execute/names-1.go b/gcc/testsuite/go.go-torture/execute/names-1.go new file mode 100644 index 00000000000..bac91945bc7 --- /dev/null +++ b/gcc/testsuite/go.go-torture/execute/names-1.go @@ -0,0 +1,195 @@ +// names-1 is a change detector for Go symbol names. We don't want +// the name mangling to change silently. +package main + +import ( + "bytes" + "debug/elf" + "debug/macho" + "debug/pe" + "debug/xcoff" + "fmt" + "os" + "strings" +) + +type Type int +type Alias = int + +//go:noinline +func Function1(out *bytes.Buffer) int { + var f2 func(int) int + f1 := func(i int) int { + if i == 0 { + return 0 + } + type NestedType struct { a int } + t := NestedType{f2(i-1)} + fmt.Fprint(out, t) + return t.a + } + f2 = func(i int) int { + if i == 0 { + return 0 + } + type NestedType struct { a int } + t := NestedType{f1(i-1)} + fmt.Fprint(out, t) + return t.a + } + return f1(10) + f2(10) +} + +//go:noinline +func Function2(out *bytes.Buffer) { + { + type T struct { b int } + fmt.Fprint(out, T{1}) + } + { + type T struct { b int } + fmt.Fprint(out, T{2}) + } +} + +func (t Type) M(bool, int8, float32, complex64, string, func(), func(int16) (float64, complex128), *byte, struct { f int "tag #$%^&{}: 世界" }, []int32, [24]int64, map[uint8]uint16, chan uint32, <-chan uint64, chan <- uintptr, Type, Alias) { +} + +//go:noinline +func Function3(out *bytes.Buffer) { + fmt.Fprintf(out, "%T", Type(0)) +} + +func main() { + var b bytes.Buffer + Function1(&b) + Function2(&b) + Function3(&b) + _ = len(b.String()) + + for _, n := range []string{"/proc/self/exe", os.Args[0]} { + if f, err := os.Open(n); err == nil { + checkFile(f) + return + } + } + fmt.Println("checksyms: could not find executable") + fmt.Println("UNSUPPORTED: checksyms") +} + +func checkFile(f *os.File) { + var syms []string + if ef, err := elf.NewFile(f); err == nil { + esyms, err := ef.Symbols() + if err != nil { + panic(err) + } + for _, esym := range esyms { + syms = append(syms, esym.Name) + } + } else if mf, err := macho.NewFile(f); err == nil { + for _, msym := range mf.Symtab.Syms { + syms = append(syms, msym.Name) + } + } else if pf, err := pe.NewFile(f); err == nil { + for _, psym := range pf.Symbols { + syms = append(syms, psym.Name) + } + } else if xf, err := xcoff.NewFile(f); err == nil { + for _, xsym := range xf.Symbols { + syms = append(syms, xsym.Name) + } + } else { + fmt.Println("checksyms: could not parse executable") + fmt.Println("UNSUPPORTED: checksyms") + return + } + checkSyms(syms) +} + +var want = []string{ + "main.Function1", + "main.Function1..f", + "main.Function1..func1", + "main.Function1..func1.main.NestedType..d", + "main.Function1..func2", + "main.Function1..func2.main.NestedType..d", + "main.Function2", + "main.Function2..f", + "main.Function2.main.T..d", + "main.Function2.main.T..i1..d", + "main.Function3", + "main.Function3..f", + "main.Type..d", + "main.Type.M", + "main.main", + "main.want", + "type...1.1main.Type", // Why is this here? + "type...1main.Function1..func1.NestedType", + "type...1main.Function1..func2.NestedType", + "type...1main.Function2.T", + "type...1main.Function2.T..i1", + "type...1main.Type", + "type..func.8.1main.Type.3bool.3int8.3float32.3complex64.3string.3func.8.9.8.9.3func.8int16.9.8float64.3complex128.9.3.1uint8.3struct.4.main.f.0int.4tag.x20.x23.x24.x25.x5e.x26.x7b.x7d.x3a.x20..u4e16..u754c.5.5.3.6.7int32.3.624.7int64.3map.6uint8.7uint16.3chan.0uint32.3.4.5chan.0uint64.3chan.4.5.0uintptr.3main.Type.3int.9.8.9", + "type..func.8bool.3int8.3float32.3complex64.3string.3func.8.9.8.9.3func.8int16.9.8float64.3complex128.9.3.1uint8.3struct.4.main.f.0int.4tag.x20.x23.x24.x25.x5e.x26.x7b.x7d.x3a.x20..u4e16..u754c.5.5.3.6.7int32.3.624.7int64.3map.6uint8.7uint16.3chan.0uint32.3.4.5chan.0uint64.3chan.4.5.0uintptr.3main.Type.3int.9.8.9", + "type..func.8main.Type.3bool.3int8.3float32.3complex64.3string.3func.8.9.8.9.3func.8int16.9.8float64.3complex128.9.3.1uint8.3struct.4.main.f.0int.4tag.x20.x23.x24.x25.x5e.x26.x7b.x7d.x3a.x20..u4e16..u754c.5.5.3.6.7int32.3.624.7int64.3map.6uint8.7uint16.3chan.0uint32.3.4.5chan.0uint64.3chan.4.5.0uintptr.3main.Type.3int.9.8.9", + "type..struct.4.main.f.0int.4tag.x20.x23.x24.x25.x5e.x26.x7b.x7d.x3a.x20..u4e16..u754c.5.5", +} + +func checkSyms(syms []string) { + m := make(map[string]bool) + for _, sym := range syms { + if strings.Contains(sym, ".") { + m[sym] = true + } + } + + ok := true + for _, w := range want { + if m[w] { + delete(m, w) + } else { + fmt.Printf("checksyms: missing expected symbol %q\n", w) + ok = false + } + } + + for sym := range m { + if !strings.Contains(sym, "main") { + continue + } + + // Skip some symbols we may see but know are unimportant. + if sym == "go-main.c" { + continue + } + if strings.HasPrefix(sym, "runtime.") { + continue + } + + // We can see a lot of spurious .eq and .hash + // functions for types defined in other packages. + // This is a bug but skip them for now. + if strings.Contains(sym, "..eq") || strings.Contains(sym, "..hash") { + continue + } + + // Skip closure types by skipping incomparable structs. + // This may be a bug, not sure. + if strings.Contains(sym, ".4x.5") { + continue + } + + // These functions may be inlined. + if sym == "main.checkFile" || sym == "main.checkSyms" { + continue + } + + fmt.Printf("checksyms: found unexpected symbol %q\n", sym) + ok = false + } + + if !ok { + fmt.Println("FAIL: checksyms") + } +} diff --git a/libgo/go/runtime/crash_test.go b/libgo/go/runtime/crash_test.go index 8ec034835ec..06200c7303b 100644 --- a/libgo/go/runtime/crash_test.go +++ b/libgo/go/runtime/crash_test.go @@ -425,7 +425,7 @@ func TestPanicTraceback(t *testing.T) { // Check functions in the traceback. fns := []string{"main.pt1.func1", "panic", "main.pt2.func1", "panic", "main.pt2", "main.pt1"} if runtime.Compiler == "gccgo" { - fns = []string{"main.$nested", "panic", "main.$nested", "panic", "main.pt2", "main.pt1"} + fns = []string{"main.pt1..func1", "panic", "main.pt2..func1", "panic", "main.pt2", "main.pt1"} } for _, fn := range fns { var re *regexp.Regexp @@ -570,7 +570,7 @@ func TestPanicInlined(t *testing.T) { buf = buf[:n] want := []byte("(*point).negate(") if runtime.Compiler == "gccgo" { - want = []byte("negate.pN18_runtime_test.point") + want = []byte("point.negate") } if !bytes.Contains(buf, want) { t.Logf("%s", buf) diff --git a/libgo/go/runtime/panic.go b/libgo/go/runtime/panic.go index b2deb6e6580..24d78eca62d 100644 --- a/libgo/go/runtime/panic.go +++ b/libgo/go/runtime/panic.go @@ -656,7 +656,7 @@ func canrecover(retaddr uintptr) bool { } // Ignore other functions in the reflect package. - if hasprefix(name, "reflect.") { + if hasprefix(name, "reflect.") || hasprefix(name, ".1reflect.") { continue } diff --git a/libgo/go/runtime/pprof/pprof_test.go b/libgo/go/runtime/pprof/pprof_test.go index 08a4f969ca2..ae6ec6d0cfc 100644 --- a/libgo/go/runtime/pprof/pprof_test.go +++ b/libgo/go/runtime/pprof/pprof_test.go @@ -730,7 +730,7 @@ func TestMutexProfile(t *testing.T) { stks := stacks(p) for _, want := range [][]string{ // {"sync.(*Mutex).Unlock", "pprof.blockMutex.func1"}, - {"sync.Unlock.pN10_sync.Mutex", "pprof.$nested17"}, + {".1sync.Mutex.Unlock", "pprof.blockMutex..func1"}, } { if !containsStack(stks, want) { t.Errorf("No matching stack entry for %+v", want) diff --git a/libgo/go/syscall/wait.c b/libgo/go/syscall/wait.c index a50f7d6f5c2..c0c5cca39af 100644 --- a/libgo/go/syscall/wait.c +++ b/libgo/go/syscall/wait.c @@ -17,7 +17,7 @@ #endif extern _Bool Exited (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.Exited.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.Exited"); _Bool Exited (uint32_t *w) @@ -26,7 +26,7 @@ Exited (uint32_t *w) } extern _Bool Signaled (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.Signaled.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.Signaled"); _Bool Signaled (uint32_t *w) @@ -35,7 +35,7 @@ Signaled (uint32_t *w) } extern _Bool Stopped (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.Stopped.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.Stopped"); _Bool Stopped (uint32_t *w) @@ -44,7 +44,7 @@ Stopped (uint32_t *w) } extern _Bool Continued (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.Continued.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.Continued"); _Bool Continued (uint32_t *w) @@ -53,7 +53,7 @@ Continued (uint32_t *w) } extern _Bool CoreDump (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.CoreDump.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.CoreDump"); _Bool CoreDump (uint32_t *w) @@ -62,7 +62,7 @@ CoreDump (uint32_t *w) } extern int ExitStatus (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.ExitStatus.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.ExitStatus"); int ExitStatus (uint32_t *w) @@ -73,7 +73,7 @@ ExitStatus (uint32_t *w) } extern int Signal (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.Signal.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.Signal"); int Signal (uint32_t *w) @@ -84,7 +84,7 @@ Signal (uint32_t *w) } extern int StopSignal (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.StopSignal.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.StopSignal"); int StopSignal (uint32_t *w) @@ -95,7 +95,7 @@ StopSignal (uint32_t *w) } extern int TrapCause (uint32_t *w) - __asm__ (GOSYM_PREFIX "syscall.TrapCause.N18_syscall.WaitStatus"); + __asm__ (GOSYM_PREFIX "syscall.WaitStatus.TrapCause"); int TrapCause (uint32_t *w __attribute__ ((unused))) diff --git a/libgo/runtime/go-callers.c b/libgo/runtime/go-callers.c index 9f376c7f4aa..2eaac682779 100644 --- a/libgo/runtime/go-callers.c +++ b/libgo/runtime/go-callers.c @@ -68,13 +68,14 @@ callback (void *data, uintptr_t pc, const char *filename, int lineno, { const char *p; - p = __builtin_strchr (function, '.'); - if (p != NULL && __builtin_strncmp (p + 1, "$thunk", 6) == 0) + p = function + __builtin_strlen (function); + while (p > function && p[-1] >= '0' && p[-1] <= '9') + --p; + if (p - function > 7 && __builtin_strncmp (p - 7, "..thunk", 7) == 0) return 0; - p = __builtin_strrchr (function, '$'); - if (p != NULL && __builtin_strcmp(p, "$recover") == 0) + if (p - function > 3 && __builtin_strcmp (p - 3, "..r") == 0) return 0; - if (p != NULL && __builtin_strncmp(p, "$stub", 5) == 0) + if (p - function > 6 && __builtin_strcmp (p - 6, "..stub") == 0) return 0; } diff --git a/libgo/runtime/go-unsafe-pointer.c b/libgo/runtime/go-unsafe-pointer.c index 9df7f3aee08..5afd0112fe2 100644 --- a/libgo/runtime/go-unsafe-pointer.c +++ b/libgo/runtime/go-unsafe-pointer.c @@ -15,10 +15,10 @@ descriptor. */ extern const struct __go_type_descriptor unsafe_Pointer - __asm__ (GOSYM_PREFIX "__go_tdn_unsafe.Pointer"); + __asm__ (GOSYM_PREFIX "unsafe.Pointer..d"); extern const byte unsafe_Pointer_gc[] - __asm__ (GOSYM_PREFIX "__go_tdn_unsafe.Pointer$gc"); + __asm__ (GOSYM_PREFIX "unsafe.Pointer..g"); /* Used to determine the field alignment. */ struct field_align @@ -38,9 +38,9 @@ static const String reflection_string = const byte unsafe_Pointer_gc[] = { 1 }; extern const FuncVal runtime_pointerhash_descriptor - __asm__ (GOSYM_PREFIX "runtime.pointerhash$descriptor"); + __asm__ (GOSYM_PREFIX "runtime.pointerhash..f"); extern const FuncVal runtime_pointerequal_descriptor - __asm__ (GOSYM_PREFIX "runtime.pointerequal$descriptor"); + __asm__ (GOSYM_PREFIX "runtime.pointerequal..f"); const struct __go_type_descriptor unsafe_Pointer = { @@ -75,7 +75,7 @@ const struct __go_type_descriptor unsafe_Pointer = it to be defined elsewhere. */ extern const struct __go_ptr_type pointer_unsafe_Pointer - __asm__ (GOSYM_PREFIX "__go_td_pN14_unsafe.Pointer"); + __asm__ (GOSYM_PREFIX "type...1unsafe.Pointer"); /* The reflection string. */ #define PREFLECTION "*unsafe.Pointer" @@ -86,7 +86,7 @@ static const String preflection_string = }; extern const byte pointer_unsafe_Pointer_gc[] - __asm__ (GOSYM_PREFIX "__go_td_pN14_unsafe.Pointer$gc"); + __asm__ (GOSYM_PREFIX "type...1unsafe.Pointer..g"); const byte pointer_unsafe_Pointer_gc[] = { 1 }; -- 2.30.2