diff options
-rw-r--r-- | ChangeLog | 35 | ||||
-rw-r--r-- | buildrun.cxx | 141 | ||||
-rw-r--r-- | elaborate.cxx | 311 | ||||
-rw-r--r-- | main.cxx | 24 | ||||
-rw-r--r-- | parse.cxx | 35 | ||||
-rw-r--r-- | session.h | 2 | ||||
-rw-r--r-- | stap.1.in | 20 | ||||
-rw-r--r-- | stapfuncs.5.in | 6 | ||||
-rw-r--r-- | staptree.cxx | 149 | ||||
-rw-r--r-- | staptree.h | 49 | ||||
-rw-r--r-- | tapset/logging.stp | 6 | ||||
-rw-r--r-- | tapsets.cxx | 83 | ||||
-rwxr-xr-x | testsuite/semko/eleven.stp | 12 | ||||
-rwxr-xr-x | testsuite/semko/thirty.stp | 2 | ||||
-rwxr-xr-x | testsuite/semko/thirtyone.stp | 2 | ||||
-rwxr-xr-x | testsuite/semko/twenty.stp | 2 | ||||
-rwxr-xr-x | testsuite/semko/twentyfive.stp | 2 | ||||
-rwxr-xr-x | testsuite/semko/twentyfour.stp | 2 | ||||
-rwxr-xr-x | testsuite/semko/twentynine.stp | 4 | ||||
-rwxr-xr-x | testsuite/semko/twentyseven.stp | 2 | ||||
-rwxr-xr-x | testsuite/semko/twentysix.stp | 2 | ||||
-rwxr-xr-x | testsuite/semok/optimize.stp | 18 | ||||
-rwxr-xr-x | testsuite/transko/one.stp | 4 | ||||
-rwxr-xr-x | testsuite/transok/five.stp | 2 | ||||
-rw-r--r-- | translate.cxx | 48 |
25 files changed, 726 insertions, 237 deletions
@@ -1,3 +1,38 @@ +2006-01-24 Frank Ch. Eigler <fche@elastic.org> + + PR 2060 etc. + * tapsets.cxx (visit_target_symbol): Tolerate failed resolution by + letting target_symbol instance pass through to optimizer and + type checker. + * elaborate.cxx (semantic_pass_optimize): New family of functions and + associated visitor classes. + (visit_for_loop): Tolerate absent init/incr clauses. + (semantic_pass): Invoke unless unoptimized (-u) option given. + * main.cxx, session.h: Add support for flag. + * staptree.cxx (visit_for_loop): Tolerate absent init/incr clauses. + (traversing_visitor::visit_arrayindex): Visit the index expressions. + (functioncall_traversing_visitor): New class. + (varuse_tracking_visitor): New class. + * staptree.h: Corresponding changes. + * parse.cxx (parse_for_loop): Represent absent init/incr expressions + with null statement pointer instead of optimized-out dummy numbers. + * stap.1.in: Document optimization. + * testsuite/{semko,transko}/*.stp: Added "-u" or other code to many + tests to check bad code without optimizer elision. + * testsuite/semok/optimize.stp: New test. + + * elaborate.cxx (unresolved, invalid, mismatch): Standardize error + message wording. + * stapfuncs.5.in: Tweak print/printf docs. + * tapset/logging.stp: Remove redundant "print" auxiliary function, + since it's a translator built-in. + * testsuite/transok/five.stp: Extend test. + * translate.cxx (emit_symbol_data): Put symbol table into a separate + temporary header file, to make "-p3" output easier on the eyes. + * buildrun.cxx (compile_pass): Eliminate test-mode support throughout. + * main.cxx, session.h, translate.cxx: Ditto. + * main.cxx (main): For last-pass=2 runs, print post-optimization ASTs. + 2006-01-18 Josh Stone <joshua.i.stone@intel.com> * tapsets.cxx (profile_derived_probe::emit_probe_entries): Setup diff --git a/buildrun.cxx b/buildrun.cxx index d678e874..744ff988 100644 --- a/buildrun.cxx +++ b/buildrun.cxx @@ -69,69 +69,34 @@ compile_pass (systemtap_session& s) // XXX // o << "CFLAGS += -ftime-report" << endl; - if (s.test_mode) - { - string module_dir = string("/lib/modules/") - + s.kernel_release + "/build"; - - o << "CFLAGS += -I \"" << module_dir << "/include\"" << endl; - o << "CFLAGS += -I \"" << s.runtime_path << "/user\"" << endl; - o << "CFLAGS += -I \"" << s.runtime_path << "\"" << endl; - o << "CFLAGS += -I \"" << module_dir << "/include/asm/mach-default\"" << endl; - o << s.module_name << ": " << s.translated_source << endl; - o << "\t$(CC) $(CFLAGS) -o " << s.module_name - << " " << s.translated_source << endl; - } - else - { - // Assumes linux 2.6 kbuild - o << "CFLAGS += -Wno-unused -Werror" << endl; - o << "CFLAGS += -I \"" << s.runtime_path << "\"" << endl; - o << "CFLAGS += -I \"" << s.runtime_path << "/relayfs\"" << endl; - o << "obj-m := " << s.module_name << ".o" << endl; - } + // Assumes linux 2.6 kbuild + o << "CFLAGS += -Wno-unused -Werror" << endl; + o << "CFLAGS += -I \"" << s.runtime_path << "\"" << endl; + o << "CFLAGS += -I \"" << s.runtime_path << "/relayfs\"" << endl; + o << "obj-m := " << s.module_name << ".o" << endl; o.close (); // Run make - if (s.test_mode) - { - string make_cmd = string("make -C \"") + s.tmpdir + "\""; - - if (s.verbose) - make_cmd += " V=1"; - else - make_cmd += " -s >/dev/null 2>&1"; - - if (s.verbose) clog << "Running " << make_cmd << endl; - rc = system (make_cmd.c_str()); - - if (s.verbose) clog << "Pass 4: compiled into \"" - << s.module_name - << "\"" << endl; - } + string module_dir = string("/lib/modules/") + + s.kernel_release + "/build"; + string make_cmd = string("make") + + string (" -C \"") + module_dir + string("\""); + make_cmd += string(" M=\"") + s.tmpdir + string("\" modules"); + + if (s.verbose) + make_cmd += " V=1"; else - { - string module_dir = string("/lib/modules/") - + s.kernel_release + "/build"; - string make_cmd = string("make") - + string (" -C \"") + module_dir + string("\""); - make_cmd += string(" M=\"") + s.tmpdir + string("\" modules"); - - if (s.verbose) - make_cmd += " V=1"; - else - make_cmd += " -s >/dev/null 2>&1"; - - if (s.verbose) clog << "Running " << make_cmd << endl; - rc = system (make_cmd.c_str()); - - - if (s.verbose) clog << "Pass 4: compiled into \"" - << s.module_name << ".ko" - << "\"" << endl; - } - + make_cmd += " -s >/dev/null 2>&1"; + + if (s.verbose) clog << "Running " << make_cmd << endl; + rc = system (make_cmd.c_str()); + + + if (s.verbose) clog << "Pass 4: compiled into \"" + << s.module_name << ".ko" + << "\"" << endl; + return rc; } @@ -141,41 +106,31 @@ run_pass (systemtap_session& s) { int rc = 0; - if (s.test_mode) - { - string run_cmd = s.tmpdir + "/" + s.module_name; - - if (s.verbose) clog << "Running " << run_cmd << endl; - rc = system (run_cmd.c_str ()); - } - else // real run - { - // for now, just spawn stpd - string stpd_cmd = string("sudo ") - + string(PKGLIBDIR) + "/stpd " - + (s.bulk_mode ? "" : "-r ") - + (s.verbose ? "" : "-q ") - + (s.output_file.empty() ? "" : "-o " + s.output_file + " "); - - stpd_cmd += "-d " + stringify(getpid()) + " "; - - if (s.cmd != "") - stpd_cmd += "-c \"" + s.cmd + "\" "; - - if (s.target_pid) - stpd_cmd += "-t " + stringify(s.target_pid) + " "; - - if (s.buffer_size) - stpd_cmd += "-b " + stringify(s.buffer_size) + " "; - - stpd_cmd += s.tmpdir + "/" + s.module_name + ".ko"; - - if (s.verbose) clog << "Running " << stpd_cmd << endl; - - signal (SIGHUP, SIG_IGN); - signal (SIGINT, SIG_IGN); - rc = system (stpd_cmd.c_str ()); - } + // for now, just spawn stpd + string stpd_cmd = string("sudo ") + + string(PKGLIBDIR) + "/stpd " + + (s.bulk_mode ? "" : "-r ") + + (s.verbose ? "" : "-q ") + + (s.output_file.empty() ? "" : "-o " + s.output_file + " "); + + stpd_cmd += "-d " + stringify(getpid()) + " "; + + if (s.cmd != "") + stpd_cmd += "-c \"" + s.cmd + "\" "; + + if (s.target_pid) + stpd_cmd += "-t " + stringify(s.target_pid) + " "; + + if (s.buffer_size) + stpd_cmd += "-b " + stringify(s.buffer_size) + " "; + + stpd_cmd += s.tmpdir + "/" + s.module_name + ".ko"; + + if (s.verbose) clog << "Running " << stpd_cmd << endl; + + signal (SIGHUP, SIG_IGN); + signal (SIGINT, SIG_IGN); + rc = system (stpd_cmd.c_str ()); return rc; } diff --git a/elaborate.cxx b/elaborate.cxx index 7fc9784e..613d85ae 100644 --- a/elaborate.cxx +++ b/elaborate.cxx @@ -751,6 +751,7 @@ semantic_pass_stats (systemtap_session & sess) static int semantic_pass_symbols (systemtap_session&); +static int semantic_pass_optimize (systemtap_session&); static int semantic_pass_types (systemtap_session&); static int semantic_pass_vars (systemtap_session&); static int semantic_pass_stats (systemtap_session&); @@ -851,6 +852,7 @@ semantic_pass (systemtap_session& s) register_standard_tapsets(s); rc = semantic_pass_symbols (s); + if (rc == 0 && ! s.unoptimized) rc = semantic_pass_optimize (s); if (rc == 0) rc = semantic_pass_types (s); if (rc == 0) rc = semantic_pass_vars (s); if (rc == 0) rc = semantic_pass_stats (s); @@ -1172,6 +1174,300 @@ symresolution_info::find_function (const string& name, unsigned arity) } + +// ------------------------------------------------------------------------ +// optimization + + +// Do away with functiondecls that are never (transitively) called +// from probes. +void semantic_pass_opt1 (systemtap_session& s, bool& relaxed_p) +{ + functioncall_traversing_visitor ftv; + for (unsigned i=0; i<s.probes.size(); i++) + s.probes[i]->body->visit (& ftv); + for (unsigned i=0; i<s.functions.size(); /* see below */) + { + if (ftv.traversed.find(s.functions[i]) == ftv.traversed.end()) + { + if (s.verbose) + clog << "Eliding unused function " << s.functions[i]->name + << endl; + s.functions.erase (s.functions.begin() + i); + relaxed_p = false; + // NB: don't increment i + } + else + i++; + } +} + + +// ------------------------------------------------------------------------ + +// Do away with local & global variables that are never +// written nor read. +void semantic_pass_opt2 (systemtap_session& s, bool& relaxed_p) +{ + varuse_collecting_visitor vut; + + for (unsigned i=0; i<s.probes.size(); i++) + s.probes[i]->body->visit (& vut); + // NB: Since varuse_collecting_visitor also traverses down + // actually called functions, we don't need to explicitly + // iterate over them. Uncalled ones should have been pruned + // in _opt1 above. + // + // for (unsigned i=0; i<s.functions.size(); i++) + // s.functions[i]->body->visit (& vut); + + // Now in vut.read/written, we have a mixture of all locals, globals + + for (unsigned i=0; i<s.probes.size(); i++) + for (unsigned j=0; j<s.probes[i]->locals.size(); /* see below */) + { + vardecl* l = s.probes[i]->locals[j]; + if (vut.read.find (l) == vut.read.end() && + vut.written.find (l) == vut.written.end()) + { + if (s.verbose) + clog << "Eliding unused local variable " + << l->name << " in probe #" << i << endl; + s.probes[i]->locals.erase(s.probes[i]->locals.begin() + j); + relaxed_p = false; + // don't increment j + } + else + j++; + } + for (unsigned i=0; i<s.functions.size(); i++) + for (unsigned j=0; j<s.functions[i]->locals.size(); /* see below */) + { + vardecl* l = s.functions[i]->locals[j]; + if (vut.read.find (l) == vut.read.end() && + vut.written.find (l) == vut.written.end()) + { + if (s.verbose) + clog << "Eliding unused local variable " + << l->name << " in function " << s.functions[i]->name + << endl; + s.functions[i]->locals.erase(s.functions[i]->locals.begin() + j); + relaxed_p = false; + // don't increment j + } + else + j++; + } + for (unsigned i=0; i<s.globals.size(); /* see below */) + { + vardecl* l = s.globals[i]; + if (vut.read.find (l) == vut.read.end() && + vut.written.find (l) == vut.written.end()) + { + if (s.verbose) + clog << "Eliding unused global variable " + << l->name << endl; + s.globals.erase(s.globals.begin() + i); + relaxed_p = false; + // don't increment i + } + else + i++; + } +} + + +// ------------------------------------------------------------------------ + +struct dead_assignment_remover: public traversing_visitor +{ + systemtap_session& session; + bool& relaxed_p; + const varuse_collecting_visitor& vut; + expression** current_expr; + + dead_assignment_remover(systemtap_session& s, bool& r, + const varuse_collecting_visitor& v): + session(s), relaxed_p(r), vut(v), current_expr(0) {} + + void visit_expr_statement (expr_statement* s); + // XXX: and other places where an assignment may be nested + + void visit_assignment (assignment* e); +}; + + +void +dead_assignment_remover::visit_expr_statement (expr_statement* s) +{ + expression** last_expr = current_expr; + current_expr = & s->value; + s->value->visit (this); + current_expr = last_expr; +} + + +void +dead_assignment_remover::visit_assignment (assignment* e) +{ + symbol* left = get_symbol_within_expression (e->left); + vardecl* leftvar = left->referent; + if (*current_expr == e) // we're not nested any deeper than expected + { + // clog << "Checking assignment to " << leftvar->name << " at " << *e->tok << endl; + if (vut.read.find(leftvar) == vut.read.end()) // var never read? + { + if (session.verbose) + clog << "Eliding assignment to " << leftvar->name + << " at " << *e->tok << endl; + *current_expr = e->right; // goodbye assignment* + relaxed_p = false; + } + } +} + + +// Let's remove assignments to variables that are never read. We +// rewrite "(foo = expr)" as "(expr)". This makes foo a candidate to +// be optimized away as an unused variable, and expr a candidate to be +// removed as a side-effect-free statement expression. Wahoo! +void semantic_pass_opt3 (systemtap_session& s, bool& relaxed_p) +{ + // Recompute the varuse data, which will probably match the opt2 + // copy of the computation, except for those totally unused + // variables that opt2 removed. + varuse_collecting_visitor vut; + for (unsigned i=0; i<s.probes.size(); i++) + s.probes[i]->body->visit (& vut); // includes reachable functions too + + dead_assignment_remover dar (s, relaxed_p, vut); + // This instance may be reused for multiple probe/function body trims. + + for (unsigned i=0; i<s.probes.size(); i++) + s.probes[i]->body->visit (& dar); + for (unsigned i=0; i<s.functions.size(); i++) + s.functions[i]->body->visit (& dar); + // The rewrite operation is performed within the visitor. +} + + +// ------------------------------------------------------------------------ + +struct dead_stmtexpr_remover: public traversing_visitor +{ + systemtap_session& session; + bool& relaxed_p; + statement** current_stmt; // pointer to current stmt* being iterated + + dead_stmtexpr_remover(systemtap_session& s, bool& r): + session(s), relaxed_p(r), current_stmt(0) {} + + void visit_block (block *s); + // XXX: and other places where stmt_expr's might be nested + + void visit_expr_statement (expr_statement *s); +}; + + +void +dead_stmtexpr_remover::visit_block (block *s) +{ + for (unsigned i=0; i<s->statements.size(); i++) + { + statement** last_stmt = current_stmt; + current_stmt = & s->statements[i]; + s->statements[i]->visit (this); + current_stmt = last_stmt; + } +} + + +void +dead_stmtexpr_remover::visit_expr_statement (expr_statement *s) +{ + // Run a varuse query against the operand expression. If it has no + // side-effects, replace the entire statement expression by a null + // statement. This replacement is done by overwriting the + // current_stmt pointer. + // + // Unlike many other visitors, we do *not* traverse this outermost + // one into the expression subtrees. There is no need - no + // expr_statement nodes will be found there. (Function bodies + // need to be visited explicitly by our caller.) + // + // NB. While we don't share nodes in the parse tree, let's not + // deallocate *s anyway, just in case... + + varuse_collecting_visitor vut; + s->value->visit (& vut); + if (vut.written.empty() && !vut.embedded_seen) + { + if (session.verbose) + clog << "Eliding side-effect-free expression " + << *s->tok << endl; + + null_statement* ns = new null_statement; + ns->tok = s->tok; + * current_stmt = ns; + // XXX: A null_statement carries more weight in the translator's + // output than a nonexistent statement. It might be nice to + // work a little harder and completely eliminate all traces of + // an elided statement. + + relaxed_p = false; + } +} + + +void semantic_pass_opt4 (systemtap_session& s, bool& relaxed_p) +{ + // Finally, let's remove some statement-expressions that have no + // side-effect. These should be exactly those whose private varuse + // visitors come back with an empty "written" and "embedded" lists. + + dead_stmtexpr_remover duv (s, relaxed_p); + // This instance may be reused for multiple probe/function body trims. + + for (unsigned i=0; i<s.probes.size(); i++) + s.probes[i]->body->visit (& duv); + for (unsigned i=0; i<s.functions.size(); i++) + s.functions[i]->body->visit (& duv); +} + + +static int +semantic_pass_optimize (systemtap_session& s) +{ + // In this pass, we attempt to rewrite probe/function bodies to + // eliminate some blatantly unnecessary code. This is run before + // type inference, but after symbol resolution and derived_probe + // creation. We run an outer "relaxation" loop that repeats the + // optimizations until none of them find anything to remove. + + int rc = 0; + + bool relaxed_p = false; + while (! relaxed_p) + { + relaxed_p = true; // until proven otherwise + + semantic_pass_opt1 (s, relaxed_p); + semantic_pass_opt2 (s, relaxed_p); + semantic_pass_opt3 (s, relaxed_p); + semantic_pass_opt4 (s, relaxed_p); + } + + if (s.probes.size() == 0) + { + cerr << "semantic error: no probes found." << endl; + rc = 1; + } + + return rc; +} + + + // ------------------------------------------------------------------------ // type resolution @@ -1568,6 +1864,11 @@ typeresolution_info::visit_symbol (symbol* e) void typeresolution_info::visit_target_symbol (target_symbol* e) { + // This occurs only if a target symbol was not resolved over in + // tapset.cxx land, that error was properly suppressed, and the + // later unused-expression-elimination pass didn't get rid of it + // either. So we have a target symbol that is believed to be of + // genuine use, yet unresolved by the provider. throw semantic_error("unresolved target-symbol expression", e->tok); } @@ -1730,11 +2031,11 @@ void typeresolution_info::visit_for_loop (for_loop* e) { t = pe_unknown; - e->init->visit (this); + if (e->init) e->init->visit (this); t = pe_long; e->cond->visit (this); t = pe_unknown; - e->incr->visit (this); + if (e->incr) e->incr->visit (this); t = pe_unknown; e->block->visit (this); } @@ -2056,7 +2357,7 @@ typeresolution_info::unresolved (const token* tok) if (assert_resolvability) { - cerr << "error: unresolved type for "; + cerr << "semantic error: unresolved type for "; if (tok) cerr << *tok; else @@ -2073,7 +2374,7 @@ typeresolution_info::invalid (const token* tok, exp_type pe) if (assert_resolvability) { - cerr << "error: invalid type " << pe << " for "; + cerr << "semantic error: invalid type " << pe << " for "; if (tok) cerr << *tok; else @@ -2090,7 +2391,7 @@ typeresolution_info::mismatch (const token* tok, exp_type t1, exp_type t2) if (assert_resolvability) { - cerr << "error: type mismatch for "; + cerr << "semantic error: type mismatch for "; if (tok) cerr << *tok; else @@ -60,7 +60,7 @@ usage (systemtap_session& s) << " -h show help" << endl << " -V show version" << endl << " -k keep temporary directory" << endl - // << " -t test mode" << (s.test_mode ? " [set]" : "") << endl + << " -u unoptimized translation" << (s.unoptimized ? " [set]" : "") << endl << " -g guru mode" << (s.guru_mode ? " [set]" : "") << endl << " -b bulk (relayfs) mode" << (s.bulk_mode ? " [set]" : "") << endl << " -s NUM buffer size in megabytes, instead of " @@ -120,9 +120,9 @@ main (int argc, char * const argv []) s.kernel_release = string (buf.release); s.architecture = string (buf.machine); s.verbose = false; - s.test_mode = false; s.guru_mode = false; s.bulk_mode = false; + s.unoptimized = false; s.buffer_size = 0; s.last_pass = 5; s.module_name = "stap_" + stringify(getpid()); @@ -145,7 +145,7 @@ main (int argc, char * const argv []) while (true) { - int grc = getopt (argc, argv, "hVvp:I:e:o:tR:r:m:kgc:x:D:bs:"); + int grc = getopt (argc, argv, "hVvp:I:e:o:R:r:m:kgc:x:D:bs:u"); if (grc < 0) break; switch (grc) @@ -182,10 +182,6 @@ main (int argc, char * const argv []) s.output_file = string (optarg); break; - case 't': - s.test_mode = true; - break; - case 'R': s.runtime_path = string (optarg); break; @@ -210,6 +206,10 @@ main (int argc, char * const argv []) s.bulk_mode = true; break; + case 'u': + s.unoptimized = true; + break; + case 's': s.buffer_size = atoi (optarg); if (s.buffer_size < 1 || s.buffer_size > 64) @@ -415,6 +415,11 @@ main (int argc, char * const argv []) v->printsig (cout); cout << endl; } + if (s.verbose) + { + f->body->print (cout); + cout << endl; + } } if (s.probes.size() > 0) @@ -433,6 +438,11 @@ main (int argc, char * const argv []) v->printsig (cout); cout << endl; } + if (s.verbose) + { + p->body->print (cout); + cout << endl; + } } } @@ -1232,11 +1232,8 @@ parser::parse_for_loop () t = peek (); if (t && t->type == tok_operator && t->content == ";") { - literal_number* l = new literal_number(0); - expr_statement* es = new expr_statement; - es->value = l; - s->init = es; - es->value->tok = es->tok = next (); + s->init = 0; + next (); } else { @@ -1266,11 +1263,8 @@ parser::parse_for_loop () t = peek (); if (t && t->type == tok_operator && t->content == ")") { - literal_number* l = new literal_number(2); - expr_statement* es = new expr_statement; - es->value = l; - s->incr = es; - es->value->tok = es->tok = next (); + s->incr = 0; + next (); } else { @@ -1301,23 +1295,12 @@ parser::parse_while_loop () throw parse_error ("expected '('"); // dummy init and incr fields - literal_number* l = new literal_number(0); - expr_statement* es = new expr_statement; - es->value = l; - s->init = es; - es->value->tok = es->tok = t; - - l = new literal_number(2); - es = new expr_statement; - es->value = l; - s->incr = es; - es->value->tok = es->tok = t; - + s->init = 0; + s->incr = 0; // condition s->cond = parse_expression (); - t = next (); if (! (t->type == tok_operator && t->content == ")")) throw parse_error ("expected ')'"); @@ -1808,6 +1791,12 @@ parser::parse_unary () expression* parser::parse_crement () // as in "increment" / "decrement" { + // NB: Ideally, we'd parse only a symbol as an operand to the + // *crement operators, instead of a general expression value. We'd + // need more complex lookahead code to tell apart the postfix cases. + // So we just punt, and leave it to pass-3 to signal errors on + // cases like "4++". + const token* t = peek (); if (t && t->type == tok_operator && (t->content == "++" || t->content == "--")) @@ -69,11 +69,11 @@ struct systemtap_session std::string cmd; int target_pid; int last_pass; - bool test_mode; bool verbose; bool keep_tmpdir; bool guru_mode; bool bulk_mode; + bool unoptimized; int buffer_size; // temporary directory for module builds etc. @@ -72,7 +72,7 @@ prints a list of supported options. .\" -t test mode .TP .B \-v -Verbose mode. Produces more informative output. +Verbose mode. Produce more informative output. .TP .B \-h Show help message. @@ -86,9 +86,12 @@ in order to examine the generated C code, or to reuse the compiled kernel object. .TP .B \-g -Guru mode. Enables parsing of unsafe expert-level constructs like +Guru mode. Enable parsing of unsafe expert-level constructs like embedded C. .TP +.B \-u +Unoptimized mode. Disable unused code elision during elaboration. +.TP .BI \-b Use relayfs-based bulk mode for kernel-to-user data transfer. .TP @@ -583,11 +586,11 @@ and all scripts (files named .IR *.stp ) found in a tapset directory. The directories listed with -.BR -I +.BR \-I are processed in sequence, each processed in "guru mode". For each directory, a number of subdirectories are also searched. These subdirectories are derived from the selected kernel version (the -.BR -R +.BR \-R option), in order to allow more kernel-version-specific scripts to override less specific ones. For example, for a kernel version @@ -617,6 +620,15 @@ appropriate kernel debugging information to be installed. In the associated probe handlers, target-side variables (whose names begin with "$") are found and have their run-time locations decoded. .PP +Next, all probes and functions are analyzed for optimization +opportunities, in order to remove variables, expressions, and +functions that have no useful value and no side-effect. Since this +process can hide latent code errors such as type mismatches or invalid +$target variables, it sometimes may be useful to disable the +optimizations with the +.BR \-u +option. +.PP Finally, all variable, function, parameter, array, and index types are inferred from context (literals and operators). Stopping the translator after pass 2 causes it to list all the probes, functions, diff --git a/stapfuncs.5.in b/stapfuncs.5.in index 56d6aed5..f68f462d 100644 --- a/stapfuncs.5.in +++ b/stapfuncs.5.in @@ -31,9 +31,9 @@ Log the given string to the common trace buffer. Append an implicit end-of-line. .TP -print:unknown (msg:string) -print:unknown (k:long) -Print the given integer or string to the common trace buffer. +print:unknown (...) +Print the given integer, string, or statistics value to +the common trace buffer. .TP printf:unknown (fmt:string, ...) diff --git a/staptree.cxx b/staptree.cxx index 0eb898ac..97b4bcc1 100644 --- a/staptree.cxx +++ b/staptree.cxx @@ -691,11 +691,11 @@ void block::print (ostream& o) const void for_loop::print (ostream& o) const { o << "for ("; - init->print (o); + if (init) init->print (o); o << "; "; cond->print (o); o << "; "; - incr->print (o); + if (incr) incr->print (o); o << ") "; block->print (o); } @@ -1262,9 +1262,9 @@ traversing_visitor::visit_if_statement (if_statement* s) void traversing_visitor::visit_for_loop (for_loop* s) { - s->init->visit (this); + if (s->init) s->init->visit (this); s->cond->visit (this); - s->incr->visit (this); + if (s->incr) s->incr->visit (this); s->block->visit (this); } @@ -1409,6 +1409,9 @@ traversing_visitor::visit_target_symbol (target_symbol* e) void traversing_visitor::visit_arrayindex (arrayindex* e) { + for (unsigned i=0; i<e->indexes.size(); i++) + e->indexes[i]->visit (this); + symbol *array = NULL; hist_op *hist = NULL; classify_indexable(e->base, array, hist); @@ -1416,8 +1419,6 @@ traversing_visitor::visit_arrayindex (arrayindex* e) return array->visit(this); else return hist->visit(this); - for (unsigned i=0; i<e->indexes.size(); i++) - e->indexes[i]->visit (this); } void @@ -1449,6 +1450,142 @@ traversing_visitor::visit_hist_op (hist_op* e) } +void +functioncall_traversing_visitor::visit_functioncall (functioncall* e) +{ + traversing_visitor::visit_functioncall (e); + + // prevent infinite recursion + if (traversed.find (e->referent) == traversed.end ()) + { + traversed.insert (e->referent); + // recurse + e->referent->body->visit (this); + } +} + + +void +varuse_collecting_visitor::visit_embeddedcode (embeddedcode *s) +{ + embedded_seen = true; +} + + +void +varuse_collecting_visitor::visit_print_format (print_format* e) +{ + // NB: Instead of being top-level statements, "print" and "printf" + // are implemented as statement-expressions containing a + // print_format. They have side-effects, but not via the + // embedded-code detection method above. + embedded_seen = true; + functioncall_traversing_visitor::visit_print_format (e); +} + + +void +varuse_collecting_visitor::visit_assignment (assignment *e) +{ + if (e->op == "=" || e->op == "<<<") // pure writes + { + expression* last_lvalue = current_lrvalue; + current_lvalue = e->left; // leave a mark for ::visit_symbol + functioncall_traversing_visitor::visit_assignment (e); + current_lvalue = last_lvalue; + } + else // read-modify-writes + { + expression* last_lrvalue = current_lrvalue; + current_lrvalue = e->left; // leave a mark for ::visit_symbol + functioncall_traversing_visitor::visit_assignment (e); + current_lrvalue = last_lrvalue; + } +} + +void +varuse_collecting_visitor::visit_symbol (symbol *e) +{ + if (e->referent == 0) + throw semantic_error ("symbol without referent", e->tok); + + if (current_lvalue == e || current_lrvalue == e) + { + written.insert (e->referent); + // clog << "write "; + } + if (current_lvalue != e || current_lrvalue == e) + { + read.insert (e->referent); + // clog << "read "; + } + // clog << *e->tok << endl; +} + +// NB: stat_op need not be overridden, since it will get to +// visit_symbol and only as a possible rvalue. + +void +varuse_collecting_visitor::visit_arrayindex (arrayindex *e) +{ + // Hooking this callback is necessary because of the hacky + // statistics representation. For the expression "i[4] = 5", the + // incoming lvalue will point to this arrayindex. However, the + // symbol corresponding to the "i[4]" is multiply inherited with + // arrayindex. If the symbol base part of this object is not at + // offset 0, then static_cast<symbol*>(e) may result in a different + // address, and not match lvalue by number when we recurse that way. + // So we explicitly override the incoming lvalue/lrvalue values to + // point at the embedded objects' actual base addresses. + + expression* last_lrvalue = current_lrvalue; + expression* last_lvalue = current_lvalue; + + symbol *array = NULL; + hist_op *hist = NULL; + classify_indexable(e->base, array, hist); + + if (array) + { + if (current_lrvalue == e) current_lrvalue = array; + if (current_lvalue == e) current_lvalue = array; + functioncall_traversing_visitor::visit_arrayindex (e); + } + else // if (hist) + { + if (current_lrvalue == e) current_lrvalue = hist->stat; + if (current_lvalue == e) current_lvalue = hist->stat; + functioncall_traversing_visitor::visit_arrayindex (e); + } + + current_lrvalue = last_lrvalue; + current_lvalue = last_lvalue; +} + + +void +varuse_collecting_visitor::visit_pre_crement (pre_crement *e) +{ + expression* last_lrvalue = current_lrvalue; + current_lrvalue = e->operand; // leave a mark for ::visit_symbol + functioncall_traversing_visitor::visit_pre_crement (e); + current_lrvalue = last_lrvalue; +} + +void +varuse_collecting_visitor::visit_post_crement (post_crement *e) +{ + expression* last_lrvalue = current_lrvalue; + current_lrvalue = e->operand; // leave a mark for ::visit_symbol + functioncall_traversing_visitor::visit_post_crement (e); + current_lrvalue = last_lrvalue; +} + + + + + + // ------------------------------------------------------------------------ @@ -12,6 +12,7 @@ #include "session.h" #include <map> #include <stack> +#include <set> #include <string> #include <vector> #include <iostream> @@ -443,9 +444,9 @@ struct block: public statement struct expr_statement; struct for_loop: public statement { - expr_statement* init; + expr_statement* init; // may be 0 expression* cond; - expr_statement* incr; + expr_statement* incr; // may be 0 statement* block; void print (std::ostream& o) const; void visit (visitor* u); @@ -485,7 +486,7 @@ struct if_statement: public statement { expression* condition; statement* thenblock; - statement* elseblock; + statement* elseblock; // may be 0 void print (std::ostream& o) const; void visit (visitor* u); }; @@ -630,9 +631,9 @@ struct visitor }; -// A default kind of visitor, which by default travels down -// to the leaves of the statement/expression tree, up to -// but excluding following vardecls (referent pointers). +// A simple kind of visitor, which travels down to the leaves of the +// statement/expression tree, up to but excluding following vardecls +// and functioncalls. struct traversing_visitor: public visitor { void visit_block (block *s); @@ -670,6 +671,42 @@ struct traversing_visitor: public visitor }; +// A kind of traversing visitor, which also follows function calls. +// It uses an internal set object to prevent infinite recursion. +struct functioncall_traversing_visitor: public traversing_visitor +{ + std::set<functiondecl*> traversed; + void visit_functioncall (functioncall* e); +}; + + +// A kind of traversing visitor, which also follows function calls, +// and stores the vardecl* referent of each variable read and/or +// written and other such sundry side-effect data. It's used by +// the elaboration-time optimizer pass. +struct varuse_collecting_visitor: public functioncall_traversing_visitor +{ + std::set<vardecl*> read; + std::set<vardecl*> written; + bool embedded_seen; + expression* current_lvalue; + expression* current_lrvalue; + varuse_collecting_visitor(): + embedded_seen (false), + current_lvalue(0), + current_lrvalue(0) {} + void visit_embeddedcode (embeddedcode *s); + void visit_print_format (print_format *e); + void visit_assignment (assignment *e); + void visit_arrayindex (arrayindex *e); + void visit_symbol (symbol *e); + void visit_pre_crement (pre_crement *e); + void visit_post_crement (post_crement *e); +}; + + + + // A kind of visitor that throws an semantic_error exception // whenever a non-overridden method is called. struct throwing_visitor: public visitor diff --git a/tapset/logging.stp b/tapset/logging.stp index c9c60c8d..a3fcac04 100644 --- a/tapset/logging.stp +++ b/tapset/logging.stp @@ -6,11 +6,7 @@ // Public License (GPL); either version 2, or (at your option) any // later version. -function print (msg:string) %{ - _stp_print (THIS->msg); -%} - -// like print but with a newline +// send a string out with a newline function log (msg:string) %{ _stp_printf ("%s\n", THIS->msg); %} diff --git a/tapsets.cxx b/tapsets.cxx index 7d10ddf3..89201cb2 100644 --- a/tapsets.cxx +++ b/tapsets.cxx @@ -1868,46 +1868,48 @@ target_variable_flavour_calculating_visitor::visit_target_symbol (target_symbol { assert(e->base_name.size() > 0 && e->base_name[0] == '$'); - try + // NB: if for whatever reason this variable does not resolve, + // or is illegally used (write in non-guru mode for instance), + // just pretend that it's OK anyway. var_expanding_copy_visitor + // will take care of throwing the appropriate exception. + + bool lvalue = is_active_lvalue(e); + flavour += lvalue ? 'w' : 'r'; + exp_type ty; + string expr; + try { - bool lvalue = is_active_lvalue(e); - if (lvalue && !q.sess.guru_mode) - throw semantic_error("Writing to target variable outside of guru mode", e->tok); - - flavour += lvalue ? 'w' : 'r'; - exp_type ty; - string expr = q.dw.literal_stmt_for_local(scope_die, - addr, - e->base_name.substr(1), - e->components, - lvalue, - ty); - switch (ty) - { - case pe_unknown: - flavour += 'U'; - break; - case pe_long: - flavour += 'L'; - break; - case pe_string: - flavour += 'S'; - break; - case pe_stats: - flavour += 'T'; - break; - } - flavour += lex_cast<string>(expr.size()); - flavour += '{'; - flavour += expr; - flavour += '}'; + expr = q.dw.literal_stmt_for_local(scope_die, + addr, + e->base_name.substr(1), + e->components, + lvalue, + ty); } - catch (const semantic_error& er) + catch (const semantic_error& e) { - semantic_error er2 (er); - er2.tok1 = e->tok; - q.sess.print_error (er2); + ty = pe_unknown; } + + switch (ty) + { + case pe_unknown: + flavour += 'U'; + break; + case pe_long: + flavour += 'L'; + break; + case pe_string: + flavour += 'S'; + break; + case pe_stats: + flavour += 'T'; + break; + } + flavour += lex_cast<string>(expr.size()); + flavour += '{'; + flavour += expr; + flavour += '}'; } @@ -2636,9 +2638,12 @@ var_expanding_copy_visitor::visit_target_symbol (target_symbol *e) } catch (const semantic_error& er) { - // No need to be verbose: the flavour-gathering visitor - // already printed a message for this exact case. - throw semantic_error ("due to failed target variable resolution"); + // We suppress this error message, and pass the unresolved + // target_symbol to the next pass. We hope that this value ends + // up not being referenced after all, so it can be optimized out + // quietly. + provide <target_symbol*> (this, e); + return; } fdecl->name = fname; diff --git a/testsuite/semko/eleven.stp b/testsuite/semko/eleven.stp index 34dfa225..f76c2df9 100755 --- a/testsuite/semko/eleven.stp +++ b/testsuite/semko/eleven.stp @@ -1,15 +1,15 @@ -#! stap -p2 +#! stap -up2 global arr,rra - +global s,n probe begin { arr[0]="value" rra["key"]=0 } probe end { # confirm that typechecking works the same way for all array indexing - if (k in arr) { k.""; arr[k]+0 } - foreach (l in arr) { l.""; arr[l]+0 } - if (m in rra) { m+1; rra[m]."" } - foreach (n in rra) { n+0; rra[n]."" } + if (k in arr) { s=k.""; i=arr[k]+0 } + foreach (l in arr) { s=l.""; i=arr[l]+0 } + if (m in rra) { i=m+1; s=rra[m]."" } + foreach (n in rra) { i=n+0; s=rra[n]."" } } diff --git a/testsuite/semko/thirty.stp b/testsuite/semko/thirty.stp index b145f826..57278af0 100755 --- a/testsuite/semko/thirty.stp +++ b/testsuite/semko/thirty.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation diff --git a/testsuite/semko/thirtyone.stp b/testsuite/semko/thirtyone.stp index 0462d4d5..dcb4fa7e 100755 --- a/testsuite/semko/thirtyone.stp +++ b/testsuite/semko/thirtyone.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation diff --git a/testsuite/semko/twenty.stp b/testsuite/semko/twenty.stp index 899efdc1..df12fa36 100755 --- a/testsuite/semko/twenty.stp +++ b/testsuite/semko/twenty.stp @@ -3,5 +3,5 @@ function a:string () { } probe begin { - a() + 1 + print (a() + 1) } diff --git a/testsuite/semko/twentyfive.stp b/testsuite/semko/twentyfive.stp index 8758bb67..5eb3b570 100755 --- a/testsuite/semko/twentyfive.stp +++ b/testsuite/semko/twentyfive.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation diff --git a/testsuite/semko/twentyfour.stp b/testsuite/semko/twentyfour.stp index ae75bf31..da6979bb 100755 --- a/testsuite/semko/twentyfour.stp +++ b/testsuite/semko/twentyfour.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation diff --git a/testsuite/semko/twentynine.stp b/testsuite/semko/twentynine.stp index b26e9872..19c66225 100755 --- a/testsuite/semko/twentynine.stp +++ b/testsuite/semko/twentynine.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation @@ -6,6 +6,6 @@ global x probe end { - x <<< 10 <<< 11 + x <<< 10 <<< 11 # but see PR 1922 } diff --git a/testsuite/semko/twentyseven.stp b/testsuite/semko/twentyseven.stp index 6c5bd4f9..287ebebf 100755 --- a/testsuite/semko/twentyseven.stp +++ b/testsuite/semko/twentyseven.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation diff --git a/testsuite/semko/twentysix.stp b/testsuite/semko/twentysix.stp index 9ebffa4a..dd4bce51 100755 --- a/testsuite/semko/twentysix.stp +++ b/testsuite/semko/twentysix.stp @@ -1,4 +1,4 @@ -#! stap -p2 +#! stap -up2 # need one of these for each prohibited statistic operation diff --git a/testsuite/semok/optimize.stp b/testsuite/semok/optimize.stp new file mode 100755 index 00000000..28ccb46c --- /dev/null +++ b/testsuite/semok/optimize.stp @@ -0,0 +1,18 @@ +#! stap -p2 + +# We count on the optimizer to blow away these ridiculous +# expressions, since they have no effect on the output. + +global b + +function zoo (x) { + return "tada" + x +} + +probe begin { + b <<< "hello" + a = b + 2 + zoo (zoo (5)) + b = "goodbye" + no . $such . $target + $variable +} diff --git a/testsuite/transko/one.stp b/testsuite/transko/one.stp index 3c69161d..508ce9d4 100755 --- a/testsuite/transko/one.stp +++ b/testsuite/transko/one.stp @@ -1,6 +1,6 @@ #! stap -p3 probe begin { - 1 = a - a+1 = 4 + print (1 = a) + print (a+1 = 4) } diff --git a/testsuite/transok/five.stp b/testsuite/transok/five.stp index 1b132409..db19b31f 100755 --- a/testsuite/transok/five.stp +++ b/testsuite/transok/five.stp @@ -13,5 +13,7 @@ probe begin for (a=0; a<=4; a=a+1) { b = a } while (99) next + while (99) break + while (99) continue while (99) {} } diff --git a/translate.cxx b/translate.cxx index b6f83540..433cf23a 100644 --- a/translate.cxx +++ b/translate.cxx @@ -1863,10 +1863,10 @@ c_tmpcounter::visit_block (block *s) void c_tmpcounter::visit_for_loop (for_loop *s) { - s->init->visit (this); + if (s->init) s->init->visit (this); s->cond->visit (this); s->block->visit (this); - s->incr->visit (this); + if (s->incr) s->incr->visit (this); } @@ -1881,7 +1881,7 @@ c_unparser::visit_for_loop (for_loop *s) string breaklabel = "break_" + ctr; // initialization - s->init->visit (this); + if (s->init) s->init->visit (this); // condition o->newline(-1) << toplabel << ":"; @@ -1901,7 +1901,7 @@ c_unparser::visit_for_loop (for_loop *s) // iteration o->newline(-1) << contlabel << ":"; o->indent(1); - s->incr->visit (this); + if (s->incr) s->incr->visit (this); o->newline() << "goto " << toplabel << ";"; // exit @@ -3644,10 +3644,15 @@ emit_symbol_data (systemtap_session& s) if (rc == 0) { ifstream kallsyms (sorted_kallsyms.c_str()); + char kallsyms_outbuf [4096]; + ofstream kallsyms_out ((s.tmpdir + "/stap-symbols.h").c_str()); + kallsyms_out.rdbuf()->pubsetbuf (kallsyms_outbuf, + sizeof(kallsyms_outbuf)); + + s.op->newline() << "\n\n#include \"stap-symbols.h\""; unsigned i=0; - s.op->newline() << "struct stap_symbol stap_symbols [] = {"; - s.op->indent(1); + kallsyms_out << "struct stap_symbol stap_symbols [] = {"; string lastaddr; while (! kallsyms.eof()) { @@ -3664,15 +3669,16 @@ emit_symbol_data (systemtap_session& s) // NB: kallsyms includes some duplicate addresses if ((type == "t" || type == "T") && lastaddr != addr) { - s.op->newline() << "{ 0x" << addr << ", " - << "\"" << sym << "\", " - << "\"" << module << "\" },"; + kallsyms_out << " { 0x" << addr << ", " + << "\"" << sym << "\", " + << "\"" << module << "\" }," + << "\n"; lastaddr = addr; i ++; } } - s.op->newline(-1) << "};"; - s.op->newline() << "unsigned stap_num_symbols = " << i << ";\n"; + kallsyms_out << "};\n"; + kallsyms_out << "unsigned stap_num_symbols = " << i << ";\n"; } return rc; @@ -3691,7 +3697,9 @@ translate_pass (systemtap_session& s) try { // This is at the very top of the file. - s.op->line() << "#define TEST_MODE " << (s.test_mode ? 1 : 0) << "\n"; + + // XXX: the runtime uses #ifdef TEST_MODE to infer systemtap usage. + s.op->line() << "#define TEST_MODE 0\n"; s.op->newline() << "#ifndef MAXNESTING"; s.op->newline() << "#define MAXNESTING 10"; @@ -3725,9 +3733,6 @@ translate_pass (systemtap_session& s) if (s.bulk_mode) s.op->newline() << "#define STP_RELAYFS"; - s.op->newline() << "#if TEST_MODE"; - s.op->newline() << "#include \"runtime.h\""; - s.op->newline() << "#else"; s.op->newline() << "#include \"runtime.h\""; s.op->newline() << "#include \"current.c\""; s.op->newline() << "#include \"stack.c\""; @@ -3736,7 +3741,6 @@ translate_pass (systemtap_session& s) s.op->newline() << "#include <linux/timer.h>"; s.op->newline() << "#include <linux/delay.h>"; s.op->newline() << "#include <linux/profile.h>"; - s.op->newline() << "#endif"; s.op->newline() << "#include \"loc2c-runtime.h\" "; s.up->emit_common_header (); @@ -3776,18 +3780,7 @@ translate_pass (systemtap_session& s) s.up->emit_module_exit (); s.op->newline(); - s.op->newline() << "#if TEST_MODE"; - s.op->newline() << "/* test mode mainline */"; - s.op->newline() << "int main () {"; - s.op->newline(1) << "int rc = systemtap_module_init ();"; - s.op->newline() << "if (!rc) systemtap_module_exit ();"; - s.op->newline() << "return rc;"; - s.op->newline(-1) << "}"; - - s.op->newline() << "#else"; - - s.op->newline(); // XXX impedance mismatch s.op->newline() << "int probe_start () {"; s.op->newline(1) << "return systemtap_module_init () ? -1 : 0;"; @@ -3799,7 +3792,6 @@ translate_pass (systemtap_session& s) s.op->newline() << "MODULE_DESCRIPTION(\"systemtap probe\");"; s.op->newline() << "MODULE_LICENSE(\"GPL\");"; // XXX - s.op->newline() << "#endif"; } catch (const semantic_error& e) { |