diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java index f128cee7b4..326f98f8a6 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.java @@ -446,7 +446,7 @@ public KPolygon addReactionFigure(KNode node, ReactionInstance reaction) { if (hasDeadlineCode) { KText contentContainerText = _kContainerRenderingExtensions.addText(contentContainer, _utilityExtensions.trimCode(reaction.getDefinition().getDeadline().getCode())); - associateWith(contentContainerText, reaction.deadline); + associateWith(contentContainerText, reaction.declaredDeadline); _kRenderingExtensions.setForeground(contentContainerText, Colors.BROWN); _kRenderingExtensions.setFontSize(contentContainerText, 6); _kRenderingExtensions.setFontName(contentContainerText, KlighdConstants.DEFAULT_MONOSPACE_FONT_NAME); diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index debfcd8334..b3a9607bae 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -198,6 +198,12 @@ public abstract class GeneratorBase extends AbstractLFValidator { */ public boolean hasModalReactors = false; + /** + * Indicates whether the program has any deadlines and thus + * needs to propagate deadlines through the reaction instance graph + */ + public boolean hasDeadlines = false; + // ////////////////////////////////////////// // // Target properties, if they are included. /** diff --git a/org.lflang/src/org/lflang/generator/GeneratorUtils.java b/org.lflang/src/org/lflang/generator/GeneratorUtils.java index d0f6978010..be4e122c5e 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/GeneratorUtils.java @@ -39,6 +39,7 @@ import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.lf.Model; +import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.TargetDecl; import org.lflang.util.FileUtil; diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index f20962d70d..cfcfe7f663 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -223,11 +223,6 @@ public ReactionInstance( */ public DeadlineInstance declaredDeadline; - /** - * Inferred deadline. Defaults to the maximum long value. - */ - public TimeValue deadline = new TimeValue(TimeValue.MAX_LONG_DEADLINE, TimeUnit.NANO); - /** * Sadly, we have no way to mark reaction "unordered" in the AST, * so instead, we use a magic comment at the start of the reaction body. @@ -356,12 +351,27 @@ public Set dependsOnReactions() { public Set getLevels() { Set result = new LinkedHashSet<>(); // Force calculation of levels if it has not been done. - parent.assignLevels(); + // FIXME: Comment out this as I think it is redundant. + // If it is NOT redundant then deadline propagation is not correct + // parent.assignLevels(); for (Runtime runtime : runtimeInstances) { result.add(runtime.level); } return result; } + + /** + * Return a set of deadlines that runtime instances of this reaction have. + * A ReactionInstance may have more than one deadline if it lies within. + */ + public Set getInferredDeadlines() { + Set result = new LinkedHashSet<>(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.deadline); + } + return result; + } + /** * Return a list of levels that runtime instances of this reaction have. @@ -372,12 +382,28 @@ public Set getLevels() { public List getLevelsList() { List result = new LinkedList<>(); // Force calculation of levels if it has not been done. - parent.assignLevels(); + // FIXME: Comment out this as I think it is redundant. + // If it is NOT redundant then deadline propagation is not correct + // parent.assignLevels(); for (Runtime runtime : runtimeInstances) { result.add(runtime.level); } return result; } + + /** + * Return a list of the deadlines that runtime instances of this reaction have. + * The size of this list is the total number of runtime instances. + * A ReactionInstance may have more than one deadline if it lies within + */ + public List getInferredDeadlinesList() { + List result = new LinkedList<>(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.deadline); + } + return result; + } + /** * Return the name of this reaction, which is 'reaction_n', @@ -530,11 +556,11 @@ public TimeValue assignLogicalExecutionTime() { /** Inner class representing a runtime instance. */ public class Runtime { - public TimeValue deadline = TimeValue.MAX_VALUE; - public Runtime dominating = null; + public TimeValue deadline; + public Runtime dominating; /** ID ranging from 0 to parent.getTotalWidth() - 1. */ - public int id = 0; - public int level = 0; + public int id; + public int level; public ReactionInstance getReaction() { return ReactionInstance.this; @@ -551,5 +577,16 @@ public String toString() { result += ")"; return result; } + + public Runtime() { + this.dominating = null; + this.id = 0; + this.level = 0; + if (ReactionInstance.this.declaredDeadline != null) { + this.deadline = ReactionInstance.this.declaredDeadline.maxDelay; + } else { + this.deadline = TimeValue.MAX_VALUE; + } + } } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 72c20bb63a..fbefe1459d 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -93,6 +93,16 @@ public void rebuild() { // throw new InvalidSourceException("Reactions form a cycle!"); } } + /** + * This function rebuilds the graph and propagates and assigns deadlines + * to all reactions. + */ + public void rebuildAndAssignDeadlines() { + this.clear(); + addNodesAndEdges(main); + assignInferredDeadlines(); + this.clear(); + } /* * Get an array of non-negative integers representing the number of reactions @@ -298,7 +308,46 @@ private void assignLevels() { adjustNumReactionsPerLevel(origin.level, 1); } } - + + /** + * This function assigns inferred deadlines to all the reactions in the graph. + * It is modeled after `assignLevels` but it starts at the leaf nodes and uses + * Kahns algorithm to build a reverse topologically sorted graph + * + */ + private void assignInferredDeadlines() { + List start = new ArrayList<>(leafNodes()); + + // All leaf nodes have deadline initialized to their declared deadline or MAX_VALUE + while (!start.isEmpty()) { + Runtime origin = start.remove(0); + Set toRemove = new LinkedHashSet<>(); + Set upstreamAdjacentNodes = getUpstreamAdjacentNodes(origin); + + // Visit effect nodes. + for (Runtime upstream : upstreamAdjacentNodes) { + // Stage edge between origin and upstream for removal. + toRemove.add(upstream); + + // Update deadline of upstream node if origins deadline is earlier. + if (origin.deadline.isEarlierThan(upstream.deadline)) { + upstream.deadline = origin.deadline; + } + } + // Remove visited edges. + for (Runtime upstream : toRemove) { + removeEdge(origin, upstream); + // If the upstream node has no more outgoing edges, + // then move it in the start set. + if (getDownstreamAdjacentNodes(upstream).size() == 0) { + start.add(upstream); + } + } + + // Remove visited origin. + removeNode(origin); + } + } /** * Adjust {@link #numReactionsPerLevel} at index level by diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index ae953e9370..782e16c2b3 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -189,6 +189,22 @@ public ReactionInstanceGraph assignLevels() { } return cachedReactionLoopGraph; } + + /** + * This function assigns/propagates deadlines through the Reaction Instance Graph. + * It performs Kahn`s algorithm in reverse, starting from the leaf nodes and + * propagates deadlines upstream. To reduce cost, it should only be invoked when + * there are user-specified deadlines in the program. + * @return + */ + public ReactionInstanceGraph assignDeadlines() { + if (depth != 0) return root().assignDeadlines(); + if (cachedReactionLoopGraph == null) { + cachedReactionLoopGraph = new ReactionInstanceGraph(this); + } + cachedReactionLoopGraph.rebuildAndAssignDeadlines(); + return cachedReactionLoopGraph; + } /** * Return the instance of a child rector created by the specified diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index ec2b7986c3..ce2dd282de 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -2571,6 +2571,9 @@ private void createMainReactorInstance() { errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); return; } + if (hasDeadlines) { + this.main.assignDeadlines(); + } // Inform the run-time of the breadth/parallelism of the reaction graph var breadth = reactionInstanceGraph.getBreadth(); if (breadth == 0) { diff --git a/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java b/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java index 44f059d64c..d28df9a249 100644 --- a/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -17,6 +17,7 @@ import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.TargetConfig; +import org.lflang.TimeValue; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TargetProperty.LogLevel; import org.lflang.federated.CGeneratorExtension; @@ -342,20 +343,41 @@ private static boolean setReactionPriorities( ) { var foundOne = false; // Force calculation of levels if it has not been done. - reactor.assignLevels(); + // FIXME: Comment out this as I think it is redundant. + // If it is NOT redundant then deadline propagation is not correct + // reactor.assignLevels(); + + // We handle four different scenarios + // 1) A reactionInstance has 1 level and 1 deadline + // 2) A reactionInstance has 1 level but multiple deadlines + // 3) A reaction instance has multiple levels but all have the same deadline + // 4) Multiple deadlines and levels - // If any reaction has multiple levels, then we need to create - // an array with the levels here, before entering the iteration over banks. var prolog = new CodeBuilder(); var epilog = new CodeBuilder(); for (ReactionInstance r : reactor.reactions) { if (currentFederate.contains(r.getDefinition())) { - var levels = r.getLevels(); - if (levels.size() != 1) { + var levelSet = r.getLevels(); + var deadlineSet = r.getInferredDeadlines(); + + if (levelSet.size() > 1 || deadlineSet.size() > 1) { + // Scenario 2-4 if (prolog.length() == 0) { prolog.startScopedBlock(); epilog.endScopedBlock(); } + } + if (deadlineSet.size() > 1) { + // Scenario (2) or (4) + var deadlines = r.getInferredDeadlinesList().stream() + .map(elt -> ("0x" + Long.toString(elt.toNanoSeconds(), 16) + "LL")) + .collect(Collectors.toList()); + + prolog.pr("interval_t "+r.uniqueID()+"_inferred_deadlines[] = { "+joinObjects(deadlines, ", ")+" };"); + } + + if (levelSet.size() > 1) { + // Scenario (3) or (4) // Cannot use the above set of levels because it is a set, not a list. prolog.pr("int "+r.uniqueID()+"_levels[] = { "+joinObjects(r.getLevelsList(), ", ")+" };"); } @@ -368,33 +390,57 @@ private static boolean setReactionPriorities( for (ReactionInstance r : reactor.reactions) { if (currentFederate.contains(r.getDefinition())) { foundOne = true; - // The most common case is that all runtime instances of the - // reaction have the same level, so deal with that case - // specially. - var levels = r.getLevels(); - if (levels.size() == 1) { - var level = -1; - for (Integer l : levels) { - level = l; - } + var levelSet = r.getLevels(); + var deadlineSet = r.getInferredDeadlines(); + + // Get the head of the associated lists. To avoid duplication in + // several of the following cases + var level = r.getLevelsList().get(0); + var inferredDeadline = r.getInferredDeadlinesList().get(0); + var runtimeIdx =CUtil.runtimeIndex(r.getParent()); + + if (levelSet.size() == 1 && deadlineSet.size() == 1) { + // Scenario (1) + // xtend doesn't support bitwise operators... - var indexValue = r.deadline.toNanoSeconds() << 16 | level; - var reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL"; + var indexValue = inferredDeadline.toNanoSeconds() << 16 | level; + + var reactionIndex = "0x" + Long.toUnsignedString(indexValue, 16) + "LL"; temp.pr(String.join("\n", CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", "// index is the OR of level "+level+" and ", - "// deadline "+r.deadline.toNanoSeconds()+" shifted left 16 bits.", + "// deadline "+ inferredDeadline.toNanoSeconds()+" shifted left 16 bits.", CUtil.reactionRef(r)+".index = "+reactionIndex+";" )); - } else { - var reactionDeadline = "0x" + Long.toString(r.deadline.toNanoSeconds(), 16) + "LL"; + } else if (levelSet.size() == 1 && deadlineSet.size() > 1) { + // Scenario 2 + temp.pr(String.join("\n", + CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", + "// index is the OR of levels["+runtimeIdx+"] and ", + "// deadlines["+runtimeIdx+"] shifted left 16 bits.", + CUtil.reactionRef(r)+".index = ("+r.uniqueID()+"_inferred_deadlines["+runtimeIdx+"] << 16) | " + + level+";" + )); + + } else if (levelSet.size() > 1 && deadlineSet.size() == 1) { + // Scenarion (3) + temp.pr(String.join("\n", + CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", + "// index is the OR of levels["+runtimeIdx+"] and ", + "// deadlines["+runtimeIdx+"] shifted left 16 bits.", + CUtil.reactionRef(r)+".index = ("+inferredDeadline.toNanoSeconds()+" << 16) | " + + r.uniqueID()+"_levels["+runtimeIdx+"];" + )); + } else if (levelSet.size() > 1 && deadlineSet.size() > 1) { + // Scenario (4) temp.pr(String.join("\n", CUtil.reactionRef(r)+".chain_id = "+r.chainID+";", - "// index is the OR of levels["+CUtil.runtimeIndex(r.getParent())+"] and ", - "// deadline "+r.deadline.toNanoSeconds()+" shifted left 16 bits.", - CUtil.reactionRef(r)+".index = ("+reactionDeadline+" << 16) | "+r.uniqueID()+"_levels["+CUtil.runtimeIndex(r.getParent())+"];" + "// index is the OR of levels["+runtimeIdx+"] and ", + "// deadlines["+runtimeIdx+"] shifted left 16 bits.", + CUtil.reactionRef(r)+".index = ("+r.uniqueID()+"_inferred_deadlines["+runtimeIdx+"] << 16) | " + + r.uniqueID()+"_levels["+runtimeIdx+"];" )); } } diff --git a/test/C/src/DeadlineInherited.lf b/test/C/src/DeadlineInherited.lf new file mode 100644 index 0000000000..eab2f56d9b --- /dev/null +++ b/test/C/src/DeadlineInherited.lf @@ -0,0 +1,31 @@ +// Test to verify that deadline priority are inherited +target C { + timeout: 1 sec, + threading: false +} + +preamble {= int global_cnt = 0; =} + +reactor NoDeadline { + timer t(0 msec, 100 msec) + + reaction(t) {= global_cnt++; =} +} + +reactor WithDeadline { + timer t(0 msec, 100 msec) + + reaction(t) {= =} + + reaction(t) {= + if (global_cnt != 0) { + lf_print_error_and_exit("Deadline reaction was not executed first. cnt=%i", global_cnt); + } + global_cnt--; + =} deadline(100 sec) {= =} +} + +main reactor { + a = new NoDeadline() + b = new WithDeadline() +} diff --git a/test/C/src/DeadlinePriority.lf b/test/C/src/DeadlinePriority.lf new file mode 100644 index 0000000000..723a29b754 --- /dev/null +++ b/test/C/src/DeadlinePriority.lf @@ -0,0 +1,29 @@ +// Test to verify that deadline gives priority +target C { + timeout: 1 sec, + threading: false +} + +preamble {= int global_cnt = 0; =} + +reactor NoDeadline { + timer t(0 msec, 100 msec) + + reaction(t) {= global_cnt++; =} +} + +reactor WithDeadline { + timer t(0 msec, 100 msec) + + reaction(t) {= + if (global_cnt != 0) { + lf_print_error_and_exit("Deadline reaction was not executed first. cnt=%i", global_cnt); + } + global_cnt--; + =} deadline(100 sec) {= =} +} + +main reactor { + a = new NoDeadline() + b = new WithDeadline() +} diff --git a/test/C/src/DeadlineWithAfterDelay.lf b/test/C/src/DeadlineWithAfterDelay.lf new file mode 100644 index 0000000000..d1b87860ac --- /dev/null +++ b/test/C/src/DeadlineWithAfterDelay.lf @@ -0,0 +1,34 @@ +// Test to verify that deadline priority are inherited when using after delays +target C { + timeout: 1 sec, + threading: false +} + +preamble {= int global_cnt = 0; =} + +reactor Source { + output out: int + timer t(0 msec, 100 msec) + + reaction(t) -> out {= + lf_set(out, 1); + global_cnt++; + =} +} + +reactor SinkWithDeadline { + input in: int + + reaction(in) {= + global_cnt--; + if (global_cnt != 0) { + lf_print_error_and_exit("Deadline reaction was not executed first. cnt=%i", global_cnt); + } + =} deadline(100 sec) {= =} +} + +main reactor { + a = new Source() + b = new SinkWithDeadline() + a.out -> b.in after 100 msec // a.out -> b.in +} diff --git a/test/C/src/DeadlineWithBanks.lf b/test/C/src/DeadlineWithBanks.lf new file mode 100644 index 0000000000..266208cdac --- /dev/null +++ b/test/C/src/DeadlineWithBanks.lf @@ -0,0 +1,63 @@ +/** + * This program verifies that reactions inside banks inherit the correct + * priority from their downstream reactions. A global variable is used to check + * execution order. Threading is disabled + */ +target C { + threading: false, + timeout: 300 msec, + build-type: Debug +} + +preamble {= volatile int global_cnt = 0; =} + +reactor Bank(bank_index: int(0)) { + timer t(0, 100 msec) + output out: int + + reaction(t) -> out {= + int exp_cnt; + switch(self->bank_index) { + case 0: { + exp_cnt = 3; + break; + } + case 1: { + exp_cnt = 1; + break; + } + case 2: { + exp_cnt = 0; + break; + } + case 3: { + exp_cnt = 2; + break; + } + } + if (global_cnt != exp_cnt) { + lf_print_error_and_exit("global_cnt=%i expected=%i\n", global_cnt, exp_cnt); + } + global_cnt++; + + if (self->bank_index==0) { + global_cnt=0; + } + =} +} + +reactor Sink(dead: time(0)) { + input in: int + + reaction(in) {= =} deadline(dead) {= =} +} + +main reactor { + rp = new[4] Bank() + s1 = new Sink(dead = 14 msec) + s2 = new Sink(dead = 12 msec) + s3 = new Sink(dead = 11 msec) + s4 = new Sink(dead = 13 msec) + + rp.out -> s1.in, s2.in, s3.in, s4.in +}