diff --git a/src/BranchState.cpp b/src/BranchState.cpp index 5bec71c..cbbca97 100644 --- a/src/BranchState.cpp +++ b/src/BranchState.cpp @@ -52,7 +52,10 @@ void BranchState::writeFloat( Token::Argument& argument ) assert( i != m_floats.end() ); } - updateDependency( argument, i->second, Alias::FLOAT, i->first, ((i->second.fields() & ~argument.fields()) != 0) ); + // a write that leaves lanes as they were keeps them in the earlier + // value's register, if one of them is read before it is written again + const unsigned int kept = i->second.fields() & ~argument.fields(); + updateDependency( argument, i->second, Alias::FLOAT, i->first, (kept & m_allocator.lanesLiveAfter( argument )) != 0 ); i->second.setFields( i->second.fields() | argument.fields() ); } @@ -429,6 +432,13 @@ void BranchState::setIntegerOutput( const std::string& name, unsigned int regNum /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool BranchState::isFloatOutput( const std::string& name ) const +{ + return m_floatOutputs.find( name ) != m_floatOutputs.end(); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + void BranchState::storeAddress( const Token::Argument& argument ) { assert( argument.type() == Token::Argument::INTEGER_REGISTER ); diff --git a/src/BranchState.h b/src/BranchState.h index 2ef02e9..9e8c979 100644 --- a/src/BranchState.h +++ b/src/BranchState.h @@ -161,6 +161,8 @@ class BranchState void setFloatOutput( const std::string& name, unsigned int regNumber ); void setIntegerOutput( const std::string& name, unsigned int regNumber ); + bool isFloatOutput( const std::string& name ) const; + bool applyRegisterOutputs(); const Register* inputRegister( Alias::Type type, const std::string& name ); diff --git a/src/RegisterAllocator.cpp b/src/RegisterAllocator.cpp index 4746568..7a293e6 100644 --- a/src/RegisterAllocator.cpp +++ b/src/RegisterAllocator.cpp @@ -261,6 +261,9 @@ bool RegisterAllocator::process( std::list& tokens ) blocks.push_back( std::make_pair( (*j)->current(), *j ) ); } + for( std::vector< std::pair< std::list::iterator, BranchState* > >::iterator b = blocks.begin(); b != blocks.end(); ++b ) + collectLanesLiveAfterWrites( b->first, b->second->exitPoint(), b->second ); + for( j = m_states.begin(); j != m_states.end(); j++ ) { if( !(*j) ) @@ -608,83 +611,207 @@ namespace use->depend( def ); return true; } -} -bool RegisterAllocator::completeLiveRanges( std::list::iterator begin, std::list::iterator end, BranchState* entry ) -{ - std::vector< std::list::iterator > nodes; - std::map< const Token*, unsigned int > index; - for( std::list::iterator t = begin; t != end; ++t ) + // A code block's tokens and the control flow between them: fall-through, + // branches to labels, and jr/jalr to the instruction after each bal/jalr + // that sets their register. + struct BlockFlow { - index[ &*t ] = static_cast( nodes.size() ); - nodes.push_back( t ); - } - const unsigned int count = static_cast( nodes.size() ); - if( !count ) - return true; - - // control flow: fall-through, branches to labels, and jr/jalr to the - // instruction after each bal/jalr that sets their register + std::vector< std::list::iterator > nodes; + std::vector< std::vector > successors; + std::vector< std::vector > predecessors; + bool fallsOffEnd; // the last token runs on into the block's exit + }; - std::vector< std::pair > returns; - for( unsigned int n = 0; n + 1 < count; ++n ) + void buildBlockFlow( std::list::iterator begin, std::list::iterator end, const std::map< std::string, std::list::iterator >& labels, BlockFlow& flow ) { - const Token& token = *nodes[n]; - if( !token.operand() || token.operand()->unit() != Operand::BRU ) - continue; - for( std::list::const_iterator a = token.arguments().begin(); a != token.arguments().end(); ++a ) + std::map< const Token*, unsigned int > index; + for( std::list::iterator t = begin; t != end; ++t ) { - if( (*a).flags() & Token::Argument::ADDRESS ) - returns.push_back( std::make_pair( &*a, n + 1 ) ); + index[ &*t ] = static_cast( flow.nodes.size() ); + flow.nodes.push_back( t ); } - } + const unsigned int count = static_cast( flow.nodes.size() ); + flow.successors.assign( count, std::vector() ); + flow.predecessors.assign( count, std::vector() ); + flow.fallsOffEnd = false; - std::vector< std::vector > successors( count ); - for( unsigned int n = 0; n < count; ++n ) - { - const Token& token = *nodes[n]; - bool fallsThrough = true; + std::vector< std::pair > returns; + for( unsigned int n = 0; n + 1 < count; ++n ) + { + const Token& token = *flow.nodes[n]; + if( !token.operand() || token.operand()->unit() != Operand::BRU ) + continue; + for( std::list::const_iterator a = token.arguments().begin(); a != token.arguments().end(); ++a ) + { + if( (*a).flags() & Token::Argument::ADDRESS ) + returns.push_back( std::make_pair( &*a, n + 1 ) ); + } + } - if( token.operand() && token.operand()->unit() == Operand::BRU ) + for( unsigned int n = 0; n < count; ++n ) { - fallsThrough = (token.operand()->flags() & Operand::DYNAMIC) != 0; + const Token& token = *flow.nodes[n]; + bool fallsThrough = true; - for( std::list::const_iterator a = token.arguments().begin(); a != token.arguments().end(); ++a ) + if( token.operand() && token.operand()->unit() == Operand::BRU ) { - if( !((*a).flags() & Token::Argument::BRANCH) ) - continue; + fallsThrough = (token.operand()->flags() & Operand::DYNAMIC) != 0; - if( (*a).type() == Token::Argument::IMMEDIATE ) + for( std::list::const_iterator a = token.arguments().begin(); a != token.arguments().end(); ++a ) { - std::map< std::string, std::list::iterator >::const_iterator label = m_labels.find( (*a).immediate() ); - if( label == m_labels.end() ) + if( !((*a).flags() & Token::Argument::BRANCH) ) continue; - std::map< const Token*, unsigned int >::const_iterator target = index.find( &*label->second ); - if( target != index.end() ) - successors[n].push_back( target->second ); - } - else if( (*a).type() == Token::Argument::INTEGER_REGISTER ) - { - for( unsigned int r = 0; r < returns.size(); ++r ) + + if( (*a).type() == Token::Argument::IMMEDIATE ) { - if( sameRegister( *returns[r].first, *a ) ) - successors[n].push_back( returns[r].second ); + std::map< std::string, std::list::iterator >::const_iterator label = labels.find( (*a).immediate() ); + if( label == labels.end() ) + continue; + std::map< const Token*, unsigned int >::const_iterator target = index.find( &*label->second ); + if( target != index.end() ) + flow.successors[n].push_back( target->second ); + } + else if( (*a).type() == Token::Argument::INTEGER_REGISTER ) + { + for( unsigned int r = 0; r < returns.size(); ++r ) + { + if( sameRegister( *returns[r].first, *a ) ) + flow.successors[n].push_back( returns[r].second ); + } } } } + + if( fallsThrough ) + { + if( n + 1 < count ) + flow.successors[n].push_back( n + 1 ); + else + flow.fallsOffEnd = true; + } } - if( fallsThrough && n + 1 < count ) - successors[n].push_back( n + 1 ); + for( unsigned int n = 0; n < count; ++n ) + { + for( unsigned int s = 0; s < flow.successors[n].size(); ++s ) + flow.predecessors[ flow.successors[n][s] ].push_back( n ); + } } - std::vector< std::vector > predecessors( count ); + // The lanes a named register access touches: the fields of a VF access, + // 1 for VI. A broadcast named by the opcode (maddaw, mulz) leaves its + // operand without fields; it reads the token's broadcast lane. + unsigned int accessLanes( const Token& token, const Token::Argument& argument ) + { + if( argument.type() != Token::Argument::FLOAT_REGISTER ) + return 1; + + unsigned int lanes = argument.fields(); + if( !lanes && (argument.flags() & Token::Argument::BROADCAST) ) + lanes = token.broadcast(); + return lanes ? lanes : (Token::X|Token::Y|Token::Z|Token::W); + } + + // The lanes of one VF variable each token reads and writes, and the writes + // that leave some of its lanes as they were. + struct VariableLanes + { + std::vector read; + std::vector written; + std::vector< std::pair > partialWrites; + }; +} + +void RegisterAllocator::collectLanesLiveAfterWrites( std::list::iterator begin, std::list::iterator end, BranchState* entry ) +{ + BlockFlow flow; + buildBlockFlow( begin, end, m_labels, flow ); + const unsigned int count = static_cast( flow.nodes.size() ); + const unsigned int all = Token::X|Token::Y|Token::Z|Token::W; + + std::map< std::string, VariableLanes > variables; for( unsigned int n = 0; n < count; ++n ) { - for( unsigned int s = 0; s < successors[n].size(); ++s ) - predecessors[ successors[n][s] ].push_back( n ); + const Token& token = *flow.nodes[n]; + for( std::list::const_iterator a = token.arguments().begin(); a != token.arguments().end(); ++a ) + { + if( ((*a).content() != Token::Argument::ALIAS) || ((*a).type() != Token::Argument::FLOAT_REGISTER) ) + continue; + + VariableLanes& variable = variables[ (*a).alias() ]; + if( variable.read.empty() ) + { + variable.read.assign( count, 0 ); + variable.written.assign( count, 0 ); + } + + const unsigned int lanes = accessLanes( token, *a ); + if( (*a).flags() & Token::Argument::WRITE ) + { + variable.written[n] |= lanes; + if( lanes != all ) + variable.partialWrites.push_back( std::make_pair( n, &*a ) ); + } + else + variable.read[n] |= lanes; + } } + for( std::map< std::string, VariableLanes >::const_iterator v = variables.begin(); v != variables.end(); ++v ) + { + const VariableLanes& variable = v->second; + if( variable.partialWrites.empty() ) + continue; + + // the lanes read later on some path before they are written again; the + // block's exit reads every lane of its outputs + + const unsigned int atExit = entry->isFloatOutput( v->first ) ? all : 0; + std::vector liveIn( count, 0 ); + std::vector liveOut( count, 0 ); + bool changed = true; + while( changed ) + { + changed = false; + for( unsigned int n = count; n-- > 0; ) + { + unsigned int out = (flow.fallsOffEnd && (n + 1 == count)) ? atExit : 0; + for( unsigned int s = 0; s < flow.successors[n].size(); ++s ) + out |= liveIn[ flow.successors[n][s] ]; + liveOut[n] = out; + + const unsigned int in = variable.read[n] | (out & ~variable.written[n]); + if( in != liveIn[n] ) + { + liveIn[n] = in; + changed = true; + } + } + } + + for( unsigned int w = 0; w < variable.partialWrites.size(); ++w ) + m_lanesLiveAfter[ variable.partialWrites[w].second ] = liveOut[ variable.partialWrites[w].first ]; + } +} + +unsigned int RegisterAllocator::lanesLiveAfter( const Token::Argument& write ) const +{ + std::map< const Token::Argument*, unsigned int >::const_iterator i = m_lanesLiveAfter.find( &write ); + return (i == m_lanesLiveAfter.end()) ? (Token::X|Token::Y|Token::Z|Token::W) : i->second; +} + +bool RegisterAllocator::completeLiveRanges( std::list::iterator begin, std::list::iterator end, BranchState* entry ) +{ + BlockFlow flow; + buildBlockFlow( begin, end, m_labels, flow ); + const std::vector< std::list::iterator >& nodes = flow.nodes; + const std::vector< std::vector >& successors = flow.successors; + const std::vector< std::vector >& predecessors = flow.predecessors; + const unsigned int count = static_cast( nodes.size() ); + if( !count ) + return true; + // every read and write of a named register, by variable std::map< Variable, VariableAccesses > variables; @@ -699,21 +826,9 @@ bool RegisterAllocator::completeLiveRanges( std::list::iterator begin, st const bool isFloat = ((*a).type() == Token::Argument::FLOAT_REGISTER); - // a broadcast named by the opcode (maddaw, mulz) leaves its operand - // without fields: it reads the token's broadcast lane - unsigned int lanes = 1; - if( isFloat ) - { - lanes = (*a).fields(); - if( !lanes && ((*a).flags() & Token::Argument::BROADCAST) ) - lanes = nodes[n]->broadcast(); - if( !lanes ) - lanes = Token::X|Token::Y|Token::Z|Token::W; - } - VariableAccess access; access.node = n; - access.lanes = lanes; + access.lanes = accessLanes( *nodes[n], *a ); access.dependency = (*a).dependency(); VariableAccesses& accesses = variables[ Variable( isFloat ? Alias::FLOAT : Alias::INTEGER, (*a).alias() ) ]; diff --git a/src/RegisterAllocator.h b/src/RegisterAllocator.h index b72a041..1e2242d 100644 --- a/src/RegisterAllocator.h +++ b/src/RegisterAllocator.h @@ -53,6 +53,10 @@ class RegisterAllocator const Register* floatRegister( unsigned int regNumber ) const; const Register* integerRegister( unsigned int regNumber ) const; + // the lanes of a VF write's variable that are read after the write, before + // they are written again (every lane where that is not known) + unsigned int lanesLiveAfter( const Token::Argument& write ) const; + const std::string& name(); void setDynamicThreshold( unsigned int threshold ); @@ -94,6 +98,10 @@ class RegisterAllocator // would have. bool completeLiveRanges( std::list::iterator begin, std::list::iterator end, BranchState* entry ); + // For each VF write that leaves lanes of its variable as they were, the + // lanes still read after it, for lanesLiveAfter. + void collectLanesLiveAfterWrites( std::list::iterator begin, std::list::iterator end, BranchState* entry ); + bool processCommonDirective( Token& token ); bool processAliases( Alias*& unallocated ); @@ -123,6 +131,7 @@ class RegisterAllocator std::map< std::string, std::list::iterator > m_labels; std::list< BranchState* > m_states; + std::map< const Token::Argument*, unsigned int > m_lanesLiveAfter; // void generateReadErrorReport( const Token::Argument& arg, const Token& token ); diff --git a/test/integration/test_loop_liveness.cpp b/test/integration/test_loop_liveness.cpp index 77da59e..b192621 100644 --- a/test/integration/test_loop_liveness.cpp +++ b/test/integration/test_loop_liveness.cpp @@ -6,6 +6,11 @@ // read after that branch in the next lost its register there, and a later // temporary could take it. Values loaded before a --cont loop and read // after an inner branch lost theirs the same way, which Tyra's programs do. +// +// A write to some lanes of a variable joined the variable's earlier value +// whenever another lane had been written before, even when that lane was +// written again before any read. Values rewritten lane by lane on every +// trip, like Tyra's output colours, stayed live for the whole program. #include "test_harness.h" #include "openvcl_runner.h" @@ -124,6 +129,31 @@ namespace "\t--exit\n" "\t--endexit\n"; + // Each trip writes fC's xyz and then its w before storing it, so the last + // trip's fC is dead by then, and four VF registers are enough. + const char* kPartialWrites = + "\t.init_vf vf01, vf02, vf03, vf04\n" + "\t.init_vi_all\n" + "\t.syntax new\n" + "\t.name vsmLoopLiveness\n" + "\t--enter\n" + "\t--endenter\n" + "\tiaddiu iN, vi00, 4\n" + "\tlq fA, 0(vi00)\n" + "lLoop:\n" + "\tlq fB, 1(vi00)\n" + "\tadd.xyz fC, fA, fB\n" + "\tadd.w fC, fB, fB\n" + "\tsq fC, 2(vi00)\n" + "\tlq fD, 3(vi00)\n" + "\tadd fE, fD, fA\n" + "\tmul fF, fE, fD\n" + "\tsq fF, 4(vi00)\n" + "\tiaddi iN, iN, -1\n" + "\tibne iN, vi00, lLoop\n" + "\t--exit\n" + "\t--endexit\n"; + ::test::RunResult compile(const char* source) { return ::test::run_openvcl(std::vector(), source); @@ -242,3 +272,9 @@ TEST_CASE("openvcl: a broadcast operand keeps only its lane live") ::test::RunResult result = compile(kBroadcastLane); CHECK(result.exit_code == 0); } + +TEST_CASE("openvcl: a partial write starts a new value when the lanes it keeps are dead") +{ + ::test::RunResult result = compile(kPartialWrites); + CHECK(result.exit_code == 0); +}