diff: Non-local lambda vars (round 2)

Andrew Bromage bromage at cs.mu.OZ.AU
Tue Feb 10 16:07:50 AEDT 1998


G'day all.

Fergus, here's the diff mark 2 as requested, incorporating your
suggested changes, plus a little more documentation.

Cheers,
Andrew Bromage


Estimated hours taken: 6.5

Fix a semantic hole.  Previously, unique variables could be used
as non-local variables inside a lambda goal and shared within that
lambda.  This fixes the problem by requiring that all non-local
vars to a lambda must be ground-shared.

compiler/modecheck_unify.m:
	Changes detailed above.

compiler/mode_errors.m:
	Add a new kind of mode error, mode_error_non_local_lambda_var.
	One small change to find_important_errors/3 to ensure that this
	error will be reported if the lambda is an implicit call argument
	unification.

compiler/inst_util.m:
	Export make_shared_inst_list/4 for use by modecheck_unify.m.

compiler/instmap.m:
	New predicate: instmap__set_vars/4, which sets multiple vars
	in an instmap.

tests/invalid/bind_var_errors.m:
tests/invalid/bind_var_errors.err_exp:
	Regression test.


Index: compiler/inst_util.m
===================================================================
RCS file: /home/staff/zs/imp/mercury/compiler/inst_util.m,v
retrieving revision 1.9
diff -u -r1.9 inst_util.m
--- inst_util.m	1998/01/23 12:56:36	1.9
+++ inst_util.m	1998/02/05 05:09:37
@@ -75,6 +75,14 @@
 	% original inst but with all occurrences of `unique' replaced
 	% with `mostly_unique'.
 
+:- pred make_shared_inst_list(list(inst), module_info, list(inst), module_info).
+:- mode make_shared_inst_list(in, in, out, out) is det.
+
+	% Given a list of insts, return a new list of insts which is the
+	% same as the original list of insts, but with all occurrences
+	% of `unique' replaced with `shared'.  It is an error if any part
+	% of the inst list is free.
+
 %-----------------------------------------------------------------------------%
 
 :- pred inst_merge(inst, inst, module_info, inst, module_info).
@@ -970,9 +978,6 @@
 maybe_make_shared_inst_list([_|_], [], _, _, _) :-
 	error("maybe_make_shared_inst_list: length mismatch").
 
