/// Called after code is generated, this function loops over all the ops /// and figures out the lifetimes of all variables, based on whether the /// args in each op are read or written. void OSLCompilerImpl::track_variable_lifetimes (const OpcodeVec &code, const SymbolPtrVec &opargs, const SymbolPtrVec &allsyms) { // Clear the lifetimes for all symbols BOOST_FOREACH (Symbol *s, allsyms) s->clear_rw (); static ustring op_for("for"); static ustring op_while("while"); static ustring op_dowhile("dowhile"); // For each op, mark its arguments as being used at that op int opnum = 0; BOOST_FOREACH (const Opcode &op, code) { // Some work to do for each argument to the op... for (int a = 0; a < op.nargs(); ++a) { SymbolPtr s = opargs[op.firstarg()+a]; ASSERT (s->dealias() == s); // s = s->dealias(); // Make sure it's de-aliased // Mark that it's read and/or written for this op s->mark_rw (opnum, op.argread(a), op.argwrite(a)); } // If this is a loop op, we need to mark its control variable // (the only arg) as used for the duration of the loop! if (op.opname() == op_for || op.opname() == op_while || op.opname() == op_dowhile) { ASSERT (op.nargs() == 1); // loops should have just one arg SymbolPtr s = opargs[op.firstarg()]; s->mark_rw (opnum+1, true, true); s->mark_rw (op.farthest_jump()-1, true, true); } ++opnum; } // Special cases: handle variables whose lifetimes cross the boundaries // of a loop. opnum = 0; BOOST_FOREACH (const Opcode &op, code) { if (op.opname() == op_for || op.opname() == op_while || op.opname() == op_dowhile) { int loopcond = op.jump (0); // after initialization, before test int loopend = op.farthest_jump() - 1; BOOST_FOREACH (Symbol *s, allsyms) { // Temporaries referenced both inside AND outside a loop // need their lifetimes extended to cover the entire // loop so they aren't coalesced incorrectly. The // specific danger is for a function that contains a // loop, and the function is passed an argument that is // a temporary calculation. if (s->symtype() == SymTypeTemp && ((s->firstuse() < loopcond && s->lastuse() >= loopcond) || (s->firstuse() < loopend && s->lastuse() >= loopend))) { s->mark_rw (opnum, true, true); s->mark_rw (loopend, true, true); } // Locals that are written within the loop should have // their usage conservatively expanded to the whole // loop. This is not a worry for temps, because they // CAN'T be read in the next iteration unless they were // set before the loop, handled above. Ideally, we // could be less conservative if we knew that the // variable in question was declared/scoped internal to // the loop, in which case it can't carry values to the // next iteration (FIXME). if (s->symtype() == SymTypeLocal && s->firstuse() < loopend && s->lastwrite() >= loopcond) { bool read = (s->lastread() >= loopcond); s->mark_rw (opnum, read, true); s->mark_rw (loopend, read, true); } } } ++opnum; }
/// Called after code is generated, this function loops over all the ops /// and figures out the lifetimes of all variables, based on whether the /// args in each op are read or written. void OSLCompilerImpl::track_variable_lifetimes (const OpcodeVec &code, const SymbolPtrVec &opargs, const SymbolPtrVec &allsyms) { // Clear the lifetimes for all symbols BOOST_FOREACH (Symbol *s, allsyms) s->clear_rw (); static ustring op_for("for"); static ustring op_while("while"); static ustring op_dowhile("dowhile"); // For each op, mark its arguments as being used at that op int opnum = 0; BOOST_FOREACH (const Opcode &op, code) { // Some work to do for each argument to the op... for (int a = 0; a < op.nargs(); ++a) { SymbolPtr s = opargs[op.firstarg()+a]; ASSERT (s->dealias() == s); // s = s->dealias(); // Make sure it's de-aliased // Mark that it's read and/or written for this op s->mark_rw (opnum, op.argread(a), op.argwrite(a)); } // If this is a loop op, we need to mark its control variable // (the only arg) as used for the duration of the loop! if (op.opname() == op_for || op.opname() == op_while || op.opname() == op_dowhile) { ASSERT (op.nargs() == 1); // loops should have just one arg SymbolPtr s = opargs[op.firstarg()]; s->mark_rw (opnum+1, true, true); s->mark_rw (op.farthest_jump()-1, true, true); } ++opnum; } // Special case: temporaries referenced both inside AND outside a // loop need their lifetimes extended to cover the entire loop so // they aren't accidentally coalesced incorrectly. The specific // danger is for a function that contains a loop, and the function // is passed an argument that is a temporary calculation. opnum = 0; BOOST_FOREACH (const Opcode &op, code) { if (op.opname() == op_for || op.opname() == op_while || op.opname() == op_dowhile) { int loopend = op.farthest_jump() - 1; BOOST_FOREACH (Symbol *s, allsyms) { if (s->symtype() == SymTypeTemp && ((s->firstuse() < opnum && s->lastuse() >= opnum) || (s->firstuse() < loopend && s->lastuse() >= loopend))) { s->mark_rw (opnum, true, true); s->mark_rw (loopend, true, true); } } } ++opnum; }
/// Called after code is generated, this function loops over all the ops /// and figures out the lifetimes of all variables, based on whether the /// args in each op are read or written. void OSLCompilerImpl::track_variable_lifetimes (const OpcodeVec &code, const SymbolPtrVec &opargs, const SymbolPtrVec &allsyms, std::vector<int> *bblockids) { // Clear the lifetimes for all symbols BOOST_FOREACH (Symbol *s, allsyms) s->clear_rw (); // Keep track of the nested loops we're inside. We track them by pairs // of begin/end instruction numbers for that loop body, including // conditional evaluation (skip the initialization). Note that the end // is inclusive. We use this vector of ranges as a stack. typedef std::pair<int,int> intpair; std::vector<intpair> loop_bounds; // For each op, mark its arguments as being used at that op int opnum = 0; BOOST_FOREACH (const Opcode &op, code) { if (op.opname() == op_for || op.opname() == op_while || op.opname() == op_dowhile) { // If this is a loop op, we need to mark its control variable // (the only arg) as used for the duration of the loop! ASSERT (op.nargs() == 1); // loops should have just one arg SymbolPtr s = opargs[op.firstarg()]; int loopcond = op.jump (0); // after initialization, before test int loopend = op.farthest_jump() - 1; // inclusive end s->mark_rw (opnum+1, true, true); s->mark_rw (loopend, true, true); // Also push the loop bounds for this loop loop_bounds.push_back (std::make_pair(loopcond, loopend)); } // Some work to do for each argument to the op... for (int a = 0; a < op.nargs(); ++a) { SymbolPtr s = opargs[op.firstarg()+a]; ASSERT (s->dealias() == s); // Make sure it's de-aliased // Mark that it's read and/or written for this op bool readhere = op.argread(a); bool writtenhere = op.argwrite(a); s->mark_rw (opnum, readhere, writtenhere); // Adjust lifetimes of symbols whose values need to be preserved // between loop iterations. BOOST_FOREACH (intpair oprange, loop_bounds) { int loopcond = oprange.first; int loopend = oprange.second; DASSERT (s->firstuse() <= loopend); // Special case: a temp or local, even if written inside a // loop, if it's entire lifetime is within one basic block // and it's strictly written before being read, then its // lifetime is truly local and doesn't need to be expanded // for the duration of the loop. if (bblockids && (s->symtype()==SymTypeLocal || s->symtype()==SymTypeTemp) && (*bblockids)[s->firstuse()] == (*bblockids)[s->lastuse()] && s->lastwrite() < s->firstread()) { continue; } // Syms written before or inside the loop, and referenced // inside or after the loop, need to preserve their value // for the duration of the loop. We know it's referenced // inside the loop because we're here examining it! if (s->firstwrite() <= loopend) { s->mark_rw (loopcond, readhere, writtenhere); s->mark_rw (loopend, readhere, writtenhere); } } } ++opnum; // Advance to the next op index // Pop any loop bounds for loops we've just exited while (!loop_bounds.empty() && loop_bounds.back().second < opnum) loop_bounds.pop_back (); }