From b87060ce92628c3c81754149d2d8a7e2733fb495 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Tue, 9 Aug 2016 14:05:17 +0000 Subject: [PATCH] compiler: rewrite compiler directive support Rewrite the compiler directive support to recognize all the compiler directives implemented by the current gc compiler. The directives other than go:linkname are now turned into GOPRAGMA flags attached to a function or function declaration. The go:linkname directive is turned into a map attached to the Lex object. No new directives are actually implemented yet, they are just recognized. Reviewed-on: https://go-review.googlesource.com/26610 From-SVN: r239282 --- gcc/go/gofrontend/MERGE | 2 +- gcc/go/gofrontend/gogo.cc | 23 ++++- gcc/go/gofrontend/gogo.h | 34 ++++--- gcc/go/gofrontend/lex.cc | 191 +++++++++++++++++++++++++++++-------- gcc/go/gofrontend/lex.h | 35 +++++-- gcc/go/gofrontend/parse.cc | 98 ++++++++++++++----- gcc/go/gofrontend/parse.h | 2 +- 7 files changed, 298 insertions(+), 87 deletions(-) diff --git a/gcc/go/gofrontend/MERGE b/gcc/go/gofrontend/MERGE index 8a0c76f7fe6..f14fdec06dc 100644 --- a/gcc/go/gofrontend/MERGE +++ b/gcc/go/gofrontend/MERGE @@ -1,4 +1,4 @@ -3b9c57a35370f26e6cf5839084e367e75e45ec97 +58be5c6c7d92182dec50a62c8e319d2d7aab12a4 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/gogo.cc b/gcc/go/gofrontend/gogo.cc index 37760a75e6c..b1dbb850200 100644 --- a/gcc/go/gofrontend/gogo.cc +++ b/gcc/go/gofrontend/gogo.cc @@ -4439,7 +4439,7 @@ 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), - is_sink_(false), results_are_named_(false), nointerface_(false), + 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), calls_defer_retaddr_(false), is_type_specific_function_(false), @@ -4511,6 +4511,24 @@ Function::update_result_variables() (*p)->result_var_value()->set_function(this); } +// Whether this method should not be included in the type descriptor. + +bool +Function::nointerface() const +{ + go_assert(this->is_method()); + return (this->pragmas_ & GOPRAGMA_NOINTERFACE) != 0; +} + +// Record that this method should not be included in the type +// descriptor. + +void +Function::set_nointerface() +{ + this->pragmas_ |= GOPRAGMA_NOINTERFACE; +} + // Return the closure variable, creating it if necessary. Named_object* @@ -5042,7 +5060,8 @@ Function::get_or_make_decl(Gogo* gogo, Named_object* no) // We want to put a nointerface function into a unique section // because there is a good chance that the linker garbage // collection can discard it. - bool in_unique_section = this->in_unique_section_ || this->nointerface_; + bool in_unique_section = (this->in_unique_section_ + || (this->is_method() && this->nointerface())); Btype* functype = this->type_->get_backend_fntype(gogo); this->fndecl_ = diff --git a/gcc/go/gofrontend/gogo.h b/gcc/go/gofrontend/gogo.h index 07234fbb06c..bac9eb0c168 100644 --- a/gcc/go/gofrontend/gogo.h +++ b/gcc/go/gofrontend/gogo.h @@ -975,23 +975,22 @@ class Function results_are_named() const { return this->results_are_named_; } + // Set the pragmas for this function. + void + set_pragmas(unsigned int pragmas) + { + this->pragmas_ = pragmas; + } + // Whether this method should not be included in the type // descriptor. bool - nointerface() const - { - go_assert(this->is_method()); - return this->nointerface_; - } + nointerface() const; // Record that this method should not be included in the type // descriptor. void - set_nointerface() - { - go_assert(this->is_method()); - this->nointerface_ = true; - } + set_nointerface(); // Record that this function is a stub method created for an unnamed // type. @@ -1238,12 +1237,12 @@ class Function // distinguish the defer stack for one function from another. This // is NULL unless we actually need a defer stack. Temporary_statement* defer_stack_; + // Pragmas for this function. This is a set of GOPRAGMA bits. + unsigned int pragmas_; // True if this function is sink-named. No code is generated. bool is_sink_ : 1; // True if the result variables are named. bool results_are_named_ : 1; - // True if this method should not be included in the type descriptor. - bool nointerface_ : 1; // True if this function is a stub method created for an unnamed // type. bool is_unnamed_type_stub_method_ : 1; @@ -1305,7 +1304,7 @@ class Function_declaration public: Function_declaration(Function_type* fntype, Location location) : fntype_(fntype), location_(location), asm_name_(), descriptor_(NULL), - fndecl_(NULL) + fndecl_(NULL), pragmas_(0) { } Function_type* @@ -1325,6 +1324,13 @@ class Function_declaration set_asm_name(const std::string& asm_name) { this->asm_name_ = asm_name; } + // Set the pragmas for this function. + void + set_pragmas(unsigned int pragmas) + { + this->pragmas_ = pragmas; + } + // Return an expression for the function descriptor, given the named // object for this function. This may only be called for functions // without a closure. This will be an immutable struct with one @@ -1367,6 +1373,8 @@ class Function_declaration Expression* descriptor_; // The function decl if needed. Bfunction* fndecl_; + // Pragmas for this function. This is a set of GOPRAGMA bits. + unsigned int pragmas_; }; // A variable. diff --git a/gcc/go/gofrontend/lex.cc b/gcc/go/gofrontend/lex.cc index 34a0811abef..94809d52b23 100644 --- a/gcc/go/gofrontend/lex.cc +++ b/gcc/go/gofrontend/lex.cc @@ -442,7 +442,7 @@ Token::print(FILE* file) const Lex::Lex(const char* input_file_name, FILE* input_file, Linemap* linemap) : input_file_name_(input_file_name), input_file_(input_file), linemap_(linemap), linebuf_(NULL), linebufsize_(120), linesize_(0), - lineoff_(0), lineno_(0), add_semi_at_eol_(false), saw_nointerface_(false), + lineoff_(0), lineno_(0), add_semi_at_eol_(false), pragmas_(0), extern_() { this->linebuf_ = new char[this->linebufsize_]; @@ -1676,29 +1676,47 @@ Lex::skip_cpp_comment() // //extern comment. this->extern_.clear(); - const char* p = this->linebuf_ + this->lineoff_; + size_t lineoff = this->lineoff_; + + const char* p = this->linebuf_ + lineoff; const char* pend = this->linebuf_ + this->linesize_; - // By convention, a C++ comment at the start of the line of the form + const char* pcheck = p; + bool saw_error = false; + while (pcheck < pend) + { + this->lineoff_ = pcheck - this->linebuf_; + unsigned int c; + bool issued_error; + pcheck = this->advance_one_utf8_char(pcheck, &c, &issued_error); + if (issued_error) + saw_error = true; + } + + if (saw_error) + return; + + // Recognize various magic comments at the start of a line. + + if (lineoff != 2) + { + // Not at the start of the line. (lineoff == 2 because of the + // two characters in "//"). + return; + } + + while (pend > p + && (pend[-1] == ' ' || pend[-1] == '\t' + || pend[-1] == '\r' || pend[-1] == '\n')) + --pend; + + // A C++ comment at the start of the line of the form // //line FILE:LINENO // is interpreted as setting the file name and line number of the // next source line. - - if (this->lineoff_ == 2 - && pend - p > 5 - && memcmp(p, "line ", 5) == 0) + if (pend - p > 5 && memcmp(p, "line ", 5) == 0) { p += 5; - - // Before finding FILE:LINENO, make sure line has valid characters. - const char* pcheck = p; - while (pcheck < pend) - { - unsigned int c; - bool issued_error; - pcheck = this->advance_one_utf8_char(pcheck, &c, &issued_error); - } - while (p < pend && *p == ' ') ++p; const char* pcolon = static_cast(memchr(p, ':', pend - p)); @@ -1726,6 +1744,7 @@ Lex::skip_cpp_comment() p = plend; } } + return; } // As a special gccgo extension, a C++ comment at the start of the @@ -1734,37 +1753,129 @@ Lex::skip_cpp_comment() // which immediately precedes a function declaration means that the // external name of the function declaration is NAME. This is // normally used to permit Go code to call a C function. - if (this->lineoff_ == 2 - && pend - p > 7 - && memcmp(p, "extern ", 7) == 0) + if (pend - p > 7 && memcmp(p, "extern ", 7) == 0) { p += 7; while (p < pend && (*p == ' ' || *p == '\t')) ++p; - const char* plend = pend; - while (plend > p - && (plend[-1] == ' ' || plend[-1] == '\t' || plend[-1] == '\n')) - --plend; - if (plend > p) - this->extern_ = std::string(p, plend - p); + if (pend > p) + this->extern_ = std::string(p, pend - p); + return; } - // For field tracking analysis: a //go:nointerface comment means - // that the next interface method should not be stored in the type - // descriptor. This permits it to be discarded if it is not needed. - if (this->lineoff_ == 2 - && pend - p > 14 - && memcmp(p, "go:nointerface", 14) == 0) - this->saw_nointerface_ = true; + // All other special comments start with "go:". - while (p < pend) + if (pend - p < 4 || memcmp(p, "go:", 3) != 0) + return; + + const char *ps = p + 3; + while (ps < pend && *ps != ' ' && *ps != '\t') + ++ps; + std::string verb = std::string(p, ps - p); + + if (verb == "go:linkname") { - this->lineoff_ = p - this->linebuf_; - unsigned int c; - bool issued_error; - p = this->advance_one_utf8_char(p, &c, &issued_error); - if (issued_error) - this->extern_.clear(); + // As in the gc compiler, set the external link name for a Go symbol. + std::string go_name; + std::string c_name; + if (ps < pend) + { + while (ps < pend && (*ps == ' ' || *ps == '\t')) + ++ps; + if (ps < pend) + { + const char* pg = ps; + while (ps < pend && *ps != ' ' && *ps != '\t') + ++ps; + if (ps < pend) + go_name = std::string(pg, ps - pg); + while (ps < pend && (*ps == ' ' || *ps == '\t')) + ++ps; + } + if (ps < pend) + { + const char* pc = ps; + while (ps < pend && *ps != ' ' && *ps != '\t') + ++ps; + if (ps <= pend) + c_name = std::string(pc, ps - pc); + } + if (ps != pend) + { + go_name.clear(); + c_name.clear(); + } + } + if (go_name.empty() || c_name.empty()) + error_at(this->location(), "usage: //go:linkname localname linkname"); + else + this->linknames_[go_name] = c_name; + } + else if (verb == "go:nointerface") + { + // For field tracking analysis: a //go:nointerface comment means + // that the next interface method should not be stored in the + // type descriptor. This permits it to be discarded if it is + // not needed. + this->pragmas_ |= GOPRAGMA_NOINTERFACE; + } + else if (verb == "go:noescape") + { + // Applies to the next function declaration. Any arguments do + // not escape. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_NOESCAPE; + } + else if (verb == "go:nosplit") + { + // Applies to the next function. Do not split the stack when + // entering the function. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_NOSPLIT; + } + else if (verb == "go:noinline") + { + // Applies to the next function. Do not inline the function. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_NOINLINE; + } + else if (verb == "go:systemstack") + { + // Applies to the next function. It must run on the system stack. + // FIXME: Should only work when compiling the runtime package. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_SYSTEMSTACK; + } + else if (verb == "go:nowritebarrier") + { + // Applies to the next function. If the function needs to use + // any write barriers, it should emit an error instead. + // FIXME: Should only work when compiling the runtime package. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_NOWRITEBARRIER; + } + else if (verb == "go:nowritebarrierrec") + { + // Applies to the next function. If the function, or any + // function that it calls, needs to use any write barriers, it + // should emit an error instead. + // FIXME: Should only work when compiling the runtime package. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_NOWRITEBARRIERREC; + } + else if (verb == "go:cgo_unsafe_args") + { + // Applies to the next function. Taking the address of any + // argument implies taking the address of all arguments. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_CGOUNSAFEARGS; + } + else if (verb == "go:uintptrescapes") + { + // Applies to the next function. If an argument is a pointer + // converted to uintptr, then the pointer escapes. + // FIXME: Not implemented. + this->pragmas_ |= GOPRAGMA_UINTPTRESCAPES; } } diff --git a/gcc/go/gofrontend/lex.h b/gcc/go/gofrontend/lex.h index 3e0152aaf2d..2f14136a525 100644 --- a/gcc/go/gofrontend/lex.h +++ b/gcc/go/gofrontend/lex.h @@ -49,6 +49,24 @@ enum Keyword KEYWORD_VAR }; +// Pragmas built from magic comments and recorded for functions. +// These are used as bits in a bitmask. +// The set of values is intended to be the same as the gc compiler. + +enum GoPragma +{ + GOPRAGMA_NOINTERFACE = 1 << 0, // Method not in type descriptor. + GOPRAGMA_NOESCAPE = 1 << 1, // Args do not escape. + GOPRAGMA_NORACE = 1 << 2, // No race detector. + GOPRAGMA_NOSPLIT = 1 << 3, // Do not split stack. + GOPRAGMA_NOINLINE = 1 << 4, // Do not inline. + GOPRAGMA_SYSTEMSTACK = 1 << 5, // Must run on system stack. + GOPRAGMA_NOWRITEBARRIER = 1 << 6, // No write barriers. + GOPRAGMA_NOWRITEBARRIERREC = 1 << 7, // No write barriers here or callees. + GOPRAGMA_CGOUNSAFEARGS = 1 << 8, // Pointer to arg is pointer to all. + GOPRAGMA_UINTPTRESCAPES = 1 << 9 // uintptr(p) escapes. +}; + // A token returned from the lexer. class Token @@ -348,13 +366,12 @@ class Lex extern_name() const { return this->extern_; } - // Return whether we have seen a //go:nointerface comment, clearing - // the flag. - bool - get_and_clear_nointerface() + // Return the current set of pragmas, and clear them. + unsigned int + get_and_clear_pragmas() { - bool ret = this->saw_nointerface_; - this->saw_nointerface_ = false; + unsigned int ret = this->pragmas_; + this->pragmas_ = 0; return ret; } @@ -492,11 +509,13 @@ class Lex size_t lineno_; // Whether to add a semicolon if we see a newline now. bool add_semi_at_eol_; - // Whether we just saw a magic go:nointerface comment. - bool saw_nointerface_; + // Pragmas for the next function, from magic comments. + unsigned int pragmas_; // The external name to use for a function declaration, from a magic // //extern comment. std::string extern_; + // The list of //go:linkname comments. + std::map linknames_; }; #endif // !defined(GO_LEX_H) diff --git a/gcc/go/gofrontend/parse.cc b/gcc/go/gofrontend/parse.cc index d9f20401def..cb7f9664aaf 100644 --- a/gcc/go/gofrontend/parse.cc +++ b/gcc/go/gofrontend/parse.cc @@ -1305,10 +1305,10 @@ Parse::declaration() { const Token* token = this->peek_token(); - bool saw_nointerface = this->lex_->get_and_clear_nointerface(); - if (saw_nointerface && !token->is_keyword(KEYWORD_FUNC)) + unsigned int pragmas = this->lex_->get_and_clear_pragmas(); + if (pragmas != 0 && !token->is_keyword(KEYWORD_FUNC)) warning_at(token->location(), 0, - "ignoring magic //go:nointerface comment before non-method"); + "ignoring magic comment before non-function"); if (token->is_keyword(KEYWORD_CONST)) this->const_decl(); @@ -1317,7 +1317,7 @@ Parse::declaration() else if (token->is_keyword(KEYWORD_VAR)) this->var_decl(); else if (token->is_keyword(KEYWORD_FUNC)) - this->function_decl(saw_nointerface); + this->function_decl(pragmas); else { error_at(this->location(), "expected declaration"); @@ -2236,13 +2236,12 @@ Parse::simple_var_decl_or_assignment(const std::string& name, // __asm__ "(" string_lit ")" . // This extension means a function whose real name is the identifier // inside the asm. This extension will be removed at some future -// date. It has been replaced with //extern comments. - -// SAW_NOINTERFACE is true if we saw a magic //go:nointerface comment, -// which means that we omit the method from the type descriptor. +// date. It has been replaced with //extern or //go:linkname comments. +// +// PRAGMAS is a bitset of magic comments. void -Parse::function_decl(bool saw_nointerface) +Parse::function_decl(unsigned int pragmas) { go_assert(this->peek_token()->is_keyword(KEYWORD_FUNC)); Location location = this->location(); @@ -2257,12 +2256,6 @@ Parse::function_decl(bool saw_nointerface) rec = this->receiver(); token = this->peek_token(); } - else if (saw_nointerface) - { - warning_at(location, 0, - "ignoring magic //go:nointerface comment before non-method"); - saw_nointerface = false; - } if (!token->is_identifier()) { @@ -2320,7 +2313,69 @@ Parse::function_decl(bool saw_nointerface) semi_loc)); } - if (!this->peek_token()->is_op(OPERATOR_LCURLY)) + static struct { + unsigned int bit; + const char* name; + bool decl_ok; + bool func_ok; + bool method_ok; + } pragma_check[] = + { + { GOPRAGMA_NOINTERFACE, "nointerface", false, false, true }, + { GOPRAGMA_NOESCAPE, "noescape", true, false, false }, + { GOPRAGMA_NORACE, "norace", false, true, true }, + { GOPRAGMA_NOSPLIT, "nosplit", false, true, true }, + { GOPRAGMA_NOINLINE, "noinline", false, true, true }, + { GOPRAGMA_SYSTEMSTACK, "systemstack", false, true, true }, + { GOPRAGMA_NOWRITEBARRIER, "nowritebarrier", false, true, true }, + { GOPRAGMA_NOWRITEBARRIERREC, "nowritebarrierrec", false, true, true }, + { GOPRAGMA_CGOUNSAFEARGS, "cgo_unsafe_args", false, true, true }, + { GOPRAGMA_UINTPTRESCAPES, "uintptrescapes", true, true, true }, + }; + + bool is_decl = !this->peek_token()->is_op(OPERATOR_LCURLY); + if (pragmas != 0) + { + for (size_t i = 0; + i < sizeof(pragma_check) / sizeof(pragma_check[0]); + ++i) + { + if ((pragmas & pragma_check[i].bit) == 0) + continue; + + if (is_decl) + { + if (pragma_check[i].decl_ok) + continue; + warning_at(location, 0, + ("ignoring magic //go:%s comment " + "before declaration"), + pragma_check[i].name); + } + else if (rec == NULL) + { + if (pragma_check[i].func_ok) + continue; + warning_at(location, 0, + ("ignoring magic //go:%s comment " + "before function definition"), + pragma_check[i].name); + } + else + { + if (pragma_check[i].method_ok) + continue; + warning_at(location, 0, + ("ignoring magic //go:%s comment " + "before method definition"), + pragma_check[i].name); + } + + pragmas &= ~ pragma_check[i].bit; + } + } + + if (is_decl) { if (named_object == NULL) { @@ -2353,10 +2408,8 @@ Parse::function_decl(bool saw_nointerface) } } - if (saw_nointerface) - warning_at(location, 0, - ("ignoring magic //go:nointerface comment " - "before declaration")); + if (pragmas != 0 && named_object->is_function_declaration()) + named_object->func_declaration_value()->set_pragmas(pragmas); } else { @@ -2372,10 +2425,11 @@ Parse::function_decl(bool saw_nointerface) named_object = this->gogo_->start_function(name, fntype, true, location); Location end_loc = this->block(); this->gogo_->finish_function(end_loc); - if (saw_nointerface + + if (pragmas != 0 && !this->is_erroneous_function_ && named_object->is_function()) - named_object->func_value()->set_nointerface(); + named_object->func_value()->set_pragmas(pragmas); this->is_erroneous_function_ = hold_is_erroneous_function; } } diff --git a/gcc/go/gofrontend/parse.h b/gcc/go/gofrontend/parse.h index 734071a7377..a7118e77c17 100644 --- a/gcc/go/gofrontend/parse.h +++ b/gcc/go/gofrontend/parse.h @@ -209,7 +209,7 @@ class Parse void simple_var_decl_or_assignment(const std::string&, Location, bool may_be_composite_lit, Range_clause*, Type_switch*); - void function_decl(bool saw_nointerface); + void function_decl(unsigned int pragmas); Typed_identifier* receiver(); Expression* operand(bool may_be_sink, bool *is_parenthesized); Expression* enclosing_var_reference(Named_object*, Named_object*, -- 2.30.2