-:- pred make_shared_inst_list(list(inst), module_info,
-				list(inst), module_info).
-:- mode make_shared_inst_list(in, in, out, out) is det.
 
 make_shared_inst_list([], ModuleInfo, [], ModuleInfo).
 make_shared_inst_list([Inst0 | Insts0], ModuleInfo0,
Index: compiler/instmap.m
===================================================================
RCS file: /home/staff/zs/imp/mercury/compiler/instmap.m,v
retrieving revision 1.18
diff -u -r1.18 instmap.m
--- instmap.m	1998/01/23 12:56:37	1.18
+++ instmap.m	1998/02/03 12:47:18
@@ -133,6 +133,11 @@
 :- pred instmap__set(instmap, var, inst, instmap).
 :- mode instmap__set(in, in, in, out) is det.
 
+	% Set multiple entries in an instmap.
+	%
+:- pred instmap__set_vars(instmap, list(var), list(inst), instmap).
+:- mode instmap__set_vars(in, in, in, out) is det.
+
 :- pred instmap_delta_set(instmap_delta, var, inst, instmap_delta).
 :- mode instmap_delta_set(in, in, in, out) is det.
 
@@ -376,6 +381,15 @@
 instmap__set(reachable(InstMapping0), Var, Inst,
 		reachable(InstMapping)) :-
 	map__set(InstMapping0, Var, Inst, InstMapping).
+
+instmap__set_vars(InstMap, [], [], InstMap).
+instmap__set_vars(InstMap0, [V | Vs], [I | Is], InstMap) :-
+	instmap__set(InstMap0, V, I, InstMap1),
+	instmap__set_vars(InstMap1, Vs, Is, InstMap).
+instmap__set_vars(_, [_ | _], [], _) :-
+	error("instmap__set_vars").
+instmap__set_vars(_, [], [_ | _], _) :-
+	error("instmap__set_vars").
 
 instmap_delta_set(unreachable, _Var, _Inst, unreachable).
 instmap_delta_set(reachable(InstMapping0), Var, Inst, Instmap) :-
Index: compiler/mode_errors.m
===================================================================
RCS file: /home/staff/zs/imp/mercury/compiler/mode_errors.m,v
retrieving revision 1.54
diff -u -r1.54 mode_errors.m
--- mode_errors.m	1998/01/30 06:12:50	1.54
+++ mode_errors.m	1998/02/09 03:05:53
@@ -68,6 +68,9 @@
 	;	mode_error_bind_var(var_lock_reason, var, inst, inst)
 			% attempt to bind a non-local variable inside
 			% a negated context
+	;	mode_error_non_local_lambda_var(var, inst)
+			% attempt to pass a live non-ground var as a
+			% non-local variable to a lambda goal
 	;	mode_error_unify_var_var(var, var, inst, inst)
 			% attempt to unify two free variables
 	;	mode_error_unify_var_functor(var, cons_id, list(var),
@@ -190,6 +193,8 @@
 	report_mode_error_no_mode_decl(ModeInfo).
 report_mode_error(mode_error_bind_var(Reason, Var, InstA, InstB), ModeInfo) -->
 	report_mode_error_bind_var(ModeInfo, Reason, Var, InstA, InstB).
+report_mode_error(mode_error_non_local_lambda_var(Var, Inst), ModeInfo) -->
+	report_mode_error_non_local_lambda_var(ModeInfo, Var, Inst).
 report_mode_error(mode_error_unify_var_var(VarA, VarB, InstA, InstB),
 		ModeInfo) -->
 	report_mode_error_unify_var_var(ModeInfo, VarA, VarB, InstA, InstB).
@@ -280,12 +285,17 @@
 
 find_important_errors([], [], []).
 find_important_errors([Error | Errors], ImportantErrors, OtherErrors) :-
-	Error = delayed_goal(_, mode_error_info(_, _, _, ModeContext), _),
+	Error = delayed_goal(_, mode_error_info(_, ModeError, _, ModeContext),
+				_),
 	(
 		% an error is important unless it is a non-explicit unification,
 		% i.e. a head unification or a call argument unification
 		ModeContext = unify(unify_context(UnifyContext, _), _),
-		UnifyContext \= explicit
+		UnifyContext \= explicit,
+		% except that errors in lambda goals are important even
+		% if the unification that creates the lambda goal is
+		% an implicit one
+		ModeError \= mode_error_non_local_lambda_var(_, _)
 	->
 		ImportantErrors1 = ImportantErrors,
 		OtherErrors = [Error | OtherErrors1]
@@ -381,9 +391,9 @@
 	( { Reason = negation },
 		io__write_string("attempt to bind a variable inside a negation.\n")
 	; { Reason = if_then_else },
-		io__write_string("attempt to bind a non-local variable inside the\n"),
+		io__write_string("attempt to bind a non-local variable\n"),
 		prog_out__write_context(Context),
-		io__write_string("  condition of an if-then-else.\n")
+		io__write_string("  inside the condition of an if-then-else.\n")
 	; { Reason = lambda(PredOrFunc) },
 		{ hlds_out__pred_or_func_to_str(PredOrFunc, PredOrFuncS) },
 		io__write_string("attempt to bind a non-local variable inside\n"),
@@ -417,6 +427,29 @@
 	;
 		[]
 	).
+
+%-----------------------------------------------------------------------------%
+
+:- pred report_mode_error_non_local_lambda_var(mode_info, var, inst,
+					io__state, io__state).
+:- mode report_mode_error_non_local_lambda_var(mode_info_ui, in, in,
+					di, uo) is det.
+
+report_mode_error_non_local_lambda_var(ModeInfo, Var, VarInst) -->
+	{ mode_info_get_context(ModeInfo, Context) },
+	{ mode_info_get_varset(ModeInfo, VarSet) },
+	{ mode_info_get_instvarset(ModeInfo, InstVarSet) },
+	mode_info_write_context(ModeInfo),
+	prog_out__write_context(Context),
+	io__write_string("  mode error: variable `"),
+	mercury_output_var(Var, VarSet, no),
+	io__write_string("' has instantiatedness `"),
+	output_inst(VarInst, InstVarSet),
+	io__write_string("',\n"),
+	prog_out__write_context(Context),
+	io__write_string("  expected instantiatedness for non-local variables\n"),
+	prog_out__write_context(Context),
+	io__write_string("  of lambda goals is `ground'.\n").
 
 %-----------------------------------------------------------------------------%
 
Index: compiler/modecheck_unify.m
===================================================================
RCS file: /home/staff/zs/imp/mercury/compiler/modecheck_unify.m,v
retrieving revision 1.27
diff -u -r1.27 modecheck_unify.m
--- modecheck_unify.m	1998/01/30 06:12:52	1.27
+++ modecheck_unify.m	1998/02/10 01:36:13
@@ -334,6 +334,8 @@
 	% First modecheck the lambda goal itself:
 	%
 	% initialize the initial insts of the lambda variables,
+	% check that the non-local vars are ground,
+	% mark the non-local vars as shared,
 	% lock the non-local vars,
 	% mark the non-clobbered lambda variables as live,
 	% modecheck the goal,
@@ -352,6 +354,15 @@
 	% initial instmap, variables will be considered as unique
 	% even if they become shared or clobbered in the lambda goal!
 	%
+	% However even this may not be enough.  If a unique non-local
+	% variable is used in its unique inst (e.g. it's used in a ui
+	% mode) and then shared within the lambda body, this is unsound.
+	% This variable should be marked as shared at the _top_ of the
+	% lambda goal.  As for implementing this, it probably means that
+	% the lambda goal should be re-modechecked, or even modechecked
+	% to a fixpoint.  For the moment, we get around this by sharing
+	% all non-local variables at the top of the lambda goal.
+	%
 
 	mode_info_get_module_info(ModeInfo0, ModuleInfo0),
 
@@ -384,35 +395,97 @@
 	Goal0 = _ - GoalInfo0,
 	goal_info_get_nonlocals(GoalInfo0, NonLocals0),
 	set__delete_list(NonLocals0, Vars, NonLocals),
-	mode_info_lock_vars(lambda(PredOrFunc), NonLocals,
-		ModeInfo2, ModeInfo3),
- 
-	mode_checkpoint(enter, "lambda goal", ModeInfo3, ModeInfo4),
-	% if we're being called from unique_modes.m, then we need to 
-	% call unique_modes__check_goal rather than modecheck_goal.
-	( HowToCheckGoal = check_unique_modes ->
-		unique_modes__check_goal(Goal0, Goal, ModeInfo4, ModeInfo5)
+	set__to_sorted_list(NonLocals, NonLocalsList),
+	instmap__lookup_vars(NonLocalsList, InstMap1, NonLocalInsts),
+	mode_info_get_module_info(ModeInfo2, ModuleInfo2),
+	(
+		% XXX This test is too conservative.
+		%
+		%     We should allow non-local variables to be non-ground
+		%     sometimes, possibly dependent on whether or not they
+		%     are dead after this unification.  In addition, we
+		%     should not "share" a unique non-local variable if
+		%     these two conditions hold:
+		%
+		%	- It is dead after this unification.
+		%	- It is not shared within the lambda body.
+		%
+		%     Unfortunately, we can't test the latter condition
+		%     until after we've mode-checked the lambda body.
+		%     (See the above comment on merging the initial and
+		%     final instmaps.)
+
+		inst_list_is_ground(NonLocalInsts, ModuleInfo2)
+	->
+		make_shared_inst_list(NonLocalInsts, ModuleInfo2,
+			SharedNonLocalInsts, ModuleInfo3),
+		instmap__set_vars(InstMap1, NonLocalsList, SharedNonLocalInsts,
+			InstMap2),
+		mode_info_set_module_info(ModeInfo2, ModuleInfo3, ModeInfo3),
+		mode_info_set_instmap(InstMap2, ModeInfo3, ModeInfo4),
+
+		mode_info_lock_vars(lambda(PredOrFunc), NonLocals,
+				ModeInfo4, ModeInfo5),
+
+		mode_checkpoint(enter, "lambda goal", ModeInfo5, ModeInfo6),
+		% if we're being called from unique_modes.m, then we need to 
+		% call unique_modes__check_goal rather than modecheck_goal.
+		(
+			HowToCheckGoal = check_unique_modes
+		->
+			unique_modes__check_goal(Goal0, Goal, ModeInfo6,
+				ModeInfo7)
+		;
+			modecheck_goal(Goal0, Goal, ModeInfo6, ModeInfo7)
+		),
+		mode_list_get_final_insts(Modes, ModuleInfo0, FinalInsts),
+		modecheck_final_insts(Vars, FinalInsts, ModeInfo7, ModeInfo8),
+		mode_checkpoint(exit, "lambda goal", ModeInfo8, ModeInfo9),
+
+		mode_info_remove_live_vars(LiveVars, ModeInfo9, ModeInfo10),
+		mode_info_unlock_vars(lambda(PredOrFunc), NonLocals,
+			ModeInfo10, ModeInfo11),
+
+		%
+		% Ensure that the non-local vars are shared OUTSIDE the
+		% lambda unification as well as inside.
+		%
+
+		instmap__set_vars(InstMap0, NonLocalsList, SharedNonLocalInsts,
+			InstMap11),
+		mode_info_set_instmap(InstMap11, ModeInfo11, ModeInfo12),
+
+		%
+		% Now modecheck the unification of X with the lambda-expression.
+		%
+
+		set__to_sorted_list(NonLocals, ArgVars),
+		modecheck_unify_lambda(X, PredOrFunc, ArgVars, Modes,
+				Det, Unification0, Mode, Unification,
+				ModeInfo12, ModeInfo),
+		RHS = lambda_goal(PredOrFunc, Vars, Modes, Det, Goal)
 	;
-		modecheck_goal(Goal0, Goal, ModeInfo4, ModeInfo5)
-	),
-	mode_list_get_final_insts(Modes, ModuleInfo0, FinalInsts),
-	modecheck_final_insts(Vars, FinalInsts, ModeInfo5, ModeInfo6),
-	mode_checkpoint(exit, "lambda goal", ModeInfo6, ModeInfo7),
- 
-	mode_info_remove_live_vars(LiveVars, ModeInfo7, ModeInfo8),
-	mode_info_unlock_vars(lambda(PredOrFunc), NonLocals,
-		ModeInfo8, ModeInfo9),
-	mode_info_set_instmap(InstMap0, ModeInfo9, ModeInfo10),
- 
-	%
-	% Now modecheck the unification of X with the lambda-expression.
-	%
+		list__filter(lambda([Var :: in] is semidet,
+			( instmap__lookup_var(InstMap1, Var, Inst),
+			  \+ inst_is_ground(ModuleInfo2, Inst)
+			)),
+			NonLocalsList, NonGroundNonLocals),
+		( NonGroundNonLocals = [BadVar | _] ->
+			instmap__lookup_var(InstMap1, BadVar, BadInst),
+			set__singleton_set(WaitingVars, BadVar),
+			mode_info_error(WaitingVars,
+				mode_error_non_local_lambda_var(BadVar,
+						BadInst),
+				ModeInfo2, ModeInfo)
+		;
+			error("modecheck_unification(lambda): very strange var")
+		),
+			% return any old garbage
+		RHS = lambda_goal(PredOrFunc, Vars, Modes0, Det, Goal0),
+		Mode = (free -> free) - (free -> free),
+		Unification = Unification0
+	).
  
-	set__to_sorted_list(NonLocals, ArgVars),
-	modecheck_unify_lambda(X, PredOrFunc, ArgVars, Modes,
-			Det, Unification0, Mode, Unification,
-			ModeInfo10, ModeInfo),
-	RHS = lambda_goal(PredOrFunc, Vars, Modes, Det, Goal).
 
 :- pred modecheck_unify_lambda(var, pred_or_func, list(var),
 			list(mode), determinism, unification,
Index: tests/invalid/bind_var_errors.err_exp
===================================================================
RCS file: /home/staff/zs/imp/tests/invalid/bind_var_errors.err_exp,v
retrieving revision 1.1
diff -u -r1.1 bind_var_errors.err_exp
--- bind_var_errors.err_exp	1998/01/30 06:13:34	1.1
+++ bind_var_errors.err_exp	1998/02/09 03:08:46
@@ -1,19 +1,23 @@
-bind_var_errors.m:031: In clause for `bind_var_in_negation':
-bind_var_errors.m:031:   scope error: attempt to bind a variable inside a negation.
-bind_var_errors.m:031:   Variable `X' has instantiatedness `free',
-bind_var_errors.m:031:   expected instantiatedness was `unique(42)'.
-bind_var_errors.m:037: In clause for `bind_var_in_ite_cond(in)':
-bind_var_errors.m:037:   scope error: attempt to bind a non-local variable inside the
-bind_var_errors.m:037:   condition of an if-then-else.
-bind_var_errors.m:037:   Variable `Y' has instantiatedness `free',
-bind_var_errors.m:037:   expected instantiatedness was `unique(42)'.
-bind_var_errors.m:046: In clause for `bind_var_in_lambda':
-bind_var_errors.m:046:   scope error: attempt to bind a non-local variable inside
-bind_var_errors.m:046:   a pred lambda goal.
-bind_var_errors.m:046:   Variable `Y' has instantiatedness `free',
-bind_var_errors.m:046:   expected instantiatedness was `unique(42)'.
-bind_var_errors.m:056: In clause for `clobber_var_in_lambda(di)':
-bind_var_errors.m:056:   in argument 1 of call to predicate `bind_var_errors:destroy/1':
-bind_var_errors.m:056:   unique-mode error: the called procedure would clobber
-bind_var_errors.m:056:   its argument, but variable `X' is still live.
+bind_var_errors.m:033: In clause for `bind_var_in_negation':
+bind_var_errors.m:033:   scope error: attempt to bind a variable inside a negation.
+bind_var_errors.m:033:   Variable `X' has instantiatedness `free',
+bind_var_errors.m:033:   expected instantiatedness was `unique(42)'.
+bind_var_errors.m:039: In clause for `bind_var_in_ite_cond(in)':
+bind_var_errors.m:039:   scope error: attempt to bind a non-local variable
+bind_var_errors.m:039:   inside the condition of an if-then-else.
+bind_var_errors.m:039:   Variable `Y' has instantiatedness `free',
+bind_var_errors.m:039:   expected instantiatedness was `unique(42)'.
+bind_var_errors.m:048: In clause for `bind_var_in_lambda':
+bind_var_errors.m:048:   in argument 1 of call to predicate `call/1':
+bind_var_errors.m:048:   mode error: variable `Y' has instantiatedness `free',
+bind_var_errors.m:048:   expected instantiatedness for non-local variables
+bind_var_errors.m:048:   of lambda goals is `ground'.
+bind_var_errors.m:053: In clause for `share_var_in_lambda(di)':
+bind_var_errors.m:053:   in argument 1 of call to predicate `bind_var_errors:destroy/1':
+bind_var_errors.m:053:   mode error: variable `X' has instantiatedness `ground',
+bind_var_errors.m:053:   expected instantiatedness was `unique'.
+bind_var_errors.m:060: In clause for `clobber_var_in_lambda(di)':
+bind_var_errors.m:060:   in argument 1 of call to predicate `bind_var_errors:destroy/1':
+bind_var_errors.m:060:   unique-mode error: the called procedure would clobber
+bind_var_errors.m:060:   its argument, but variable `X' is still live.
 For more information, try recompiling with `-E'.
Index: tests/invalid/bind_var_errors.m
===================================================================
RCS file: /home/staff/zs/imp/tests/invalid/bind_var_errors.m,v
retrieving revision 1.1
diff -u -r1.1 bind_var_errors.m
--- bind_var_errors.m	1998/01/30 06:13:35	1.1
+++ bind_var_errors.m	1998/02/05 06:34:45
@@ -9,7 +9,9 @@
 
 :- pred bind_var_in_lambda is semidet.
 
-% :- pred share_var_in_lambda(T :: di) is det.
+:- pred share_var_in_lambda(T :: di) is det.
+
+:- pred share_dead_var_in_lambda(T :: di) is det.
 
 :- pred clobber_var_in_lambda(T :: di) is det.
 
@@ -46,11 +48,13 @@
 	call((pred) is det :- Y = 42),
 	consume(Y).
 
-	% The compiler currently does not pass this test.  It should
-	% report an error but doesn't.
-% share_var_in_lambda(X) :-
-% 	call((pred) is det :- share(X)),
-% 	destroy(X).
+share_var_in_lambda(X) :-
+	call((pred) is det :- share(X)),
+	destroy(X).
+
+	% This one is OK since X is dead after the lambda.
+share_dead_var_in_lambda(X) :-
+	call((pred) is det :- share(X)).
 
 clobber_var_in_lambda(X) :-
 	call((pred) is det :- destroy(X)),




More information about the developers mailing list