From efa1b2615064395d6862a972314bd2f0b749b35f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= <tseeker@nocternity.net>
Date: Tue, 12 Dec 2017 18:30:41 +0100
Subject: [PATCH] Optimizer - Constant propagation for variables

* It doesn't work for function arguments at this point, but shouldn't be
too hard to implement that.
* Can't use it to its full potential mostly because I need to get rid of
the node-specific shit in the AST.
---
 c-opast.cc     | 34 +++++++++++++++++-
 c-opast.hh     | 12 +++++++
 c-opopt.cc     | 97 +++++++++++++++++++++++++++++++++++++++++++++++++-
 test/build.srd |  4 ++-
 4 files changed, 144 insertions(+), 3 deletions(-)

diff --git a/c-opast.cc b/c-opast.cc
index f16b332..ceb1b17 100644
--- a/c-opast.cc
+++ b/c-opast.cc
@@ -32,6 +32,15 @@ T_RootNode& A_Node::root( ) const noexcept
 			const_cast< A_Node* >( node ) );
 }
 
+void A_Node::replaceChild(
+		A_Node const&		/*child*/ ,
+		T_OwnPtr< A_Node >	/*replacement*/
+		) noexcept
+{
+	fprintf( stderr , "replaceChild called on invalid node\n" );
+	std::terminate( );
+}
+
 /*----------------------------------------------------------------------------*/
 
 A_Node* opast::ASTVisitorBrowser(
@@ -492,6 +501,19 @@ T_BinaryOperatorNode::T_BinaryOperatorNode(
 	})( ) , parent ) , op_( op )
 { }
 
+void T_BinaryOperatorNode::replaceChild(
+		A_Node const&		child ,
+		T_OwnPtr< A_Node >	replacement
+		) noexcept
+{
+	if ( right_.get( ) == &child ) {
+		right_ = std::move( replacement );
+	} else {
+		assert( left_.get( ) == &child );
+		left_ = std::move( replacement );
+	}
+}
+
 
 /*= T_FramebufferInstrNode ===================================================*/
 
@@ -629,7 +651,17 @@ T_Optional< T_SRDLocation > T_LocalsInstrNode::addVariable(
 }
 
 
-/*= T_CondInstrNode ============================================================*/
+/*= T_CondInstrNode ==========================================================*/
+
+void T_CondInstrNode::T_Expression::replaceChild(
+		A_Node const&		child ,
+		T_OwnPtr< A_Node >	replacement ) noexcept
+{
+	assert( expression_.get( ) == &child );
+	expression_ = std::move( replacement );
+}
+
+/*----------------------------------------------------------------------------*/
 
 void T_CondInstrNode::setExpression(
 		P_ExpressionNode expression ) noexcept
diff --git a/c-opast.hh b/c-opast.hh
index 4a499e5..bb5eb19 100644
--- a/c-opast.hh
+++ b/c-opast.hh
@@ -94,6 +94,10 @@ class A_Node
 		{ assert( parent_ ); return *parent_; }
 
 	T_RootNode& root( ) const noexcept;
+
+	virtual void replaceChild(
+			A_Node const& child ,
+			T_OwnPtr< A_Node > replacement ) noexcept;
 };
 
 // Browser function to be used with T_Visitor
@@ -460,6 +464,10 @@ class T_CondInstrNode : public A_InstructionNode
 
 		void expression( P_ExpressionNode expr ) noexcept
 			{ assert( expr ); expression_ = std::move( expr ); }
+
+		void replaceChild(
+				A_Node const& child ,
+				T_OwnPtr< A_Node > replacement ) noexcept override;
 	};
 
 	class T_ValuedCase : public A_Node
@@ -1362,6 +1370,10 @@ class T_BinaryOperatorNode : public A_ExpressionNode
 		{ return *right_; }
 	A_ExpressionNode& right( ) noexcept
 		{ return *right_; }
+
+	void replaceChild(
+			A_Node const& child ,
+			T_OwnPtr< A_Node > replacement ) noexcept override;
 };
 
 
