diff --git a/TODO b/TODO
index da15ec9..8db62c2 100644
--- a/TODO
+++ b/TODO
@@ -15,7 +15,6 @@ Scripting:
 * Display errors in UI
 * Optimizer:
 	* Dead code elimination
-		* Local variables
 		* Unused arguments
 		* Dead store: call arguments
 		* Unused functions
diff --git a/c-opast.hh b/c-opast.hh
index b457c96..38ad3de 100644
--- a/c-opast.hh
+++ b/c-opast.hh
@@ -315,6 +315,9 @@ class A_FuncNode : public A_Node
 	T_String const& getLocalName(
 			const uint32_t index ) const noexcept
 		{ return locals_[ index ].name; }
+	ebcl::T_SRDLocation const& getLocalLocation(
+			const uint32_t index ) const noexcept
+		{ return locals_[ index ].location; }
 
 	E_DataType getLocalType(
 			T_String const& name ) const noexcept
@@ -327,6 +330,9 @@ class A_FuncNode : public A_Node
 			const uint32_t index ,
 			const E_DataType type ) noexcept
 		{ locals_[ index ].type = type; }
+
+	void removeLocal( T_String const& name ) noexcept
+		{ locals_.remove( name ); }
 };
 using P_InstrListNode = T_OwnPtr< T_InstrListNode >;
 
diff --git a/c-opopt.cc b/c-opopt.cc
index 80cf7d4..13e6b4f 100644
--- a/c-opopt.cc
+++ b/c-opopt.cc
@@ -1702,6 +1702,51 @@ bool RDCDeadStores_(
 	return changesMade;
 }
 
+/*----------------------------------------------------------------------------*/
+
+bool RDCUnusedLocals_(
+		T_OpsParserOutput&	program ,
+		T_OptData&		oData
+		) noexcept
+{
+	M_LOGSTR_( "...... Removing unused locals" , 3 );
+	oData.buildUseDefineChains( program );
+
+	const auto nf{ program.root.nFunctions( ) };
+	bool changed{ false };
+	for ( auto f = 0u ; f < nf ; f ++ ) {
+		auto& func{ program.root.function( f ) };
+		bool restart{ false };
+		for ( auto l = 0u ; l < func.locals( ) ;
+				restart ? ( restart = false , l = 0 ) : l ++ ) {
+
+			auto const& ln{ func.getLocalName( l ) };
+			if ( func.isArgument( ln ) ) {
+				// TODO
+				continue;
+			}
+
+			const T_OptData::T_VarId vid{
+				ln , func.name( ) , false
+			};
+			if ( oData.varUDChains.contains( vid ) ) {
+				continue;
+			}
+			oData.logger( [&](){
+				T_StringBuilder sb;
+				sb << "Removing used local variable "
+					<< ln << " at "
+					<< func.getLocalLocation( l );
+				return sb;
+			} , 4 );
+			func.removeLocal( ln );
+			restart = true;
+			changed = true;
+		}
+	}
+	return changed;
+}
+
 } // namespace <anon>
 
 /*----------------------------------------------------------------------------*/
@@ -1724,7 +1769,8 @@ bool opopt::RemoveDeadCode(
 		}
 
 		didStuffThisTime = RDCConditionals_( program , oData )
-			|| RDCDeadStores_( program , oData );
+			|| RDCDeadStores_( program , oData )
+			|| RDCUnusedLocals_( program , oData );
 		didStuff = didStuff || didStuffThisTime;
 	} while ( didStuffThisTime );