Skip to content

Commit 9f3bdfb

Browse files
authored
[Civl] Adjust refinement to handle calls to yield procedures (#1084)
1 parent bd40175 commit 9f3bdfb

File tree

10 files changed

+180
-126
lines changed

10 files changed

+180
-126
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,9 @@ jobs:
6060
# Create packages
6161
dotnet pack --no-build -c ${{ matrix.configuration }} ${SOLUTION}
6262
# Run unit tests
63-
dotnet test --blame-hang-timeout 120s --no-build -c ${{ matrix.configuration }} ${SOLUTION}
63+
dotnet test --blame-hang-timeout 300s --no-build -c ${{ matrix.configuration }} ${SOLUTION}
6464
# Run lit test suite
65-
lit --param ${{ matrix.lit_param }} -v --timeout=120 -D configuration=${{ matrix.configuration }} Test
65+
lit --param ${{ matrix.lit_param }} -v --timeout=300 -D configuration=${{ matrix.configuration }} Test
6666
- name: Deploy to nuget
6767
# Note: if you change the build matrix, update the following
6868
# line to ensure it matches only one element of the matrix.

Source/Concurrency/CivlTypeChecker.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,49 @@ public void TypeCheck()
125125
YieldSufficiencyChecker.TypeCheck(this);
126126
}
127127