diff --git a/c-opopt.cc b/c-opopt.cc
index d23323c..5ed3864 100644
--- a/c-opopt.cc
+++ b/c-opopt.cc
@@ -1441,6 +1441,41 @@ bool opopt::FoldConstants(
 
 
 /*= CONSTANT PROPAGATION =====================================================*/
+namespace {
+
+void CPReplaceWithConstant_(
+		A_InstructionNode&		instruction ,
+		T_OptData::T_VarId const&	var ,
+		const double			value ,
+		T_OptData&			oData
+		) noexcept
+{
+	oData.visitor.visit( instruction , [&]( A_Node& node , const bool exit ) {
+		if ( !exit || node.type( ) != A_Node::EXPR_ID ) {
+			return node.type( ) != A_Node::ILIST;
+		}
+
+		auto& eid{ (T_IdentifierExprNode&) node };
+		if ( eid.id( ) != var.name ) {
+			return true;
+		}
+
+		auto& p{ eid.parent( ) };
+		auto replacement{ NewOwned< T_ConstantExprNode >( p , value ) };
+		replacement->location( ) = eid.location( );
+		p.replaceChild( eid , std::move( replacement ) );
+		oData.logger( [&]() {
+			T_StringBuilder sb;
+			sb << "Propagated constant from " << var.name
+				<< " at " << instruction.location( )
+				<< " (value " << value << ")";
+			return sb;
+		} , LL2 );
+		return true;
+	} );
+}
+
+} // namespace <anon>
 
 bool opopt::PropagateConstants(
 		T_OpsParserOutput& program ,
@@ -1463,7 +1498,67 @@ bool opopt::PropagateConstants(
 	oData.buildUseDefineChains( program );
 	M_LOGSTR_( "... Propagating constants" , 2 );
 
-	return false;
+	T_Set< uint32_t > cDefs{ UseTag< ArrayBacked< 16 > >( ) };
+	T_AutoArray< double , 16 > cValues;
+	bool changesMade{ false };
+	for ( auto const& udc : oData.varUDChains.values( ) ) {
+		cDefs.clear( );
+		cValues.clear( );
+
+		// Find all constant definitions
+		const auto nDefs{ udc.defines.size( ) };
+		for ( auto i = 0u ; i < nDefs ; i ++ ) {
+			auto const& def{ udc.defines[ i ] };
+			auto const* const ri{ oData.instructions[ def.node ].node };
+			if ( ri->type( ) == A_Node::OP_SET ) {
+				auto const* const si{
+					dynamic_cast< T_SetInstrNode const* >( ri ) };
+				if ( si->expression( ).type( ) == A_Node::EXPR_CONST ) {
+					cDefs.add( i );
+					cValues.add( ( (T_ConstantExprNode&) si->expression( ) ).floatValue( ) );
+				}
+			} else {
+				// Instruction is not supported, exit
+				assert( cDefs.size( ) == 0 );
+				break;
+			}
+		}
+		assert( cValues.size( ) == cDefs.size( ) );
+		if ( cValues.empty( ) ) {
+			continue;
+		}
+
+		const auto nUses{ udc.uses.size( ) };
+		for ( auto i = 0u ; i < nUses ; i ++ ) {
+			auto const& use{ udc.uses[ i ] };
+			const auto nRefs{ use.refs.size( ) };
+			T_Optional< double > repVal{ };
+			for ( auto j = 0u ; j < nRefs ; j ++ ) {
+				const auto dIdx{ cDefs.indexOf( use.refs[ j ] ) };
+				if ( dIdx == -1 ) {
+					repVal.clear( );
+					break;
+				}
+				if ( !repVal ) {
+					repVal = cValues[ dIdx ];
+				} else if ( *repVal != cValues[ dIdx ] ) {
+					repVal.clear( );
+					break;
+				}
+			}
+			if ( !repVal ) {
+				continue;
+			}
+
+			printf( "Could replace use at %d with constant %f\n" ,
+					use.node , *repVal );
+			CPReplaceWithConstant_( *oData.instructions[ use.node ].node ,
+					udc.var , *repVal , oData );
+			changesMade = true;
+		}
+	}
+
+	return changesMade;
 }
 
 
diff --git a/test/build.srd b/test/build.srd
index 139ec1d..bd62872 100644
--- a/test/build.srd
+++ b/test/build.srd
@@ -4,7 +4,9 @@
 		(1920 1080)
 	)
 	(optimizer on
-		(constant-folding on)
+		(constant-folding on
+			# (fixed-resolution on)
+		)
 		(constant-propagation on)
 	)
 )