As part of a code analysis experiment, the gc compiler implements a feature based on comments: if a //go:nointerface comment appears before a method for a type, that method is not included in any interfaces. This works in conjunction with a field tracking feature, not yet implemented in gccgo, that lets a program determine which struct fields are actually referenced. This patch implements //go:nointerface for gccgo. Bootstrapped and ran Go testsuite on x86_64-unknown-linux-gnu. Committed to mainline.
Ian
diff -r 2fe578353c73 go/gogo.cc --- a/go/gogo.cc Sat Nov 24 16:51:19 2012 -0800 +++ b/go/gogo.cc Tue Nov 27 16:05:11 2012 -0800 @@ -3074,8 +3074,8 @@ : type_(type), enclosing_(enclosing), results_(NULL), closure_var_(NULL), block_(block), location_(location), labels_(), local_type_count_(0), fndecl_(NULL), defer_stack_(NULL), - results_are_named_(false), calls_recover_(false), is_recover_thunk_(false), - has_recover_thunk_(false) + results_are_named_(false), nointerface_(false), calls_recover_(false), + is_recover_thunk_(false), has_recover_thunk_(false) { } diff -r 2fe578353c73 go/gogo.h --- a/go/gogo.h Sat Nov 24 16:51:19 2012 -0800 +++ b/go/gogo.h Tue Nov 27 16:05:11 2012 -0800 @@ -911,6 +911,24 @@ results_are_named() const { return this->results_are_named_; } + // Whether this method should not be included in the type + // descriptor. + bool + nointerface() const + { + go_assert(this->is_method()); + return this->nointerface_; + } + + // Record that this method should not be included in the type + // descriptor. + void + set_nointerface() + { + go_assert(this->is_method()); + this->nointerface_ = true; + } + // Add a new field to the closure variable. void add_closure_field(Named_object* var, Location loc) @@ -1113,6 +1131,8 @@ Temporary_statement* defer_stack_; // True if the result variables are named. bool results_are_named_; + // True if this method should not be included in the type descriptor. + bool nointerface_; // True if this function calls the predeclared recover function. bool calls_recover_; // True if this a thunk built for a function which calls recover. diff -r 2fe578353c73 go/lex.cc --- a/go/lex.cc Sat Nov 24 16:51:19 2012 -0800 +++ b/go/lex.cc Tue Nov 27 16:05:11 2012 -0800 @@ -442,7 +442,8 @@ 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), extern_() + lineoff_(0), lineno_(0), add_semi_at_eol_(false), saw_nointerface_(false), + extern_() { this->linebuf_ = new char[this->linebufsize_]; this->linemap_->start_file(input_file_name, 0); @@ -1704,6 +1705,12 @@ this->extern_ = std::string(p, plend - p); } + // 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 && memcmp(p, "go:nointerface", 14) == 0) + this->saw_nointerface_ = true; + while (p < pend) { this->lineoff_ = p - this->linebuf_; diff -r 2fe578353c73 go/lex.h --- a/go/lex.h Sat Nov 24 16:51:19 2012 -0800 +++ b/go/lex.h Tue Nov 27 16:05:11 2012 -0800 @@ -349,6 +349,16 @@ extern_name() const { return this->extern_; } + // Return whether we have seen a //go:nointerface comment, clearing + // the flag. + bool + get_and_clear_nointerface() + { + bool ret = this->saw_nointerface_; + this->saw_nointerface_ = false; + return ret; + } + // Return whether the identifier NAME should be exported. NAME is a // mangled name which includes only ASCII characters. static bool @@ -483,6 +493,8 @@ 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_; // The external name to use for a function declaration, from a magic // //extern comment. std::string extern_; diff -r 2fe578353c73 go/parse.cc --- a/go/parse.cc Sat Nov 24 16:51:19 2012 -0800 +++ b/go/parse.cc Tue Nov 27 16:05:11 2012 -0800 @@ -1280,6 +1280,12 @@ 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)) + warning_at(token->location(), 0, + "ignoring magic //go:nointerface comment before non-method"); + if (token->is_keyword(KEYWORD_CONST)) this->const_decl(); else if (token->is_keyword(KEYWORD_TYPE)) @@ -1287,7 +1293,7 @@ else if (token->is_keyword(KEYWORD_VAR)) this->var_decl(); else if (token->is_keyword(KEYWORD_FUNC)) - this->function_decl(); + this->function_decl(saw_nointerface); else { error_at(this->location(), "expected declaration"); @@ -2166,8 +2172,11 @@ // 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. + void -Parse::function_decl() +Parse::function_decl(bool saw_nointerface) { go_assert(this->peek_token()->is_keyword(KEYWORD_FUNC)); Location location = this->location(); @@ -2180,6 +2189,12 @@ 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()) { @@ -2256,6 +2271,11 @@ } } } + + if (saw_nointerface) + warning_at(location, 0, + ("ignoring magic //go:nointerface comment " + "before declaration")); } else { @@ -2268,9 +2288,13 @@ this->gogo_->add_erroneous_name(name); name = this->gogo_->pack_hidden_name("_", false); } - this->gogo_->start_function(name, fntype, true, location); + named_object = this->gogo_->start_function(name, fntype, true, location); Location end_loc = this->block(); this->gogo_->finish_function(end_loc); + if (saw_nointerface + && !this->is_erroneous_function_ + && named_object->is_function()) + named_object->func_value()->set_nointerface(); this->is_erroneous_function_ = hold_is_erroneous_function; } } diff -r 2fe578353c73 go/parse.h --- a/go/parse.h Sat Nov 24 16:51:19 2012 -0800 +++ b/go/parse.h Tue Nov 27 16:05:11 2012 -0800 @@ -214,7 +214,7 @@ void simple_var_decl_or_assignment(const std::string&, Location, bool may_be_composite_lit, Range_clause*, Type_switch*); - void function_decl(); + void function_decl(bool saw_nointerface); Typed_identifier* receiver(); Expression* operand(bool may_be_sink); Expression* enclosing_var_reference(Named_object*, Named_object*, diff -r 2fe578353c73 go/types.cc --- a/go/types.cc Sat Nov 24 16:51:19 2012 -0800 +++ b/go/types.cc Tue Nov 27 16:05:11 2012 -0800 @@ -2068,6 +2068,13 @@ continue; if (only_value_methods && !p->second->is_value_method()) continue; + + // This is where we implement the magic //go:nointerface + // comment. If we saw that comment, we don't add this + // method to the type descriptor. + if (p->second->nointerface()) + continue; + smethods.push_back(std::make_pair(p->first, p->second)); } } @@ -6891,6 +6898,24 @@ } return false; } + + // If the magic //go:nointerface comment was used, the method + // may not be used to implement interfaces. + if (m->nointerface()) + { + if (reason != NULL) + { + std::string n = Gogo::message_name(p->name()); + size_t len = 100 + n.length(); + char* buf = new char[len]; + snprintf(buf, len, + _("method %s%s%s is marked go:nointerface"), + open_quote, n.c_str(), close_quote); + reason->assign(buf); + delete[] buf; + } + return false; + } } return true; @@ -7530,6 +7555,15 @@ return bme; } +// Return whether this method should not participate in interfaces. + +bool +Named_method::do_nointerface() const +{ + Named_object* no = this->named_object_; + return no->is_function() && no->func_value()->nointerface(); +} + // Class Interface_method. // Bind a method to an object. @@ -8834,6 +8868,9 @@ Type::build_one_stub_method(gogo, m, buf, stub_params, fntype->is_varargs(), location); gogo->finish_function(fntype->location()); + + if (m->nointerface() && stub->is_function()) + stub->func_value()->set_nointerface(); } m->set_stub_object(stub); diff -r 2fe578353c73 go/types.h --- a/go/types.h Sat Nov 24 16:51:19 2012 -0800 +++ b/go/types.h Tue Nov 27 16:05:11 2012 -0800 @@ -179,6 +179,12 @@ this->stub_ = no; } + // Return true if this method should not participate in any + // interfaces. + bool + nointerface() const + { return this->do_nointerface(); } + protected: // These objects are only built by the child classes. Method(const Field_indexes* field_indexes, unsigned int depth, @@ -204,6 +210,10 @@ virtual Expression* do_bind_method(Expression* expr, Location location) const = 0; + // Return whether this method should not participate in interfaces. + virtual bool + do_nointerface() const = 0; + private: // The sequence of field indexes used for this method. If this is // NULL, then the method is defined for the current type. @@ -254,6 +264,10 @@ Expression* do_bind_method(Expression* expr, Location location) const; + // Return whether this method should not participate in interfaces. + bool + do_nointerface() const; + private: // The method itself. For a method which needs a stub, this starts // out as the underlying method, and is later replaced with the stub @@ -295,6 +309,11 @@ Expression* do_bind_method(Expression* expr, Location location) const; + // Return whether this method should not participate in interfaces. + bool + do_nointerface() const + { return false; } + private: // The name of the interface method to call. std::string name_;