[m-rev.] for review: Fix for semidet in det context abort (Mantis bug #586).
Peter Wang
novalazy at gmail.com
Tue Sep 29 12:45:08 AEST 2026
There was a rare interaction between the saved vars const, follow code,
and simplification passes that led to a "semidet model in det context"
abort in the low-level code generator. I could only reproduce the
problem in deep profiling grades with the split-switch-arms pass enabled.
compiler/saved_vars.m:
Do not duplicate a goal to construct a Var (where it is assigned a
constant value) after a switch on the same Var.
tests/valid/Mercury.options:
tests/valid/Mmakefile:
tests/valid/bug586.m:
Add test case.
diff --git a/compiler/saved_vars.m b/compiler/saved_vars.m
index 063bfac995..4f1e0b5e5d 100644
--- a/compiler/saved_vars.m
+++ b/compiler/saved_vars.m
@@ -489,15 +489,36 @@ saved_vars_delay_goal([Goal0 | Goals0], Goals, Construct, Var, IsNonLocal,
IsNonLocal, !SlotInfo),
Goals = [Goal1 | Goals1]
;
- Goal0Expr = switch(SwitchVar, CF, Cases0),
+ Goal0Expr = switch(SwitchVar, CanFail, Cases0),
( if SwitchVar = Var then
- saved_vars_delay_goal(Goals0, Goals1, Construct, Var,
- IsNonLocal, !SlotInfo),
+ (
+ IsNonLocal = yes,
+ % If this is a switch on Var, and Var is a non-local
+ % variable, then insert the goal that constructs Var here,
+ % and do not duplicate the construction at the end of the
+ % conjunction.
+ %
+ % Otherwise, the following sequence of events could occur:
+ % 1. The Construct goal is duplicated after the switch.
+ % 2. The follow_code pass duplicates the Construct goal
+ % into each of the switch arms.
+ % 3. In one or more switch arms, the duplicated goal is
+ % simplified to a fail goal, as it unifies Var with a
+ % functor that cannot occur in that switch arm.
+ % 4. The switch becomes semidet, which leads to an abort in
+ % the code generator if the switch occurs in a det context.
+ Goals1 = Goals0
+ ;
+ IsNonLocal = no,
+ saved_vars_delay_goal(Goals0, Goals1, Construct, Var,
+ IsNonLocal, !SlotInfo)
+ ),
Goals = [Construct, Goal0 | Goals1]
else
push_into_cases_rename(Cases0, Cases, Construct, Var,
!SlotInfo),
- Goal1 = hlds_goal(switch(SwitchVar, CF, Cases), Goal0Info),
+ Goal1 = hlds_goal(switch(SwitchVar, CanFail, Cases),
+ Goal0Info),
saved_vars_delay_goal(Goals0, Goals1, Construct, Var,
IsNonLocal, !SlotInfo),
Goals = [Goal1 | Goals1]
diff --git a/tests/valid/Mercury.options b/tests/valid/Mercury.options
index 146d81b9dd..871778e112 100644
--- a/tests/valid/Mercury.options
+++ b/tests/valid/Mercury.options
@@ -56,6 +56,7 @@ MCFLAGS-bug493 += --loop-invariants
MCFLAGS-bug512 += -O5 --intermodule-optimization
MCFLAGS-bug539 += --optimize-constructor-last-call
MCFLAGS-bug544 += -O5
+MCFLAGS-bug586 += --optimize-saved-vars-const --follow-code
MCFLAGS-compl_unify_bug += -O3
MCFLAGS-constraint_prop_bug += -O0 --common-struct
MCFLAGS-constraint_prop_bug += --local-constraint-propagation
diff --git a/tests/valid/Mmakefile b/tests/valid/Mmakefile
index 00347e9a2e..9783276b28 100644
--- a/tests/valid/Mmakefile
+++ b/tests/valid/Mmakefile
@@ -140,6 +140,7 @@ OTHER_PROGS = \
bug539 \
bug544 \
bug572 \
+ bug586 \
bug85 \
builtin_false \
call_failure \
diff --git a/tests/valid/bug586.m b/tests/valid/bug586.m
new file mode 100644
index 0000000000..97a1216274
--- /dev/null
+++ b/tests/valid/bug586.m
@@ -0,0 +1,83 @@
+%---------------------------------------------------------------------------%
+% vim: ts=4 sw=4 et ft=mercury
+%---------------------------------------------------------------------------%
+%
+% This is a regression test for Mantis bug #586. A rare interaction between
+% the saved vars const, follow code and simplification passes led to an abort
+% in the code generator. The problem has only been observed in deep profiling
+% grades with the split switch arms pass enabled, but otherwise is not
+% related.
+%
+% Uncaught Mercury exception:
+% Software Error: predicate `ll_backend.code_gen.generate_goal'/7:
+% Unexpected: semidet model in det context
+%
+% The problem is this. After the deep profiling transformation,
+% the saved_vars_const pass runs and tries to delay the construction of Val0.
+% When it gets to the switch, Val0 is required, so it is constructed,
+% but because Val0 is a non-local variable, it also duplicated the
+% construction goal at the end of the conjunction.
+%
+% Val0 = auto, % construction goal
+% ( % cannot_fail switch on Val0
+% Val0 = auto,
+% ...
+% ;
+% Val0 = computed(Val)
+% ),
+% ... ,
+% Val0 = auto % duplicated construction goal
+%
+% After the followcode pass moves the unification into the switch arms,
+% simplification determines that the unification "Val0 = auto" cannot succeed
+% in the computed/1 arm, and replaces it with failure, making the entire
+% switch semidet. This leads to the "semidet model in a det context" abort.
+
+:- module bug586.
+:- interface.
+
+:- type parent
+ ---> parent(
+ align :: align
+ ).
+
+:- type unresolved_spec
+ ---> initial
+ ; inherit
+ ; value(spec_align).
+
+:- type spec_align
+ ---> computed(align)
+ ; auto.
+
+:- type align
+ ---> start
+ ; end.
+
+:- func get_computed_align(unresolved_spec, parent) = align.
+
+%--------------------------------------------------------------------%
+
+:- implementation.
+
+get_computed_align(Spec, Parent) = Val :-
+ (
+ Spec = inherit,
+ Val = start
+ ;
+ (
+ Spec = initial,
+ Val0 = auto
+ ;
+ Spec = value(Val0)
+ ),
+ ParentVal = Parent ^ align,
+ (
+ Val0 = auto,
+ Val = ParentVal
+ ;
+ Val0 = computed(Val)
+ )
+ ).
+
+%--------------------------------------------------------------------%
--
2.54.0
More information about the reviews
mailing list