diff --git a/c-opast.cc b/c-opast.cc
index 1e3bae8..d0c0fa1 100644
--- a/c-opast.cc
+++ b/c-opast.cc
@@ -474,7 +474,7 @@ void T_InstrListNode::replaceMultiple(
 			children_.addNew( );
 		}
 		const auto newSize{ children_.size( ) };
-		const auto nMove{ oldSize + 1 - index };
+		const auto nMove{ rep.size( ) > 1 ? ( oldSize - index ) : 0 };
 		for ( auto i = 1u ; i < nMove ; i ++ ) {
 			children_[ newSize - i ] = std::move(
 					children_[ oldSize - i ] );
@@ -486,6 +486,8 @@ void T_InstrListNode::replaceMultiple(
 					rep.children_[ i ] );
 			children_[ index + i ]->setParent( *this );
 		}
+
+		rep.children_.clear( );
 	} else {
 		children_.remove( index );
 	}
diff --git a/c-opast.hh b/c-opast.hh
index 38ad3de..b038984 100644
--- a/c-opast.hh
+++ b/c-opast.hh
@@ -622,6 +622,9 @@ class T_SetInstrNode : public A_InstructionNode
 	ebcl::T_SRDLocation const& idLocation( ) const noexcept
 		{ return idLocation_; }
 
+	void id( T_String const& nid ) noexcept
+		{ id_ = nid; }
+
 	void setExpression( P_ExpressionNode expression ) noexcept
 	{
 		if ( children_.size( ) ) {
@@ -707,6 +710,13 @@ class T_FramebufferInstrNode : public A_ResourceDefInstrNode
 		T_ArgumentNode* lod( ) const noexcept
 			{ return (T_ArgumentNode*) ( children_.size( )
 					? child( 0 ).get( ) : nullptr ); }
+
+		void id( T_String const& nid ,
+				ebcl::T_SRDLocation const& loc ) noexcept
+		{
+			id_ = nid;
+			location( ) = loc;
+		}
 	};
 
     private:
@@ -802,6 +812,14 @@ class T_PipelineInstrNode : public A_ResourceDefInstrNode
 		{ return pids_[ index ]; }
 	ebcl::T_SRDLocation const& pLocation( const uint32_t index ) const noexcept
 		{ return pidLocations_[ index ]; }
+
+	void program( const uint32_t index ,
+			T_String const& id ,
+			ebcl::T_SRDLocation const& location ) noexcept
+	{
+		pids_[ index ] = id;
+		pidLocations_[ index ] = location;
+	}
 };
 
 // Program loader instruction
@@ -1119,6 +1137,9 @@ class T_UniformsInstrNode : public A_InstructionNode
 		{ return progId_; }
 	ebcl::T_SRDLocation const& progIdLocation( ) const noexcept
 		{ return progIdLocation_; }
+	void progId( T_String const& id ,
+			ebcl::T_SRDLocation const& location ) noexcept
+		{ progId_ = id; progIdLocation_ = location; }
 
 	uint32_t uloc( ) const noexcept
 		{ return uloc_; }
@@ -1171,6 +1192,9 @@ class T_UseInstrNode : public A_InstructionNode
 		{ return id_; }
 	ebcl::T_SRDLocation const& idLocation( ) const noexcept
 		{ return idLocation_; }
+	void id( T_String const& nid ,
+			ebcl::T_SRDLocation const& location ) noexcept
+		{ id_ = nid; idLocation_ = location; }
 };
 
 // Texture/sampler use instructions
@@ -1203,6 +1227,9 @@ class T_UseTextureInstrNode : public T_UseInstrNode
 		{ return samplerId_; }
 	ebcl::T_SRDLocation const& samplerIdLocation( ) const noexcept
 		{ return samplerIdLocation_; }
+	void samplerId( T_String const& id ,
+			ebcl::T_SRDLocation const& location ) noexcept
+		{ samplerId_ = id; samplerIdLocation_ = location; }
 };
 
 // Viewport instruction