128+
public static List<Requires> InlineYieldRequires(YieldProcedureDecl yieldProcedureDecl, int layerNum)
129+
{
130+
var requires = new List<Requires>();
131+
foreach (var callCmd in yieldProcedureDecl.DesugaredYieldRequires)
132+
{
133+
var yieldInvariant = (YieldInvariantDecl)callCmd.Proc;
134+
if (layerNum == yieldInvariant.Layer)
135+
{
136+
Dictionary<Variable, Expr> map = yieldInvariant.InParams.Zip(callCmd.Ins)
137+
.ToDictionary(x => x.Item1, x => x.Item2);
138+
Substitution subst = Substituter.SubstitutionFromDictionary(map);
139+
foreach (Requires req in yieldInvariant.Preserves)
140+
{
141+
requires.Add(new Requires(req.tok, req.Free, Substituter.Apply(subst, req.Condition),
142+
null,
143+
req.Attributes));
144+
}
145+
}
146+
}
147+
return requires;
148+
}
149+
150+
public static List<Ensures> InlineYieldEnsures(YieldProcedureDecl yieldProcedureDecl, int layerNum, bool isFree)
151+
{
152+
var ensures = new List<Ensures>();
153+
foreach (var callCmd in yieldProcedureDecl.DesugaredYieldEnsures)
154+
{
155+
var yieldInvariant = (YieldInvariantDecl)callCmd.Proc;
156+
if (layerNum == yieldInvariant.Layer)
157+
{
158+
Dictionary<Variable, Expr> map = yieldInvariant.InParams.Zip(callCmd.Ins)
159+
.ToDictionary(x => x.Item1, x => x.Item2);
160+
Substitution subst = Substituter.SubstitutionFromDictionary(map);
161+
foreach (Requires req in yieldInvariant.Preserves)
162+
{
163+
ensures.Add(new Ensures(req.tok, req.Free || isFree, Substituter.Apply(subst, req.Condition),
164+
null,
165+
req.Attributes));
166+
}
167+
}
168+
}
169+
return ensures;
170+
}
128171
private HashSet<ActionDecl> TypeCheckActions()
129172
{
130173
var actionDecls = program.Procedures.OfType<ActionDecl>().ToHashSet();

Source/Concurrency/YieldingProcChecker.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public static void AddCheckers(CivlTypeChecker civlTypeChecker, List<Declaration
1717
{
1818
continue;
1919
}
20-
YieldingProcDuplicator duplicator = new YieldingProcDuplicator(civlTypeChecker, layerNum, false);
20+
var duplicator = new YieldingProcDuplicator(civlTypeChecker, layerNum, false);
2121
foreach (var yieldProcedureDecl in civlTypeChecker.program.TopLevelDeclarations.OfType<YieldProcedureDecl>())
2222
{
2323
if (yieldProcedureDecl.Layer >= layerNum)
@@ -51,7 +51,7 @@ public static void AddCheckers(CivlTypeChecker civlTypeChecker, List<Declaration
5151
{
5252
continue;
5353
}
54-
YieldingProcDuplicator duplicator = new YieldingProcDuplicator(civlTypeChecker, layerNum, true);
54+
var duplicator = new YieldingProcDuplicator(civlTypeChecker, layerNum, true);
5555
foreach (var yieldProcedureDecl in civlTypeChecker.program.TopLevelDeclarations.OfType<YieldProcedureDecl>())
5656
{
5757
if (yieldProcedureDecl.Layer == layerNum)

Source/Concurrency/YieldingProcDuplicator.cs

Lines changed: 86 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class YieldingProcDuplicator : Duplicator
1212
* Async calls are also rewritten so that the resulting copies are guaranteed not to
1313
* have any async calls. The copy of yielding procedure X shares local variables of X.
1414
* This sharing is exploited when instrumenting the duplicates in the class
15-
* YieldProcInstrumentation.
15+
* YieldingProcInstrumentation.
1616
*/
1717
private CivlTypeChecker civlTypeChecker;
1818
private int layerNum;
@@ -29,11 +29,11 @@ public YieldingProcDuplicator(CivlTypeChecker civlTypeChecker, int layerNum, boo
2929
{
3030
this.civlTypeChecker = civlTypeChecker;
3131
this.layerNum = layerNum;
32-
this.doRefinementCheck = doRefinementCheck;
33-
this.procToDuplicate = new Dictionary<Procedure, Procedure>();
32+
this.procToDuplicate = [];
3433
this.absyMap = new AbsyMap();
35-
this.asyncCallPreconditionCheckers = new Dictionary<string, Procedure>();
34+
this.asyncCallPreconditionCheckers = [];
3635
this.linearRewriter = new LinearRewriter(civlTypeChecker);
36+
this.doRefinementCheck = doRefinementCheck;
3737
}
3838

3939
#region Procedure duplication
@@ -62,15 +62,15 @@ public override Procedure VisitYieldProcedureDecl(YieldProcedureDecl node)
6262
var proc = new Procedure(
6363
node.tok,
6464
procName,
65-
new List<TypeVariable>(),
65+
[],
6666
VisitVariableSeq(node.InParams),
6767
VisitVariableSeq(node.OutParams),
6868
false,
6969
requires,
70-
new List<Requires>(),
70+
[],
7171
ensures,
7272
(node.HasMoverType && node.Layer == layerNum
73-
? node.ModifiedVars.Select(g => Expr.Ident(g))
73+
? node.ModifiedVars.Select(Expr.Ident)
7474
: civlTypeChecker.GlobalVariables.Select(v => Expr.Ident(v))).ToList()
7575
);
7676
procToDuplicate[node] = proc;
@@ -140,6 +140,8 @@ public override Ensures VisitEnsures(Ensures node)
140140

141141
private Dictionary<CtorType, Variable> returnedPAs;
142142

143+
private Dictionary<string, Variable> addedLocalVariables;
144+
143145
private Variable ReturnedPAs(CtorType pendingAsyncType)
144146
{
145147
if (!returnedPAs.ContainsKey(pendingAsyncType))
@@ -168,11 +170,13 @@ public override Implementation VisitImplementation(Implementation impl)
168170
Debug.Assert(layerNum <= enclosingYieldingProc.Layer);
169171

170172
returnedPAs = new Dictionary<CtorType, Variable>();
173+
addedLocalVariables = new Dictionary<string, Variable>();
171174

172175
Implementation newImpl = base.VisitImplementation(impl);
173176
newImpl.Name = newImpl.Proc.Name;
174177

175178
newImpl.LocVars.AddRange(returnedPAs.Values);
179+
newImpl.LocVars.AddRange(addedLocalVariables.Values);
176180

177181
if (!enclosingYieldingProc.HasMoverType && RefinedAction.HasPendingAsyncs && doRefinementCheck)
178182
{
@@ -259,6 +263,52 @@ public override List<Cmd> VisitCmdSeq(List<Cmd> cmdSeq)
259263
return newCmdSeq;
260264
}
261265

266+
private void AddParallelCall(List<CallCmd> callCmds, Absy absy)
267+
{
268+
var parCallCmd = new ParCallCmd(absy.tok, callCmds);
269+
absyMap[parCallCmd] = absyMap[absy];
270+
newCmdSeq.Add(parCallCmd);
271+
}
272+
273+
private bool NeedsRefinementChecking(CallCmd callCmd)
274+
{
275+
if (callCmd.Proc is not YieldProcedureDecl yieldingProc)
276+
{
277+
return false;
278+
}
279+
if (layerNum != yieldingProc.Layer)
280+
{
281+
return false;
282+
}
283+
var visibleFormalNames = enclosingYieldingProc.VisibleFormals.Select(v =>v.Name);
284+
var visibleOutFormalNames = enclosingYieldingProc.OutParams.Select(v => v.Name).Intersect(visibleFormalNames);
285+
var callOutParamNames = callCmd.Outs.Select(ie => ie.Decl.Name);
286+
var isCallSkippable = yieldingProc.RefinedAction.ActionDecl == civlTypeChecker.SkipActionDecl &&
287+
!visibleOutFormalNames.Intersect(callOutParamNames).Any();
288+
return !isCallSkippable;
289+
}
290+
291+
// Create a duplicate of callCmd and update the outputs of callCmd to fresh local variables.
292+
// The duplicate call, returned by this method, is rewritten to call the refined action.
293+
// The original callCmd with rewritten outputs is used as normal in the parallel call that models
294+
// the yield after the call to the refined action.
295+
// Assumes constraining each output to the corresponding fresh output are added after the parallel call.
296+
private CallCmd PrepareCallCmd(CallCmd callCmd)
297+
{
298+
var copyCallCmd = (CallCmd)VisitCallCmd(callCmd);
299+
var freshOuts = new List<IdentifierExpr>();
300+
copyCallCmd.Outs.ForEach(ie => {
301+
if (!addedLocalVariables.TryGetValue(ie.Decl.Name, out Variable copyVar))
302+
{
303+
copyVar = civlTypeChecker.LocalVariable($"{ie.Decl.Name}_{callCmd.UniqueId}", ie.Type);
304+
addedLocalVariables[ie.Decl.Name] = copyVar;
305+
}
306+
freshOuts.Add(Expr.Ident(copyVar));
307+
});
308+
callCmd.Outs = freshOuts;
309+
return copyCallCmd;
310+
}
311+
262312
private void ProcessCallCmd(CallCmd newCall)
263313
{
264314
if (newCall.Proc.IsPure)
@@ -295,9 +345,7 @@ cmd is AssertCmd assertCmd && makeAssume
295345
{
296346
if (layerNum == yieldInvariant.Layer)
297347
{
298-
var parCallCmd = new ParCallCmd(newCall.tok, new List<CallCmd> {newCall});
299-
absyMap[parCallCmd] = absyMap[newCall];
300-
newCmdSeq.Add(parCallCmd);
348+
AddParallelCall([newCall], newCall);
301349
}
302350
return;
303351
}
@@ -353,15 +401,13 @@ cmd is AssertCmd assertCmd && makeAssume
353401
}
354402
else
355403
{
356-
if (doRefinementCheck && yieldingProc.RefinedAction.ActionDecl != civlTypeChecker.SkipActionDecl)
404+
if (doRefinementCheck && NeedsRefinementChecking(newCall))
357405
{
358-
var parCallCmdBefore = new ParCallCmd(newCall.tok, []);
359-
absyMap[parCallCmdBefore] = absyMap[newCall];
360-
newCmdSeq.Add(parCallCmdBefore);
361-
AddActionCall(newCall, yieldingProc);
362-
var parCallCmdAfter = new ParCallCmd(newCall.tok, []);
363-
absyMap[parCallCmdAfter] = absyMap[newCall];
364-
newCmdSeq.Add(parCallCmdAfter);
406+
AddParallelCall([], newCall);
407+
var copyCall = PrepareCallCmd(newCall);
408+
AddActionCall(copyCall, yieldingProc);
409+
AddDuplicateCall(newCall, true);
410+
newCmdSeq.AddRange(newCall.Outs.Zip(copyCall.Outs).Select(x => CmdHelper.AssumeCmd(Expr.Eq(x.First, x.Second))));
365411
}
366412
else
367413
{
@@ -376,11 +422,14 @@ private void ProcessParCallCmd(ParCallCmd newParCall)
376422
{
377423
var callCmds = new List<CallCmd>();
378424

379-
void AddParallelCall()
425+
void AddParallelCallWrapper()
380426
{
381-
var parCallCmd = new ParCallCmd(newParCall.tok, callCmds);
382-
absyMap[parCallCmd] = absyMap[newParCall];
383-
newCmdSeq.Add(parCallCmd);
427+
callCmds.Where(iter => iter.Proc is YieldProcedureDecl)
428+
.ForEach(iter => {
429+
iter.Proc = procToDuplicate[iter.Proc];
430+
iter.callee = iter.Proc.Name;
431+
});
432+
AddParallelCall(callCmds, newParCall);
384433
}
385434

386435
void ProcessPendingCallCmds()
@@ -389,18 +438,19 @@ void ProcessPendingCallCmds()
389438
{
390439
return;
391440
}
392-
var callCmd = callCmds.Find(cmd =>
393-
cmd.Proc is YieldProcedureDecl yieldingProc &&
394-
layerNum == yieldingProc.Layer &&
395-
yieldingProc.RefinedAction.ActionDecl != civlTypeChecker.SkipActionDecl);
396-
if (doRefinementCheck && callCmd != null)
441+
CallCmd callCmd = doRefinementCheck ? callCmds.Find(NeedsRefinementChecking) : null;
442+
if (callCmd != null)
397443
{
398-
callCmds.Remove(callCmd);
399-
callCmds.ForEach(callCmd => Debug.Assert(callCmd.Proc is YieldInvariantDecl));
400-
AddParallelCall();
401-
AddActionCall(callCmd, (YieldProcedureDecl)callCmd.Proc);
444+
AddParallelCall([], callCmd);
445+
var copyCall = PrepareCallCmd(callCmd);
446+
AddActionCall(copyCall, (YieldProcedureDecl)callCmd.Proc);
447+
AddParallelCallWrapper();
448+
newCmdSeq.AddRange(callCmd.Outs.Zip(copyCall.Outs).Select(x => CmdHelper.AssumeCmd(Expr.Eq(x.First, x.Second))));
449+
}
450+
else
451+
{
452+
AddParallelCallWrapper();
402453
}
403-
AddParallelCall();
404454
callCmds = [];
405455
}
406456

@@ -415,11 +465,6 @@ cmd.Proc is YieldProcedureDecl yieldingProc &&
415465
ProcessCallCmd(callCmd);
416466
continue;
417467
}
418-
}
419-
if (procToDuplicate.ContainsKey(callCmd.Proc))
420-
{
421-
callCmd.Proc = procToDuplicate[callCmd.Proc];
422-
callCmd.callee = callCmd.Proc.Name;
423468
callCmds.Add(callCmd);
424469
}
425470
else
@@ -506,7 +551,7 @@ private void InjectGate(Action action, CallCmd callCmd, bool assume)
506551
return;
507552
}
508553

509-
Dictionary<Variable, Expr> map = new Dictionary<Variable, Expr>();
554+
Dictionary<Variable, Expr> map = [];
510555
for (int i = 0; i < action.Impl.InParams.Count; i++)
511556
{
512557
// Parameters come from the implementation that defines the action
@@ -577,9 +622,7 @@ private void AddDuplicateCall(CallCmd newCall, bool makeParallel)
577622
newCall.callee = newCall.Proc.Name;
578623
if (makeParallel)
579624
{
580-
var parCallCmd = new ParCallCmd(newCall.tok, new List<CallCmd> {newCall});
581-
absyMap[parCallCmd] = absyMap[newCall];
582-
newCmdSeq.Add(parCallCmd);
625+
AddParallelCall([newCall], newCall);
583626
}
584627
else
585628
{
@@ -594,7 +637,7 @@ private void DesugarAsyncCall(CallCmd newCall)
594637
asyncCallPreconditionCheckers[newCall.Proc.Name] = DeclHelper.Procedure(
595638
civlTypeChecker.AddNamePrefix($"AsyncCall_{newCall.Proc.Name}_{layerNum}"),
596639
newCall.Proc.InParams, newCall.Proc.OutParams,
597-
procToDuplicate[newCall.Proc].Requires, new List<Requires>(), new List<Ensures>(), new List<IdentifierExpr>());
640+
procToDuplicate[newCall.Proc].Requires, [], [], []);
598641
}
599642

600643
var asyncCallPreconditionChecker = asyncCallPreconditionCheckers[newCall.Proc.Name];
@@ -661,7 +704,7 @@ public class AbsyMap
661704

662705
public AbsyMap()
663706
{
664-
this.absyMap = new Dictionary<Absy, Absy>();
707+
this.absyMap = [];
665708
}
666709

667710
public Absy this[Absy absy]

Source/Concurrency/YieldingProcInstrumentation.cs

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -342,39 +342,8 @@ private void InlineYieldRequiresAndEnsures()
342342
foreach (var impl in absyMap.Keys.OfType<Implementation>())
343343
{
344344
var yieldingProc = GetYieldingProc(impl);
345-
foreach (var callCmd in yieldingProc.DesugaredYieldRequires)
346-
{
347-
var yieldInvariant = (YieldInvariantDecl)callCmd.Proc;
348-
if (layerNum == yieldInvariant.Layer)
349-
{
350-
Dictionary<Variable, Expr> map = yieldInvariant.InParams.Zip(callCmd.Ins)
351-
.ToDictionary(x => x.Item1, x => x.Item2);
352-
Substitution subst = Substituter.SubstitutionFromDictionary(map);
353-
foreach (Requires req in yieldInvariant.Preserves)
354-
{
355-
impl.Proc.Requires.Add(new Requires(req.tok, req.Free, Substituter.Apply(subst, req.Condition),
356-
null,
357-
req.Attributes));
358-
}
359-
}
360-
}
361-
362-
foreach (var callCmd in yieldingProc.DesugaredYieldEnsures)
363-
{
364-
var yieldInvariant = (YieldInvariantDecl)callCmd.Proc;
365-
if (layerNum == yieldInvariant.Layer)
366-
{
367-
Dictionary<Variable, Expr> map = yieldInvariant.InParams.Zip(callCmd.Ins)
368-
.ToDictionary(x => x.Item1, x => x.Item2);
369-
Substitution subst = Substituter.SubstitutionFromDictionary(map);
370-
foreach (Requires req in yieldInvariant.Preserves)
371-
{
372-
impl.Proc.Ensures.Add(new Ensures(req.tok, req.Free || doRefinementCheck, Substituter.Apply(subst, req.Condition),
373-
null,
374-
req.Attributes));
375-
}
376-
}
377-
}
345+
impl.Proc.Requires.AddRange(CivlTypeChecker.InlineYieldRequires(yieldingProc, layerNum));
346+
impl.Proc.Ensures.AddRange(CivlTypeChecker.InlineYieldEnsures(yieldingProc, layerNum, doRefinementCheck));
378347
}
379348
}
380349

0 commit comments

Comments
 (0)