diff --git a/core/src/main/java/org/lflang/AttributeUtils.java b/core/src/main/java/org/lflang/AttributeUtils.java
index 79447cb440..83fab9666f 100644
--- a/core/src/main/java/org/lflang/AttributeUtils.java
+++ b/core/src/main/java/org/lflang/AttributeUtils.java
@@ -142,6 +142,33 @@ public static String getAttributeValue(EObject node, String attrName) {
return value;
}
+ /**
+ * Return the first argument, which has the type Time, specified for the attribute.
+ *
+ *
This should be used if the attribute is expected to have a single argument whose type is
+ * Time. If there is no argument, null is returned.
+ */
+ public static Time getFirstArgumentTime(Attribute attr) {
+ if (attr == null || attr.getAttrParms().isEmpty()) {
+ return null;
+ }
+ return attr.getAttrParms().get(0).getTime();
+ }
+
+ /**
+ * Search for an attribute with the given name on the given AST node and return its first argument
+ * as Time.
+ *
+ *
This should only be used on attributes that are expected to have a single argument with type
+ * Time.
+ *
+ *
Returns null if the attribute is not found or if it does not have any arguments.
+ */
+ public static Time getAttributeTime(EObject node, String attrName) {
+ final var attr = findAttributeByName(node, attrName);
+ return getFirstArgumentTime(attr);
+ }
+
/**
* Search for an attribute with the given name on the given AST node and return its first argument
* as a String.
@@ -241,6 +268,15 @@ public static boolean hasCBody(Reaction reaction) {
return findAttributeByName(reaction, "_c_body") != null;
}
+ /** Return a time value that represents the WCET of a reaction. */
+ public static TimeValue getWCET(Reaction reaction) {
+ Time wcet = getAttributeTime(reaction, "wcet");
+ if (wcet == null) return TimeValue.MAX_VALUE;
+ int value = wcet.getInterval();
+ TimeUnit unit = TimeUnit.fromName(wcet.getUnit());
+ return new TimeValue(value, unit);
+ }
+
/** Return the declared label of the node, as given by the @label annotation. */
public static String getLabel(EObject node) {
return getAttributeValue(node, "label");
diff --git a/core/src/main/java/org/lflang/LinguaFranca.xtext b/core/src/main/java/org/lflang/LinguaFranca.xtext
index 5b1bda9028..7f5678100f 100644
--- a/core/src/main/java/org/lflang/LinguaFranca.xtext
+++ b/core/src/main/java/org/lflang/LinguaFranca.xtext
@@ -257,7 +257,7 @@ Attribute:
;
AttrParm:
- (name=ID '=')? value=Literal;
+ (name=ID '=')? (value=Literal | time=Time);
/////////// For target parameters
diff --git a/core/src/main/java/org/lflang/TimeTag.java b/core/src/main/java/org/lflang/TimeTag.java
new file mode 100644
index 0000000000..086adf7905
--- /dev/null
+++ b/core/src/main/java/org/lflang/TimeTag.java
@@ -0,0 +1,58 @@
+package org.lflang;
+
+/**
+ * Class representing a logical time tag, which is a pair that consists of a timestamp and a
+ * microstep.
+ */
+public class TimeTag implements Comparable {
+
+ public static final TimeTag ZERO = new TimeTag(TimeValue.ZERO, 0L);
+ public static final TimeTag FOREVER = new TimeTag(TimeValue.MAX_VALUE, Long.MAX_VALUE);
+
+ public final TimeValue time;
+ public final Long microstep;
+
+ /** Constructor */
+ public TimeTag(TimeValue time, Long microstep) {
+ this.time = time;
+ this.microstep = microstep;
+ }
+
+ /** Copy constructor */
+ public TimeTag(TimeTag that) {
+ this.time = that.time;
+ this.microstep = that.microstep;
+ }
+
+ /**
+ * Whether this time tag represents FOREVER, which is interpreted as the maximum TimeValue.
+ *
+ * @return True if the tag is FOREVER, false otherwise.
+ */
+ public boolean isForever() {
+ return time.equals(TimeValue.MAX_VALUE);
+ }
+
+ /**
+ * When comparing two time tags, first compare their time fields. If they are equal, compare their
+ * microsteps.
+ */
+ @Override
+ public int compareTo(TimeTag t) {
+ if (this.time.compareTo(t.time) < 0) return -1;
+ else if (this.time.compareTo(t.time) > 0) return 1;
+ else return this.microstep.compareTo(t.microstep);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o instanceof TimeTag t && this.compareTo(t) == 0) return true;
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "( " + this.time + ", " + this.microstep + " )";
+ }
+}
diff --git a/core/src/main/java/org/lflang/analyses/statespace/Event.java b/core/src/main/java/org/lflang/analyses/statespace/Event.java
index 7f8b54b20b..9774686b5a 100644
--- a/core/src/main/java/org/lflang/analyses/statespace/Event.java
+++ b/core/src/main/java/org/lflang/analyses/statespace/Event.java
@@ -1,14 +1,15 @@
package org.lflang.analyses.statespace;
+import org.lflang.TimeTag;
import org.lflang.generator.TriggerInstance;
-/** A node in the state space diagram representing a step in the execution of an LF program. */
+/** A class representing a tagged signal, for analytical purposes */
public class Event implements Comparable {
private final TriggerInstance> trigger;
- private Tag tag;
+ private TimeTag tag;
- public Event(TriggerInstance trigger, Tag tag) {
+ public Event(TriggerInstance trigger, TimeTag tag) {
this.trigger = trigger;
this.tag = tag;
}
@@ -41,7 +42,7 @@ public String toString() {
return "(" + trigger.getFullName() + ", " + tag + ")";
}
- public Tag getTag() {
+ public TimeTag getTag() {
return tag;
}
diff --git a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java
index 7c04206da7..f4530e6134 100644
--- a/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java
+++ b/core/src/main/java/org/lflang/analyses/statespace/EventQueue.java
@@ -2,10 +2,7 @@
import java.util.PriorityQueue;
-/**
- * An event queue implementation that sorts events in the order of _time tags_ and _trigger names_
- * based on the implementation of compareTo() in the Event class.
- */
+/** An event queue for analyzing the logical behavior of an LF program */
public class EventQueue extends PriorityQueue {
/**
diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java b/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java
index 89c3bd19de..ef906a3c60 100644
--- a/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java
+++ b/core/src/main/java/org/lflang/analyses/statespace/StateInfo.java
@@ -2,23 +2,29 @@
import java.util.ArrayList;
import java.util.HashMap;
+import org.lflang.TimeTag;
/** A class that represents information in a step in a counterexample trace */
public class StateInfo {
public ArrayList reactions = new ArrayList<>();
- public Tag tag;
+ public TimeTag tag;
public HashMap variables = new HashMap<>();
public HashMap triggers = new HashMap<>();
public HashMap scheduled = new HashMap<>();
public HashMap payloads = new HashMap<>();
public void display() {
- System.out.println("reactions: " + reactions);
- System.out.println("tag: " + tag);
- System.out.println("variables: " + variables);
- System.out.println("triggers: " + triggers);
- System.out.println("scheduled: " + scheduled);
- System.out.println("payloads: " + payloads);
+ System.out.println(
+ String.join(
+ "\n",
+ "/**************************/",
+ "reactions: " + reactions,
+ "tag: " + tag,
+ "variables: " + variables,
+ "triggers: " + triggers,
+ "scheduled: " + scheduled,
+ "payloads: " + payloads,
+ "/**************************/"));
}
}
diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java
index e2aa66737d..398bd1ed5c 100644
--- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java
+++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceDiagram.java
@@ -1,5 +1,7 @@
package org.lflang.analyses.statespace;
+import java.io.IOException;
+import java.nio.file.Path;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
@@ -7,8 +9,13 @@
import org.lflang.generator.CodeBuilder;
import org.lflang.generator.ReactionInstance;
import org.lflang.graph.DirectedGraph;
+import org.lflang.pretvm.ExecutionPhase;
-/** A directed graph representing the state space of an LF program. */
+/**
+ * A directed graph representing the state space of an LF program.
+ *
+ * @author Shaokai J. Lin
+ */
public class StateSpaceDiagram extends DirectedGraph {
/** The first node of the state space diagram. */
@@ -29,13 +36,19 @@ public class StateSpaceDiagram extends DirectedGraph {
*/
public StateSpaceNode loopNodeNext;
- /** The logical time elapsed for each loop iteration. */
- public long loopPeriod;
+ /**
+ * The logical time elapsed for each loop iteration. With the assumption of "logical time =
+ * physical time," this is also the hyperperiod in physical time.
+ */
+ public long hyperperiod;
+
+ /** The exploration phase in which this diagram is generated */
+ public ExecutionPhase phase;
/** A dot file that represents the diagram */
private CodeBuilder dot;
- /** */
+ /** A flag that indicates whether we want the dot to be compact */
private final boolean compactDot = false;
/** Before adding the node, assign it an index. */
@@ -56,7 +69,7 @@ public StateSpaceNode getDownstreamNode(StateSpaceNode node) {
public void display() {
System.out.println("*************************************************");
System.out.println("* Pretty printing worst-case state space diagram:");
- long timestamp;
+ TimeValue time;
StateSpaceNode node = this.head;
if (node == null) {
System.out.println("* EMPTY");
@@ -68,14 +81,15 @@ public void display() {
node.display();
// Store the tag of the prior step.
- timestamp = node.getTag().timestamp;
+ time = node.getTag().time;
// Assume a unique next state.
node = getDownstreamNode(node);
// Compute time difference
if (node != null) {
- TimeValue tsDiff = TimeValue.fromNanoSeconds(node.getTag().timestamp - timestamp);
+ TimeValue tsDiff =
+ TimeValue.fromNanoSeconds(node.getTag().time.toNanoSeconds() - time.toNanoSeconds());
System.out.println("* => Advance time by " + tsDiff);
}
}
@@ -87,7 +101,8 @@ public void display() {
if (this.loopNode != null) {
// Compute time difference
TimeValue tsDiff =
- TimeValue.fromNanoSeconds(loopNodeNext.getTag().timestamp - tail.getTag().timestamp);
+ TimeValue.fromNanoSeconds(
+ loopNodeNext.getTag().time.toNanoSeconds() - tail.getTag().time.toNanoSeconds());
System.out.println("* => Advance time by " + tsDiff);
System.out.println("* Goes back to loop node: state " + this.loopNode.getIndex());
@@ -107,7 +122,7 @@ public CodeBuilder generateDot() {
dot = new CodeBuilder();
dot.pr("digraph G {");
dot.indent();
- if (this.loopNode != null) {
+ if (this.isCyclic()) {
dot.pr("layout=circo;");
}
dot.pr("rankdir=LR;");
@@ -173,7 +188,8 @@ public CodeBuilder generateDot() {
StateSpaceNode next = getDownstreamNode(this.head);
while (current != null && next != null && current != this.tail) {
TimeValue tsDiff =
- TimeValue.fromNanoSeconds(next.getTag().timestamp - current.getTag().timestamp);
+ TimeValue.fromNanoSeconds(
+ next.getTag().time.toNanoSeconds() - current.getTag().time.toNanoSeconds());
dot.pr(
"S"
+ current.getIndex()
@@ -192,8 +208,9 @@ public CodeBuilder generateDot() {
if (loopNode != null) {
TimeValue tsDiff =
- TimeValue.fromNanoSeconds(loopNodeNext.getTag().timestamp - tail.getTag().timestamp);
- TimeValue period = TimeValue.fromNanoSeconds(loopPeriod);
+ TimeValue.fromNanoSeconds(
+ loopNodeNext.getTag().time.toNanoSeconds() - tail.getTag().time.toNanoSeconds());
+ TimeValue period = TimeValue.fromNanoSeconds(hyperperiod);
dot.pr(
"S"
+ current.getIndex()
@@ -216,4 +233,24 @@ public CodeBuilder generateDot() {
}
return this.dot;
}
+
+ public void generateDotFile(Path dir, String filename) {
+ try {
+ Path path = dir.resolve(filename);
+ CodeBuilder dot = generateDot();
+ dot.writeToFile(path.toString());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Check if the diagram is periodic by checking if the loop node is set. */
+ public boolean isCyclic() {
+ return loopNode != null;
+ }
+
+ /** Check if the diagram is empty. */
+ public boolean isEmpty() {
+ return (head == null);
+ }
}
diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java
index 6b869e7ebd..94a6171029 100644
--- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java
+++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceExplorer.java
@@ -1,11 +1,14 @@
package org.lflang.analyses.statespace;
+import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.List;
import java.util.Set;
-import org.lflang.TimeUnit;
+import org.lflang.TimeTag;
import org.lflang.TimeValue;
+import org.lflang.ast.ASTUtils;
import org.lflang.generator.ActionInstance;
import org.lflang.generator.PortInstance;
import org.lflang.generator.ReactionInstance;
@@ -15,167 +18,82 @@
import org.lflang.generator.TimerInstance;
import org.lflang.generator.TriggerInstance;
import org.lflang.lf.Expression;
-import org.lflang.lf.Time;
import org.lflang.lf.Variable;
+import org.lflang.pretvm.ExecutionPhase;
+import org.lflang.target.TargetConfig;
+import org.lflang.target.property.TimeOutProperty;
/**
* (EXPERIMENTAL) Explores the state space of an LF program. Use with caution since this is
* experimental code.
+ *
+ * @author Shaokai J. Lin
*/
public class StateSpaceExplorer {
- // Instantiate an empty state space diagram.
- public StateSpaceDiagram diagram = new StateSpaceDiagram();
-
- // Indicate whether a back loop is found in the state space.
- // A back loop suggests periodic behavior.
- public boolean loopFound = false;
-
- /**
- * Instantiate a global event queue. We will use this event queue to symbolically simulate the
- * logical timeline. This simulation is also valid for runtime implementations that are federated
- * or relax global barrier synchronization, since an LF program defines a unique logical timeline
- * (assuming all reactions behave _consistently_ throughout the execution).
- */
- public EventQueue eventQ = new EventQueue();
-
- /** The main reactor instance based on which the state space is explored. */
- public ReactorInstance main;
-
- // Constructor
- public StateSpaceExplorer(ReactorInstance main) {
- this.main = main;
- }
-
- /** Recursively add the first events to the event queue. */
- public void addInitialEvents(ReactorInstance reactor) {
- // Add the startup trigger, if exists.
- var startup = reactor.getStartupTrigger();
- if (startup != null) eventQ.add(new Event(startup, new Tag(0, 0, false)));
-
- // Add the initial timer firings, if exist.
- for (TimerInstance timer : reactor.timers) {
- eventQ.add(new Event(timer, new Tag(timer.getOffset().toNanoSeconds(), 0, false)));
- }
-
- // Recursion
- for (var child : reactor.children) {
- addInitialEvents(child);
- }
- }
-
/**
* Explore the state space and populate the state space diagram until the specified horizon (i.e.
* the end tag) is reached OR until the event queue is empty.
*
- *
As an optimization, if findLoop is true, the algorithm tries to find a loop in the state
- * space during exploration. If a loop is found (i.e. a previously encountered state is reached
- * again) during exploration, the function returns early.
+ *
As an optimization, the algorithm tries to find a loop in the state space during
+ * exploration. If a loop is found (i.e. a previously encountered state is reached again) during
+ * exploration, the function returns early.
+ *
+ *
If the phase is INIT_AND_PERIODIC, the explorer starts with startup triggers and timers'
+ * initial firings. If the phase is SHUTDOWN_*, the explorer starts with shutdown triggers.
*
- *
Note: This is experimental code which is to be refactored in a future PR. Use with caution.
+ *
Note: This is experimental code. Use with caution.
*/
- public void explore(Tag horizon, boolean findLoop) {
- // Traverse the main reactor instance recursively to find
- // the known initial events (startup and timers' first firings).
- // FIXME: It seems that we need to handle shutdown triggers
- // separately, because they could break the back loop.
- addInitialEvents(this.main);
-
- Tag previousTag = null; // Tag in the previous loop ITERATION
- Tag currentTag = null; // Tag in the current loop ITERATION
+ public static StateSpaceDiagram explore(
+ ReactorInstance main, TimeTag horizon, ExecutionPhase phase, TargetConfig targetConfig) {
+ if (phase != ExecutionPhase.INIT_AND_PERIODIC
+ && phase != ExecutionPhase.SHUTDOWN_TIMEOUT
+ && phase != ExecutionPhase.SHUTDOWN_STARVATION)
+ throw new RuntimeException("Unsupported phase detected in the explorer.");
+
+ // Variable initilizations
+ StateSpaceDiagram diagram = new StateSpaceDiagram();
+ diagram.phase = phase;
+ EventQueue eventQ = new EventQueue();
+ TimeTag previousTag = null; // TimeTag in the previous loop ITERATION
+ TimeTag currentTag = null; // TimeTag in the current loop ITERATION
StateSpaceNode currentNode = null;
StateSpaceNode previousNode = null;
HashMap uniqueNodes = new HashMap<>();
boolean stop = true;
- if (this.eventQ.size() > 0) {
+
+ // Add initial events to the event queue.
+ eventQ.addAll(addInitialEvents(main, phase, targetConfig));
+
+ // Check if we should stop already.
+ if (eventQ.size() > 0) {
stop = false;
currentTag = eventQ.peek().getTag();
}
// A list of reactions invoked at the current logical tag
Set reactionsInvoked;
+ // A temporary list of reactions processed in the current LOOP ITERATION
+ Set reactionsTemp;
+ // Iterate until stop conditions are met.
while (!stop) {
// Pop the events from the earliest tag off the event queue.
- ArrayList currentEvents = new ArrayList();
- while (eventQ.size() > 0 && eventQ.peek().getTag().compareTo(currentTag) == 0) {
- Event e = eventQ.poll();
- currentEvents.add(e);
- }
+ List currentEvents = popCurrentEvents(eventQ, currentTag);
// Collect all the reactions invoked in this current LOOP ITERATION
// triggered by the earliest events.
- // Using a hash set here to make sure the reactions invoked
- // are unique. Sometimes multiple events can trigger the same reaction,
- // and we do not want to record duplicate reaction invocations.
-
- // A temporary list of reactions processed in the current LOOP ITERATION
- Set reactionsTemp = new HashSet<>();
- for (Event e : currentEvents) {
- Set dependentReactions = e.getTrigger().getDependentReactions();
- reactionsTemp.addAll(dependentReactions);
-
- // If the event is a timer firing, enqueue the next firing.
- if (e.getTrigger() instanceof TimerInstance timer) {
- eventQ.add(
- new Event(
- timer,
- new Tag(
- e.getTag().timestamp + timer.getPeriod().toNanoSeconds(),
- 0, // A time advancement resets microstep to 0.
- false)));
- }
- }
+ reactionsTemp = getReactionsTriggeredByCurrentEvents(currentEvents);
// For each reaction invoked, compute the new events produced.
- for (ReactionInstance reaction : reactionsTemp) {
- // Iterate over all the effects produced by this reaction.
- // If the effect is a port, obtain the downstream port along
- // a connection and enqueue a future event for that port.
- // If the effect is an action, enqueue a future event for
- // this action.
- for (TriggerInstance extends Variable> effect : reaction.effects) {
- if (effect instanceof PortInstance) {
-
- for (SendRange senderRange : ((PortInstance) effect).getDependentPorts()) {
-
- for (RuntimeRange destinationRange : senderRange.destinations) {
- PortInstance downstreamPort = destinationRange.instance;
-
- // Getting delay from connection
- // FIXME: Is there a more concise way to do this?
- long delay = 0;
- Expression delayExpr = senderRange.connection.getDelay();
- if (delayExpr instanceof Time) {
- long interval = ((Time) delayExpr).getInterval();
- String unit = ((Time) delayExpr).getUnit();
- TimeValue timeValue = new TimeValue(interval, TimeUnit.fromName(unit));
- delay = timeValue.toNanoSeconds();
- }
-
- // Create and enqueue a new event.
- Event e =
- new Event(downstreamPort, new Tag(currentTag.timestamp + delay, 0, false));
- eventQ.add(e);
- }
- }
- } else if (effect instanceof ActionInstance) {
- // Get the minimum delay of this action.
- long min_delay = ((ActionInstance) effect).getMinDelay().toNanoSeconds();
- long microstep = 0;
- if (min_delay == 0) {
- microstep = currentTag.microstep + 1;
- }
- // Create and enqueue a new event.
- Event e =
- new Event(effect, new Tag(currentTag.timestamp + min_delay, microstep, false));
- eventQ.add(e);
- }
- }
- }
+ List newEvents = createNewEvents(currentEvents, reactionsTemp, currentTag);
+ // FIXME: Need to make sure that addAll() is using the overridden version
+ // that makes sure new events added are unique. By default, this should be
+ // the case.
+ eventQ.addAll(newEvents);
// We are at the first iteration.
// Initialize currentNode.
@@ -205,25 +123,35 @@ public void explore(Tag horizon, boolean findLoop) {
// This makes sure that the granularity of nodes is
// at the timestamp-level, so that we don't have to
// worry about microsteps.
- else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) {
+ else if (previousTag != null && currentTag.compareTo(previousTag) > 0) {
+ // Check if we are in the SHUTDOWN_TIMEOUT mode,
+ // if so, stop the loop immediately, because TIMEOUT is the last tag.
+ if (phase == ExecutionPhase.SHUTDOWN_TIMEOUT) {
+ // Make the hyperperiod for the SHUTDOWN_TIMEOUT phase Long.MAX_VALUE,
+ // so that this is guaranteed to be feasibile from the perspective of
+ // the EGS scheduler.
+ diagram.hyperperiod = Long.MAX_VALUE;
+ diagram.loopNode = null; // The SHUTDOWN_TIMEOUT phase is acyclic.
+ break;
+ }
+
// Whenever we finish a tag, check for loops fist.
// If currentNode matches an existing node in uniqueNodes,
// duplicate is set to the existing node.
StateSpaceNode duplicate;
- if (findLoop && (duplicate = uniqueNodes.put(currentNode.hash(), currentNode)) != null) {
+ if ((duplicate = uniqueNodes.put(currentNode.hash(), currentNode)) != null) {
// Mark the loop in the diagram.
- loopFound = true;
- this.diagram.loopNode = duplicate;
- this.diagram.loopNodeNext = currentNode;
- this.diagram.tail = previousNode;
+ diagram.loopNode = duplicate;
+ diagram.loopNodeNext = currentNode;
+ diagram.tail = previousNode;
// Loop period is the time difference between the 1st time
// the node is reached and the 2nd time the node is reached.
- this.diagram.loopPeriod =
- this.diagram.loopNodeNext.getTag().timestamp
- - this.diagram.loopNode.getTag().timestamp;
- this.diagram.addEdge(this.diagram.loopNode, this.diagram.tail);
- return; // Exit the while loop early.
+ diagram.hyperperiod =
+ diagram.loopNodeNext.getTag().time.toNanoSeconds()
+ - diagram.loopNode.getTag().time.toNanoSeconds();
+ diagram.addEdge(diagram.loopNode, diagram.tail);
+ return diagram; // Exit the while loop early.
}
// Now we are at a new tag, and a loop is not found,
@@ -231,16 +159,14 @@ else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) {
// Adding a node to the graph once it is finalized
// because this makes checking duplicate nodes easier.
// We don't have to remove a node from the graph.
- this.diagram.addNode(currentNode);
- this.diagram.tail = currentNode; // Update the current tail.
+ diagram.addNode(currentNode);
+ diagram.tail = currentNode; // Update the current tail.
// If the head is not empty, add an edge from the previous state
// to the next state. Otherwise initialize the head to the new node.
if (previousNode != null) {
- // System.out.println("--- Add a new edge between " + currentNode + " and " + node);
- // this.diagram.addEdge(currentNode, previousNode); // Sink first, then source
- if (previousNode != currentNode) this.diagram.addEdge(currentNode, previousNode);
- } else this.diagram.head = currentNode; // Initialize the head.
+ if (previousNode != currentNode) diagram.addEdge(currentNode, previousNode);
+ } else diagram.head = currentNode; // Initialize the head.
//// Now we are done with the node at the previous tag,
//// work on the new node at the current timestamp.
@@ -266,7 +192,7 @@ else if (previousTag != null && currentTag.timestamp > previousTag.timestamp) {
}
// Timestamp does not advance because we are processing
// connections with zero delay.
- else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) {
+ else if (previousTag != null && currentTag.equals(previousTag)) {
// Add reactions explored in the current loop iteration
// to the existing state space node.
currentNode.getReactionsInvoked().addAll(reactionsTemp);
@@ -287,7 +213,10 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) {
// 2. the horizon is reached.
if (eventQ.size() == 0) {
stop = true;
- } else if (currentTag.timestamp > horizon.timestamp) {
+ }
+ // FIXME: If horizon is forever, explore() might not terminate.
+ // How to set a reasonable upperbound?
+ else if (!horizon.isForever() && currentTag.compareTo(horizon) > 0) {
stop = true;
}
}
@@ -298,17 +227,357 @@ else if (previousTag != null && currentTag.timestamp == previousTag.timestamp) {
// or (previousTag != null
// && currentTag.compareTo(previousTag) > 0) is true and then
// the simulation ends, leaving a new node dangling.
- if (previousNode == null || previousNode.getTag().timestamp < currentNode.getTag().timestamp) {
- this.diagram.addNode(currentNode);
- this.diagram.tail = currentNode; // Update the current tail.
+ if (currentNode != null
+ && (previousNode == null || previousNode.getTag().compareTo(currentNode.getTag()) < 0)) {
+ diagram.addNode(currentNode);
+ diagram.tail = currentNode; // Update the current tail.
if (previousNode != null) {
- this.diagram.addEdge(currentNode, previousNode);
+ diagram.addEdge(currentNode, previousNode);
}
}
- // When we exit and we still don't have a head,
- // that means there is only one node in the diagram.
+ // At this point if we still don't have a head,
+ // then it means there is only one node in the diagram.
// Set the current node as the head.
- if (this.diagram.head == null) this.diagram.head = currentNode;
+ if (diagram.head == null) diagram.head = currentNode;
+
+ return diagram;
+ }
+
+ //////////////////////////////////////////////////////
+ ////////////////// Private Methods
+
+ /**
+ * Return a (unordered) list of initial events to be given to the state space explorer based on a
+ * given phase.
+ *
+ * @param reactor The reactor wrt which initial events are inferred
+ * @param phase The phase for which initial events are inferred
+ * @return A list of initial events
+ */
+ public static List addInitialEvents(
+ ReactorInstance reactor, ExecutionPhase phase, TargetConfig targetConfig) {
+ List events = new ArrayList<>();
+ addInitialEventsRecursive(reactor, events, phase, targetConfig);
+ return events;
+ }
+
+ /**
+ * Recursively add the first events to the event list for state space exploration. For the
+ * SHUTDOWN modes, it is okay to create shutdown events at (0,0) because this tag is a relative
+ * offset wrt to a phase (e.g., the shutdown phase), not the absolute tag at runtime.
+ */
+ public static void addInitialEventsRecursive(
+ ReactorInstance reactor,
+ List events,
+ ExecutionPhase phase,
+ TargetConfig targetConfig) {
+ switch (phase) {
+ case INIT_AND_PERIODIC:
+ {
+ // Add the startup trigger, if exists.
+ var startup = reactor.getStartupTrigger();
+ if (startup != null) events.add(new Event(startup, TimeTag.ZERO));
+
+ // Add the initial timer firings, if exist.
+ for (TimerInstance timer : reactor.timers) {
+ events.add(
+ new Event(
+ timer,
+ new TimeTag(TimeValue.fromNanoSeconds(timer.getOffset().toNanoSeconds()), 0L)));
+ }
+ break;
+ }
+ case SHUTDOWN_TIMEOUT:
+ {
+ // To get the state space of the instant at shutdown,
+ // we over-approximate by assuming all triggers are present at
+ // (timeout, 0). This could generate unnecessary instructions
+ // for reactions that are not meant to trigger at (timeout, 0),
+ // but they will be treated as NOPs at runtime.
+
+ // Add the shutdown trigger, if exists.
+ var shutdown = reactor.getShutdownTrigger();
+ if (shutdown != null) events.add(new Event(shutdown, TimeTag.ZERO));
+
+ // Check for timers that fire at (timeout, 0).
+ for (TimerInstance timer : reactor.timers) {
+ // If timeout = timer.offset + N * timer.period for some non-negative
+ // integer N, add a timer event.
+ Long offset = timer.getOffset().toNanoSeconds();
+ Long period = timer.getPeriod().toNanoSeconds();
+ Long timeout = targetConfig.get(TimeOutProperty.INSTANCE).toNanoSeconds();
+ if (period != 0 && (timeout - offset) % period == 0) {
+ // The tag is set to (0,0) because, again, this is relative to the
+ // shutdown phase, not the actual absolute tag at runtime.
+ events.add(new Event(timer, TimeTag.ZERO));
+ }
+ }
+
+ // Assume all input ports and logical actions present.
+ // FIXME: Also physical action. Will add it later.
+ for (PortInstance input : reactor.inputs) {
+ events.add(new Event(input, TimeTag.ZERO));
+ }
+ for (ActionInstance logicalAction :
+ reactor.actions.stream().filter(it -> !it.isPhysical()).toList()) {
+ events.add(new Event(logicalAction, TimeTag.ZERO));
+ }
+ break;
+ }
+ case SHUTDOWN_STARVATION:
+ {
+ // Add the shutdown trigger, if exists.
+ var shutdown = reactor.getShutdownTrigger();
+ if (shutdown != null) events.add(new Event(shutdown, TimeTag.ZERO));
+ break;
+ }
+ default:
+ throw new RuntimeException("UNREACHABLE");
+ }
+
+ // Recursion
+ for (var child : reactor.children) {
+ addInitialEventsRecursive(child, events, phase, targetConfig);
+ }
+ }
+
+ /** Pop events with currentTag off an eventQ */
+ private static List popCurrentEvents(EventQueue eventQ, TimeTag currentTag) {
+ List currentEvents = new ArrayList<>();
+ // FIXME: Use stream methods here?
+ while (eventQ.size() > 0 && eventQ.peek().getTag().compareTo(currentTag) == 0) {
+ Event e = eventQ.poll();
+ currentEvents.add(e);
+ }
+ return currentEvents;
+ }
+
+ /**
+ * Return a list of reaction instances triggered by a list of current events. The events must
+ * carry the same tag. Using a hash set here to make sure the reactions invoked are unique.
+ * Sometimes multiple events can trigger the same reaction, and we do not want to record duplicate
+ * reaction invocations.
+ */
+ private static Set getReactionsTriggeredByCurrentEvents(
+ List currentEvents) {
+ Set reactions = new HashSet<>();
+ for (Event e : currentEvents) {
+ Set dependentReactions = e.getTrigger().getDependentReactions();
+ reactions.addAll(dependentReactions);
+ }
+ return reactions;
+ }
+
+ /**
+ * Create a list of new events from reactions invoked at current tag. These new events should be
+ * able to trigger reactions, which means that the method needs to compute how events propagate
+ * downstream.
+ *
+ *
FIXME: This function does not handle port hierarchies, or the lack of them, yet. It should
+ * be updated with a new implementation that uses eventualDestinations() from PortInstance.java.
+ * But the challenge is to also get the delays. Perhaps eventualDestinations() should be extended
+ * to collect delays.
+ */
+ private static List createNewEvents(
+ List currentEvents, Set reactions, TimeTag currentTag) {
+
+ List newEvents = new ArrayList<>();
+
+ // If the event is a timer firing, enqueue the next firing.
+ for (Event e : currentEvents) {
+ if (e.getTrigger() instanceof TimerInstance) {
+ TimerInstance timer = (TimerInstance) e.getTrigger();
+ newEvents.add(
+ new Event(
+ timer,
+ new TimeTag(
+ TimeValue.fromNanoSeconds(
+ e.getTag().time.toNanoSeconds() + timer.getPeriod().toNanoSeconds()),
+ 0L // A time advancement resets microstep to 0.
+ )));
+ }
+ }
+
+ // For each reaction invoked, compute the new events produced
+ // that can immediately trigger reactions.
+ for (ReactionInstance reaction : reactions) {
+ // Iterate over all the effects produced by this reaction.
+ // If the effect is a port, obtain the downstream port along
+ // a connection and enqueue a future event for that port.
+ // If the effect is an action, enqueue a future event for
+ // this action.
+ for (TriggerInstance extends Variable> effect : reaction.effects) {
+ // If the reaction writes to a port.
+ if (effect instanceof PortInstance) {
+
+ for (SendRange senderRange : ((PortInstance) effect).getDependentPorts()) {
+
+ for (RuntimeRange destinationRange : senderRange.destinations) {
+ PortInstance downstreamPort = destinationRange.instance;
+
+ // Getting delay from connection
+ Expression delayExpr = senderRange.connection.getDelay();
+ Long delay = ASTUtils.getDelay(delayExpr);
+ if (delay == null) delay = 0L;
+
+ // Create and enqueue a new event.
+ Event e =
+ new Event(
+ downstreamPort,
+ new TimeTag(
+ TimeValue.fromNanoSeconds(currentTag.time.toNanoSeconds() + delay), 0L));
+ newEvents.add(e);
+ }
+ }
+ }
+ // Ensure we only generate new events for LOGICAL actions.
+ else if (effect instanceof ActionInstance && !((ActionInstance) effect).isPhysical()) {
+ // Get the minimum delay of this action.
+ long min_delay = ((ActionInstance) effect).getMinDelay().toNanoSeconds();
+ long microstep = 0;
+ if (min_delay == 0) {
+ microstep = currentTag.microstep + 1;
+ }
+ // Create and enqueue a new event.
+ Event e =
+ new Event(
+ effect,
+ new TimeTag(
+ TimeValue.fromNanoSeconds(currentTag.time.toNanoSeconds() + min_delay),
+ microstep));
+ newEvents.add(e);
+ }
+ }
+ }
+
+ return newEvents;
+ }
+
+ /**
+ * Generate a list of state space fragments for an LF program. This function calls
+ * generateStateSpaceDiagram() multiple times to capture the full behavior of the LF
+ * program.
+ */
+ public static List generateStateSpaceDiagrams(
+ ReactorInstance reactor, TargetConfig targetConfig, Path graphDir) {
+
+ // Initialize variables
+ List SSDs;
+
+ /* Initialization and Periodic phase */
+
+ // Generate a state space diagram for the initialization and periodic phase
+ // of an LF program.
+ StateSpaceDiagram stateSpaceInitAndPeriodic =
+ explore(reactor, TimeTag.ZERO, ExecutionPhase.INIT_AND_PERIODIC, targetConfig);
+ stateSpaceInitAndPeriodic.generateDotFile(
+ graphDir, "state_space_" + ExecutionPhase.INIT_AND_PERIODIC + ".dot");
+
+ // Split the graph into a list of diagrams.
+ List splittedDiagrams =
+ splitInitAndPeriodicDiagrams(stateSpaceInitAndPeriodic);
+
+ // Convert the diagrams into fragments (i.e., having a notion of upstream &
+ // downstream and carrying object file) and add them to the fragments list.
+ SSDs = splittedDiagrams;
+
+ // Checking abnomalies.
+ // FIXME: For some reason, the message reporter does not work here.
+ if (SSDs.size() == 0) {
+ throw new RuntimeException(
+ "No behavior found. The program is not schedulable. Please provide an initial trigger.");
+ }
+ if (SSDs.size() > 2) {
+ throw new RuntimeException(
+ "More than two fragments detected when splitting the initialization and periodic phase!");
+ }
+
+ /* Shutdown phase */
+
+ // Scenario 1: TIMEOUT
+ // Generate a state space diagram for the timeout scenario of the
+ // shutdown phase.
+ if (targetConfig.get(TimeOutProperty.INSTANCE) != null) {
+ StateSpaceDiagram stateSpaceShutdownTimeout =
+ explore(reactor, TimeTag.ZERO, ExecutionPhase.SHUTDOWN_TIMEOUT, targetConfig);
+ stateSpaceInitAndPeriodic.generateDotFile(
+ graphDir, "state_space_" + ExecutionPhase.SHUTDOWN_TIMEOUT + ".dot");
+ SSDs.add(stateSpaceShutdownTimeout);
+ }
+
+ // Scenario 2: STARVATION
+ // TODO: Generate a state space diagram for the starvation scenario of the
+ // shutdown phase.
+
+ return SSDs;
+ }
+
+ /**
+ * Identify an initialization phase and a periodic phase of the state space diagram, and create
+ * two different state space diagrams.
+ */
+ public static ArrayList splitInitAndPeriodicDiagrams(
+ StateSpaceDiagram stateSpace) {
+
+ ArrayList diagrams = new ArrayList<>();
+ StateSpaceNode current = stateSpace.head;
+ StateSpaceNode previous = null;
+
+ // Create an initialization phase diagram.
+ if (stateSpace.head != stateSpace.loopNode) {
+ StateSpaceDiagram initPhase = new StateSpaceDiagram();
+ initPhase.head = current;
+ while (current != stateSpace.loopNode) {
+ // Add node and edges to diagram.
+ initPhase.addNode(current);
+ initPhase.addEdge(current, previous);
+
+ // Update current and previous pointer.
+ previous = current;
+ current = stateSpace.getDownstreamNode(current);
+ }
+ initPhase.tail = previous;
+ if (stateSpace.loopNode != null)
+ initPhase.hyperperiod = stateSpace.loopNode.getTime().toNanoSeconds();
+ else initPhase.hyperperiod = 0;
+ initPhase.phase = ExecutionPhase.INIT;
+ diagrams.add(initPhase);
+ }
+
+ // Create a periodic phase diagram.
+ if (stateSpace.isCyclic()) {
+
+ // State this assumption explicitly.
+ assert current == stateSpace.loopNode : "Current is not pointing to loopNode.";
+
+ StateSpaceDiagram periodicPhase = new StateSpaceDiagram();
+ periodicPhase.head = current;
+ periodicPhase.addNode(current); // Add the first node.
+ if (current == stateSpace.tail) {
+ periodicPhase.addEdge(current, current); // Add edges to diagram.
+ }
+ while (current != stateSpace.tail) {
+ // Update current and previous pointer.
+ // We bring the updates before addNode() because
+ // we need to make sure tail is added.
+ // For the init diagram, we do not want to add loopNode.
+ previous = current;
+ current = stateSpace.getDownstreamNode(current);
+
+ // Add node and edges to diagram.
+ periodicPhase.addNode(current);
+ periodicPhase.addEdge(current, previous);
+ }
+ periodicPhase.tail = current;
+ periodicPhase.loopNode = stateSpace.loopNode;
+ periodicPhase.addEdge(periodicPhase.loopNode, periodicPhase.tail); // Add loop.
+ periodicPhase.loopNodeNext = stateSpace.loopNodeNext;
+ periodicPhase.hyperperiod = stateSpace.hyperperiod;
+ periodicPhase.phase = ExecutionPhase.PERIODIC;
+ diagrams.add(periodicPhase);
+ }
+
+ return diagrams;
}
}
diff --git a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java
index 0061853157..2e0e88930e 100644
--- a/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java
+++ b/core/src/main/java/org/lflang/analyses/statespace/StateSpaceNode.java
@@ -1,9 +1,9 @@
package org.lflang.analyses.statespace;
import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
import java.util.Set;
-import java.util.stream.Collectors;
+import org.lflang.TimeTag;
import org.lflang.TimeValue;
import org.lflang.generator.ReactionInstance;
import org.lflang.generator.TriggerInstance;
@@ -11,27 +11,32 @@
/** A node in the state space diagram representing a step in the execution of an LF program. */
public class StateSpaceNode {
- private int index; // Set in StateSpaceDiagram.java
- private Tag tag;
- private TimeValue time; // Readable representation of tag.timestamp
+ private int index; // An integer ID for this node
+ private TimeTag tag;
private Set reactionsInvoked;
private ArrayList eventQcopy; // A snapshot of the eventQ represented as an ArrayList
public StateSpaceNode(
- Tag tag, Set reactionsInvoked, ArrayList eventQcopy) {
+ TimeTag tag, Set reactionsInvoked, ArrayList eventQcopy) {
this.tag = tag;
this.eventQcopy = eventQcopy;
this.reactionsInvoked = reactionsInvoked;
- this.time = TimeValue.fromNanoSeconds(tag.timestamp);
+ }
+
+ /** Copy constructor */
+ public StateSpaceNode(StateSpaceNode that) {
+ this.tag = new TimeTag(that.tag);
+ this.eventQcopy = new ArrayList<>(that.eventQcopy);
+ this.reactionsInvoked = new HashSet<>(that.reactionsInvoked);
}
/** Two methods for pretty printing */
public void display() {
- System.out.println("(" + this.time + ", " + reactionsInvoked + ", " + eventQcopy + ")");
+ System.out.println("(" + this.tag.time + ", " + reactionsInvoked + ", " + eventQcopy + ")");
}
public String toString() {
- return "(" + this.time + ", " + reactionsInvoked + ", " + eventQcopy + ")";
+ return "(" + this.tag.time + ", " + reactionsInvoked + ", " + eventQcopy + ")";
}
/**
@@ -50,23 +55,20 @@ public int hash() {
result = 31 * result + reactionsInvoked.hashCode();
// Generate hash for the triggers in the queued events.
- List eventNames =
- this.eventQcopy.stream()
+ int eventsHash =
+ this.getEventQcopy().stream()
.map(Event::getTrigger)
.map(TriggerInstance::getFullName)
- .collect(Collectors.toList());
- result = 31 * result + eventNames.hashCode();
-
- // Generate hash for a list of time differences between future events' tags and
- // the current tag.
- List timeDiff =
- this.eventQcopy.stream()
- .map(
- e -> {
- return e.getTag().timestamp - this.tag.timestamp;
- })
- .collect(Collectors.toList());
- result = 31 * result + timeDiff.hashCode();
+ .mapToInt(Object::hashCode)
+ .reduce(1, (a, b) -> 31 * a + b);
+ result = 31 * result + eventsHash;
+
+ // Generate hash for the time differences.
+ long timeDiffHash =
+ this.getEventQcopy().stream()
+ .mapToLong(e -> e.getTag().time.toNanoSeconds() - this.tag.time.toNanoSeconds())
+ .reduce(1, (a, b) -> 31 * a + b);
+ result = 31 * result + (int) timeDiffHash;
return result;
}
@@ -79,12 +81,16 @@ public void setIndex(int i) {
index = i;
}
- public Tag getTag() {
+ public TimeTag getTag() {
return tag;
}
+ public void setTag(TimeTag newTag) {
+ tag = newTag;
+ }
+
public TimeValue getTime() {
- return time;
+ return tag.time;
}
public Set getReactionsInvoked() {
diff --git a/core/src/main/java/org/lflang/analyses/statespace/Tag.java b/core/src/main/java/org/lflang/analyses/statespace/Tag.java
deleted file mode 100644
index f62dde6c32..0000000000
--- a/core/src/main/java/org/lflang/analyses/statespace/Tag.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package org.lflang.analyses.statespace;
-
-import org.lflang.TimeValue;
-
-/**
- * Class representing a logical time tag, which is a pair that consists of a timestamp (type long)
- * and a microstep (type long).
- */
-public class Tag implements Comparable {
-
- public final long timestamp;
- public final long microstep;
- public final boolean forever; // Whether the tag is FOREVER into the future.
-
- public Tag(long timestamp, long microstep, boolean forever) {
- this.timestamp = timestamp;
- this.microstep = microstep;
- this.forever = forever;
- }
-
- @Override
- public int compareTo(Tag t) {
- // If one tag is forever, and the other is not,
- // then forever tag is later. If both tags are
- // forever, then they are equal.
- if (this.forever && !t.forever) return 1;
- else if (!this.forever && t.forever) return -1;
- else if (this.forever && t.forever) return 0;
-
- // Compare the timestamps if they are not equal.
- if (this.timestamp != t.timestamp) {
- if (this.timestamp > t.timestamp) return 1;
- else if (this.timestamp < t.timestamp) return -1;
- else return 0;
- }
-
- // Otherwise, compare the microsteps.
- if (this.microstep > t.microstep) return 1;
- else if (this.microstep < t.microstep) return -1;
- else return 0;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o instanceof Tag) {
- Tag t = (Tag) o;
- if (this.timestamp == t.timestamp
- && this.microstep == t.microstep
- && this.forever == t.forever) return true;
- }
- return false;
- }
-
- @Override
- public String toString() {
- if (this.forever) return "(FOREVER, " + this.microstep + ")";
- else {
- TimeValue time = TimeValue.fromNanoSeconds(this.timestamp);
- return "(" + time + ", " + this.microstep + ")";
- }
- }
-}
diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java
index 9253856fcc..1a36750bba 100644
--- a/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java
+++ b/core/src/main/java/org/lflang/analyses/uclid/UclidGenerator.java
@@ -37,6 +37,7 @@
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.eclipse.emf.ecore.resource.Resource;
+import org.lflang.TimeTag;
import org.lflang.TimeUnit;
import org.lflang.TimeValue;
import org.lflang.analyses.c.AstUtils;
@@ -48,7 +49,6 @@
import org.lflang.analyses.statespace.StateSpaceDiagram;
import org.lflang.analyses.statespace.StateSpaceExplorer;
import org.lflang.analyses.statespace.StateSpaceNode;
-import org.lflang.analyses.statespace.Tag;
import org.lflang.ast.ASTUtils;
import org.lflang.dsl.CLexer;
import org.lflang.dsl.CParser;
@@ -76,6 +76,7 @@
import org.lflang.lf.Connection;
import org.lflang.lf.Expression;
import org.lflang.lf.Time;
+import org.lflang.pretvm.ExecutionPhase;
import org.lflang.target.Target;
import org.lflang.util.StringUtil;
@@ -1611,11 +1612,12 @@ private void populateLists(ReactorInstance reactor) {
*/
private void computeCT() {
- StateSpaceExplorer explorer = new StateSpaceExplorer(this.main);
- explorer.explore(
- new Tag(this.horizon, 0, false), true // findLoop
- );
- StateSpaceDiagram diagram = explorer.diagram;
+ StateSpaceDiagram diagram =
+ StateSpaceExplorer.explore(
+ this.main,
+ new TimeTag(TimeValue.fromNanoSeconds(this.horizon), 0L),
+ ExecutionPhase.INIT_AND_PERIODIC,
+ targetConfig);
diagram.display();
// Generate a dot file.
@@ -1629,7 +1631,7 @@ private void computeCT() {
}
//// Compute CT
- if (!explorer.loopFound) {
+ if (!diagram.isCyclic()) {
if (this.logicalTimeBased) this.CT = diagram.nodeCount();
else {
// FIXME: This could be much more efficient with
@@ -1647,20 +1649,21 @@ private void computeCT() {
else {
// Subtract the non-periodic logical time
// interval from the total horizon.
- long horizonRemained = Math.subtractExact(this.horizon, diagram.loopNode.getTag().timestamp);
+ long horizonRemained =
+ Math.subtractExact(this.horizon, diagram.loopNode.getTag().time.toNanoSeconds());
// Check how many loop iteration is required
// to check the remaining horizon.
int loopIterations = 0;
- if (diagram.loopPeriod == 0 && horizonRemained != 0)
+ if (diagram.hyperperiod == 0 && horizonRemained != 0)
throw new RuntimeException(
"ERROR: Zeno behavior detected while the horizon is non-zero. The program has no"
+ " finite CT.");
- else if (diagram.loopPeriod == 0 && horizonRemained == 0) {
+ else if (diagram.hyperperiod == 0 && horizonRemained == 0) {
// Handle this edge case.
throw new RuntimeException("Unhandled case: both the horizon and period are 0!");
} else {
- loopIterations = (int) Math.ceil((double) horizonRemained / diagram.loopPeriod);
+ loopIterations = (int) Math.ceil((double) horizonRemained / diagram.hyperperiod);
}
if (this.logicalTimeBased) {
diff --git a/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java b/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java
index 4ec0703c79..2d9ddc3c81 100644
--- a/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java
+++ b/core/src/main/java/org/lflang/analyses/uclid/UclidRunner.java
@@ -14,8 +14,9 @@
import org.json.JSONException;
import org.json.JSONObject;
import org.lflang.MessageReporter;
+import org.lflang.TimeTag;
+import org.lflang.TimeValue;
import org.lflang.analyses.statespace.StateInfo;
-import org.lflang.analyses.statespace.Tag;
import org.lflang.generator.GeneratorCommandFactory;
import org.lflang.util.LFCommand;
@@ -98,7 +99,8 @@ public StateInfo parseStateInfo(String smtStr) {
// The rest falls into group 2.
else tagStr = m.group(2).strip();
String[] tag = tagStr.split("\\s+");
- info.tag = new Tag(Long.parseLong(tag[0]), Long.parseLong(tag[1]), false);
+ info.tag =
+ new TimeTag(TimeValue.fromNanoSeconds(Long.parseLong(tag[0])), Long.parseLong(tag[1]));
// Variables
// Currently all integers.
diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java
index ef66d24cd1..563c63cd4d 100644
--- a/core/src/main/java/org/lflang/ast/ASTUtils.java
+++ b/core/src/main/java/org/lflang/ast/ASTUtils.java
@@ -65,6 +65,8 @@
import org.lflang.generator.CodeMap;
import org.lflang.generator.InvalidSourceException;
import org.lflang.generator.NamedInstance;
+import org.lflang.generator.PortInstance;
+import org.lflang.generator.ReactionInstance;
import org.lflang.generator.ReactorInstance;
import org.lflang.lf.Action;
import org.lflang.lf.Assignment;
@@ -514,11 +516,49 @@ public static List allModes(Reactor definition) {
return ASTUtils.collectElements(definition, featurePackage.getReactor_Modes());
}
- public static List recursiveChildren(ReactorInstance r) {
+ /**
+ * A recursive method for returning all reactor instances
+ *
+ * @param r The reactor at which the search begins
+ * @return A list of reactors, including r and the recursively nested children of r
+ */
+ public static List allReactorInstances(ReactorInstance r) {
List ret = new ArrayList<>();
ret.add(r);
for (var child : r.children) {
- ret.addAll(recursiveChildren(child));
+ ret.addAll(allReactorInstances(child));
+ }
+ return ret;
+ }
+
+ /**
+ * A recursive method for returning all reaction instances under a parent reactor
+ *
+ * @param r The reactor at which the search begins
+ * @return A list of reactions, including those within r and in the recursively nested children of
+ * r
+ */
+ public static List allReactionInstances(ReactorInstance r) {
+ List ret = new ArrayList<>();
+ ret.addAll(r.reactions);
+ for (var child : r.children) {
+ ret.addAll(allReactionInstances(child));
+ }
+ return ret;
+ }
+
+ /**
+ * A recursive method for returning all port instances under a parent reactor
+ *
+ * @param r The reactor at which the search begins
+ * @return A list of ports, including those within r and in the recursively nested children of r
+ */
+ public static List allPortInstances(ReactorInstance r) {
+ List ret = new ArrayList<>();
+ ret.addAll(r.inputs);
+ ret.addAll(r.outputs);
+ for (var child : r.children) {
+ ret.addAll(allPortInstances(child));
}
return ret;
}
diff --git a/core/src/main/java/org/lflang/generator/PortInstance.java b/core/src/main/java/org/lflang/generator/PortInstance.java
index d25d5b362b..a8be677410 100644
--- a/core/src/main/java/org/lflang/generator/PortInstance.java
+++ b/core/src/main/java/org/lflang/generator/PortInstance.java
@@ -146,10 +146,31 @@ public List eventualDestinations() {
// Construct the full range for this port.
RuntimeRange range = new RuntimeRange.Port(this);
- eventualDestinationRanges = eventualDestinations(range);
+ eventualDestinationRanges = eventualDestinations(range, true);
return eventualDestinationRanges;
}
+ /**
+ * Similar to eventualDestinations(), this method returns a list of ranges of this port, where
+ * each range sends to a list of destination ports that receive data from the range of this port.
+ * Each destination port is annotated with the channel range on which it receives data. The ports
+ * listed are only ports that are sources for reactions, not relay ports that the data may go
+ * through on the way.
+ *
+ *
Different than eventualDestinations(), this method includes destinations with after delays
+ * in between.
+ */
+ public List eventualDestinationsWithAfterDelays() {
+ if (eventualDestinationRangesWithAfterDelays != null) {
+ return eventualDestinationRangesWithAfterDelays;
+ }
+
+ // Construct the full range for this port.
+ RuntimeRange range = new RuntimeRange.Port(this);
+ eventualDestinationRangesWithAfterDelays = eventualDestinations(range, false);
+ return eventualDestinationRangesWithAfterDelays;
+ }
+
/**
* Return a list of ranges of ports that send data to this port. If this port is directly written
* to by one more more reactions, then it is its own eventual source and only this port will be
@@ -257,7 +278,8 @@ public int getLevelUpperBound(MixedRadixInt index) {
*
* @param srcRange The source range.
*/
- private static List eventualDestinations(RuntimeRange srcRange) {
+ private static List eventualDestinations(
+ RuntimeRange srcRange, boolean skipAfterDelays) {
// Getting the destinations is more complex than getting the sources
// because of multicast, where there is more than one connection statement
@@ -291,9 +313,11 @@ private static List eventualDestinations(RuntimeRange s
// Need to find send ranges that overlap with this srcRange.
for (SendRange wSendRange : srcPort.dependentPorts) {
- if (wSendRange.connection != null
- && (wSendRange.connection.getDelay() != null || wSendRange.connection.isPhysical())) {
- continue;
+ if (skipAfterDelays) {
+ if (wSendRange.connection != null
+ && (wSendRange.connection.getDelay() != null || wSendRange.connection.isPhysical())) {
+ continue;
+ }
}
wSendRange = wSendRange.overlap(srcRange);
@@ -303,7 +327,7 @@ private static List eventualDestinations(RuntimeRange s
}
for (RuntimeRange dstRange : wSendRange.destinations) {
// Recursively get the send ranges of that destination port.
- List dstSendRanges = eventualDestinations(dstRange);
+ List dstSendRanges = eventualDestinations(dstRange, skipAfterDelays);
int sendRangeStart = 0;
for (SendRange dstSend : dstSendRanges) {
queue.add(dstSend.newSendRange(wSendRange, sendRangeStart));
@@ -447,6 +471,9 @@ private void setInitialWidth(MessageReporter messageReporter) {
/** Cached list of destination ports with channel ranges. */
private List eventualDestinationRanges;
+ /** Cached list of destination ports with channel ranges including after delays. */
+ private List eventualDestinationRangesWithAfterDelays;
+
/** Cached list of source ports with channel ranges. */
private List> eventualSourceRanges;
diff --git a/core/src/main/java/org/lflang/generator/ReactionInstance.java b/core/src/main/java/org/lflang/generator/ReactionInstance.java
index 029eb13f0a..29dbcba332 100644
--- a/core/src/main/java/org/lflang/generator/ReactionInstance.java
+++ b/core/src/main/java/org/lflang/generator/ReactionInstance.java
@@ -25,11 +25,14 @@
package org.lflang.generator;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.eclipse.xtext.xbase.lib.StringExtensions;
+import org.lflang.AttributeUtils;
import org.lflang.TimeValue;
import org.lflang.ast.ASTUtils;
import org.lflang.lf.Action;
@@ -176,6 +179,8 @@ public ReactionInstance(Reaction definition, ReactorInstance parent, int index)
if (this.definition.getDeadline() != null) {
this.declaredDeadline = new DeadlineInstance(this.definition.getDeadline(), this);
}
+ // If @wcet annotation is specified, update the wcet.
+ this.wcet = AttributeUtils.getWCET(this.definition);
}
//////////////////////////////////////////////////////
@@ -210,6 +215,12 @@ public ReactionInstance(Reaction definition, ReactorInstance parent, int index)
*/
public Set> triggers = new LinkedHashSet<>();
+ /**
+ * The worst-case execution time (WCET) of the reaction. Note that this is platform dependent. If
+ * the WCET is unknown, set it to the maximum value.
+ */
+ public TimeValue wcet = TimeValue.MAX_VALUE;
+
//////////////////////////////////////////////////////
//// Public methods.
@@ -293,6 +304,39 @@ public Set dependsOnReactions() {
return dependsOnReactionsCache;
}
+ /**
+ * Return the set of downstream reactions, which are reactions that receive data produced by this
+ * reaction, paired with an associated delay along a connection.
+ *
+ *
FIXME: Add caching.
+ *
+ *
FIXME: The use of `port.dependentPorts` here restricts the supported LF programs to a single
+ * hierarchy. More needs to be done to relax this.
+ *
+ *
FIXME: How to get the accumulated delays?
+ */
+ public Map downstreamReactions() {
+ Map downstreamReactions = new HashMap<>();
+ // Add reactions that get data from this one via a port, coupled with the
+ // delay value.
+ for (TriggerInstance extends Variable> effect : effects) {
+ if (effect instanceof PortInstance port) {
+ for (SendRange senderRange : port.eventualDestinationsWithAfterDelays()) {
+ Long delay = 0L;
+ if (senderRange.connection == null) continue;
+ var delayExpr = senderRange.connection.getDelay();
+ if (delayExpr != null) delay = ASTUtils.getDelay(senderRange.connection.getDelay());
+ for (RuntimeRange destinationRange : senderRange.destinations) {
+ for (var dependentReaction : destinationRange.instance.dependentReactions) {
+ downstreamReactions.put(dependentReaction, delay);
+ }
+ }
+ }
+ }
+ }
+ return downstreamReactions;
+ }
+
/**
* Return a set of levels that runtime instances of this reaction have. A ReactionInstance may
* have more than one level if it lies within a bank and its dependencies on other reactions pass
diff --git a/core/src/main/java/org/lflang/generator/c/CActionGenerator.java b/core/src/main/java/org/lflang/generator/c/CActionGenerator.java
index 03a859f558..10d82243b2 100644
--- a/core/src/main/java/org/lflang/generator/c/CActionGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CActionGenerator.java
@@ -1,7 +1,5 @@
package org.lflang.generator.c;
-import static org.lflang.generator.c.CGenerator.variableStructType;
-
import java.util.ArrayList;
import java.util.List;
import org.lflang.ast.ASTUtils;
@@ -105,7 +103,7 @@ public static void generateDeclarations(
TypeParameterizedReactor tpr, CodeBuilder body, CodeBuilder constructorCode) {
for (Action action : ASTUtils.allActions(tpr.reactor())) {
var actionName = action.getName();
- body.pr(CGenerator.variableStructType(action, tpr, false) + " _lf_" + actionName + ";");
+ body.pr(CUtil.variableStructType(action, tpr, false) + " _lf_" + actionName + ";");
// Initialize the trigger pointer and the parent pointer in the action.
constructorCode.pr(
"self->_lf_" + actionName + "._base.trigger = &self->_lf__" + actionName + ";");
@@ -152,7 +150,7 @@ public static String generateAuxiliaryStruct(
code.pr(valueDeclaration(tpr, action, target, types));
code.pr(federatedExtension.toString());
code.unindent();
- code.pr("} " + variableStructType(action, tpr, userFacing) + ";");
+ code.pr("} " + CUtil.variableStructType(action, tpr, userFacing) + ";");
return code.toString();
}
diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java
index 2c21ae7b7f..8f16a124b0 100644
--- a/core/src/main/java/org/lflang/generator/c/CGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java
@@ -82,7 +82,6 @@
import org.lflang.lf.ReactorDecl;
import org.lflang.lf.StateVar;
import org.lflang.lf.VarRef;
-import org.lflang.lf.Variable;
import org.lflang.lf.WidthSpec;
import org.lflang.target.Target;
import org.lflang.target.TargetConfig;
@@ -437,6 +436,20 @@ public void doGenerate(Resource resource, LFGeneratorContext context) {
throw e;
}
+ // Generate static schedule
+ // TO REMOVE LATER - JUST FOR TESTING.
+ // CScheduleGenerator schedGen =
+ // new CScheduleGenerator(
+ // this.fileConfig,
+ // this.targetConfig,
+ // this.messageReporter,
+ // this.main,
+ // ASTUtils.allReactorInstances(main),
+ // ASTUtils.allReactionInstances(main),
+ // ASTUtils.allPortInstances(main)
+ // );
+ // schedGen.doGenerate();
+
// Inform the runtime of the number of watchdogs
// TODO: Can we do this at a better place? We need to do it when we have the main reactor
// since we need main to get all enclaves.
@@ -1206,14 +1219,18 @@ private void generateInteractingContainedReactors(
// to be malloc'd at initialization.
if (!ASTUtils.isMultiport(port)) {
// Not a multiport.
- body.pr(variableStructType(port, containedTpr, false) + " " + port.getName() + ";");
+ body.pr(
+ CUtil.variableStructType(port, containedTpr, false) + " " + port.getName() + ";");
} else {
// Is a multiport.
// Memory will be malloc'd in initialization.
body.pr(
String.join(
"\n",
- variableStructType(port, containedTpr, false) + "** " + port.getName() + ";",
+ CUtil.variableStructType(port, containedTpr, false)
+ + "** "
+ + port.getName()
+ + ";",
"int " + port.getName() + "_width;"));
}
} else {
@@ -1222,7 +1239,8 @@ private void generateInteractingContainedReactors(
// self struct of the container.
if (!ASTUtils.isMultiport(port)) {
// Not a multiport.
- body.pr(variableStructType(port, containedTpr, false) + "* " + port.getName() + ";");
+ body.pr(
+ CUtil.variableStructType(port, containedTpr, false) + "* " + port.getName() + ";");
} else {
// Is a multiport.
// Here, we will use an array of pointers.
@@ -1230,7 +1248,10 @@ private void generateInteractingContainedReactors(
body.pr(
String.join(
"\n",
- variableStructType(port, containedTpr, false) + "** " + port.getName() + ";",
+ CUtil.variableStructType(port, containedTpr, false)
+ + "** "
+ + port.getName()
+ + ";",
"int " + port.getName() + "_width;"));
}
body.pr("trigger_t " + port.getName() + "_trigger;");
@@ -1680,31 +1701,6 @@ public void processProtoFile(String filename) {
}
}
- /**
- * Construct a unique type for the struct of the specified typed variable (port or action) of the
- * specified reactor class. This is required to be the same as the type name returned by {@link
- * #variableStructType(TriggerInstance)}.
- */
- public static String variableStructType(
- Variable variable, TypeParameterizedReactor tpr, boolean userFacing) {
- return (userFacing ? tpr.getName().toLowerCase() : CUtil.getName(tpr))
- + "_"
- + variable.getName()
- + "_t";
- }
-
- /**
- * Construct a unique type for the struct of the specified instance (port or action). This is
- * required to be the same as the type name returned by {@link #variableStructType(Variable,
- * TypeParameterizedReactor, boolean)}.
- *
- * @param portOrAction The port or action instance.
- * @return The name of the self struct.
- */
- public static String variableStructType(TriggerInstance> portOrAction) {
- return CUtil.getName(portOrAction.getParent().tpr) + "_" + portOrAction.getName() + "_t";
- }
-
/**
* If tracing is turned on, then generate code that records the full name of the specified reactor
* instance in the trace table. If tracing is not turned on, do nothing.
@@ -2161,6 +2157,6 @@ private void generateSelfStructs(ReactorInstance r) {
}
private Stream allTypeParameterizedReactors() {
- return ASTUtils.recursiveChildren(main).stream().map(it -> it.tpr).distinct();
+ return ASTUtils.allReactorInstances(main).stream().map(it -> it.tpr).distinct();
}
}
diff --git a/core/src/main/java/org/lflang/generator/c/CPortGenerator.java b/core/src/main/java/org/lflang/generator/c/CPortGenerator.java
index 56c2d85ff7..1b699691ca 100644
--- a/core/src/main/java/org/lflang/generator/c/CPortGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CPortGenerator.java
@@ -1,7 +1,5 @@
package org.lflang.generator.c;
-import static org.lflang.generator.c.CGenerator.variableStructType;
-
import org.lflang.AttributeUtils;
import org.lflang.MessageReporter;
import org.lflang.ast.ASTUtils;
@@ -76,7 +74,7 @@ public static String generateAuxiliaryStruct(
var name =
decl != null
? localPortName(tpr, decl, port.getName())
- : variableStructType(port, tpr, userFacing);
+ : CUtil.variableStructType(port, tpr, userFacing);
code.pr("} " + name + ";");
return code.toString();
}
@@ -101,8 +99,8 @@ public static String initializeInputMultiport(PortInstance input, String reactor
"\n",
portRefName + "_width = " + input.getWidth() + ";",
"// Allocate memory for multiport inputs.",
- portRefName + " = (" + variableStructType(input) + "**)lf_allocate(",
- " " + input.getWidth() + ", sizeof(" + variableStructType(input) + "*),",
+ portRefName + " = (" + CUtil.variableStructType(input) + "**)lf_allocate(",
+ " " + input.getWidth() + ", sizeof(" + CUtil.variableStructType(input) + "*),",
" &" + reactorSelfStruct + "->base.allocations); ",
"// Set inputs by default to an always absent default input.",
"for (int i = 0; i < " + input.getWidth() + "; i++) {",
@@ -152,7 +150,7 @@ public static String initializeInputMultiport(PortInstance input, String reactor
*/
public static String initializeOutputMultiport(PortInstance output, String reactorSelfStruct) {
var portRefName = CUtil.portRefName(output);
- var portStructType = variableStructType(output);
+ var portStructType = CUtil.variableStructType(output);
return output.isMultiport()
? String.join(
"\n",
@@ -221,10 +219,10 @@ private static void generateInputDeclarations(
String.join(
"\n",
"// Multiport input array will be malloc'd later.",
- variableStructType(input, tpr, false) + "** _lf_" + inputName + ";",
+ CUtil.variableStructType(input, tpr, false) + "** _lf_" + inputName + ";",
"int _lf_" + inputName + "_width;",
"// Default input (in case it does not get connected)",
- variableStructType(input, tpr, false) + " _lf_default__" + inputName + ";",
+ CUtil.variableStructType(input, tpr, false) + " _lf_default__" + inputName + ";",
"// Struct to support efficiently reading sparse inputs.",
"lf_sparse_io_record_t* _lf_" + inputName + "__sparse;"));
} else {
@@ -232,11 +230,11 @@ private static void generateInputDeclarations(
body.pr(
String.join(
"\n",
- variableStructType(input, tpr, false) + "* _lf_" + inputName + ";",
+ CUtil.variableStructType(input, tpr, false) + "* _lf_" + inputName + ";",
"// width of -2 indicates that it is not a multiport.",
"int _lf_" + inputName + "_width;",
"// Default input (in case it does not get connected)",
- variableStructType(input, tpr, false) + " _lf_default__" + inputName + ";"));
+ CUtil.variableStructType(input, tpr, false) + " _lf_default__" + inputName + ";"));
constructorCode.pr(
String.join(
@@ -264,7 +262,7 @@ private static void generateOutputDeclarations(
String.join(
"\n",
"// Array of output ports.",
- variableStructType(output, tpr, false) + "* _lf_" + outputName + ";",
+ CUtil.variableStructType(output, tpr, false) + "* _lf_" + outputName + ";",
"int _lf_" + outputName + "_width;",
"// An array of pointers to the individual ports. Useful",
"// for the lf_set macros to work out-of-the-box for",
@@ -272,12 +270,15 @@ private static void generateOutputDeclarations(
"// value can be accessed via a -> operator (e.g.,foo[i]->value).",
"// So we have to handle multiports specially here a construct that",
"// array of pointers.",
- variableStructType(output, tpr, false) + "** _lf_" + outputName + "_pointers;"));
+ CUtil.variableStructType(output, tpr, false)
+ + "** _lf_"
+ + outputName
+ + "_pointers;"));
} else {
body.pr(
String.join(
"\n",
- variableStructType(output, tpr, false) + " _lf_" + outputName + ";",
+ CUtil.variableStructType(output, tpr, false) + " _lf_" + outputName + ";",
"int _lf_" + outputName + "_width;"));
}
}
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 a2f2a93f7d..ea008ba0d8 100644
--- a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java
@@ -166,7 +166,7 @@ public static String generateInitializationForReaction(
// If it has already appeared as trigger, do not redefine it.
if (!actionsAsTriggers.contains(effect.getVariable())) {
reactionInitialization.pr(
- CGenerator.variableStructType(variable, tpr, false)
+ CUtil.variableStructType(variable, tpr, false)
+ "* "
+ variable.getName()
+ " = &self->_lf_"
@@ -382,8 +382,7 @@ private static void generateVariablesForSendingToContainedReactors(
structs.put(definition, structBuilder);
}
String inputStructType =
- CGenerator.variableStructType(
- input, new TypeParameterizedReactor(definition, container), false);
+ CUtil.variableStructType(input, new TypeParameterizedReactor(definition, container), false);
String defName = definition.getName();
String defWidth = generateWidthVariable(defName);
String inputName = input.getName();
@@ -473,7 +472,7 @@ private static void generatePortVariablesInReaction(
// port is an output of a contained reactor.
Output output = (Output) port.getVariable();
String portStructType =
- CGenerator.variableStructType(
+ CUtil.variableStructType(
output, new TypeParameterizedReactor(port.getContainer(), tpr), false);
CodeBuilder structBuilder = structs.get(port.getContainer());
@@ -548,7 +547,7 @@ private static void generatePortVariablesInReaction(
*/
private static String generateActionVariablesInReaction(
Action action, TypeParameterizedReactor tpr, CTypes types) {
- String structType = CGenerator.variableStructType(action, tpr, false);
+ String structType = CUtil.variableStructType(action, tpr, false);
// If the action has a type, create variables for accessing the value.
InferredType type = ASTUtils.getInferredType(action);
// Pointer to the lf_token_t sent as the payload in the trigger.
@@ -614,7 +613,7 @@ private static String generateActionVariablesInReaction(
*/
private static String generateInputVariablesInReaction(
Input input, TypeParameterizedReactor tpr, CTypes types) {
- String structType = CGenerator.variableStructType(input, tpr, false);
+ String structType = CUtil.variableStructType(input, tpr, false);
InferredType inputType = ASTUtils.getInferredType(input);
CodeBuilder builder = new CodeBuilder();
String inputName = input.getName();
@@ -782,8 +781,8 @@ public static String generateOutputVariablesInReaction(
// the reactor containing the reaction.
String outputStructType =
(effect.getContainer() == null)
- ? CGenerator.variableStructType(output, tpr, false)
- : CGenerator.variableStructType(
+ ? CUtil.variableStructType(output, tpr, false)
+ : CUtil.variableStructType(
output, new TypeParameterizedReactor(effect.getContainer(), tpr), false);
if (!ASTUtils.isMultiport(output)) {
// Output port is not a multiport.
diff --git a/core/src/main/java/org/lflang/generator/c/CReactorHeaderFileGenerator.java b/core/src/main/java/org/lflang/generator/c/CReactorHeaderFileGenerator.java
index 785db3a44c..1965a28511 100644
--- a/core/src/main/java/org/lflang/generator/c/CReactorHeaderFileGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CReactorHeaderFileGenerator.java
@@ -216,7 +216,7 @@ private record PortVariable(
String getType(boolean userFacing) {
var typeName =
container == null
- ? CGenerator.variableStructType(tv, r, userFacing)
+ ? CUtil.variableStructType(tv, r, userFacing)
: CPortGenerator.localPortName(
new TypeParameterizedReactor(container, r),
container.getReactorClass(),
diff --git a/core/src/main/java/org/lflang/generator/c/CScheduleGenerator.java b/core/src/main/java/org/lflang/generator/c/CScheduleGenerator.java
new file mode 100644
index 0000000000..4d33a46e98
--- /dev/null
+++ b/core/src/main/java/org/lflang/generator/c/CScheduleGenerator.java
@@ -0,0 +1,217 @@
+/*************
+ * Copyright (c) 2019-2023, The University of California at Berkeley.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+ * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+ * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ ***************/
+
+package org.lflang.generator.c;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import org.lflang.analyses.statespace.StateSpaceDiagram;
+import org.lflang.analyses.statespace.StateSpaceExplorer;
+import org.lflang.generator.PortInstance;
+import org.lflang.generator.ReactionInstance;
+import org.lflang.generator.ReactorInstance;
+import org.lflang.pretvm.InstructionGenerator;
+import org.lflang.pretvm.PartialSchedule;
+import org.lflang.pretvm.Registers;
+import org.lflang.pretvm.dag.Dag;
+import org.lflang.pretvm.dag.DagGenerator;
+import org.lflang.pretvm.instruction.Instruction;
+import org.lflang.pretvm.scheduler.LoadBalancedScheduler;
+import org.lflang.pretvm.scheduler.StaticScheduler;
+import org.lflang.target.TargetConfig;
+import org.lflang.target.property.CompileDefinitionsProperty;
+import org.lflang.target.property.WorkersProperty;
+
+public class CScheduleGenerator {
+
+ /** File config */
+ protected final CFileConfig fileConfig;
+
+ /** Target configuration */
+ protected TargetConfig targetConfig;
+
+ /** Main reactor instance */
+ protected ReactorInstance main;
+
+ /** The number of workers to schedule for */
+ protected int workers;
+
+ /** A list of reactor instances */
+ protected List reactors;
+
+ /** A list of reaction instances */
+ protected List reactions;
+
+ /** A list of ports */
+ protected List ports;
+
+ /** A path for storing graph */
+ protected Path graphDir;
+
+ /** PretVM registers */
+ protected Registers registers;
+
+ /** Flag indicating whether optimizers are used */
+ protected boolean optimize = false;
+
+ // Constructor
+ public CScheduleGenerator(
+ CFileConfig fileConfig,
+ TargetConfig targetConfig,
+ ReactorInstance main,
+ List reactors,
+ List reactions,
+ List ports) {
+ this.fileConfig = fileConfig;
+ this.targetConfig = targetConfig;
+ this.main = main;
+ this.workers =
+ targetConfig.get(WorkersProperty.INSTANCE) == 0
+ ? 1
+ : targetConfig.get(WorkersProperty.INSTANCE);
+ this.reactors = reactors;
+ this.reactions = reactions;
+ this.ports = ports;
+ this.registers = new Registers(workers);
+ this.optimize = false;
+
+ // Create a directory for storing graph.
+ this.graphDir = fileConfig.getSrcGenPath().resolve("graphs");
+ try {
+ Files.createDirectories(this.graphDir);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ // Main function for generating static_schedule.c
+ public void doGenerate() {
+ // Generate a list of state space fragments that captures
+ // all the behavior of the LF program.
+ List SSDs =
+ StateSpaceExplorer.generateStateSpaceDiagrams(main, targetConfig, this.graphDir);
+
+ // Instantiate object files with SSDs and connect them.
+ List schedules = new ArrayList<>();
+ for (var ssd : SSDs) {
+ PartialSchedule ps = new PartialSchedule();
+ ps.setDiagram(ssd);
+ schedules.add(ps);
+ }
+ PartialSchedule.link(schedules, registers);
+
+ // Create a DAG generator
+ DagGenerator dagGenerator = new DagGenerator(this.fileConfig);
+
+ // Create a scheduler.
+ StaticScheduler scheduler = createStaticScheduler();
+
+ // Determine the number of workers, if unspecified.
+ if (this.workers == 0) {
+ // Update the previous value of 0.
+ this.workers = scheduler.setNumberOfWorkers();
+ WorkersProperty.INSTANCE.update(targetConfig, this.workers);
+
+ // Update CMAKE compile definitions.
+ final var defs = new HashMap();
+ defs.put("NUMBER_OF_WORKERS", String.valueOf(targetConfig.get(WorkersProperty.INSTANCE)));
+ CompileDefinitionsProperty.INSTANCE.update(targetConfig, defs);
+ }
+
+ // Create InstructionGenerator, which acts as a compiler and a linker.
+ InstructionGenerator instGen =
+ new InstructionGenerator(
+ this.fileConfig,
+ this.targetConfig,
+ this.workers,
+ this.main,
+ this.reactors,
+ this.reactions,
+ this.ports,
+ this.registers);
+
+ // For each partial schedule, generate a DAG, perform DAG scheduling (mapping tasks
+ // to workers), and generate instructions for each worker.
+ for (int i = 0; i < schedules.size(); i++) {
+ // Get the partial schedule.
+ PartialSchedule schedule = schedules.get(i);
+
+ // Generate a raw DAG from the partial schedule's state space diagram.
+ Dag dag = dagGenerator.generateDag(schedule.getDiagram());
+
+ // Validate the generated raw DAG.
+ if (!dag.isValid()) throw new RuntimeException("The generated DAG is invalid: " + dag);
+
+ // Generate a dot file.
+ Path dagRawDot = graphDir.resolve("dag_raw" + "_" + i + ".dot");
+ dag.generateDotFile(dagRawDot);
+
+ // Prune redundant edges and generate a dot file.
+ // FIXME: To remove.
+ dag.removeRedundantEdges();
+ Path file = graphDir.resolve("dag_pruned" + "_" + i + ".dot");
+ dag.generateDotFile(file);
+
+ // Generate a partitioned DAG based on the number of workers,
+ // and generate a dot graph.
+ Dag dagPartitioned = scheduler.partitionDag(dag, i, this.workers);
+ Path dagPartitionedDot = graphDir.resolve("dag_partitioned" + "_" + i + ".dot");
+ dagPartitioned.generateDotFile(dagPartitionedDot);
+
+ // Generate instructions (wrapped in an object file) from DAG partitions.
+ List> instructions = instGen.generateInstructions(dagPartitioned, schedule);
+
+ // TODO: Check if deadlines could be violated.
+
+ // Point the partitioned DAG and the instructions to the partial schedule.
+ schedule.setDag(dagPartitioned);
+ schedule.setInstructions(instructions);
+ }
+
+ // Invoke the dag-based optimizer on each object file.
+ // It is invoked before linking because after linking,
+ // the DAG information is gone.
+
+ // Link multiple object files into a single executable
+ // (represented also in an object file class).
+ // Instructions are also inserted based on transition guards between fragments.
+ // In addition, PREAMBLE and EPILOGUE instructions are inserted here.
+ List> linkedInstructions = instGen.link(schedules, graphDir);
+
+ // Invoke the peephole optimizer.
+ // FIXME: Should only apply to basic blocks!
+
+ // Generate C code.
+ instGen.generateCode(linkedInstructions);
+ }
+
+ /** Create a static scheduler. */
+ private StaticScheduler createStaticScheduler() {
+ return new LoadBalancedScheduler(this.graphDir);
+ }
+}
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 ebef8e5933..6aec998b6e 100644
--- a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java
@@ -349,7 +349,7 @@ private static String connectPortToEventualDestinations(PortInstance src) {
for (SendRange srcRange : src.eventualDestinations()) {
for (RuntimeRange dstRange : srcRange.destinations) {
var dst = dstRange.instance;
- var destStructType = CGenerator.variableStructType(dst);
+ var destStructType = CUtil.variableStructType(dst);
// NOTE: For federated execution, dst.getParent() should always be contained
// by the currentFederate because an AST transformation removes connections
@@ -1018,7 +1018,7 @@ private static String deferredReactionMemory(
code.startScopedBlock(trigger.getParent());
var width = trigger.getWidth();
- var portStructType = CGenerator.variableStructType(trigger);
+ var portStructType = CUtil.variableStructType(trigger);
code.pr(
String.join(
@@ -1064,7 +1064,7 @@ private static String deferredAllocationForEffectsOnInputs(ReactorInstance react
code.pr("// A reaction writes to a multiport of a child. Allocate memory.");
portsHandled.add(effect);
code.startScopedBlock(effect.getParent());
- var portStructType = CGenerator.variableStructType(effect);
+ var portStructType = CUtil.variableStructType(effect);
var effectRef = CUtil.portRefNestedName(effect);
code.pr(
String.join(
diff --git a/core/src/main/java/org/lflang/generator/c/CUtil.java b/core/src/main/java/org/lflang/generator/c/CUtil.java
index dae1425342..c4a51910c3 100644
--- a/core/src/main/java/org/lflang/generator/c/CUtil.java
+++ b/core/src/main/java/org/lflang/generator/c/CUtil.java
@@ -518,6 +518,31 @@ public static String selfType(ReactorInstance instance) {
return selfType(instance.tpr);
}
+ /**
+ * Construct a unique type for the struct of the specified typed variable (port or action) of the
+ * specified reactor class. This is required to be the same as the type name returned by {@link
+ * #variableStructType(TriggerInstance)}.
+ */
+ public static String variableStructType(
+ Variable variable, TypeParameterizedReactor tpr, boolean userFacing) {
+ return (userFacing ? tpr.getName().toLowerCase() : CUtil.getName(tpr))
+ + "_"
+ + variable.getName()
+ + "_t";
+ }
+
+ /**
+ * Construct a unique type for the struct of the specified instance (port or action). This is
+ * required to be the same as the type name returned by {@link #variableStructType(Variable,
+ * TypeParameterizedReactor, boolean)}.
+ *
+ * @param portOrAction The port or action instance.
+ * @return The name of the self struct.
+ */
+ public static String variableStructType(TriggerInstance> portOrAction) {
+ return CUtil.getName(portOrAction.getParent().tpr) + "_" + portOrAction.getName() + "_t";
+ }
+
/**
* Return a reference to the trigger_t struct of the specified trigger instance (input port or
* action). This trigger_t struct is on the self struct.
diff --git a/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java b/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java
index e83ea122af..9ae14d2e68 100644
--- a/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java
+++ b/core/src/main/java/org/lflang/generator/c/CWatchdogGenerator.java
@@ -235,7 +235,7 @@ private static String generateInitializationForWatchdog(
*/
private static String generateActionVariablesInHandler(
Action action, TypeParameterizedReactor tpr) {
- String structType = CGenerator.variableStructType(action, tpr, false);
+ String structType = CUtil.variableStructType(action, tpr, false);
CodeBuilder builder = new CodeBuilder();
builder.pr(
"// Expose the action struct as a local variable whose name matches the action name.");
diff --git a/core/src/main/java/org/lflang/generator/python/PythonActionGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonActionGenerator.java
index 5270a4d69e..42e2c565d8 100644
--- a/core/src/main/java/org/lflang/generator/python/PythonActionGenerator.java
+++ b/core/src/main/java/org/lflang/generator/python/PythonActionGenerator.java
@@ -1,6 +1,6 @@
package org.lflang.generator.python;
-import org.lflang.generator.c.CGenerator;
+import org.lflang.generator.c.CUtil;
import org.lflang.generator.c.TypeParameterizedReactor;
import org.lflang.lf.Action;
@@ -11,7 +11,7 @@ public static String generateAliasTypeDef(
return "typedef "
+ genericActionType
+ " "
- + CGenerator.variableStructType(action, tpr, false)
+ + CUtil.variableStructType(action, tpr, false)
+ ";";
}
}
diff --git a/core/src/main/java/org/lflang/generator/python/PythonPortGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonPortGenerator.java
index aa1e9a21c3..01d8dec757 100644
--- a/core/src/main/java/org/lflang/generator/python/PythonPortGenerator.java
+++ b/core/src/main/java/org/lflang/generator/python/PythonPortGenerator.java
@@ -5,7 +5,7 @@
import java.util.List;
import org.lflang.ast.ASTUtils;
import org.lflang.generator.CodeBuilder;
-import org.lflang.generator.c.CGenerator;
+import org.lflang.generator.c.CUtil;
import org.lflang.generator.c.TypeParameterizedReactor;
import org.lflang.lf.Action;
import org.lflang.lf.Input;
@@ -199,11 +199,7 @@ public static String generatePythonListForContainedBank(
public static String generateAliasTypeDef(
TypeParameterizedReactor tpr, Port port, boolean isTokenType, String genericPortType) {
- return "typedef "
- + genericPortType
- + " "
- + CGenerator.variableStructType(port, tpr, false)
- + ";";
+ return "typedef " + genericPortType + " " + CUtil.variableStructType(port, tpr, false) + ";";
}
private static String generateConvertCPortToPy(String port) {
diff --git a/core/src/main/java/org/lflang/pretvm/ExecutionPhase.java b/core/src/main/java/org/lflang/pretvm/ExecutionPhase.java
new file mode 100644
index 0000000000..1d4fc07f8a
--- /dev/null
+++ b/core/src/main/java/org/lflang/pretvm/ExecutionPhase.java
@@ -0,0 +1,12 @@
+package org.lflang.pretvm;
+
+public enum ExecutionPhase {
+ PREAMBLE,
+ INIT,
+ PERIODIC,
+ EPILOGUE,
+ SYNC_BLOCK,
+ INIT_AND_PERIODIC,
+ SHUTDOWN_TIMEOUT,
+ SHUTDOWN_STARVATION,
+}
diff --git a/core/src/main/java/org/lflang/pretvm/InstructionGenerator.java b/core/src/main/java/org/lflang/pretvm/InstructionGenerator.java
new file mode 100644
index 0000000000..bc5996cd98
--- /dev/null
+++ b/core/src/main/java/org/lflang/pretvm/InstructionGenerator.java
@@ -0,0 +1,2297 @@
+package org.lflang.pretvm;
+
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.UUID;
+import org.lflang.FileConfig;
+import org.lflang.TimeValue;
+import org.lflang.ast.ASTUtils;
+import org.lflang.generator.CodeBuilder;
+import org.lflang.generator.PortInstance;
+import org.lflang.generator.ReactionInstance;
+import org.lflang.generator.ReactorInstance;
+import org.lflang.generator.RuntimeRange;
+import org.lflang.generator.SendRange;
+import org.lflang.generator.TriggerInstance;
+import org.lflang.generator.c.CUtil;
+import org.lflang.generator.c.TypeParameterizedReactor;
+import org.lflang.lf.Connection;
+import org.lflang.lf.Expression;
+import org.lflang.lf.Output;
+import org.lflang.pretvm.dag.Dag;
+import org.lflang.pretvm.dag.DagNode;
+import org.lflang.pretvm.dag.JobNode;
+import org.lflang.pretvm.dag.TimeNode;
+import org.lflang.pretvm.instruction.*;
+import org.lflang.pretvm.register.Register;
+import org.lflang.pretvm.register.ReturnAddr;
+import org.lflang.pretvm.register.RuntimeVar;
+import org.lflang.pretvm.register.WorkerRegister;
+import org.lflang.target.TargetConfig;
+import org.lflang.target.property.FastProperty;
+import org.lflang.target.property.TimeOutProperty;
+
+/**
+ * A generator that generates PRET VM programs from DAGs. It also acts as a linker that piece
+ * together multiple PRET VM object files.
+ */
+public class InstructionGenerator {
+
+ /** File configuration */
+ FileConfig fileConfig;
+
+ /** Target configuration */
+ TargetConfig targetConfig;
+
+ /** Main reactor instance */
+ protected ReactorInstance main;
+
+ /** A list of reactor instances in the program */
+ List reactors;
+
+ /** A list of reaction instances in the program */
+ List reactions;
+
+ /** A list of port instances in the program */
+ List ports;
+
+ /** Number of workers */
+ int workers;
+
+ /**
+ * A nested map that maps a source port to a C function name, which updates a priority queue
+ * holding tokens in a delayed connection. Each input can identify a unique connection because no
+ * more than one connection can feed into an input port.
+ */
+ private Map preConnectionHelperFunctionNameMap = new HashMap<>();
+
+ private Map postConnectionHelperFunctionNameMap = new HashMap<>();
+
+ /**
+ * A map that maps a trigger to a list of (BEQ) instructions where this trigger's presence is
+ * tested.
+ */
+ private Map> triggerPresenceTestMap = new HashMap<>();
+
+ /** PretVM registers */
+ private Registers registers;
+
+ /** Constructor */
+ public InstructionGenerator(
+ FileConfig fileConfig,
+ TargetConfig targetConfig,
+ int workers,
+ ReactorInstance main,
+ List reactors,
+ List reactions,
+ List ports,
+ Registers registers) {
+ this.fileConfig = fileConfig;
+ this.targetConfig = targetConfig;
+ this.workers = workers;
+ this.main = main;
+ this.reactors = reactors;
+ this.reactions = reactions;
+ this.ports = ports;
+ this.registers = registers;
+ }
+
+ /** Topologically sort the dag nodes and assign release values to DAG nodes for counting locks. */
+ public void assignReleaseIndices(Dag dagParitioned) {
+ // Initialize a reaction index array to keep track of the latest counting
+ // lock value for each worker.
+ Long[] releaseValues = new Long[workers];
+ Arrays.fill(releaseValues, 0L); // Initialize all elements to 0
+
+ // Iterate over a topologically sorted list of dag nodes.
+ for (DagNode current : dagParitioned.getTopologicalSort()) {
+ if (current instanceof JobNode currentJob) {
+ releaseValues[currentJob.getWorker()] += 1;
+ currentJob.setReleaseIndex(releaseValues[currentJob.getWorker()]);
+ }
+ }
+ }
+
+ /** Traverse the DAG from head to tail using Khan's algorithm (topological sort). */
+ public List> generateInstructions(
+ Dag dagParitioned, PartialSchedule partialSchedule) {
+ // Map from a reactor to its latest associated SYNC node.
+ // Use case 1: This is used to determine when ADVIs and DUs should be generated without
+ // duplicating them for each reaction node in the same reactor.
+ // Use case 2: Determine a relative time increment for ADVIs.
+ Map reactorToLastSeenSyncNodeMap = new HashMap<>();
+
+ // Map an output port to its last seen EXE instruction at the current
+ // tag. When we know for sure that no other reactions can modify a port, we then
+ // go back to the last seen reaction-invoking EXE that can modify this port and
+ // _insert_ a connection helper right after the last seen EXE in the schedule.
+ // All the key value pairs in this map are waiting to be handled,
+ // since all the output port values must be written to the buffers at the
+ // end of the tag.
+ Map portToUnhandledReactionExeMap = new HashMap<>();
+
+ // Map a reaction to its last seen invocation, which is a DagNode.
+ // If two invocations are mapped to different workers, a WU needs to
+ // be generated to prevent race condition.
+ // This map is used to check whether the WU needs to be generated.
+ Map reactionToLastSeenInvocationMap = new HashMap<>();
+
+ // Assign release values for the reaction nodes.
+ assignReleaseIndices(dagParitioned);
+
+ // Instructions for all workers
+ List> instructions = new ArrayList<>();
+ for (int i = 0; i < workers; i++) {
+ instructions.add(new ArrayList());
+ }
+
+ // Iterate over a topologically sorted list of dag nodes.
+ for (DagNode current : dagParitioned.getTopologicalSort()) {
+ // Get the upstream reaction nodes.
+ List upstreamReactionNodes =
+ dagParitioned.dagEdgesRev.getOrDefault(current, new HashMap<>()).keySet().stream()
+ .filter(n -> n instanceof JobNode)
+ .map(n -> (JobNode) n)
+ .toList();
+
+ if (current instanceof JobNode currentJob) {
+ // Find the worker assigned to the REACTION node,
+ // the reactor, and the reaction.
+ int worker = currentJob.getWorker();
+ ReactionInstance reaction = currentJob.getReaction();
+ ReactorInstance reactor = reaction.getParent();
+
+ // Current worker schedule
+ List currentSchedule = instructions.get(worker);
+
+ // Get the nearest upstream sync node.
+ TimeNode associatedSyncNode = currentJob.getAssociatedSyncNode();
+
+ // WU Case 1:
+ // If the reaction depends on upstream reactions owned by other
+ // workers, generate WU instructions to resolve the dependencies.
+ // FIXME: The current implementation generates multiple unnecessary WUs
+ // for simplicity. How to only generate WU when necessary?
+ for (JobNode n : upstreamReactionNodes) {
+ int upstreamOwner = n.getWorker();
+ if (upstreamOwner != worker) {
+ addInstructionForWorker(
+ instructions,
+ currentJob.getWorker(),
+ currentJob,
+ null,
+ new WU(registers.progressIndices.get(upstreamOwner), n.getReleaseIndex()));
+ }
+ }
+
+ // WU Case 2:
+ // FIXME: Is there a way to implement this using waitUntilDependencies
+ // in the Dag class?
+ // If the reaction has an _earlier_ invocation and is mapped to a
+ // _different_ worker, then a WU needs to be generated to prevent from
+ // processing of these two invocations of the same reaction in parallel.
+ // If they are processed in parallel, the shared logical time field in
+ // the reactor could get concurrent updates, resulting in incorrect
+ // execution.
+ // Most often, there is not an edge between these two nodes,
+ // making this a trickier case to handle.
+ // The strategy here is to use a variable to remember the last seen
+ // invocation of the same reaction instance.
+ JobNode lastSeen = reactionToLastSeenInvocationMap.get(reaction);
+ if (lastSeen != null && lastSeen.getWorker() != currentJob.getWorker()) {
+ addInstructionForWorker(
+ instructions,
+ currentJob.getWorker(),
+ currentJob,
+ null,
+ new WU(
+ registers.progressIndices.get(lastSeen.getWorker()), lastSeen.getReleaseIndex()));
+ if (currentJob
+ .getAssociatedSyncNode()
+ .getTime()
+ .isEarlierThan(lastSeen.getAssociatedSyncNode().getTime())) {
+ System.out.println(
+ "FATAL ERROR: The current node is earlier than the lastSeen node. This case should"
+ + " not be possible and this strategy needs to be revised.");
+ System.exit(1);
+ }
+ }
+ reactionToLastSeenInvocationMap.put(reaction, currentJob);
+
+ // WU Case 3:
+ // If the node has an upstream dependency based on connection, but the
+ // upstream is mapped to a different worker. Generate a WU.
+ List upstreamsFromConnection = dagParitioned.waitUntilDependencies.get(current);
+ if (upstreamsFromConnection != null && upstreamsFromConnection.size() > 0) {
+ for (JobNode us : upstreamsFromConnection) {
+ if (us.getWorker() != currentJob.getWorker()) {
+ addInstructionForWorker(
+ instructions,
+ currentJob.getWorker(),
+ currentJob,
+ null,
+ new WU(registers.progressIndices.get(us.getWorker()), us.getReleaseIndex()));
+ }
+ }
+ }
+
+ // When the new associated sync node _differs_ from the last associated sync
+ // node of the reactor, this means that the current node's reactor needs
+ // to advance to a new tag (i.e., reaches a new timestamp).
+ // The code should update the associated sync node
+ // in the reactorToLastSeenSyncNodeMap map. And if
+ // associatedSyncNode is not the head, generate time-advancement
+ // instructions (abbreviated as ADVI) and DU.
+ if (associatedSyncNode != reactorToLastSeenSyncNodeMap.get(reactor)) {
+ // Before updating reactorToLastSeenSyncNodeMap,
+ // compute a relative time increment to be used when generating an ADVI.
+ long relativeTimeIncrement;
+ if (reactorToLastSeenSyncNodeMap.get(reactor) != null) {
+ relativeTimeIncrement =
+ associatedSyncNode.getTime().toNanoSeconds()
+ - reactorToLastSeenSyncNodeMap.get(reactor).getTime().toNanoSeconds();
+ } else {
+ relativeTimeIncrement = associatedSyncNode.getTime().toNanoSeconds();
+ }
+
+ // Update the mapping.
+ reactorToLastSeenSyncNodeMap.put(reactor, associatedSyncNode);
+
+ // If the reaction depends on a single SYNC node,
+ // advance to the LOGICAL time of the SYNC node first,
+ // as well as delay until the PHYSICAL time indicated by the SYNC node.
+ // Skip if it is the start node since this is done in the sync block.
+ // FIXME: Here we have an implicit assumption "logical time is
+ // physical time." We need to find a way to relax this assumption.
+ // FIXME: One way to relax this is that "logical time is physical time
+ // only when executing real-time reactions, otherwise fast mode for
+ // non-real-time reactions."
+ if (associatedSyncNode != dagParitioned.start) {
+
+ // A pre-connection helper for an output port cannot be inserted
+ // until we are sure that all reactions that can modify this port
+ // at this tag has been invoked. At this point, since we have
+ // detected time advancement, this condition is satisfied.
+ // Iterate over all the ports of this reactor. We know at
+ // this point that the EXE instruction stored in
+ // portToUnhandledReactionExeMap is that the very last reaction
+ // invocation that can modify these ports. So we can insert
+ // pre-connection helpers after that reaction invocation.
+ for (PortInstance output : reactor.outputs) {
+ // Only generate for delayed connections.
+ if (outputToDelayedConnection(output)) {
+ EXE lastPortModifyingReactionExe = portToUnhandledReactionExeMap.get(output);
+ if (lastPortModifyingReactionExe != null) {
+ int exeWorker = lastPortModifyingReactionExe.getWorker();
+ int indexToInsert =
+ indexOfByReference(instructions.get(exeWorker), lastPortModifyingReactionExe)
+ + 1;
+ generatePreConnectionHelper(
+ output,
+ instructions,
+ exeWorker,
+ indexToInsert,
+ lastPortModifyingReactionExe.getDagNode());
+ // Remove the entry since this port is handled.
+ portToUnhandledReactionExeMap.remove(output);
+ }
+ }
+ }
+
+ // Generate an ADVI instruction using a relative time increment.
+ // (instead of absolute). Relative style of coding promotes code reuse.
+ // FIXME: Factor out in a separate function.
+ String reactorTime = getFromEnvReactorTimePointer(main, reactor);
+ Register reactorTimeReg = registers.getRuntimeVar(reactorTime);
+ var timeAdvInsts =
+ generateTimeAdvancementInstructions(reactor, reactorTimeReg, relativeTimeIncrement);
+ addInstructionSequenceForWorker(instructions, worker, current, null, timeAdvInsts);
+
+ // Generate a DU using a relative time increment.
+ // There are two cases for NOT generating a DU within a
+ // hyperperiod: 1. if fast is on, 2. if dash is on and the parent
+ // reactor is not realtime.
+ // Generate a DU instruction if neither case holds.
+ if (!(targetConfig.get(FastProperty.INSTANCE))) {
+ // reactorTimeReg is already updated by time advancement instructions.
+ // Just delay until its recently updated value.
+ addInstructionForWorker(
+ instructions, worker, current, null, new DU(reactorTimeReg, 0L));
+ }
+ }
+ }
+
+ // Create an EXE instruction that invokes the reaction.
+ String reactorPointer = getFromEnvReactorPointer(main, reactor);
+ String reactorTimePointer = getFromEnvReactorTimePointer(main, reactor);
+ String reactionPointer = getFromEnvReactionFunctionPointer(main, reaction);
+ EXE exeReaction =
+ new EXE(
+ registers.getRuntimeVar(reactionPointer),
+ registers.getRuntimeVar(reactorPointer),
+ reaction.index);
+ exeReaction.addLabel(
+ new Label(
+ "EXECUTE_" + reaction.getFullNameWithJoiner("_") + "_" + generateShortUUID()));
+
+ ////////////////////////////////////////////////////////////////
+ // Generate instructions for deadline handling.
+ // The general scheme for deadline handling is:
+ //
+ // Line x-3: ADDI temp0_reg, tag.time, reaction_deadline
+ // Line x-2: EXE update_temp1_to_current_time() // temp1_reg := lf_time_physical()
+ // Line x-1: BLT temp0_reg, temp1_reg, x+1
+ // Line x : EXE reaction_body_function
+ // Line x+1: JAL x+3 // Jump pass the deadline handler if reaction body is executed.
+ // Line x+2: EXE deadline_handler_function
+ //
+ // Here we need to create the ADDI, EXE, and BLT instructions involved.
+ ////////////////////////////////////////////////////////////////
+ // Declare a sequence of instructions related to invoking the
+ // reaction body and handling deadline violations.
+ List reactionInvokingSequence = new ArrayList<>();
+ if (reaction.declaredDeadline != null) {
+ // Create ADDI for storing the physical time after which the
+ // deadline is considered violated,
+ // basically, current tag + deadline value.
+ Instruction addiDeadlineTime =
+ new ADDI(
+ registers.temp0.get(worker),
+ registers.getRuntimeVar(reactorTimePointer),
+ reaction.declaredDeadline.maxDelay.toNanoSeconds());
+ addiDeadlineTime.addLabel(
+ new Label(
+ "CALCULATE_DEADLINE_VIOLATION_TIME_FOR_"
+ + reaction.getFullNameWithJoiner("_")
+ + "_"
+ + generateShortUUID()));
+
+ // Create EXE for updating the time register.
+ var exeUpdateTimeRegister =
+ new EXE(
+ registers.getRuntimeVar("update_temp1_to_current_time"),
+ registers.temp1.get(worker),
+ null);
+
+ // Create deadline handling EXE
+ String deadlineHandlerPointer =
+ getFromEnvReactionDeadlineHandlerFunctionPointer(main, reaction);
+ Instruction exeDeadlineHandler =
+ new EXE(
+ registers.getRuntimeVar(deadlineHandlerPointer),
+ registers.getRuntimeVar(reactorPointer),
+ reaction.index);
+ exeDeadlineHandler.addLabel(
+ new Label(
+ "HANDLE_DEADLINE_VIOLATION_OF_"
+ + reaction.getFullNameWithJoiner("_")
+ + "_"
+ + generateShortUUID()));
+
+ // Create BLT for checking deadline violation.
+ var bltDeadlineViolation =
+ new BLT(
+ registers.temp0.get(worker),
+ registers.temp1.get(worker),
+ exeDeadlineHandler.getLabel());
+
+ // Create JAL for jumping pass the deadline handler if the
+ // deadline is not violated.
+ var jalPassHandler = new JAL(registers.zero, exeDeadlineHandler.getLabel(), 1L);
+
+ // Add the reaction-invoking EXE and deadline handling
+ // instructions to the schedule in the right order.
+ reactionInvokingSequence.add(addiDeadlineTime);
+ reactionInvokingSequence.add(exeUpdateTimeRegister);
+ reactionInvokingSequence.add(bltDeadlineViolation);
+ reactionInvokingSequence.add(exeReaction);
+ reactionInvokingSequence.add(jalPassHandler);
+ reactionInvokingSequence.add(exeDeadlineHandler);
+ } else {
+ // If the reaction does not have a deadline, just add the EXE
+ // running the reaction body.
+ reactionInvokingSequence.add(exeReaction);
+ }
+
+ // It is important that the beginning and the end of the
+ // sequence has labels, so that the trigger checking BEQ
+ // instructions can jump to the right place.
+ if (reactionInvokingSequence.get(0).getLabel() == null
+ || reactionInvokingSequence.get(reactionInvokingSequence.size() - 1) == null) {
+ throw new RuntimeException(
+ "The reaction invoking instruction sequence either misses a label at the first"
+ + " instruction or at the last instruction, or both.");
+ }
+
+ // Create BEQ instructions for checking ports.
+ // Check if the reaction has input port ports or not. If so,
+ // we need guards implemented using BEQ.
+ boolean hasGuards = false;
+ for (var trigger : reaction.triggers) {
+ if (trigger instanceof PortInstance port && port.isInput()) {
+ hasGuards = true;
+ Register reg1;
+ Register reg2;
+ // If connection has delay, check the connection buffer to see if
+ // the earliest event matches the reactor's current logical time.
+ if (inputFromDelayedConnection(port)) {
+ String pqueueHeadTime = getFromEnvPqueueHeadTimePointer(main, port);
+ reg1 = registers.getRuntimeVar(pqueueHeadTime); // RUNTIME_STRUCT
+ reg2 = registers.getRuntimeVar(reactorTimePointer); // RUNTIME_STRUCT
+ }
+ // Otherwise, if the connection has zero delay, check for the presence of the
+ // downstream port.
+ else {
+ String isPresentField =
+ "&" + getTriggerIsPresentFromEnv(main, trigger); // The is_present field
+ reg1 = registers.getRuntimeVar(isPresentField); // RUNTIME_STRUCT
+ reg2 = registers.one; // Checking if is_present == 1
+ }
+ Instruction reactionSequenceFront = reactionInvokingSequence.get(0);
+ Instruction beq = new BEQ(reg1, reg2, reactionSequenceFront.getLabel());
+ beq.addLabel(
+ new Label(
+ "TEST_TRIGGER_" + port.getFullNameWithJoiner("_") + "_" + generateShortUUID()));
+ addInstructionForWorker(instructions, currentJob.getWorker(), current, null, beq);
+ // Update triggerPresenceTestMap.
+ if (triggerPresenceTestMap.get(port) == null)
+ triggerPresenceTestMap.put(port, new LinkedList<>());
+ triggerPresenceTestMap.get(port).add(beq);
+ }
+ }
+
+ // If none of the guards are activated, jump to one line after the
+ // reaction-invoking instruction sequence.
+ if (hasGuards)
+ addInstructionForWorker(
+ instructions,
+ worker,
+ current,
+ null,
+ new JAL(
+ registers.zero,
+ reactionInvokingSequence.get(reactionInvokingSequence.size() - 1).getLabel(),
+ 1L));
+
+ // Add the reaction-invoking sequence to the instructions.
+ addInstructionSequenceForWorker(
+ instructions, currentJob.getWorker(), current, null, reactionInvokingSequence);
+
+ // Add the post-connection helper to the schedule, in case this reaction
+ // is triggered by an input port, which is connected to a connection
+ // buffer.
+ // Reaction invocations can be skipped,
+ // and we don't want the connection management to be skipped.
+ // FIXME: This does not seem to support the case when an input port
+ // ports multiple reactions. We only want to add a post connection
+ // helper after the last reaction triggered by this port.
+ int indexToInsert = indexOfByReference(currentSchedule, exeReaction) + 1;
+ generatePostConnectionHelpers(
+ reaction, instructions, worker, indexToInsert, exeReaction.getDagNode());
+
+ // Add this reaction invoking EXE to the output-port-to-EXE map,
+ // so that we know when to insert pre-connection helpers.
+ for (TriggerInstance effect : reaction.effects) {
+ if (effect instanceof PortInstance output) {
+ portToUnhandledReactionExeMap.put(output, exeReaction);
+ }
+ }
+
+ // Increment the progress index of the worker.
+ // IMPORTANT: This ADDI has to be last because executing it releases
+ // downstream workers. If this ADDI is executed before
+ // connection management, then there is a race condition between
+ // upstream pushing events into connection buffers and downstream
+ // reading connection buffers.
+ // Instantiate an ADDI to be executed after EXE, releasing the counting locks.
+ var addi =
+ new ADDI(
+ registers.progressIndices.get(currentJob.getWorker()),
+ registers.progressIndices.get(currentJob.getWorker()),
+ 1L);
+ addInstructionForWorker(instructions, worker, current, null, addi);
+
+ } else if (current instanceof TimeNode currentTime) {
+ if (current == dagParitioned.end) {
+ // At this point, we know for sure that all reactors are done with
+ // its current tag and are ready to advance time. We now insert a
+ // connection helper after each port's last reaction's ADDI
+ // (indicating the reaction is handled).
+ // FIXME: This _after_ is sus. Should be before!
+ for (var entry : portToUnhandledReactionExeMap.entrySet()) {
+ PortInstance output = entry.getKey();
+ // Only generate for delayed connections.
+ if (outputToDelayedConnection(output)) {
+ Instruction lastReactionExe = entry.getValue();
+ int exeWorker = lastReactionExe.getWorker();
+ int indexToInsert =
+ indexOfByReference(instructions.get(exeWorker), lastReactionExe) + 1;
+ generatePreConnectionHelper(
+ output, instructions, exeWorker, indexToInsert, lastReactionExe.getDagNode());
+ }
+ }
+ portToUnhandledReactionExeMap.clear();
+
+ // When the timeStep = TimeValue.MAX_VALUE in a SYNC node,
+ // this means that the DAG is acyclic and can end without
+ // real-time constraints, hence we do not genereate DU and ADDI.
+ if (currentTime.getTime() != TimeValue.MAX_VALUE) {
+ for (int worker = 0; worker < workers; worker++) {
+ // [Only Worker 0] Update the time offset increment register.
+ if (worker == 0) {
+ addInstructionForWorker(
+ instructions,
+ worker,
+ current,
+ null,
+ new ADDI(
+ registers.offsetInc,
+ registers.zero,
+ currentTime.getTime().toNanoSeconds()));
+ }
+ // Let all workers go to SYNC_BLOCK after finishing PREAMBLE.
+ addInstructionForWorker(
+ instructions,
+ worker,
+ current,
+ null,
+ new JAL(
+ registers.returnAddrs.get(worker),
+ Label.getExecutionPhaseLabel(ExecutionPhase.SYNC_BLOCK)));
+ // Add a DU instruction if the fast mode is off.
+ // Turning on the dash mode does not affect this DU. The
+ // hyperperiod is still real-time.
+ // ALTERNATIVE DESIGN: remove the DU here and let the start node,
+ // instead of the end node, handle DU. This potentially allows
+ // breaking the hyperperiod boundary.
+ //
+ // At this point, the global offset register has been
+ // updated in SYNC_BLOCK.
+ //
+ // We want to place this DU after the SYNC_BLOCK so that
+ // workers enters a new hyperperiod with almost zero lag.
+ // If this DU is placed before, then the SYNC_BLOCK will
+ // contribute the lag at the beginning of the hyperperiod.
+ if (!targetConfig.get(FastProperty.INSTANCE))
+ addInstructionForWorker(
+ instructions, worker, current, null, new DU(registers.offset, 0L));
+ }
+ }
+ }
+ }
+ }
+ // Add a label to the first instruction using the exploration phase
+ // (INIT, PERIODIC, SHUTDOWN_TIMEOUT, etc.).
+ for (int w = 0; w < workers; w++) {
+ // First, check if there is any instruction generated.
+ // A worker without any work assignment has an empty schedule.
+ // In this case, generate a dummy instruction: adding zero to a
+ // temp register.
+ // Without this dummy instruction, currently there will be
+ // compilation errors due to not having a place to put phase labels.
+ if (instructions.get(w).size() == 0) {
+ addInstructionForWorker(
+ instructions,
+ w,
+ dagParitioned.end,
+ null,
+ new ADD(registers.temp0.get(w), registers.temp0.get(w), registers.zero));
+ }
+ // Then assign a label to the first instruction.
+ instructions.get(w).get(0).addLabel(Label.getExecutionPhaseLabel(partialSchedule.getPhase()));
+ }
+
+ return instructions;
+ }
+
+ /**
+ * Generate a sequence of instructions for advancing a reactor's logical time. First, the
+ * reactor's local time register needs to be incremented. Then, the `is_present` fields of the
+ * reactor's output ports need to be set to false. This function replaces two previously
+ * specialized instructions: ADV & ADVI.
+ *
+ *
This function is designed to have the same signature as ADV and ADVI.
+ *
+ * @param reactor The reactor instance to advance time and clear output ports for
+ * @param baseTimeReg The base time this reactor should advance to (either the reactor's current
+ * time register, or the time offset for the next hyperperiod)
+ * @param relativeTimeIncrement The time increment added on top of baseTimeReg
+ * @return A list of instructions for advancing reactor's local time
+ */
+ private List generateTimeAdvancementInstructions(
+ ReactorInstance reactor, Register baseTimeReg, long relativeTimeIncrement) {
+ List timeAdvInsts = new ArrayList<>();
+
+ // Increment the reactor local time.
+ String reactorTimePointer = getFromEnvReactorTimePointer(main, reactor);
+ Register reactorTimeReg = registers.getRuntimeVar(reactorTimePointer);
+ var addiIncrementTime = new ADDI(reactorTimeReg, baseTimeReg, relativeTimeIncrement);
+ var uuid = generateShortUUID();
+ addiIncrementTime.addLabel(
+ new Label("ADVANCE_TAG_FOR_" + reactor.getFullNameWithJoiner("_") + "_" + uuid));
+ timeAdvInsts.add(addiIncrementTime);
+
+ // Reset the is_present fields of all output ports of this reactor.
+ var outputs = ASTUtils.allOutputs(reactor.tpr.reactor());
+ for (int i = 0; i < outputs.size(); i++) {
+ Output output = outputs.get(i);
+ String selfType = CUtil.selfType(reactor.tpr);
+ String portName = output.getName();
+ String isPresentPointer = getPortIsPresentFieldPointer(main, reactor, selfType, portName);
+ Register portIsPresentReg = registers.getRuntimeVar(isPresentPointer);
+ var addiResetIsPresent = new ADD(portIsPresentReg, registers.zero, registers.zero);
+ timeAdvInsts.add(addiResetIsPresent);
+ }
+ return timeAdvInsts;
+ }
+
+ /**
+ * Helper function for adding an instruction to a worker schedule. This function is not meant to
+ * be called in the code generation logic above, because a node needs to be associated with each
+ * instruction added.
+ *
+ * @param instructions The instructions under generation for a particular phase
+ * @param worker The worker who owns the instruction
+ * @param inst The instruction to be added
+ * @param index The index at which to insert the instruction. If the index is null, append the
+ * instruction at the end. Otherwise, append it at the specific index.
+ */
+ private void _addInstructionForWorker(
+ List> instructions, int worker, Integer index, Instruction inst) {
+ if (index == null) {
+ // Add instruction to the instruction list.
+ instructions.get(worker).add(inst);
+ } else {
+ // Insert instruction to the instruction list at the specified index.
+ instructions.get(worker).add(index, inst);
+ }
+ // Remember the worker at the instruction level.
+ inst.setWorker(worker);
+ }
+
+ /**
+ * Helper function for adding an instruction to a worker schedule
+ *
+ * @param instructions The instructions under generation for a particular phase
+ * @param worker The worker who owns the instruction
+ * @param node The DAG node for which this instruction is added
+ * @param index The index at which to insert the instruction. If the index is null, append the
+ * instruction at the end. Otherwise, append it at the specific index.
+ * @param inst The instruction to be added
+ */
+ private void addInstructionForWorker(
+ List> instructions,
+ int worker,
+ DagNode node,
+ Integer index,
+ Instruction inst) {
+ // Add an instruction to the instruction list.
+ _addInstructionForWorker(instructions, worker, index, inst);
+ // Store the reference to the DAG node in the instruction.
+ inst.addDagNode(node);
+ }
+
+ /**
+ * Helper function for adding an instruction to a worker schedule
+ *
+ * @param instructions The instructions under generation for a particular phase
+ * @param worker The worker who owns the instruction
+ * @param nodes A list of DAG nodes for which this instruction is added
+ * @param index The index at which to insert the instruction. If the index is null, append the
+ * instruction at the end. Otherwise, append it at the specific index.
+ * @param inst The instruction to be added
+ */
+ private void addInstructionForWorker(
+ List> instructions,
+ int worker,
+ List nodes,
+ Integer index,
+ Instruction inst) {
+ // Add an instruction to the instruction list.
+ _addInstructionForWorker(instructions, worker, index, inst);
+ for (DagNode node : nodes) {
+ // Store the reference to the DAG node in the instruction.
+ inst.addDagNode(node);
+ }
+ }
+
+ /**
+ * Helper function for adding a sequence of instructions to a worker schedule
+ *
+ * @param instructions The instructions under generation for a particular phase
+ * @param worker The worker who owns the instruction
+ * @param node The DAG node for which this instruction is added
+ * @param index The index at which to insert the instruction. If the index is null, append the
+ * instruction at the end. Otherwise, append it at the specific index.
+ * @param instList The list of instructions to be added
+ */
+ private void addInstructionSequenceForWorker(
+ List> instructions,
+ int worker,
+ DagNode node,
+ Integer index,
+ List instList) {
+ // Add instructions to the instruction list.
+ for (int i = 0; i < instList.size(); i++) {
+ Instruction inst = instList.get(i);
+ _addInstructionForWorker(instructions, worker, index, inst);
+ // Store the reference to the DAG node in the instruction.
+ inst.addDagNode(node);
+ }
+ }
+
+ /**
+ * Helper function for adding a sequence of instructions to a worker schedule
+ *
+ * @param instructions The instructions under generation for a particular phase
+ * @param worker The worker who owns the instruction
+ * @param nodes A list of DAG nodes for which this instruction is added
+ * @param index The index at which to insert the instruction. If the index is null, append the
+ * instruction at the end. Otherwise, append it at the specific index.
+ * @param instList The list of instructions to be added
+ */
+ private void addInstructionSequenceForWorker(
+ List> instructions,
+ int worker,
+ List nodes,
+ Integer index,
+ List instList) {
+ // Add instructions to the instruction list.
+ for (int i = 0; i < instList.size(); i++) {
+ Instruction inst = instList.get(i);
+ _addInstructionForWorker(instructions, worker, index, inst);
+ // Store the reference to the DAG node in the instruction.
+ for (DagNode node : nodes) {
+ // Store the reference to the DAG node in the instruction.
+ inst.addDagNode(node);
+ }
+ }
+ }
+
+ /** Generate C code from the instructions list. */
+ public void generateCode(List> instructions) {
+
+ // Instantiate a code builder.
+ Path srcgen = fileConfig.getSrcGenPath();
+ Path file = srcgen.resolve("static_schedule.c");
+ CodeBuilder code = new CodeBuilder();
+
+ // Generate a block comment.
+ code.pr(
+ String.join(
+ "\n",
+ "/**",
+ " * An auto-generated schedule file for the STATIC scheduler.",
+ " * ",
+ " * reactor array:",
+ " * " + this.reactors,
+ " * ",
+ " * reaction array:",
+ " * " + this.reactions,
+ " */"));
+
+ // Header files
+ code.pr(
+ String.join(
+ "\n",
+ "#include ",
+ "#include // size_t",
+ "#include // ULLONG_MAX",
+ "#include \"core/environment.h\"",
+ "#include \"core/threaded/scheduler_instance.h\"",
+ "#include \"core/threaded/scheduler_static_functions.h\"",
+ "#include " + "\"" + fileConfig.name + ".h" + "\""));
+
+ // Include reactor header files.
+ List tprs = this.reactors.stream().map(it -> it.tpr).toList();
+ Set headerNames = new HashSet<>();
+ for (var tpr : tprs) headerNames.add(CUtil.getName(tpr));
+ for (var name : headerNames) {
+ code.pr("#include " + "\"" + name + ".h" + "\"");
+ }
+
+ // Generate label macros.
+ for (int workerId = 0; workerId < instructions.size(); workerId++) {
+ List schedule = instructions.get(workerId);
+ for (int lineNumber = 0; lineNumber < schedule.size(); lineNumber++) {
+ Instruction inst = schedule.get(lineNumber);
+ // If the instruction already has a label, print it.
+ if (inst.hasLabel()) {
+ List