// Philipp Klaus Krause, philipp@informatik.uni-frankfurt.de, pkk@spth.de, 2011 // // (c) 2011 Goethe-Universität Frankfurt // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the // Free Software Foundation; either version 2, or (at your option) any // later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // // // Optimal placement of bank switching instructions for named address spaces. // // For details, see: // // Philipp Klaus Krause, // "Optimal Placement of Bank Selection Instructions in Polynomial Time", // Proceedings of the 16th International Workshop on Software and Compilers for Embedded Systems, // M-SCOPES '13, pp. 23-30. // Association for Computing Machinery, // 2013. #ifndef SDCCNADDR_HH #define SDCCNADDR_HH 1 #include #include #include #include // Workaround for boost bug #11880 #include #if BOOST_VERSION == 106000 #include #endif #include extern "C" { #include "SDCCsymt.h" #include "SDCCicode.h" #include "SDCCBBlock.h" #include "SDCCopt.h" #include "SDCCy.h" } typedef short int naddrspace_t; // Named address spaces. -1: Undefined, Others: see map. typedef std::set naddrspaceset_t; struct assignment_naddr { float s; naddrspaceset_t local; std::vector global; bool operator<(const assignment_naddr& a) const { naddrspaceset_t::const_iterator i, ai, i_end, ai_end; i_end = local.end(); ai_end = a.local.end(); for (i = local.begin(), ai = a.local.begin();; ++i, ++ai) { if (i == i_end) return(true); if (ai == ai_end) return(false); if (*i < *ai) return(true); if (*i > *ai) return(false); if (global[*i] < a.global[*ai]) return(true); if (global[*i] > a.global[*ai]) return(false); } } }; bool assignments_naddr_locally_same(const assignment_naddr &a1, const assignment_naddr &a2) { if (a1.local != a2.local) return(false); naddrspaceset_t::const_iterator i, i_end; for (i = a1.local.begin(), i_end = a1.local.end(); i != i_end; ++i) if (a1.global[*i] != a2.global[*i]) return(false); return(true); } struct cfg_naddr_node { iCode *ic; naddrspaceset_t possible_naddrspaces; }; typedef std::list assignment_list_naddr_t; struct tree_dec_naddr_node { std::set bag; assignment_list_naddr_t assignments; unsigned weight; // The weight is the number of nodes at which intermediate results need to be remembered. In general, to minimize memory consumption, at join nodes the child with maximum weight should be processed first. }; typedef boost::adjacency_list cfg_t; // The edge property is the cost of subdividing the edge and inserting a bank switching instruction. typedef boost::adjacency_list tree_dec_t; #ifdef HAVE_TREEDEC_COMBINATIONS_HPP #include TREEDEC_TREEDEC_BAG_TRAITS(tree_dec_t, bag); #endif #include "SDCCtree_dec.hpp" // Annotate nodes of the control flow graph with the set of possible named address spaces active there. void annotate_cfg_naddr(cfg_t &cfg, std::map &addrspaces) { /* MSVC 2010 doesn't like the typename here, though it accepts it elsewhere */ typedef /*typename*/ boost::graph_traits::vertex_descriptor vertex_t; std::map sym_to_index; naddrspace_t na_max = -1; std::vector predetermined(boost::num_vertices (cfg), false); // Initialize the cfg vertices where there is information on the desired named address space. for (vertex_t i = 0; i < boost::num_vertices (cfg); i++) { const iCode *ic = cfg[i].ic; const symbol *addrspace; // We do not know the current named address space when entering a function or after calling one. if (ic->op == CALL || ic->op == PCALL || ic->op == FUNCTION) predetermined[i] = true; // Set the required named address spaces if (addrspace = getAddrspaceiCode (ic)) { naddrspace_t na; if (sym_to_index.find (addrspace) == sym_to_index.end ()) sym_to_index[addrspace] = ++na_max; na = sym_to_index[addrspace]; addrspaces[na] = addrspace; cfg[i].possible_naddrspaces.insert (na); predetermined[i] = true; } else cfg[i].possible_naddrspaces.insert(-1); } // Extend. bool change; do { change = false; for (vertex_t i = 0; i < boost::num_vertices (cfg); i++) { if (predetermined[i]) continue; size_t oldsize = cfg[i].possible_naddrspaces.size(); { /* MSVC 2010 doesn't like the typename here, though it accepts it elsewhere */ typedef /*typename*/ boost::graph_traits::out_edge_iterator n_iter_t; n_iter_t n, n_end; for (boost::tie(n, n_end) = boost::out_edges(i, cfg); n != n_end; ++n) { vertex_t v = boost::target(*n, cfg); cfg[i].possible_naddrspaces.insert(cfg[v].possible_naddrspaces.begin(), cfg[v].possible_naddrspaces.end()); } } { /* MSVC 2010 doesn't like the typename here, though it accepts it elsewhere */ typedef /*typename*/ boost::graph_traits::in_edge_iterator n_iter_t; n_iter_t n, n_end; for (boost::tie(n, n_end) = boost::in_edges(i, cfg); n != n_end; ++n) { vertex_t v = boost::source(*n, cfg); cfg[i].possible_naddrspaces.insert(cfg[v].possible_naddrspaces.begin(), cfg[v].possible_naddrspaces.end()); } } if (oldsize != cfg[i].possible_naddrspaces.size()) change = true; } } while(change); } // Handle Leaf nodes in the nice tree decomposition template void tree_dec_naddrswitch_leaf(T_t &T, typename boost::graph_traits::vertex_descriptor t, const G_t &G) { assignment_naddr a; assignment_list_naddr_t &alist = T[t].assignments; a.s = 0; a.global.resize(boost::num_vertices(G), -2); alist.push_back(a); } // Handle introduce nodes in the nice tree decomposition template int tree_dec_naddrswitch_introduce(T_t &T, typename boost::graph_traits::vertex_descriptor t, const G_t &G) { typedef typename boost::graph_traits::adjacency_iterator adjacency_iter_t; adjacency_iter_t c, c_end; assignment_list_naddr_t::iterator ai; boost::tie(c, c_end) = adjacent_vertices(t, T); assignment_list_naddr_t &alist2 = T[t].assignments; assignment_list_naddr_t &alist = T[*c].assignments; std::set new_inst; std::set_difference(T[t].bag.begin(), T[t].bag.end(), T[*c].bag.begin(), T[*c].bag.end(), std::inserter(new_inst, new_inst.end())); unsigned short int i = *(new_inst.begin()); for(ai = alist.begin(); ai != alist.end(); ++ai) { ai->local.insert(i); naddrspaceset_t::const_iterator ni, ni_end; for(ni = G[i].possible_naddrspaces.begin(), ni_end = G[i].possible_naddrspaces.end(); ni != ni_end; ++ni) { ai->global[i] = *ni; alist2.push_back(*ai); } } alist.clear(); return((int)alist2.size() <= options.max_allocs_per_node ? 0 : -1); } // Handle forget nodes in the nice tree decomposition template void tree_dec_naddrswitch_forget(T_t &T, typename boost::graph_traits::vertex_descriptor t, const G_t &G) { typedef typename boost::graph_traits::adjacency_iterator adjacency_iter_t; adjacency_iter_t c, c_end; boost::tie(c, c_end) = adjacent_vertices(t, T); assignment_list_naddr_t &alist = T[t].assignments; std::swap(alist, T[*c].assignments); std::set old_inst; std::set_difference(T[*c].bag.begin(), T[*c].bag.end(), T[t].bag.begin(), T[t].bag.end(), std::inserter(old_inst, old_inst.end())); unsigned short int i = *(old_inst.begin()); assignment_list_naddr_t::iterator ai, aif; // Restrict assignments (locally) to current variables. for (ai = alist.begin(); ai != alist.end(); ++ai) { ai->local.erase(i); { typedef typename boost::graph_traits::out_edge_iterator n_iter_t; n_iter_t n, n_end; for (boost::tie(n, n_end) = boost::out_edges(i, G); n != n_end; ++n) { if (ai->local.find(boost::target(*n, G)) == ai->local.end() || ai->global[boost::target(*n, G)] == -1) continue; if (ai->global[boost::source(*n, G)] == ai->global[boost::target(*n, G)]) continue; ai->s += G[*n]; } } { typedef typename boost::graph_traits::in_edge_iterator n_iter_t; n_iter_t n, n_end; for (boost::tie(n, n_end) = boost::in_edges(i, G); n != n_end; ++n) { if (ai->local.find(boost::source(*n, G)) == ai->local.end() || ai->global[boost::target(*n, G)] == -1) continue; if (ai->global[boost::source(*n, G)] == ai->global[boost::target(*n, G)]) continue; ai->s += G[*n]; } } } alist.sort(); // Collapse (locally) identical assignments. for (ai = alist.begin(); ai != alist.end();) { aif = ai; for (++ai; ai != alist.end() && assignments_naddr_locally_same(*aif, *ai);) { if (aif->s > ai->s) { alist.erase(aif); aif = ai; ++ai; } else { alist.erase(ai); ai = aif; ++ai; } } } } // Handle join nodes in the nice tree decomposition template void tree_dec_naddrswitch_join(T_t &T, typename boost::graph_traits::vertex_descriptor t, const G_t &G) { typedef typename boost::graph_traits::adjacency_iterator adjacency_iter_t; adjacency_iter_t c, c_end, c2, c3; boost::tie(c, c_end) = adjacent_vertices(t, T); c2 = c; ++c; c3 = c; assignment_list_naddr_t &alist1 = T[t].assignments; assignment_list_naddr_t &alist2 = T[*c2].assignments; assignment_list_naddr_t &alist3 = T[*c3].assignments; alist2.sort(); alist3.sort(); assignment_list_naddr_t::iterator ai2, ai3; for (ai2 = alist2.begin(), ai3 = alist3.begin(); ai2 != alist2.end() && ai3 != alist3.end();) { if (assignments_naddr_locally_same(*ai2, *ai3)) { ai2->s += ai3->s; for (size_t i = 0; i < ai2->global.size(); i++) ai2->global[i] = ((ai2->global[i] != -2) ? ai2->global[i] : ai3->global[i]); alist1.push_back(*ai2); ++ai2; ++ai3; } else if (*ai2 < *ai3) { ++ai2; continue; } else if (*ai3 < *ai2) { ++ai3; continue; } } alist2.clear(); alist3.clear(); } template int tree_dec_naddrswitch_nodes(T_t &T, typename boost::graph_traits::vertex_descriptor t, const G_t &G) { typedef typename boost::graph_traits::adjacency_iterator adjacency_iter_t; adjacency_iter_t c, c_end; typename boost::graph_traits::vertex_descriptor c0, c1; boost::tie(c, c_end) = adjacent_vertices(t, T); switch (out_degree(t, T)) { case 0: tree_dec_naddrswitch_leaf(T, t, G); break; case 1: c0 = *c; tree_dec_naddrswitch_nodes(T, c0, G); if (T[c0].bag.size() < T[t].bag.size()) { if (tree_dec_naddrswitch_introduce(T, t, G)) return(-1); } else tree_dec_naddrswitch_forget(T, t, G); break; case 2: c0 = *c++; c1 = *c; if (T[c0].weight < T[c1].weight) // Minimize memory consumption. std::swap (c0, c1); tree_dec_naddrswitch_nodes(T, c0, G); tree_dec_naddrswitch_nodes(T, c1, G); tree_dec_naddrswitch_join(T, t, G); break; default: std::cerr << "Not nice.\n"; break; } return(0); } template static void implement_naddr_assignment(const assignment_naddr &a, const G_t &G, const std::map addrspaces) { typedef typename boost::graph_traits::vertex_descriptor vertex_t; typedef typename boost::graph_traits::edge_iterator ei_t; ei_t e, e_end; for(boost::tie(e, e_end) = boost::edges(G); e != e_end; ++e) { const vertex_t source = boost::source(*e, G); const vertex_t target = boost::target(*e, G); const naddrspace_t sourcespace = a.global[source]; const naddrspace_t targetspace = a.global[target]; // Nothing to do if the space doesn't change, or we just forget it. if(targetspace == -1 || sourcespace == targetspace) continue; // This shouldn't happen with the CFGs sdcc generates and a cost function based on code size. if(G[source].ic->next != G[target].ic) std::cerr << "Trying to switch address space at weird edge in CFG."; switchAddressSpaceAt(G[target].ic, addrspaces.find(targetspace)->second); } } template int tree_dec_address_switch(T_t &T, const G_t &G, const std::map addrspaces) { if(tree_dec_naddrswitch_nodes(T, find_root(T), G)) return(-1); const assignment_naddr &winner = *(T[find_root(T)].assignments.begin()); #if 0 std::cout << "Winner: "; for(unsigned int i = 0; i < boost::num_vertices(G); i++) { std::cout << "(" << i << ", " << int(winner.global[i]) << ") "; } std::cout << "\n"; std::cout << "Cost: " << winner.s << "\n"; std::cout.flush(); #endif implement_naddr_assignment(winner, G, addrspaces); return(0); } // Dump cfg, with numbered nodes, show possible address spaces at each node. void dump_cfg_naddr(const cfg_t &cfg) { std::ofstream dump_file((std::string(dstFileName) + ".dumpnaddrcfg" + (currFunc ? currFunc->rname : "__global") + ".dot").c_str()); std::string *name = new std::string[num_vertices(cfg)]; for (unsigned int i = 0; i < boost::num_vertices(cfg); i++) { std::ostringstream os; os << i << ", " << cfg[i].ic->key << ": "; naddrspaceset_t::const_iterator n; for (n = cfg[i].possible_naddrspaces.begin(); n != cfg[i].possible_naddrspaces.end(); ++n) os << *n << " "; name[i] = os.str(); } boost::write_graphviz(dump_file, cfg, boost::make_label_writer(name), boost::default_writer(), cfg_titlewriter(currFunc->rname, " bank selection instr. placement")); delete[] name; } // Dump tree decomposition, show bag and live variables at each node. static void dump_tree_decomposition_naddr(const tree_dec_t &tree_dec) { std::ofstream dump_file((std::string(dstFileName) + ".dumpnaddrdec" + currFunc->rname + ".dot").c_str()); unsigned int w = 0; std::string *name = new std::string[num_vertices(tree_dec)]; for (unsigned int i = 0; i < boost::num_vertices(tree_dec); i++) { if (tree_dec[i].bag.size() > w) w = tree_dec[i].bag.size(); std::ostringstream os; std::set::const_iterator v1; os << i << " | "; for (v1 = tree_dec[i].bag.begin(); v1 != tree_dec[i].bag.end(); ++v1) os << *v1 << " "; name[i] = os.str(); } boost::write_graphviz(dump_file, tree_dec, boost::make_label_writer(name), boost::default_writer(), dec_titlewriter((w - 1), currFunc->rname, " bank selection instr. placement")); delete[] name; } #endif