Skip to content

Commit

Permalink
Merge pull request #2294 from lf-lang/gedf
Browse files Browse the repository at this point in the history
Fix deadline inference, test GEDF, and remove chain ID
  • Loading branch information
edwardalee authored May 30, 2024
2 parents 9e909a8 + 24a6b20 commit 030f93b
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 71 deletions.
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.");
}
=}
}

0 comments on commit 030f93b

Please sign in to comment.