diff --git a/c-opopt.cc b/c-opopt.cc
index 569c02c..a22a831 100644
--- a/c-opopt.cc
+++ b/c-opopt.cc
@@ -14,6 +14,19 @@ using namespace opopt;
 #define M_LOGSTR_( S , L ) \
 	logger( [](){ return T_StringBuilder{ S }; } , L )
 
+uint32_t opopt::ComputeHash(
+		T_OptData::T_VarId const& id ) noexcept
+{
+	const uint32_t nh{ ComputeHash( id.name ) };
+	const uint32_t oh{ id.type != T_OptData::E_UDVarType::GLOBAL
+		? ComputeHash( id.name )
+		: 0
+	};
+	return ( uint32_t( id.type ) << 8 )
+		^ nh
+		^ ( ( oh << 29 ) | ( oh >> 3 ) );
+}
+
 
 /*= T_OptData - INPUT DECLARATIONS ===========================================*/
 
@@ -57,7 +70,7 @@ bool ODNIVisitor_(
 		dynamic_cast< A_InstructionNode* >( &node ) };
 	if ( iptr && !exit ) {
 		auto const& il{ dynamic_cast< T_InstrListNode& >( iptr->parent( ) ) };
-		const auto hash{ ComputeHash( (uint64_t)iptr ) };
+		const auto hash{ ebcl::ComputeHash( (uint64_t)iptr ) };
 		oData.instrIndex.add( hash );
 		oData.instructions.add( T_OptData::T_InstrPos{
 			oData.instructions.size( ) , iptr ,
@@ -87,7 +100,7 @@ void T_OptData::numberInstructions(
 uint32_t T_OptData::indexOf(
 		opast::A_InstructionNode const& instr ) noexcept
 {
-	const auto hash{ ComputeHash( (uint64_t)&instr ) };
+	const auto hash{ ebcl::ComputeHash( (uint64_t)&instr ) };
 	uint32_t existing{ instrIndex.first( hash ) };
 	while ( existing != T_HashIndex::INVALID_INDEX ) {
 		if ( &instr == instructions[ existing ].node ) {
@@ -100,17 +113,19 @@ uint32_t T_OptData::indexOf(
 }
 
 
-/*= T_OptData - CONSTROL FLOW GRAPH CONSTRUCTION =============================*/
+/*= T_OptData - CONTROL FLOW GRAPH CONSTRUCTION ==============================*/
 namespace {
 
 #warning Remove this later
-#define LL1 1
-#define LL2 1
+#define LL1 2
+#define LL2 2
 
+// CFG type shortcuts
 using T_CFN_ = T_OptData::T_CtrlFlowNode;
 using P_CFN_ = T_OptData::P_CtrlFlowNode;
 using RP_CFN_ = T_OptData::RP_CtrlFlowNode;
 
+// Helpers to create or re-use CFG nodes
 T_OptData::P_CtrlFlowNode BCFGNewNode_(
 		T_Array< P_CFN_ >& pool ) noexcept
 {
@@ -132,6 +147,8 @@ T_OptData::P_CtrlFlowNode BCFGNewNode_(
 
 } // namespace <anon>
 
+/*----------------------------------------------------------------------------*/
+
 void T_OptData::buildControlFlowGraph(
 		T_OpsParserOutput& program ) noexcept
 {
@@ -334,6 +351,109 @@ void T_OptData::buildControlFlowGraph(
 	}
 }
 
+#undef M_ADDNEW_
+#undef M_NEWNODE_
+
+
+/*= T_OptData - USE/DEFINE CHAINS ============================================*/
+
+#warning Remove this later
+#undef LL1
+#undef LL2
+#define LL1 1
+#define LL2 1
+
+void T_OptData::buildUseDefineChains(
+		T_OpsParserOutput& program ) noexcept
+{
+	M_LOGSTR_( "Building use/define chains" , LL1 );
+	varUDChains.clear( );
+
+	/*
+	 * We need to locate all variable uses. For each use, we will go
+	 * backwards in the graph, until one of the following becomes true:
+	 *	- we find an instruction that sets the variable;
+	 *	- we find the initial block due to cycling back;
+	 *	- we reached the CFG_ENTER node.
+	 * In the latter case, it's an error - the variable is never set.
+	 *
+	 * FIXME: Nope, actually that won't work. We need to find (set)
+	 * instuctions, calls, and also resource initialisations as well
+	 * when visiting the tree; *then* we can associate records from
+	 * both lists.
+	 *
+	 * Note: for function arguments, most of this can be avoided by simply
+	 * looking at the call sites.
+	 */
+	visitor.visit( program.root , [&]( auto& n , const bool exit ) {
+		if ( n.type( ) != A_Node::EXPR_ID || exit ) {
+			return true;
+		}
+
+		// Find instruction and function index
+		auto& nId{ dynamic_cast< T_IdentifierExprNode& >( n ) };
+		A_FuncNode* func{ nullptr };
+		T_Optional< uint32_t > instrId;
+		A_Node* pn{ &nId };
+		while ( pn ) {
+			auto* const asInstr{ dynamic_cast< A_InstructionNode* >( pn ) };
+			func = dynamic_cast< A_FuncNode* >( pn );
+			if ( !instrId && asInstr ) {
+				instrId = indexOf( *asInstr );
+			} else if ( func ) {
+				break;
+			}
+			pn = &pn->parent( );
+		}
+		assert( func && instrId );
+
+		// Generate the identifier
+		const T_VarId varId{ [&]() {
+			auto const& n{ nId.id( ) };
+			if ( func->hasLocal( nId.id( ) ) ) {
+				return T_VarId{ n , func->name( ) ,
+					func->isArgument( nId.id( ) ) };
+			}
+			return T_VarId{ n };
+		} () };
+
+		// Access or create the record
+		auto* const varRec{ [&]() {
+			auto* const x{ varUDChains.get( varId ) };
+			if ( x ) {
+				return x;
+			}
+			varUDChains.add( T_VarUseDefine{ varId } );
+			return varUDChains.get( varId );
+		} () };
+		assert( varRec );
+
+		// Add use record
+		auto& useRec{ varRec->uses.addNew( ) };
+		useRec.node = *instrId;
+		useRec.fnIndex = program.root.functionIndex( func->name( ) );
+
+		logger( [&](){
+			T_StringBuilder sb;
+			sb << "use " << varId.name << " at " << nId.location( ) << " (";
+			if ( varId.type == E_UDVarType::GLOBAL ) {
+				sb << "global";
+			} else {
+				if ( varId.type == E_UDVarType::LOCAL ) {
+					sb << "local";
+				} else {
+					sb << "argument";
+				}
+				sb << " of " << varId.owner;
+			}
+			sb << ')';
+			return sb;
+		} , LL2 );
+
+		return true;
+	} );
+}
+
 
 /*============================================================================*/
 
@@ -716,6 +836,7 @@ bool opopt::PropagateConstants(
 
 	oData.numberInstructions( program );
 	oData.buildControlFlowGraph( program );
+	oData.buildUseDefineChains( program );
 	M_LOGSTR_( "... Propagating constants" , 2 );
 
 	return false;
diff --git a/c-opopt.hh b/c-opopt.hh
index a052637..2603497 100644
--- a/c-opopt.hh
+++ b/c-opopt.hh
@@ -85,11 +85,87 @@ struct T_OptData
 	// Control flow graph
 	T_Array< P_CtrlFlowNode > ctrlFlowGraph;
 	T_KeyValueTable< T_String , T_BasicBlock > cfgFunctions;
+
+	// Build the control flow graph. Instruction numbering must be
+	// up-to-date before calling this.
 	void buildControlFlowGraph(
 			T_OpsParserOutput& program ) noexcept;
 
+	//----------------------------------------------------------------------
+
+	// Type of variables used for use/define chains
+	enum class E_UDVarType {
+		GLOBAL ,
+		LOCAL ,
+		ARGUMENT
+	};
+
+	// Variable identifier for the U/D chains
+	struct T_VarId
+	{
+		E_UDVarType type;			// Var type
+		T_String name;				// Var name
+		T_String owner;				// Function name (empty for globals)
+
+		explicit T_VarId( T_String const& name ) noexcept
+			: type{ E_UDVarType::GLOBAL } , name{ name } ,
+				owner{ }
+		{ }
+
+		T_VarId( T_String const& name ,
+				T_String const& owner ,
+				const bool isArgument ) noexcept
+			: type{ isArgument ? E_UDVarType::ARGUMENT : E_UDVarType::LOCAL } ,
+				name{ name } , owner{ owner }
+		{ assert( owner ); }
+
+		bool operator ==( T_VarId const& other ) const noexcept
+		{
+			return type == other.type && name == other.name
+				&& owner == other.owner;
+		}
+
+		bool operator !=( T_VarId const& other ) const noexcept
+		{
+			return type != other.type || name != other.name
+				|| owner != other.owner;
+		}
+	};
+
+	// Use/define chain data
+	struct T_VarUDRecord
+	{
+		uint32_t node;				// Instruction that uses or sets it
+		uint32_t fnIndex;			// Function in which the use/define is located
+		T_AutoArray< uint32_t , 16 > refs;	// Corresponding uses/defines
+	};
+
+	// Use/define chains for a variable
+	struct T_VarUseDefine
+	{
+		T_VarId var;
+		T_AutoArray< T_VarUDRecord , 16 > uses;
+		T_AutoArray< T_VarUDRecord , 4> defines;
+
+		explicit T_VarUseDefine( T_VarId const& id )
+			: var{ id }
+		{ }
+	};
+
+	// Use/define chains
+	T_ObjectTable< T_VarId , T_VarUseDefine > varUDChains{
+		[]( auto const& var ) -> T_VarId {
+			return var.var;
+		} };
+
+	// Build the use/define chains. The control flow graph must be up to
+	// date before calling this.
+	void buildUseDefineChains(
+			T_OpsParserOutput& program ) noexcept;
 };
 
+uint32_t ComputeHash( T_OptData::T_VarId const& id ) noexcept;
+
 
 /*= INDIVIDUAL OPTIMISATIONS =================================================*/
 // All functions below return true if transformations were made, false if not.