[vala] Add support for closures



commit 1f14688e1d3508a2ba3c62322c362b468cea6bf8
Author: Jürg Billeter <j bitron ch>
Date:   Tue Sep 15 17:09:02 2009 +0200

    Add support for closures
    
    Fixes bug 554781.

 codegen/valaccodebasemodule.vala         |  126 +++++++++++++++++++++++++++++-
 codegen/valaccodedelegatemodule.vala     |   22 ++++-
 codegen/valaccodememberaccessmodule.vala |    4 +
 codegen/valaccodemethodcallmodule.vala   |   28 +++++--
 codegen/valaccodemethodmodule.vala       |   49 +++++++++++-
 vala/valablock.vala                      |    2 +
 vala/valalocalvariable.vala              |    2 +
 vala/valamemberaccess.vala               |   10 ++-
 vala/valamethod.vala                     |    2 +
 vala/valasemanticanalyzer.vala           |    7 ++
 10 files changed, 234 insertions(+), 18 deletions(-)
---
diff --git a/codegen/valaccodebasemodule.vala b/codegen/valaccodebasemodule.vala
index 3b60885..44b5e4b 100644
--- a/codegen/valaccodebasemodule.vala
+++ b/codegen/valaccodebasemodule.vala
@@ -132,6 +132,8 @@ internal class Vala.CCodeBaseModule : CCodeModule {
 	public bool in_static_or_class_context = false;
 	public bool current_method_inner_error = false;
 	public int next_coroutine_state = 1;
+	int next_block_id = 0;
+	Map<Block,int> block_map = new HashMap<Block,int> ();
 
 	public DataType void_type = new VoidType ();
 	public DataType bool_type;
@@ -1574,6 +1576,15 @@ internal class Vala.CCodeBaseModule : CCodeModule {
 		current_method_inner_error = old_method_inner_error;
 	}
 
+	public int get_block_id (Block b) {
+		int result = block_map[b];
+		if (result == 0) {
+			result = ++next_block_id;
+			block_map[b] = result;
+		}
+		return result;
+	}
+
 	public override void visit_block (Block b) {
 		var old_symbol = current_symbol;
 		current_symbol = b;
@@ -1586,7 +1597,104 @@ internal class Vala.CCodeBaseModule : CCodeModule {
 		}
 		
 		var cblock = new CCodeBlock ();
-		
+
+		if (b.captured) {
+			var parent_block = b.parent_symbol as Block;
+			while (parent_block != null && !parent_block.captured) {
+				parent_block = parent_block.parent_symbol as Block;
+			}
+
+			int block_id = get_block_id (b);
+			string struct_name = "Block%dData".printf (block_id);
+
+			var free_block = new CCodeBlock ();
+
+			var data = new CCodeStruct ("_" + struct_name);
+			data.add_field ("int", "ref_count");
+			if (parent_block != null) {
+				int parent_block_id = get_block_id (parent_block);
+
+				data.add_field ("Block%dData *".printf (parent_block_id), "_data%d_".printf (parent_block_id));
+
+				var unref_call = new CCodeFunctionCall (new CCodeIdentifier ("block%d_data_unref".printf (parent_block_id)));
+				unref_call.add_argument (new CCodeMemberAccess.pointer (new CCodeIdentifier ("data"), "_data%d_".printf (parent_block_id)));
+				free_block.add_statement (new CCodeExpressionStatement (unref_call));
+			} else if (current_method.binding == MemberBinding.INSTANCE) {
+				data.add_field ("%s *".printf (current_class.get_cname ()), "self");
+
+				var ma = new MemberAccess.simple ("this");
+				ma.symbol_reference = current_class;
+				free_block.add_statement (new CCodeExpressionStatement (get_unref_expression (new CCodeMemberAccess.pointer (new CCodeIdentifier ("data"), "self"), new ObjectType (current_class), ma)));
+			}
+			foreach (var local in local_vars) {
+				if (local.captured) {
+					data.add_field (local.variable_type.get_cname (), get_variable_cname (local.name) + local.variable_type.get_cdeclarator_suffix ());
+
+					if (local.variable_type is DelegateType) {
+						data.add_field ("gpointer", get_delegate_target_cname (get_variable_cname (local.name)));
+					}
+
+					if (requires_destroy (local.variable_type)) {
+						var ma = new MemberAccess.simple (local.name);
+						ma.symbol_reference = local;
+						free_block.add_statement (new CCodeExpressionStatement (get_unref_expression (new CCodeMemberAccess.pointer (new CCodeIdentifier ("data"), get_variable_cname (local.name)), local.variable_type, ma)));
+					}
+				}
+			}
+
+			var typedef = new CCodeTypeDefinition ("struct _" + struct_name, new CCodeVariableDeclarator (struct_name));
+			source_declarations.add_type_declaration (typedef);
+			source_declarations.add_type_definition (data);
+
+			var data_alloc = new CCodeFunctionCall (new CCodeIdentifier ("g_slice_new0"));
+			data_alloc.add_argument (new CCodeIdentifier (struct_name));
+
+			var data_decl = new CCodeDeclaration (struct_name + "*");
+			data_decl.add_declarator (new CCodeVariableDeclarator ("_data%d_".printf (block_id), data_alloc));
+			cblock.add_statement (data_decl);
+
+			// initialize ref_count
+			cblock.add_statement (new CCodeExpressionStatement (new CCodeAssignment (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (block_id)), "ref_count"), new CCodeIdentifier ("1"))));
+
+			if (parent_block != null) {
+				int parent_block_id = get_block_id (parent_block);
+
+				var ref_call = new CCodeFunctionCall (new CCodeIdentifier ("block%d_data_ref".printf (parent_block_id)));
+				ref_call.add_argument (new CCodeIdentifier ("_data%d_".printf (parent_block_id)));
+
+				cblock.add_statement (new CCodeExpressionStatement (new CCodeAssignment (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (block_id)), "_data%d_".printf (parent_block_id)), ref_call)));
+			} else if (current_method.binding == MemberBinding.INSTANCE) {
+				var ref_call = new CCodeFunctionCall (get_dup_func_expression (new ObjectType (current_class), b.source_reference));
+				ref_call.add_argument (new CCodeIdentifier ("self"));
+
+				cblock.add_statement (new CCodeExpressionStatement (new CCodeAssignment (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (block_id)), "self"), ref_call)));
+			}
+
+			var data_free = new CCodeFunctionCall (new CCodeIdentifier ("g_slice_free"));
+			data_free.add_argument (new CCodeIdentifier (struct_name));
+			data_free.add_argument (new CCodeIdentifier ("data"));
+			free_block.add_statement (new CCodeExpressionStatement (data_free));
+
+			// create ref/unref functions
+			var ref_fun = new CCodeFunction ("block%d_data_ref".printf (block_id), struct_name + "*");
+			ref_fun.add_parameter (new CCodeFormalParameter ("data", struct_name + "*"));
+			ref_fun.modifiers = CCodeModifiers.STATIC;
+			source_declarations.add_type_member_declaration (ref_fun.copy ());
+			ref_fun.block = new CCodeBlock ();
+			ref_fun.block.add_statement (new CCodeExpressionStatement (new CCodeUnaryExpression (CCodeUnaryOperator.PREFIX_INCREMENT, new CCodeMemberAccess.pointer (new CCodeIdentifier ("data"), "ref_count"))));
+			ref_fun.block.add_statement (new CCodeReturnStatement (new CCodeIdentifier ("data")));
+			source_type_member_definition.append (ref_fun);
+
+			var unref_fun = new CCodeFunction ("block%d_data_unref".printf (block_id), struct_name + "*");
+			unref_fun.add_parameter (new CCodeFormalParameter ("data", struct_name + "*"));
+			unref_fun.modifiers = CCodeModifiers.STATIC;
+			source_declarations.add_type_member_declaration (unref_fun.copy ());
+			unref_fun.block = new CCodeBlock ();
+			var dec = new CCodeBinaryExpression (CCodeBinaryOperator.EQUALITY, new CCodeUnaryExpression (CCodeUnaryOperator.PREFIX_DECREMENT, new CCodeMemberAccess.pointer (new CCodeIdentifier ("data"), "ref_count")), new CCodeConstant ("0"));
+			unref_fun.block.add_statement (new CCodeIfStatement (dec, free_block));
+			source_type_member_definition.append (unref_fun);
+		}
+
 		foreach (CodeNode stmt in b.get_statements ()) {
 			if (stmt.error) {
 				continue;
@@ -1602,7 +1710,7 @@ internal class Vala.CCodeBaseModule : CCodeModule {
 		}
 
 		foreach (LocalVariable local in local_vars) {
-			if (!local.floating && requires_destroy (local.variable_type)) {
+			if (!local.floating && !local.captured && requires_destroy (local.variable_type)) {
 				var ma = new MemberAccess.simple (local.name);
 				ma.symbol_reference = local;
 				cblock.add_statement (new CCodeExpressionStatement (get_unref_expression (get_variable_cexpression (local.name), local.variable_type, ma)));
@@ -1620,6 +1728,14 @@ internal class Vala.CCodeBaseModule : CCodeModule {
 			}
 		}
 
+		if (b.captured) {
+			int block_id = get_block_id (b);
+
+			var data_unref = new CCodeFunctionCall (new CCodeIdentifier ("block%d_data_unref".printf (block_id)));
+			data_unref.add_argument (new CCodeIdentifier ("_data%d_".printf (block_id)));
+			cblock.add_statement (new CCodeExpressionStatement (data_unref));
+		}
+
 		b.ccodenode = cblock;
 
 		current_symbol = old_symbol;
@@ -1778,7 +1894,11 @@ internal class Vala.CCodeBaseModule : CCodeModule {
 			pre_statement_fragment = null;
 		}
 
-		if (current_method != null && current_method.coroutine) {
+		if (local.captured) {
+			if (local.initializer != null) {
+				cfrag.append (new CCodeExpressionStatement (new CCodeAssignment (new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (get_block_id ((Block) local.parent_symbol))), get_variable_cname (local.name)), rhs)));
+			}
+		} else if (current_method != null && current_method.coroutine) {
 			closure_struct.add_field (local.variable_type.get_cname (), get_variable_cname (local.name) + local.variable_type.get_cdeclarator_suffix ());
 
 			if (local.initializer != null) {
diff --git a/codegen/valaccodedelegatemodule.vala b/codegen/valaccodedelegatemodule.vala
index 7fb93a4..357e1dc 100644
--- a/codegen/valaccodedelegatemodule.vala
+++ b/codegen/valaccodedelegatemodule.vala
@@ -136,7 +136,13 @@ internal class Vala.CCodeDelegateModule : CCodeArrayModule {
 			var invocation_expr = (MethodCall) delegate_expr;
 			return invocation_expr.delegate_target;
 		} else if (delegate_expr is LambdaExpression) {
-			if (get_this_type () != null || in_constructor) {
+			var closure_block = current_symbol as Block;
+			while (closure_block != null && !closure_block.captured) {
+				closure_block = closure_block.parent_symbol as Block;
+			}
+			if (closure_block != null) {
+				return new CCodeIdentifier ("_data%d_".printf (get_block_id (closure_block)));
+			} else if (get_this_type () != null || in_constructor) {
 				return new CCodeIdentifier ("self");
 			} else {
 				return new CCodeConstant ("NULL");
@@ -157,11 +163,17 @@ internal class Vala.CCodeDelegateModule : CCodeArrayModule {
 				}
 			} else if (delegate_expr.symbol_reference is LocalVariable) {
 				var local = (LocalVariable) delegate_expr.symbol_reference;
-				var target_expr = new CCodeIdentifier (get_delegate_target_cname (local.name));
-				if (is_out) {
-					return new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, target_expr);
+				if (local.captured) {
+					// captured variables are stored on the heap
+					var block = (Block) local.parent_symbol;
+					return new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (get_block_id (block))), get_delegate_target_cname (local.name));
 				} else {
-					return target_expr;
+					var target_expr = new CCodeIdentifier (get_delegate_target_cname (local.name));
+					if (is_out) {
+						return new CCodeUnaryExpression (CCodeUnaryOperator.ADDRESS_OF, target_expr);
+					} else {
+						return target_expr;
+					}
 				}
 			} else if (delegate_expr.symbol_reference is Field) {
 				var field = (Field) delegate_expr.symbol_reference;
diff --git a/codegen/valaccodememberaccessmodule.vala b/codegen/valaccodememberaccessmodule.vala
index d698a03..027d4a2 100644
--- a/codegen/valaccodememberaccessmodule.vala
+++ b/codegen/valaccodememberaccessmodule.vala
@@ -344,6 +344,10 @@ internal class Vala.CCodeMemberAccessModule : CCodeControlFlowModule {
 			if (local.is_result) {
 				// used in postconditions
 				expr.ccodenode = new CCodeIdentifier ("result");
+			} else if (local.captured) {
+				// captured variables are stored on the heap
+				var block = (Block) local.parent_symbol;
+				expr.ccodenode = new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (get_block_id (block))), get_variable_cname (local.name));
 			} else {
 				expr.ccodenode = get_variable_cexpression (local.name);
 			}
diff --git a/codegen/valaccodemethodcallmodule.vala b/codegen/valaccodemethodcallmodule.vala
index 619ac36..efff5c1 100644
--- a/codegen/valaccodemethodcallmodule.vala
+++ b/codegen/valaccodemethodcallmodule.vala
@@ -295,13 +295,27 @@ internal class Vala.CCodeMethodCallModule : CCodeAssignmentModule {
 								var delegate_method = arg.symbol_reference as Method;
 								var lambda = arg as LambdaExpression;
 								var arg_ma = arg as MemberAccess;
-								if (lambda != null && get_this_type () != null) {
-									// type of delegate target is same as `this'
-									// for lambda expressions in instance methods
-									var ref_call = new CCodeFunctionCall (get_dup_func_expression (get_this_type (), arg.source_reference));
-									ref_call.add_argument (delegate_target);
-									delegate_target = ref_call;
-									delegate_target_destroy_notify = get_destroy_func_expression (get_this_type ());
+								if (lambda != null) {
+									if (delegate_method.closure) {
+										var closure_block = current_symbol as Block;
+										while (closure_block != null && !closure_block.captured) {
+											closure_block = closure_block.parent_symbol as Block;
+										}
+										int block_id = get_block_id (closure_block);
+										var ref_call = new CCodeFunctionCall (new CCodeIdentifier ("block%d_data_ref".printf (block_id)));
+										ref_call.add_argument (delegate_target);
+										delegate_target = ref_call;
+										delegate_target_destroy_notify = new CCodeIdentifier ("block%d_data_unref".printf (block_id));
+									} else if (get_this_type () != null) {
+										// type of delegate target is same as `this'
+										// for lambda expressions in instance methods
+										var ref_call = new CCodeFunctionCall (get_dup_func_expression (get_this_type (), arg.source_reference));
+										ref_call.add_argument (delegate_target);
+										delegate_target = ref_call;
+										delegate_target_destroy_notify = get_destroy_func_expression (get_this_type ());
+									} else {
+										delegate_target_destroy_notify = new CCodeConstant ("NULL");
+									}
 								} else if (delegate_method != null && delegate_method.binding == MemberBinding.INSTANCE
 								           && arg_ma != null && arg_ma.inner != null && arg_ma.inner.value_type.data_type != null
 								           && arg_ma.inner.value_type.data_type.is_reference_counting ()) {
diff --git a/codegen/valaccodemethodmodule.vala b/codegen/valaccodemethodmodule.vala
index ff471c3..670a2a9 100644
--- a/codegen/valaccodemethodmodule.vala
+++ b/codegen/valaccodemethodmodule.vala
@@ -398,7 +398,44 @@ internal class Vala.CCodeMethodModule : CCodeStructModule {
 					source_type_member_definition.append (co_function);
 				}
 
-				if (m.parent_symbol is Class && !m.coroutine) {
+				if (m.closure) {
+					// add variables for parent closure blocks
+					// as closures only have one parameter for the innermost closure block
+					var closure_block = m.parent_symbol as Block;
+					while (closure_block != null && !closure_block.captured) {
+						closure_block = closure_block.parent_symbol as Block;
+					}
+					int block_id = get_block_id (closure_block);
+					while (true) {
+						var parent_closure_block = closure_block.parent_symbol as Block;
+						while (parent_closure_block != null && !parent_closure_block.captured) {
+							parent_closure_block = parent_closure_block.parent_symbol as Block;
+						}
+						if (parent_closure_block == null) {
+							break;
+						}
+						int parent_block_id = get_block_id (parent_closure_block);
+
+						var parent_data = new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (block_id)), "_data%d_".printf (parent_block_id));
+						var cdecl = new CCodeDeclaration ("Block%dData*".printf (parent_block_id));
+						cdecl.add_declarator (new CCodeVariableDeclarator ("_data%d_".printf (parent_block_id), parent_data));
+
+						cinit.append (cdecl);
+
+						closure_block = parent_closure_block;
+						block_id = parent_block_id;
+					}
+
+					// add self variable for closures
+					// as closures have block data parameter
+					if (m.binding == MemberBinding.INSTANCE) {
+						var cself = new CCodeMemberAccess.pointer (new CCodeIdentifier ("_data%d_".printf (block_id)), "self");
+						var cdecl = new CCodeDeclaration ("%s *".printf (current_class.get_cname ()));
+						cdecl.add_declarator (new CCodeVariableDeclarator ("self", cself));
+
+						cinit.append (cdecl);
+					}
+				} else if (m.parent_symbol is Class && !m.coroutine) {
 					var cl = (Class) m.parent_symbol;
 					if (m.overrides || (m.base_interface_method != null && !m.is_abstract && !m.is_virtual)) {
 						Method base_method;
@@ -720,7 +757,15 @@ internal class Vala.CCodeMethodModule : CCodeStructModule {
 	}
 
 	public override void generate_cparameters (Method m, CCodeDeclarationSpace decl_space, Map<int,CCodeFormalParameter> cparam_map, CCodeFunction func, CCodeFunctionDeclarator? vdeclarator = null, Map<int,CCodeExpression>? carg_map = null, CCodeFunctionCall? vcall = null, int direction = 3) {
-		if (m.parent_symbol is Class && m is CreationMethod) {
+		if (m.closure) {
+			var closure_block = m.parent_symbol as Block;
+			while (closure_block != null && !closure_block.captured) {
+				closure_block = closure_block.parent_symbol as Block;
+			}
+			int block_id = get_block_id (closure_block);
+			var instance_param = new CCodeFormalParameter ("_data%d_".printf (block_id), "Block%dData*".printf (block_id));
+			cparam_map.set (get_param_pos (m.cinstance_parameter_position), instance_param);
+		} else if (m.parent_symbol is Class && m is CreationMethod) {
 			var cl = (Class) m.parent_symbol;
 			if (!cl.is_compact && vcall == null) {
 				cparam_map.set (get_param_pos (m.cinstance_parameter_position), new CCodeFormalParameter ("object_type", "GType"));
diff --git a/vala/valablock.vala b/vala/valablock.vala
index 9c99ec8..e9dfe2d 100644
--- a/vala/valablock.vala
+++ b/vala/valablock.vala
@@ -33,6 +33,8 @@ public class Vala.Block : Symbol, Statement {
 	 */
 	public bool contains_jump_statement { get; set; }
 
+	public bool captured { get; set; }
+
 	private Gee.List<Statement> statement_list = new ArrayList<Statement> ();
 	private Gee.List<LocalVariable> local_variables = new ArrayList<LocalVariable> ();
 	
diff --git a/vala/valalocalvariable.vala b/vala/valalocalvariable.vala
index c404b2d..8d600cb 100644
--- a/vala/valalocalvariable.vala
+++ b/vala/valalocalvariable.vala
@@ -61,6 +61,8 @@ public class Vala.LocalVariable : Symbol {
 	 */
 	public bool floating { get; set; }
 
+	public bool captured { get; set; }
+
 	private Expression? _initializer;
 	private DataType? _variable_type;
 
diff --git a/vala/valamemberaccess.vala b/vala/valamemberaccess.vala
index 8413f34..1e076e4 100644
--- a/vala/valamemberaccess.vala
+++ b/vala/valamemberaccess.vala
@@ -419,7 +419,15 @@ public class Vala.MemberAccess : Expression {
 			return false;
 		}
 
-		if (member is Field) {
+		if (member is LocalVariable) {
+			var local = (LocalVariable) member;
+			var block = (Block) local.parent_symbol;
+			if (analyzer.find_parent_method (block) != analyzer.current_method) {
+				local.captured = true;
+				block.captured = true;
+				analyzer.current_method.closure = true;
+			}
+		} else if (member is Field) {
 			var f = (Field) member;
 			access = f.access;
 			instance = (f.binding == MemberBinding.INSTANCE);
diff --git a/vala/valamethod.vala b/vala/valamethod.vala
index a3cc995..b0ecc39 100644
--- a/vala/valamethod.vala
+++ b/vala/valamethod.vala
@@ -222,6 +222,8 @@ public class Vala.Method : Member {
 
 	public weak Signal signal_reference { get; set; }
 
+	public bool closure { get; set; }
+
 	public bool coroutine { get; set; }
 
 	public bool is_async_callback { get; set; }
diff --git a/vala/valasemanticanalyzer.vala b/vala/valasemanticanalyzer.vala
index 1527d02..103344b 100644
--- a/vala/valasemanticanalyzer.vala
+++ b/vala/valasemanticanalyzer.vala
@@ -786,6 +786,13 @@ public class Vala.SemanticAnalyzer : CodeVisitor {
 		return null;
 	}
 
+	public Method? find_parent_method (Symbol sym) {
+		while (sym is Block) {
+			sym = sym.parent_symbol;
+		}
+		return sym as Method;
+	}
+
 	public bool is_in_constructor () {
 		var sym = current_symbol;
 		while (sym != null) {



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]