diff --git a/c-opopt.cc b/c-opopt.cc
index e115f9c..4f14c47 100644
--- a/c-opopt.cc
+++ b/c-opopt.cc
@@ -1858,6 +1858,8 @@ bool opopt::InlineFunctions(
 		T_String target;
 		T_CallInstrNode* callInstrNode;
 		uint32_t fnIndex;
+		uint32_t nArgs{ 0 };
+		uint32_t nLocals{ 0 };
 
 		T_InlineFunction_(
 				T_String target ,
@@ -1871,6 +1873,7 @@ bool opopt::InlineFunctions(
 	struct T_InlineTarget_ {
 		uint32_t count{ 1 };
 		uint32_t extraLocals{ 0 };
+		T_AutoArray< T_String , 16 > elNames;
 	};
 
 	// Find functions that can be inlined
@@ -1970,11 +1973,12 @@ bool opopt::InlineFunctions(
 	// Compute extra locals required
 	const auto nInlines{ ordered.size( ) };
 	for ( auto i = 0u ; i < nInlines ; i ++ ) {
-		auto const& ifd{ inlineFunctions.values( )[ ordered[ i ] ] };
+		auto& ifd{ inlineFunctions.values( )[ ordered[ i ] ] };
 		auto const& fn{ program.root.function( ifd.fnIndex ) };
 		uint32_t nLocals{ fn.locals( ) };
 		for ( auto j = 0u ; j < fn.locals( ) ; j ++ ) {
 			if ( fn.isArgument( fn.getLocalName( j ) ) ) {
+				ifd.nArgs ++;
 				nLocals --;
 			}
 		}
@@ -1984,11 +1988,234 @@ bool opopt::InlineFunctions(
 			nLocals += asTarget->extraLocals;
 		}
 
+		ifd.nLocals = nLocals;
+
 		auto* target{ inlineTargets.get( ifd.target ) };
 		target->extraLocals = std::max( target->extraLocals , nLocals );
 	}
 
-	// Merge (bottom to top)
+	// Add extra locals
+	const auto nTargets{ inlineTargets.size( ) };
+	T_StringBuilder idsb;
+	for ( auto i = 0u ; i < nTargets ; i ++ ) {
+		auto& itd{ inlineTargets.values( )[ i ] };
+		auto& fn{ program.root.function( inlineTargets.keys( )[ i ] ) };
+		for ( auto j = 0u , c = 0u ; c < itd.extraLocals ; j ++ ) {
+			idsb.clear( ) << "*inline*" << j;
+			T_String id{ idsb };
+			if ( !fn.hasLocal( id ) ) {
+				fn.addLocalVariable( id , T_SRDLocation{} );
+				fn.setLocalType( fn.locals( ) - 1 ,
+						opast::E_DataType::VARIABLE );
+				itd.elNames.add( std::move( id ) );
+				c ++;
+			}
+		}
+	}
 
-	return false;
+	// Merge (bottom to top)
+	for ( auto i = 0u ; i < nInlines ; i ++ ) {
+		auto const& ifd{ inlineFunctions.values( )[ i ] };
+		auto const& itd{ *inlineTargets.get( ifd.target ) };
+		auto& src{ program.root.function( ifd.fnIndex ) };
+		auto& call{ *ifd.callInstrNode };
+		oData.logger( [&]() {
+			T_StringBuilder sb;
+			sb << "Inlining function " << src.name( );
+			return sb;
+		} , 5 );
+
+		// Replace arguments and locals
+		oData.visitor.visit( src.instructions( ) , [&]( A_Node& node , const bool exit ) {
+			if ( !exit ) {
+				return true;
+			}
+
+			switch ( node.type( ) ) {
+
+			    default: break;
+
+			    case A_Node::EXPR_ID: {
+				auto const& id{ (T_IdentifierExprNode&) node };
+				if ( !src.hasLocal( id.id( ) ) ) {
+					break;
+				}
+				T_OwnPtr< A_Node > nv;
+				if ( src.isArgument( id.id( ) ) ) {
+					const auto argIndex{ src.getLocalIndex( id.id( ) ) };
+					auto const& callArg{ call.argument( argIndex ) };
+					auto const& caValue{ callArg.expression( ) };
+					if ( caValue.type( ) == A_Node::EXPR_CONST ) {
+						nv = NewOwned< T_ConstantExprNode >(
+								node.parent( ) ,
+								( (T_ConstantExprNode&) caValue ).floatValue( ) );
+					} else {
+						nv = NewOwned< T_IdentifierExprNode >(
+								node.parent( ) ,
+								( (T_IdentifierExprNode&) caValue ).id( ) );
+					}
+				} else {
+					nv = NewOwned< T_IdentifierExprNode >(
+							node.parent( ) ,
+							itd.elNames[ src.getLocalIndex( id.id( ) ) - ifd.nArgs ] );
+				}
+				oData.logger( [&]() {
+					T_StringBuilder sb;
+					sb << "Replacing identifier " << id.id( )
+						<< " at " << id.location( )
+						<< " with ";
+					if ( nv->type( ) == A_Node::EXPR_ID ) {
+						sb << "identifier " << ((T_IdentifierExprNode&)*nv).id( );
+					} else {
+						sb << "constant " << ((T_ConstantExprNode&)*nv).floatValue( );
+					}
+					return sb;
+				} , 6 );
+				node.parent( ).replace( node , nv );
+				break;
+			    }
+
+			    case A_Node::OP_UNIFORMS: {
+				auto& n{ (T_UniformsInstrNode&) node };
+				if ( src.isArgument( n.progId( ) ) ) {
+					const auto argIndex{ src.getLocalIndex( n.progId( ) ) };
+					auto const& callArg{ call.argument( argIndex ) };
+					auto const& caValue{ callArg.expression( ) };
+					assert( caValue.type( ) == A_Node::EXPR_ID );
+					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
+					oData.logger( [&]() {
+						T_StringBuilder sb;
+						sb << "Replacing identifier " << n.progId( )
+							<< " at " << n.progIdLocation( )
+							<< " with identifier " << repId;
+						return sb;
+					} , 6 );
+					n.progId( repId , caValue.location( ) );
+				}
+				break;
+			    }
+
+			    case A_Node::OP_USE_TEXTURE: {
+				auto& n{ (T_UseTextureInstrNode&) node };
+				if ( src.isArgument( n.samplerId( ) ) ) {
+					const auto argIndex{ src.getLocalIndex( n.samplerId( ) ) };
+					auto const& callArg{ call.argument( argIndex ) };
+					auto const& caValue{ callArg.expression( ) };
+					assert( caValue.type( ) == A_Node::EXPR_ID );
+					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
+					oData.logger( [&]() {
+						T_StringBuilder sb;
+						sb << "Replacing identifier " << n.samplerId( )
+							<< " at " << n.samplerIdLocation( )
+							<< " with identifier " << repId;
+						return sb;
+					} , 6 );
+					n.samplerId( repId , caValue.location( ) );
+				}
+			    }	// fallthrough
+			    case A_Node::OP_USE_PROGRAM:
+			    case A_Node::OP_USE_PIPELINE:
+			    case A_Node::OP_USE_FRAMEBUFFER: {
+				auto& n{ (T_UseInstrNode&) node };
+				if ( src.isArgument( n.id( ) ) ) {
+					const auto argIndex{ src.getLocalIndex( n.id( ) ) };
+					auto const& callArg{ call.argument( argIndex ) };
+					auto const& caValue{ callArg.expression( ) };
+					assert( caValue.type( ) == A_Node::EXPR_ID );
+					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
+					oData.logger( [&]() {
+						T_StringBuilder sb;
+						sb << "Replacing identifier " << n.id( )
+							<< " at " << n.idLocation( )
+							<< " with identifier " << repId;
+						return sb;
+					} , 6 );
+					n.id( repId , caValue.location( ) );
+				}
+				break;
+			    }
+
+			    case A_Node::OP_PIPELINE: {
+				auto& n{ (T_PipelineInstrNode&) node };
+				const auto np{ n.size( ) };
+				for ( auto i = 0u ; i < np ; i ++ ) {
+					if ( src.isArgument( n.program( i ) ) ) {
+						const auto argIndex{ src.getLocalIndex( n.program( i ) ) };
+						auto const& callArg{ call.argument( argIndex ) };
+						auto const& caValue{ callArg.expression( ) };
+						assert( caValue.type( ) == A_Node::EXPR_ID );
+						auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
+						oData.logger( [&]() {
+							T_StringBuilder sb;
+							sb << "Replacing identifier " << n.program( i )
+								<< " at " << n.pLocation( i )
+								<< " with identifier " << repId;
+							return sb;
+						} , 6 );
+						n.program( i , repId , caValue.location( ) );
+					}
+				}
+				break;
+			    }
+
+			    case A_Node::TN_FBATT: {
+				auto& n{ (T_FramebufferInstrNode::T_Attachment&) node };
+				if ( src.isArgument( n.id( ) ) ) {
+					const auto argIndex{ src.getLocalIndex( n.id( ) ) };
+					auto const& callArg{ call.argument( argIndex ) };
+					auto const& caValue{ callArg.expression( ) };
+					assert( caValue.type( ) == A_Node::EXPR_ID );
+					auto const& repId{ ( (T_IdentifierExprNode&) caValue ).id( ) };
+					oData.logger( [&]() {
+						T_StringBuilder sb;
+						sb << "Replacing identifier " << n.id( )
+							<< " at " << n.location( )
+							<< " with identifier " << repId;
+						return sb;
+					} , 6 );
+					n.id( repId , caValue.location( ) );
+				}
+				break;
+			    }
+
+			    case A_Node::OP_SET: {
+				auto& n{ (T_SetInstrNode&) node };
+				if ( !src.hasLocal( n.id( ) ) ) {
+					break;
+				}
+				auto const& nid{
+					itd.elNames[ src.getLocalIndex( n.id( ) ) - ifd.nArgs ]
+				};
+				oData.logger( [&]() {
+					T_StringBuilder sb;
+					sb << "Replacing identifier " << n.id( )
+						<< " at " << n.location( )
+						<< " with identifier " << nid;
+					return sb;
+				} , 6 );
+				n.id( nid );
+				break;
+			    }
+
+			}
+
+			return true;
+		} );
+
+		((T_InstrListNode&) call.parent( )).replaceMultiple(
+			call , &src.instructions( ) );
+	}
+
+	T_AutoArray< T_String , 32 > iffns;
+	iffns.ensureCapacity( nInlines );
+	for ( auto i = 0u ; i < nInlines ; i ++ ) {
+		auto const& ifd{ inlineFunctions.values( )[ i ] };
+		iffns.add( program.root.function( ifd.fnIndex ).name( ) );
+	}
+	for ( auto i = 0u ; i < nInlines ; i ++ ) {
+		program.root.removeFunction( iffns[ i ] );
+	}
+
+	oData.state = {};
+	return true;
 }