Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix deadline inference, test GEDF, and remove chain ID #2294

Merged
merged 13 commits into from
May 30, 2024
9 changes: 0 additions & 9 deletions core/src/main/java/org/lflang/generator/ReactionInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<TriggerInstance<? extends Variable>> effects = new LinkedHashSet<>();

Expand Down
59 changes: 19 additions & 40 deletions core/src/main/java/org/lflang/generator/ReactionInstanceGraph.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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<ReactionInstance.Runtime> start = new ArrayList<>(leafNodes());
List<Runtime> start = new ArrayList<>(leafNodes());
Set<Runtime> 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<Runtime> toRemove = new LinkedHashSet<>();
visited.add(origin);
Set<Runtime> 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);
}
}

Expand Down
16 changes: 0 additions & 16 deletions core/src/main/java/org/lflang/generator/ReactorInstance.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 + ";"));
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand Down
54 changes: 54 additions & 0 deletions test/C/src/PeriodicTask.lf
Original file line number Diff line number Diff line change
@@ -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.");
}
=}
}
Loading