diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java index 385dbd6da8..339a68be83 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java @@ -173,15 +173,6 @@ public ReactionInstance(Reaction definition, ReactorInstance parent, int index) ////////////////////////////////////////////////////// //// Public fields. - /** - * Indicates the chain this reaction is a part of. It is constructed through a bit-wise or among - * all upstream chains. Each fork in the dependency graph setting a new, unused bit to true in - * order to disambiguate it from parallel chains. Note that zero results in no overlap with any - * other reaction, which means the reaction can execute in parallel with any other reaction. The - * default is 1L. If left at the default, parallel execution will be based purely on levels. - */ - public long chainID = 1L; - /** The ports or actions that this reaction may write to. */ public Set> effects = new LinkedHashSet<>(); diff --git a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java index 9dd7548d6c..5ce3ff38fb 100644 --- a/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java +++ b/core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java @@ -24,12 +24,7 @@ package org.lflang.generator; -import java.util.ArrayList; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.NavigableMap; -import java.util.Set; -import java.util.TreeMap; +import java.util.*; import java.util.stream.Collectors; import org.lflang.generator.ReactionInstance.Runtime; import org.lflang.generator.c.CUtil; @@ -70,34 +65,24 @@ public ReactionInstanceGraph(ReactorInstance main) { /** The main reactor instance that this graph is associated with. */ public final ReactorInstance main; - /////////////////////////////////////////////////////////// - //// Public methods - /** * Rebuild this graph by clearing and repeating the traversal that adds all the nodes and edges. + * Note that after this executes, the graph is empty unless it has causality cycles, in which case + * it contains only those causality cycles. */ - public void rebuild() { + private void rebuild() { this.clear(); addNodesAndEdges(main); addEdgesForTpoLevels(main); - - // FIXME: Use {@link TargetProperty#EXPORT_DEPENDENCY_GRAPH}. + assignInferredDeadlines(); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. + // This will destroy the graph, leaving only nodes in cycles. assignLevels(); // Do not throw an exception when nodeCount != 0 so that cycle visualization can proceed. } - /** This function rebuilds the graph and propagates and assigns deadlines to all reactions. */ - public void rebuildAndAssignDeadlines() { - this.clear(); - addNodesAndEdges(main); - // addDependentNetworkEdges(main); - assignInferredDeadlines(); - this.clear(); - } - /* * Get an array of non-negative integers representing the number of reactions * per each level, where levels are indices of the array. @@ -399,40 +384,34 @@ private void assignPortLevel(Runtime current) { /** * This function assigns inferred deadlines to all the reactions in the graph. It is modeled after - * {@code assignLevels} but it starts at the leaf nodes and uses Kahns algorithm to build a - * reverse topologically sorted graph + * {@code assignLevels} but it starts at the leaf nodes, but it does not destroy the graph. */ private void assignInferredDeadlines() { - List start = new ArrayList<>(leafNodes()); + List start = new ArrayList<>(leafNodes()); + Set visited = new HashSet<>(); // 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<>(); + visited.add(origin); Set upstreamAdjacentNodes = getUpstreamAdjacentNodes(origin); - // Visit effect nodes. + // Visit upstream nodes. for (Runtime upstream : upstreamAdjacentNodes) { - // Stage edge between origin and upstream for removal. - toRemove.add(upstream); - + // If the upstream node has been visited, then we have a cycle, which will be + // an error condition. Skip it. + if (visited.contains(upstream)) continue; // 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); + // Determine whether the upstream node is now a leaf node. + var isLeaf = true; + for (Runtime downstream : getDownstreamAdjacentNodes(upstream)) { + if (!visited.contains(downstream)) isLeaf = false; } + if (isLeaf) start.add(upstream); } - - // Remove visited origin. - removeNode(origin); } } diff --git a/core/src/main/java/org/lflang/generator/ReactorInstance.java b/core/src/main/java/org/lflang/generator/ReactorInstance.java index ad8f532a80..78f1312c97 100644 --- a/core/src/main/java/org/lflang/generator/ReactorInstance.java +++ b/core/src/main/java/org/lflang/generator/ReactorInstance.java @@ -207,22 +207,6 @@ 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 definition or null if there is * none. diff --git a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java index 246e4e03eb..87b5c6fcb9 100644 --- a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java @@ -897,7 +897,6 @@ public static void generateReactionAndTriggerStructs( // Set the defaults of the reaction_t struct in the constructor. // Since the self struct is allocated using calloc, there is no need to set: // self->_lf__reaction_"+reactionCount+".index = 0; - // self->_lf__reaction_"+reactionCount+".chain_id = 0; // self->_lf__reaction_"+reactionCount+".pos = 0; // self->_lf__reaction_"+reactionCount+".status = inactive; // self->_lf__reaction_"+reactionCount+".deadline = 0LL; diff --git a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java index 133d06ff02..cf1587ae85 100644 --- a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -240,7 +240,6 @@ private static boolean setReactionPriorities(ReactorInstance reactor, CodeBuilde temp.pr( String.join( "\n", - CUtil.reactionRef(r) + ".chain_id = " + r.chainID + ";", "// index is the OR of level " + level + " and ", "// deadline " + inferredDeadline.toNanoSeconds() + " shifted left 16 bits.", CUtil.reactionRef(r) + ".index = " + reactionIndex + ";")); @@ -249,7 +248,6 @@ private static boolean setReactionPriorities(ReactorInstance reactor, CodeBuilde 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) @@ -266,7 +264,6 @@ private static boolean setReactionPriorities(ReactorInstance reactor, CodeBuilde 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) @@ -283,7 +280,6 @@ private static boolean setReactionPriorities(ReactorInstance reactor, CodeBuilde 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) diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index c7583dcd64..d54dbd6435 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c7583dcd64f0726fea898b118cb00e853251de42 +Subproject commit d54dbd64350dc2bb3e173e296580be1ae2401b2d diff --git a/test/C/src/PeriodicTask.lf b/test/C/src/PeriodicTask.lf new file mode 100644 index 0000000000..83a83226c5 --- /dev/null +++ b/test/C/src/PeriodicTask.lf @@ -0,0 +1,54 @@ +/** + * This test ensures that level 2 reaction with a tighter deadline runs before a level 1 reaction + * with a looser deadline when there is no dependency between these reactions. This is the correct + * behavior of the GEDF scheduler, so the scheduler is set to GEDF_NP. + */ +target C { + timeout: 200 ms, + scheduler: GEDF_NP, + workers: 1 +} + +reactor Periodic(period: time = 50 ms, name: string = "Unnamed") { + timer trigger(0, period) + + output t: time + + reaction(trigger) -> t {= + instant_t start_time = lf_time_physical_elapsed(); + lf_set(t, start_time); + lf_print("%s started at physical time: " PRINTF_TIME, self->name, start_time); + =} +} + +reactor Probe(dl: time = 2 ms, name: string = "Unnamed") { + input i: time + output t: time + + reaction(i) -> t {= + instant_t start_time = lf_time_physical_elapsed(); + lf_set(t, start_time); + lf_print("%s started at physical time: " PRINTF_TIME, self->name, start_time); + =} deadline(dl) {= + instant_t start_time = lf_time_physical_elapsed(); + lf_set(t, start_time); + lf_print("%s VIOLATED DEADLINE at physical time: " PRINTF_TIME, self->name, start_time); + =} +} + +main reactor { + task1 = new Periodic(period = 50 ms, name="task1") + detector1 = new Probe(dl = 50 ms, name="detector1") + + task1.t -> detector1.i + task2 = new Periodic(period = 50 ms, name="task2") + detector2 = new Probe(dl = 25 ms, name="detector2") + + task2.t -> detector2.i + + reaction(task1.t, detector2.t) {= + if (task1.t->is_present && detector2.t->is_present && task1.t->value < detector2.t->value) { + lf_print_error_and_exit("EDF policy violated. detector2 should execute before task1 when both are triggered."); + } + =} +}