diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index f762f01902..843e147128 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit f762f01902745dff40b9170dce273d29b6df5212 +Subproject commit 843e147128315ffa5216fffd6cba36396c3cd084 diff --git a/org.lflang/src/lib/py/reactor-c-py b/org.lflang/src/lib/py/reactor-c-py index 9f82617589..80c24aa2e5 160000 --- a/org.lflang/src/lib/py/reactor-c-py +++ b/org.lflang/src/lib/py/reactor-c-py @@ -1 +1 @@ -Subproject commit 9f82617589994ce5f71c88a091b32f5a4ac431f0 +Subproject commit 80c24aa2e5bc814434893e9b6e4b183b4827c36a diff --git a/org.lflang/src/org/lflang/ASTUtils.java b/org.lflang/src/org/lflang/ASTUtils.java index 2cb9dad397..f8bd78de25 100644 --- a/org.lflang/src/org/lflang/ASTUtils.java +++ b/org.lflang/src/org/lflang/ASTUtils.java @@ -32,7 +32,6 @@ import java.util.LinkedHashSet; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -61,8 +60,6 @@ import org.lflang.generator.ReactorInstance; import org.lflang.lf.Action; import org.lflang.lf.Assignment; -import org.lflang.lf.AttrParm; -import org.lflang.lf.Attribute; import org.lflang.lf.Code; import org.lflang.lf.Connection; import org.lflang.lf.Element; @@ -92,6 +89,7 @@ import org.lflang.lf.Type; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.util.StringUtil; @@ -263,7 +261,6 @@ public static Target getTarget(EObject object) { /** * Add a new target property to the given resource. - * * This also creates a config object if the resource does not yey have one. * * @param resource The resource to modify @@ -292,11 +289,11 @@ public static boolean hasMultipleConnections(Connection connection) { if (connection.getLeftPorts().size() > 1 || connection.getRightPorts().size() > 1) { return true; } - VarRef leftPort = connection.getLeftPorts().get(0); + VarRef leftPort = connection.getLeftPorts().get(0); VarRef rightPort = connection.getRightPorts().get(0); - Instantiation leftContainer = leftPort.getContainer(); + Instantiation leftContainer = leftPort.getContainer(); Instantiation rightContainer = rightPort.getContainer(); - Port leftPortAsPort = (Port) leftPort.getVariable(); + Port leftPortAsPort = (Port) leftPort.getVariable(); Port rightPortAsPort = (Port) rightPort.getVariable(); return leftPortAsPort.getWidthSpec() != null || leftContainer != null && leftContainer.getWidthSpec() != null @@ -450,6 +447,16 @@ public static List allReactions(Reactor definition) { return ASTUtils.collectElements(definition, featurePackage.getReactor_Reactions()); } + /** + * Given a reactor class, return a list of all its watchdogs. + * + * @param definition Reactor class definition + * @return List + */ + public static List allWatchdogs(Reactor definition) { + return ASTUtils.collectElements(definition, featurePackage.getReactor_Watchdogs()); + } + /** * Given a reactor class, return a list of all its state variables, * which includes state variables of base classes that it extends. @@ -485,7 +492,7 @@ public static List allModes(Reactor definition) { public static List recursiveChildren(ReactorInstance r) { List ret = new ArrayList<>(); ret.add(r.reactorDefinition); - for (var child: r.children) { + for (var child : r.children) { ret.addAll(recursiveChildren(child)); } return ret; @@ -790,7 +797,6 @@ private static Element toElement(String str, boolean addQuotes) { Element e = LfFactory.eINSTANCE.createElement(); e.setLiteral(strToReturn); return e; - } /** @@ -937,12 +943,12 @@ public static boolean isFloat(String literal) { return true; } - /** + /** * Report whether the given code is an integer number or not. * @param code AST node to inspect. * @return True if the given code is an integer, false otherwise. */ - public static boolean isInteger(Code code) { + public static boolean isInteger(Code code) { return isInteger(toText(code)); } @@ -966,13 +972,13 @@ public static boolean isInteger(Expression expr) { * @return True if the argument denotes a valid time, false otherwise. */ public static boolean isValidTime(Expression expr) { - if (expr instanceof ParameterReference) { - return isOfTimeType(((ParameterReference)expr).getParameter()); - } else if (expr instanceof Time) { - return isValidTime((Time) expr); - } else if (expr instanceof Literal) { - return isZero(((Literal) expr).getLiteral()); - } + if (expr instanceof ParameterReference) { + return isOfTimeType(((ParameterReference) expr).getParameter()); + } else if (expr instanceof Time) { + return isValidTime((Time) expr); + } else if (expr instanceof Literal) { + return isZero(((Literal) expr).getLiteral()); + } return false; } @@ -1021,7 +1027,7 @@ public static boolean isListInitializer(Initializer init) { * can be inferred: "time" and "timeList". Return the * "undefined" type if neither can be inferred. * - * @param type Explicit type declared on the declaration + * @param type Explicit type declared on the declaration * @param init The initializer expression * @return The inferred type, or "undefined" if none could be inferred. */ @@ -1114,8 +1120,6 @@ public static InferredType getInferredType(Port p) { return getInferredType(p.getType(), null); } - - /** * If the given string can be recognized as a floating-point number that has a leading decimal point, * prepend the string with a zero and return it. Otherwise, return the original string. diff --git a/org.lflang/src/org/lflang/LinguaFranca.xtext b/org.lflang/src/org/lflang/LinguaFranca.xtext index 1ce6656e96..4e1645e9db 100644 --- a/org.lflang/src/org/lflang/LinguaFranca.xtext +++ b/org.lflang/src/org/lflang/LinguaFranca.xtext @@ -87,6 +87,7 @@ Reactor: | (outputs+=Output) | (timers+=Timer) | (actions+=Action) + | (watchdogs+=Watchdog) | (instantiations+=Instantiation) | (connections+=Connection) | (reactions+=Reaction) @@ -174,6 +175,7 @@ Mode: (stateVars+=StateVar) | (timers+=Timer) | (actions+=Action) | + (watchdogs+=Watchdog) | (instantiations+=Instantiation) | (connections+=Connection) | (reactions+=Reaction) @@ -213,6 +215,11 @@ BuiltinTriggerRef: Deadline: 'deadline' '(' delay=Expression ')' code=Code; +Watchdog: + 'watchdog' name=ID '(' timeout=Expression ')' + ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + code=Code; + STP: 'STP' '(' value=Expression ')' code=Code; @@ -273,7 +280,7 @@ TypedVariable: ; Variable: - TypedVariable | Timer | Mode; + TypedVariable | Timer | Mode | Watchdog; VarRef: (variable=[Variable] | container=[Instantiation] '.' variable=[Variable] @@ -457,7 +464,7 @@ SignedFloat: // Just escaping with \ is not a good idea because then every \ has to be escaped \\. // Perhaps the string EQUALS_BRACE could become '=}'? Code: - //{Code} '{=' (tokens+=Token)* '=}' + // {Code} '{=' (tokens+=Token)* '=}' {Code} '{=' body=Body '=}' ; @@ -504,7 +511,8 @@ Token: 'mutable' | 'input' | 'output' | 'timer' | 'action' | 'reaction' | 'startup' | 'shutdown' | 'after' | 'deadline' | 'mutation' | 'preamble' | 'new' | 'federated' | 'at' | 'as' | 'from' | 'widthof' | 'const' | 'method' | - 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'named' | + 'interleaved' | 'mode' | 'initial' | 'reset' | 'history' | 'watchdog' | 'named' | + // Other terminals NEGINT | TRUE | FALSE | // Action origins diff --git a/org.lflang/src/org/lflang/ast/IsEqual.java b/org.lflang/src/org/lflang/ast/IsEqual.java index 7f2dd072ea..af0274ea2a 100644 --- a/org.lflang/src/org/lflang/ast/IsEqual.java +++ b/org.lflang/src/org/lflang/ast/IsEqual.java @@ -60,6 +60,7 @@ import org.lflang.lf.TypedVariable; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.lf.util.LfSwitch; @@ -319,7 +320,6 @@ public Boolean caseSTP(STP object) { .conclusion; } - @Override public Boolean casePreamble(Preamble object) { return new ComparisonMachine<>(object, Preamble.class) @@ -496,6 +496,16 @@ public Boolean caseArraySpec(ArraySpec object) { .conclusion; } + @Override + public Boolean caseWatchdog(Watchdog object) { + return new ComparisonMachine<>(object, Watchdog.class) + .equalAsObjects(Watchdog::getName) + .equivalent(Watchdog::getTimeout) + .listsEquivalent(Watchdog::getEffects) + .equivalent(Watchdog::getCode) + .conclusion; + } + @Override public Boolean caseWidthSpec(WidthSpec object) { return new ComparisonMachine<>(object, WidthSpec.class) diff --git a/org.lflang/src/org/lflang/ast/ToLf.java b/org.lflang/src/org/lflang/ast/ToLf.java index 52a0d302f2..ce1cdca9fe 100644 --- a/org.lflang/src/org/lflang/ast/ToLf.java +++ b/org.lflang/src/org/lflang/ast/ToLf.java @@ -76,6 +76,7 @@ import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.Visibility; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; import org.lflang.lf.WidthTerm; import org.lflang.lf.util.LfSwitch; @@ -393,6 +394,7 @@ public MalleableString caseReactor(Reactor object) { // | (outputs+=Output) // | (timers+=Timer) // | (actions+=Action) + // | (watchdogs+=Watchdog) // | (instantiations+=Instantiation) // | (connections+=Connection) // | (reactions+=Reaction) @@ -410,6 +412,7 @@ public MalleableString caseReactor(Reactor object) { object.getOutputs(), object.getTimers(), object.getActions(), + object.getWatchdogs(), object.getInstantiations(), object.getConnections(), object.getStateVars()), @@ -656,6 +659,40 @@ public MalleableString caseDeadline(Deadline object) { return handler(object, "deadline", Deadline::getDelay, Deadline::getCode); } + @Override + public MalleableString caseWatchdog(Watchdog object) { + // 'watchdog' name=ID '(' timeout=Expression ')' + // ('->' effects+=VarRefOrModeTransition (',' effects+=VarRefOrModeTransition)*)? + // code=Code; + + Builder msb = new Builder(); + msb.append("watchdog "); + msb.append(object.getName()); + msb.append(list(true, object.getTimeout())); + + if (!object.getEffects().isEmpty()) { + List allModes = ASTUtils.allModes(ASTUtils.getEnclosingReactor(object)); + msb.append(" -> ", " ->\n") + .append( + object.getEffects().stream() + .map( + varRef -> + (allModes.stream() + .anyMatch( + m -> m.getName().equals(varRef.getVariable().getName()))) + ? new Builder() + .append(varRef.getTransition()) + .append("(") + .append(doSwitch(varRef)) + .append(")") + .get() + : doSwitch(varRef)) + .collect(new Joiner(", "))); + } + msb.append(" ").append(doSwitch(object.getCode())); + return msb.get(); + } + @Override public MalleableString caseSTP(STP object) { // 'STP' '(' value=Expression ')' code=Code @@ -844,9 +881,8 @@ public MalleableString caseInitializer(Initializer object) { } /** - * Return true if the initializer should be output with an equals initializer. - * Old-style assignments with parentheses are also output that - * way to help with the transition. + * Return true if the initializer should be output with an equals initializer. Old-style + * assignments with parentheses are also output that way to help with the transition. */ private boolean shouldOutputAsAssignment(Initializer init) { return init.isAssign() @@ -882,7 +918,6 @@ private MalleableString initializer(Initializer init) { return list(", ", prefix, suffix, false, false, init.getExprs()); } - @Override public MalleableString caseParameter(Parameter object) { // name=ID (':' (type=Type))? diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.java b/org.lflang/src/org/lflang/generator/GeneratorBase.java index 461f571333..16eb901676 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.java @@ -1,16 +1,16 @@ /************* * Copyright (c) 2019-2020, 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 @@ -166,6 +166,9 @@ public abstract class GeneratorBase extends AbstractLFValidator { */ public boolean hasDeadlines = false; + /** Indicates whether the program has any watchdogs. This is used to check for support. */ + public boolean hasWatchdogs = false; + // ////////////////////////////////////////// // // Private fields. @@ -294,6 +297,10 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Check for existence and support of modes hasModalReactors = IterableExtensions.exists(reactors, it -> !it.getModes().isEmpty()); checkModalReactorSupport(false); + + // Check for the existence and support of watchdogs + hasWatchdogs = IterableExtensions.exists(reactors, it -> !it.getWatchdogs().isEmpty()); + checkWatchdogSupport(targetConfig.threading && getTarget() == Target.C); additionalPostProcessingForModes(); } @@ -429,13 +436,27 @@ public int getReactionBankIndex(Reaction reaction) { protected void checkModalReactorSupport(boolean isSupported) { if (hasModalReactors && !isSupported) { errorReporter.reportError("The currently selected code generation or " + - "target configuration does not support modal reactors!"); + "target configuration does not support modal reactors!"); + } + } + + /** + * Check whether watchdogs are present and are supported. + * + * @param isSupported indicates whether or not this is a supported target and whether or not it + * is + * a threaded runtime. + */ + protected void checkWatchdogSupport(boolean isSupported) { + if (hasWatchdogs && !isSupported) { + errorReporter.reportError( + "Watchdogs are currently only supported for threaded programs in the C target."); } } /** - * Finds and transforms connections into forwarding reactions iff the connections have the same destination as other - * connections or reaction in mutually exclusive modes. + * Finds and transforms connections into forwarding reactions iff the connections have the same + * destination as other connections or reaction in mutually exclusive modes. */ private void transformConflictingConnectionsInModalReactors() { for (LFResource r : resources) { @@ -471,6 +492,7 @@ private void transformConflictingConnectionsInModalReactors() { } } } + /** * Return target code for forwarding reactions iff the connections have the * same destination as other connections or reaction in mutually exclusive modes. @@ -646,5 +668,4 @@ public void printInfo(LFGeneratorContext.Mode mode) { * Return the Targets enum for the current target */ public abstract Target getTarget(); - } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 20f9718aea..39528a703b 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -39,8 +39,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Optional; import java.util.Set; -import org.eclipse.emf.ecore.util.EcoreUtil; - import org.lflang.ASTUtils; import org.lflang.AttributeUtils; import org.lflang.ErrorReporter; @@ -67,9 +65,9 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Timer; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.lf.WidthSpec; - /** * Representation of a compile-time instance of a reactor. * If the reactor is instantiated as a bank of reactors, or if any @@ -150,6 +148,9 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re /** List of reaction instances for this reactor instance. */ public final List reactions = new ArrayList<>(); + /** List of watchdog instances for this reactor instance. */ + public final List watchdogs = new ArrayList<>(); + /** The timer instances belonging to this reactor instance. */ public final List timers = new ArrayList<>(); @@ -772,6 +773,21 @@ protected TriggerInstance getOrCreateBuiltinTrigger(Buil return builtinTriggers.computeIfAbsent(trigger.getType(), ref -> TriggerInstance.builtinTrigger(trigger, this)); } + /** Create all the watchdog instances of this reactor instance. */ + protected void createWatchdogInstances() { + List watchdogs = ASTUtils.allWatchdogs(reactorDefinition); + if (watchdogs != null) { + for (Watchdog watchdog : watchdogs) { + // Create the watchdog instance. + var watchdogInstance = new WatchdogInstance(watchdog, this); + + // Add the watchdog instance to the list of watchdogs for this + // reactor. + this.watchdogs.add(watchdogInstance); + } + } + } + //////////////////////////////////////// //// Private constructors diff --git a/org.lflang/src/org/lflang/generator/WatchdogInstance.java b/org.lflang/src/org/lflang/generator/WatchdogInstance.java new file mode 100644 index 0000000000..78c0c9bc81 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/WatchdogInstance.java @@ -0,0 +1,71 @@ +/** + * @file + * @author Benjamin Asch + * @author Edward A. Lee + * @copyright (c) 2023, The University of California at Berkeley + * License in [BSD 2-clause](https://github.com/lf-lang/lingua-franca/blob/master/LICENSE) + * @brief Instance of a watchdog + */ +package org.lflang.generator; + +import org.lflang.TimeValue; +import org.lflang.lf.Watchdog; + +/** + * Instance of a watchdog. Upon creation the actual delay is converted into a proper time value. If + * a parameter is referenced, it is looked up in the given (grand)parent reactor instance. + * + * @author{Benjamin Asch } + */ +public class WatchdogInstance { + + /** Create a new watchdog instance associated with the given reactor instance. */ + public WatchdogInstance(Watchdog definition, ReactorInstance reactor) { + if (definition.getTimeout() != null) { + // Get the timeout value given in the watchdog declaration. + this.timeout = reactor.getTimeValue(definition.getTimeout()); + } else { + // NOTE: The grammar does not allow the timeout to be omitted, so this should not occur. + this.timeout = TimeValue.ZERO; + } + + this.name = definition.getName().toString(); + this.definition = definition; + this.reactor = reactor; + } + + ////////////////////////////////////////////////////// + //// Public methods. + + public String getName() { + return this.name; + } + + public Watchdog getDefinition() { + return this.definition; + } + + public TimeValue getTimeout() { + return (TimeValue) this.timeout; + } + + public ReactorInstance getReactor() { + return this.reactor; + } + + @Override + public String toString() { + return "WatchdogInstance " + name + "(" + timeout.toString() + ")"; + } + + ////////////////////////////////////////////////////// + //// Private fields. + + private final TimeValue timeout; + + private final String name; + + private final Watchdog definition; + + private final ReactorInstance reactor; +} diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index 3c5d872f96..32174be22c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -96,7 +96,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.util.ArduinoUtil; import org.lflang.util.FileUtil; -import com.google.common.base.Objects; import com.google.common.collect.Iterables; /** @@ -305,6 +304,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ @SuppressWarnings("StaticPseudoFunctionalStyleMethod") public class CGenerator extends GeneratorBase { + // Regular expression pattern for compiler error messages with resource // and line number information. The first match will a resource URI in the // form of "file:/path/file.lf". The second match will be a line number. @@ -352,6 +352,7 @@ public class CGenerator extends GeneratorBase { private int resetReactionCount = 0; private int modalReactorCount = 0; private int modalStateResetCount = 0; + private int watchdogCount = 0; // Indicate whether the generator is in Cpp mode or not private final boolean CCppMode; @@ -397,7 +398,7 @@ public void accommodatePhysicalActionsIfPresent() { // keepalive is set to true, unless the user has explicitly set it to false. for (Resource resource : GeneratorUtils.getResources(reactors)) { for (Action action : ASTUtils.allElementsOfClass(resource, Action.class)) { - if (Objects.equal(action.getOrigin(), ActionOrigin.PHYSICAL)) { + if (ActionOrigin.PHYSICAL.equals(action.getOrigin())) { // If the unthreaded runtime is not requested by the user, use the threaded runtime instead // because it is the only one currently capable of handling asynchronous events. if (!targetConfig.threading && !targetConfig.setByUser.contains(TargetProperty.THREADING)) { @@ -588,7 +589,6 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { } catch (IOException e) { Exceptions.sneakyThrow(e); } - } // If a build directive has been given, invoke it now. @@ -641,8 +641,9 @@ private void generateCodeFor( "int _lf_timer_triggers_count = 0;", "SUPPRESS_UNUSED_WARNING(_lf_timer_triggers_count);", "int bank_index;", - "SUPPRESS_UNUSED_WARNING(bank_index);" - )); + "SUPPRESS_UNUSED_WARNING(bank_index);", + "int watchdog_number = 0;", + "SUPPRESS_UNUSED_WARNING(watchdog_number);")); // Add counters for modal initialization initializeTriggerObjects.pr(CModesGenerator.generateModalInitalizationCounters(hasModalReactors)); @@ -672,6 +673,9 @@ private void generateCodeFor( // If there are reset reactions, create a table of triggers. code.pr(CReactionGenerator.generateBuiltinTriggersTable(resetReactionCount, "reset")); + // If there are watchdogs, create a table of triggers. + code.pr(CWatchdogGenerator.generateWatchdogTable(watchdogCount)); + // If there are modes, create a table of mode state to be checked for transitions. code.pr(CModesGenerator.generateModeStatesTable( hasModalReactors, @@ -949,7 +953,6 @@ private void pickCompilePlatform() { } } - /** * Copy target-specific header file to the src-gen directory. */ @@ -957,7 +960,9 @@ protected void copyTargetFiles() throws IOException { // Copy the core lib String coreLib = LFGeneratorContext.BuildParm.EXTERNAL_RUNTIME_PATH.getValue(context); Path dest = fileConfig.getSrcGenPath(); - if (targetConfig.platformOptions.platform == Platform.ARDUINO) dest = dest.resolve("src"); + if (targetConfig.platformOptions.platform == Platform.ARDUINO) { + dest = dest.resolve("src"); + } if (coreLib != null) { FileUtil.copyDirectoryContents(Path.of(coreLib), dest, true); } else { @@ -999,6 +1004,7 @@ protected void copyTargetFiles() throws IOException { //////////////////////////////////////////// //// Code generators. + /** * Generate a reactor class definition for the specified federate. * A class definition has four parts: @@ -1060,6 +1066,8 @@ private void generateReactorClassBody(Reactor reactor, CodeBuilder header, CodeB // go into the constructor. Collect those lines of code here: var constructorCode = new CodeBuilder(); generateAuxiliaryStructs(header, reactor, false); + // The following must go before the self struct so the #include watchdog.h ends up in the header. + CWatchdogGenerator.generateWatchdogs(src, header, reactor, errorReporter); generateSelfStruct(header, reactor, constructorCode); generateMethods(src, reactor); generateReactions(src, reactor); @@ -1202,6 +1210,9 @@ private void generateSelfStruct(CodeBuilder builder, ReactorDecl decl, CodeBuild types ); + // Generate the fields needed for each watchdog. + CWatchdogGenerator.generateWatchdogStruct(body, decl, constructorCode); + // Next, generate fields for modes CModesGenerator.generateDeclarations(reactor, body, constructorCode); @@ -1449,8 +1460,6 @@ private void recordBuiltinTriggers(ReactorInstance instance) { } } - - /** * Generate code to set up the tables used in _lf_start_time_step to decrement reference * counts and mark outputs absent between time steps. This function puts the code @@ -1480,7 +1489,7 @@ private void generateStartTimeStep(ReactorInstance instance) { temp.pr("// Add port "+port.getFullName()+" to array of is_present fields."); - if (!Objects.equal(port.getParent(), instance)) { + if (!instance.equals(port.getParent())) { // The port belongs to contained reactor, so we also have // iterate over the instance bank members. temp.startScopedBlock(); @@ -1503,7 +1512,7 @@ private void generateStartTimeStep(ReactorInstance instance) { startTimeStepIsPresentCount += port.getWidth() * port.getParent().getTotalWidth(); - if (!Objects.equal(port.getParent(), instance)) { + if (!instance.equals(port.getParent())) { temp.pr("count++;"); temp.endScopedBlock(); temp.endScopedBlock(); @@ -1691,6 +1700,7 @@ public void generateReactorInstance(ReactorInstance instance) { initializeOutputMultiports(instance); initializeInputMultiports(instance); recordBuiltinTriggers(instance); + watchdogCount += CWatchdogGenerator.generateInitializeWatchdogs(initializeTriggerObjects, instance); // Next, initialize the "self" struct with state variables. // These values may be expressions that refer to the parameter values defined above. @@ -1899,7 +1909,7 @@ public TargetTypes getTargetTypes() { } /** - * + * Get the Docker generator. * @param context * @return */ @@ -2039,6 +2049,7 @@ public GeneratorBase.ErrorFileAndLine parseCommandOutput(String line) { //////////////////////////////////////////// //// Private methods. + /** Returns the Target enum for this generator */ @Override public Target getTarget() { @@ -2079,7 +2090,6 @@ private void createMainReactorInstance() { } } } - } /** diff --git a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java index 100f3deaa4..3dd1aef157 100644 --- a/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CReactionGenerator.java @@ -34,6 +34,7 @@ import org.lflang.lf.TriggerRef; import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; import org.lflang.util.StringUtil; public class CReactionGenerator { @@ -193,12 +194,13 @@ public static String generateInitializationForReaction(String body, reactionInitialization, fieldsForStructsForContainedReactors, effect.getContainer(), - (Input) variable - ); + (Input) variable); + } else if (variable instanceof Watchdog) { + reactionInitialization.pr(generateWatchdogVariablesInReaction(effect, decl)); } else { errorReporter.reportError( reaction, - "In generateReaction(): effect is neither an input nor an output." + "In generateReaction(): effect is not an input, output, or watchdog." ); } } @@ -659,6 +661,22 @@ public static String generateOutputVariablesInReaction( } } + /** + * Generate into the specified string builder the code to initialize local variables for watchdogs + * in a reaction function from the "self" struct. + * + * @param effect The effect declared by the reaction. This must refer to a watchdog. + * @param decl The reactor containing the reaction or the import statement. + */ + public static String generateWatchdogVariablesInReaction(VarRef effect, ReactorDecl decl) { + Watchdog watchdog = (Watchdog) effect.getVariable(); + String watchdogName = watchdog.getName(); + return String.join( + "\n", + List.of("watchdog_t* " + watchdogName + " = &(self->_lf_watchdog_" + watchdogName + ");") + ); + } + /** * Generate the fields of the self struct and statements for the constructor * to create and initialize a reaction_t struct for each reaction in the @@ -822,6 +840,11 @@ public static void generateReactionAndTriggerStructs( for (Input input : ASTUtils.allInputs(reactor)) { createTriggerT(body, input, triggerMap, constructorCode, types); } + + // Next handle watchdogs. + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + createTriggerT(body, watchdog, triggerMap, constructorCode, types); + } } /** @@ -829,9 +852,9 @@ public static void generateReactionAndTriggerStructs( * reaction_t pointers pointing to reactions triggered by this variable, * and initialize the pointers in the array in the constructor. * @param body The place to write the self struct entries. - * @param variable The trigger variable (Timer, Action, or Input). - * @param triggerMap A map from Variables to a list of the reaction indices - * triggered by the variable. + * @param variable The trigger variable (Timer, Action, Watchdog, or Input). + * @param triggerMap A map from Variables to a list of the reaction indices triggered by the + * variable. * @param constructorCode The place to write the constructor code. */ private static void createTriggerT( @@ -1160,7 +1183,14 @@ public static String generateStpFunctionHeader(Reactor r, return generateFunctionHeader(functionName); } - private static String generateFunctionHeader(String functionName) { + /** + * Return the start of a function declaration for a function that takes + * a `void*` argument and returns void. + * + * @param functionName + * @return + */ + public static String generateFunctionHeader(String functionName) { return "void " + functionName + "(void* instance_args)"; } } diff --git a/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java new file mode 100644 index 0000000000..9ab15ea98e --- /dev/null +++ b/org.lflang/src/org/lflang/generator/c/CWatchdogGenerator.java @@ -0,0 +1,307 @@ +/** + * @file + * @author Benjamin Asch + * @author Edward A. Lee + * @copyright (c) 2023, The University of California at Berkeley. + * License: BSD 2-clause + * @brief Code generation methods for watchdogs in C. + */ +package org.lflang.generator.c; + +import java.util.List; +import org.lflang.ASTUtils; +import org.lflang.ErrorReporter; +import org.lflang.generator.CodeBuilder; +import org.lflang.generator.ReactorInstance; +import org.lflang.lf.Mode; +import org.lflang.lf.ModeTransition; +import org.lflang.lf.Reactor; +import org.lflang.lf.ReactorDecl; +import org.lflang.lf.VarRef; +import org.lflang.lf.Variable; +import org.lflang.lf.Watchdog; + +/** + * @brief Generate C code for watchdogs. + * This class contains a collection of static methods supporting code generation in C + * for watchdogs. These methods are protected because they are intended to be used + * only within the same package. + * + * @author Benjamin Asch + * @author Edward A. Lee + */ +public class CWatchdogGenerator { + + /** + * Return true if the given reactor has one or more watchdogs. + * @param reactor The reactor. + * @return True if the given reactor has watchdogs. + */ + public static boolean hasWatchdogs(Reactor reactor) { + List watchdogs = ASTUtils.allWatchdogs(reactor); + if (watchdogs != null && !watchdogs.isEmpty()) return true; + return false; + } + + ///////////////////////////////////////////////////////////////// + // Protected methods + + /** + * For the specified reactor instance, generate initialization code for each watchdog + * in the reactor. This code initializes the watchdog-related fields on the self struct + * of the reactor instance. + * @param code The place to put the code + * @param instance The reactor instance + * @return The count of watchdogs found in the reactor + */ + protected static int generateInitializeWatchdogs(CodeBuilder code, ReactorInstance instance) { + var foundOne = false; + var temp = new CodeBuilder(); + var reactorRef = CUtil.reactorRef(instance); + int watchdogCount = 0; + for (Watchdog watchdog + : ASTUtils.allWatchdogs(ASTUtils.toDefinition(instance.getDefinition().getReactorClass()))) { + var watchdogField = reactorRef + "->_lf_watchdog_" + watchdog.getName(); + temp.pr(String.join("\n", + "_lf_watchdogs[watchdog_number++] = &" + watchdogField + ";", + watchdogField + ".min_expiration = " + + CTypes.getInstance().getTargetTimeExpr(instance.getTimeValue(watchdog.getTimeout())) + + ";", + watchdogField + ".thread_active = false;", + "if (" + watchdogField + ".base->reactor_mutex == NULL) {", + " " + watchdogField + ".base->reactor_mutex = (lf_mutex_t*)calloc(1, sizeof(lf_mutex_t));", + "}" + )); + watchdogCount += 1; + foundOne = true; + } + // temp.pr("#endif"); + if (foundOne) { + code.pr(temp.toString()); + } + code.pr("SUPPRESS_UNUSED_WARNING(_lf_watchdog_count);"); + return watchdogCount; + } + + /** + * Generate watchdog functions definition for a reactor. These functions have a single argument + * that is a void* pointing to the self struct of the reactor, which contains parameters, state + * variables, inputs (triggering or not), actions (triggering or produced), and outputs. + * + * @param src The place to put the code + * @param header The place to put header code + * @param decl The reactor declaration + */ + protected static void generateWatchdogs( + CodeBuilder src, CodeBuilder header, ReactorDecl decl, ErrorReporter errorReporter + ) { + var reactor = ASTUtils.toDefinition(decl); + if (hasWatchdogs(reactor)) { + header.pr("#include \"core/threaded/watchdog.h\""); + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + src.pr(generateWatchdogFunction(watchdog, decl, errorReporter)); + } + } + } + + /** + * Generate watchdog definitions in the reactor's self struct. + * @param body The place to put the definitions + * @param decl The reactor declaration + * @param constructorCode The place to put initialization code. + */ + protected static void generateWatchdogStruct( + CodeBuilder body, ReactorDecl decl, CodeBuilder constructorCode) { + var reactor = ASTUtils.toDefinition(decl); + + for (Watchdog watchdog : ASTUtils.allWatchdogs(reactor)) { + String watchdogName = watchdog.getName(); + + body.pr(watchdog, "watchdog_t _lf_watchdog_" + watchdogName + ";"); + + // watchdog function name + var watchdogFunctionName = watchdogFunctionName(watchdog, decl); + // Set values of watchdog_t struct in the reactor's constructor. + constructorCode.pr( + watchdog, + String.join( + "\n", + "self->_lf_watchdog_" + watchdogName + ".base = &(self->base);", + "self->_lf_watchdog_" + watchdogName + ".expiration = NEVER;", + "self->_lf_watchdog_" + watchdogName + ".thread_active = false;", + "self->_lf_watchdog_" + + watchdogName + + ".watchdog_function = " + + watchdogFunctionName + + ";", + "self->_lf_watchdog_" + + watchdogName + + ".trigger = &(self->_lf__" + + watchdogName + + ");" + ) + ); + } + } + + /** + * Generate a global table of watchdog structs. + * @param count The number of watchdogs found. + * @return The code that defines the table or a comment if count is 0. + */ + protected static String generateWatchdogTable(int count) { + if (count == 0) { + return String.join("\n", + "// No watchdogs found.", + "typedef void watchdog_t;", + "watchdog_t* _lf_watchdogs = NULL;", + "int _lf_watchdog_count = 0;" + ); + } + return String.join( + "\n", + List.of( + "// Array of pointers to watchdog structs.", + "watchdog_t* _lf_watchdogs[" + count + "];", + "int _lf_watchdog_count = " + count + ";" + ) + ); + } + + ///////////////////////////////////////////////////////////////// + // Private methods + + /** + * Generate necessary initialization code inside the body of a watchdog handler. + * + * @param watchdog The wotchdog + * @param decl The declaration for the reactor that has the watchdog + */ + private static String generateInitializationForWatchdog( + Watchdog watchdog, + ReactorDecl decl, + ErrorReporter errorReporter + ) { + Reactor reactor = ASTUtils.toDefinition(decl); + + // Construct the reactionInitialization code to go into + // the body of the function before the verbatim code. + CodeBuilder watchdogInitialization = new CodeBuilder(); + + CodeBuilder code = new CodeBuilder(); + + // Define the "self" struct. + String structType = CUtil.selfType(reactor); + // A null structType means there are no inputs, state, + // or anything else. No need to declare it. + if (structType != null) { + code.pr( + String.join( + "\n", + structType + + "* self = (" + + structType + + "*)instance_args; SUPPRESS_UNUSED_WARNING(self);")); + } + + // Declare mode if in effects field of watchdog + if (watchdog.getEffects() != null) { + for (VarRef effect : watchdog.getEffects()) { + Variable variable = effect.getVariable(); + if (variable instanceof Mode) { + // Mode change effect + int idx = ASTUtils.allModes(reactor).indexOf((Mode) effect.getVariable()); + String name = effect.getVariable().getName(); + if (idx >= 0) { + watchdogInitialization.pr( + "reactor_mode_t* " + + name + + " = &self->_lf__modes[" + + idx + + "];\n" + + "lf_mode_change_type_t _lf_" + + name + + "_change_type = " + + (effect.getTransition() == ModeTransition.HISTORY + ? "history_transition" + : "reset_transition") + + ";"); + } else { + errorReporter.reportError( + watchdog, + "In generateInitializationForWatchdog(): " + name + " not a valid mode of this reactor." + ); + } + } + } + } + // Add watchdog definition + watchdogInitialization.pr( + "watchdog_t* " + + watchdog.getName() + + " = &(self->_lf_watchdog_" + + watchdog.getName() + + ");\n"); + + // Next generate all the collected setup code. + code.pr(watchdogInitialization.toString()); + return code.toString(); + } + + /** + * Do heavy lifting to generate the watchdog handler function + * + * @param header function name and declaration. + * @param init initialize variable. + * @param watchdog The watchdog. + */ + private static String generateFunction(String header, String init, Watchdog watchdog) { + var function = new CodeBuilder(); + function.pr(header + " {"); + function.indent(); + function.pr(init); + function.pr("_lf_schedule((*" + watchdog.getName() + ").trigger, 0, NULL);"); + function.prSourceLineNumber(watchdog.getCode()); + function.pr(ASTUtils.toText(watchdog.getCode())); + function.unindent(); + function.pr("}"); + return function.toString(); + } + + + /** Generate the watchdog handler function. */ + private static String generateWatchdogFunction( + Watchdog watchdog, ReactorDecl decl, ErrorReporter errorReporter + ) { + return generateFunction( + generateWatchdogFunctionHeader(watchdog, decl), + generateInitializationForWatchdog(watchdog, decl, errorReporter), + watchdog); + } + + /** + * Return the start of a C function definition for a watchdog. + * + * @param watchdog The watchdog + * @param decl The reactor declaration + * @return The function name for the watchdog function. + */ + private static String generateWatchdogFunctionHeader(Watchdog watchdog, ReactorDecl decl) { + String functionName = watchdogFunctionName(watchdog, decl); + return CReactionGenerator.generateFunctionHeader(functionName); + } + + /** + * Return the name of the watchdog expiration handler function. + * + * @param watchdog The watchdog + * @param decl The reactor with the watchdog + * @return Name of the watchdog handler function + */ + private static String watchdogFunctionName(Watchdog watchdog, ReactorDecl decl) { + return decl.getName().toLowerCase() + + "_" + + watchdog.getName().toLowerCase() + + "_watchdog_function"; + } +} diff --git a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java index ae268a9327..70a05a40f7 100644 --- a/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java +++ b/org.lflang/src/org/lflang/scoping/LFScopeProviderImpl.java @@ -11,15 +11,15 @@ 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 +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 +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. ***************/ @@ -45,19 +45,18 @@ import org.lflang.lf.Import; import org.lflang.lf.ImportedReactor; import org.lflang.lf.Instantiation; +import org.lflang.lf.LfPackage; +import org.lflang.lf.Mode; import org.lflang.lf.Model; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.VarRef; -import org.lflang.lf.LfPackage; -import org.lflang.lf.Mode; /** - * This class enforces custom rules. In particular, it resolves references to - * parameters, ports, actions, and timers. Ports can be referenced across at - * most one level of hierarchy. Parameters, actions, and timers can be - * referenced locally, within the reactor. + * This class enforces custom rules. In particular, it resolves references to parameters, ports, + * actions, and timers. Ports can be referenced across at most one level of hierarchy. Parameters, + * actions, and timers can be referenced locally, within the reactor. * * @author Marten Lohstroh * @see @@ -78,6 +77,7 @@ enum RefType { TRIGGER, SOURCE, EFFECT, + WATCHDOG, DEADLINE, CLEFT, CRIGHT @@ -165,7 +165,6 @@ protected IScope getScopeForAssignment(Assignment assignment, EReference referen if (defn != null) { return Scopes.scopeFor(allParameters(defn)); } - } if (reference == LfPackage.Literals.ASSIGNMENT__RHS) { return Scopes.scopeFor(((Reactor) assignment.eContainer().eContainer()).getParameters()); @@ -226,6 +225,7 @@ protected IScope getScopeForVarRef(VarRef variable, EReference reference) { candidates.addAll(allInputs(reactor)); candidates.addAll(allActions(reactor)); candidates.addAll(allTimers(reactor)); + candidates.addAll(allWatchdogs(reactor)); return Scopes.scopeFor(candidates); } case SOURCE: @@ -238,8 +238,11 @@ protected IScope getScopeForVarRef(VarRef variable, EReference reference) { } candidates.addAll(allOutputs(reactor)); candidates.addAll(allActions(reactor)); + candidates.addAll(allWatchdogs(reactor)); return Scopes.scopeFor(candidates); } + case WATCHDOG: + return Scopes.scopeFor(allWatchdogs(reactor)); case DEADLINE: case CLEFT: return Scopes.scopeFor(allInputs(reactor)); diff --git a/test/C/src/concurrent/Watchdog.lf b/test/C/src/concurrent/Watchdog.lf new file mode 100644 index 0000000000..bc8ab1387b --- /dev/null +++ b/test/C/src/concurrent/Watchdog.lf @@ -0,0 +1,66 @@ +/** + * Test watchdog. This test starts a watchdog timer of 150ms every 100ms. Half + * the time, it then sleeps after starting the watchdog so that the watchdog + * expires. There should be a total of five watchdog expirations. + * @author Benjamin Asch + * @author Edward A. Lee + */ +target C { + timeout: 1100 ms +} + +reactor Watcher(timeout: time = 150 ms) { + timer t(100 ms, 100 ms) // Offset ameliorates startup time. + // Period has to be smaller than watchdog timeout. Produced if the watchdog + // triggers. + output d: int + state count: int = 0 + + watchdog poodle(timeout) {= + instant_t p = lf_time_physical_elapsed(); + lf_print("******** Watchdog timed out at elapsed physical time: " PRINTF_TIME, p); + self->count++; + =} + + reaction(t) -> poodle, d {= + lf_watchdog_start(poodle, 0); + lf_print("Watchdog started at physical time " PRINTF_TIME, lf_time_physical_elapsed()); + lf_print("Will expire at " PRINTF_TIME, lf_time_logical_elapsed() + self->timeout); + lf_set(d, 42); + =} + + reaction(poodle) -> d {= + lf_print("Reaction poodle was called."); + lf_set(d, 1); + =} + + reaction(shutdown) -> poodle {= + lf_watchdog_stop(poodle); + // Watchdog may expire in tests even without the sleep, but it should at least expire twice. + if (self->count < 2) { + lf_print_error_and_exit("Watchdog expired %d times. Expected at least 2.", self->count); + } + =} +} + +main reactor { + logical action a + state count: int = 0 + + w = new Watcher() + + reaction(w.d) {= + lf_print("Watcher reactor produced an output. %d", self->count % 2); + self->count++; + if (self->count % 4 == 0) { + lf_print(">>>>>> Taking a long time to process that output!"); + lf_sleep(MSEC(160)); + } + =} + + reaction(shutdown) {= + if (self->count < 12) { + lf_print_error_and_exit("Watchdog produced output %d times. Expected at least 12.", self->count); + } + =} +}