This patch by Cherry Zhang adds a new -fgo-debug-escape-hash option to the Go frontend. This option takes a string that can be used to select specific functions on which to run escape analysis. This makes it easier to find escape analysis bugs by doing a binary search over all the functions, enabling or disabling escape analysis for each one. Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu. Committed to mainline.
Ian 2018-01-09 Cherry Zhang <cherr...@google.com> * lang.opt (fgo-debug-escape-hash): New option. * go-c.h (struct go_create_gogo_args): Add debug_escape_hash field. * go-lang.c (go_langhook_init): Set debug_escape_hash field. * gccgo.texi (Invoking gccgo): Document -fgo-debug-escape-hash.
Index: gcc/go/gccgo.texi =================================================================== --- gcc/go/gccgo.texi (revision 256366) +++ gcc/go/gccgo.texi (working copy) @@ -239,6 +239,13 @@ heap when possible. In the future this Output escape analysis debugging information. Larger values of @var{n} generate more information. +@item -fgo-debug-escape-hash=@var{n} +@cindex @option{-fgo-debug-escape-hash} +A hash value to debug escape analysis. @var{n} is a binary string. +This runs escape analysis only on functions whose names hash to values +that match the given suffix @var{n}. This can be used to binary +search across functions to uncover escape analysis bugs. + @item -fgo-c-header=@var{file} @cindex @option{-fgo-c-header} Write top-level named Go struct definitions to @var{file} as C code. Index: gcc/go/go-c.h =================================================================== --- gcc/go/go-c.h (revision 256366) +++ gcc/go/go-c.h (working copy) @@ -47,6 +47,7 @@ struct go_create_gogo_args bool check_divide_overflow; bool compiling_runtime; int debug_escape_level; + const char* debug_escape_hash; int64_t nil_check_size_threshold; }; Index: gcc/go/go-lang.c =================================================================== --- gcc/go/go-lang.c (revision 256366) +++ gcc/go/go-lang.c (working copy) @@ -116,6 +116,7 @@ go_langhook_init (void) args.check_divide_overflow = go_check_divide_overflow; args.compiling_runtime = go_compiling_runtime; args.debug_escape_level = go_debug_escape_level; + args.debug_escape_hash = go_debug_escape_hash; args.nil_check_size_threshold = TARGET_AIX ? -1 : 4096; args.linemap = go_get_linemap(); args.backend = go_get_backend(); Index: gcc/go/gofrontend/escape.cc =================================================================== --- gcc/go/gofrontend/escape.cc (revision 256389) +++ gcc/go/gofrontend/escape.cc (working copy) @@ -19,6 +19,7 @@ #include "ast-dump.h" #include "go-optimize.h" #include "go-diagnostics.h" +#include "go-sha1.h" // class Node. @@ -821,6 +822,39 @@ Escape_note::parse_tag(std::string* tag) Go_optimize optimize_allocation_flag("allocs"); +// A helper function to compute whether a function name has a +// matching hash value. + +static bool +escape_hash_match(std::string suffix, std::string name) +{ + if (suffix.empty()) + return true; + if (suffix.at(0) == '-') + return !escape_hash_match(suffix.substr(1), name); + + const char* p = name.c_str(); + Go_sha1_helper* sha1_helper = go_create_sha1_helper(); + sha1_helper->process_bytes(p, strlen(p)); + std::string s = sha1_helper->finish(); + delete sha1_helper; + + int j = suffix.size() - 1; + for (int i = s.size() - 1; i >= 0; i--) + { + char c = s.at(i); + for (int k = 0; k < 8; k++, j--, c>>=1) + { + if (j < 0) + return true; + char bit = suffix.at(j) - '0'; + if ((c&1) != bit) + return false; + } + } + return false; +} + // Analyze the program flow for escape information. void @@ -839,11 +873,46 @@ Gogo::analyze_escape() // information in this package. this->discover_analysis_sets(); + if (!this->debug_escape_hash().empty()) + std::cerr << "debug-escape-hash " << this->debug_escape_hash() << "\n"; + for (std::vector<Analysis_set>::iterator p = this->analysis_sets_.begin(); p != this->analysis_sets_.end(); ++p) { std::vector<Named_object*> stack = p->first; + + if (!this->debug_escape_hash().empty()) + { + bool match = false; + for (std::vector<Named_object*>::const_iterator fn = stack.begin(); + fn != stack.end(); + ++fn) + match = match || escape_hash_match(this->debug_escape_hash(), (*fn)->message_name()); + if (!match) + { + // Escape analysis won't run on these functions, but still + // need to tag them, so the caller knows. + for (std::vector<Named_object*>::iterator fn = stack.begin(); + fn != stack.end(); + ++fn) + if ((*fn)->is_function()) + { + Function_type* fntype = (*fn)->func_value()->type(); + fntype->set_is_tagged(); + + std::cerr << "debug-escape-hash disables " << debug_function_name(*fn) << "\n"; + } + + continue; + } + for (std::vector<Named_object*>::const_iterator fn = stack.begin(); + fn != stack.end(); + ++fn) + if ((*fn)->is_function()) + std::cerr << "debug-escape-hash triggers " << debug_function_name(*fn) << "\n"; + } + Escape_context* context = new Escape_context(this, p->second); // Analyze the flow of each function; build the connection graph. Index: gcc/go/gofrontend/go.cc =================================================================== --- gcc/go/gofrontend/go.cc (revision 256366) +++ gcc/go/gofrontend/go.cc (working copy) @@ -41,6 +41,8 @@ go_create_gogo(const struct go_create_go if (args->c_header != NULL) ::gogo->set_c_header(args->c_header); ::gogo->set_debug_escape_level(args->debug_escape_level); + if (args->debug_escape_hash != NULL) + ::gogo->set_debug_escape_hash(args->debug_escape_hash); ::gogo->set_nil_check_size_threshold(args->nil_check_size_threshold); } Index: gcc/go/gofrontend/gogo.h =================================================================== --- gcc/go/gofrontend/gogo.h (revision 256366) +++ gcc/go/gofrontend/gogo.h (working copy) @@ -318,6 +318,16 @@ class Gogo set_debug_escape_level(int level) { this->debug_escape_level_ = level; } + // Return the hash for debug escape analysis. + std::string + debug_escape_hash() const + { return this->debug_escape_hash_; } + + // Set the hash value for debug escape analysis. + void + set_debug_escape_hash(const std::string& s) + { this->debug_escape_hash_ = s; } + // Return the size threshold used to determine whether to issue // a nil-check for a given pointer dereference. A threshold of -1 // implies that all potentially faulting dereference ops should @@ -1035,6 +1045,10 @@ class Gogo // The level of escape analysis debug information to emit, from the // -fgo-debug-escape option. int debug_escape_level_; + // A hash value for debug escape analysis, from the + // -fgo-debug-escape-hash option. The analysis is run only on + // functions with names that hash to the matching value. + std::string debug_escape_hash_; // Nil-check size threshhold. int64_t nil_check_size_threshold_; // A list of types to verify. Index: gcc/go/lang.opt =================================================================== --- gcc/go/lang.opt (revision 256366) +++ gcc/go/lang.opt (working copy) @@ -81,6 +81,10 @@ fgo-debug-escape Go Joined UInteger Var(go_debug_escape_level) Init(0) Emit debugging information related to the escape analysis pass when run with -fgo-optimize-allocs. +fgo-debug-escape-hash= +Go Joined RejectNegative Var(go_debug_escape_hash) Init(0) +-fgo-debug-escape-hash=<string> Hash value to debug escape analysis. + o Go Joined Separate ; Documented in common.opt