From d258aca0ca8f369b532a66e96d6204347ea404b5 Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 18 Nov 2021 14:59:31 -0800 Subject: [PATCH 001/221] Rough starting point at refactoring to make banks scalable. Doesn't compile yet. --- .../synthesis/LinguaFrancaSynthesis.xtend | 65 ++-- .../styles/LinguaFrancaShapeExtensions.xtend | 7 +- .../lflang/federated/FederateInstance.xtend | 11 +- .../org/lflang/generator/GeneratorBase.xtend | 23 +- .../lflang/generator/ParameterInstance.java | 12 - .../lflang/generator/ReactionInstance.java | 60 +--- .../org/lflang/generator/ReactorInstance.java | 297 +++++------------- .../org/lflang/generator/c/CGenerator.xtend | 178 ++++++----- .../generator/python/PythonGenerator.xtend | 8 +- 9 files changed, 230 insertions(+), 431 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 6da78a33e7..d81c8552a5 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -323,13 +323,7 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { node.setLayoutOption(LayeredOptions.SPACING_COMPONENT_COMPONENT, LayeredOptions.SPACING_COMPONENT_COMPONENT.^default * 0.5f) } } else { - // If the reactor is a bank, then obtain the details from the first - // element of the bank rather than the bank itself. - val instance = if (reactorInstance.bankSize > 0) { - reactorInstance.bankMembers.get(0) - } else { - reactorInstance - } + val instance = reactorInstance // Expanded Rectangle node.addReactorFigure(reactorInstance, label) => [ ReactorFigureComponents comps | @@ -573,12 +567,9 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { // Transform instances for (entry : reactorInstance.children.reverseView.indexed) { val child = entry.value - // Do not render individual reactors in a bank. - if (child.getBank() === null) { - val rNodes = child.createReactorNode(child.getExpansionState?:false, inputPorts, outputPorts, allReactorNodes) - rNodes.head.setLayoutOption(CoreOptions.PRIORITY, entry.key) - nodes += rNodes - } + val rNodes = child.createReactorNode(child.getExpansionState?:false, inputPorts, outputPorts, allReactorNodes) + rNodes.head.setLayoutOption(CoreOptions.PRIORITY, entry.key) + nodes += rNodes } // Create timers @@ -677,29 +668,24 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { // connect outputs port = null // create new ports for (TriggerInstance effect : reaction.effects?:emptyList) { - // Skip this effect if it is contained in a bank with index other than 0. - if (!(effect instanceof PortInstance) - || (effect.parent.bankIndex <= 0) - ) { - port = if (REACTIONS_USE_HYPEREDGES.booleanValue && port !== null) { - port + port = if (REACTIONS_USE_HYPEREDGES.booleanValue && port !== null) { + port + } else { + node.addInvisiblePort() => [ + setLayoutOption(CoreOptions.PORT_SIDE, PortSide.EAST) + ] + } + if (effect instanceof ActionInstance) { + actionSources.put(effect, port) + } else if (effect instanceof PortInstance) { + var KPort dst = null + if (effect.isOutput) { + dst = parentOutputPorts.get(effect) } else { - node.addInvisiblePort() => [ - setLayoutOption(CoreOptions.PORT_SIDE, PortSide.EAST) - ] + dst = inputPorts.get(effect.parent, effect) } - if (effect instanceof ActionInstance) { - actionSources.put(effect, port) - } else if (effect instanceof PortInstance) { - var KPort dst = null - if (effect.isOutput) { - dst = parentOutputPorts.get(effect) - } else { - dst = inputPorts.get(effect.parent, effect) - } - if (dst !== null) { - createDependencyEdge(effect).connect(port, dst) - } + if (dst !== null) { + createDependencyEdge(effect).connect(port, dst) } } } @@ -872,17 +858,10 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { b.append(reactorInstance.reactorDefinition.name) } if (REACTOR_PARAMETER_MODE.objectValue === ReactorParameterDisplayModes.TITLE) { - // If the reactor is a bank, then obtain the details from the first - // element of the bank rather than the bank itself. - val instance = if (reactorInstance.bankSize > 0) { - reactorInstance.bankMembers.get(0) - } else { - reactorInstance - } - if (instance.parameters.empty) { + if (reactorInstance.parameters.empty) { b.append("()") } else { - b.append(instance.parameters.join("(", ", ", ")") [ + b.append(reactorInstance.parameters.join("(", ", ", ")") [ createParameterLabel(false) ]) } diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend index abebc446f2..711c7d6c10 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend @@ -200,17 +200,16 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { val bank = newArrayList val container = node.addInvisibleContainerRendering => [ // TODO handle unresolved width - val bankWidth = reactorInstance.bankSize//instance.widthSpec.width addRoundedRectangle(8, 8, 1) => [ style.apply(it) setAreaPlacementData().from(LEFT, BANK_FIGURE_X_OFFSET_SUM, 0, TOP, BANK_FIGURE_Y_OFFSET_SUM, 0).to(RIGHT, 0, 0, BOTTOM, 0, 0) ] - if (bankWidth === 3) { + if (reactorInstance.width === 3) { addRoundedRectangle(8, 8, 1) => [ style.apply(it) setAreaPlacementData().from(LEFT, BANK_FIGURE_X_OFFSET_SUM / 2, 0, TOP, BANK_FIGURE_Y_OFFSET_SUM / 2, 0).to(RIGHT, BANK_FIGURE_X_OFFSET_SUM / 2, 0, BOTTOM, BANK_FIGURE_Y_OFFSET_SUM / 2, 0) ] - } else if (bankWidth !== 2 && bankWidth !== 3) { + } else if (reactorInstance.width !== 2 && reactorInstance.width !== 3) { addRoundedRectangle(8, 8, 1) => [ style.apply(it) setAreaPlacementData().from(LEFT, 2 * BANK_FIGURE_X_OFFSET_SUM / 3, 0, TOP, 2 * BANK_FIGURE_Y_OFFSET_SUM / 3, 0).to(RIGHT, BANK_FIGURE_X_OFFSET_SUM / 3, 0, BOTTOM, BANK_FIGURE_Y_OFFSET_SUM / 3, 0) @@ -230,7 +229,7 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { setAreaPlacementData().from(LEFT, 12, 0, BOTTOM, 9, 0).to(RIGHT, 6, 0, BOTTOM, 0.5f, 0) // TODO handle unresolved width // addText(instance.widthSpec.toText) => [ - addText(Integer.toString(reactorInstance.bankSize)) => [ + addText(Integer.toString(reactorInstance.width)) => [ horizontalAlignment = HorizontalAlignment.LEFT verticalAlignment = VerticalAlignment.BOTTOM fontSize = 6 diff --git a/org.lflang/src/org/lflang/federated/FederateInstance.xtend b/org.lflang/src/org/lflang/federated/FederateInstance.xtend index 1ea04f5b26..76bc656a34 100644 --- a/org.lflang/src/org/lflang/federated/FederateInstance.xtend +++ b/org.lflang/src/org/lflang/federated/FederateInstance.xtend @@ -367,10 +367,6 @@ class FederateInstance { * reactor instance is contained by this federate. * If the specified instance is the top-level reactor, return true * (this reactor belongs to all federates). - * If it is a bank member, then this returns true only if the bankIndex - * of the reactor instance matches the federate instance bank index. - * This also returns true for the bank placeholder for a bank that - * contains an instance that matches this bank index. * If this federate instance is a singleton, then return true if the * instance is non null. * @@ -387,12 +383,7 @@ class FederateInstance { // Start with this instance, then check its parents. var i = instance; while (i !== null) { - if (i.definition === this.instantiation - && ( - i.bankIndex < 0 // Not a bank member - || i.bankIndex == this.bankIndex // Index matches. - ) - ) { + if (i.definition === this.instantiation) { return true; } i = i.parent; diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 4017639814..7b0d4239c1 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -1610,12 +1610,12 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes for (instantiation : mainReactor.allInstantiations) { var bankWidth = ASTUtils.width(instantiation.widthSpec, context); if (bankWidth < 0) { - errorReporter.reportError(instantiation, "Cannot determine bank width!"); + errorReporter.reportError(instantiation, "Cannot determine bank width! Assuming width of 1."); // Continue with a bank width of 1. bankWidth = 1; } - // Create one federate instance for each reactor instance in the bank of reactors. - val federateInstances = new ArrayList(); + // Create one federate instance for each instance in a bank of reactors. + val federateInstances = new ArrayList(bankWidth); for (var i = 0; i < bankWidth; i++) { // Assign an integer ID to the federate. var federateID = federates.size @@ -1682,10 +1682,9 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes val mainInstance = new ReactorInstance(mainReactor, errorReporter, 1) for (federateReactor : mainInstance.children) { - // Skip banks and just process the individual instances. - if (federateReactor.bankIndex > -2) { - val bankIndex = (federateReactor.bankIndex >= 0)? federateReactor.bankIndex : 0 - val federateInstance = federatesByInstantiation.get(federateReactor.definition).get(bankIndex); + // FIXME: This used to Skip banks and just process the individual instances. + val federateInstances = federatesByInstantiation.get(federateReactor.definition); + for (federateInstance : federateInstances) { for (input : federateReactor.inputs) { replaceConnectionFromSource(input, federateInstance, federateReactor, mainInstance) } @@ -1707,7 +1706,10 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes // If the port is not an input, ignore it. if (input.isInput) { for (source : input.immediateSources()) { - val sourceBankIndex = (source.getPortInstance().parent.bankIndex >= 0) ? source.getPortInstance().parent.bankIndex : 0 + // FIXME: How to determine the bank index of the source? + // Need ReactorInstance support for ranges. + // val sourceBankIndex = (source.getPortInstance().parent.bankIndex >= 0) ? source.getPortInstance().parent.bankIndex : 0 + val sourceBankIndex = 0 val sourceFederate = federatesByInstantiation.get(source.getPortInstance().parent.definition).get(sourceBankIndex); // Set up dependency information. @@ -1750,16 +1752,17 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes // Make one communication for each channel. // FIXME: There is an opportunity for optimization here by aggregating channels. + // FIXME: bank indices not handled yet. for (var i = 0; i < source.channelWidth; i++) { FedASTUtils.makeCommunication( source.getPortInstance(), input, connection, sourceFederate, - source.getPortInstance().parent.bankIndex, + 0, // FIXME: source.getPortInstance().parent.bankIndex, source.startChannel + i, destinationFederate, - input.parent.bankIndex, + 0, // FIXME: input.parent.bankIndex, channel + i, this, targetConfig.coordination diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index 7a935b5cc7..8acc079777 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -27,14 +27,12 @@ package org.lflang.generator; -import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.lflang.InferredType; import org.lflang.JavaAstUtils; import org.lflang.lf.Assignment; -import org.lflang.lf.LfFactory; import org.lflang.lf.Parameter; import org.lflang.lf.Value; @@ -63,16 +61,6 @@ public ParameterInstance(Parameter definition, ReactorInstance parent) { this.type = JavaAstUtils.getInferredType(definition); this.init = parent.initialParameterValue(definition); - - // If the parent is in a bank and the parameter name is "bank_index", then - // override the default value provided to make it equal to the bank index. - if (parent.bankIndex >= 0 && getName().equals("bank_index")) { - Value value = LfFactory.eINSTANCE.createValue(); - value.setLiteral("" + parent.bankIndex); - List list = new ArrayList(1); - list.add(value); - this.init = list; - } } ///////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index fd6eac6aef..07a669566d 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -95,24 +95,11 @@ protected ReactionInstance( ReactorInstance containedReactor = parent.lookupReactorInstance(((VarRef)trigger).getContainer()); if (containedReactor != null) { - if (containedReactor.bankMembers != null) { - // Contained reactor is a bank. Connect to all bank members. - for (ReactorInstance bankMember : containedReactor.bankMembers) { - portInstance = bankMember.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } - } - } else { - // Contained reactor is not a bank. - portInstance = containedReactor.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } + portInstance = containedReactor.lookupPortInstance((Port)variable); + if (portInstance != null) { + this.sources.add(portInstance); + portInstance.dependentReactions.add(this); + this.triggers.add(portInstance); } } } @@ -161,24 +148,11 @@ protected ReactionInstance( ReactorInstance containedReactor = parent.lookupReactorInstance(source.getContainer()); if (containedReactor != null) { - if (containedReactor.bankMembers != null) { - // Contained reactor is a bank. Connect to all members. - for (ReactorInstance bankMember : containedReactor.bankMembers) { - portInstance = bankMember.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.reads.add(portInstance); - } - } - } else { - // The trigger is a port of a contained reactor that is not a bank. - portInstance = containedReactor.lookupPortInstance((Port)variable); - if (portInstance != null) { - this.sources.add(portInstance); - portInstance.dependentReactions.add(this); - this.triggers.add(portInstance); - } + portInstance = containedReactor.lookupPortInstance((Port)variable); + if (portInstance != null) { + this.sources.add(portInstance); + portInstance.dependentReactions.add(this); + this.triggers.add(portInstance); } } } @@ -212,18 +186,8 @@ protected ReactionInstance( this.effects.add(portInstance); portInstance.dependsOnReactions.add(this); } else { - // The effect container must be a bank of reactors. - // Need to find the ports of all the instances within the bank. - ReactorInstance bank = parent.lookupReactorInstance(effect.getContainer()); - if (bank == null || bank.bankIndex != -2) { - throw new InvalidSourceException( - "Unexpected effect. Cannot find port " + variable.getName()); - } - for (ReactorInstance bankElement : bank.bankMembers) { - portInstance = bankElement.lookupPortInstance((Port)variable); - this.effects.add(portInstance); - portInstance.dependsOnReactions.add(this); - } + throw new InvalidSourceException( + "Unexpected effect. Cannot find port " + variable.getName()); } } else if (variable instanceof Action) { var actionInstance = parent.lookupActionInstance( diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 10b8fd1a4e..c7c5314e5f 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -104,6 +104,12 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, Set un /** The action instances belonging to this reactor instance. */ public List actions = new ArrayList(); + /** + * For the convenience of code generators, this public field can + * be used to annotate the reactor instance. + */ + public String annotation; + /** * The contained reactor instances, in order of declaration. * For banks of reactors, this includes both the bank definition @@ -192,17 +198,6 @@ public boolean assignLevels() { } } - /** - * Returns the size of the bank that this reactor represents or - * 0 if this reactor does not represent a bank. - */ - public int bankSize() { - if (bankMembers != null) { - return bankMembers.size(); - } - return 0; - } - /** * Return the destinations of the specified port. * The result is a set (albeit an ordered set) of ports that are destinations @@ -216,30 +211,6 @@ public Set destinations(PortInstance source) { return null; } - /** - * If this reactor is in a bank of reactors, then return - * the reactor instance defining the bank. Otherwise, return null. - */ - public ReactorInstance getBank() { - return bank; - } - - /** - * If this reactor is in a bank of reactors, return its index, otherwise, return -1 - * for an ordinary reactor and -2 for a placeholder for a bank of reactors. - */ - public int getBankIndex() { - return bankIndex; - } - - /** - * Return the members of this bank, or null if there are none. - * @return actual bank size or -1 if this is not a bank master. - */ - public List getBankMembers() { - return bankMembers; - } - /** * Return the instance of a child rector created by the specified * definition or null if there is none. @@ -277,6 +248,14 @@ public Map>> getConnections() { return connections; } + /** + * Get the depth of the reactor instance. This is 0 for the main reactor, + * 1 for reactors immediately contained therein, etc. + */ + public int getDepth() { + return depth; + } + /** * Return the specified input by name or null if there is no such input. * @param name The input name. @@ -291,18 +270,13 @@ public PortInstance getInput(String name) { } /** - * Override the base class to append [index] if this reactor - * is in a bank of reactors. - * @return The full name of this instance. + * Override the base class to append [i_d], where d is the depth, + * if this reactor is in a bank of reactors. + * @return The name of this instance. */ @Override public String getName() { - var result = this.definition.getName(); - if (this.bankIndex >= 0) { - result += "[" + this.bankIndex + "]"; - } - if (result == null) return ""; - return result; + return this.definition.getName(); } /** @@ -317,7 +291,19 @@ public PortInstance getOutput(String name) { } return null; } - + + /** + * Get the number of reactor instances associated with this + * reactor. This is 1 plus the total number of reactors + * in any contained reactors. If this is a bank, then + * this number does not account for the bank width. + * Use getTotalNumberOfReactorInstances() to take into + * account bank width. + */ + public int getNumReactorInstances() { + return numReactorInstances; + } + /** * Return a parameter matching the specified name if the reactor has one * and otherwise return null. @@ -346,6 +332,16 @@ public TriggerInstance getShutdownTrigger() { return shutdownTrigger; } + /** + * Get the total number of reactor instances associated with + * this reactor. This is equal to the result of + * getNumReactorInstances() times the bank width, as returned + * by width(). + */ + public int getTotalNumReactorInstances() { + return totalNumReactorInstances; + } + /** * Return the trigger instances (input ports, timers, and actions * that trigger reactions) belonging to this reactor instance. @@ -440,8 +436,7 @@ public List instantiations() { * @return true if a reactor is a bank, false otherwise */ public boolean isBank() { - // FIXME magic number - return bankIndex == -2; + return (definition.getWidthSpec() != null); } /** @@ -639,64 +634,24 @@ public Set transitiveClosure(PortInstance source) { return result; } - /** - * Override the base class to return the uniqueID of the bank rather - * than this member of the bank, if this is a member of a bank of reactors. - * - * @return An identifier for this instance that is guaranteed to be - * unique within the top-level parent. - */ - @Override - public String uniqueID() { - if (this.bank != null) { - return this.bank.uniqueID(); - } - return super.uniqueID(); - } - /** - * For the specified width specification, return the width. - * This may be for a bank of reactors within this reactor instance or - * for a port of this reactor instance. If the argument is null, there - * is no width specification, so return 1. Otherwise, evaluate the - * width value by determining the value of any referenced parameters. - * - * @param widthSpec The width specification. - * + * If this is a bank of reactors, return the width or -1 if it cannot + * be determined. Otherwise, return 1. * @return The width, or -1 if it cannot be determined. */ - public int width(WidthSpec widthSpec) { - if (widthSpec.eContainer() instanceof Instantiation && parent != null) { + public int width() { + WidthSpec widthSpec = definition.getWidthSpec(); + if (widthSpec != null) { // We need the instantiations list of the containing reactor, // not this one. return ASTUtils.width(widthSpec, parent.instantiations()); } - return ASTUtils.width(widthSpec, instantiations()); + return 1; } ////////////////////////////////////////////////////// //// Protected fields. - /** - * If this reactor is in a bank of reactors, then this member - * refers to the reactor instance defining the bank. - */ - protected ReactorInstance bank = null; - - /** - * If this reactor instance is a placeholder for a bank of reactors, - * as created by the new[width] ReactorClass() syntax, then this - * list will be non-null and will contain the reactor instances in - * the bank. - */ - protected List bankMembers = null; - - /** - * If this reactor is in a bank of reactors, its index, otherwise, -1 - * for an ordinary reactor and -2 for a placeholder for a bank of reactors. - */ - protected int bankIndex = -1; - /** * Table recording connections and which connection created a link between * a source and destination. Use a source port as a key to obtain a Map. @@ -866,43 +821,12 @@ protected void transitiveClosure( //////////////////////////////////////// //// Private constructors - /** - * Create reactor instance resulting from the specified top-level instantiation. - * @param instance The Instance statement in the AST. - * @param parent The parent, or null for the main rector. - * @param reporter The error reporter. - * @param desiredDepth The depth to which to expand the hierarchy. - * @param unorderedReactions A list of reactions that should be treated as unordered. - */ - private ReactorInstance( - Instantiation definition, - ReactorInstance parent, - ErrorReporter reporter, - int desiredDepth, - Set unorderedReactions - ) { - // If the reactor is being instantiated with new[width], then pass -2 - // to the constructor, otherwise pass -1. - this( - definition, - parent, - reporter, - (definition.getWidthSpec() != null)? -2 : -1, - 0, - desiredDepth, - unorderedReactions - ); - } - /** * Create a runtime instance from the specified definition * and with the specified parent that instantiated it. - * @param instance The Instance statement in the AST. + * @param definition The instantiation statement in the AST. * @param parent The parent, or null for the main rector. - * @param generator The generator (for error reporting). - * @param reactorIndex -1 for an ordinary reactor, -2 for a - * placeholder for a bank of reactors, or the index of the - * reactor in a bank of reactors otherwise. + * @param reporter An error reporter. * @param depth The depth of this reactor in the hierarchy. * @param desiredDepth The depth to which to expand the hierarchy. * @param unorderedReactions A list of reactions that should be treated as unordered. @@ -912,15 +836,20 @@ private ReactorInstance( Instantiation definition, ReactorInstance parent, ErrorReporter reporter, - int reactorIndex, - int depth, int desiredDepth, Set unorderedReactions) { super(definition, parent); this.reporter = reporter; - this.bankIndex = reactorIndex; this.reactorDefinition = ASTUtils.toDefinition(definition.getReactorClass()); - this.depth = depth; + + // Calculate the depth. + this.depth = 0; + ReactorInstance p = parent; + while (p != null) { + p = p.parent; + this.depth++; + } + if (unorderedReactions != null) { this.unorderedReactions = unorderedReactions; } @@ -943,30 +872,7 @@ private ReactorInstance( if (recursive) { reporter.reportError(definition, "Recursive reactor instantiation."); } - - // If this reactor is actually a bank of reactors, then instantiate - // each individual reactor in the bank and skip the rest of the - // initialization for this reactor instance. - if (reactorIndex == -2) { - // If the bank width is variable, then we have to wait until the first connection - // before instantiating the children. - var width = width(definition.getWidthSpec()); - if (width > 0) { - this.bankMembers = new ArrayList(width); - for (var index = 0; index < width; index++) { - var childInstance = new ReactorInstance( - definition, parent, reporter, index, depth, desiredDepth, this.unorderedReactions - ); - this.bankMembers.add(childInstance); - childInstance.bank = this; - childInstance.bankIndex = index; - } - } else { - reporter.reportWarning(definition, "Cannot infer width."); - } - return; - } - + // If the reactor definition is null, give up here. Otherwise, diagram generation // will fail an NPE. if (reactorDefinition == null) { @@ -997,17 +903,11 @@ private ReactorInstance( child, this, reporter, - (child.getWidthSpec() != null)? -2 : -1, - depth + 1, desiredDepth, this.unorderedReactions ); this.children.add(childInstance); - // If the child is a bank of instances, add all the bank instances. - // These must be added after the bank itself. - if (childInstance.bankMembers != null) { - this.children.addAll(childInstance.bankMembers); - } + this.numReactorInstances += childInstance.getTotalNumReactorInstances(); } // Instantiate timers for this reactor instance @@ -1027,6 +927,8 @@ private ReactorInstance( // Note that this can only happen _after_ the children, // port, action, and timer instances have been created. createReactionInstances(); + + this.totalNumReactorInstances = width() * this.numReactorInstances; } } @@ -1081,29 +983,6 @@ private void connectPortInstances( // The following is support for the diagram visualization. - // The source may be at a bank index greater than 0. - // For visualization, this needs to be converted to the source - // at bank 0, because only that one is rendered. - // We want the rendering to represent all connections. - var src = srcInstance; - var dst = dstInstance; - if (src.isOutput() && src.parent.bankIndex > 0) { - // Replace the source with the corresponding port instance - // at bank index 0. - ReactorInstance newParent = src.parent.bank.bankMembers.get(0); - src = newParent.getOutput(src.getName()); - } - // The destination may be at a bank index greater than 0. - // For visualization, this needs to be converted to the destination - // at bank 0, because only that one is rendered. - // We want the rendering to represent all connections. - if (dst.isInput() && dst.parent.bankIndex > 0) { - // Replace the destination with the corresponding port instance - // at bank index 0. - ReactorInstance newParent = dst.parent.bank.bankMembers.get(0); - dst = newParent.getInput(dst.getName()); - } - // Record this representative connection for visualization in the // connections map. Map> map = connections.get(connection); @@ -1111,12 +990,12 @@ private void connectPortInstances( map = new LinkedHashMap>(); connections.put(connection, map); } - Set destinations = map.get(src); + Set destinations = map.get(srcInstance); if (destinations == null) { destinations = new LinkedHashSet(); - map.put(src, destinations); + map.put(srcInstance, destinations); } - destinations.add(dst); + destinations.add(dstInstance); // Original cryptic xtend code below. // val src2 = src @@ -1206,8 +1085,7 @@ private void establishPortConnections() { * If a given port reference `b.m`, where `b` is a bank and `m` is a multiport, * is unqualified, this function iterates over bank members first, then ports. * E.g., if `b` and `m` have width 2, it returns `[b0.m0, b0.m1, b1.m0, b1.m1]`. - * - * If a given port reference has the form `interleaved(b.m)`, where `b` is a + * If instead a given port reference has the form `interleaved(b.m)`, where `b` is a * bank and `m` is a multiport, this function iterates over ports first, * then bank members. E.g., if `b` and `m` have width 2, it returns * `[b0.m0, b1.m0, b0.m1, b1.m1]`. @@ -1231,38 +1109,9 @@ private List listPortInstances(List references) { // The reactor can be null only if there is an error in the code. // Skip this portRef so that diagram synthesis can complete. if (reactor != null) { - if (reactor.bankMembers != null) { - // Reactor is a bank. - // Only here does the "interleaved" annotation matter. - if (!portRef.isInterleaved()) { - // Port is not interleaved, so iterate first over bank members, then channels. - for (ReactorInstance memberReactor: reactor.bankMembers) { - PortInstance portInstance = memberReactor.lookupPortInstance( - (Port) portRef.getVariable()); - result.add(portInstance.newRange(0, portInstance.width)); - } - } else { - // Port is interleaved, so iterate first over channels, then bank members. - // Need to return a list of width-one ranges. - // NOTE: Here, we get multiplicative complexity (bank width times port width). - // We assume all ports in each bank have the same width. - // First, get an array of bank members so as to not have to look up each time. - List bankPorts = new ArrayList(); - for (ReactorInstance b : reactor.bankMembers) { - bankPorts.add(b.lookupPortInstance((Port) portRef.getVariable())); - } - for (int i = 0; i < bankPorts.get(0).width; i++) { - for (PortInstance p : bankPorts) { - result.add(p.newRange(i, 1)); - } - } - } - } else { - // Reactor is not a bank. - PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); - PortInstance.Range range = portInstance.newRange(0, portInstance.width); - result.add(range); - } + PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); + PortInstance.Range range = portInstance.newRange(0, portInstance.width); + result.add(range); } } return result; @@ -1294,4 +1143,10 @@ private List listPortInstances(List references) { * this reactor and its contained reactors. */ private int totalNumberOfReactionsCache = -1; + + /** Number of reactor instances (if a bank, in a bank element). */ + private int numReactorInstances = 1; + + /** Total number of reactor instances, including bank width. */ + private int totalNumReactorInstances = 1; } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 2f81b254d2..2b6603e8da 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -662,7 +662,7 @@ class CGenerator extends GeneratorBase { // Generate main instance, if there is one. // Note that any main reactors in imported files are ignored. if (this.main !== null) { - generateFederate(federate) + generateMain(federate) // Generate function to set default command-line options. // A literal array needs to be given outside any function definition, // so start with that. @@ -1002,14 +1002,12 @@ class CGenerator extends GeneratorBase { // Allocate the memory for triggers used in federated execution pr(CGeneratorExtension.allocateTriggersForFederate(federate, this)); - // Assign appropriate pointers to the triggers - pr(initializeTriggerObjectsEnd, - CGeneratorExtension.initializeTriggerForControlReactions(this.main, federate, this)); pr(initializeTriggerObjects.toString) - pr('// Allocate memory.') - pr('// Populate arrays of trigger pointers.') - pr(initializeTriggerObjectsEnd.toString) + + // Assign appropriate pointers to the triggers + // FIXME: For python target, almost surely in the wrong place. + pr(CGeneratorExtension.initializeTriggerForControlReactions(this.main, federate, this).toString()); doDeferredInitialize(federate) // Put the code here to set up the tables that drive resetting is_present and @@ -1143,12 +1141,7 @@ class CGenerator extends GeneratorBase { LinkedHashSet generatedReactorDecls ) { for (r : reactor.children) { - // FIXME: If the reactor is the bank itself, it is just a placeholder and should be skipped. - // It seems that the way banks are instantiated is that - // for a bank new[4] Foo, there will be a reactor instance Foo and four additional - // reactor instances of Foo (5 total), but the first instance doesn't include - // any of the reactor instances within Foo in its children structure. - if (r.bankIndex != -2 && federate.contains(r)) { + if (federate.contains(r)) { val declarations = this.instantiationGraph.getDeclarations(r.reactorDefinition); if (!declarations.isNullOrEmpty) { for (d : declarations) { @@ -1545,7 +1538,7 @@ class CGenerator extends GeneratorBase { * * @param federate The federate that is sending its neighbor structure */ - def generateFederateNeighborStructure(FederateInstance federate) { + private def generateFederateNeighborStructure(FederateInstance federate) { val rtiCode = new StringBuilder(); pr(rtiCode, ''' @@ -3653,9 +3646,12 @@ class CGenerator extends GeneratorBase { } /** Return the unique name for the "self" struct of the specified - * reactor instance from the instance ID. If the instance is a member - * of a bank of reactors, this returns something of the form - * name_self[index], where the index is the position within the bank. + * reactor instance from the instance ID. If the instance is a + * bank of reactors, this returns something of the form + * name_self[i_d], where d is the depth of the reactor. + * This assumes that this will appear within a for loop that + * uses index i_d. The use of the depth qualifier enables the + * for loops to be nested when a bank contains other banks. * @param instance The reactor instance. * @return The name of the self struct. */ @@ -3663,8 +3659,8 @@ class CGenerator extends GeneratorBase { var result = instance.uniqueID + "_self" // If this reactor is a member of a bank of reactors, then change // the name of its self struct to append [index]. - if (instance.bankIndex >= 0) { - result += "[" + instance.bankIndex + "]" + if (instance.isBank) { + result += "[i_" + instance.getDepth() + "]" } return result } @@ -3800,10 +3796,10 @@ class CGenerator extends GeneratorBase { } /** - * Generate code to instantiate the specified federate at the top level. - * @param federate The federate to instantiate or null to generate everything. + * Generate code to instantiate the main reactor in the specified federate. + * @param federate The federate to instantiate or a singleton federate to generate everything. */ - private def void generateFederate(FederateInstance federate) { + private def void generateMain(FederateInstance federate) { currentFederate = federate; @@ -3831,17 +3827,14 @@ class CGenerator extends GeneratorBase { generateParameterInitialization(main); for (child: main.children) { - // If the child has a multiport that is an effect of some reaction in main, - // then we have to generate code to allocate memory for arrays pointing to - // its data. If the child is a bank, then memory is allocated for the entire - // bank width because a reaction cannot specify which bank members it writes - // to so we have to assume it can write to any. Hence, we do not want to - // filter which children we do this for by federate, which is why this call - // is here. - if (federate.contains(child) || child.bankIndex >= 0) { - generateAllocationForEffectsOnInputs(child); - } if (federate.contains(child)) { + // If the child has a multiport that is an effect of some reaction in main, + // then we have to generate code to allocate memory for arrays pointing to + // its data. If the child is a bank, then memory is allocated for the entire + // bank width because a reaction cannot specify which bank members it writes + // to so we have to assume it can write to any. + generateAllocationForEffectsOnInputs(child); + generateReactorInstance(child); } } @@ -3876,18 +3869,24 @@ class CGenerator extends GeneratorBase { var structType = selfStructType(reactorClass) // If this reactor is a placeholder for a bank of reactors, then generate - // an array of instances of reactors and return. - if (instance.bankMembers !== null) { + // an array of instances of reactors and create an enclosing for loop. + if (instance.isBank) { + val index = "i_" + instance.depth; + // Array is the self struct name, but without the indexing. + var selfStructArrayName = instance.uniqueID + "_self" + pr(initializeTriggerObjects, ''' - «structType»* «nameOfSelfStruct»[«instance.bankMembers.size»]; + «structType»* «selfStructArrayName»[«instance.width»]; + // Create bank members. + for (int «index»; «index» < «instance.width»; «index»++) { ''') - return + indent(initializeTriggerObjects); } // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). The form is slightly different // depending on whether its in a bank of reactors. - if (instance.bankIndex >= 0) { + if (instance.isBank) { pr(initializeTriggerObjects, ''' «nameOfSelfStruct» = new_«reactorClass.name»(); ''') @@ -4012,6 +4011,18 @@ class CGenerator extends GeneratorBase { // Note that this function is also run once at the end // so that it can deallocate any memory. generateStartTimeStep(instance) + + // End code must go here because it references the for loop variable if we are in a bank. + pr(initializeTriggerObjects, initializeTriggerObjectsEnd) + initializeTriggerObjectsEnd = new StringBuilder(); + + if (instance.isBank()) { + // Close the for loop. + unindent(initializeTriggerObjects); + pr(initializeTriggerObjects, ''' + } + ''') + } pr(initializeTriggerObjects, "//***** End initializing " + fullName) } @@ -4186,16 +4197,6 @@ class CGenerator extends GeneratorBase { val portStructType = variableStructType( effect.definition, reactor.definition.reactorClass); - // FIXME: As of now, the following never happens because bank members - // are handled individually. But I plan to fix this, so I'm leaving this - // dead code here. - if (reactor.bankIndex === -2) { - pr(initializeTriggerObjectsEnd, ''' - for (int j = 0; j < «reactor.bankSize»; j++) { - ''') - indent(initializeTriggerObjectsEnd); - containerName += "[j]"; - } pr(initializeTriggerObjectsEnd, ''' «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width = «effect.width»; // Allocate memory to store output of reaction feeding a multiport input of a contained reactor. @@ -4205,12 +4206,6 @@ class CGenerator extends GeneratorBase { «nameOfSelfStruct»->_lf_«containerName».«effect.name»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); } ''') - if (reactor.bankIndex === -2) { - unindent(initializeTriggerObjectsEnd); - pr(initializeTriggerObjectsEnd, ''' - } - ''') - } } } } @@ -4403,33 +4398,25 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, nameOfSelfStruct + "->" + stateVar.name + " = " + initializer + ";") } else { - var temporaryVariableName = instance.uniqueID + '_initial_' + stateVar.name - // To ensure uniqueness, if this reactor is in a bank, append the bank member index. - if (instance.getBank() !== null) { - temporaryVariableName += "_" + instance.bankIndex - } // Array type has to be handled specially because C doesn't accept // type[] as a type designator. // Use the superclass to avoid [] being replaced by *. var type = super.getTargetType(stateVar.inferredType) val matcher = arrayPatternVariable.matcher(type) + + var declaration = type + " _initial"; if (matcher.find()) { // If the state type ends in [], then we have to move the [] // because C is very picky about where this goes. It has to go // after the variable name. - pr( - initializeTriggerObjects, - "static " + matcher.group(1) + " " + temporaryVariableName + "[] = " + initializer + ";" - ) - } else { - pr( - initializeTriggerObjects, - "static " + type + " " + temporaryVariableName + " = " + initializer + ";" - ) + declaration = matcher.group(1) + " _initial[]" } - pr( - initializeTriggerObjects, - nameOfSelfStruct + "->" + stateVar.name + " = " + temporaryVariableName + ";" + pr(initializeTriggerObjects, ''' + { // For scoping + static «declaration» = «initializer»; + «nameOfSelfStruct»->«stateVar.name» = _initial; + } // End scoping. + ''' ) } } @@ -5218,12 +5205,11 @@ class CGenerator extends GeneratorBase { // ////////////////////////////////////////// // // Private methods. - /** Perform deferred initializations in initialize_trigger_objects. - * @param federate The federate for which we are doing this. + /** + * Perform deferred initializations in initialize_trigger_objects. + * @param federate The federate for which we are doing this. */ private def doDeferredInitialize(FederateInstance federate) { - // First, populate the trigger tables for each output. - // The entries point to the trigger_t structs for the destination inputs. pr('// doDeferredInitialize') // For outputs that are not primitive types (of form type* or type[]), @@ -5238,7 +5224,8 @@ class CGenerator extends GeneratorBase { /** * Generate assignments of pointers in the "self" struct of a destination * port's reactor to the appropriate entries in the "self" struct of the - * source reactor. + * source reactor. This has to be done after all reactors have been created + * because inputs point to outputs that are arbitrarily far away. * @param instance The reactor instance. * @param federate The federate for which we are generating code or null * if there is no federation. @@ -5248,6 +5235,17 @@ class CGenerator extends GeneratorBase { return; } pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') + + // If the reactor is a bank, have to surround with a for loop. + FIXME wont work reactpr self struct is out of scope + if (instance.isBank) { + val index = "i_" + instance.depth; + pr(''' + // Initialize bank members. + for (int «index»; «index» < «instance.width»; «index»++) { + ''') + indent(); + } // Iterate over all ports of this reactor that have dependent reactions. for (input : instance.inputs) { if (!input.dependentReactions.isEmpty()) { @@ -5272,6 +5270,12 @@ class CGenerator extends GeneratorBase { // output of a contained reactor. connectReactionsToPorts(instance, federate) + if (instance.isBank) { + unindent(); + pr(''' + } + ''') + } pr('''// END Connect inputs and outputs for reactor «instance.getFullName».''') } @@ -5899,15 +5903,25 @@ class CGenerator extends GeneratorBase { } } - /** For each output that has a token type (type* or type[]), - * create a default token and put it on the self struct. - * @param parent The container reactor. - * @param federate The federate, or null if there is no federation. + /** + * For each output that has a token type (type* or type[]), + * create a default token and put it on the self struct. + * @param parent The container reactor. + * @param federate The federate, or null if there is no federation. */ private def void createDefaultTokens(ReactorInstance parent, FederateInstance federate) { for (containedReactor : parent.children) { // Do this only for reactors in the federate. if (federate.contains(containedReactor)) { + // If the reactor is a bank, have to surround with a for loop. + if (containedReactor.isBank) { + val index = "i_" + containedReactor.depth; + pr(''' + // Initialize bank members. + for (int «index»; «index» < «containedReactor.width»; «index»++) { + ''') + indent(); + } var nameOfSelfStruct = selfStructName(containedReactor) for (output : containedReactor.outputs) { val type = (output.definition as Output).inferredType @@ -5933,6 +5947,12 @@ class CGenerator extends GeneratorBase { } // In case this is a composite, handle its contained reactors. createDefaultTokens(containedReactor, federate) + if (containedReactor.isBank) { + unindent(); + pr(''' + } + ''') + } } } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index d18ac3046c..04ceb404d6 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -699,8 +699,8 @@ class PythonGenerator extends CGenerator { // Do not instantiate reactor classes that don't have a reaction in Python // Do not instantiate the federated main reactor since it is generated in C if (!instance.definition.reactorClass.toDefinition.allReactions.isEmpty && !instance.definition.reactorClass.toDefinition.isFederated) { - if (federate.contains(instance) && instance.bankMembers !== null) { - // If this reactor is a placeholder for a bank of reactors, then generate + if (federate.contains(instance) && instance.isBank) { + // If this reactor is a bank, then generate // a list of instances of reactors and return. pythonClassesInstantiation. append(''' @@ -1681,7 +1681,7 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» = get_python_function("«topLevelName»", «nameOfSelfStruct»->_lf_name, - «IF (instance.bankIndex > -1)» «instance.bankIndex» «ELSE» «0» «ENDIF», + «IF (instance.isBank)» i_«instance.depth» «ELSE» «0» «ENDIF», "«pythonFunctionName»"); ''') @@ -1690,7 +1690,7 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = get_python_function("«topLevelName»", «nameOfSelfStruct»->_lf_name, - «IF (instance.bankIndex > -1)» «instance.bankIndex» «ELSE» «0» «ENDIF», + «IF (instance.isBank)» i_«instance.depth» «ELSE» «0» «ENDIF», "deadline_function_«reaction.index»"); ''') } From f70aee3ebb8ccd7a4909376015991e9f6760695e Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 18 Nov 2021 15:01:25 -0800 Subject: [PATCH 002/221] Create array for self structs. --- .../src/org/lflang/generator/c/CGenerator.xtend | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 2b6603e8da..318169dafe 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3814,10 +3814,22 @@ class CGenerator extends GeneratorBase { var timersInFederate = main.timers.filter[ t | return federate.contains(t.definition); ]; + + // Create an array to store all self structs. + // This is needed because connections cannot be established until + // all reactor instances have self structs because ports that + // receive data reference the self structs of the originating + // reactors, which are arbitarily far away in the program graph. + // The type of each struct type is different, so the type of this + // array is vague. + pr(initializeTriggerObjects, ''' + void* selfStructs[«main.getTotalNumReactorInstances()»] + ''') // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' «selfStructType(main.definition.reactorClass)»* «selfStructName(main)» = new_«main.name»(); + selfStructs[0] = «selfStructName(main)»; ''') // Generate code for top-level parameters, actions, timers, and reactions that From a0538ab18f5e890106f0ea94d8de49857801787d Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 18 Nov 2021 15:53:49 -0800 Subject: [PATCH 003/221] Aligned reactor-c submodule with master (again). --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index c9d610060d..e82c413d2f 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit c9d610060d3077d58544508bbc5fc87b4a9160d2 +Subproject commit e82c413d2f005901c8efe33df3b45762bb79451c From a33cd664ecae15eae935ec83d01618836d6c419c Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 18 Nov 2021 18:20:08 -0800 Subject: [PATCH 004/221] Intermediate checkin --- .../org/lflang/generator/ReactorInstance.java | 64 +++++++- .../org/lflang/generator/c/CGenerator.xtend | 148 ++++++++++-------- 2 files changed, 137 insertions(+), 75 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index c7c5314e5f..6d51944640 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -295,8 +295,8 @@ public PortInstance getOutput(String name) { /** * Get the number of reactor instances associated with this * reactor. This is 1 plus the total number of reactors - * in any contained reactors. If this is a bank, then - * this number does not account for the bank width. + * in any contained reactors, as returned by getTotalNumReactorInstances(). + * If this is a bank, then this number does not account for the bank width. * Use getTotalNumberOfReactorInstances() to take into * account bank width. */ @@ -410,6 +410,44 @@ public List initialParameterValue(Parameter parameter) { return ASTUtils.initialValue(parameter, instantiations()); } + /** + * Return an expression that, when evaluated in a + * context with bank index variables defined, returns a unique index + * for a runtime reactor instance. This can be used to maintain an + * array of runtime instance objects, each of which will have a + * unique index. + * + * This is rather complicated because + * this reactor instance and any of its parents may actually + * represent a bank of runtime reactor instances rather a single + * runtime instance. This method returns an expression that should + * be evaluatable in any target language that uses + for addition + * and * for multiplication and has defined variables in the context + * in which this will be evaluated that specify which bank member is + * desired for this reactor instance and any of its parents that is + * a bank. The names of these variables should be rd, where r is + * the root string given here as an argument and d is the depth of + * the reactor instance that represents a bank. + * + * If this is a top-level reactor, this appends "0" and returns. + * If this is one level down, this appends "n", where n is the + * sum of the total + */ + public String indexExpression(String root) { + if (depth == 0) return("0"); + if (isBank()) { + return( + root + depth + " * " + numReactorInstances // Position of the bank member relative to the bank. + + " + " + indexOffset // Position of the bank within its parent. + + " + " + parent.indexExpression(root) // Position of the parent. + ); + } else { + return(indexOffset // Position within the parent. + + " + " + parent.indexExpression(root) // Position of the parent. + ); + } + } + /** * Return a list of Instantiation objects for evaluating parameter * values. The first object in the list is the AST Instantiation @@ -897,7 +935,9 @@ private ReactorInstance( // Do not process content (except interface above) if recursive if (!recursive && (desiredDepth < 0 || this.depth < desiredDepth)) { - // Instantiate children for this reactor instance + // Instantiate children for this reactor instance. + // While doing this, assign an index offset to each. + int offset = 1; for (Instantiation child : ASTUtils.allInstantiations(reactorDefinition)) { var childInstance = new ReactorInstance( child, @@ -908,6 +948,10 @@ private ReactorInstance( ); this.children.add(childInstance); this.numReactorInstances += childInstance.getTotalNumReactorInstances(); + childInstance.indexOffset = offset; + // Next child will have an offset augmented by the total number of + // reactor instances in this one. + offset += childInstance.getTotalNumReactorInstances(); } // Instantiate timers for this reactor instance @@ -1138,15 +1182,23 @@ private List listPortInstances(List references) { */ private int levelsAssignedAlready = 0; + /** Number of reactor instances (if a bank, in a bank element). */ + private int numReactorInstances = 1; + + /** + * The offset relative to the parent. This is the sum of + * the total number of reactor instances of peer reactors + * (those with the same parent) that are instantiated before + * this in the parent. This is used by indexExpression(). + */ + private int indexOffset = 0; + /** * Cached version of the total number of reactions within * this reactor and its contained reactors. */ private int totalNumberOfReactionsCache = -1; - /** Number of reactor instances (if a bank, in a bank element). */ - private int numReactorInstances = 1; - /** Total number of reactor instances, including bank width. */ private int totalNumReactorInstances = 1; } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 318169dafe..aec25724d3 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -580,6 +580,8 @@ class CGenerator extends GeneratorBase { val compileThreadPool = Executors.newFixedThreadPool(numOfCompileThreads); System.out.println("******** Using "+numOfCompileThreads+" threads."); for (federate : federates) { + currentFederate = federate; + startTimeStepIsPresentCount = 0 startTimeStepTokens = 0 @@ -626,8 +628,9 @@ class CGenerator extends GeneratorBase { initializeTriggerObjects = new StringBuilder() initializeTriggerObjectsEnd = new StringBuilder() - // Enable clock synchronization if the federate is not local and clock-sync is enabled - initializeClockSynchronization(federate) + // Enable clock synchronization if the federate + // is not local and clock-sync is enabled + initializeClockSynchronization() startTimeStep = new StringBuilder() @@ -641,7 +644,7 @@ class CGenerator extends GeneratorBase { copyTargetHeaderFile() // Generate code for each reactor. - generateReactorDefinitionsForFederate(federate); + generateReactorDefinitionsForFederate(); // Derive target filename from the .lf filename. val cFilename = CCompiler.getTargetFileName(topLevelName, this.CCppMode); @@ -662,7 +665,7 @@ class CGenerator extends GeneratorBase { // Generate main instance, if there is one. // Note that any main reactors in imported files are ignored. if (this.main !== null) { - generateMain(federate) + generateMain() // Generate function to set default command-line options. // A literal array needs to be given outside any function definition, // so start with that. @@ -1008,7 +1011,7 @@ class CGenerator extends GeneratorBase { // Assign appropriate pointers to the triggers // FIXME: For python target, almost surely in the wrong place. pr(CGeneratorExtension.initializeTriggerForControlReactions(this.main, federate, this).toString()); - doDeferredInitialize(federate) + doDeferredInitialize() // Put the code here to set up the tables that drive resetting is_present and // decrementing reference counts between time steps. This code has to appear @@ -1095,17 +1098,15 @@ class CGenerator extends GeneratorBase { * - If there are any cmake-include files, add them to the current list * of cmake-include files. * - If there are any preambles, add them to the preambles of the reactor. - * - * @param federate The federate to generate reactors for */ - def void generateReactorDefinitionsForFederate(FederateInstance federate) { + private def void generateReactorDefinitionsForFederate() { val generatedReactorDecls = newLinkedHashSet if (this.main !== null) { - generateReactorChildrenForReactorInFederate(this.main, federate, generatedReactorDecls); + generateReactorChildrenForReactorInFederate(this.main, generatedReactorDecls); } if (this.mainDef !== null) { - generateReactorFederated(this.mainDef.reactorClass, federate) + generateReactorFederated(this.mainDef.reactorClass) } // Generate code for each reactor that was not instantiated in main or its children. @@ -1117,7 +1118,7 @@ class CGenerator extends GeneratorBase { // generate code for it anyway (at a minimum, this means that the compiler is invoked // so that reaction bodies are checked). if (mainDef === null && declarations.isEmpty()) { - generateReactorFederated(r, null) + generateReactorFederated(r) } } } @@ -1133,23 +1134,21 @@ class CGenerator extends GeneratorBase { * - If there are any preambles, add them to the preambles of the reactor. * * @param reactor Used to extract children from - * @param federate All generated reactors will belong to this federate */ - def void generateReactorChildrenForReactorInFederate( + private def void generateReactorChildrenForReactorInFederate( ReactorInstance reactor, - FederateInstance federate, LinkedHashSet generatedReactorDecls ) { for (r : reactor.children) { - if (federate.contains(r)) { + if (currentFederate.contains(r)) { val declarations = this.instantiationGraph.getDeclarations(r.reactorDefinition); if (!declarations.isNullOrEmpty) { for (d : declarations) { if (!generatedReactorDecls.contains(d)) { generatedReactorDecls.add(d); - generateReactorChildrenForReactorInFederate(r, federate, generatedReactorDecls); + generateReactorChildrenForReactorInFederate(r, generatedReactorDecls); inspectReactorEResource(d); - generateReactorFederated(d, federate); + generateReactorFederated(d); } } } @@ -1366,13 +1365,11 @@ class CGenerator extends GeneratorBase { * * Clock synchronization can be enabled using the clock-sync target property. * @see https://github.com/icyphy/lingua-franca/wiki/Distributed-Execution#clock-synchronization - * - * @param federate The federate to initialize clock synchronizatino for */ - protected def initializeClockSynchronization(FederateInstance federate) { + protected def initializeClockSynchronization() { // Check if clock synchronization should be enabled for this federate in the first place if (targetConfig.clockSync != ClockSyncMode.OFF - && (!federationRTIProperties.get('host').toString.equals(federate.host) + && (!federationRTIProperties.get('host').toString.equals(currentFederate.host) || targetConfig.clockSyncOptions.localFederatesOn) ) { // Insert the #defines at the beginning @@ -1383,13 +1380,13 @@ class CGenerator extends GeneratorBase { #define _LF_CLOCK_SYNC_ATTENUATION «targetConfig.clockSyncOptions.attenuation» ''') System.out.println("Initial clock synchronization is enabled for federate " - + federate.id + + currentFederate.id ); if (targetConfig.clockSync == ClockSyncMode.ON) { var collectStatsEnable = '' if (targetConfig.clockSyncOptions.collectStats) { collectStatsEnable = "#define _LF_CLOCK_SYNC_COLLECT_STATS" - System.out.println("Will collect clock sync statistics for federate " + federate.id) + System.out.println("Will collect clock sync statistics for federate " + currentFederate.id) // Add libm to the compiler flags // FIXME: This is a linker flag not compile flag but we don't have a way to add linker flags // FIXME: This is probably going to fail on MacOS (especially using clang) @@ -1401,7 +1398,7 @@ class CGenerator extends GeneratorBase { «collectStatsEnable» ''') System.out.println("Runtime clock synchronization is enabled for federate " - + federate.id + + currentFederate.id ); } } @@ -1672,9 +1669,8 @@ class CGenerator extends GeneratorBase { * will not be generated if they are triggered by or send * data to contained reactors that are not in the federate. * @param reactor The parsed reactor data structure. - * @param federate A federate name, or null to unconditionally generate. */ - def generateReactorFederated(ReactorDecl reactor, FederateInstance federate) { + private def generateReactorFederated(ReactorDecl reactor) { // FIXME: Currently we're not reusing definitions for declarations that point to the same definition. val defn = reactor.toDefinition @@ -1692,11 +1688,11 @@ class CGenerator extends GeneratorBase { // go into the constructor. Collect those lines of code here: val constructorCode = new StringBuilder() val destructorCode = new StringBuilder() - generateAuxiliaryStructs(reactor, federate) - generateSelfStruct(reactor, federate, constructorCode, destructorCode) - generateReactions(reactor, federate) - generateConstructor(reactor, federate, constructorCode) - generateDestructor(reactor, federate, destructorCode) + generateAuxiliaryStructs(reactor, currentFederate) + generateSelfStruct(reactor, currentFederate, constructorCode, destructorCode) + generateReactions(reactor, currentFederate) + generateConstructor(reactor, currentFederate, constructorCode) + generateDestructor(reactor, currentFederate, destructorCode) pr("// =============== END reactor class " + reactor.name) pr("") @@ -3797,22 +3793,19 @@ class CGenerator extends GeneratorBase { /** * Generate code to instantiate the main reactor in the specified federate. - * @param federate The federate to instantiate or a singleton federate to generate everything. */ - private def void generateMain(FederateInstance federate) { - - currentFederate = federate; - + private def void generateMain() { + // Create lists of the actions, timers, and reactions that are in the federate. // These default to the full list for non-federated programs. var actionsInFederate = main.actions.filter[ - a | return federate.contains(a.definition); + a | return currentFederate.contains(a.definition); ]; var reactionsInFederate = main.reactions.filter[ - r | return federate.contains(r.definition); + r | return currentFederate.contains(r.definition); ]; var timersInFederate = main.timers.filter[ - t | return federate.contains(t.definition); + t | return currentFederate.contains(t.definition); ]; // Create an array to store all self structs. @@ -3839,7 +3832,7 @@ class CGenerator extends GeneratorBase { generateParameterInitialization(main); for (child: main.children) { - if (federate.contains(child)) { + if (currentFederate.contains(child)) { // If the child has a multiport that is an effect of some reaction in main, // then we have to generate code to allocate memory for arrays pointing to // its data. If the child is a bank, then memory is allocated for the entire @@ -3883,7 +3876,7 @@ class CGenerator extends GeneratorBase { // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. if (instance.isBank) { - val index = "i_" + instance.depth; + val index = INDEX_PREFIX + instance.depth; // Array is the self struct name, but without the indexing. var selfStructArrayName = instance.uniqueID + "_self" @@ -3901,12 +3894,17 @@ class CGenerator extends GeneratorBase { if (instance.isBank) { pr(initializeTriggerObjects, ''' «nameOfSelfStruct» = new_«reactorClass.name»(); + selfStructs[«instance.indexExpression(INDEX_PREFIX)»] = «nameOfSelfStruct»; ''') } else { pr(initializeTriggerObjects, ''' «structType»* «nameOfSelfStruct» = new_«reactorClass.name»(); ''') } + // Record the self struct on the big array of self structs. + pr(initializeTriggerObjects, ''' + selfStructs[«instance.indexExpression(INDEX_PREFIX)»] = «nameOfSelfStruct»; + ''') // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. @@ -4037,7 +4035,7 @@ class CGenerator extends GeneratorBase { } pr(initializeTriggerObjects, "//***** End initializing " + fullName) } - + /** * Initialize actions by creating a lf_token_t in the self struct. * This has the information required to allocate memory for the action payload. @@ -5219,18 +5217,17 @@ class CGenerator extends GeneratorBase { /** * Perform deferred initializations in initialize_trigger_objects. - * @param federate The federate for which we are doing this. */ - private def doDeferredInitialize(FederateInstance federate) { + private def doDeferredInitialize() { pr('// doDeferredInitialize') // For outputs that are not primitive types (of form type* or type[]), // create a default token on the self struct. - createDefaultTokens(main, federate) + createDefaultTokens(main) // Next, for every input port, populate its "self" struct // fields with pointers to the output port that sends it data. - connectInputsToOutputs(main, federate) + connectInputsToOutputs(main) } /** @@ -5239,48 +5236,54 @@ class CGenerator extends GeneratorBase { * source reactor. This has to be done after all reactors have been created * because inputs point to outputs that are arbitrarily far away. * @param instance The reactor instance. - * @param federate The federate for which we are generating code or null - * if there is no federation. */ - private def void connectInputsToOutputs(ReactorInstance instance, FederateInstance federate) { - if (!federate.contains(instance)) { + private def void connectInputsToOutputs(ReactorInstance instance) { + if (!currentFederate.contains(instance)) { return; } pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') // If the reactor is a bank, have to surround with a for loop. - FIXME wont work reactpr self struct is out of scope if (instance.isBank) { - val index = "i_" + instance.depth; + val index = INDEX_PREFIX + instance.depth; pr(''' // Initialize bank members. for (int «index»; «index» < «instance.width»; «index»++) { ''') indent(); } + + // Retrieve the self struct from the common array of self structs. + var nameOfSelfStruct = selfStructName(instance) + var structType = selfStructType(instance.definition.reactorClass) + pr(''' + «structType»* «nameOfSelfStruct» + = («structType»*)selfStructs[«instance.indexExpression(INDEX_PREFIX)»]; + ''') + // Iterate over all ports of this reactor that have dependent reactions. for (input : instance.inputs) { if (!input.dependentReactions.isEmpty()) { // Input has reactions. Connect it to its eventual source. - connectPortToEventualSource(input, federate); + connectPortToEventualSource(input); } } for (output : instance.outputs) { if (!output.dependentReactions.isEmpty() && output.dependsOnPorts.isEmpty()) { // Output has reactions and no upstream ports. // Connect it to its eventual source. - connectPortToEventualSource(output, federate); + connectPortToEventualSource(output); } } for (child : instance.children) { // In case this is a composite, recurse. - connectInputsToOutputs(child, federate) + connectInputsToOutputs(child) } // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an // output of a contained reactor. - connectReactionsToPorts(instance, federate) + connectReactionsToPorts(instance) if (instance.isBank) { unindent(); @@ -5296,10 +5299,8 @@ class CGenerator extends GeneratorBase { * port's reactor to the appropriate entries in the "self" struct of the * source reactor. * @param instance A port with dependant reactions. - * @param federate The federate for which we are generating code or null - * if there is no federation. */ - private def void connectPortToEventualSource(PortInstance port, FederateInstance federate) { + private def void connectPortToEventualSource(PortInstance port) { // Find the sources that send data to this port, // which could be the same port if it is an input port written to by a reaction // or it could be an upstream output port. @@ -5308,7 +5309,7 @@ class CGenerator extends GeneratorBase { var startChannel = 0; for (eventualSource: port.eventualSources()) { val src = eventualSource.portInstance; - if (src != port && federate.contains(src.parent)) { + if (src != port && currentFederate.contains(src.parent)) { // The eventual source is different from the port and is in the federate. val destStructType = variableStructType( port.definition as TypedVariable, @@ -5365,9 +5366,8 @@ class CGenerator extends GeneratorBase { * another contained reactor and reactions that are triggered by an * output of a contained reactor. * @param instance The reactor instance that contains the reactions. - * @param fedeate The federate instance. */ - private def connectReactionsToPorts(ReactorInstance instance, FederateInstance federate) { + private def connectReactionsToPorts(ReactorInstance instance) { for (reaction : instance.reactions) { // First handle the effects that are inputs of contained reactors. for (port : reaction.effects.filter(PortInstance)) { @@ -5375,7 +5375,7 @@ class CGenerator extends GeneratorBase { // This reaction is sending to an input. Must be // the input of a contained reactor. If the contained reactor is // not in the federate, then we don't do anything here. - if (federate.contains(port.parent)) { + if (currentFederate.contains(port.parent)) { val destStructType = variableStructType( port.definition as TypedVariable, port.parent.definition.reactorClass @@ -5406,7 +5406,7 @@ class CGenerator extends GeneratorBase { // This reaction is receiving data from an output // of a contained reactor. If the contained reactor is // not in the federate, then we don't do anything here. - if (federate.contains(port.parent)) { + if (currentFederate.contains(port.parent)) { val destStructType = variableStructType( port.definition as TypedVariable, port.parent.definition.reactorClass @@ -5919,15 +5919,14 @@ class CGenerator extends GeneratorBase { * For each output that has a token type (type* or type[]), * create a default token and put it on the self struct. * @param parent The container reactor. - * @param federate The federate, or null if there is no federation. */ - private def void createDefaultTokens(ReactorInstance parent, FederateInstance federate) { + private def void createDefaultTokens(ReactorInstance parent) { for (containedReactor : parent.children) { // Do this only for reactors in the federate. - if (federate.contains(containedReactor)) { + if (currentFederate.contains(containedReactor)) { // If the reactor is a bank, have to surround with a for loop. if (containedReactor.isBank) { - val index = "i_" + containedReactor.depth; + val index = INDEX_PREFIX + containedReactor.depth; pr(''' // Initialize bank members. for (int «index»; «index» < «containedReactor.width»; «index»++) { @@ -5935,6 +5934,14 @@ class CGenerator extends GeneratorBase { indent(); } var nameOfSelfStruct = selfStructName(containedReactor) + + // Retrieve the self struct from the common array of self structs. + var structType = selfStructType(containedReactor.definition.reactorClass) + pr(''' + «structType»* «nameOfSelfStruct» + = («structType»*)selfStructs[«containedReactor.indexExpression(INDEX_PREFIX)»]; + ''') + for (output : containedReactor.outputs) { val type = (output.definition as Output).inferredType if (type.isTokenType) { @@ -5958,7 +5965,7 @@ class CGenerator extends GeneratorBase { } } // In case this is a composite, handle its contained reactors. - createDefaultTokens(containedReactor, federate) + createDefaultTokens(containedReactor) if (containedReactor.isBank) { unindent(); pr(''' @@ -6176,4 +6183,7 @@ class CGenerator extends GeneratorBase { // FIXME: Get rid of this, if possible. /** The current federate for which we are generating code. */ var currentFederate = null as FederateInstance; + + /** Prefix used for-loop variables when iterating over bank members. */ + static val INDEX_PREFIX = "i_"; } From e5a73db7dd965fe058e7ad54360888514802c10a Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 19 Nov 2021 05:34:58 -0800 Subject: [PATCH 005/221] Compiles now and simple examples run. --- .../org/lflang/generator/c/CGenerator.xtend | 54 +++++++++++++++---- .../generator/python/PythonGenerator.xtend | 46 ++++++++++------ 2 files changed, 72 insertions(+), 28 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index aec25724d3..766309e6c3 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3816,7 +3816,7 @@ class CGenerator extends GeneratorBase { // The type of each struct type is different, so the type of this // array is vague. pr(initializeTriggerObjects, ''' - void* selfStructs[«main.getTotalNumReactorInstances()»] + void* selfStructs[«main.getTotalNumReactorInstances()»]; ''') // Generate the self struct declaration for the top level. @@ -5190,7 +5190,6 @@ class CGenerator extends GeneratorBase { return null as ErrorFileAndLine } - /** * Strip all line directives from the given C code. * @param code The code to remove # line directives from. @@ -5211,7 +5210,31 @@ class CGenerator extends GeneratorBase { } return builder.toString() } - + + /** + * Start a scoped section of code, surrounded by { ... }. + * @param builder The string builder to write to. + * @param description A description of the section of code. + */ + protected def startScopedSection(StringBuilder builder, String description) { + pr(builder, ''' + // «description» + { // For scoping + ''') + indent(); + } + + /** + * End a scoped section of code, surrounded by { ... }. + * @param builder The string builder to write to. + */ + protected def endScopedSection(StringBuilder builder) { + unindent(); + pr(builder, ''' + } // End scoped section + ''') + } + // ////////////////////////////////////////// // // Private methods. @@ -5223,11 +5246,15 @@ class CGenerator extends GeneratorBase { // For outputs that are not primitive types (of form type* or type[]), // create a default token on the self struct. - createDefaultTokens(main) + startScopedSection(code, "Create default tokens for token types."); + createDefaultTokens(main); + endScopedSection(code); // Next, for every input port, populate its "self" struct // fields with pointers to the output port that sends it data. + startScopedSection(code, "Connect inputs to outputs."); connectInputsToOutputs(main) + endScopedSection(code); } /** @@ -5935,16 +5962,21 @@ class CGenerator extends GeneratorBase { } var nameOfSelfStruct = selfStructName(containedReactor) - // Retrieve the self struct from the common array of self structs. - var structType = selfStructType(containedReactor.definition.reactorClass) - pr(''' - «structType»* «nameOfSelfStruct» - = («structType»*)selfStructs[«containedReactor.indexExpression(INDEX_PREFIX)»]; - ''') - + // Look for outputs with token types. + var foundOne = false; for (output : containedReactor.outputs) { val type = (output.definition as Output).inferredType if (type.isTokenType) { + if (!foundOne) { + // Retrieve the self struct from the common array of self structs. + var structType = selfStructType(containedReactor.definition.reactorClass) + pr(''' + «structType»* «nameOfSelfStruct» + = («structType»*)selfStructs[«containedReactor.indexExpression(INDEX_PREFIX)»]; + ''') + + foundOne = true; + } // Create the template token that goes in the trigger struct. // Its reference count is zero, enabling it to be used immediately. var rootType = type.targetType.rootType diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 04ceb404d6..1e2388a251 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -699,25 +699,37 @@ class PythonGenerator extends CGenerator { // Do not instantiate reactor classes that don't have a reaction in Python // Do not instantiate the federated main reactor since it is generated in C if (!instance.definition.reactorClass.toDefinition.allReactions.isEmpty && !instance.definition.reactorClass.toDefinition.isFederated) { - if (federate.contains(instance) && instance.isBank) { - // If this reactor is a bank, then generate - // a list of instances of reactors and return. - pythonClassesInstantiation. - append(''' - «instance.uniqueID»_lf = \ - [«FOR member : instance.bankMembers SEPARATOR ", \\\n"»\ - _«className»(bank_index = «member.bankIndex/* bank_index is specially assigned by us*/»,\ - «FOR param : member.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»)«ENDFOR»] + if (federate.contains(instance) && instance.width > 0 && !instance.definition.reactorClass.toDefinition.allReactions.isEmpty) { + if (instance.isBank) { + // If this reactor is a bank, then generate + // a list of instances of reactors and return. + pythonClassesInstantiation. + append(''' + «instance.uniqueID»_lf = \ + _«className»(bank_index = 0 /* bank_index is specially assigned by us*/»,\ + «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR») + ''') + for (var i = 1; i < instance.width; i++) { + pythonClassesInstantiation. + append(''' + _«className»(bank_index = «i» /* bank_index is specially assigned by us*/,\ + «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), \\\n + ''') + } + pythonClassesInstantiation. + append(''' + ] + ''') + return + } else { + // FIXME: Why does this add a bank_index if it's not a bank? + pythonClassesInstantiation.append(''' + «instance.uniqueID»_lf = \ + [_«className»(bank_index = 0 /* bank_index is specially assigned by us*/, \ + «FOR param : instance.parameters SEPARATOR ", \\\n"»_«param.name»=«param.pythonInitializer»«ENDFOR»)] ''') - return - } else if (instance.bankIndex === -1 && !instance.definition.reactorClass.toDefinition.allReactions.isEmpty) { - pythonClassesInstantiation.append(''' - «instance.uniqueID»_lf = \ - [_«className»(bank_index = 0«/* bank_index is specially assigned by us*/», \ - «FOR param : instance.parameters SEPARATOR ", \\\n"»_«param.name»=«param.pythonInitializer»«ENDFOR»)] - ''') + } } - } for (child : instance.children) { From 68cc1ce1b173da53e80d2b0f33c0e30b6df27571 Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 19 Nov 2021 14:40:21 -0800 Subject: [PATCH 006/221] Begin factoring out utility functions into CUtil.java --- .../lflang/federated/CGeneratorExtension.java | 7 +- .../org/lflang/generator/ReactorInstance.java | 12 +- .../org/lflang/generator/c/CGenerator.xtend | 208 ++++++------------ .../src/org/lflang/generator/c/CUtil.java | 140 ++++++++++++ .../generator/python/PythonGenerator.xtend | 5 +- 5 files changed, 225 insertions(+), 147 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/c/CUtil.java diff --git a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java index 82604068fa..6300ad0e0c 100644 --- a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java @@ -28,8 +28,9 @@ import org.lflang.ASTUtils; import org.lflang.TimeValue; -import org.lflang.generator.c.CGenerator; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.c.CGenerator; +import org.lflang.generator.c.CUtil; import org.lflang.lf.Delay; import org.lflang.lf.Input; import org.lflang.lf.Port; @@ -143,7 +144,7 @@ public static StringBuilder initializeTriggerForControlReactions( ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); Reactor reactor = ASTUtils.toDefinition(reactorClass); - String nameOfSelfStruct = CGenerator.selfStructName(instance); + String nameOfSelfStruct = CUtil.selfRef(instance); // Initialize triggers for network input control reactions for (Port trigger : federate.networkInputControlReactionsTriggers) { @@ -169,7 +170,7 @@ public static StringBuilder initializeTriggerForControlReactions( } } - nameOfSelfStruct = CGenerator.selfStructName(instance); + nameOfSelfStruct = CUtil.selfRef(instance); // Initialize the trigger for network output control reactions if it doesn't exists if (federate.networkOutputControlReactionsTrigger != null) { diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 6d51944640..c0b73ea6a5 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -426,24 +426,26 @@ public List initialParameterValue(Parameter parameter) { * in which this will be evaluated that specify which bank member is * desired for this reactor instance and any of its parents that is * a bank. The names of these variables should be rd, where r is - * the root string given here as an argument and d is the depth of + * the prefix string given here as an argument and d is the depth of * the reactor instance that represents a bank. * * If this is a top-level reactor, this appends "0" and returns. * If this is one level down, this appends "n", where n is the * sum of the total + * + * @param prefix The prefix used for index variables for bank members. */ - public String indexExpression(String root) { + public String indexExpression(String prefix) { if (depth == 0) return("0"); if (isBank()) { return( - root + depth + " * " + numReactorInstances // Position of the bank member relative to the bank. + prefix + depth + " * " + numReactorInstances // Position of the bank member relative to the bank. + " + " + indexOffset // Position of the bank within its parent. - + " + " + parent.indexExpression(root) // Position of the parent. + + " + " + parent.indexExpression(prefix) // Position of the parent. ); } else { return(indexOffset // Position within the parent. - + " + " + parent.indexExpression(root) // Position of the parent. + + " + " + parent.indexExpression(prefix) // Position of the parent. ); } } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 766309e6c3..f0861414ba 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -2668,7 +2668,7 @@ class CGenerator extends GeneratorBase { // trigger downstream reactions. for (reaction : reactions) { val instance = reaction.parent; - val nameOfSelfStruct = selfStructName(instance) + val nameOfSelfStruct = CUtil.selfRef(instance) generateReactionOutputs(reaction); @@ -3129,7 +3129,7 @@ class CGenerator extends GeneratorBase { */ private def generateRemoteTriggerTable(Iterable reactions) { for (reaction : reactions) { - val selfStruct = selfStructName(reaction.parent); + val selfStruct = CUtil.selfRef(reaction.parent); val name = reaction.parent.getFullName; var channelCount = 0 @@ -3238,7 +3238,7 @@ class CGenerator extends GeneratorBase { int reactionNumber ) { val reactorInstance = reaction.parent; - val selfStruct = selfStructName(reactorInstance) + val selfStruct = CUtil.selfRef(reactorInstance) // Record the number of reactions that this reaction depends on. // This is used for optimization. When that number is 1, the reaction can @@ -3255,7 +3255,7 @@ class CGenerator extends GeneratorBase { && currentFederate.contains(dominatingReaction.definition) && currentFederate.contains(dominatingReaction.parent) ) { - val upstreamReaction = '''«selfStructName(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' + val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' pr(initializeTriggerObjectsEnd, ''' // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); @@ -3278,7 +3278,7 @@ class CGenerator extends GeneratorBase { // input of a contained reactor that is present. for (child : instance.children) { if (currentFederate.contains(child)) { - var nameOfSelfStruct = selfStructName(child) + var nameOfSelfStruct = CUtil.selfRef(child) for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { if (input.isMultiport()) { @@ -3306,7 +3306,7 @@ class CGenerator extends GeneratorBase { } } } - var containerSelfStructName = selfStructName(instance) + var containerSelfStructName = CUtil.selfRef(instance) // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an // output of a contained reactor. @@ -3398,7 +3398,7 @@ class CGenerator extends GeneratorBase { // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { if (currentFederate.contains(child)) { - var nameOfSelfStruct = selfStructName(child) + var nameOfSelfStruct = CUtil.selfRef(child) for (output : child.outputs) { if (output.isMultiport()) { pr(startTimeStep, ''' @@ -3566,49 +3566,7 @@ class CGenerator extends GeneratorBase { ''' } } - - /** - * Return a string for referencing the struct with the value and is_present - * fields of the specified port. This is used for establishing the destination of - * data for a connection between ports. - * This will have the following form: - * - * * selfStruct->_lf_portName - * - * @param port An instance of a destination input port. - */ - static def destinationReference(PortInstance port) { - // Note that if the port is an output, then it must - // have dependent reactions, otherwise it would not - // be a destination. - var destStruct = selfStructName(port.parent) - return '''«destStruct»->_lf_«port.name»''' - } - - /** - * Return a string for referencing the port struct with the value - * and is_present fields in a self struct that receives data from - * the specified output port to be used by a reaction. - * The output port is contained by a contained reactor. - * This will have the following form: - * - * * selfStruct->_lf_reactorName.portName - * - * The selfStruct is that of the container of reactor that - * contains the port. - * - * @param port An instance of a destination port. - */ - static def reactionReference(PortInstance port) { - var destStruct = selfStructName(port.parent.parent) - - if (port.isOutput) { - return '''«destStruct»->_lf_«port.parent.name».«port.name»''' - } else { - return '// Nothing to do. Port is an input.' - } - } - + /** * Return a string for referencing the data or is_present value of * the specified port. This is used for establishing the source of @@ -3633,60 +3591,43 @@ class CGenerator extends GeneratorBase { */ static def sourceReference(PortInstance port) { if (port.isOutput()) { - val sourceStruct = selfStructName(port.parent); + val sourceStruct = CUtil.selfRef(port.parent); return '''«sourceStruct»->_lf_«port.name»''' } else { - val sourceStruct = selfStructName(port.parent.parent) + val sourceStruct = CUtil.selfRef(port.parent.parent) return '''«sourceStruct»->_lf_«port.parent.name».«port.name»''' } } - /** Return the unique name for the "self" struct of the specified - * reactor instance from the instance ID. If the instance is a - * bank of reactors, this returns something of the form - * name_self[i_d], where d is the depth of the reactor. - * This assumes that this will appear within a for loop that - * uses index i_d. The use of the depth qualifier enables the - * for loops to be nested when a bank contains other banks. - * @param instance The reactor instance. - * @return The name of the self struct. - */ - static def selfStructName(ReactorInstance instance) { - var result = instance.uniqueID + "_self" - // If this reactor is a member of a bank of reactors, then change - // the name of its self struct to append [index]. - if (instance.isBank) { - result += "[i_" + instance.getDepth() + "]" - } - return result - } - - /** Construct a unique type for the "self" struct of the specified - * reactor class from the reactor class. - * @param reactor The reactor class. - * @return The name of the self struct. + /** + * Construct a unique type for the "self" struct of the specified + * reactor class from the reactor class. + * @param reactor The reactor class. + * @return The name of the self struct. */ - def selfStructType(ReactorDecl reactor) { + static def selfStructType(ReactorDecl reactor) { return reactor.name.toLowerCase + "_self_t" } - /** Construct a unique type for the struct of the specified - * typed variable (port or action) of the specified reactor class. - * @param variable The variable. - * @param reactor The reactor class. - * @return The name of the self struct. + /** + * Construct a unique type for the struct of the specified + * typed variable (port or action) of the specified reactor class. + * @param variable The variable. + * @param reactor The reactor class. + * @return The name of the self struct. */ - def variableStructType(Variable variable, ReactorDecl reactor) { + static def variableStructType(Variable variable, ReactorDecl reactor) { '''«reactor.name.toLowerCase»_«variable.name»_t''' } - /** Return the function name for specified reaction of the - * specified reactor. - * @param reactor The reactor - * @param reactionIndex The reaction index. - * @return The function name for the reaction. + /** + * Return the function name for specified reaction of the + * specified reactor. + * @param reactor The reactor + * @param reactionIndex The reaction index. + * @return The function name for the reaction. */ - def reactionFunctionName(ReactorDecl reactor, int reactionIndex) { + static def reactionFunctionName(ReactorDecl reactor, int reactionIndex) { reactor.name.toLowerCase + "reaction_function_" + reactionIndex } @@ -3697,7 +3638,7 @@ class CGenerator extends GeneratorBase { * @return The name of the trigger struct. */ static def triggerStructName(TriggerInstance instance) { - return selfStructName(instance.parent) + return CUtil.selfRef(instance.parent) + '''->_lf__''' + instance.name } @@ -3710,7 +3651,7 @@ class CGenerator extends GeneratorBase { * of the container of the reaction. */ static def triggerStructName(PortInstance port, ReactorInstance reactor) { - return '''«selfStructName(reactor)»->_lf_«port.parent.name».«port.name»_trigger''' + return '''«CUtil.selfRef(reactor)»->_lf_«port.parent.name».«port.name»_trigger''' } /** @@ -3774,7 +3715,7 @@ class CGenerator extends GeneratorBase { // the header information in the trace file. if (targetConfig.tracing !== null) { var description = getShortenedName(instance) - var nameOfSelfStruct = selfStructName(instance) + var nameOfSelfStruct = CUtil.selfRef(instance) pr(initializeTriggerObjects, ''' _lf_register_trace_event(«nameOfSelfStruct», NULL, trace_reactor, "«description»"); ''') @@ -3821,8 +3762,8 @@ class CGenerator extends GeneratorBase { // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' - «selfStructType(main.definition.reactorClass)»* «selfStructName(main)» = new_«main.name»(); - selfStructs[0] = «selfStructName(main)»; + «selfStructType(main.definition.reactorClass)»* «CUtil.selfRef(main)» = new_«main.name»(); + selfStructs[0] = «CUtil.selfRef(main)»; ''') // Generate code for top-level parameters, actions, timers, and reactions that @@ -3870,13 +3811,13 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, '// ************* Instance ' + fullName + ' of class ' + reactorClass.name) - var nameOfSelfStruct = selfStructName(instance) + var nameOfSelfStruct = CUtil.selfRef(instance) var structType = selfStructType(reactorClass) // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. if (instance.isBank) { - val index = INDEX_PREFIX + instance.depth; + val index = CUtil.INDEX_PREFIX + instance.depth; // Array is the self struct name, but without the indexing. var selfStructArrayName = instance.uniqueID + "_self" @@ -3894,7 +3835,6 @@ class CGenerator extends GeneratorBase { if (instance.isBank) { pr(initializeTriggerObjects, ''' «nameOfSelfStruct» = new_«reactorClass.name»(); - selfStructs[«instance.indexExpression(INDEX_PREFIX)»] = «nameOfSelfStruct»; ''') } else { pr(initializeTriggerObjects, ''' @@ -3903,7 +3843,7 @@ class CGenerator extends GeneratorBase { } // Record the self struct on the big array of self structs. pr(initializeTriggerObjects, ''' - selfStructs[«instance.indexExpression(INDEX_PREFIX)»] = «nameOfSelfStruct»; + selfStructs[«instance.indexExpression(CUtil.INDEX_PREFIX)»] = «nameOfSelfStruct»; ''') // Generate code to initialize the "self" struct in the @@ -4062,7 +4002,7 @@ class CGenerator extends GeneratorBase { } } - var nameOfSelfStruct = selfStructName(action.parent); + var nameOfSelfStruct = CUtil.selfRef(action.parent); // Create a reference token initialized to the payload size. // This token is marked to not be freed so that the trigger_t struct @@ -4202,8 +4142,8 @@ class CGenerator extends GeneratorBase { // Port is a multiport input that the parent's reaction is writing to. portsHandled.add(effect); - val nameOfSelfStruct = selfStructName(reactor.parent); - var containerName = reactor.name; + val nameOfSelfStruct = CUtil.selfRef(reactor.parent); + var containerName = CUtil.reactorRef(reactor); val portStructType = variableStructType( effect.definition, reactor.definition.reactorClass); @@ -4237,7 +4177,7 @@ class CGenerator extends GeneratorBase { // Port is an effect of a parent's reaction. // That is, the port belongs to the same reactor as the reaction. // The reaction is writing to an output of its container reactor. - val nameOfSelfStruct = selfStructName(port.parent); + val nameOfSelfStruct = CUtil.selfRef(port.parent); val portStructType = variableStructType( port.definition, port.parent.definition.reactorClass @@ -4280,7 +4220,7 @@ class CGenerator extends GeneratorBase { * @param The reaction instance. */ private def void generateReactionOutputs(ReactionInstance reaction) { - val nameOfSelfStruct = selfStructName(reaction.parent); + val nameOfSelfStruct = CUtil.selfRef(reaction.parent); // Count the output ports and inputs of contained reactors that // may be set by this reaction. This ignores actions in the effects. @@ -4391,7 +4331,7 @@ class CGenerator extends GeneratorBase { */ def generateStateVariableInitializations(ReactorInstance instance) { val reactorClass = instance.definition.reactorClass - val nameOfSelfStruct = selfStructName(instance) + val nameOfSelfStruct = CUtil.selfRef(instance) for (stateVar : reactorClass.toDefinition.stateVars) { val initializer = getInitializer(stateVar, instance) @@ -4442,7 +4382,7 @@ class CGenerator extends GeneratorBase { for (reaction : reactions) { if (reaction.declaredDeadline !== null) { var deadline = reaction.declaredDeadline.maxDelay - val reactionStructName = '''«selfStructName(reaction.parent)»->_lf__reaction_«reaction.index»''' + val reactionStructName = '''«CUtil.selfRef(reaction.parent)»->_lf__reaction_«reaction.index»''' pr(initializeTriggerObjects, ''' «reactionStructName».deadline = «timeInTargetLanguage(deadline)»; ''') @@ -4455,7 +4395,7 @@ class CGenerator extends GeneratorBase { * @param instance The reactor instance. */ def void generateParameterInitialization(ReactorInstance instance) { - var nameOfSelfStruct = selfStructName(instance) + var nameOfSelfStruct = CUtil.selfRef(instance) // Array type parameters have to be handled specially. // Use the superclass getTargetType to avoid replacing the [] with *. for (parameter : instance.parameters) { @@ -4515,9 +4455,9 @@ class CGenerator extends GeneratorBase { if (contained !== null) { // Caution: If port belongs to a contained reactor, the self struct needs to be that // of the contained reactor instance, not this containe - selfStruct = selfStructName(reactorInstance.getChildReactorInstance(contained)) + selfStruct = CUtil.selfRef(reactorInstance.getChildReactorInstance(contained)) } else { - selfStruct =selfStructName(reactorInstance); + selfStruct =CUtil.selfRef(reactorInstance); } } if (port.widthSpec !== null) { @@ -4547,7 +4487,7 @@ class CGenerator extends GeneratorBase { for (i : state?.init) { if (i.parameter !== null) { - list.add(parent.selfStructName + "->" + i.parameter.name) + list.add(CUtil.selfRef(parent) + "->" + i.parameter.name) } else if (state.isOfTimeType) { list.add(i.targetTime) } else { @@ -4573,7 +4513,7 @@ class CGenerator extends GeneratorBase { if (federate === null || federate.contains( r.definition )) { - val reactionStructName = '''«selfStructName(r.parent)»->_lf__reaction_«r.index»''' + val reactionStructName = '''«CUtil.selfRef(r.parent)»->_lf__reaction_«r.index»''' // xtend doesn't support bitwise operators... val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, r.level) val reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL" @@ -5217,11 +5157,9 @@ class CGenerator extends GeneratorBase { * @param description A description of the section of code. */ protected def startScopedSection(StringBuilder builder, String description) { - pr(builder, ''' - // «description» - { // For scoping - ''') - indent(); + pr(builder, "// " + description); + pr(builder, "{ // For scoping"); + indent(builder); } /** @@ -5229,10 +5167,8 @@ class CGenerator extends GeneratorBase { * @param builder The string builder to write to. */ protected def endScopedSection(StringBuilder builder) { - unindent(); - pr(builder, ''' - } // End scoped section - ''') + unindent(builder); + pr(builder, "} // End scoped section"); } // ////////////////////////////////////////// @@ -5272,7 +5208,7 @@ class CGenerator extends GeneratorBase { // If the reactor is a bank, have to surround with a for loop. if (instance.isBank) { - val index = INDEX_PREFIX + instance.depth; + val index = CUtil.INDEX_PREFIX + instance.depth; pr(''' // Initialize bank members. for (int «index»; «index» < «instance.width»; «index»++) { @@ -5281,11 +5217,11 @@ class CGenerator extends GeneratorBase { } // Retrieve the self struct from the common array of self structs. - var nameOfSelfStruct = selfStructName(instance) + var nameOfSelfStruct = CUtil.selfRef(instance) var structType = selfStructType(instance.definition.reactorClass) pr(''' «structType»* «nameOfSelfStruct» - = («structType»*)selfStructs[«instance.indexExpression(INDEX_PREFIX)»]; + = («structType»*)selfStructs[«instance.indexExpression(CUtil.INDEX_PREFIX)»]; ''') // Iterate over all ports of this reactor that have dependent reactions. @@ -5358,7 +5294,7 @@ class CGenerator extends GeneratorBase { { // To scope variable j int j = «eventualSource.startChannel»; for (int i = «startChannel»; i < «eventualSource.channelWidth» + «startChannel»; i++) { - «destinationReference(port)»[i] = («destStructType»*)«modifier»«sourceReference(src)»[j++]; + «CUtil.destinationRef(port)»[i] = («destStructType»*)«modifier»«sourceReference(src)»[j++]; } } ''') @@ -5367,21 +5303,21 @@ class CGenerator extends GeneratorBase { // Source is a multiport, destination is a single port. pr(''' // Connect «src.getFullName» to port «port.getFullName» - «destinationReference(port)» = («destStructType»*)«modifier»«sourceReference(src)»[«eventualSource.startChannel»]; + «CUtil.destinationRef(port)» = («destStructType»*)«modifier»«sourceReference(src)»[«eventualSource.startChannel»]; ''') } } else if (port.isMultiport()) { // Source is a single port, Destination is a multiport. pr(''' // Connect «src.getFullName» to port «port.getFullName» - «destinationReference(port)»[«startChannel»] = («destStructType»*)&«sourceReference(src)»; + «CUtil.destinationRef(port)»[«startChannel»] = («destStructType»*)&«sourceReference(src)»; ''') startChannel++; } else { // Both ports are single ports. pr(''' // Connect «src.getFullName» to port «port.getFullName» - «destinationReference(port)» = («destStructType»*)&«sourceReference(src)»; + «CUtil.destinationRef(port)» = («destStructType»*)&«sourceReference(src)»; ''') } } @@ -5412,14 +5348,14 @@ class CGenerator extends GeneratorBase { // Connect «port», which gets data from reaction «reaction.index» // of «instance.getFullName», to «port.getFullName». for (int i = 0; i < «port.width»; i++) { - «destinationReference(port)»[i] = («destStructType»*)«sourceReference(port)»[i]; + «CUtil.destinationRef(port)»[i] = («destStructType»*)«sourceReference(port)»[i]; } ''') } else { pr(''' // Connect «port», which gets data from reaction «reaction.index» // of «instance.getFullName», to «port.getFullName». - «destinationReference(port)» = («destStructType»*)&«sourceReference(port)»; + «CUtil.destinationRef(port)» = («destStructType»*)&«sourceReference(port)»; ''') } // FIXME: Don't we also to set set the destination reference for more @@ -5448,7 +5384,7 @@ class CGenerator extends GeneratorBase { // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. for (int i = 0; i < «eventualSource.channelWidth»; i++) { - «reactionReference(port)»[i + «portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»[i + «eventualSource.startChannel»]; + «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»[i + «eventualSource.startChannel»]; } ''') portChannelCount += eventualSource.channelWidth; @@ -5457,7 +5393,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «reactionReference(port)» = («destStructType»*)&«sourceReference(sourcePort)»[«eventualSource.startChannel»]; + «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»[«eventualSource.startChannel»]; ''') portChannelCount++; } else if (port.isMultiport) { @@ -5465,7 +5401,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «reactionReference(port)»[«portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»; + «CUtil.containedPortRef(port)»[«portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»; ''') portChannelCount++; } else { @@ -5473,7 +5409,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «reactionReference(port)» = («destStructType»*)&«sourceReference(sourcePort)»; + «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»; ''') portChannelCount++; } @@ -5953,14 +5889,14 @@ class CGenerator extends GeneratorBase { if (currentFederate.contains(containedReactor)) { // If the reactor is a bank, have to surround with a for loop. if (containedReactor.isBank) { - val index = INDEX_PREFIX + containedReactor.depth; + val index = CUtil.INDEX_PREFIX + containedReactor.depth; pr(''' // Initialize bank members. for (int «index»; «index» < «containedReactor.width»; «index»++) { ''') indent(); } - var nameOfSelfStruct = selfStructName(containedReactor) + var nameOfSelfStruct = CUtil.selfRef(containedReactor) // Look for outputs with token types. var foundOne = false; @@ -5972,7 +5908,7 @@ class CGenerator extends GeneratorBase { var structType = selfStructType(containedReactor.definition.reactorClass) pr(''' «structType»* «nameOfSelfStruct» - = («structType»*)selfStructs[«containedReactor.indexExpression(INDEX_PREFIX)»]; + = («structType»*)selfStructs[«containedReactor.indexExpression(CUtil.INDEX_PREFIX)»]; ''') foundOne = true; @@ -6216,6 +6152,4 @@ class CGenerator extends GeneratorBase { /** The current federate for which we are generating code. */ var currentFederate = null as FederateInstance; - /** Prefix used for-loop variables when iterating over bank members. */ - static val INDEX_PREFIX = "i_"; } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java new file mode 100644 index 0000000000..753c80e71b --- /dev/null +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -0,0 +1,140 @@ +/* Utilities for C code generation. */ + +/************* +Copyright (c) 2019-2021, 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 org.lflang.generator.PortInstance; +import org.lflang.generator.ReactorInstance; + +/** + * A collection of utilties for C code generation. + * This class codifies the coding conventions for the C target code generator. + * I.e., it defines how variables are named and referenced. + */ +public class CUtil { + + ////////////////////////////////////////////////////// + //// Public methods. + + /** + * Return a name of a variable to refer to the bank index of a reactor + * in a bank. This is has the form INDEX_PREFIX_i where i is the depth + * of the reactor (how many parents it has). + * @param instance A reactor instance. + */ + static public String bankIndex(ReactorInstance instance) { + return INDEX_PREFIX + instance.getDepth(); + } + + /** + * Return a string for referencing the port struct (which has the value + * and is_present fields) in the self struct of the port's parent's + * parent. This is used by reactions that are triggered by an output + * of a contained reactor and by reactions that send data to inputs + * of a contained reactor. This will have one of the following forms: + * + * * selfStructRef->_lf_reactorName.portName + * * selfStructRef->_lf_reactorName[id].portName + * + * Where the selfStructRef is as returned by selfRef(). + * + * @param port The port. + */ + static public String containedPortRef(PortInstance port) { + var destStruct = CUtil.selfRef(port.getParent().getParent()); + return destStruct + "->_lf_" + reactorRef(port.getParent()) + "." + port.getName(); + } + + /** + * Return a string for referencing the struct with the value and is_present + * fields of the specified port. This is used for establishing the destination of + * data for a connection between ports. + * This will have the following form: + * + * * selfStruct->_lf_portName + * + * @param port An instance of a destination input port. + */ + static public String destinationRef(PortInstance port) { + // Note that if the port is an output, then it must + // have dependent reactions, otherwise it would not + // be a destination. + return selfRef(port.getParent()) + "->_lf_" + port.getName(); + } + + /** + * Return a reference to the specified reactor instance within a self + * struct. The result has one of the following forms: + * + * * instanceName + * * instanceName[id] + * + * where "id" is a variable referring to the bank index if the reactor + * is a bank. + * + * @param instance The reactor instance. + * @return A reference to the reactor within a self struct. + */ + static public String reactorRef(ReactorInstance instance) { + // If this reactor is a member of a bank of reactors, then change + // the name of its self struct to append [index]. + if (instance.isBank()) { + return instance.getName() + "[" + INDEX_PREFIX + instance.getDepth() + "]"; + } else { + return instance.getName(); + } + } + + /** + * Return the unique reference for the "self" struct of the specified + * reactor instance. If the instance is a bank of reactors, this returns + * something of the form name_self[id], where i is INDEX_PREFIX and + * d is the depth of the reactor. This assumes that the resulting string + * will be used in a context that defines a variable id. The result has + * one of the following forms: + * + * * uniqueID + * * uniqueID[id] + * + * @param instance The reactor instance. + * @return A reference to the self struct. + */ + static public String selfRef(ReactorInstance instance) { + var result = instance.uniqueID() + "_self"; + // If this reactor is a member of a bank of reactors, then change + // the name of its self struct to append [index]. + if (instance.isBank()) { + result += "[" + INDEX_PREFIX + instance.getDepth() + "]"; + } + return result; + } + + ////////////////////////////////////////////////////// + //// Public constants. + + /** Prefix used for-loop variables when iterating over bank members. */ + public static String INDEX_PREFIX = "i_"; +} diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 1e2388a251..e1222a7ce2 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -52,6 +52,7 @@ import org.lflang.generator.ReactionInstance import org.lflang.generator.ReactorInstance import org.lflang.generator.c.CCompiler import org.lflang.generator.c.CGenerator +import org.lflang.generator.c.CUtil import org.lflang.lf.Action import org.lflang.lf.Delay import org.lflang.lf.Input @@ -1624,7 +1625,7 @@ class PythonGenerator extends CGenerator { // Here, we attempt to convert the parameter value to // integer. If it succeeds, we also initialize it in C. // If it fails, we defer the initialization to Python. - var nameOfSelfStruct = selfStructName(instance) + var nameOfSelfStruct = CUtil.selfRef(instance) for (parameter : instance.parameters) { val initializer = parameter.getInitializer try { @@ -1670,7 +1671,7 @@ class PythonGenerator extends CGenerator { override void generateReactorInstanceExtension( ReactorInstance instance, Iterable reactions ) { - var nameOfSelfStruct = selfStructName(instance) + var nameOfSelfStruct = CUtil.selfRef(instance) var reactor = instance.definition.reactorClass.toDefinition // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C From aecf66d9f573cf3c14cd8001b27b571b304569a9 Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 19 Nov 2021 18:16:35 -0800 Subject: [PATCH 007/221] Separated out methods that generate code that has to be executed after all reactors have been instantiated, applied a common naming convention, and used the array of self struct pointers to solve scoping problems. --- .../org/lflang/generator/c/CGenerator.xtend | 1526 ++++++++--------- 1 file changed, 755 insertions(+), 771 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index f0861414ba..ffc1f79aea 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -327,9 +327,6 @@ class CGenerator extends GeneratorBase { // Place to collect code to initialize the trigger objects for all reactor instances. protected var initializeTriggerObjects = new StringBuilder() - // Place to collect code to go at the end of the _lf_initialize_trigger_objects() function. - var initializeTriggerObjectsEnd = new StringBuilder() - // The command to run the generated code if specified in the target directive. var runCommand = new ArrayList() @@ -626,7 +623,6 @@ class CGenerator extends GeneratorBase { // Clear out previously generated code. code = new StringBuilder(commonCode) initializeTriggerObjects = new StringBuilder() - initializeTriggerObjectsEnd = new StringBuilder() // Enable clock synchronization if the federate // is not local and clock-sync is enabled @@ -1011,7 +1007,12 @@ class CGenerator extends GeneratorBase { // Assign appropriate pointers to the triggers // FIXME: For python target, almost surely in the wrong place. pr(CGeneratorExtension.initializeTriggerForControlReactions(this.main, federate, this).toString()); - doDeferredInitialize() + + var reactionsInFederate = main.reactions.filter[ + r | return currentFederate.contains(r.definition); + ]; + + deferredInitialize(main, reactionsInFederate) // Put the code here to set up the tables that drive resetting is_present and // decrementing reference counts between time steps. This code has to appear @@ -2659,42 +2660,19 @@ class CGenerator extends GeneratorBase { } /** - * Generate code to allocate the memory needed by reactions for triggering - * downstream reactions. Also, record startup and shutdown reactions. + * Record startup and shutdown reactions. * @param reactions A list of reactions. */ - private def void generateReactionMemory(Iterable reactions) { + private def void recordStartupAndShutdown(Iterable reactions) { // For each reaction instance, allocate the arrays that will be used to // trigger downstream reactions. for (reaction : reactions) { val instance = reaction.parent; val nameOfSelfStruct = CUtil.selfRef(instance) - generateReactionOutputs(reaction); - // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. for (trigger : reaction.triggers) { - if (trigger instanceof PortInstance) { - // If the port is a multiport, then we need to create an entry for each - // individual port. - if (trigger.isMultiport() && trigger.parent !== null && trigger.isOutput) { - // If the width is given as a numeric constant, then add that constant - // to the output count. Otherwise, assume it is a reference to one or more parameters. - val width = trigger.width; - val containerName = trigger.parent.name - val portStructType = variableStructType(trigger.definition, - trigger.parent.definition.reactorClass) - - // FIXME: What if the effect is a bank? Need to index the container. - pr(initializeTriggerObjectsEnd, ''' - «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width = «width»; - // Allocate memory to store pointers to the multiport outputs of a contained reactor. - «nameOfSelfStruct»->_lf_«containerName».«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width); - ''') - } - } if (trigger.isStartup) { pr(initializeTriggerObjects, ''' _lf_startup_reactions[«startupReactionCount++»] = &«nameOfSelfStruct»->_lf__reaction_«reaction.index»; @@ -2715,7 +2693,7 @@ class CGenerator extends GeneratorBase { } } } - + /** * Generate code that passes existing intended tag to all output ports * and actions. This intended tag is the minimum intended tag of the @@ -3112,162 +3090,6 @@ class CGenerator extends GeneratorBase { return result } - /** - * Generate code to create the trigger table for each given reaction. - * Each table lists the triggers that the reaction - * execution may trigger. Each table is an array of arrays - * of pointers to the trigger_t structs representing the downstream inputs - * (or outputs of the container reactor) that are triggered by the reaction. - * Each trigger table goes into the reaction's reaction_t triggers field. - * That reaction_t struct is assumed to be on the self struct of the reactor - * instance with name "_lf__reaction_i", where i is the index of the reaction. - * The generated code will also set the values of the triggered_sizes array - * on the reaction_t struct to indicate the size of each array of trigger_t - * pointers. The generated code will malloc each of these arrays, and the - * destructor for the reactor instance will free them. - * @param reactions The reactions. - */ - private def generateRemoteTriggerTable(Iterable reactions) { - for (reaction : reactions) { - val selfStruct = CUtil.selfRef(reaction.parent); - val name = reaction.parent.getFullName; - var channelCount = 0 - - optimizeForSingleDominatingReaction(reaction, reaction.index); - - // Insert a string name to facilitate debugging. - if (targetConfig.logLevel >= LogLevel.LOG) { - pr(initializeTriggerObjectsEnd, ''' - // Reaction «reaction.index» of «name». - «selfStruct»->_lf__reaction_«reaction.index».name = "«name» reaction «reaction.index»"; - ''') - } - - for (port : reaction.effects.filter(PortInstance)) { - // Skip ports whose parent is not in the federation. - // This can happen with reactions in the top-level that have - // as an effect a port in a bank. - if (currentFederate.contains(port.parent)) { - - // If the port is a multiport, then its channels may have different sets - // of destinations. For ordinary ports, there will be only one range and - // its width will be 1. - // We generate the code to fill the triggers array first in a temporary buffer, - // so that we can simultaneously calculate the size of the total array. - for (PortInstance.SendRange range : port.eventualDestinations()) { - val temp = new StringBuilder(); - var destRangeCount = 0; - for (destinationRange : range.destinations) { - val destination = destinationRange.getPortInstance(); - if (destination.isOutput) { - // Include this destination port only if it has at least one - // reaction in the federation. - var belongs = false; - for (destinationReaction : destination.dependentReactions) { - if (currentFederate.contains(destinationReaction.parent)) { - belongs = true - } - } - if (belongs) { - pr(temp, ''' - // Port «port.getFullName» has reactions in its parent's parent. - // Point to the trigger struct for those reactions. - triggerArray[«destRangeCount»] = &«triggerStructName( - destination, - destination.parent.parent - )»; - ''') - // One array entry for each destination range is sufficient. - destRangeCount++; - } - } else { - // Destination is an input port. - pr(temp, ''' - // Point to destination port «destination.getFullName»'s trigger struct. - triggerArray[«destRangeCount»] = &«triggerStructName(destination)»; - ''') - // One array entry for each destination range is sufficient. - destRangeCount++; - } - } - - // Record the total size of the array. - pr(initializeTriggerObjectsEnd, ''' - for (int i = 0; i < «range.channelWidth»; i++) { - // Reaction «reaction.index» of «name» triggers «channelCount» - // downstream reactions through port «port.getFullName»[«channelCount» + i]. - «selfStruct»->_lf__reaction_«reaction.index».triggered_sizes[«channelCount» + i] = «destRangeCount»; - } - ''') - - // Malloc the memory for the arrays. - pr(initializeTriggerObjectsEnd, ''' - { // For scoping - // For reaction «reaction.index» of «name», allocate an - // array of trigger pointers for downstream reactions through port «port.getFullName» - trigger_t** triggerArray = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); - for (int i = 0; i < «range.channelWidth»; i++) { - «selfStruct»->_lf__reaction_«reaction.index».triggers[«channelCount» + i] = triggerArray; - } - // Fill the trigger array. - «temp.toString()» - } - ''') - channelCount += range.channelWidth; - } - } else { - // Count the port even if it is not contained in the federate because effect - // may be a bank (it can't be an instance of a bank), so an empty placeholder - // will be needed for each member of the bank that is not in the federate. - channelCount += port.width; - } - } - } - } - - /** - * Set the last_enabling_reaction field of the reaction struct to point - * to the single dominating upstream reaction, if there is one, or to be - * NULL if not. - * - * @param reaction The reaction. - * @param reactionNumber The reaction number within the parent. - */ - def optimizeForSingleDominatingReaction ( - ReactionInstance reaction, - int reactionNumber - ) { - val reactorInstance = reaction.parent; - val selfStruct = CUtil.selfRef(reactorInstance) - - // Record the number of reactions that this reaction depends on. - // This is used for optimization. When that number is 1, the reaction can - // be executed immediately when its triggering reaction has completed. - var dominatingReaction = reaction.findSingleDominatingReaction(); - - // The dominating reaction may not be included in this federate, in which case, we need to keep searching. - while (dominatingReaction !== null - && (!currentFederate.contains(dominatingReaction.definition)) - ) { - dominatingReaction = dominatingReaction.findSingleDominatingReaction(); - } - if (dominatingReaction !== null - && currentFederate.contains(dominatingReaction.definition) - && currentFederate.contains(dominatingReaction.parent) - ) { - val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' - pr(initializeTriggerObjectsEnd, ''' - // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. - «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); - ''') - } else { - pr(initializeTriggerObjectsEnd, ''' - // Reaction «reactionNumber» of «reactorInstance.getFullName» does not depend on one maximal upstream reaction. - «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = NULL; - ''') - } - } - /** * 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 @@ -3774,23 +3596,14 @@ class CGenerator extends GeneratorBase { for (child: main.children) { if (currentFederate.contains(child)) { - // If the child has a multiport that is an effect of some reaction in main, - // then we have to generate code to allocate memory for arrays pointing to - // its data. If the child is a bank, then memory is allocated for the entire - // bank width because a reaction cannot specify which bank members it writes - // to so we have to assume it can write to any. - generateAllocationForEffectsOnInputs(child); - generateReactorInstance(child); } } - generateReactionMemory(reactionsInFederate); + recordStartupAndShutdown(reactionsInFederate); generateStateVariableInitializations(main); - generateRemoteTriggerTable(reactionsInFederate); generateTimerInitializations(timersInFederate); generateActionInitializations(actionsInFederate); - generateInputNumDestinations(reactionsInFederate); generateInitializeActionToken(actionsInFederate); generateSetDeadline(reactionsInFederate); generateStartTimeStep(main); @@ -3869,15 +3682,7 @@ class CGenerator extends GeneratorBase { } } - // For each input and output that is a multiport and an effect of some reaction, - // generate code to allocate memory for arrays pointing to its data. Do this here - // for the inputs of the children and the outputs of this reactor. - for (child : instance.children) { - generateAllocationForEffectsOnInputs(child); - } - - generateAllocationForEffectsOnOutputs(instance); - generateReactionMemory(instance.reactions); + recordStartupAndShutdown(instance.reactions); // Next, allocate memory for input. // NOTE: Not done for top level. @@ -3905,16 +3710,10 @@ class CGenerator extends GeneratorBase { // These values may be expressions that refer to the parameter values defined above. generateStateVariableInitializations(instance); - generateRemoteTriggerTable(instance.reactions); - // Generate trigger objects for the instance. generateTimerInitializations(instance.timers); generateActionInitializations(instance.actions); - - // Initialize the num_destinations fields of port structs on the self struct. - generateOutputNumDestinations(instance); // NOTE: Not done for top level. - generateInputNumDestinations(instance.reactions); - + generateInitializeActionToken(instance.actions); generateSetDeadline(instance.reactions); @@ -3962,10 +3761,6 @@ class CGenerator extends GeneratorBase { // so that it can deallocate any memory. generateStartTimeStep(instance) - // End code must go here because it references the for loop variable if we are in a bank. - pr(initializeTriggerObjects, initializeTriggerObjectsEnd) - initializeTriggerObjectsEnd = new StringBuilder(); - if (instance.isBank()) { // Close the for loop. unindent(initializeTriggerObjects); @@ -4029,305 +3824,27 @@ class CGenerator extends GeneratorBase { } /** - * For each output port of the specified reactor, - * set the num_destinations field of port structs on its self struct - * equal to the total number of destination reactors. This is used - * to initialize reference counts in dynamically allocated tokens - * sent to other reactors. - * @param reactor The reactor instance. + * Generate code that is executed while the reactor instance is being initialized. + * This is provided as an extension point for subclasses. + * Normally, the reactions argument is the full list of reactions, + * but for the top-level of a federate, will be a subset of reactions that + * is relevant to the federate. + * @param instance The reactor instance. + * @param reactions The reactions of this instance. */ - private def void generateOutputNumDestinations(ReactorInstance reactor) { - // Reference counts are decremented by each destination reactor - // at the conclusion of a time step. Hence, the initial reference - // count should equal the number of destination _reactors_, not the - // number of destination ports nor the number of destination reactions. - // One of the destination reactors may be the container of this - // instance because it may have a reaction to an output of this instance. - for (output : reactor.outputs) { - for (sendingRange : output.eventualDestinations) { - // Syntax is slightly difference for a multiport output vs. single port. - // For a single port, there should be only one sendingRange. - if (output.isMultiport()) { - val start = sendingRange.startChannel; - val end = sendingRange.startChannel + sendingRange.channelWidth; - // Eliminate the for loop for the case where range.channelWidth == 1, - // a common situation on multiport to bank messaging. - if (sendingRange.channelWidth == 1) { - pr(initializeTriggerObjectsEnd, ''' - «sourceReference(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; - ''') - } else { - pr(initializeTriggerObjectsEnd, ''' - for (int i = «start»; i < «end»; i++) { - «sourceReference(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; - } - ''') - } - } else { - pr(initializeTriggerObjectsEnd, ''' - «sourceReference(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; - ''') - } - } - } + def void generateReactorInstanceExtension( + ReactorInstance instance, + Iterable reactions + ) { + // Do nothing } /** - * For each input port of a contained reactor that receives data - * from one or more of the specified reactions, set the num_destinations - * field of the corresponding port structs on the self struct of - * the reaction's parent reactor equal to the total number of - * destination reactors. This is used to initialize reference - * counts in dynamically allocated tokens sent to other reactors. - * @param reactions The reactions. - */ - private def void generateInputNumDestinations(Iterable reactions) { - // Reference counts are decremented by each destination reactor - // at the conclusion of a time step. Hence, the initial reference - // count should equal the number of destination _reactors_, not the - // number of destination ports nor the number of destination reactions. - // One of the destination reactors may be the container of this - // instance because it may have a reaction to an output of this instance. - - // Since a port may be written to by multiple reactions, - // ensure that this is done only once. - val portsHandled = new HashSet(); - for (reaction : reactions) { - for (port : reaction.effects.filter(PortInstance)) { - if (port.isInput && !portsHandled.contains(port)) { - // Port is an input of a contained reactor that gets data from a reaction of this reactor. - portsHandled.add(port); - - // The input port may itself have multiple destinations. - for (sendingRange : port.eventualDestinations) { - - // Syntax is slightly different for a multiport output vs. single port. - if (port.isMultiport()) { - val start = sendingRange.startChannel; - val end = sendingRange.startChannel + sendingRange.channelWidth; - pr(initializeTriggerObjectsEnd, ''' - for (int i = «start»; i < «end»; i++) { - «sourceReference(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; - } - ''') - } else { - pr(initializeTriggerObjectsEnd, ''' - «sourceReference(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; - ''') - } - } - } - } - } - } - - /** - * If any input port of the specified reactor is a multiport - * and is mentioned as an effect of a reaction in its reactors's parent - * (the reaction provides input to a contained reactor), then generate - * code to allocate memory to store the data produced by those reactions. - * The allocated memory is pointed to by a field called - * `_lf_containername.portname` on the self struct of the reactor's parent. - * @param reactor A contained reactor. - */ - private def void generateAllocationForEffectsOnInputs(ReactorInstance reactor) { - // Keep track of ports already handled. There may be more than one reaction - // in the container writing to the port, but we want only one memory allocation. - val portsHandled = new HashSet(); - - // Find parent reactions that mention multiport inputs of this reactor. - for (reaction : reactor.parent.reactions) { - for (effect : reaction.effects.filter(PortInstance)) { - if (effect.isMultiport && reactor.inputs.contains(effect) && !portsHandled.contains(effect)) { - // Port is a multiport input that the parent's reaction is writing to. - portsHandled.add(effect); - - val nameOfSelfStruct = CUtil.selfRef(reactor.parent); - var containerName = CUtil.reactorRef(reactor); - val portStructType = variableStructType( - effect.definition, reactor.definition.reactorClass); - - pr(initializeTriggerObjectsEnd, ''' - «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width = «effect.width»; - // Allocate memory to store output of reaction feeding a multiport input of a contained reactor. - «nameOfSelfStruct»->_lf_«containerName».«effect.name» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width); - for (int i = 0; i < «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width; i++) { - «nameOfSelfStruct»->_lf_«containerName».«effect.name»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); - } - ''') - } - } - } - } - - /** - * If any output port of the specified reactor is a multiport and is mentioned - * as an effect of a reaction in the reactor, then generate code to - * allocate memory to store the data produced by those reactions. - * The allocated memory is pointed to by a field called `_lf_portname`. - * @param reactor A reactor instance. - */ - private def void generateAllocationForEffectsOnOutputs(ReactorInstance reactor) { - for (port : reactor.outputs) { - if (port.isMultiport) { - for (reaction : reactor.reactions) { - for (effect : reaction.effects) { - if (effect === port) { - // Port is an effect of a parent's reaction. - // That is, the port belongs to the same reactor as the reaction. - // The reaction is writing to an output of its container reactor. - val nameOfSelfStruct = CUtil.selfRef(port.parent); - val portStructType = variableStructType( - port.definition, - port.parent.definition.reactorClass - ) - - pr(initializeTriggerObjectsEnd, ''' - «nameOfSelfStruct»->_lf_«port.name»_width = «port.width»; - // Allocate memory to store output of reaction. - «nameOfSelfStruct»->_lf_«port.name» = («portStructType»*)calloc(«nameOfSelfStruct»->_lf_«port.name»_width, - sizeof(«portStructType»)); - «nameOfSelfStruct»->_lf_«port.name»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«port.name»_width); - // Assign each output port pointer to be used in reactions to facilitate user access to output ports - for(int i=0; i < «nameOfSelfStruct»->_lf_«port.name»_width; i++) { - «nameOfSelfStruct»->_lf_«port.name»_pointers[i] = &(«nameOfSelfStruct»->_lf_«port.name»[i]); - } - ''') - // There may be more reactions writing to this port, - // but once we have done the allocation, no need to do anything for those. - return; - } - } - } - } - } - } - - /** - * For the specified reaction, for output ports that it writes to, - * set up the arrays that store the output values (if necessary) and - * that are used to trigger downstream reactions if an output is actually - * produced. - * - * NOTE: This method is quite complicated because of the possibility that - * that the reaction is writing to a multiport output or to an - * input port of a contained reactor, and the possibility that that - * the contained reactor is a bank of reactors and that its input port may - * be a multiport. - * - * @param The reaction instance. - */ - private def void generateReactionOutputs(ReactionInstance reaction) { - val nameOfSelfStruct = CUtil.selfRef(reaction.parent); - - // Count the output ports and inputs of contained reactors that - // may be set by this reaction. This ignores actions in the effects. - // Collect initialization statements for the output_produced array for the reaction - // to point to the is_present field of the appropriate output. - // These statements must be inserted after the array is malloc'd, - // but we construct them while we are counting outputs. - var outputCount = 0; - val initialization = new StringBuilder() - // The reaction.effects does not contain multiports, but rather the individual - // ports of the multiport. We handle each multiport only once using this set. - val handledMultiports = new LinkedHashSet(); - for (effect : reaction.effects) { - if (effect instanceof PortInstance) { - // Effect is a port. There are six cases. - // 1. The port is an ordinary port contained by the same reactor that contains this reaction. - // 2. The port is a multiport contained by the same reactor that contains reaction. - // 3. The port is an ordinary input port contained by a contained reactor. - // 4. The port is a multiport input contained by a contained reactor. - // 5. The port is an ordinary port contained by a contained bank of reactors. - // 6. The port is an multiport contained by a contained bank of reactors. - // Create the entry in the output_produced array for this port. - // If the port is a multiport, then we need to create an entry for each - // individual port. - if (effect.isMultiport() && !handledMultiports.contains(effect)) { - // The effect is a multiport that has not been handled yet. - handledMultiports.add(effect); - // Point the output_produced field to where the is_present field of the port is. - if (effect.parent === reaction.parent) { - // The port belongs to the same reactor as the reaction. - pr(initialization, ''' - for (int i = 0; i < «effect.width»; i++) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.name»[i]''', "is_present")»; - } - ''') - } else { - // The port belongs to a contained reactor. - val containerName = effect.parent.name - pr(initialization, ''' - for (int i = 0; i < «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width; i++) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«nameOfSelfStruct»->_lf_«containerName».«effect.name»[i]->is_present; - } - ''') - } - outputCount += effect.getWidth(); - } else if (!effect.isMultiport()) { - // The effect is not a multiport nor a port contained by a multiport. - if (effect.parent === reaction.parent) { - // The port belongs to the same reactor as the reaction. - pr(initialization, ''' - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] - = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.name»''', "is_present")»; - ''') - } else { - // The port belongs to a contained reactor. - pr(initialization, ''' - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] - = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.parent.name».«effect.name»''', "is_present")»; - ''') - } - outputCount++ - } - } - } - pr(initializeTriggerObjectsEnd, ''' - // Total number of outputs (single ports and multiport channels) produced by the reaction. - «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs = «outputCount»; - // Allocate arrays for triggering downstream reactions. - if («nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs > 0) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced - = (bool**)malloc(sizeof(bool*) * «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs); - «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggers - = (trigger_t***)malloc(sizeof(trigger_t**) * «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs); - «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggered_sizes - = (int*)calloc(«nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs, sizeof(int)); - } - ''') - pr(initializeTriggerObjectsEnd, ''' - // Initialize the output_produced array. - «initialization.toString» - ''') - } - - /** - * Generate code that is executed while the reactor instance is being initialized. - * This is provided as an extension point for subclasses. - * Normally, the reactions argument is the full list of reactions, - * but for the top-level of a federate, will be a subset of reactions that - * is relevant to the federate. - * @param instance The reactor instance. - * @param reactions The reactions of this instance. - */ - def void generateReactorInstanceExtension( - ReactorInstance instance, - Iterable reactions - ) { - // Do nothing - } - - /** - * Generate code that initializes the state variables for a given instance. - * Unlike parameters, state variables are uniformly initialized for all instances - * of the same reactor. - * @param instance The reactor class instance - * @return Initialization code fore state variables of instance + * Generate code that initializes the state variables for a given instance. + * Unlike parameters, state variables are uniformly initialized for all instances + * of the same reactor. + * @param instance The reactor class instance + * @return Initialization code fore state variables of instance */ def generateStateVariableInitializations(ReactorInstance instance) { val reactorClass = instance.definition.reactorClass @@ -5151,117 +4668,64 @@ class CGenerator extends GeneratorBase { return builder.toString() } + // ////////////////////////////////////////// + // // Private methods. + /** - * Start a scoped section of code, surrounded by { ... }. - * @param builder The string builder to write to. - * @param description A description of the section of code. + * Start a scoped block for the specified reactor. + * If the reactor is a bank, then this starts a for loop + * that iterates over the bank members using a standard index + * variable. If the reactor is not a bank, then this simply + * starts a scoped block by printing an opening curly brace. + * This must be followed by an endScopedBlock(). + * @param builder The string builder into which to write. + * @param reactor The reactor instance. */ - protected def startScopedSection(StringBuilder builder, String description) { - pr(builder, "// " + description); - pr(builder, "{ // For scoping"); + private def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { + if (reactor.isBank) { + val index = CUtil.INDEX_PREFIX + reactor.depth; + pr(builder, ''' + // Initialize bank members. + for (int «index»; «index» < «reactor.width»; «index»++) { + ''') + } else { + pr(builder, "{"); + } indent(builder); } - + /** - * End a scoped section of code, surrounded by { ... }. - * @param builder The string builder to write to. + * End a scoped block. + * @param builder The string builder into which to write. */ - protected def endScopedSection(StringBuilder builder) { + private def void endScopedBlock(StringBuilder builder) { unindent(builder); - pr(builder, "} // End scoped section"); + pr(builder, "}"); } - // ////////////////////////////////////////// - // // Private methods. - - /** - * Perform deferred initializations in initialize_trigger_objects. + /** + * For the specified reactor, print code to the specified builder + * that retrieves the reactor instance using the current context + * variables to determine which instance of a bank is needed. + * A pointer to the self struct will be stored in a variable name + * given by CUtil.selfRef(reactor). + * @param builder The string builder into which to write. + * @param reactor The reactor instance. */ - private def doDeferredInitialize() { - pr('// doDeferredInitialize') - - // For outputs that are not primitive types (of form type* or type[]), - // create a default token on the self struct. - startScopedSection(code, "Create default tokens for token types."); - createDefaultTokens(main); - endScopedSection(code); - - // Next, for every input port, populate its "self" struct - // fields with pointers to the output port that sends it data. - startScopedSection(code, "Connect inputs to outputs."); - connectInputsToOutputs(main) - endScopedSection(code); - } + private def void getSelfStruct(StringBuilder builder, ReactorInstance reactor) { + var nameOfSelfStruct = CUtil.selfRef(reactor) + var structType = selfStructType(reactor.definition.reactorClass) + pr(builder, ''' + «structType»* «nameOfSelfStruct» + = («structType»*)selfStructs[«reactor.indexExpression(CUtil.INDEX_PREFIX)»]; + ''') + } /** * Generate assignments of pointers in the "self" struct of a destination * port's reactor to the appropriate entries in the "self" struct of the - * source reactor. This has to be done after all reactors have been created - * because inputs point to outputs that are arbitrarily far away. - * @param instance The reactor instance. - */ - private def void connectInputsToOutputs(ReactorInstance instance) { - if (!currentFederate.contains(instance)) { - return; - } - pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') - - // If the reactor is a bank, have to surround with a for loop. - if (instance.isBank) { - val index = CUtil.INDEX_PREFIX + instance.depth; - pr(''' - // Initialize bank members. - for (int «index»; «index» < «instance.width»; «index»++) { - ''') - indent(); - } - - // Retrieve the self struct from the common array of self structs. - var nameOfSelfStruct = CUtil.selfRef(instance) - var structType = selfStructType(instance.definition.reactorClass) - pr(''' - «structType»* «nameOfSelfStruct» - = («structType»*)selfStructs[«instance.indexExpression(CUtil.INDEX_PREFIX)»]; - ''') - - // Iterate over all ports of this reactor that have dependent reactions. - for (input : instance.inputs) { - if (!input.dependentReactions.isEmpty()) { - // Input has reactions. Connect it to its eventual source. - connectPortToEventualSource(input); - } - } - for (output : instance.outputs) { - if (!output.dependentReactions.isEmpty() && output.dependsOnPorts.isEmpty()) { - // Output has reactions and no upstream ports. - // Connect it to its eventual source. - connectPortToEventualSource(output); - } - } - for (child : instance.children) { - // In case this is a composite, recurse. - connectInputsToOutputs(child) - } - - // Handle inputs that get sent data from a reaction rather than from - // another contained reactor and reactions that are triggered by an - // output of a contained reactor. - connectReactionsToPorts(instance) - - if (instance.isBank) { - unindent(); - pr(''' - } - ''') - } - pr('''// END Connect inputs and outputs for reactor «instance.getFullName».''') - } - - /** - * Generate assignments of pointers in the "self" struct of a destination - * port's reactor to the appropriate entries in the "self" struct of the - * source reactor. - * @param instance A port with dependant reactions. + * source reactor. + * @param instance A port with dependant reactions. */ private def void connectPortToEventualSource(PortInstance port) { // Find the sources that send data to this port, @@ -5324,102 +4788,6 @@ class CGenerator extends GeneratorBase { } } - /** - * Connect inputs that get sent data from a reaction rather than from - * another contained reactor and reactions that are triggered by an - * output of a contained reactor. - * @param instance The reactor instance that contains the reactions. - */ - private def connectReactionsToPorts(ReactorInstance instance) { - for (reaction : instance.reactions) { - // First handle the effects that are inputs of contained reactors. - for (port : reaction.effects.filter(PortInstance)) { - if (port.definition instanceof Input) { - // This reaction is sending to an input. Must be - // the input of a contained reactor. If the contained reactor is - // not in the federate, then we don't do anything here. - if (currentFederate.contains(port.parent)) { - val destStructType = variableStructType( - port.definition as TypedVariable, - port.parent.definition.reactorClass - ) - if (port.isMultiport()) { - pr(''' - // Connect «port», which gets data from reaction «reaction.index» - // of «instance.getFullName», to «port.getFullName». - for (int i = 0; i < «port.width»; i++) { - «CUtil.destinationRef(port)»[i] = («destStructType»*)«sourceReference(port)»[i]; - } - ''') - } else { - pr(''' - // Connect «port», which gets data from reaction «reaction.index» - // of «instance.getFullName», to «port.getFullName». - «CUtil.destinationRef(port)» = («destStructType»*)&«sourceReference(port)»; - ''') - } - // FIXME: Don't we also to set set the destination reference for more - // deeply contained ports? - } - } - } - // Next handle the sources that are outputs of contained reactors. - for (port : reaction.sources.filter(PortInstance)) { - if (port.definition instanceof Output) { - // This reaction is receiving data from an output - // of a contained reactor. If the contained reactor is - // not in the federate, then we don't do anything here. - if (currentFederate.contains(port.parent)) { - val destStructType = variableStructType( - port.definition as TypedVariable, - port.parent.definition.reactorClass - ) - // The port may be deeper in the hierarchy. - var portChannelCount = 0; - for (eventualSource: port.eventualSources()) { - val sourcePort = eventualSource.portInstance - if (sourcePort.isMultiport && port.isMultiport) { - // Both source and destination are multiports. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - for (int i = 0; i < «eventualSource.channelWidth»; i++) { - «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»[i + «eventualSource.startChannel»]; - } - ''') - portChannelCount += eventualSource.channelWidth; - } else if (sourcePort.isMultiport) { - // Destination is not a multiport, so the channelWidth of the source port should be 1. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»[«eventualSource.startChannel»]; - ''') - portChannelCount++; - } else if (port.isMultiport) { - // Source is not a multiport, but the destination is. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)»[«portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»; - ''') - portChannelCount++; - } else { - // Neither is a multiport. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»; - ''') - portChannelCount++; - } - } - } - } - } - } - } - /** Generate action variables for a reaction. * @param builder The string builder into which to write the code. * @param action The action. @@ -5878,72 +5246,6 @@ class CGenerator extends GeneratorBase { } } - /** - * For each output that has a token type (type* or type[]), - * create a default token and put it on the self struct. - * @param parent The container reactor. - */ - private def void createDefaultTokens(ReactorInstance parent) { - for (containedReactor : parent.children) { - // Do this only for reactors in the federate. - if (currentFederate.contains(containedReactor)) { - // If the reactor is a bank, have to surround with a for loop. - if (containedReactor.isBank) { - val index = CUtil.INDEX_PREFIX + containedReactor.depth; - pr(''' - // Initialize bank members. - for (int «index»; «index» < «containedReactor.width»; «index»++) { - ''') - indent(); - } - var nameOfSelfStruct = CUtil.selfRef(containedReactor) - - // Look for outputs with token types. - var foundOne = false; - for (output : containedReactor.outputs) { - val type = (output.definition as Output).inferredType - if (type.isTokenType) { - if (!foundOne) { - // Retrieve the self struct from the common array of self structs. - var structType = selfStructType(containedReactor.definition.reactorClass) - pr(''' - «structType»* «nameOfSelfStruct» - = («structType»*)selfStructs[«containedReactor.indexExpression(CUtil.INDEX_PREFIX)»]; - ''') - - foundOne = true; - } - // Create the template token that goes in the trigger struct. - // Its reference count is zero, enabling it to be used immediately. - var rootType = type.targetType.rootType - // If the rootType is 'void', we need to avoid generating the code - // 'sizeof(void)', which some compilers reject. - val size = (rootType == 'void') ? '0' : '''sizeof(«rootType»)''' - if (output.isMultiport()) { - pr(''' - for (int i = 0; i < «output.width»; i++) { - «nameOfSelfStruct»->_lf_«output.name»[i].token = _lf_create_token(«size»); - } - ''') - } else { - pr(''' - «nameOfSelfStruct»->_lf_«output.name».token = _lf_create_token(«size»); - ''') - } - } - } - // In case this is a composite, handle its contained reactors. - createDefaultTokens(containedReactor) - if (containedReactor.isBank) { - unindent(); - pr(''' - } - ''') - } - } - } - } - // Regular expression pattern for array types with specified length. // \s is whitespace, \w is a word character (letter, number, or underscore). // For example, for "foo[10]", the first match will be "foo" and the second "[10]". @@ -6005,6 +5307,688 @@ class CGenerator extends GeneratorBase { throw new UnsupportedOperationException("TODO: auto-generated method stub") } + ////////////////////////////////////////////////////////////// + // Private methods that generate code to go at the end + // of _lf_initialize_trigger_objects(). + + /** + * Perform deferred initializations in initialize_trigger_objects. + * @param reactor for which to generate deferred initialization. + * @param reactions The reactions of the reactor to initialize (normally all of them). + */ + private def void deferredInitialize( + ReactorInstance reactor, Iterable reactions + ) { + if (!currentFederate.contains(reactor)) { + return; + } + + pr('''// deferredInitialize for «reactor.getFullName()»''') + startScopedBlock(code, reactor); + getSelfStruct(code, reactor); + + // Initialize the num_destinations fields of port structs on the self struct. + deferredOutputNumDestinations(reactor); // NOTE: Not done for top level. + deferredInputNumDestinations(reactions); + + deferredReactionMemory(reactions); + deferredRemoteTriggerTable(reactions); + + // For outputs that are not primitive types (of form type* or type[]), + // create a default token on the self struct. + deferredCreateDefaultTokens(reactor); + + // Next, for every input port, populate its "self" struct + // fields with pointers to the output port that sends it data. + deferredConnectInputsToOutputs(reactor) + + deferredAllocationForEffectsOnOutputs(reactor); + + for (child: reactor.children) { + // If the child has a multiport that is an effect of some reaction in its container, + // then we have to generate code to allocate memory for arrays pointing to + // its data. If the child is a bank, then memory is allocated for the entire + // bank width because a reaction cannot specify which bank members it writes + // to so we have to assume it can write to any. + deferredAllocationForEffectsOnInputs(child); + + deferredInitialize(child, child.reactions); + } + // Handle inputs that get sent data from a reaction rather than from + // another contained reactor and reactions that are triggered by an + // output of a contained reactor. + deferredConnectReactionsToPorts(reactor) + + endScopedBlock(code) + } + + /** + * Generate assignments of pointers in the "self" struct of a destination + * port's reactor to the appropriate entries in the "self" struct of the + * source reactor. This has to be done after all reactors have been created + * because inputs point to outputs that are arbitrarily far away. + * @param instance The reactor instance. + */ + private def void deferredConnectInputsToOutputs(ReactorInstance instance) { + pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') + // Iterate over all ports of this reactor that have dependent reactions. + for (input : instance.inputs) { + if (!input.dependentReactions.isEmpty()) { + // Input has reactions. Connect it to its eventual source. + connectPortToEventualSource(input); + } + } + for (output : instance.outputs) { + if (!output.dependentReactions.isEmpty() && output.dependsOnPorts.isEmpty()) { + // Output has reactions and no upstream ports. + // Connect it to its eventual source. + connectPortToEventualSource(output); + } + } + } + + /** + * Connect inputs that get sent data from a reaction rather than from + * another contained reactor and reactions that are triggered by an + * output of a contained reactor. + * @param instance The reactor instance that contains the reactions. + */ + private def deferredConnectReactionsToPorts(ReactorInstance instance) { + for (reaction : instance.reactions) { + // First handle the effects that are inputs of contained reactors. + for (port : reaction.effects.filter(PortInstance)) { + if (port.definition instanceof Input) { + // This reaction is sending to an input. Must be + // the input of a contained reactor. If the contained reactor is + // not in the federate, then we don't do anything here. + if (currentFederate.contains(port.parent)) { + val destStructType = variableStructType( + port.definition as TypedVariable, + port.parent.definition.reactorClass + ) + if (port.isMultiport()) { + pr(''' + // Connect «port», which gets data from reaction «reaction.index» + // of «instance.getFullName», to «port.getFullName». + for (int i = 0; i < «port.width»; i++) { + «CUtil.destinationRef(port)»[i] = («destStructType»*)«sourceReference(port)»[i]; + } + ''') + } else { + pr(''' + // Connect «port», which gets data from reaction «reaction.index» + // of «instance.getFullName», to «port.getFullName». + «CUtil.destinationRef(port)» = («destStructType»*)&«sourceReference(port)»; + ''') + } + } + } + } + // Next handle the sources that are outputs of contained reactors. + for (port : reaction.sources.filter(PortInstance)) { + if (port.definition instanceof Output) { + // This reaction is receiving data from an output + // of a contained reactor. If the contained reactor is + // not in the federate, then we don't do anything here. + if (currentFederate.contains(port.parent)) { + val destStructType = variableStructType( + port.definition as TypedVariable, + port.parent.definition.reactorClass + ) + // The port may be deeper in the hierarchy. + var portChannelCount = 0; + for (eventualSource: port.eventualSources()) { + val sourcePort = eventualSource.portInstance + if (sourcePort.isMultiport && port.isMultiport) { + // Both source and destination are multiports. + pr(''' + // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» + // of «instance.getFullName», on its self struct. + for (int i = 0; i < «eventualSource.channelWidth»; i++) { + «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»[i + «eventualSource.startChannel»]; + } + ''') + portChannelCount += eventualSource.channelWidth; + } else if (sourcePort.isMultiport) { + // Destination is not a multiport, so the channelWidth of the source port should be 1. + pr(''' + // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» + // of «instance.getFullName», on its self struct. + «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»[«eventualSource.startChannel»]; + ''') + portChannelCount++; + } else if (port.isMultiport) { + // Source is not a multiport, but the destination is. + pr(''' + // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» + // of «instance.getFullName», on its self struct. + «CUtil.containedPortRef(port)»[«portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»; + ''') + portChannelCount++; + } else { + // Neither is a multiport. + pr(''' + // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» + // of «instance.getFullName», on its self struct. + «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»; + ''') + portChannelCount++; + } + } + } + } + } + } + } + + /** + * For each output of the specified reactor that has a token type + * (type* or type[]), create a default token and put it on the self struct. + * @param parent The reactor. + */ + private def void deferredCreateDefaultTokens(ReactorInstance reactor) { + var nameOfSelfStruct = CUtil.selfRef(reactor); + + // Look for outputs with token types. + for (output : reactor.outputs) { + val type = (output.definition as Output).inferredType; + if (type.isTokenType) { + // Create the template token that goes in the trigger struct. + // Its reference count is zero, enabling it to be used immediately. + var rootType = type.targetType.rootType; + // If the rootType is 'void', we need to avoid generating the code + // 'sizeof(void)', which some compilers reject. + val size = (rootType == 'void') ? '0' : '''sizeof(«rootType»)''' + if (output.isMultiport()) { + pr(''' + for (int i = 0; i < «output.width»; i++) { + «nameOfSelfStruct»->_lf_«output.name»[i].token = _lf_create_token(«size»); + } + ''') + } else { + pr(''' + «nameOfSelfStruct»->_lf_«output.name».token = _lf_create_token(«size»); + ''') + } + } + } + } + + /** + * For each output port of the specified reactor, + * set the num_destinations field of port structs on its self struct + * equal to the total number of destination reactors. This is used + * to initialize reference counts in dynamically allocated tokens + * sent to other reactors. + * @param reactor The reactor instance. + */ + private def void deferredOutputNumDestinations(ReactorInstance reactor) { + // For top-level, ignore this. + if (reactor == main) return; + + // Reference counts are decremented by each destination reactor + // at the conclusion of a time step. Hence, the initial reference + // count should equal the number of destination _reactors_, not the + // number of destination ports nor the number of destination reactions. + // One of the destination reactors may be the container of this + // instance because it may have a reaction to an output of this instance. + for (output : reactor.outputs) { + for (sendingRange : output.eventualDestinations) { + // Syntax is slightly difference for a multiport output vs. single port. + // For a single port, there should be only one sendingRange. + if (output.isMultiport()) { + val start = sendingRange.startChannel; + val end = sendingRange.startChannel + sendingRange.channelWidth; + // Eliminate the for loop for the case where range.channelWidth == 1, + // a common situation on multiport to bank messaging. + if (sendingRange.channelWidth == 1) { + pr(''' + «sourceReference(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + ''') + } else { + pr(''' + for (int i = «start»; i < «end»; i++) { + «sourceReference(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + } + ''') + } + } else { + pr(''' + «sourceReference(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + ''') + } + } + } + } + + /** + * For each input port of a contained reactor that receives data + * from one or more of the specified reactions, set the num_destinations + * field of the corresponding port structs on the self struct of + * the reaction's parent reactor equal to the total number of + * destination reactors. This is used to initialize reference + * counts in dynamically allocated tokens sent to other reactors. + * @param reactions The reactions. + */ + private def void deferredInputNumDestinations(Iterable reactions) { + // Reference counts are decremented by each destination reactor + // at the conclusion of a time step. Hence, the initial reference + // count should equal the number of destination _reactors_, not the + // number of destination ports nor the number of destination reactions. + // One of the destination reactors may be the container of this + // instance because it may have a reaction to an output of this instance. + + // Since a port may be written to by multiple reactions, + // ensure that this is done only once. + val portsHandled = new HashSet(); + for (reaction : reactions) { + for (port : reaction.effects.filter(PortInstance)) { + if (port.isInput && !portsHandled.contains(port)) { + // Port is an input of a contained reactor that gets data from a reaction of this reactor. + portsHandled.add(port); + + // The input port may itself have multiple destinations. + for (sendingRange : port.eventualDestinations) { + + // Syntax is slightly different for a multiport output vs. single port. + if (port.isMultiport()) { + val start = sendingRange.startChannel; + val end = sendingRange.startChannel + sendingRange.channelWidth; + pr(''' + for (int i = «start»; i < «end»; i++) { + «sourceReference(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + } + ''') + } else { + pr(''' + «sourceReference(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + ''') + } + } + } + } + } + } + + /** + * If any input port of the specified reactor is a multiport + * and is mentioned as an effect of a reaction in its reactors's parent + * (the reaction provides input to a contained reactor), then generate + * code to allocate memory to store the data produced by those reactions. + * The allocated memory is pointed to by a field called + * `_lf_containername.portname` on the self struct of the reactor's parent. + * @param reactor A contained reactor. + */ + private def void deferredAllocationForEffectsOnInputs(ReactorInstance reactor) { + // Keep track of ports already handled. There may be more than one reaction + // in the container writing to the port, but we want only one memory allocation. + val portsHandled = new HashSet(); + + // Find parent reactions that mention multiport inputs of this reactor. + for (reaction : reactor.parent.reactions) { + for (effect : reaction.effects.filter(PortInstance)) { + if (effect.isMultiport && reactor.inputs.contains(effect) && !portsHandled.contains(effect)) { + // Port is a multiport input that the parent's reaction is writing to. + portsHandled.add(effect); + + val nameOfSelfStruct = CUtil.selfRef(reactor.parent); + var containerName = CUtil.reactorRef(reactor); + val portStructType = variableStructType( + effect.definition, reactor.definition.reactorClass); + + pr(''' + «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width = «effect.width»; + // Allocate memory to store output of reaction feeding a multiport input of a contained reactor. + «nameOfSelfStruct»->_lf_«containerName».«effect.name» = («portStructType»**)malloc(sizeof(«portStructType»*) + * «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width); + for (int i = 0; i < «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width; i++) { + «nameOfSelfStruct»->_lf_«containerName».«effect.name»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); + } + ''') + } + } + } + } + + /** + * If any output port of the specified reactor is a multiport and is mentioned + * as an effect of a reaction in the reactor, then generate code to + * allocate memory to store the data produced by those reactions. + * The allocated memory is pointed to by a field called `_lf_portname`. + * @param reactor A reactor instance. + */ + private def void deferredAllocationForEffectsOnOutputs(ReactorInstance reactor) { + for (port : reactor.outputs) { + if (port.isMultiport) { + for (reaction : reactor.reactions) { + for (effect : reaction.effects) { + if (effect === port) { + // Port is an effect of a parent's reaction. + // That is, the port belongs to the same reactor as the reaction. + // The reaction is writing to an output of its container reactor. + val nameOfSelfStruct = CUtil.selfRef(port.parent); + val portStructType = variableStructType( + port.definition, + port.parent.definition.reactorClass + ) + + pr(''' + «nameOfSelfStruct»->_lf_«port.name»_width = «port.width»; + // Allocate memory to store output of reaction. + «nameOfSelfStruct»->_lf_«port.name» = («portStructType»*)calloc(«nameOfSelfStruct»->_lf_«port.name»_width, + sizeof(«portStructType»)); + «nameOfSelfStruct»->_lf_«port.name»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) + * «nameOfSelfStruct»->_lf_«port.name»_width); + // Assign each output port pointer to be used in reactions to facilitate user access to output ports + for(int i=0; i < «nameOfSelfStruct»->_lf_«port.name»_width; i++) { + «nameOfSelfStruct»->_lf_«port.name»_pointers[i] = &(«nameOfSelfStruct»->_lf_«port.name»[i]); + } + ''') + // There may be more reactions writing to this port, + // but once we have done the allocation, no need to do anything for those. + return; + } + } + } + } + } + } + + /** + * Set the last_enabling_reaction field of the reaction struct to point + * to the single dominating upstream reaction, if there is one, or to be + * NULL if not. + * + * @param reaction The reaction. + * @param reactionNumber The reaction number within the parent. + */ + def deferredOptimizeForSingleDominatingReaction ( + ReactionInstance reaction, + int reactionNumber + ) { + val reactorInstance = reaction.parent; + val selfStruct = CUtil.selfRef(reactorInstance) + + // Record the number of reactions that this reaction depends on. + // This is used for optimization. When that number is 1, the reaction can + // be executed immediately when its triggering reaction has completed. + var dominatingReaction = reaction.findSingleDominatingReaction(); + + // The dominating reaction may not be included in this federate, in which case, we need to keep searching. + while (dominatingReaction !== null + && (!currentFederate.contains(dominatingReaction.definition)) + ) { + dominatingReaction = dominatingReaction.findSingleDominatingReaction(); + } + if (dominatingReaction !== null + && currentFederate.contains(dominatingReaction.definition) + && currentFederate.contains(dominatingReaction.parent) + ) { + val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' + pr(''' + // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. + «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); + ''') + } else { + pr(''' + // Reaction «reactionNumber» of «reactorInstance.getFullName» does not depend on one maximal upstream reaction. + «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = NULL; + ''') + } + } + + /** + * Generate code to allocate the memory needed by reactions for triggering + * downstream reactions. Also, record startup and shutdown reactions. + * @param reactions A list of reactions. + */ + private def void deferredReactionMemory(Iterable reactions) { + // For each reaction instance, allocate the arrays that will be used to + // trigger downstream reactions. + for (reaction : reactions) { + val instance = reaction.parent; + val nameOfSelfStruct = CUtil.selfRef(instance) + + deferredReactionOutputs(reaction); + + // Next handle triggers of the reaction that come from a multiport output + // of a contained reactor. Also, handle startup and shutdown triggers. + for (trigger : reaction.triggers) { + if (trigger instanceof PortInstance) { + // If the port is a multiport, then we need to create an entry for each + // individual port. + if (trigger.isMultiport() && trigger.parent !== null && trigger.isOutput) { + // If the width is given as a numeric constant, then add that constant + // to the output count. Otherwise, assume it is a reference to one or more parameters. + val width = trigger.width; + val containerName = trigger.parent.name + val portStructType = variableStructType(trigger.definition, + trigger.parent.definition.reactorClass) + + // FIXME: What if the effect is a bank? Need to index the container. + pr(''' + «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width = «width»; + // Allocate memory to store pointers to the multiport outputs of a contained reactor. + «nameOfSelfStruct»->_lf_«containerName».«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) + * «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width); + ''') + } + } + } + } + } + + /** + * For the specified reaction, for output ports that it writes to, + * set up the arrays that store the output values (if necessary) and + * that are used to trigger downstream reactions if an output is actually + * produced. + * + * NOTE: This method is quite complicated because of the possibility that + * that the reaction is writing to a multiport output or to an + * input port of a contained reactor, and the possibility that that + * the contained reactor is a bank of reactors and that its input port may + * be a multiport. + * + * @param The reaction instance. + */ + private def void deferredReactionOutputs(ReactionInstance reaction) { + val nameOfSelfStruct = CUtil.selfRef(reaction.parent); + + // Count the output ports and inputs of contained reactors that + // may be set by this reaction. This ignores actions in the effects. + // Collect initialization statements for the output_produced array for the reaction + // to point to the is_present field of the appropriate output. + // These statements must be inserted after the array is malloc'd, + // but we construct them while we are counting outputs. + var outputCount = 0; + val initialization = new StringBuilder() + + for (effect : reaction.effects) { + if (effect instanceof PortInstance) { + // Effect is a port. There are four cases. + // 1. The port is an ordinary port contained by the same reactor that contains this reaction. + // 2. The port is a multiport contained by the same reactor that contains reaction. + // 3. The port is an ordinary input port contained by a contained reactor. + // 4. The port is a multiport input contained by a contained reactor. + // Create the entry in the output_produced array for this port. + // If the port is a multiport, then we need to create an entry for each + // individual channel. + if (effect.isMultiport()) { + // Point the output_produced field to where the is_present field of the port is. + if (effect.parent === reaction.parent) { + // The port belongs to the same reactor as the reaction. + pr(initialization, ''' + for (int i = 0; i < «effect.width»; i++) { + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] + = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.name»[i]''', "is_present")»; + } + ''') + } else { + // The port belongs to a contained reactor. + val containerName = effect.parent.name + pr(initialization, ''' + for (int i = 0; i < «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width; i++) { + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] + = &«nameOfSelfStruct»->_lf_«containerName».«effect.name»[i]->is_present; + } + ''') + } + outputCount += effect.getWidth(); + } else { + // The effect is not a multiport nor a port contained by a multiport. + if (effect.parent === reaction.parent) { + // The port belongs to the same reactor as the reaction. + pr(initialization, ''' + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] + = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.name»''', "is_present")»; + ''') + } else { + // The port belongs to a contained reactor. + pr(initialization, ''' + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] + = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.parent.name».«effect.name»''', "is_present")»; + ''') + } + outputCount++ + } + } + } + pr(''' + // Total number of outputs (single ports and multiport channels) produced by the reaction. + «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs = «outputCount»; + // Allocate arrays for triggering downstream reactions. + if («nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs > 0) { + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced + = (bool**)malloc(sizeof(bool*) * «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs); + «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggers + = (trigger_t***)malloc(sizeof(trigger_t**) * «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs); + «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggered_sizes + = (int*)calloc(«nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs, sizeof(int)); + } + ''') + pr(''' + // Initialize the output_produced array. + «initialization.toString» + ''') + } + + /** + * Generate code to create the trigger table for each given reaction. + * Each table lists the triggers that the reaction + * execution may trigger. Each table is an array of arrays + * of pointers to the trigger_t structs representing the downstream inputs + * (or outputs of the container reactor) that are triggered by the reaction. + * Each trigger table goes into the reaction's reaction_t triggers field. + * That reaction_t struct is assumed to be on the self struct of the reactor + * instance with name "_lf__reaction_i", where i is the index of the reaction. + * The generated code will also set the values of the triggered_sizes array + * on the reaction_t struct to indicate the size of each array of trigger_t + * pointers. The generated code will malloc each of these arrays, and the + * destructor for the reactor instance will free them. + * @param reactions The reactions. + */ + private def deferredRemoteTriggerTable(Iterable reactions) { + for (reaction : reactions) { + val selfStruct = CUtil.selfRef(reaction.parent); + val name = reaction.parent.getFullName; + var channelCount = 0 + + deferredOptimizeForSingleDominatingReaction(reaction, reaction.index); + + // Insert a string name to facilitate debugging. + if (targetConfig.logLevel >= LogLevel.LOG) { + pr(''' + // Reaction «reaction.index» of «name». + «selfStruct»->_lf__reaction_«reaction.index».name = "«name» reaction «reaction.index»"; + ''') + } + + for (port : reaction.effects.filter(PortInstance)) { + // Skip ports whose parent is not in the federation. + // This can happen with reactions in the top-level that have + // as an effect a port in a bank. + if (currentFederate.contains(port.parent)) { + + // If the port is a multiport, then its channels may have different sets + // of destinations. For ordinary ports, there will be only one range and + // its width will be 1. + // We generate the code to fill the triggers array first in a temporary buffer, + // so that we can simultaneously calculate the size of the total array. + for (PortInstance.SendRange range : port.eventualDestinations()) { + val temp = new StringBuilder(); + var destRangeCount = 0; + for (destinationRange : range.destinations) { + val destination = destinationRange.getPortInstance(); + if (destination.isOutput) { + // Include this destination port only if it has at least one + // reaction in the federation. + var belongs = false; + for (destinationReaction : destination.dependentReactions) { + if (currentFederate.contains(destinationReaction.parent)) { + belongs = true + } + } + if (belongs) { + pr(temp, ''' + // Port «port.getFullName» has reactions in its parent's parent. + // Point to the trigger struct for those reactions. + triggerArray[«destRangeCount»] = &«triggerStructName( + destination, + destination.parent.parent + )»; + ''') + // One array entry for each destination range is sufficient. + destRangeCount++; + } + } else { + // Destination is an input port. + pr(temp, ''' + // Point to destination port «destination.getFullName»'s trigger struct. + triggerArray[«destRangeCount»] = &«triggerStructName(destination)»; + ''') + // One array entry for each destination range is sufficient. + destRangeCount++; + } + } + + // Record the total size of the array. + pr(''' + for (int i = 0; i < «range.channelWidth»; i++) { + // Reaction «reaction.index» of «name» triggers «channelCount» + // downstream reactions through port «port.getFullName»[«channelCount» + i]. + «selfStruct»->_lf__reaction_«reaction.index».triggered_sizes[«channelCount» + i] = «destRangeCount»; + } + ''') + + // Malloc the memory for the arrays. + pr(''' + { // For scoping + // For reaction «reaction.index» of «name», allocate an + // array of trigger pointers for downstream reactions through port «port.getFullName» + trigger_t** triggerArray = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); + for (int i = 0; i < «range.channelWidth»; i++) { + «selfStruct»->_lf__reaction_«reaction.index».triggers[«channelCount» + i] = triggerArray; + } + // Fill the trigger array. + «temp.toString()» + } + ''') + channelCount += range.channelWidth; + } + } else { + // Count the port even if it is not contained in the federate because effect + // may be a bank (it can't be an instance of a bank), so an empty placeholder + // will be needed for each member of the bank that is not in the federate. + channelCount += port.width; + } + } + } + } + + ////////////////////////////////////////////////////////////// + // Inner class + /** * Data structure that for each instantiation of a contained * reactor. This provides a set of input and output ports that trigger From 60f0a7d87b6137ecfe20383049f2ad65c23b70b5 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 19 Nov 2021 22:12:04 -0800 Subject: [PATCH 008/221] Port pr, indent, etc. to Java. --- .../org/lflang/generator/GeneratorBase.xtend | 177 +----------------- .../lflang/generator/JavaGeneratorBase.java | 152 +++++++++++++++ .../generator/python/PythonGenerator.xtend | 3 +- 3 files changed, 155 insertions(+), 177 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/JavaGeneratorBase.java diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 7b0d4239c1..d63ca487a4 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -98,7 +98,7 @@ import static extension org.lflang.JavaAstUtils.* * @author{Matt Weber } * @author{Soroush Bateni } */ -abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes { +abstract class GeneratorBase extends JavaGeneratorBase { //////////////////////////////////////////// //// Public fields. @@ -118,11 +118,6 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes //////////////////////////////////////////// //// Protected fields. - - /** - * All code goes into this string buffer. - */ - protected var code = new StringBuilder /** * The current target configuration. @@ -245,10 +240,6 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes // ////////////////////////////////////////// // // Private fields. - /** - * Map from builder to its current indentation. - */ - var indentation = new LinkedHashMap() /** * Create a new GeneratorBase object. @@ -1023,149 +1014,6 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes } } - /** - * Get the code produced so far. - * @return The code produced so far as a String. - */ - protected def getCode() { - code.toString() - } - - /** - * Increase the indentation of the output code produced. - */ - protected def indent() { - indent(code) - } - - /** - * Increase the indentation of the output code produced - * on the specified builder. - * @param The builder to indent. - */ - protected def indent(StringBuilder builder) { - var prefix = indentation.get(builder) - if (prefix === null) { - prefix = "" - } - prefix += " "; - indentation.put(builder, prefix) - } - - /** - * Append the specified text plus a final newline to the current - * code buffer. - * @param format A format string to be used by String.format or - * the text to append if no further arguments are given. - * @param args Additional arguments to pass to the formatter. - */ - protected def pr(String format, Object... args) { - pr(code, if (args !== null && args.length > 0) - String.format(format, args) - else - format) - } - - /** - * Append the specified text plus a final newline to the specified - * code buffer. - * @param builder The code buffer. - * @param text The text to append. - */ - protected def pr(StringBuilder builder, Object text) { - // Handle multi-line text. - var string = text.toString - var indent = indentation.get(builder) - if (indent === null) { - indent = "" - } - if (string.contains("\n")) { - // Replace all tabs with four spaces. - string = string.replaceAll("\t", " ") - // Use two passes, first to find the minimum leading white space - // in each line of the source text. - var split = string.split("\n") - var offset = Integer.MAX_VALUE - var firstLine = true - for (line : split) { - // Skip the first line, which has white space stripped. - if (firstLine) { - firstLine = false - } else { - var numLeadingSpaces = line.indexOf(line.trim()); - if (numLeadingSpaces < offset) { - offset = numLeadingSpaces - } - } - } - // Now make a pass for each line, replacing the offset leading - // spaces with the current indentation. - firstLine = true - for (line : split) { - builder.append(indent) - // Do not trim the first line - if (firstLine) { - builder.append(line) - firstLine = false - } else { - builder.append(line.substring(offset)) - } - builder.append("\n") - } - } else { - builder.append(indent) - builder.append(text) - builder.append("\n") - } - } - - /** - * Prints an indented block of text with the given begin and end markers, - * but only if the actions print any text at all. - * This is helpful to avoid the production of empty blocks. - * @param begin The prologue of the block. - * @param end The epilogue of the block. - * @param actions Actions that print the interior of the block. - */ - protected def prBlock(String begin, String end, Runnable... actions) { - val i = code.length - indent() - for (action : actions) { - action.run() - } - unindent() - if (i < code.length) { - val inserted = code.substring(i, code.length) - code.delete(i, code.length) - pr(begin) - code.append(inserted) - pr(end) - } - } - - /** - * Leave a marker in the generated code that indicates the original line - * number in the LF source. - * @param eObject The node. - */ - protected def prSourceLineNumber(EObject eObject) { - if (eObject instanceof Code) { - pr(code, '''// «NodeModelUtils.getNode(eObject).startLine +1»''') - } else { - pr(code, '''// «NodeModelUtils.getNode(eObject).startLine»''') - } - } - - /** - * Print a comment to the generated file. - * Particular targets will need to override this if comments - * start with something other than '//'. - * @param comment The comment. - */ - protected def prComment(String comment) { - pr(code, '// ' + comment); - } - /** * Given a line of text from the output of a compiler, return * an instance of ErrorFileAndLine if the line is recognized as @@ -1313,29 +1161,6 @@ abstract class GeneratorBase extends AbstractLFValidator implements TargetTypes println("Unable to refresh workspace: " + e) } } - } - - /** Reduce the indentation by one level for generated code - * in the default code buffer. - */ - protected def unindent() { - unindent(code) - } - - /** Reduce the indentation by one level for generated code - * in the specified code buffer. - */ - protected def unindent(StringBuilder builder) { - var indent = indentation.get(builder) - if (indent !== null) { - val end = indent.length - 4; - if (end < 0) { - indent = "" - } else { - indent = indent.substring(0, end) - } - indentation.put(builder, indent) - } } /** diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java b/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java new file mode 100644 index 0000000000..23f3c330bd --- /dev/null +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java @@ -0,0 +1,152 @@ +package org.lflang.generator; + +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.nodemodel.util.NodeModelUtils; + +import org.lflang.lf.Code; +import org.lflang.validation.AbstractLFValidator; + +/** + * Generator base class for shared code between code generators. + * This is created to ease our migration from Xtend. + */ +public abstract class JavaGeneratorBase extends AbstractLFValidator implements TargetTypes { + //////////////////////////////////////////// + //// Protected fields. + + /** + * All code goes into this string buffer. + */ + protected StringBuilder code = new StringBuilder(); + + ///////////////////////////////////////////// + ///// Private fields. + + /** + * Map from builder to its current indentation. + */ + private final Map indentation = new HashMap<>(); + + ///////////////////////////////////////////// + ///// Protected methods. + + /** + * Get the code produced so far. + * @return The code produced so far as a String. + */ + protected String getCode() { + return code.toString(); + } + + /** + * Increase the indentation of the output code produced. + */ + protected void indent() { + indent(code); + } + + /** + * Increase the indentation of the output code produced + * on the specified builder. + * @param builder The builder to indent. + */ + protected void indent(StringBuilder builder) { + String prefix = indentation.get(builder); + if (prefix == null) { + prefix = ""; + } + prefix += " "; + indentation.put(builder, prefix); + } + + /** Reduce the indentation by one level for generated code + * in the default code buffer. + */ + protected void unindent() { + unindent(code); + } + + /** Reduce the indentation by one level for generated code + * in the specified code buffer. + */ + protected void unindent(StringBuilder builder) { + String indent = indentation.get(builder); + if (indent != null) { + indentation.put(builder, indent.substring(0, Math.max(0, indent.length() - 4))); + } + } + + /** + * Append the specified text plus a final newline to the current + * code buffer. + * @param format A format string to be used by {@code String.format} or + * the text to append if no further arguments are given. + * @param args Additional arguments to pass to the formatter. + */ + protected void pr(String format, Object... args) { + pr( + code, + (args != null && args.length > 0) ? String.format(format, args) : format + ); + } + + /** + * Append the specified text plus a final newline to the specified + * code buffer. + * @param builder The code buffer. + * @param text The text to append. + */ + protected void pr(StringBuilder builder, Object text) { + String string = text.toString(); + String indent = indentation.get(builder); + if (indent == null) { + indent = ""; + } + string = string.replaceAll("\t", " "); + String[] split = string.split("\n"); + int offset = Stream.of(split).skip(1) + .mapToInt(line -> line.indexOf(line.trim())) + .min() + .orElse(0); + // Now make a pass for each line, replacing the offset leading + // spaces with the current indentation. + boolean firstLine = true; + for (String line : split) { + builder.append(indent); + // Do not trim the first line + if (firstLine) { + builder.append(line); + firstLine = false; + } else { + builder.append(line.substring(offset)); + } + builder.append("\n"); + } + } + + /** + * Leave a marker in the generated code that indicates the original line + * number in the LF source. + * @param eObject The node. + */ + protected void prSourceLineNumber(EObject eObject) { + pr(code, String.format( + "// %d", + NodeModelUtils.getNode(eObject).getStartLine() + (eObject instanceof Code ? 1 : 0) + )); + } + + /** + * Print a comment to the generated file. + * Particular targets will need to override this if comments + * start with something other than '//'. + * @param comment The comment. + */ + protected void prComment(String comment) { + pr(code, "// " + comment); + } +} diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index e1222a7ce2..b3baadf465 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1410,7 +1410,8 @@ class PythonGenerator extends CGenerator { // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C if(reactor.name.contains(GEN_DELAY_CLASS_NAME) || ((decl === this.mainDef?.reactorClass) && reactor.isFederated)) { - return super.generateReaction(reaction, decl, reactionIndex) + super.generateReaction(reaction, decl, reactionIndex) + return } // Contains "O" characters. The number of these characters depend on the number of inputs to the reaction From 48e31be360d288250fde4717c387a23655ddb5a5 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 20 Nov 2021 12:16:14 -0800 Subject: [PATCH 009/221] Organized imports and removed obsolete comment. --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 6 ------ 1 file changed, 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index d63ca487a4..4ebde95127 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -41,11 +41,9 @@ import java.util.regex.Pattern import org.eclipse.core.resources.IMarker import org.eclipse.core.resources.IResource import org.eclipse.core.resources.ResourcesPlugin -import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGeneratorContext -import org.eclipse.xtext.nodemodel.util.NodeModelUtils import org.eclipse.xtext.resource.XtextResource import org.eclipse.xtext.util.CancelIndicator import org.eclipse.xtext.validation.CheckMode @@ -66,7 +64,6 @@ import org.lflang.federated.serialization.SupportedSerializers import org.lflang.graph.InstantiationGraph import org.lflang.lf.Action import org.lflang.lf.ActionOrigin -import org.lflang.lf.Code import org.lflang.lf.Delay import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory @@ -82,15 +79,12 @@ import org.lflang.lf.TimeUnit import org.lflang.lf.Value import org.lflang.lf.VarRef import org.lflang.lf.Variable -import org.lflang.validation.AbstractLFValidator import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* /** * Generator base class for shared code between code generators. - * This extends AbstractLinguaFrancaValidator so that errors can be highlighted - * in the XText-based IDE. * * @author{Edward A. Lee } * @author{Marten Lohstroh } From 24bbeaa287871c6fb56df6b58d0f38e97598169e Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 20 Nov 2021 12:16:49 -0800 Subject: [PATCH 010/221] First step towards iterations over bank members while constructing and initializing reactors. --- .../org/lflang/generator/ReactorInstance.java | 78 +++----- .../org/lflang/generator/c/CGenerator.xtend | 173 +++++++++++------- .../src/org/lflang/generator/c/CUtil.java | 86 +++++++-- 3 files changed, 201 insertions(+), 136 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index c0b73ea6a5..5c43394164 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -256,6 +256,18 @@ public int getDepth() { return depth; } + /** + * Return an integer is equal to one plus the total number of reactor instances + * (including bank members) that have been instantiated before this one + * within the same parent. + * This can be used, for example, to index into an array of data structures + * representing instances of reactors in generated code. + * This assumes that index 0 refers to the parent, hence the "one plus." + */ + public int getIndexOffset() { + return indexOffset; + } + /** * Return the specified input by name or null if there is no such input. * @param name The input name. @@ -279,6 +291,18 @@ public String getName() { return this.definition.getName(); } + /** + * Get the number of reactor instances associated with this + * reactor. This is 1 plus the total number of reactors + * in any contained reactors, as returned by getTotalNumReactorInstances(). + * If this is a bank, then this number does not account for the bank width. + * Use getTotalNumberOfReactorInstances() to take into + * account bank width. + */ + public int getNumReactorInstances() { + return numReactorInstances; + } + /** * Return the specified output by name or null if there is no such output. * @param name The output name. @@ -292,18 +316,6 @@ public PortInstance getOutput(String name) { return null; } - /** - * Get the number of reactor instances associated with this - * reactor. This is 1 plus the total number of reactors - * in any contained reactors, as returned by getTotalNumReactorInstances(). - * If this is a bank, then this number does not account for the bank width. - * Use getTotalNumberOfReactorInstances() to take into - * account bank width. - */ - public int getNumReactorInstances() { - return numReactorInstances; - } - /** * Return a parameter matching the specified name if the reactor has one * and otherwise return null. @@ -410,46 +422,6 @@ public List initialParameterValue(Parameter parameter) { return ASTUtils.initialValue(parameter, instantiations()); } - /** - * Return an expression that, when evaluated in a - * context with bank index variables defined, returns a unique index - * for a runtime reactor instance. This can be used to maintain an - * array of runtime instance objects, each of which will have a - * unique index. - * - * This is rather complicated because - * this reactor instance and any of its parents may actually - * represent a bank of runtime reactor instances rather a single - * runtime instance. This method returns an expression that should - * be evaluatable in any target language that uses + for addition - * and * for multiplication and has defined variables in the context - * in which this will be evaluated that specify which bank member is - * desired for this reactor instance and any of its parents that is - * a bank. The names of these variables should be rd, where r is - * the prefix string given here as an argument and d is the depth of - * the reactor instance that represents a bank. - * - * If this is a top-level reactor, this appends "0" and returns. - * If this is one level down, this appends "n", where n is the - * sum of the total - * - * @param prefix The prefix used for index variables for bank members. - */ - public String indexExpression(String prefix) { - if (depth == 0) return("0"); - if (isBank()) { - return( - prefix + depth + " * " + numReactorInstances // Position of the bank member relative to the bank. - + " + " + indexOffset // Position of the bank within its parent. - + " + " + parent.indexExpression(prefix) // Position of the parent. - ); - } else { - return(indexOffset // Position within the parent. - + " + " + parent.indexExpression(prefix) // Position of the parent. - ); - } - } - /** * Return a list of Instantiation objects for evaluating parameter * values. The first object in the list is the AST Instantiation @@ -1191,7 +1163,7 @@ private List listPortInstances(List references) { * The offset relative to the parent. This is the sum of * the total number of reactor instances of peer reactors * (those with the same parent) that are instantiated before - * this in the parent. This is used by indexExpression(). + * this in the parent. */ private int indexOffset = 0; diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index ffc1f79aea..8eb1e1f952 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1722,7 +1722,7 @@ class CGenerator extends GeneratorBase { protected def generateConstructor( ReactorDecl reactor, FederateInstance federate, StringBuilder constructorCode ) { - val structType = selfStructType(reactor) + val structType = CUtil.selfType(reactor) pr(''' «structType»* new_«reactor.name»() { «structType»* self = («structType»*)calloc(1, sizeof(«structType»)); @@ -1757,7 +1757,7 @@ class CGenerator extends GeneratorBase { reactionCount++; } - val structType = selfStructType(decl) + val structType = CUtil.selfType(decl) pr(''' void delete_«decl.name»(«structType»* self) { «destructorCode.toString» @@ -1949,7 +1949,7 @@ class CGenerator extends GeneratorBase { StringBuilder destructorCode ) { val reactor = decl.toDefinition - val selfType = selfStructType(decl) + val selfType = CUtil.selfType(decl) // Construct the typedef for the "self" struct. // Create a type name for the self struct. @@ -2862,7 +2862,7 @@ class CGenerator extends GeneratorBase { var StringBuilder reactionInitialization = new StringBuilder() // Define the "self" struct. - var structType = selfStructType(decl) + var structType = CUtil.selfType(decl) // A null structType means there are no inputs, state, // or anything else. No need to declare it. if (structType !== null) { @@ -3099,12 +3099,20 @@ class CGenerator extends GeneratorBase { // First, set up to decrement reference counts for each token type // input of a contained reactor that is present. for (child : instance.children) { - if (currentFederate.contains(child)) { + if (currentFederate.contains(child) && child.inputs.size > 0) { + + // Avoid generating code if not needed. + var foundOne = false; + val temp = new StringBuilder(); var nameOfSelfStruct = CUtil.selfRef(child) + startScopedBlock(temp, child); + getSelfStruct(temp, child); + for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { + foundOne = true; if (input.isMultiport()) { - pr(startTimeStep, ''' + pr(temp, ''' for (int i = 0; i < «input.width»; i++) { _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token = &«nameOfSelfStruct»->_lf_«input.name»[i]->token; @@ -3115,7 +3123,7 @@ class CGenerator extends GeneratorBase { ''') startTimeStepTokens += input.width } else { - pr(startTimeStep, ''' + pr(temp, ''' _lf_tokens_with_ref_count[«startTimeStepTokens»].token = &«nameOfSelfStruct»->_lf_«input.name»->token; _lf_tokens_with_ref_count[«startTimeStepTokens»].status @@ -3126,9 +3134,19 @@ class CGenerator extends GeneratorBase { } } } + endScopedBlock(temp); + if (foundOne) { + pr(startTimeStep, temp.toString()); + } } } + // Avoid generating dead code if nothing is relevant. + var foundOne = false; + val temp = new StringBuilder(); var containerSelfStructName = CUtil.selfRef(instance) + startScopedBlock(temp, instance); + getSelfStruct(temp, instance); + // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an // output of a contained reactor. @@ -3146,7 +3164,8 @@ class CGenerator extends GeneratorBase { // If this is a multiport, then the port struct on the self // struct is a pointer. Otherwise, it is the struct itself. if (port.isMultiport) { - pr(startTimeStep, ''' + foundOne = true; + pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. for (int i = 0; i < «port.width»; i++) { _lf_is_present_fields[«startTimeStepIsPresentCount» + i] @@ -3155,7 +3174,7 @@ class CGenerator extends GeneratorBase { ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution. - pr(startTimeStep, ''' + pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. for (int i = 0; i < «port.width»; i++) { _lf_intended_tag_fields[«startTimeStepIsPresentCount» + i] @@ -3165,14 +3184,15 @@ class CGenerator extends GeneratorBase { } startTimeStepIsPresentCount += port.width; } else { - pr(startTimeStep, ''' + foundOne = true; + pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«containerSelfStructName»->_lf_«port.parent.name».«port.name».is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution. - pr(startTimeStep, ''' + pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«containerSelfStructName»->_lf_«port.parent.name».«port.name».intended_tag; @@ -3191,8 +3211,9 @@ class CGenerator extends GeneratorBase { portsSeen.add(output) // This reaction is receiving data from the port. if (isTokenType((output.definition as Output).inferredType)) { + foundOne = true; if (output.isMultiport()) { - pr(startTimeStep, ''' + pr(temp, ''' for (int i = 0; i < «output.width»; i++) { _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token = &«containerSelfStructName»->_lf_«output.parent.name».«output.name»[i]->token; @@ -3203,7 +3224,7 @@ class CGenerator extends GeneratorBase { ''') startTimeStepTokens += output.width } else { - pr(startTimeStep, ''' + pr(temp, ''' _lf_tokens_with_ref_count[«startTimeStepTokens»].token = &«containerSelfStructName»->_lf_«output.parent.name».«output.name»->token; _lf_tokens_with_ref_count[«startTimeStepTokens»].status @@ -3217,10 +3238,35 @@ class CGenerator extends GeneratorBase { } } } + for (action : instance.actions) { + if (currentFederate === null || currentFederate.contains(action.definition)) { + pr(startTimeStep, ''' + // Add action «action.getFullName» to array of is_present fields. + _lf_is_present_fields[«startTimeStepIsPresentCount»] + = &«containerSelfStructName»->_lf_«action.name».is_present; + ''') + if (isFederatedAndDecentralized) { + // Intended_tag is only applicable to actions in federated execution with decentralized coordination. + pr(startTimeStep, ''' + // Add action «action.getFullName» to array of intended_tag fields. + _lf_intended_tag_fields[«startTimeStepIsPresentCount»] + = &«containerSelfStructName»->_lf_«action.name».intended_tag; + ''') + } + startTimeStepIsPresentCount++ + } + } + + endScopedBlock(temp); + if (foundOne) pr(startTimeStep, temp.toString()); + // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { - if (currentFederate.contains(child)) { + if (currentFederate.contains(child) && child.outputs.size > 0) { var nameOfSelfStruct = CUtil.selfRef(child) + startScopedBlock(startTimeStep, child); + getSelfStruct(startTimeStep, child); + for (output : child.outputs) { if (output.isMultiport()) { pr(startTimeStep, ''' @@ -3260,24 +3306,7 @@ class CGenerator extends GeneratorBase { startTimeStepIsPresentCount++ } } - } - } - for (action : instance.actions) { - if (currentFederate === null || currentFederate.contains(action.definition)) { - pr(startTimeStep, ''' - // Add action «action.getFullName» to array of is_present fields. - _lf_is_present_fields[«startTimeStepIsPresentCount»] - = &«containerSelfStructName»->_lf_«action.name».is_present; - ''') - if (isFederatedAndDecentralized) { - // Intended_tag is only applicable to actions in federated execution with decentralized coordination. - pr(startTimeStep, ''' - // Add action «action.getFullName» to array of intended_tag fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount»] - = &«containerSelfStructName»->_lf_«action.name».intended_tag; - ''') - } - startTimeStepIsPresentCount++ + endScopedBlock(startTimeStep); } } } @@ -3421,16 +3450,6 @@ class CGenerator extends GeneratorBase { } } - /** - * Construct a unique type for the "self" struct of the specified - * reactor class from the reactor class. - * @param reactor The reactor class. - * @return The name of the self struct. - */ - static def selfStructType(ReactorDecl reactor) { - return reactor.name.toLowerCase + "_self_t" - } - /** * Construct a unique type for the struct of the specified * typed variable (port or action) of the specified reactor class. @@ -3584,7 +3603,7 @@ class CGenerator extends GeneratorBase { // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' - «selfStructType(main.definition.reactorClass)»* «CUtil.selfRef(main)» = new_«main.name»(); + «CUtil.selfType(main)»* «CUtil.selfRef(main)» = new_«main.name»(); selfStructs[0] = «CUtil.selfRef(main)»; ''') @@ -3621,26 +3640,24 @@ class CGenerator extends GeneratorBase { def void generateReactorInstance(ReactorInstance instance) { var reactorClass = instance.definition.reactorClass var fullName = instance.fullName - pr(initializeTriggerObjects, '// ************* Instance ' + fullName + ' of class ' + - reactorClass.name) + pr(initializeTriggerObjects, + '// ************* Instance ' + fullName + ' of class ' + reactorClass.name) var nameOfSelfStruct = CUtil.selfRef(instance) - var structType = selfStructType(reactorClass) + var structType = CUtil.selfType(reactorClass) // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. if (instance.isBank) { - val index = CUtil.INDEX_PREFIX + instance.depth; // Array is the self struct name, but without the indexing. var selfStructArrayName = instance.uniqueID + "_self" pr(initializeTriggerObjects, ''' «structType»* «selfStructArrayName»[«instance.width»]; // Create bank members. - for (int «index»; «index» < «instance.width»; «index»++) { ''') - indent(initializeTriggerObjects); } + startScopedBlock(initializeTriggerObjects, instance); // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). The form is slightly different @@ -3656,7 +3673,7 @@ class CGenerator extends GeneratorBase { } // Record the self struct on the big array of self structs. pr(initializeTriggerObjects, ''' - selfStructs[«instance.indexExpression(CUtil.INDEX_PREFIX)»] = «nameOfSelfStruct»; + selfStructs[«CUtil.indexExpression(instance)»] = «nameOfSelfStruct»; ''') // Generate code to initialize the "self" struct in the @@ -3761,13 +3778,7 @@ class CGenerator extends GeneratorBase { // so that it can deallocate any memory. generateStartTimeStep(instance) - if (instance.isBank()) { - // Close the for loop. - unindent(initializeTriggerObjects); - pr(initializeTriggerObjects, ''' - } - ''') - } + endScopedBlock(initializeTriggerObjects); pr(initializeTriggerObjects, "//***** End initializing " + fullName) } @@ -4018,23 +4029,28 @@ class CGenerator extends GeneratorBase { return list.join('{', ', ', '}', [it]) } - /** Set the reaction priorities based on dependency analysis. - * @param reactor The reactor on which to do this. - * @param federate A federate to conditionally generate code for - * contained reactors or null if there are no federates. + /** + * Set the reaction priorities based on dependency analysis. + * @param reactor The reactor on which to do this. + * @param federate A federate to conditionally generate code for + * contained reactors or null if there are no federates. */ def void setReactionPriorities(ReactorInstance reactor, FederateInstance federate) { - // Use "reactionToReactionTName" property of reactionInstance - // to set the levels. + val temp = new StringBuilder(); + var foundOne = false; + startScopedBlock(temp, reactor); + getSelfStruct(temp, reactor); + for (r : reactor.reactions) { if (federate === null || federate.contains( r.definition )) { - val reactionStructName = '''«CUtil.selfRef(r.parent)»->_lf__reaction_«r.index»''' + foundOne = true; + val reactionStructName = '''«CUtil.selfRef(reactor)»->_lf__reaction_«r.index»''' // xtend doesn't support bitwise operators... val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, r.level) val reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL" - pr(''' + pr(temp, ''' «reactionStructName».chain_id = «r.chainID.toString»; // index is the OR of level «r.level» and // deadline «r.deadline.toNanoSeconds» shifted left 16 bits. @@ -4042,6 +4058,9 @@ class CGenerator extends GeneratorBase { ''') } } + endScopedBlock(temp); + if (foundOne) pr(temp.toString()); + for (child : reactor.children) { if (federate.contains(child)) { setReactionPriorities(child, federate) @@ -4675,15 +4694,15 @@ class CGenerator extends GeneratorBase { * Start a scoped block for the specified reactor. * If the reactor is a bank, then this starts a for loop * that iterates over the bank members using a standard index - * variable. If the reactor is not a bank, then this simply + * variable. If the reactor is null or is not a bank, then this simply * starts a scoped block by printing an opening curly brace. * This must be followed by an endScopedBlock(). * @param builder The string builder into which to write. * @param reactor The reactor instance. */ private def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { - if (reactor.isBank) { - val index = CUtil.INDEX_PREFIX + reactor.depth; + if (reactor !== null && reactor.isBank) { + val index = CUtil.bankIndex(reactor); pr(builder, ''' // Initialize bank members. for (int «index»; «index» < «reactor.width»; «index»++) { @@ -4714,10 +4733,9 @@ class CGenerator extends GeneratorBase { */ private def void getSelfStruct(StringBuilder builder, ReactorInstance reactor) { var nameOfSelfStruct = CUtil.selfRef(reactor) - var structType = selfStructType(reactor.definition.reactorClass) + var structType = CUtil.selfType(reactor) pr(builder, ''' - «structType»* «nameOfSelfStruct» - = («structType»*)selfStructs[«reactor.indexExpression(CUtil.INDEX_PREFIX)»]; + «structType»* «nameOfSelfStruct» = («structType»*)selfStructs[«CUtil.indexExpression(reactor)»]; ''') } @@ -5724,11 +5742,19 @@ class CGenerator extends GeneratorBase { && currentFederate.contains(dominatingReaction.definition) && currentFederate.contains(dominatingReaction.parent) ) { + if (dominatingReaction.parent != reaction.parent) { + // Define the destination struct pointer. + // FIXME: If the destination is a bank, need to define the bank_index variable. + getSelfStruct(code, dominatingReaction.parent); + } + startScopedBlock(code, null); + val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' pr(''' // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); ''') + endScopedBlock(code); } else { pr(''' // Reaction «reactionNumber» of «reactorInstance.getFullName» does not depend on one maximal upstream reaction. @@ -5920,6 +5946,11 @@ class CGenerator extends GeneratorBase { var destRangeCount = 0; for (destinationRange : range.destinations) { val destination = destinationRange.getPortInstance(); + + // Define the destination struct pointer. + // FIXME: If the destination is a bank, need to define the bank_index variable. + getSelfStruct(temp, destination.parent); + if (destination.isOutput) { // Include this destination port only if it has at least one // reaction in the federation. diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 753c80e71b..2a6d7d7474 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -28,11 +28,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.PortInstance; import org.lflang.generator.ReactorInstance; +import org.lflang.lf.ReactorDecl; /** * A collection of utilties for C code generation. * This class codifies the coding conventions for the C target code generator. * I.e., it defines how variables are named and referenced. + * @author{Edward A. Lee } */ public class CUtil { @@ -41,12 +43,13 @@ public class CUtil { /** * Return a name of a variable to refer to the bank index of a reactor - * in a bank. This is has the form INDEX_PREFIX_i where i is the depth - * of the reactor (how many parents it has). + * in a bank. This is has the form uniqueID_bank_index where uniqueID + * is an identifier for the instance that is guaranteed to be different + * from the ID of any other instance in the program. * @param instance A reactor instance. */ static public String bankIndex(ReactorInstance instance) { - return INDEX_PREFIX + instance.getDepth(); + return instance.uniqueID() + "_bank_index"; } /** @@ -67,7 +70,7 @@ static public String containedPortRef(PortInstance port) { var destStruct = CUtil.selfRef(port.getParent().getParent()); return destStruct + "->_lf_" + reactorRef(port.getParent()) + "." + port.getName(); } - + /** * Return a string for referencing the struct with the value and is_present * fields of the specified port. This is used for establishing the destination of @@ -85,6 +88,51 @@ static public String destinationRef(PortInstance port) { return selfRef(port.getParent()) + "->_lf_" + port.getName(); } + /** + * Return an expression that, when evaluated in a context with + * bank index variables defined for the specified reactor and + * any container(s) that are also banks, returns a unique index + * for a runtime reactor instance. This can be used to maintain an + * array of runtime instance objects, each of which will have a + * unique index. + * + * This is rather complicated because + * this reactor instance and any of its parents may actually + * represent a bank of runtime reactor instances rather a single + * runtime instance. This method returns an expression that should + * be evaluatable in any target language that uses + for addition + * and * for multiplication and has defined variables in the context + * in which this will be evaluated that specify which bank member is + * desired for this reactor instance and any of its parents that is + * a bank. The names of these variables need to be values returned + * by bankIndex(). + * + * If this is a top-level reactor, this returns "0". + * + * @see bankIndex(ReactorInstance) + * @param prefix The prefix used for index variables for bank members. + */ + static public String indexExpression(ReactorInstance instance) { + if (instance.getDepth() == 0) return("0"); + if (instance.isBank()) { + return( + // Position of the bank member relative to the bank. + bankIndex(instance) + " * " + instance.getNumReactorInstances() + // Position of the bank within its parent. + + " + " + instance.getIndexOffset() + // Position of the parent. + + " + " + indexExpression(instance.getParent()) + ); + } else { + return( + // Position within the parent. + instance.getIndexOffset() + // Position of the parent. + + " + " + indexExpression(instance.getParent()) + ); + } + } + /** * Return a reference to the specified reactor instance within a self * struct. The result has one of the following forms: @@ -102,7 +150,7 @@ static public String reactorRef(ReactorInstance instance) { // If this reactor is a member of a bank of reactors, then change // the name of its self struct to append [index]. if (instance.isBank()) { - return instance.getName() + "[" + INDEX_PREFIX + instance.getDepth() + "]"; + return instance.getName() + "[" + bankIndex(instance) + "]"; } else { return instance.getName(); } @@ -127,14 +175,28 @@ static public String selfRef(ReactorInstance instance) { // If this reactor is a member of a bank of reactors, then change // the name of its self struct to append [index]. if (instance.isBank()) { - result += "[" + INDEX_PREFIX + instance.getDepth() + "]"; + result += "[" + bankIndex(instance) + "]"; } return result; } - - ////////////////////////////////////////////////////// - //// Public constants. - - /** Prefix used for-loop variables when iterating over bank members. */ - public static String INDEX_PREFIX = "i_"; + + /** + * Return a unique type for the "self" struct of the specified + * reactor class from the reactor class. + * @param reactor The reactor class. + * @return The type of a self struct for the specified reactor class. + */ + static public String selfType(ReactorDecl reactor) { + return reactor.getName().toLowerCase() + "_self_t"; + } + + /** + * Construct a unique type for the "self" struct of the specified + * reactor class from the reactor class. + * @param reactor The reactor class. + * @return The name of the self struct. + */ + static public String selfType(ReactorInstance instance) { + return selfType(instance.getDefinition().getReactorClass()); + } } From 7cddef1dbc67380b11ab52c04716df8c17496601 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 20 Nov 2021 14:04:54 -0800 Subject: [PATCH 011/221] One more iteration over bank indices. --- .../src/org/lflang/generator/c/CGenerator.xtend | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 8eb1e1f952..17cc330fd3 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3446,7 +3446,12 @@ class CGenerator extends GeneratorBase { return '''«sourceStruct»->_lf_«port.name»''' } else { val sourceStruct = CUtil.selfRef(port.parent.parent) - return '''«sourceStruct»->_lf_«port.parent.name».«port.name»''' + // The form is slightly different depending on whether the port belongs to a bank. + if (port.parent.isBank) { + return '''«sourceStruct»->_lf_«port.parent.name»[«CUtil.bankIndex(port.parent)»].«port.name»''' + } else { + return '''«sourceStruct»->_lf_«port.parent.name».«port.name»''' + } } } @@ -4705,7 +4710,7 @@ class CGenerator extends GeneratorBase { val index = CUtil.bankIndex(reactor); pr(builder, ''' // Initialize bank members. - for (int «index»; «index» < «reactor.width»; «index»++) { + for (int «index» = 0; «index» < «reactor.width»; «index»++) { ''') } else { pr(builder, "{"); @@ -5605,6 +5610,8 @@ class CGenerator extends GeneratorBase { // Port is an input of a contained reactor that gets data from a reaction of this reactor. portsHandled.add(port); + startScopedBlock(code, port.parent); + // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { @@ -5623,6 +5630,7 @@ class CGenerator extends GeneratorBase { ''') } } + endScopedBlock(code); } } } From 32f77c324f998756a394fad4f85f19f2c862605e Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 20 Nov 2021 18:38:14 -0800 Subject: [PATCH 012/221] More refactoring to CUtil to use sourceRef more uniformly --- .../org/lflang/generator/c/CGenerator.xtend | 129 +++++------------- .../src/org/lflang/generator/c/CUtil.java | 46 ++++++- 2 files changed, 73 insertions(+), 102 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 17cc330fd3..a9c22aee5c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3263,7 +3263,6 @@ class CGenerator extends GeneratorBase { // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { - var nameOfSelfStruct = CUtil.selfRef(child) startScopedBlock(startTimeStep, child); getSelfStruct(startTimeStep, child); @@ -3274,7 +3273,7 @@ class CGenerator extends GeneratorBase { { // Scope to avoid collisions with variable names. int i = «startTimeStepIsPresentCount»; for (int j = 0; j < «output.width»; j++) { - _lf_is_present_fields[i++] = &«nameOfSelfStruct»->_lf_«output.name»[j].is_present; + _lf_is_present_fields[i++] = &«CUtil.sourceRef(output)»[j].is_present; } } ''') @@ -3285,22 +3284,23 @@ class CGenerator extends GeneratorBase { { // Scope to avoid collisions with variable names. int i = «startTimeStepIsPresentCount»; for (int j = 0; j < «output.width»; j++) { - _lf_intended_tag_fields[i++] = &«nameOfSelfStruct»->_lf_«output.name»[j].intended_tag; + _lf_intended_tag_fields[i++] = &«CUtil.sourceRef(output)»[j].intended_tag; } } ''') } startTimeStepIsPresentCount += output.width; } else { + // Output is not a multiport. pr(startTimeStep, ''' // Add port «output.getFullName» to array of is_present fields. - _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«output.name»''', "is_present")»; + _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«CUtil.sourceRef(output)».is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution with decentralized coordination. pr(startTimeStep, ''' // Add port «output.getFullName» to array of Intended_tag fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«output.name»''', "intended_tag")»; + _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«CUtil.sourceRef(output)».intended_tag; ''') } startTimeStepIsPresentCount++ @@ -3418,43 +3418,6 @@ class CGenerator extends GeneratorBase { } } - /** - * Return a string for referencing the data or is_present value of - * the specified port. This is used for establishing the source of - * data for a connection between ports. - * This will have one of the following forms: - * - * * &selfStruct->_lf_portName - * * &selfStruct->_lf_parentName.portName - * - * It is assumed that the specified port is - * the eventual upstream port where the data is stored. E.g., it is an input that - * connected to upstream output, then portName will be the name - * of the upstream output and the selfStruct will be that of the - * upstream reactor. If the port is an input port that is written to - * by a reaction of the parent of the port's parent, then the selfStruct - * will be that of the parent of the port's parent, and parentName - * will the name of the port's parent. - * If the port is an output, then selfStruct will be the parent's - * selfStruct and the portName will be the name of the port. - * - * @param port An instance of the port to be referenced. - */ - static def sourceReference(PortInstance port) { - if (port.isOutput()) { - val sourceStruct = CUtil.selfRef(port.parent); - return '''«sourceStruct»->_lf_«port.name»''' - } else { - val sourceStruct = CUtil.selfRef(port.parent.parent) - // The form is slightly different depending on whether the port belongs to a bank. - if (port.parent.isBank) { - return '''«sourceStruct»->_lf_«port.parent.name»[«CUtil.bankIndex(port.parent)»].«port.name»''' - } else { - return '''«sourceStruct»->_lf_«port.parent.name».«port.name»''' - } - } - } - /** * Construct a unique type for the struct of the specified * typed variable (port or action) of the specified reactor class. @@ -3519,15 +3482,6 @@ class CGenerator extends GeneratorBase { . ''' - /** - * Generates C code to retrieve port.member - * This function is used for clarity and is called whenever struct is allocated on stack memory. - * @param portName The name of the port in string - * @param member The member's name(e.g., is_present) - * @return Generated code - */ - def getStackPortMember(String portName, String member) '''«portName».«member»''' - /** * Return the full name of the specified instance without * the leading name of the top-level reactor, unless this @@ -4120,7 +4074,7 @@ class CGenerator extends GeneratorBase { self->_lf_«outputName».value = («action.inferredType.targetType»)self->_lf__«action.name».token->value; self->_lf_«outputName».token = (lf_token_t*)self->_lf__«action.name».token; ((lf_token_t*)self->_lf__«action.name».token)->ref_count++; - self->«getStackPortMember('''_lf_«outputName»''', "is_present")» = true; + self->_lf_«outputName».is_present = true; ''' } else { ''' @@ -4781,7 +4735,7 @@ class CGenerator extends GeneratorBase { { // To scope variable j int j = «eventualSource.startChannel»; for (int i = «startChannel»; i < «eventualSource.channelWidth» + «startChannel»; i++) { - «CUtil.destinationRef(port)»[i] = («destStructType»*)«modifier»«sourceReference(src)»[j++]; + «CUtil.destinationRef(port)»[i] = («destStructType»*)«modifier»«CUtil.sourceRef(src)»[j++]; } } ''') @@ -4790,21 +4744,21 @@ class CGenerator extends GeneratorBase { // Source is a multiport, destination is a single port. pr(''' // Connect «src.getFullName» to port «port.getFullName» - «CUtil.destinationRef(port)» = («destStructType»*)«modifier»«sourceReference(src)»[«eventualSource.startChannel»]; + «CUtil.destinationRef(port)» = («destStructType»*)«modifier»«CUtil.sourceRef(src)»[«eventualSource.startChannel»]; ''') } } else if (port.isMultiport()) { // Source is a single port, Destination is a multiport. pr(''' // Connect «src.getFullName» to port «port.getFullName» - «CUtil.destinationRef(port)»[«startChannel»] = («destStructType»*)&«sourceReference(src)»; + «CUtil.destinationRef(port)»[«startChannel»] = («destStructType»*)&«CUtil.sourceRef(src)»; ''') startChannel++; } else { // Both ports are single ports. pr(''' // Connect «src.getFullName» to port «port.getFullName» - «CUtil.destinationRef(port)» = («destStructType»*)&«sourceReference(src)»; + «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(src)»; ''') } } @@ -5434,14 +5388,14 @@ class CGenerator extends GeneratorBase { // Connect «port», which gets data from reaction «reaction.index» // of «instance.getFullName», to «port.getFullName». for (int i = 0; i < «port.width»; i++) { - «CUtil.destinationRef(port)»[i] = («destStructType»*)«sourceReference(port)»[i]; + «CUtil.destinationRef(port)»[i] = («destStructType»*)«CUtil.sourceRef(port)»[i]; } ''') } else { pr(''' // Connect «port», which gets data from reaction «reaction.index» // of «instance.getFullName», to «port.getFullName». - «CUtil.destinationRef(port)» = («destStructType»*)&«sourceReference(port)»; + «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(port)»; ''') } } @@ -5468,7 +5422,7 @@ class CGenerator extends GeneratorBase { // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. for (int i = 0; i < «eventualSource.channelWidth»; i++) { - «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»[i + «eventualSource.startChannel»]; + «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«CUtil.sourceRef(sourcePort)»[i + «eventualSource.startChannel»]; } ''') portChannelCount += eventualSource.channelWidth; @@ -5477,7 +5431,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»[«eventualSource.startChannel»]; + «CUtil.containedPortRef(port)» = («destStructType»*)&«CUtil.sourceRef(sourcePort)»[«eventualSource.startChannel»]; ''') portChannelCount++; } else if (port.isMultiport) { @@ -5485,7 +5439,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)»[«portChannelCount»] = («destStructType»*)&«sourceReference(sourcePort)»; + «CUtil.containedPortRef(port)»[«portChannelCount»] = («destStructType»*)&«CUtil.sourceRef(sourcePort)»; ''') portChannelCount++; } else { @@ -5493,7 +5447,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)» = («destStructType»*)&«sourceReference(sourcePort)»; + «CUtil.containedPortRef(port)» = («destStructType»*)&«CUtil.sourceRef(sourcePort)»; ''') portChannelCount++; } @@ -5566,18 +5520,18 @@ class CGenerator extends GeneratorBase { // a common situation on multiport to bank messaging. if (sendingRange.channelWidth == 1) { pr(''' - «sourceReference(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.sourceRef(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') } else { pr(''' for (int i = «start»; i < «end»; i++) { - «sourceReference(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.sourceRef(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; } ''') } } else { pr(''' - «sourceReference(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.sourceRef(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } } @@ -5621,12 +5575,12 @@ class CGenerator extends GeneratorBase { val end = sendingRange.startChannel + sendingRange.channelWidth; pr(''' for (int i = «start»; i < «end»; i++) { - «sourceReference(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.sourceRef(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; } ''') } else { pr(''' - «sourceReference(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.sourceRef(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } } @@ -5850,40 +5804,19 @@ class CGenerator extends GeneratorBase { // individual channel. if (effect.isMultiport()) { // Point the output_produced field to where the is_present field of the port is. - if (effect.parent === reaction.parent) { - // The port belongs to the same reactor as the reaction. - pr(initialization, ''' - for (int i = 0; i < «effect.width»; i++) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.name»[i]''', "is_present")»; - } - ''') - } else { - // The port belongs to a contained reactor. - val containerName = effect.parent.name - pr(initialization, ''' - for (int i = 0; i < «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width; i++) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«nameOfSelfStruct»->_lf_«containerName».«effect.name»[i]->is_present; - } - ''') - } + pr(initialization, ''' + for (int i = 0; i < «effect.width»; i++) { + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] + = &«CUtil.sourceRef(effect)»[i].is_present; + } + ''') outputCount += effect.getWidth(); } else { // The effect is not a multiport nor a port contained by a multiport. - if (effect.parent === reaction.parent) { - // The port belongs to the same reactor as the reaction. - pr(initialization, ''' - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] - = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.name»''', "is_present")»; - ''') - } else { - // The port belongs to a contained reactor. - pr(initialization, ''' - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] - = &«nameOfSelfStruct»->«getStackPortMember('''_lf_«effect.parent.name».«effect.name»''', "is_present")»; - ''') - } + pr(initialization, ''' + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] + = &«CUtil.sourceRef(effect)».is_present; + ''') outputCount++ } } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 2a6d7d7474..a9e52a5942 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -159,13 +159,13 @@ static public String reactorRef(ReactorInstance instance) { /** * Return the unique reference for the "self" struct of the specified * reactor instance. If the instance is a bank of reactors, this returns - * something of the form name_self[id], where i is INDEX_PREFIX and - * d is the depth of the reactor. This assumes that the resulting string - * will be used in a context that defines a variable id. The result has + * something of the form name_self[bankIndex], where bankIndex is the + * returned by {@link bankIndex(ReactorInstance)}. This assumes that the resulting string + * will be used in a context that defines a variable with name bankIndex. The result has * one of the following forms: * * * uniqueID - * * uniqueID[id] + * * uniqueID[bankIndex] * * @param instance The reactor instance. * @return A reference to the self struct. @@ -199,4 +199,42 @@ static public String selfType(ReactorDecl reactor) { static public String selfType(ReactorInstance instance) { return selfType(instance.getDefinition().getReactorClass()); } + + /** + * Return a string for referencing the data, width, or is_present value of + * the specified port that is a source of data. + * This will have one of the following forms: + * + * * selfStruct->_lf_portName + * * selfStruct->_lf_parentName.portName + * * selfStruct->_lf_parentName[bankIndex].portName + * + * The selfStruct points to the port's parent (for an output) or the + * port's parent's parent (for an input), and it that parent + * is a bank, then it will have the form selfStruct[bankIndex], + * where bankIndex is the string returned by {@link bankIndex(ReactorInstance)}. + * + * If the port is an output, then the first form is returned. + * The second and third forms are returned for an input, in which case + * the "source" is a reaction port's parent's parent. If that parent + * is a bank, then the third form results, and bankIndex will be the + * value returned by {@link bankIndex(ReactorInstance)} for that parent. + * + * @param port An instance of the port to be referenced. + */ + static public String sourceRef(PortInstance port) { + if (port.isOutput()) { + String sourceStruct = CUtil.selfRef(port.getParent()); + return sourceStruct + "->_lf_" + port.getName(); + } else { + String sourceStruct = CUtil.selfRef(port.getParent().getParent()); + // The form is slightly different depending on whether the port belongs to a bank. + if (port.getParent().isBank()) { + return sourceStruct + "->_lf_" + port.getParent().getName() + + "[" + bankIndex(port.getParent()) + "]." + port.getName(); + } else { + return sourceStruct + "->_lf_" + port.getParent().getName() + "." + port.getName(); + } + } + } } From 218f8598e0d207df6db7a05d26de0f2961d017b3 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sun, 21 Nov 2021 00:07:47 -0800 Subject: [PATCH 013/221] Begin to factor out and port GeneratorBase methods to Java. --- .../org/lflang/generator/GeneratorBase.xtend | 181 ++---------------- .../src/org/lflang/generator/c/CCompiler.java | 6 +- .../org/lflang/generator/c/CGenerator.xtend | 62 +++--- .../generator/python/PythonGenerator.xtend | 11 +- .../org/lflang/generator/ts/TSGenerator.kt | 10 +- 5 files changed, 52 insertions(+), 218 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 4ebde95127..d2cbb42208 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -37,6 +37,7 @@ import java.util.LinkedHashSet import java.util.List import java.util.Map import java.util.Set +import java.util.stream.Collectors import java.util.regex.Pattern import org.eclipse.core.resources.IMarker import org.eclipse.core.resources.IResource @@ -44,9 +45,7 @@ import org.eclipse.core.resources.ResourcesPlugin import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGeneratorContext -import org.eclipse.xtext.resource.XtextResource import org.eclipse.xtext.util.CancelIndicator -import org.eclipse.xtext.validation.CheckMode import org.lflang.ASTUtils import org.lflang.ErrorReporter import org.lflang.FileConfig @@ -55,7 +54,6 @@ import org.lflang.MainConflictChecker import org.lflang.Target import org.lflang.TargetConfig import org.lflang.TargetConfig.Mode -import org.lflang.TargetProperty import org.lflang.TargetProperty.CoordinationType import org.lflang.TimeValue import org.lflang.federated.FedASTUtils @@ -63,7 +61,6 @@ import org.lflang.federated.FederateInstance import org.lflang.federated.serialization.SupportedSerializers import org.lflang.graph.InstantiationGraph import org.lflang.lf.Action -import org.lflang.lf.ActionOrigin import org.lflang.lf.Delay import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory @@ -73,7 +70,6 @@ import org.lflang.lf.Port import org.lflang.lf.Reaction import org.lflang.lf.Reactor import org.lflang.lf.StateVar -import org.lflang.lf.TargetDecl import org.lflang.lf.Time import org.lflang.lf.TimeUnit import org.lflang.lf.Value @@ -267,83 +263,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { return this.delayClasses.findFirst[it|it.name.equals(className)] } - /** - * Set the appropriate target properties based on the target properties of - * the main .lf file. - */ - protected def void setTargetConfig(IGeneratorContext context) { - - val target = fileConfig.resource.findTarget - if (target.config !== null) { - // Update the configuration according to the set target properties. - TargetProperty.set(this.targetConfig, target.config.pairs ?: emptyList, errorReporter) - } - - // Accommodate the physical actions in the main .lf file - accommodatePhysicalActionsIfPresent(fileConfig.resource); - - // Override target properties if specified as command line arguments. - if (context instanceof StandaloneContext) { - if (context.args.containsKey("no-compile")) { - targetConfig.noCompile = true - } - if (context.args.containsKey("threads")) { - targetConfig.threads = Integer.parseInt(context.args.getProperty("threads")) - } - if (context.args.containsKey("target-compiler")) { - targetConfig.compiler = context.args.getProperty("target-compiler") - } - if (context.args.containsKey("target-flags")) { - targetConfig.compilerFlags.clear() - if (!context.args.getProperty("target-flags").isEmpty) { - targetConfig.compilerFlags.addAll(context.args.getProperty("target-flags").split(' ')) - } - } - if (context.args.containsKey("runtime-version")) { - targetConfig.runtimeVersion = context.args.getProperty("runtime-version") - } - if (context.args.containsKey("external-runtime-path")) { - targetConfig.externalRuntimePath = context.args.getProperty("external-runtime-path") - } - if (context.args.containsKey(TargetProperty.KEEPALIVE.description)) { - targetConfig.keepalive = Boolean.parseBoolean( - context.args.getProperty(TargetProperty.KEEPALIVE.description)); - } - } - } - - /** - * Look for physical actions in 'resource'. - * If found, take appropriate actions to accommodate. - * - * Set keepalive to true. - */ - protected def void accommodatePhysicalActionsIfPresent(Resource resource) { - if (!target.setsKeepAliveOptionAutomatically) { - return; // nothing to do - } - - // If there are any physical actions, ensure the threaded engine is used and that - // keepalive is set to true, unless the user has explicitly set it to false. - for (action : resource.allContents.toIterable.filter(Action)) { - if (action.origin == ActionOrigin.PHYSICAL) { - // Check if the user has explicitly set keepalive to false or true - if (!targetConfig.setByUser.contains(TargetProperty.KEEPALIVE) - && targetConfig.keepalive == false - ) { - // If not, set it to true - targetConfig.keepalive = true - errorReporter.reportWarning( - action, - '''Setting «TargetProperty.KEEPALIVE.displayName» to true because of «action.name».« - » This can be overridden by setting the «TargetProperty.KEEPALIVE.description»« - » target property manually.''' - ); - } - } - } - } - /** * If there is a main or federated reactor, then create a synthetic Instantiation * for that top-level reactor and set the field mainDef to refer to it. @@ -375,7 +294,9 @@ abstract class GeneratorBase extends JavaGeneratorBase { */ def void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { - setTargetConfig(context) + JavaGeneratorUtils.setTargetConfig( + context, JavaGeneratorUtils.findTarget(fileConfig.resource), targetConfig, errorReporter + ) fileConfig.cleanIfNeeded() @@ -415,9 +336,16 @@ abstract class GeneratorBase extends JavaGeneratorBase { // to validate, which happens in setResources(). setReactorsAndInstantiationGraph() - // Collect the reactors defined in this resource (i.e., file in Eclipse speak) and (non-main) - // reactors defined in imported resources. - setResources(context) + JavaGeneratorUtils.validateImports(context, fileConfig, instantiationGraph, errorReporter) + val allResources = JavaGeneratorUtils.getResources(reactors) + resources.addAll(allResources.stream() + .filter [it | it != fileConfig.resource] + .map [it | JavaGeneratorUtils.getLFResource(it, fsa, context, errorReporter)] + .collect(Collectors.toList()) + ) + JavaGeneratorUtils.accommodatePhysicalActionsIfPresent(allResources, target, targetConfig, errorReporter); + // FIXME: Should the GeneratorBase pull in `files` from imported + // resources? // Reroute connections that have delays associated with them via generated delay reactors. transformDelays() @@ -436,8 +364,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { // enable support for them. enableSupportForSerialization(context.cancelIndicator); } - - } /** @@ -478,68 +404,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - /** - * Update the class variable that lists all the involved resources. Also report validation problems of imported - * resources at the import statements through those failing resources are reached. - * - * @param context The context providing the cancel indicator used by the validator. - */ - protected def setResources(IGeneratorContext context) { - val fsa = this.fileConfig.fsa; - val validator = (this.fileConfig.resource as XtextResource).resourceServiceProvider.resourceValidator - if (mainDef !== null) { - reactors.add(mainDef.reactorClass as Reactor); - this.resources.add( - new LFResource( - mainDef.reactorClass.eResource, - this.fileConfig, - this.targetConfig)); - } - // Iterate over reactors and mark their resources as tainted if they import resources that are either marked - // as tainted or fail to validate. - val tainted = newHashSet - for (r : this.reactors) { - val res = r.eResource - if (!this.resources.contains(res)) { - if (res !== this.fileConfig.resource) { - if (tainted.contains(res) || - (validator.validate(res, CheckMode.ALL, context.cancelIndicator)).size > 0) { - for (inst : this.instantiationGraph.getDownstreamAdjacentNodes(r)) { - for (imp : (inst.eContainer as Model).imports) { - for (decl : imp.reactorClasses) { - if (decl.reactorClass.eResource === res) { - errorReporter.reportError(imp, '''Unresolved compilation issues in '«imp.importURI»'.''') - tainted.add(decl.eResource) - } - } - } - } - } - // Read the target property of the imported file - val target = res.findTarget - var targetConfig = new TargetConfig(); - if (target.config !== null) { - TargetProperty.set(targetConfig, target.config.pairs ?: emptyList, errorReporter); - } - val fileConfig = new FileConfig(res, fsa, context); - // Add it to the list of LFResources - this.resources.add( - new LFResource( - res, - fileConfig, - targetConfig) - ); - - // Accommodate the physical actions in the imported .lf file - accommodatePhysicalActionsIfPresent(res); - // FIXME: Should the GeneratorBase pull in `files` from imported - // resources? If so, uncomment the following line. - // copyUserFiles(targetConfig, fileConfig); - } - } - } - } - /** * Copy all files listed in the target property `files` into the * src-gen folder of the main .lf file. @@ -791,23 +655,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { code = new StringBuilder } - /** - * Return the target. - */ - def findTarget(Resource resource) { - var TargetDecl targetDecl - for (t : resource.allContents.toIterable.filter(TargetDecl)) { - if (targetDecl !== null) { - throw new InvalidSourceException("There is more than one target!") // FIXME: check this in validator - } - targetDecl = t - } - if (targetDecl === null) { - throw new InvalidSourceException("No target found!") - } - targetDecl - } - /** * Generate code for the body of a reaction that handles the * action that is triggered by receiving a message from a remote diff --git a/org.lflang/src/org/lflang/generator/c/CCompiler.java b/org.lflang/src/org/lflang/generator/c/CCompiler.java index c37b3ed122..9b17fe1b5c 100644 --- a/org.lflang/src/org/lflang/generator/c/CCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCompiler.java @@ -192,10 +192,8 @@ public LFCommand compileCCommand( compileArgs.add("-DNUMBER_OF_WORKERS="+targetConfig.threads); } - // Finally add the compiler flags in target parameters (if any) - if (!targetConfig.compilerFlags.isEmpty()) { - compileArgs.addAll(targetConfig.compilerFlags); - } + // Finally, add the compiler flags in target parameters (if any) + compileArgs.addAll(targetConfig.compilerFlags); // Only set the output file name if it hasn't already been set // using a target property or Args line flag. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index a9c22aee5c..d03d76c5cc 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -63,6 +63,7 @@ import org.lflang.federated.serialization.FedROS2CPPSerialization import org.lflang.federated.serialization.SupportedSerializers import org.lflang.generator.ActionInstance import org.lflang.generator.GeneratorBase +import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.ParameterInstance import org.lflang.generator.PortInstance import org.lflang.generator.ReactionInstance @@ -356,12 +357,12 @@ class CGenerator extends GeneratorBase { var boolean CCppMode = false; new(FileConfig fileConfig, ErrorReporter errorReporter, boolean CCppMode) { - this(fileConfig, errorReporter) - this.CCppMode = CCppMode; + super(fileConfig, errorReporter) + this.CCppMode = CCppMode; } new(FileConfig fileConfig, ErrorReporter errorReporter) { - super(fileConfig, errorReporter) + this(fileConfig, errorReporter, false) } //////////////////////////////////////////// @@ -373,14 +374,10 @@ class CGenerator extends GeneratorBase { } /** - * Set the appropriate target properties based on the target properties of - * the main .lf file. + * Set C-specific default target properties if needed. */ - override setTargetConfig(IGeneratorContext context) { - super.setTargetConfig(context); - // Set defaults for the compiler after parsing the target properties - // of the main .lf file. - if (targetConfig.useCmake == false && targetConfig.compiler.isNullOrEmpty) { + def setCSpecificDefaults() { + if (!targetConfig.useCmake && targetConfig.compiler.isNullOrEmpty) { if (this.CCppMode) { targetConfig.compiler = "g++" targetConfig.compilerFlags.addAll("-O2", "-Wno-write-strings") @@ -392,34 +389,28 @@ class CGenerator extends GeneratorBase { } /** - * Look for physical actions in 'resource'. - * If found, take appropriate actions to accommodate. - * - * Set keepalive to true. - * Set threads to be at least one to allow asynchronous schedule calls + * Look for physical actions in all resources. + * If found, set threads to be at least one to allow asynchronous schedule calls. */ - override accommodatePhysicalActionsIfPresent(Resource resource) { - super.accommodatePhysicalActionsIfPresent(resource); - + def accommodatePhysicalActionsIfPresent() { // If there are any physical actions, ensure the threaded engine is used and that // keepalive is set to true, unless the user has explicitly set it to false. - for (action : resource.allContents.toIterable.filter(Action)) { - if (action.origin == ActionOrigin.PHYSICAL) { - // If the unthreaded runtime is requested, use the threaded runtime instead - // because it is the only one currently capable of handling asynchronous events. - if (targetConfig.threads < 1) { - targetConfig.threads = 1 - errorReporter.reportWarning( - action, - '''Using the threaded C runtime to allow for asynchronous handling of« - » physical action «action.name».''' - ); + for (resource : JavaGeneratorUtils.getResources(reactors)) { + for (action : resource.allContents.toIterable.filter(Action)) { + if (action.origin == ActionOrigin.PHYSICAL) { + // If the unthreaded runtime is requested, use the threaded runtime instead + // because it is the only one currently capable of handling asynchronous events. + if (targetConfig.threads < 1) { + targetConfig.threads = 1 + errorReporter.reportWarning( + action, + '''Using the threaded C runtime to allow for asynchronous handling of« + » physical action «action.name».''' + ); + } } - } - } - } /** @@ -462,10 +453,13 @@ class CGenerator extends GeneratorBase { * generation. * @param resource The resource containing the source code. * @param fsa The file system access (used to write the result). - * @param context FIXME: Undocumented argument. No idea what this is. + * @param context Information about the invocation of this code generator, + * including whether the code generation process has been cancelled. */ override void doGenerate(Resource resource, IFileSystemAccess2 fsa, IGeneratorContext context) { + accommodatePhysicalActionsIfPresent() + setCSpecificDefaults() // The following generates code needed by all the reactors. super.doGenerate(resource, fsa, context) @@ -598,7 +592,7 @@ class CGenerator extends GeneratorBase { targetConfig.filesNamesWithoutPath.clear(); // Re-apply the cmake-include target property of the main .lf file. - val target = mainDef.reactorClass.eResource.findTarget + val target = JavaGeneratorUtils.findTarget(mainDef.reactorClass.eResource) if (target.config !== null) { // Update the cmake-include TargetProperty.updateOne( diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index b3baadf465..4e8f0ca6a7 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1379,16 +1379,7 @@ class PythonGenerator extends CGenerator { override generateForwardBody(Action action, VarRef port) { val outputName = generateVarRef(port) if (action.inferredType.isTokenType) { - // Forward the entire token and prevent freeing. - // Increment the ref_count because it will be decremented - // by both the action handling code and the input handling code. - ''' - «DISABLE_REACTION_INITIALIZATION_MARKER» - self->_lf_«outputName».value = («action.inferredType.targetType»)self->_lf__«action.name».token->value; - self->_lf_«outputName».token = (lf_token_t*)self->_lf__«action.name».token; - ((lf_token_t*)self->_lf__«action.name».token)->ref_count++; - self->«getStackPortMember('''_lf_«outputName»''', "is_present")» = true; - ''' + super.generateForwardBody(action, port) } else { ''' SET(«outputName», «action.name»->token->value); diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 0ac8160e91..772fa20353 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -40,6 +40,7 @@ import org.lflang.scoping.LFGlobalScopeProvider import java.nio.file.Files import java.util.* import org.lflang.federated.serialization.SupportedSerializers +import org.lflang.generator.JavaGeneratorUtils /** * Generator for TypeScript target. @@ -196,7 +197,8 @@ class TSGenerator( if (pnpmInstall != null) { val ret = pnpmInstall.run(context.cancelIndicator) if (ret != 0) { - errorReporter.reportError(findTarget(resource), + errorReporter.reportError( + JavaGeneratorUtils.findTarget(resource), "ERROR: pnpm install command failed: " + pnpmInstall.errors.toString()) } } else { @@ -214,9 +216,11 @@ class TSGenerator( } if (npmInstall.run(context.cancelIndicator) != 0) { - errorReporter.reportError(findTarget(resource), + errorReporter.reportError( + JavaGeneratorUtils.findTarget(resource), "ERROR: npm install command failed: " + npmInstall.errors.toString()) - errorReporter.reportError(findTarget(resource), "ERROR: npm install command failed." + + errorReporter.reportError( + JavaGeneratorUtils.findTarget(resource), "ERROR: npm install command failed." + "\nFor installation instructions, see: https://www.npmjs.com/get-npm") return } From fe3b28595df7c905f436b20b12f0748b838b447f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sun, 21 Nov 2021 10:44:09 -0800 Subject: [PATCH 014/221] Add file that should have been in previous commit. --- .../lflang/generator/JavaGeneratorUtils.java | 289 ++++++++++++++++++ 1 file changed, 289 insertions(+) create mode 100644 org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java new file mode 100644 index 0000000000..4511a8725e --- /dev/null +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -0,0 +1,289 @@ +package org.lflang.generator; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.emf.ecore.resource.Resource; +import org.eclipse.xtext.generator.IFileSystemAccess2; +import org.eclipse.xtext.generator.IGeneratorContext; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.validation.CheckMode; +import org.eclipse.xtext.validation.IResourceValidator; + +import org.lflang.ErrorReporter; +import org.lflang.FileConfig; +import org.lflang.Target; +import org.lflang.TargetConfig; +import org.lflang.TargetProperty; +import org.lflang.graph.InstantiationGraph; +import org.lflang.lf.Action; +import org.lflang.lf.ActionOrigin; +import org.lflang.lf.Import; +import org.lflang.lf.KeyValuePair; +import org.lflang.lf.KeyValuePairs; +import org.lflang.lf.Model; +import org.lflang.lf.Reactor; +import org.lflang.lf.TargetDecl; + +/** + * A helper class with functions that may be useful for code + * generators. + * This is created to ease our transition from Xtend and + * possibly Eclipse. All functions in this class should + * instead be in GeneratorUtils.kt, but Eclipse cannot + * handle Kotlin files. + */ +public final class JavaGeneratorUtils { + + private JavaGeneratorUtils() { + // utility class + } + + /** + * Return the target declaration found in the given resource. + */ + public static TargetDecl findTarget(Resource resource) { + TargetDecl targetDecl = null; + for (TargetDecl t : findAll(resource, TargetDecl.class)) { // getAllContents should never return null. + if (targetDecl != null) { + throw new InvalidSourceException("There is more than one target!"); // FIXME: check this in validator + } + targetDecl = t; + } + if (targetDecl == null) { + throw new InvalidSourceException("No target found!"); + } + return targetDecl; + } + + /** + * Set the appropriate target properties based on the target properties of + * the main .lf file and the given command-line arguments, if applicable. + * @param context The generator invocation context. + * @param target The target configuration that appears in an LF source file. + * @param targetConfig The target config to be updated. + * @param errorReporter The error reporter to which errors should be sent. + */ + public static void setTargetConfig( + IGeneratorContext context, + TargetDecl target, + TargetConfig targetConfig, + ErrorReporter errorReporter + ) { + if (target.getConfig() != null) { + List pairs = target.getConfig().getPairs(); + TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), errorReporter); + } + if (context instanceof StandaloneContext) { + StandaloneContext standaloneContext = (StandaloneContext) context; + if (standaloneContext.getArgs().containsKey("no-compile")) { + targetConfig.noCompile = true; + } + if (standaloneContext.getArgs().containsKey("threads")) { + targetConfig.threads = Integer.parseInt(standaloneContext.getArgs().getProperty("threads")); + } + if (standaloneContext.getArgs().containsKey("target-compiler")) { + targetConfig.compiler = standaloneContext.getArgs().getProperty("target-compiler"); + } + if (standaloneContext.getArgs().containsKey("target-flags")) { + targetConfig.compilerFlags.clear(); + if (!standaloneContext.getArgs().getProperty("target-flags").isEmpty()) { + targetConfig.compilerFlags.addAll(List.of( + standaloneContext.getArgs().getProperty("target-flags").split(" ") + )); + } + } + if (standaloneContext.getArgs().containsKey("runtime-version")) { + targetConfig.runtimeVersion = standaloneContext.getArgs().getProperty("runtime-version"); + } + if (standaloneContext.getArgs().containsKey("external-runtime-path")) { + targetConfig.externalRuntimePath = standaloneContext.getArgs().getProperty("external-runtime-path"); + } + if (standaloneContext.getArgs().containsKey(TargetProperty.KEEPALIVE.description)) { + targetConfig.keepalive = Boolean.parseBoolean( + standaloneContext.getArgs().getProperty(TargetProperty.KEEPALIVE.description)); + } + } + } + + /** + * Look for physical actions in 'resource'. + * If appropriate, set keepalive to true in + * {@code targetConfig}. + * This is a helper function for setTargetConfig. It + * should not be used elsewhere. + */ + public static void accommodatePhysicalActionsIfPresent( + List resources, + Target target, + TargetConfig targetConfig, + ErrorReporter errorReporter + ) { + if (!target.setsKeepAliveOptionAutomatically()) { + return; + } + for (Resource resource : resources) { + for (Action action : findAll(resource, Action.class)) { + if (action.getOrigin() == ActionOrigin.PHYSICAL) { + // Check if the user has explicitly set keepalive to false + if (!targetConfig.setByUser.contains(TargetProperty.KEEPALIVE) && !targetConfig.keepalive) { + // If not, set it to true + targetConfig.keepalive = true; + errorReporter.reportWarning( + action, + String.format( + "Setting %s to true because of the physical action %s.", + TargetProperty.KEEPALIVE.getDisplayName(), + action.getName() + ) + ); + return; + } + } + } + } + } + + /** + * Returns all instances of {@code eObjectType} in + * {@code resource}. + * @param resource A resource to be searched. + * @param nodeType The type of the desired parse tree + * nodes. + * @param The type of the desired parse tree nodes. + * @return all instances of {@code eObjectType} in + * {@code resource} + */ + public static Iterable findAll(Resource resource, Class nodeType) { + Iterator contents = resource.getAllContents(); + assert contents != null : "Although getAllContents is not marked as NotNull, it should be."; + EObject temp = null; + while (!nodeType.isInstance(temp) && contents.hasNext()) temp = contents.next(); + EObject next_ = temp; + return () -> new Iterator<>() { + EObject next = next_; + + @Override + public boolean hasNext() { + return nodeType.isInstance(next); + } + + @Override + public T next() { + // This cast is safe if hasNext() holds. + assert hasNext() : "next() was called on an Iterator when hasNext() was false."; + //noinspection unchecked + T current = (T) next; + next = null; + while (!nodeType.isInstance(next) && contents.hasNext()) next = contents.next(); + return current; + } + }; + } + + /** + * Validate the files containing reactors in the given + * {@code instantiationGraph} and propagate the + * resulting errors. + * @param context The context providing the cancel + * indicator used by the validator. + * @param fileConfig The file system configuration. + * @param instantiationGraph A DAG containing all + * reactors of interest. + * @param errorReporter An error acceptor. + */ + public static void validateImports( + IGeneratorContext context, + FileConfig fileConfig, + InstantiationGraph instantiationGraph, + ErrorReporter errorReporter + ) { + // FIXME: This method is based on a part of setResources, a method that used to exist in GeneratorBase. + // It is quite different. There should be a test that verifies that it has the correct behavior. + IResourceValidator validator = ((XtextResource) fileConfig.resource).getResourceServiceProvider() + .getResourceValidator(); + HashSet bad = new HashSet<>(); + HashSet visited = new HashSet<>(); + // The graph must be traversed in topological order so that errors will propagate through arbitrarily many + // levels. + for (Reactor reactor : instantiationGraph.nodesInTopologicalOrder()) { + Resource resource = reactor.eResource(); + if (visited.contains(resource)) continue; + visited.add(resource); + if ( + bad.contains(resource) || validator.validate( + resource, CheckMode.ALL, context.getCancelIndicator() + ).size() > 0 + ) { + for (Reactor downstreamReactor : instantiationGraph.getDownstreamAdjacentNodes(reactor)) { + for (Import importStatement : ((Model) downstreamReactor.eContainer()).getImports()) { + errorReporter.reportError(importStatement, String.format( + "Unresolved compilation issues in '%s'.", importStatement.getImportURI() + )); + bad.add(downstreamReactor.eResource()); + } + } + } + } + } + + /** + * Returns the resources that provide the given + * reactors. + * @param reactors The reactors for which to find + * containing resources. + * @return the resources that provide the given + * reactors. + */ + public static List getResources(Iterable reactors) { + HashSet visited = new HashSet<>(); + List resources = new ArrayList<>(); + for (Reactor r : reactors) { + Resource resource = r.eResource(); + if (!visited.contains(resource)) { + visited.add(resource); + resources.add(resource); + } + } + return resources; + } + + /** + * Returns the {@code LFResource} representation of the + * given resource. + * @param resource The {@code Resource} to be + * represented as an {@code LFResource} + * @param fsa An object that provides access to the file + * system + * @param context The generator invocation context. + * @param errorReporter An error message acceptor. + * @return the {@code LFResource} representation of the + * given resource. + */ + public static LFResource getLFResource( + Resource resource, + IFileSystemAccess2 fsa, + IGeneratorContext context, + ErrorReporter errorReporter + ) { + TargetDecl target = JavaGeneratorUtils.findTarget(resource); + KeyValuePairs config = target.getConfig(); + var targetConfig = new TargetConfig(); + if (config != null) { + List pairs = config.getPairs(); + TargetProperty.set(targetConfig, pairs != null ? pairs : List.of(), errorReporter); + } + FileConfig fc; + try { + fc = new FileConfig(resource, fsa, context); + } catch (IOException e) { + throw new RuntimeException("Failed to instantiate an imported resource because an I/O error " + + "occurred."); + } + return new LFResource(resource, fc, targetConfig); + } +} From 71cc7801e2a1d7c21174b68bf746838daf04339f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sun, 21 Nov 2021 23:15:53 -0800 Subject: [PATCH 015/221] More factoring out of GeneratorBase. --- org.lflang/src/org/lflang/JavaAstUtils.java | 48 +++++++++++++++++++ .../federated/PythonGeneratorExtension.java | 7 +-- .../org/lflang/generator/GeneratorBase.xtend | 41 ---------------- .../org/lflang/generator/c/CGenerator.xtend | 13 ++--- .../generator/python/PythonGenerator.xtend | 5 +- .../org/lflang/generator/ts/TSGenerator.kt | 4 +- .../generator/ts/TSReactionGenerator.kt | 3 +- 7 files changed, 66 insertions(+), 55 deletions(-) diff --git a/org.lflang/src/org/lflang/JavaAstUtils.java b/org.lflang/src/org/lflang/JavaAstUtils.java index c9dc138f7c..96ff398494 100644 --- a/org.lflang/src/org/lflang/JavaAstUtils.java +++ b/org.lflang/src/org/lflang/JavaAstUtils.java @@ -34,6 +34,7 @@ import org.lflang.lf.StateVar; import org.lflang.lf.Type; import org.lflang.lf.Value; +import org.lflang.lf.VarRef; /** * Helper class to manipulate the LF AST. This is partly @@ -161,4 +162,51 @@ public static String addZeroToLeadingDot(String literal) { if (m.matches()) return literal.replace(".", "0."); return literal; } + + //////////////////////////////// + //// Utility functions for translating AST nodes into text + // This is a continuation of a large section of ASTUtils.xtend + // with the same name. + + /** + * Generate code for referencing a port, action, or timer. + * @param reference The reference to the variable. + */ + public static String generateVarRef(VarRef reference) { + var prefix = ""; + if (reference.getContainer() != null) { + prefix = reference.getContainer().getName() + "."; + } + return prefix + reference.getVariable().getName(); + } + + /** + * Generate code for referencing a port possibly indexed by + * a bank index and/or a multiport index. This assumes the target language uses + * the usual array indexing [n] for both cases. If the provided reference is + * not a port, then this returns the string "ERROR: not a port.". + * @param reference The reference to the port. + * @param bankIndex A bank index or null or negative if not in a bank. + * @param multiportIndex A multiport index or null or negative if not in a multiport. + */ + public static String generatePortRef(VarRef reference, Integer bankIndex, Integer multiportIndex) { + // FIXME: Should this be moved to CUtil? It is intended to generalize beyond C, but as of this + // writing, only CGenerator and PythonGeneratorExtension use it. + if (!(reference.getVariable() instanceof Port)) { + return "ERROR: not a port."; // FIXME: This is not the fail-fast approach, and it seems arbitrary. + } + var prefix = ""; + if (reference.getContainer() != null) { + var bank = ""; + if (reference.getContainer().getWidthSpec() != null && bankIndex != null && bankIndex >= 0) { + bank = "[" + bankIndex + "]"; + } + prefix = reference.getContainer().getName() + bank + "."; + } + var multiport = ""; + if (((Port) reference.getVariable()).getWidthSpec() != null && multiportIndex != null && multiportIndex >= 0) { + multiport = "[" + multiportIndex + "]"; + } + return prefix + reference.getVariable().getName() + multiport; + } } diff --git a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java index 01a0461d44..53c4620656 100644 --- a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java @@ -28,6 +28,7 @@ import org.lflang.InferredType; import org.lflang.TargetProperty.CoordinationType; +import org.lflang.JavaAstUtils; import org.lflang.federated.serialization.FedNativePythonSerialization; import org.lflang.federated.serialization.FedSerialization; import org.lflang.federated.serialization.SupportedSerializers; @@ -74,8 +75,8 @@ public static String generateNetworkSenderBody( SupportedSerializers serializer, PythonGenerator generator ) { - String sendRef = generator.generatePortRef(sendingPort, sendingBankIndex, sendingChannelIndex); - String receiveRef = generator.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. + String sendRef = JavaAstUtils.generatePortRef(sendingPort, sendingBankIndex, sendingChannelIndex); + String receiveRef = JavaAstUtils.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. StringBuilder result = new StringBuilder(); result.append("// Sending from " + sendRef + " in federate " + sendingFed.name + " to " + receiveRef + " in federate " + receivingFed.name + "\n"); @@ -179,7 +180,7 @@ public static String generateNetworkReceiverBody( PythonGenerator generator ) { - String receiveRef = generator.generatePortRef(receivingPort, receivingBankIndex, receivingChannelIndex); + String receiveRef = JavaAstUtils.generatePortRef(receivingPort, receivingBankIndex, receivingChannelIndex); StringBuilder result = new StringBuilder(); // Transfer the physical time of arrival from the action to the port diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index d2cbb42208..892e23ec46 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -465,47 +465,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { */ abstract def String generateDelayGeneric(); - /** - * Generate code for referencing a port, action, or timer. - * @param reference The reference to the variable. - */ - def String generateVarRef(VarRef reference) { - var prefix = ""; - if (reference.container !== null) { - prefix = reference.container.name + "." - } - return prefix + reference.variable.name - } - - /** - * Generate code for referencing a port possibly indexed by - * a bank index and/or a multiport index. This assumes the target language uses - * the usual array indexing [n] for both cases. If not, this needs to be overridden - * by the target code generator. If the provided reference is not a port, then - * this return the string "ERROR: not a port.". - * @param reference The reference to the port. - * @param bankIndex A bank index or null or negative if not in a bank. - * @param multiportIndex A multiport index or null or negative if not in a multiport. - */ - def String generatePortRef(VarRef reference, Integer bankIndex, Integer multiportIndex) { - if (!(reference.variable instanceof Port)) { - return "ERROR: not a port."; - } - var prefix = ""; - if (reference.container !== null) { - var bank = ""; - if (reference.container.widthSpec !== null && bankIndex !== null && bankIndex >= 0) { - bank = "[" + bankIndex + "]"; - } - prefix = reference.container.name + bank + "." - } - var multiport = ""; - if ((reference.variable as Port).widthSpec !== null && multiportIndex !== null && multiportIndex >= 0) { - multiport = "[" + multiportIndex + "]"; - } - return prefix + reference.variable.name + multiport; - } - /** * Return true if the reaction is unordered. An unordered reaction is one * that does not have any dependency on other reactions in the containing diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d03d76c5cc..aab73e7b57 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -45,6 +45,7 @@ import org.eclipse.xtext.generator.IGeneratorContext import org.eclipse.xtext.nodemodel.util.NodeModelUtils import org.eclipse.xtext.util.CancelIndicator import org.lflang.ASTUtils +import org.lflang.JavaAstUtils import org.lflang.ErrorReporter import org.lflang.FileConfig import org.lflang.InferredType @@ -4031,7 +4032,7 @@ class CGenerator extends GeneratorBase { * @param port The port to read from */ override generateDelayBody(Action action, VarRef port) { - val ref = generateVarRef(port); + val ref = JavaAstUtils.generateVarRef(port); // Note that the action.type set by the base class is actually // the port type. if (action.inferredType.isTokenType) { @@ -4058,7 +4059,7 @@ class CGenerator extends GeneratorBase { * @param port The port to write to. */ override generateForwardBody(Action action, VarRef port) { - val outputName = generateVarRef(port) + val outputName = JavaAstUtils.generateVarRef(port) if (action.inferredType.isTokenType) { // Forward the entire token and prevent freeing. // Increment the ref_count because it will be decremented @@ -4119,7 +4120,7 @@ class CGenerator extends GeneratorBase { (receivingPort.variable as Port).type.id = "char*" } - var receiveRef = generatePortRef(receivingPort, receivingBankIndex, receivingChannelIndex) + var receiveRef = JavaAstUtils.generatePortRef(receivingPort, receivingBankIndex, receivingChannelIndex) val result = new StringBuilder() // Transfer the physical time of arrival from the action to the port @@ -4211,8 +4212,8 @@ class CGenerator extends GeneratorBase { Delay delay, SupportedSerializers serializer ) { - var sendRef = generatePortRef(sendingPort, sendingBankIndex, sendingChannelIndex); - val receiveRef = generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. + var sendRef = JavaAstUtils.generatePortRef(sendingPort, sendingBankIndex, sendingChannelIndex); + val receiveRef = JavaAstUtils.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. val result = new StringBuilder() result.append(''' // Sending from «sendRef» in federate «sendingFed.name» to «receiveRef» in federate «receivingFed.name» @@ -4377,7 +4378,7 @@ class CGenerator extends GeneratorBase { ) { // Store the code val result = new StringBuilder(); - var sendRef = generatePortRef(port, sendingBankIndex, sendingChannelIndex); + var sendRef = JavaAstUtils.generatePortRef(port, sendingBankIndex, sendingChannelIndex); // Get the delay literal var String additionalDelayString = diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 4e8f0ca6a7..3e13c88523 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -38,6 +38,7 @@ import org.eclipse.xtext.util.CancelIndicator import org.lflang.ErrorReporter import org.lflang.FileConfig import org.lflang.InferredType +import org.lflang.JavaAstUtils import org.lflang.Target import org.lflang.TargetConfig import org.lflang.TargetProperty.CoordinationType @@ -1337,7 +1338,7 @@ class PythonGenerator extends CGenerator { * @param port The port to read from */ override generateDelayBody(Action action, VarRef port) { - val ref = generateVarRef(port); + val ref = JavaAstUtils.generateVarRef(port); // Note that the action.type set by the base class is actually // the port type. if (action.inferredType.isTokenType) { @@ -1377,7 +1378,7 @@ class PythonGenerator extends CGenerator { * @param port The port to write to. */ override generateForwardBody(Action action, VarRef port) { - val outputName = generateVarRef(port) + val outputName = JavaAstUtils.generateVarRef(port) if (action.inferredType.isTokenType) { super.generateForwardBody(action, port) } else { diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 772fa20353..ce4d0933f9 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -494,11 +494,11 @@ class TSGenerator( // Virtual methods. override fun generateDelayBody(action: Action, port: VarRef): String { - return "actions.${action.name}.schedule(0, ${generateVarRef(port)} as ${getActionType(action)});" + return "actions.${action.name}.schedule(0, ${JavaAstUtils.generateVarRef(port)} as ${getActionType(action)});" } override fun generateForwardBody(action: Action, port: VarRef): String { - return "${generateVarRef(port)} = ${action.name} as ${getActionType(action)};" + return "${JavaAstUtils.generateVarRef(port)} = ${action.name} as ${getActionType(action)};" } override fun generateDelayGeneric(): String { diff --git a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt index 1e73e390f9..add28f96d2 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSReactionGenerator.kt @@ -1,6 +1,7 @@ package org.lflang.generator.ts import org.lflang.ErrorReporter +import org.lflang.JavaAstUtils import org.lflang.federated.FederateInstance import org.lflang.generator.PrependOperator import org.lflang.lf.* @@ -30,7 +31,7 @@ class TSReactionGenerator( private fun StateVar.getTargetType(): String = tsGenerator.getTargetTypeW(this) private fun Type.getTargetType(): String = tsGenerator.getTargetTypeW(this) - private fun VarRef.generateVarRef(): String = tsGenerator.generateVarRef(this) + private fun VarRef.generateVarRef(): String = JavaAstUtils.generateVarRef(this) /** * Return a TS type for the specified action. From 3b5964a652e7be5ecd742d528c025a822da48391 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 22 Nov 2021 14:01:22 -0800 Subject: [PATCH 016/221] More factoring out of GeneratorBase. --- org.lflang/src/org/lflang/JavaAstUtils.java | 72 +++++++++++- .../org/lflang/generator/GeneratorBase.xtend | 109 +++--------------- .../org/lflang/generator/c/CGenerator.xtend | 34 +++--- .../org/lflang/generator/cpp/CppExtensions.kt | 1 - .../generator/python/PythonGenerator.xtend | 2 +- .../org/lflang/generator/ts/TSGenerator.kt | 16 +-- 6 files changed, 105 insertions(+), 129 deletions(-) diff --git a/org.lflang/src/org/lflang/JavaAstUtils.java b/org.lflang/src/org/lflang/JavaAstUtils.java index 96ff398494..25d313055a 100644 --- a/org.lflang/src/org/lflang/JavaAstUtils.java +++ b/org.lflang/src/org/lflang/JavaAstUtils.java @@ -29,9 +29,12 @@ import java.util.regex.Pattern; import org.lflang.lf.Action; +import org.lflang.lf.Delay; import org.lflang.lf.Parameter; import org.lflang.lf.Port; import org.lflang.lf.StateVar; +import org.lflang.lf.Time; +import org.lflang.lf.TimeUnit; import org.lflang.lf.Type; import org.lflang.lf.Value; import org.lflang.lf.VarRef; @@ -184,7 +187,7 @@ public static String generateVarRef(VarRef reference) { * Generate code for referencing a port possibly indexed by * a bank index and/or a multiport index. This assumes the target language uses * the usual array indexing [n] for both cases. If the provided reference is - * not a port, then this returns the string "ERROR: not a port.". + * not a port, then this returns the string "ERROR: not a port." * @param reference The reference to the port. * @param bankIndex A bank index or null or negative if not in a bank. * @param multiportIndex A multiport index or null or negative if not in a multiport. @@ -209,4 +212,71 @@ public static String generatePortRef(VarRef reference, Integer bankIndex, Intege } return prefix + reference.getVariable().getName() + multiport; } + + /** + * Given a representation of time that may include units, return + * a string that the target language can recognize as a value. + * If units are given, e.g. "msec", then we convert the units to upper + * case and return an expression of the form "MSEC(value)". + * @param time A TimeValue that represents a time. + * @return A string, such as "MSEC(100)" for 100 milliseconds. + */ + public static String getTargetTime(TimeValue time) { + // The following apply to other methods in this section. + // FIXME: This is only used in a few code generators. Does the code reuse achieved justify the + // coupling that results from having this common dependency, in comparison to having separate + // implementations sequestered in the appropriate packages? + // FIXME: In Kotlin, this would be done more concisely in the style of AstExtensions.kt. + if (time != null) { + if (time.unit != TimeUnit.NONE) { + return time.unit.name() + '(' + time.time + ')'; + } else { + return String.valueOf(time.time); + } + } + return "0"; // FIXME: do this or throw exception? + } + + /** + * Return the time specified by {@code t}, expressed as + * code that is valid for some target languages. + */ + public static String getTargetTime(Time t) { + return getTargetTime(new TimeValue(t.getInterval(), t.getUnit())); + } + + /** + * Return the time specified by {@code d}, expressed as + * code that is valid for some target languages. + */ + public static String getTargetTime(Delay d) { + return d.getParameter() != null ? ASTUtils.toText(d) : getTargetTime( + ASTUtils.getInitialTimeValue(d.getParameter()) // The time is given as a parameter reference. + ); + } + + /** + * Return the time specified by {@code v}, expressed as + * code that is valid for some target languages. + */ + public static String getTargetTime(Value v) { + if (v.getTime() != null) return getTargetTime(v.getTime()); + if (ASTUtils.isZero(v)) return getTargetTime(new TimeValue(0, TimeUnit.NONE)); + return ASTUtils.toText(v); + } + + /** + * Get textual representation of a value in the target language. + * + * If the value evaluates to 0, it is interpreted as a normal value. + * + * @param v A time AST node + * @return A time string in the target language + */ + public static String getTargetValue(Value v) { + // FIXME: This is practically the same as getTargetTime, and it is almost always used for times + // (at least in the TypeScript generator). Perhaps it can be eliminated. + if (v.getTime() != null) return getTargetTime(v.getTime()); + return ASTUtils.toText(v); + } } diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 892e23ec46..f265a928fd 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -47,6 +47,7 @@ import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGeneratorContext import org.eclipse.xtext.util.CancelIndicator import org.lflang.ASTUtils +import org.lflang.JavaAstUtils import org.lflang.ErrorReporter import org.lflang.FileConfig import org.lflang.InferredType @@ -70,9 +71,7 @@ import org.lflang.lf.Port import org.lflang.lf.Reaction import org.lflang.lf.Reactor import org.lflang.lf.StateVar -import org.lflang.lf.Time import org.lflang.lf.TimeUnit -import org.lflang.lf.Value import org.lflang.lf.VarRef import org.lflang.lf.Variable @@ -398,7 +397,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { /** * For each involved resource, replace connections with delays with generated delay reactors. */ - protected def transformDelays() { + private def transformDelays() { for (r : this.resources) { r.eResource.insertGeneratedDelays(this) } @@ -533,28 +532,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { if (reactionBankIndices.get(reaction) === null) return -1 return reactionBankIndices.get(reaction) } - - /** - * Given a representation of time that may possibly include units, return - * a string that the target language can recognize as a value. In this base - * class, if units are given, e.g. "msec", then we convert the units to upper - * case and return an expression of the form "MSEC(value)". Particular target - * generators will need to either define functions or macros for each possible - * time unit or override this method to return something acceptable to the - * target language. - * @param time A TimeValue that represents a time. - * @return A string, such as "MSEC(100)" for 100 milliseconds. - */ - def String timeInTargetLanguage(TimeValue time) { - if (time !== null) { - if (time.unit != TimeUnit.NONE) { - return time.unit.name() + '(' + time.time + ')' - } else { - return time.time.toString() - } - } - return "0" // FIXME: do this or throw exception? - } /** * Run the custom build command specified with the "build" parameter. @@ -974,9 +951,9 @@ abstract class GeneratorBase extends JavaGeneratorBase { for (i : param?.init) { if (param.isOfTimeType) { - list.add(i.targetTime) + list.add(JavaAstUtils.getTargetTime(i)) } else { - list.add(i.targetValue) + list.add(JavaAstUtils.getTargetValue(i)) } } return list @@ -999,9 +976,9 @@ abstract class GeneratorBase extends JavaGeneratorBase { if (i.parameter !== null) { list.add(i.parameter.targetReference) } else if (state.isOfTimeType) { - list.add(i.targetTime) + list.add(JavaAstUtils.getTargetTime(i)) } else { - list.add(i.targetValue) + list.add(JavaAstUtils.getTargetValue(i)) } } return list @@ -1032,10 +1009,15 @@ abstract class GeneratorBase extends JavaGeneratorBase { // the parameter was overwritten in the instantiation var list = new ArrayList(); for (init : assignments.get(0)?.rhs) { + // FIXME: This pattern of checking if it is a time and then calling + // the appropriate function is used repetitively. Factor out? + // It only seems to be necessary because of the special case where the value + // is zero, with no units. Otherwise, values would know whether or not they + // are times -- we would not need to ask their parent nodes. if (param.isOfTimeType) { - list.add(init.targetTime) + list.add(JavaAstUtils.getTargetTime(init)) } else { - list.add(init.targetValue) + list.add(JavaAstUtils.getTargetValue(init)) } } return list @@ -1110,13 +1092,10 @@ abstract class GeneratorBase extends JavaGeneratorBase { // ////////////////////////////////////////////////// // // Private functions /** - * Get textual representation of a time in the target language. - * This is a separate function from - * getTargetTime to avoid producing invalid RTI - * code for targets that override timeInTargetLanguage - * to return a C-incompatible time type. - * - * @param v A time AST node + * Get textual representation of a time in the target language + * in an RTI-compatible form. + * + * @param d A time AST node * @return An RTI-compatible (ie. C target) time string */ protected def getRTITime(Delay d) { @@ -1134,8 +1113,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - - /** * Remove triggers in each federates' network reactions that are defined in remote federates. * @@ -1449,58 +1426,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { return p.inferredType.targetType } - /** - * Get textual representation of a time in the target language. - * - * @param t A time AST node - * @return A time string in the target language - */ - protected def getTargetTime(Time t) { - val value = new TimeValue(t.interval, t.unit) - return value.timeInTargetLanguage - } - - /** - * Get textual representation of a value in the target language. - * - * If the value evaluates to 0, it is interpreted as a normal value. - * - * @param v A time AST node - * @return A time string in the target language - */ - protected def getTargetValue(Value v) { - if (v.time !== null) { - return v.time.targetTime - } - return v.toText - } - - /** - * Get textual representation of a value in the target language. - * - * If the value evaluates to 0, it is interpreted as a time. - * - * @param v A time AST node - * @return A time string in the target language - */ - protected def getTargetTime(Value v) { - if (v.time !== null) { - return v.time.targetTime - } else if (v.isZero) { - val value = new TimeValue(0, TimeUnit.NONE) - return value.timeInTargetLanguage - } - return v.toText - } - - protected def getTargetTime(Delay d) { - if (d.parameter !== null) { - return d.toText - } else { - return new TimeValue(d.interval, d.unit).timeInTargetLanguage - } - } - /** * Write the source code to file. * @param code The code to be written. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index aab73e7b57..2fda037f79 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1371,7 +1371,7 @@ class CGenerator extends GeneratorBase { // Insert the #defines at the beginning code.insert(0, ''' #define _LF_CLOCK_SYNC_INITIAL - #define _LF_CLOCK_SYNC_PERIOD_NS «targetConfig.clockSyncOptions.period.timeInTargetLanguage» + #define _LF_CLOCK_SYNC_PERIOD_NS «JavaAstUtils.getTargetTime(targetConfig.clockSyncOptions.period)» #define _LF_CLOCK_SYNC_EXCHANGES_PER_INTERVAL «targetConfig.clockSyncOptions.trials» #define _LF_CLOCK_SYNC_ATTENUATION «targetConfig.clockSyncOptions.attenuation» ''') @@ -1423,7 +1423,7 @@ class CGenerator extends GeneratorBase { val stp = param.init.get(0).getTimeValue if (stp !== null) { pr(''' - set_stp_offset(«stp.timeInTargetLanguage»); + set_stp_offset(«JavaAstUtils.getTargetTime(stp)»); ''') } } @@ -1594,11 +1594,7 @@ class CGenerator extends GeneratorBase { candidate_tmp = NEVER; ''') } else { - var delayTime = delay.getTargetTime - if (delay.parameter !== null) { - // The delay is given as a parameter reference. Find its value. - delayTime = ASTUtils.getInitialTimeValue(delay.parameter).timeInTargetLanguage - } + var delayTime = JavaAstUtils.getTargetTime(delay) pr(rtiCode, ''' if («delayTime» < candidate_tmp) { candidate_tmp = «delayTime»; @@ -3319,9 +3315,9 @@ class CGenerator extends GeneratorBase { var minDelay = action.minDelay var minSpacing = action.minSpacing pr(initializeTriggerObjects, ''' - «triggerStructName».offset = «timeInTargetLanguage(minDelay)»; + «triggerStructName».offset = «JavaAstUtils.getTargetTime(minDelay)»; «IF minSpacing !== null» - «triggerStructName».period = «timeInTargetLanguage(minSpacing)»; + «triggerStructName».period = «JavaAstUtils.getTargetTime(minSpacing)»; «ELSE» «triggerStructName».period = «CGenerator.UNDEFINED_MIN_SPACING»; «ENDIF» @@ -3344,8 +3340,8 @@ class CGenerator extends GeneratorBase { for (timer : timers) { if (!timer.isStartup) { var triggerStructName = triggerStructName(timer) - val offset = timeInTargetLanguage(timer.offset) - val period = timeInTargetLanguage(timer.period) + val offset = JavaAstUtils.getTargetTime(timer.offset) + val period = JavaAstUtils.getTargetTime(timer.period) pr(initializeTriggerObjects, ''' «triggerStructName».offset = «offset»; «triggerStructName».period = «period»; @@ -3720,7 +3716,7 @@ class CGenerator extends GeneratorBase { parameter cooridiation-options with a value like {advance-message-interval: 10 msec}"''') } pr(initializeTriggerObjects, ''' - _fed.min_delay_from_physical_action_to_federate_output = «minDelay.timeInTargetLanguage»; + _fed.min_delay_from_physical_action_to_federate_output = «JavaAstUtils.getTargetTime(minDelay)»; ''') } } @@ -3866,7 +3862,7 @@ class CGenerator extends GeneratorBase { var deadline = reaction.declaredDeadline.maxDelay val reactionStructName = '''«CUtil.selfRef(reaction.parent)»->_lf__reaction_«reaction.index»''' pr(initializeTriggerObjects, ''' - «reactionStructName».deadline = «timeInTargetLanguage(deadline)»; + «reactionStructName».deadline = «JavaAstUtils.getTargetTime(deadline)»; ''') } } @@ -3971,9 +3967,9 @@ class CGenerator extends GeneratorBase { if (i.parameter !== null) { list.add(CUtil.selfRef(parent) + "->" + i.parameter.name) } else if (state.isOfTimeType) { - list.add(i.targetTime) + list.add(JavaAstUtils.getTargetTime(i)) } else { - list.add(i.targetValue) + list.add(JavaAstUtils.getTargetValue(i)) } } @@ -4345,7 +4341,7 @@ class CGenerator extends GeneratorBase { // Find the maximum STP for decentralized coordination if(isFederatedAndDecentralized) { result.append(''' - max_STP = «maxSTP.timeInTargetLanguage»; + max_STP = «JavaAstUtils.getTargetTime(maxSTP)»; ''') } @@ -4498,7 +4494,7 @@ class CGenerator extends GeneratorBase { pr('#define TARGET_FILES_DIRECTORY "' + fileConfig.srcGenPath + '"'); if (targetConfig.coordinationOptions.advance_message_interval !== null) { - pr('#define ADVANCE_MESSAGE_INTERVAL ' + targetConfig.coordinationOptions.advance_message_interval.timeInTargetLanguage) + pr('#define ADVANCE_MESSAGE_INTERVAL ' + JavaAstUtils.getTargetTime(targetConfig.coordinationOptions.advance_message_interval)) } includeTargetLanguageSourceFiles() @@ -5264,9 +5260,9 @@ class CGenerator extends GeneratorBase { protected def String getInitializer(ParameterInstance p) { if (p.type.isList && p.init.size > 1) { - return p.init.join('{', ', ', '}', [it.targetValue]) + return p.init.join('{', ', ', '}', [JavaAstUtils.getTargetValue(it)]) } else { - return p.init.get(0).targetValue + return JavaAstUtils.getTargetValue(p.init.get(0)) } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt b/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt index c0761419bb..3a991036bc 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppExtensions.kt @@ -66,7 +66,6 @@ fun Value.toTime(outerContext: Boolean = false): String = * Get textual representation of a value in C++ code * * If the value evaluates to 0, it is interpreted as a normal value. - * FIXME this is redundant to GeneratorBase.getTargetValue */ fun Value.toCppCode(): String = CppTypes.getTargetExpr(this, null) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 3e13c88523..99f4e8f9f1 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -231,7 +231,7 @@ class PythonGenerator extends CGenerator { switch(v.toText) { case "false": returnValue = "False" case "true": returnValue = "True" - default: returnValue = super.getTargetValue(v) + default: returnValue = JavaAstUtils.getTargetValue(v) } // Parameters in Python are always prepended with a 'self.' diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index ce4d0933f9..ca482debba 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -86,7 +86,7 @@ class TSGenerator( // Wrappers to expose GeneratorBase methods. fun federationRTIPropertiesW() = federationRTIProperties - fun getTargetValueW(v: Value): String = getTargetValue(v) + fun getTargetValueW(v: Value): String = JavaAstUtils.getTargetValue(v) fun getTargetTypeW(p: Parameter): String = getTargetType(p.inferredType) fun getTargetTypeW(state: StateVar): String = getTargetType(state) fun getTargetTypeW(t: Type): String = getTargetType(t) @@ -348,20 +348,6 @@ class TSGenerator( } } - /** Given a representation of time that may possibly include units, - * return a string that TypeScript can recognize as a value. - * @param value Literal that represents a time value. - * @return A string, as "[ timeLiteral, TimeUnit.unit]" . - */ - override fun timeInTargetLanguage(value: TimeValue): String { - return if (value.unit != TimeUnit.NONE) { - "TimeValue.${value.unit}(${value.time})" - } else { - // The value must be zero. - "TimeValue.zero()" - } - } - override fun getTargetType(s: StateVar): String { val type = super.getTargetType(s) return if (!isInitialized(s)) { From b37898b47df42eccd1fef5efc2ec73e8383ba26f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 22 Nov 2021 14:14:06 -0800 Subject: [PATCH 017/221] Shorten oddly written methods. --- .../src/org/lflang/generator/GeneratorBase.xtend | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index f265a928fd..02e0fcc978 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -708,11 +708,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { * coordination mechanism. */ def isFederatedAndDecentralized() { - if (isFederated && - targetConfig.coordination === CoordinationType.DECENTRALIZED) { - return true - } - return false + return isFederated && targetConfig.coordination === CoordinationType.DECENTRALIZED } /** @@ -720,11 +716,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { * coordination mechanism. */ def isFederatedAndCentralized() { - if (isFederated && - targetConfig.coordination === CoordinationType.CENTRALIZED) { - return true - } - return false + return isFederated && targetConfig.coordination === CoordinationType.CENTRALIZED } /** From 02ca78e8aca445ca9be3fc469ef99777710804bc Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 22 Nov 2021 08:28:31 -0800 Subject: [PATCH 018/221] A bit more iterating over bank members. --- .../org/lflang/generator/c/CGenerator.xtend | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 2fda037f79..aaa9213ee1 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5758,16 +5758,11 @@ class CGenerator extends GeneratorBase { } /** - * For the specified reaction, for output ports that it writes to, - * set up the arrays that store the output values (if necessary) and - * that are used to trigger downstream reactions if an output is actually - * produced. - * - * NOTE: This method is quite complicated because of the possibility that - * that the reaction is writing to a multiport output or to an - * input port of a contained reactor, and the possibility that that - * the contained reactor is a bank of reactors and that its input port may - * be a multiport. + * For the specified reaction, for ports that it writes to, + * set up the arrays that store the results (if necessary) and + * that are used to trigger downstream reactions if an effect is actually + * produced. The port may be an output of the reaction's parent + * or an input to a reactor contained by the parent. * * @param The reaction instance. */ @@ -5785,20 +5780,24 @@ class CGenerator extends GeneratorBase { for (effect : reaction.effects) { if (effect instanceof PortInstance) { - // Effect is a port. There are four cases. - // 1. The port is an ordinary port contained by the same reactor that contains this reaction. - // 2. The port is a multiport contained by the same reactor that contains reaction. - // 3. The port is an ordinary input port contained by a contained reactor. - // 4. The port is a multiport input contained by a contained reactor. + // Effect is a port. // Create the entry in the output_produced array for this port. // If the port is a multiport, then we need to create an entry for each // individual channel. + + // If the port is an input of a contained reactor, then, if that + // contained reactor is a bank, we will have to iterate over bank + // members. + if (effect.isInput) { + startScopedBlock(initialization, effect.parent); + } + if (effect.isMultiport()) { // Point the output_produced field to where the is_present field of the port is. pr(initialization, ''' for (int i = 0; i < «effect.width»; i++) { «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«CUtil.sourceRef(effect)»[i].is_present; + = &«CUtil.sourceRef(effect)»[i]->is_present; } ''') outputCount += effect.getWidth(); @@ -5810,6 +5809,9 @@ class CGenerator extends GeneratorBase { ''') outputCount++ } + if (effect.isInput) { + endScopedBlock(initialization); + } } } pr(''' From 9efc38dd10c23b0cc51fb558ab9b09a44ec13fee Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 22 Nov 2021 14:31:26 -0800 Subject: [PATCH 019/221] Reversed order of startScopedBlock and getSelfStruct. --- .../org/lflang/generator/c/CGenerator.xtend | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index aaa9213ee1..9a74632c4e 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3096,8 +3096,8 @@ class CGenerator extends GeneratorBase { var foundOne = false; val temp = new StringBuilder(); var nameOfSelfStruct = CUtil.selfRef(child) - startScopedBlock(temp, child); getSelfStruct(temp, child); + startScopedBlock(temp, child); for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { @@ -3135,8 +3135,8 @@ class CGenerator extends GeneratorBase { var foundOne = false; val temp = new StringBuilder(); var containerSelfStructName = CUtil.selfRef(instance) - startScopedBlock(temp, instance); getSelfStruct(temp, instance); + startScopedBlock(temp, instance); // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an @@ -3254,8 +3254,8 @@ class CGenerator extends GeneratorBase { // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { - startScopedBlock(startTimeStep, child); getSelfStruct(startTimeStep, child); + startScopedBlock(startTimeStep, child); for (output : child.outputs) { if (output.isMultiport()) { @@ -3988,8 +3988,8 @@ class CGenerator extends GeneratorBase { def void setReactionPriorities(ReactorInstance reactor, FederateInstance federate) { val temp = new StringBuilder(); var foundOne = false; - startScopedBlock(temp, reactor); getSelfStruct(temp, reactor); + startScopedBlock(temp, reactor); for (r : reactor.reactions) { if (federate === null || federate.contains( @@ -4674,19 +4674,26 @@ class CGenerator extends GeneratorBase { /** * For the specified reactor, print code to the specified builder - * that retrieves the reactor instance using the current context - * variables to determine which instance of a bank is needed. - * A pointer to the self struct will be stored in a variable name - * given by CUtil.selfRef(reactor). + * that defines a pointer to the self struct of the specified + * reactor. If the specified reactor is a bank, then the pointer + * is to the array of pointers to the self structs for that bank * @param builder The string builder into which to write. * @param reactor The reactor instance. */ private def void getSelfStruct(StringBuilder builder, ReactorInstance reactor) { - var nameOfSelfStruct = CUtil.selfRef(reactor) + var nameOfSelfStruct = reactor.uniqueID() + "_self"; var structType = CUtil.selfType(reactor) - pr(builder, ''' - «structType»* «nameOfSelfStruct» = («structType»*)selfStructs[«CUtil.indexExpression(reactor)»]; - ''') + if (reactor.isBank) { + pr(builder, ''' + «structType»** «nameOfSelfStruct» = &(«structType»*)selfStructs[ + «CUtil.indexExpression(reactor.parent)» + «reactor.getIndexOffset»]; + ''') + } else { + pr(builder, ''' + «structType»* «nameOfSelfStruct» = («structType»*)selfStructs[ + «CUtil.indexExpression(reactor)»]; + ''') + } } /** @@ -5292,8 +5299,8 @@ class CGenerator extends GeneratorBase { } pr('''// deferredInitialize for «reactor.getFullName()»''') - startScopedBlock(code, reactor); getSelfStruct(code, reactor); + startScopedBlock(code, reactor); // Initialize the num_destinations fields of port structs on the self struct. deferredOutputNumDestinations(reactor); // NOTE: Not done for top level. @@ -5793,11 +5800,15 @@ class CGenerator extends GeneratorBase { } if (effect.isMultiport()) { + // Form is slightly different for inputs vs. outputs. + var connector = "."; + if (effect.isInput) connector = "->"; + // Point the output_produced field to where the is_present field of the port is. pr(initialization, ''' for (int i = 0; i < «effect.width»; i++) { «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«CUtil.sourceRef(effect)»[i]->is_present; + = &«CUtil.sourceRef(effect)»[i]«connector»is_present; } ''') outputCount += effect.getWidth(); From b995189bef0dce23a6f2b3c79925b83f16ade1ee Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 22 Nov 2021 14:44:31 -0800 Subject: [PATCH 020/221] Merged --- .../org/lflang/generator/c/CGenerator.xtend | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 9a74632c4e..a52833e733 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -94,7 +94,7 @@ import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* /** - * Generator for C target. This class generates C code definining each reactor + * Generator for C target. This class generates C code defining each reactor * class given in the input .lf file and imported .lf files. The generated code * has the following components: * @@ -469,7 +469,7 @@ class CGenerator extends GeneratorBase { if (!isOSCompatible()) return; // Incompatible OS and configuration - // Check for duplicate declerations. + // Check for duplicate declarations. val names = newLinkedHashSet for (r : reactors) { // Get the declarations for reactors that are instantiated somewhere. @@ -2019,7 +2019,7 @@ class CGenerator extends GeneratorBase { // An array of pointers to the individual ports. Useful // for the SET macros to work out-of-the-box for // multiports in the body of reactions because their - // value can be accessed via a -> operater (e.g.,foo[i]->value). + // 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, decl)»** _lf_«output.name»_pointers; @@ -3057,7 +3057,7 @@ class CGenerator extends GeneratorBase { // Search for instances of the parent within the tail of the breadcrumbs list. val container = nestedBreadcrumbs.first.reactorClass.toDefinition for (instantiation: container.instantiations) { - // Put this new instantation at the head of the list. + // Put this new instantiation at the head of the list. nestedBreadcrumbs.add(0, instantiation) if (instantiation.reactorClass.toDefinition == parent) { // Found a matching instantiation of the parent. @@ -3713,7 +3713,7 @@ class CGenerator extends GeneratorBase { With centralized coordination, this can result in a large number of messages to the RTI. Consider refactoring the code so that the output does not depend on the physical action, or consider using decentralized coordination. To silence this warning, set the target - parameter cooridiation-options with a value like {advance-message-interval: 10 msec}"''') + parameter coordination-options with a value like {advance-message-interval: 10 msec}"''') } pr(initializeTriggerObjects, ''' _fed.min_delay_from_physical_action_to_federate_output = «JavaAstUtils.getTargetTime(minDelay)»; @@ -3932,7 +3932,7 @@ class CGenerator extends GeneratorBase { if (reactorInstance !== null) { if (contained !== null) { // Caution: If port belongs to a contained reactor, the self struct needs to be that - // of the contained reactor instance, not this containe + // of the contained reactor instance, not this container selfStruct = CUtil.selfRef(reactorInstance.getChildReactorInstance(contained)) } else { selfStruct =CUtil.selfRef(reactorInstance); @@ -4142,7 +4142,7 @@ class CGenerator extends GeneratorBase { } } case SupportedSerializers.PROTO: { - throw new UnsupportedOperationException("Protbuf serialization is not supported yet."); + throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); } case SupportedSerializers.ROS2: { val portType = (receivingPort.variable as Port).inferredType @@ -4262,7 +4262,7 @@ class CGenerator extends GeneratorBase { // Handle native types. if (isTokenType(type)) { // NOTE: Transporting token types this way is likely to only work if the sender and receiver - // both have the same endianess. Otherwise, you have to use protobufs or some other serialization scheme. + // both have the same endianness. Otherwise, you have to use protobufs or some other serialization scheme. result.append(''' size_t message_length = «sendRef»->token->length * «sendRef»->token->element_size; «sendingFunction»(«commonArgs», (unsigned char*) «sendRef»->value); @@ -4287,7 +4287,7 @@ class CGenerator extends GeneratorBase { } } case SupportedSerializers.PROTO: { - throw new UnsupportedOperationException("Protbuf serialization is not supported yet."); + throw new UnsupportedOperationException("Protobuf serialization is not supported yet."); } case SupportedSerializers.ROS2: { var variableToSerialize = sendRef; @@ -5011,7 +5011,7 @@ class CGenerator extends GeneratorBase { * from the "self" struct. * @param builder The string builder. * @param effect The effect declared by the reaction. This must refer to an output. - * @param decl The reactor containing the rection or the import statement. + * @param decl The reactor containing the reaction or the import statement. */ private def generateOutputVariablesInReaction( StringBuilder builder, @@ -5282,14 +5282,17 @@ class CGenerator extends GeneratorBase { throw new UnsupportedOperationException("TODO: auto-generated method stub") } - ////////////////////////////////////////////////////////////// - // Private methods that generate code to go at the end - // of _lf_initialize_trigger_objects(). - - /** - * Perform deferred initializations in initialize_trigger_objects. - * @param reactor for which to generate deferred initialization. - * @param reactions The reactions of the reactor to initialize (normally all of them). + /** + * Data structure that for each instantiation of a contained + * reactor. This provides a set of input and output ports that trigger + * reactions of the container, are read by a reaction of the + * container, or that receive data from a reaction of the container. + * For each port, this provides a list of reaction indices that + * are triggered by the port, or an empty list if there are no + * reactions triggered by the port. + * @param reactor The container. + * @param federate The federate (used to determine whether a + * reaction belongs to the federate). */ private def void deferredInitialize( ReactorInstance reactor, Iterable reactions From cbb02b3541ea81673bc09bf229e03efe8673892e Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 22 Nov 2021 14:52:56 -0800 Subject: [PATCH 021/221] Applied Peter's fix for benchmark failures in scalability-banks --- .../org/lflang/generator/c/CGenerator.xtend | 55 ++++++++----------- 1 file changed, 24 insertions(+), 31 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index a52833e733..09094ddaab 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5632,8 +5632,7 @@ class CGenerator extends GeneratorBase { } /** - * If any output port of the specified reactor is a multiport and is mentioned - * as an effect of a reaction in the reactor, then generate code to + * If any output port of the specified reactor is a multiport, then generate code to * allocate memory to store the data produced by those reactions. * The allocated memory is pointed to by a field called `_lf_portname`. * @param reactor A reactor instance. @@ -5641,36 +5640,30 @@ class CGenerator extends GeneratorBase { private def void deferredAllocationForEffectsOnOutputs(ReactorInstance reactor) { for (port : reactor.outputs) { if (port.isMultiport) { - for (reaction : reactor.reactions) { - for (effect : reaction.effects) { - if (effect === port) { - // Port is an effect of a parent's reaction. - // That is, the port belongs to the same reactor as the reaction. - // The reaction is writing to an output of its container reactor. - val nameOfSelfStruct = CUtil.selfRef(port.parent); - val portStructType = variableStructType( - port.definition, - port.parent.definition.reactorClass - ) - - pr(''' - «nameOfSelfStruct»->_lf_«port.name»_width = «port.width»; - // Allocate memory to store output of reaction. - «nameOfSelfStruct»->_lf_«port.name» = («portStructType»*)calloc(«nameOfSelfStruct»->_lf_«port.name»_width, - sizeof(«portStructType»)); - «nameOfSelfStruct»->_lf_«port.name»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«port.name»_width); - // Assign each output port pointer to be used in reactions to facilitate user access to output ports - for(int i=0; i < «nameOfSelfStruct»->_lf_«port.name»_width; i++) { - «nameOfSelfStruct»->_lf_«port.name»_pointers[i] = &(«nameOfSelfStruct»->_lf_«port.name»[i]); - } - ''') - // There may be more reactions writing to this port, - // but once we have done the allocation, no need to do anything for those. - return; - } + // Port is an effect of a parent's reaction. + // That is, the port belongs to the same reactor as the reaction. + // The reaction is writing to an output of its container reactor. + val nameOfSelfStruct = CUtil.selfRef(port.parent); + val portStructType = variableStructType( + port.definition, + port.parent.definition.reactorClass + ) + + pr(''' + «nameOfSelfStruct»->_lf_«port.name»_width = «port.width»; + // Allocate memory to store output of reaction. + «nameOfSelfStruct»->_lf_«port.name» = («portStructType»*)calloc(«nameOfSelfStruct»->_lf_«port.name»_width, + sizeof(«portStructType»)); + «nameOfSelfStruct»->_lf_«port.name»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) + * «nameOfSelfStruct»->_lf_«port.name»_width); + // Assign each output port pointer to be used in reactions to facilitate user access to output ports + for(int i=0; i < «nameOfSelfStruct»->_lf_«port.name»_width; i++) { + «nameOfSelfStruct»->_lf_«port.name»_pointers[i] = &(«nameOfSelfStruct»->_lf_«port.name»[i]); } - } + ''') + // There may be more reactions writing to this port, + // but once we have done the allocation, no need to do anything for those. + return; } } } From a1675e8d3a69deaf7c4633aec476e9e692a78e20 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 9 Nov 2021 09:06:59 -0600 Subject: [PATCH 022/221] Updated the PingPong implementations --- benchmark/Python/Savina/PingPong.lf | 6 +++--- benchmark/Python/Savina/PingPong.py | 14 ++++++++------ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/benchmark/Python/Savina/PingPong.lf b/benchmark/Python/Savina/PingPong.lf index 641b1332a4..81749da90e 100644 --- a/benchmark/Python/Savina/PingPong.lf +++ b/benchmark/Python/Savina/PingPong.lf @@ -49,7 +49,7 @@ reactor Ping(count(1000000)) { if self.pingsLeft > 0: serve.schedule(0) else: - stop() + request_stop() =} } reactor Pong(expected(1000000)) { @@ -69,8 +69,8 @@ reactor Pong(expected(1000000)) { } main reactor PingPong { - ping = new Ping(); - pong = new Pong(); + ping = new Ping(count = 100000); + pong = new Pong(expected = 100000); ping.send -> pong.receive; pong.send -> ping.receive; } diff --git a/benchmark/Python/Savina/PingPong.py b/benchmark/Python/Savina/PingPong.py index f7194440cd..b3e791bca7 100644 --- a/benchmark/Python/Savina/PingPong.py +++ b/benchmark/Python/Savina/PingPong.py @@ -4,11 +4,11 @@ import sys import copy -sys.setrecursionlimit(100000) +sys.setrecursionlimit(1000000) EXPECTED = 10000 class _Ping: - count = 1000000 + count = EXPECTED pingsLeft = count def __init__(self, **kwargs): self.__dict__.update(kwargs) @@ -17,18 +17,18 @@ def reaction_function_1(self): if self.pingsLeft > 0: pingpong_pong_lf.reaction_function_0(self.pingsLeft) else: - exit() + return return 0 class _Pong: - expected = 1000000 + expected = EXPECTED count = 0 def __init__(self, **kwargs): self.__dict__.update(kwargs) def reaction_function_0(self , receive): self.count += 1 if(self.count == self.expected): - exit() + return pingpong_ping_lf.reaction_function_1() return 0 def reaction_function_1(self ): @@ -46,7 +46,9 @@ def reaction_function_1(self ): def main(): pingpong_ping_lf.reaction_function_1() +import timeit # As is customary in Python programs, the main() function # should only be executed if the main module is active. if __name__=="__main__": - main() + t = timeit.timeit("main()", setup="from __main__ import main") + print(t) From 9c945531857158bb6c53957557658f8e7398b97e Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 9 Nov 2021 09:09:46 -0600 Subject: [PATCH 023/221] Changed the default number of pings in the LF benchmark to match the native Python's --- benchmark/Python/Savina/PingPong.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/benchmark/Python/Savina/PingPong.lf b/benchmark/Python/Savina/PingPong.lf index 81749da90e..3fd4fc24a1 100644 --- a/benchmark/Python/Savina/PingPong.lf +++ b/benchmark/Python/Savina/PingPong.lf @@ -69,8 +69,8 @@ reactor Pong(expected(1000000)) { } main reactor PingPong { - ping = new Ping(count = 100000); - pong = new Pong(expected = 100000); + ping = new Ping(count = 10000); + pong = new Pong(expected = 10000); ping.send -> pong.receive; pong.send -> ping.receive; } From 9c1c418d6bbb9a9eba7a7f367e72e09d2ccb2fa9 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 12 Nov 2021 10:29:50 -0600 Subject: [PATCH 024/221] Resurrected the benchmark runner reactor --- benchmark/C/Savina/src/BenchmarkRunner.lf | 204 ++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 benchmark/C/Savina/src/BenchmarkRunner.lf diff --git a/benchmark/C/Savina/src/BenchmarkRunner.lf b/benchmark/C/Savina/src/BenchmarkRunner.lf new file mode 100644 index 0000000000..61d1e10caf --- /dev/null +++ b/benchmark/C/Savina/src/BenchmarkRunner.lf @@ -0,0 +1,204 @@ +target C; + +/** + * Reactor that starts the kernel of a benchmark, measures its runtime and outputs + * the results for a given number of iterations. + * + * This reactor is instantiated by the main reactor of a benchmark and + * the startup reaction of this reactor is the starting point for that benchmark. + * The reactor runs a given number of iterations of the benchmark, measures + * the runtime of each iteration and outputs them. The benchmark itself is responsible + * to reset its state between the iterations. + * A benchmark can have an optional initialization phase that is run once before + * the first iteration and is not measured. + * A benchmark can have an optional cleanup phase after each iteration before + * the next iteration start which is not considered in the runtime measurement. + * + * How to use: + * - Instantiate this reactor in the main reactor of the benchmark. + * - Connect the ports inStart, outIterationStart, inIterationFinish with + * the appropriate reactors of the benchmark. + * - Optionally connect the ports for initialization and cleanup. + * - Create a startup reaction in the main reactor that calls printBenchmarkInfo(), + * + * Prototype startup reaction in the main reactor of a benchmark: + * runner = new BenchmarkRunner(num_iterations=num_iterations); + * reaction(startup) -> runner.inStart {= + * printBenchmarkInfo("ThreadRingReactorLFCppBenchmark"); + * printSystemInfo(); + * SET(runner.inStart, true); + * =} + * + * @param num_iterations How many times to execute the kernel of the benchmark to measure. + * @param use_init Benchmarks needs initialization and handles the corresponding signals. + * @param use_cleanup_iteration Benchmark needs cleanup after each iteration and handles the corresponding signals. + * + * @author Hannes Klein + * @author Shaokai Lin + */ +reactor BenchmarkRunner(num_iterations:int(12), use_init:bool(false), use_cleanup_iteration:bool(false)) { + + /** Signal to start execution. Set this input from a startup reaction in the main reactor. */ + input inStart:bool; + + /** Signals for starting and finishing the kernel and runtime measurement. */ + output outIterationStart:bool; + input inIterationFinish:bool; + + /** Signals for initializations that are not part of the measured kernel. */ + output outInitializeStart:bool; + input inInitializeFinish:bool; + + /** Signals for cleanup operations after each iteration of the kernel. */ + output outCleanupIterationStart:bool; + input inCleanupIterationFinish:bool; + + /** Events to switch between the phases of running the iterations. */ + logical action initBenchmark:bool; + logical action cleanupIteration:bool; + logical action nextIteration:bool; + logical action finish:bool; + + /** Number of iterations already executed. */ + state count:unsigned(0); + + /** Start time for runtime measurement. */ + state startTime:instant_t; + + /** Runtime measurements. */ + state measuredTimes:interval_t[]; + + + reaction(startup) {= + // Initialize an array of interval_t + self->measuredTimes = calloc(self->num_iterations, sizeof(interval_t)); + =} + + reaction(inStart) -> nextIteration, initBenchmark {= + if(self->use_init) { + schedule(initBenchmark, 0); + } else { + schedule(nextIteration, 0); + } + =} + + reaction(initBenchmark) -> outInitializeStart {= + SET(outInitializeStart, true); + =} + + reaction(inInitializeFinish) -> nextIteration {= + schedule(nextIteration, 0); + =} + + reaction(cleanupIteration) -> outCleanupIterationStart {= + SET(outCleanupIterationStart, true); + =} + + reaction(inCleanupIterationFinish) -> nextIteration {= + schedule(nextIteration, 0); + =} + + reaction(nextIteration) -> outIterationStart, finish {= + if (self->count < self->num_iterations) { + self->startTime = get_physical_time(); + SET(outIterationStart, true); + } else { + schedule(finish, 0); + } + =} + + reaction(inIterationFinish) -> nextIteration, cleanupIteration {= + interval_t end_time = get_physical_time(); + interval_t duration = end_time - self->startTime; + self->measuredTimes[self->count] = duration; + self->count += 1; + + printf("Iteration: %d\t Duration: %.3f msec\n", self->count, toMS(duration)); + + if(self->use_cleanup_iteration) { + schedule(cleanupIteration, 0); + } else { + schedule(nextIteration, 0); + } + =} + + reaction(finish) {= + double* measuredMSTimes = getMSMeasurements(self->measuredTimes, self->num_iterations); + qsort(measuredMSTimes, self->num_iterations, sizeof(double), comp); + + printf("Execution - Summary:\n"); + printf("Best Time:\t %.3f msec\n", measuredMSTimes[0]); + printf("Worst Time:\t %.3f msec\n", measuredMSTimes[self->num_iterations - 1]); + printf("Median Time:\t %.3f msec\n", median(measuredMSTimes, self->num_iterations)); + request_stop(); + =} + + preamble {= + + static double toMS(interval_t t) { + return t / 1000000.0; + } + + int comp (const void * elem1, const void * elem2) { + int f = *((double*)elem1); + int s = *((double*)elem2); + if (f > s) return 1; + if (f < s) return -1; + return 0; + } + + static double median(double* execTimes, int size) { + if (size == 0) { + return 0.0; + } + + int middle = size / 2; + if(size % 2 == 1) { + return execTimes[middle]; + } else { + return (execTimes[middle-1] + execTimes[middle]) / 2; + } + } + + static double* getMSMeasurements(interval_t* measured_times, int num_iterations) { + + double* msMeasurements = calloc(num_iterations, sizeof(double)); + for (int i = 0; i < num_iterations; i++) { + msMeasurements[i] = toMS(measured_times[i]); + } + + return msMeasurements; + } + =} + + preamble {= + + void printBenchmarkInfo(char* benchmarkId) { + printf("Benchmark: %s\n", benchmarkId); + } + + void printSystemInfo() { + + printf("System information\n"); + printf("O/S Name: "); + + #ifdef _WIN32 + printf("Windows 32-bit"); + #elif _WIN64 + printf("Windows 64-bit"); + #elif __APPLE__ || __MACH__ + printf("Mac OSX"); + #elif __linux__ + printf("Linux"); + #elif __FreeBSD__ + printf("FreeBSD"); + #elif __unix || __unix__ + printf("Unix"); + #else + printf("Other"); + #endif + + printf("\n"); + } + =} +} \ No newline at end of file From 61f02dcee5546543a0fab96e14cffea77576fc4d Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 12 Nov 2021 10:30:43 -0600 Subject: [PATCH 025/221] Updated RecMatMul to use the BenchmarkRunner reactor --- benchmark/C/Savina/src/parallelism/MatMul.lf | 37 ++++++++++++++++--- .../savina_parallelism_recmatmul.yaml | 3 ++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/benchmark/C/Savina/src/parallelism/MatMul.lf b/benchmark/C/Savina/src/parallelism/MatMul.lf index 02d9b91239..b4e7a9a2e9 100644 --- a/benchmark/C/Savina/src/parallelism/MatMul.lf +++ b/benchmark/C/Savina/src/parallelism/MatMul.lf @@ -16,9 +16,12 @@ target C { threads: 0, /// [[[end]]] cmake-include: "../lib/matrix.cmake", - files: ["../lib/matrix.c", "../include/matrix.h"] + files: ["../lib/matrix.c", "../include/matrix.h"], + build-type: RelWithDebInfo }; +import BenchmarkRunner from "../BenchmarkRunner.lf"; + preamble {= #include #include @@ -144,6 +147,10 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { input[num_workers] more_work: {= work_item_t* =}; + // Ports to interact with the benchmarkRunner reactor + input start: bool; + output finished: bool; + logical action next; logical action done; @@ -156,17 +163,20 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { state work_stack: work_stack_t; - reaction (startup) -> data, next {= + reaction (startup) {= // Fill both input arrays with data self->A = mat_new_d(self->data_length, self->data_length); self->B = mat_new_d(self->data_length, self->data_length); - self->C = mat_new_d(self->data_length, self->data_length); for (size_t i = 0; i < self->data_length; ++i) { for (size_t j = 0; j < self->data_length; ++j) { *mat_at_d(self->A, i, j) = i; *mat_at_d(self->B, i, j) = j; } } + =} + + reaction (start) -> data, next {= + self->C = mat_new_d(self->data_length, self->data_length); SET_NEW_ARRAY(data, 3); data->value[0] = self->A; data->value[1] = self->B; @@ -209,13 +219,16 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { } =} - reaction (done) {= + reaction (done) -> finished {= check_if_valid(self->C, self->data_length); work_stack_free(self->work_stack); + mat_destroy_d(self->C); + SET_PRESENT(finished); + =} + + reaction(shutdown) {= mat_destroy_d(self->A); mat_destroy_d(self->B); - mat_destroy_d(self->C); - request_stop(); =} } @@ -275,20 +288,32 @@ reactor Worker(threshold: size_t(16384), data_length: size_t(1024)) { main reactor ( /*[[[cog + cog.outl(f'num_iterations: size_t({num_iterations}),') cog.outl(f'data_length: size_t({data_length}),') cog.outl(f'block_threshold: int({block_threshold}),') cog.outl(f'priorities: int({priorities}),') cog.outl(f'num_workers: int({num_workers})') ]]] */ + num_iterations: size_t(12), data_length: size_t(1024), block_threshold: int(16384), priorities: int(10), num_workers: int(20) /// [[[end]]] ) { + runner = new BenchmarkRunner(num_iterations=num_iterations); manager = new Manager(num_workers=num_workers, data_length=data_length); workers = new[num_workers] Worker(threshold=block_threshold, data_length=data_length); + reaction(startup) -> runner.inStart {= + printBenchmarkInfo("ThreadRingReactorLFCppBenchmark"); + printSystemInfo(); + SET(runner.inStart, true); + =} + + runner.outIterationStart -> manager.start; + manager.finished -> runner.inIterationFinish; + (manager.data)+ -> workers.data; manager.do_work -> workers.do_work; workers.more_work -> manager.more_work; diff --git a/benchmark/runner/conf/benchmark/savina_parallelism_recmatmul.yaml b/benchmark/runner/conf/benchmark/savina_parallelism_recmatmul.yaml index 2bbd954af3..48ed2f274b 100644 --- a/benchmark/runner/conf/benchmark/savina_parallelism_recmatmul.yaml +++ b/benchmark/runner/conf/benchmark/savina_parallelism_recmatmul.yaml @@ -5,6 +5,7 @@ params: data_length: 1024 block_threshold: 16384 priorities: 10 + num_iterations: 12 # target specific configuration targets: @@ -38,12 +39,14 @@ targets: workers: ["--numWorkers", ""] lf-c: copy_sources: + - "${lf_path}/benchmark/C/Savina/src/BenchmarkRunner.lf" - "${lf_path}/benchmark/C/Savina/src/parallelism/" - "${lf_path}/benchmark/C/Savina/src/lib/" - "${lf_path}/benchmark/C/Savina/src/include/" lf_file: "parallelism/MatMul.lf" binary: "MatMul" gen_args: + num_iterations: ["-D", "num_iterations="] data_length: ["-D", "data_length="] block_threshold: ["-D", "block_threshold="] priorities: ["-D", "priorities="] From 9d58a46d422123e27d970545a4398bc86ff72a33 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 12 Nov 2021 11:11:05 -0600 Subject: [PATCH 026/221] Update benchmark/C/Savina/src/parallelism/MatMul.lf Co-authored-by: Peter Donovan <33707478+petervdonovan@users.noreply.github.com> --- benchmark/C/Savina/src/parallelism/MatMul.lf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark/C/Savina/src/parallelism/MatMul.lf b/benchmark/C/Savina/src/parallelism/MatMul.lf index b4e7a9a2e9..1be4f92fec 100644 --- a/benchmark/C/Savina/src/parallelism/MatMul.lf +++ b/benchmark/C/Savina/src/parallelism/MatMul.lf @@ -306,7 +306,7 @@ main reactor ( workers = new[num_workers] Worker(threshold=block_threshold, data_length=data_length); reaction(startup) -> runner.inStart {= - printBenchmarkInfo("ThreadRingReactorLFCppBenchmark"); + printBenchmarkInfo("RecMatMulBenchmark"); printSystemInfo(); SET(runner.inStart, true); =} From 61e5dda65e74bc3488d1f6c3802fe08a40c88b24 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 16 Nov 2021 01:07:15 -0800 Subject: [PATCH 027/221] Transpose B matrix for C, C++ versions. --- benchmark/C/Savina/src/parallelism/MatMul.lf | 12 +++++++++-- .../Cpp/Savina/src/parallelism/MatMul.lf | 21 +++++++++++++++---- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/benchmark/C/Savina/src/parallelism/MatMul.lf b/benchmark/C/Savina/src/parallelism/MatMul.lf index 1be4f92fec..9903aec4f9 100644 --- a/benchmark/C/Savina/src/parallelism/MatMul.lf +++ b/benchmark/C/Savina/src/parallelism/MatMul.lf @@ -125,6 +125,14 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { #include #include "matrix.h" + /* + * Accesses the given matrix using the assumption that it is the + * transpose of the matrix that we wish to represent. + */ + double* transposed_mat_at_d(matrix_t matrix, size_t i, size_t j) { + return mat_at_d(matrix, j, i); + } + /* * Prints a message if there exists an incorrect entry in the given matrix. */ @@ -170,7 +178,7 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { for (size_t i = 0; i < self->data_length; ++i) { for (size_t j = 0; j < self->data_length; ++j) { *mat_at_d(self->A, i, j) = i; - *mat_at_d(self->B, i, j) = j; + *transposed_mat_at_d(self->B, i, j) = j; } } =} @@ -277,7 +285,7 @@ reactor Worker(threshold: size_t(16384), data_length: size_t(1024)) { for (size_t k = 0; k < wi.dim; ++k) { *mat_at_d(self->C, i, j) += ( (*mat_at_d(self->A, i, wi.scA + k)) - * (*mat_at_d(self->B, wi.srB + k, j)) + * (*transposed_mat_at_d(self->B, wi.srB + k, j)) ); } } diff --git a/benchmark/Cpp/Savina/src/parallelism/MatMul.lf b/benchmark/Cpp/Savina/src/parallelism/MatMul.lf index 7f2addc46a..364cbaa164 100644 --- a/benchmark/Cpp/Savina/src/parallelism/MatMul.lf +++ b/benchmark/Cpp/Savina/src/parallelism/MatMul.lf @@ -43,6 +43,19 @@ public preamble {= #include #include #include "Matrix.hh" + + template + class TransposedMatrix { + private: + std::vector data; + size_t size_x; + + public: + TransposedMatrix(size_t size_x, size_t size_y) : data(size_x * size_y), size_x(size_x) {} + + const T& at(size_t x, size_t y) const { return data[y*size_x+x]; } + T& at(size_t x, size_t y) { return data[y*size_x+x]; } + }; struct WorkItem { size_t srA; // srA = start row in matrix A @@ -58,7 +71,7 @@ public preamble {= reactor Manager(numWorkers: size_t{20}, dataLength: size_t{1024}) { state A: Matrix(dataLength, dataLength); - state B: Matrix(dataLength, dataLength); + state B: TransposedMatrix(dataLength, dataLength); state C: Matrix(dataLength, dataLength); state workQueue: std::deque<{=reactor::ImmutableValuePtr=}>; @@ -69,7 +82,7 @@ reactor Manager(numWorkers: size_t{20}, dataLength: size_t{1024}) { input start: void; output finished: void; - output data: {=std::tuple*, const Matrix*, Matrix*>=}; + output data: {=std::tuple*, const TransposedMatrix*, Matrix*>=}; output[numWorkers] doWork: WorkItem; input[numWorkers] moreWork: {=std::array, 8>=}; @@ -149,10 +162,10 @@ reactor Manager(numWorkers: size_t{20}, dataLength: size_t{1024}) { reactor Worker(threshold: size_t{16384}) { state A: {=const Matrix*=}; - state B: {=const Matrix*=}; + state B: {=const TransposedMatrix*=}; state C: {=Matrix*=}; - input data: {=std::tuple*, const Matrix*, Matrix*>=}; + input data: {=std::tuple*, const TransposedMatrix*, Matrix*>=}; input doWork: WorkItem; output moreWork: {=std::array, 8>=}; From b94de8f2a10dba25417101b62c12eff958e1ee0f Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 16 Nov 2021 01:20:17 -0800 Subject: [PATCH 028/221] Add the original benchmark alongside this version. This is based on a suggestion from Christian, since the Python runner script does not parse the output of the modified benchmark correctly. The original benchmark has a couple of minor corrections that also appear in the one that uses the benchmark runner. --- benchmark/C/Savina/src/parallelism/MatMul.lf | 33 +- .../src/parallelism/MatMulBenchmarkRunner.lf | 328 ++++++++++++++++++ 2 files changed, 332 insertions(+), 29 deletions(-) create mode 100644 benchmark/C/Savina/src/parallelism/MatMulBenchmarkRunner.lf diff --git a/benchmark/C/Savina/src/parallelism/MatMul.lf b/benchmark/C/Savina/src/parallelism/MatMul.lf index 9903aec4f9..44c49e708d 100644 --- a/benchmark/C/Savina/src/parallelism/MatMul.lf +++ b/benchmark/C/Savina/src/parallelism/MatMul.lf @@ -20,8 +20,6 @@ target C { build-type: RelWithDebInfo }; -import BenchmarkRunner from "../BenchmarkRunner.lf"; - preamble {= #include #include @@ -155,10 +153,6 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { input[num_workers] more_work: {= work_item_t* =}; - // Ports to interact with the benchmarkRunner reactor - input start: bool; - output finished: bool; - logical action next; logical action done; @@ -171,20 +165,17 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { state work_stack: work_stack_t; - reaction (startup) {= + reaction (startup) -> data, next {= // Fill both input arrays with data self->A = mat_new_d(self->data_length, self->data_length); self->B = mat_new_d(self->data_length, self->data_length); + self->C = mat_new_d(self->data_length, self->data_length); for (size_t i = 0; i < self->data_length; ++i) { for (size_t j = 0; j < self->data_length; ++j) { *mat_at_d(self->A, i, j) = i; *transposed_mat_at_d(self->B, i, j) = j; } } - =} - - reaction (start) -> data, next {= - self->C = mat_new_d(self->data_length, self->data_length); SET_NEW_ARRAY(data, 3); data->value[0] = self->A; data->value[1] = self->B; @@ -227,16 +218,12 @@ reactor Manager(num_workers: int(20), data_length: size_t(1024)) { } =} - reaction (done) -> finished {= + reaction (done) {= check_if_valid(self->C, self->data_length); work_stack_free(self->work_stack); - mat_destroy_d(self->C); - SET_PRESENT(finished); - =} - - reaction(shutdown) {= mat_destroy_d(self->A); mat_destroy_d(self->B); + mat_destroy_d(self->C); =} } @@ -296,32 +283,20 @@ reactor Worker(threshold: size_t(16384), data_length: size_t(1024)) { main reactor ( /*[[[cog - cog.outl(f'num_iterations: size_t({num_iterations}),') cog.outl(f'data_length: size_t({data_length}),') cog.outl(f'block_threshold: int({block_threshold}),') cog.outl(f'priorities: int({priorities}),') cog.outl(f'num_workers: int({num_workers})') ]]] */ - num_iterations: size_t(12), data_length: size_t(1024), block_threshold: int(16384), priorities: int(10), num_workers: int(20) /// [[[end]]] ) { - runner = new BenchmarkRunner(num_iterations=num_iterations); manager = new Manager(num_workers=num_workers, data_length=data_length); workers = new[num_workers] Worker(threshold=block_threshold, data_length=data_length); - reaction(startup) -> runner.inStart {= - printBenchmarkInfo("RecMatMulBenchmark"); - printSystemInfo(); - SET(runner.inStart, true); - =} - - runner.outIterationStart -> manager.start; - manager.finished -> runner.inIterationFinish; - (manager.data)+ -> workers.data; manager.do_work -> workers.do_work; workers.more_work -> manager.more_work; diff --git a/benchmark/C/Savina/src/parallelism/MatMulBenchmarkRunner.lf b/benchmark/C/Savina/src/parallelism/MatMulBenchmarkRunner.lf new file mode 100644 index 0000000000..9903aec4f9 --- /dev/null +++ b/benchmark/C/Savina/src/parallelism/MatMulBenchmarkRunner.lf @@ -0,0 +1,328 @@ +/** + * This benchmark is a C implementation of the parallel matrix multiplication + * algorithm that appears in the Savina suite, including the same race condition. + * + * For details on this benchmark, see the Cpp version from which it was derived: + * https://github.com/lf-lang/lingua-franca/blob/master/benchmark/C/Savina/src/parallelism/FilterBank.lf + */ + +target C { + /* [[[cog + if (threaded_runtime=="True"): + cog.outl(f"threads: {threads},") + else: + cog.outl("threads: 0,") + ]]] */ + threads: 0, + /// [[[end]]] + cmake-include: "../lib/matrix.cmake", + files: ["../lib/matrix.c", "../include/matrix.h"], + build-type: RelWithDebInfo +}; + +import BenchmarkRunner from "../BenchmarkRunner.lf"; + +preamble {= + #include + #include + + typedef struct work_item_t { + size_t srA; // srA = start row in matrix A + size_t scA; // scA = start column in matrix A + size_t srB; + size_t scB; + size_t srC; + size_t scC; + size_t num_blocks; // total number of elements per block in both dimensions + size_t dim; // number of elements in one dimension in one block + } work_item_t; + + typedef struct work_stack_t { + work_item_t** stacks; + size_t current_n_stacks; + // Invariant: current_stack <= next_item <= current_stack_end + // Invariant: 8 << current_n_stacks == current_stack_end - current_stack, unless + // current_n_stacks == 0 + // current_stack_end is never a valid memory location to use + work_item_t* current_stack; + work_item_t* next_item; + work_item_t* current_stack_end; + } work_stack_t; + + /* + * Allocates and initializes a work stack. + */ + work_stack_t work_stack_new() { + // 2^53 - 8 WorkItems is more than can fit in a computer, and it is fine for + // work stacks to be initialized with space for 50 pointers because for every + // work_stack_t that exists, at least one expensive parallel computation is + // performed, and the number of expensive computations that can be performed is + // limited. + work_item_t** stacks = malloc(50 * sizeof(work_item_t*)); + assert(stacks != NULL); + *stacks = NULL; + return (work_stack_t) { stacks, 0, NULL, NULL, NULL }; + } + + /* + * Frees the memory required by the work stack. This operation invalidates the work + * stack. + */ + void work_stack_free(work_stack_t work) { + for (size_t i = 1; i <= work.current_n_stacks; ++i) { + free((work.stacks)[i]); + } + } + + /* + * Pushes a work item to the given work stack. + * @param work A work stack. + * @param w A new work item. + */ + void work_stack_push(work_stack_t* work, work_item_t w) { + if (work->next_item == work->current_stack_end) { + size_t current_height = 8L << (++work->current_n_stacks); + work_item_t* current_stack = (work_item_t*) malloc( + current_height * sizeof(work_item_t) + ); + assert(work->current_n_stacks < 50); + (work->stacks)[work->current_n_stacks] = current_stack; + work->current_stack = current_stack; + work->next_item = current_stack; + work->current_stack_end = (work_item_t*) current_stack + current_height; + } + *(work->next_item++) = w; + } + + /* + * Pops (removes) an item from the given work stack. + * @return The work item at the top of the stack. + */ + work_item_t work_stack_pop(work_stack_t* work) { + if (work->next_item == work->current_stack) { + assert(work->current_n_stacks > 1); + size_t current_height = 8L << (--work->current_n_stacks); + free(work->current_stack); + work_item_t* current_stack = (work->stacks)[work->current_n_stacks]; + work_item_t* end = (work_item_t*) current_stack + current_height; + work->current_stack = current_stack; + work->next_item = end; + work->current_stack_end = end; + } + return *(--work->next_item); + } + + /* + * Returns whether the given work stack is empty. + */ + bool work_stack_empty(work_stack_t* work) { + return work->current_n_stacks <= 1 && (work->next_item == work->current_stack); + } +=} + +reactor Manager(num_workers: int(20), data_length: size_t(1024)) { + preamble {= + #include + #include "matrix.h" + + /* + * Accesses the given matrix using the assumption that it is the + * transpose of the matrix that we wish to represent. + */ + double* transposed_mat_at_d(matrix_t matrix, size_t i, size_t j) { + return mat_at_d(matrix, j, i); + } + + /* + * Prints a message if there exists an incorrect entry in the given matrix. + */ + void check_if_valid(matrix_t C, size_t data_length) { + for (size_t i = 0; i < data_length; ++i) { + for (size_t j = 0; j < data_length; ++j) { + double actual = *mat_at_d(C, i, j); + double expected = 1.0 * data_length * i * j; + if (fabs(actual - expected) > 0.0001) { + printf( + "Validation failed for (i,j)=(%li, %li) with (%f, %f)\n", + i, j, actual, expected + ); + return; + } + } + } + } + =} + + input[num_workers] more_work: {= work_item_t* =}; + + // Ports to interact with the benchmarkRunner reactor + input start: bool; + output finished: bool; + + logical action next; + logical action done; + + output data: matrix_t[]; + output[num_workers] do_work: work_item_t; + + state A: matrix_t; + state B: matrix_t; + state C: matrix_t; + + state work_stack: work_stack_t; + + reaction (startup) {= + // Fill both input arrays with data + self->A = mat_new_d(self->data_length, self->data_length); + self->B = mat_new_d(self->data_length, self->data_length); + for (size_t i = 0; i < self->data_length; ++i) { + for (size_t j = 0; j < self->data_length; ++j) { + *mat_at_d(self->A, i, j) = i; + *transposed_mat_at_d(self->B, i, j) = j; + } + } + =} + + reaction (start) -> data, next {= + self->C = mat_new_d(self->data_length, self->data_length); + SET_NEW_ARRAY(data, 3); + data->value[0] = self->A; + data->value[1] = self->B; + data->value[2] = self->C; + size_t num_blocks = self->data_length * self->data_length; + work_stack_t ws = work_stack_new(); + work_stack_push(&ws, (work_item_t) { + 0, 0, 0, 0, 0, 0, num_blocks, self->data_length + }); + self->work_stack = ws; + schedule(next, 0); + =} + + reaction (next) -> next, done, do_work {= + if (work_stack_empty(&(self->work_stack))) { + schedule(done, 0); + } else { + for ( + int i = 0; + i < do_work_width && !work_stack_empty(&(self->work_stack)); + ++i + ) { + work_item_t ws = work_stack_pop(&(self->work_stack)); + SET((do_work[i]), ws); + } + schedule(next, 0); + } + =} + + reaction (more_work) {= + // append all work items received from the workers to the internal work queue + for (int i = 0; i < more_work_width; ++i) { + if (more_work[i]->is_present) { + work_item_t* items = more_work[i]->value; + // If the port is present, then it certainly has exactly 8 WorkItems. + for (int j = 0; j < 8; ++j) { + work_stack_push(&(self->work_stack), items[j]); + } + } + } + =} + + reaction (done) -> finished {= + check_if_valid(self->C, self->data_length); + work_stack_free(self->work_stack); + mat_destroy_d(self->C); + SET_PRESENT(finished); + =} + + reaction(shutdown) {= + mat_destroy_d(self->A); + mat_destroy_d(self->B); + =} +} + +reactor Worker(threshold: size_t(16384), data_length: size_t(1024)) { + input data: matrix_t[]; + input do_work: work_item_t; + output more_work: {= work_item_t* =}; + + state A: {= matrix_t =}; + state B: {= matrix_t =}; + state C: {= matrix_t =}; + + reaction (data) {= + matrix_t* matrices = data->value; + self->A = matrices[0]; + self->B = matrices[1]; + self->C = matrices[2]; + =} + + reaction (do_work) -> more_work {= + work_item_t wi = do_work->value; + // If the number of blocks to process is above the threshold, + // then we split the problem into smaller chunks and generate more work items + if (wi.num_blocks > self->threshold) { + size_t dim = wi.dim / 2; + size_t num_blocks = wi.num_blocks / 4; + + SET_NEW_ARRAY(more_work, 8); + + more_work->value[0] = (work_item_t) {wi.srA , wi.scA , wi.srB , wi.scB , wi.srC , wi.scC , num_blocks, dim}; + more_work->value[1] = (work_item_t) {wi.srA , wi.scA + dim, wi.srB + dim, wi.scB , wi.srC , wi.scC , num_blocks, dim}; + more_work->value[2] = (work_item_t) {wi.srA , wi.scA , wi.srB , wi.scB + dim, wi.srC , wi.scC + dim, num_blocks, dim}; + more_work->value[3] = (work_item_t) {wi.srA , wi.scA + dim, wi.srB + dim, wi.scB + dim, wi.srC , wi.scC + dim, num_blocks, dim}; + more_work->value[4] = (work_item_t) {wi.srA + dim, wi.scA , wi.srB , wi.scB , wi.srC + dim, wi.scC , num_blocks, dim}; + more_work->value[5] = (work_item_t) {wi.srA + dim, wi.scA + dim, wi.srB + dim, wi.scB , wi.srC + dim, wi.scC , num_blocks, dim}; + more_work->value[6] = (work_item_t) {wi.srA + dim, wi.scA , wi.srB , wi.scB + dim, wi.srC + dim, wi.scC + dim, num_blocks, dim}; + more_work->value[7] = (work_item_t) {wi.srA + dim, wi.scA + dim, wi.srB + dim, wi.scB + dim, wi.srC + dim, wi.scC + dim, num_blocks, dim}; + + } else { + // otherwise we compute the result directly + size_t end_r = wi.srC + wi.dim; + size_t end_c = wi.scC + wi.dim; + + for (size_t i = wi.srC; i < end_r; ++i) { + for (size_t j = wi.scC; j < end_c; ++j) { + for (size_t k = 0; k < wi.dim; ++k) { + *mat_at_d(self->C, i, j) += ( + (*mat_at_d(self->A, i, wi.scA + k)) + * (*transposed_mat_at_d(self->B, wi.srB + k, j)) + ); + } + } + } + } + =} +} + +main reactor ( + /*[[[cog + cog.outl(f'num_iterations: size_t({num_iterations}),') + cog.outl(f'data_length: size_t({data_length}),') + cog.outl(f'block_threshold: int({block_threshold}),') + cog.outl(f'priorities: int({priorities}),') + cog.outl(f'num_workers: int({num_workers})') + ]]] */ + num_iterations: size_t(12), + data_length: size_t(1024), + block_threshold: int(16384), + priorities: int(10), + num_workers: int(20) + /// [[[end]]] +) { + runner = new BenchmarkRunner(num_iterations=num_iterations); + manager = new Manager(num_workers=num_workers, data_length=data_length); + workers = new[num_workers] Worker(threshold=block_threshold, data_length=data_length); + + reaction(startup) -> runner.inStart {= + printBenchmarkInfo("RecMatMulBenchmark"); + printSystemInfo(); + SET(runner.inStart, true); + =} + + runner.outIterationStart -> manager.start; + manager.finished -> runner.inIterationFinish; + + (manager.data)+ -> workers.data; + manager.do_work -> workers.do_work; + workers.more_work -> manager.more_work; +} From 379e0ff21df73349eac5dbf0689b00dc7ab019bd Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Mon, 22 Nov 2021 09:25:29 +0100 Subject: [PATCH 029/221] clean up in the benchmark directory --- .../src}/PingPongDistributed.lf | 0 benchmark/C/Savina/src/concurrency/Smokers.lf | 139 ------ .../bndbuffer/ProducerConsumerAsync.lf | 469 ------------------ .../bndbuffer/ProducerConsumerSync.lf | 126 ----- 4 files changed, 734 deletions(-) rename benchmark/C/{Savina/src/micro/Distributed => Distributed/src}/PingPongDistributed.lf (100%) delete mode 100644 benchmark/C/Savina/src/concurrency/Smokers.lf delete mode 100644 benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerAsync.lf delete mode 100644 benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerSync.lf diff --git a/benchmark/C/Savina/src/micro/Distributed/PingPongDistributed.lf b/benchmark/C/Distributed/src/PingPongDistributed.lf similarity index 100% rename from benchmark/C/Savina/src/micro/Distributed/PingPongDistributed.lf rename to benchmark/C/Distributed/src/PingPongDistributed.lf diff --git a/benchmark/C/Savina/src/concurrency/Smokers.lf b/benchmark/C/Savina/src/concurrency/Smokers.lf deleted file mode 100644 index a7ec7f3d4c..0000000000 --- a/benchmark/C/Savina/src/concurrency/Smokers.lf +++ /dev/null @@ -1,139 +0,0 @@ -/** - * At startup, there is N smokers and one arbiter. The arbiter - * randomly selects N-1 ingredients and indicate that the - * smoker with the complementary ingredients can smoke by - * sending a `complementary_ingredient` message the smoker. - * Upon receiving the message, if the smoker holds the needed - * complementary ingredient, they can now smoke and sends a - * `smoking` message to the arbiter after their smoke time. - * When receiving the `smoking` message, the arbiter increment - * their count of total cigarettes smoked. After R total - * cigarettes smoked, the arbiter finishes. - */ - - /* [[[cog -# This file is a code generator using the python module cog: -# See https://nedbatchelder.com/code/cog/ -# -# All instructions for code generation are in-lined in comments -# like this one. With that you can use this file as a normal source file -# but also to generate code. -# -# To change the generated code in-line within this file run: -# $ python -m cog -r this-file.lf -# To generate a new file from this file stripping the generator code in the process run: -# $ python -m cog -d -o output-file.lf this-file.lf -# -# Use the command line option -D to specify generator parameters, for example: -# $ python -m cog -r -D parameter=100 this-file.lf -# -# Generator parameters used in this file: -# -D num_smokers=200 -# -]]] */ -// [[[end]]] - -/* [[[cog - # force existence, type and default values of generator parameters - if 'num_smokers' in globals(): - num_smokers = int(num_smokers) - else: - globals()['num_smokers'] = 200 - - # output the current value of the generator parameters used in the last generation run - cog.outl(f'// Generated file with the following parameters:') - cog.outl(f'// num_smokers = {num_smokers}') -]]] */ -// Generated file with the following parameters: -// num_smokers = 200 -// [[[end]]] - -target C { - fast: true, - threads: 1, - tracing: true -}; -reactor Arbiter( - num_smokers:int(10),// N, number of smokers and ingredients - num_success:int(20) // Number of successes before terminate -) { - preamble {= - // randomly selects N-1 ingredients and return the complementary ingredient - int select_ingredients(int num_ingredients) { - int complementary_ingredient = rand()%num_ingredients; - return complementary_ingredient; - } - =} - - input[num_smokers] smoking:bool; // indicate that smoker i is currently smoking - - output[num_smokers] complementary_ingredient:int; // indicates that the smoker with the complementary ingredient can smoke - output finish:bool; - - state success_count:int; - state num_ingredients:int; - - logical action get_ingredients; - - reaction(startup) -> get_ingredients {= - self->num_ingredients = self->num_smokers; - self->success_count = 0; - schedule(get_ingredients,0); - =} - reaction(get_ingredients) -> finish, complementary_ingredient {= - if (self->success_count >= self->num_success){ - SET(finish,true); - // printf("%d cigarettes have been smoked. Arbiter is done.\n", self->success_count); - }else { - int ingredient = select_ingredients(self->num_ingredients); - SET(complementary_ingredient[ingredient], ingredient); - // printf("Arbiter: smoker with ingredient %d can now smoke.\n", ingredient); - } - =} - reaction(smoking)-> get_ingredients{= - for(int i = 0; inum_smokers;i++){ - if (smoking[i]->is_present && smoking[i]->value == true){ - // printf("Arbiter: smoker %d smoked their cigarette. Increment success count.\n", i); - self->success_count++; - schedule(get_ingredients,0); - } - } - =} - - -} - -reactor Smoker(ingredient_supply:int(0),smoke_time:time(1 nsec)){ - input complementary_ingredient:int; - logical action make_cigarette; - state busy:bool; - output smoking:bool; - - - reaction(startup) {= - self->ingredient_supply = self->bank_index; - self->busy = false; - =} - reaction(complementary_ingredient) -> make_cigarette{= - if(complementary_ingredient->value == self->ingredient_supply && self->busy == false){ - self->busy = true; - schedule(make_cigarette,self->smoke_time); - // printf("Smoker %d received their ingredients.\n", self->ingredient_supply); - } - =} - reaction(make_cigarette)-> smoking{= - SET(smoking,true); - self->busy = false; - =} -} - -main reactor (num_smokers:int(200),num_success:int(1000)){ - /* [[[cog - cog.outl(f'arbiter = new Arbiter(num_smokers = {num_smokers}, num_success=num_success);') - ]]] */ - arbiter = new Arbiter(num_smokers = num_smokers, num_success=num_success); - // [[[end]]] - smokers = new[num_smokers] Smoker(); - arbiter.complementary_ingredient -> smokers.complementary_ingredient; - smokers.smoking -> arbiter.smoking; -} diff --git a/benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerAsync.lf b/benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerAsync.lf deleted file mode 100644 index 07089726c2..0000000000 --- a/benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerAsync.lf +++ /dev/null @@ -1,469 +0,0 @@ -/* - * Concurrency benchmark from the Savina benchmark suite. - * See https://shamsimam.github.io/papers/2014-agere-savina.pdf. - * @author Hannes Klein - * @author Marten Lohstroh - * @author Soroush Bateni - * @author Shoakai Lin - */ - -target C { - flags: "-lm", - files: ../include/PseudoRandom.h, - threads:24 -}; - -preamble {= - #include - #include - #include - #include "PseudoRandom.h" - - typedef struct int_fifo_t { - size_t head; - size_t tail; - size_t max_size; - bool is_full; - int* arr; - } int_fifo_t; - - /** - * Report whether the given queue is empty. - */ - bool int_fifo_is_empty(int_fifo_t* queue) { - if (queue->head == queue->tail && !queue->is_full) { - return true; - } else { - return false; - } - } - - /** - * Report how many elements are in the queue. - */ - size_t int_fifo_size(int_fifo_t* queue) { - if (int_fifo_is_empty(queue)) { - return 0; - } else if (queue->is_full) { - return queue->max_size; - } else if (queue->tail > queue->head) { - return queue->tail - queue->head; - } else { - return queue->max_size - (queue->head - queue->tail); - } - } - - /** - * Clear the given queue by resetting its head and tail pointer. - */ - void int_fifo_clear(int_fifo_t* queue) { - queue->head = 0; - queue->tail = 0; - queue->is_full = false; - } - - /** - * Create a new bounded FIFO queue. - */ - int_fifo_t* int_fifo_create(size_t capacity) { - int_fifo_t* queue = malloc(sizeof(int_fifo_t)); - queue->arr = malloc(capacity * sizeof(int)); - queue->max_size = capacity; - int_fifo_clear(queue); - return queue; - } - - /** - * Assign the given pointer to the first element in the queue. - * Return true if the assignment was successful, false otherwise. - */ - bool int_fifo_pop(int_fifo_t* queue, int* data) { - if (int_fifo_is_empty(queue)) { - //printf("Buffer is empty.\n"); - return false; - } else { - *data = queue->arr[queue->head]; - //printf("Popping: %d\n", *data); - queue->head = (queue->head + 1) % queue->max_size; - queue->is_full = false; - return true; - } - } - - /** - * Add a new entry to the queue. - */ - bool int_fifo_push(int_fifo_t* queue, int elem) { - if (queue->is_full) { - //printf("Buffer is full.\n"); - return false; - } else { - //printf("Pushing: %d\n", elem); - queue->arr[queue->tail] = elem; - queue->tail = (queue->tail + 1) % queue->max_size; - if (queue->head == queue->tail) { - queue->is_full = true; - } - return true; - } - } - - typedef struct double_fifo_t { - size_t head; - size_t tail; - size_t max_size; - bool is_full; - double* arr; - } double_fifo_t; - - /** - * Report whether the given queue is empty. - */ - bool double_fifo_is_empty(double_fifo_t* queue) { - if (queue->head == queue->tail && !queue->is_full) { - return true; - } else { - return false; - } - } - - /** - * Report how many elements are in the queue. - */ - size_t double_fifo_size(double_fifo_t* queue) { - if (double_fifo_is_empty(queue)) { - return 0; - } else if (queue->is_full) { - return queue->max_size; - } else if (queue->tail > queue->head) { - return queue->tail - queue->head; - } else { - return queue->max_size - (queue->head - queue->tail); - } - } - - /** - * Clear the given queue by resetting its head and tail pointer. - */ - void double_fifo_clear(double_fifo_t* queue) { - queue->head = 0; - queue->tail = 0; - queue->is_full = false; - } - - /** - * Create a new bounded FIFO queue. - */ - double_fifo_t* double_fifo_create(size_t capacity) { - double_fifo_t* queue = malloc(sizeof(double_fifo_t)); - queue->arr = malloc(capacity * sizeof(double)); - queue->max_size = capacity; - double_fifo_clear(queue); - return queue; - } - - /** - * Assign the given pointer to the first element in the queue. - * Return true if the assignment was successful, false otherwise. - */ - bool double_fifo_pop(double_fifo_t* queue, double* data) { - if (double_fifo_is_empty(queue)) { - //printf("Buffer is empty.\n"); - return false; - } else { - *data = queue->arr[queue->head]; - queue->head = (queue->head + 1) % queue->max_size; - queue->is_full = false; - return true; - } - } - - /** - * Add a new entry to the queue. - */ - bool double_fifo_push(double_fifo_t* queue, int elem) { - if (queue->is_full) { - //printf("Buffer is full.\n"); - return false; - } else { - queue->arr[queue->tail] = elem; - queue->tail = (queue->tail + 1) % queue->max_size; - if (queue->head == queue->tail) { - queue->is_full = true; - } - return true; - } - } - - - static double processItem(const double curTerm, const int cost) { - double res = curTerm; - struct PseudoRandom random; - initPseudoRandom(&random, cost); - if(cost > 0) { - for(int i = 0; i < cost; i++) { - for(int j = 0; j < 100; j++) { - res += log(fabs(nextDouble(random)) + 0.01); - } - } - } else { - res += log(fabs(nextDouble(random)) + 0.01); - } - return res; - } - - void printBenchmarkInfo(char* benchmarkId) { - printf("Benchmark: %s\n", benchmarkId); - } - - void printSystemInfo() { - - printf("System information\n"); - printf("O/S Name: "); - - #ifdef _WIN32 - printf("Windows 32-bit"); - #elif _WIN64 - printf("Windows 64-bit"); - #elif __APPLE__ || __MACH__ - printf("Mac OSX"); - #elif __linux__ - printf("Linux"); - #elif __FreeBSD__ - printf("FreeBSD"); - #elif __unix || __unix__ - printf("Unix"); - #else - printf("Other"); - #endif - - printf("\n"); - } - -=} - -reactor Manager(bufferSize:size_t(50), numProducers:size_t(40), numConsumers:size_t(40)) { - - state adjustedBufferSize:int(0); - state availableProducers:int_fifo_t*; - state availableConsumers:int_fifo_t*; - state pendingData:double_fifo_t*; - - input[numProducers] producerData:double; - output[numProducers] produceNext:bool; - input[numConsumers] consumerReady:bool; - output[numConsumers] consumerData:double; - - logical action requestData; - logical action consumeMore; - - /** - * Populate the "available" FIFO queues and schedule - * the produce action. - */ - reaction(startup) {= - printf("Manager started up.\n"); - self->adjustedBufferSize = self->bufferSize - self->numProducers; - self->availableProducers = int_fifo_create(self->numProducers); - self->availableConsumers = int_fifo_create(self->numConsumers); - self->pendingData = double_fifo_create(self->bufferSize); - - for (int i = 0; i < self->numConsumers; i++) { - int_fifo_push(self->availableConsumers, i); - } - for (int i = 0; i < self->numProducers; i++) { - int_fifo_push(self->availableProducers, i); - } - =} - - /** - * Tell producers to start producing, provided that there is enough - * space in the pending data FIFO and the producers are not all done. - */ - reaction(startup, requestData) -> produceNext {= - printf("Manager requesting data production.\n"); - - while(double_fifo_size(self->pendingData) < self->adjustedBufferSize) { - if (int_fifo_is_empty(self->availableProducers)) { - printf("All producers are now busy.\n"); - break; - } - int idx; - int_fifo_pop(self->availableProducers, &idx); - printf("Asking producer %d to start.\n", idx); - SET(produceNext[idx], true); - } - =} - - /** - * If consumers are ready, schedule a consumeMore action, which will - * result in the buffer being drained and new data being requested. - */ - reaction(consumeMore) -> consumerData, produceNext {= - - while(!int_fifo_is_empty(self->availableConsumers)) { - if (double_fifo_is_empty(self->pendingData)) { - break; - } - - double data; - double_fifo_pop(self->pendingData, &data); - int idx; - int_fifo_pop(self->availableConsumers, &idx); - SET(consumerData[idx], data); - - if (!int_fifo_is_empty(self->availableProducers)) { - int_fifo_pop(self->availableProducers, &idx); - SET(produceNext[idx], true); - } - } - =} - - reaction(consumerReady) -> consumeMore {= - for (int i = 0; i < consumerReady_width; i++) { - if (consumerReady[i]->is_present) { - printf("Consumer %d is ready for more data.\n", i); - int_fifo_push(self->availableConsumers, i); - } - } - schedule(consumeMore, 0); - =} - - - /** - * Respond to new data coming in. - */ - reaction(producerData) -> requestData, consumeMore {= - // Stick all incoming data in a buffer. - for (int i = 0; i < producerData_width; i++) { - if (producerData[i]->is_present) { - double_fifo_push(self->pendingData, producerData[i]->value); - int_fifo_push(self->availableProducers, i); - } - schedule(requestData, 0); - schedule(consumeMore, 0); - } - - =} - - /** - * If there is fresh data or consumers became available, drain the buffer. - * Also schedule a requestData action so that producers will be asked to - * produce more data. - */ -// reaction(producerData, consumeMore) -> consumerData, requestData {= -// // Find a consumer for all pending data. -// int idx; -// double data; -// while (!double_fifo_is_empty(self->pendingData)) { -// if (int_fifo_pop(self->availableConsumers, &idx)) { -// double_fifo_pop(self->pendingData, &data); -// SET(consumerData[idx], data); -// } else { -// break; -// } -// } -// schedule(requestData, 0); -// =} -// - -} - -reactor Producer(numItemsToProduce:int(1000), prodCost:int(25)) { - - state prodItem:double(0.0); - state itemsProduced:int(0); - - input next:bool; - output data:double; - - reaction(next) -> data {= - printf(">>>>\n"); - if (self->itemsProduced < self->numItemsToProduce) { - printf("Producer creating data.\n"); - SET(data, processItem(self->prodItem, self->prodCost)); - self->itemsProduced += 1; - } - =} -} - -reactor Consumer(numItemsToConsume:int(1000), consCost:int(25)) { - - state consItem:double(0.0); - state itemsConsumed:int(0); - input data:double; - output ready:bool; - - reaction(data) -> ready {= - printf("One consumer got new data.\n"); - self->consItem = processItem(self->consItem + data->value, self->consCost); - self->itemsConsumed += 1; - SET(ready, true); - =} -} - -reactor ProducerRelayer { - input data_in:double; - output data_out:double; - state data:double; - physical action relay; - - reaction(data_in) -> relay {= - self->data = data_in->value; - schedule(relay, 0); - =} - - reaction(relay) -> data_out {= - SET(data_out, self->data); - =} - } - - reactor ConsumerRelayer { - input data_in:bool; - output data_out:bool; - state data:bool; - physical action relay; - - reaction(data_in) -> relay {= - self->data = data_in->value; - schedule(relay, 0); - =} - - reaction(relay) -> data_out {= - //printf("<<>>"); - SET(data_out, self->data); - =} - } - -main reactor ProdConsAsync(bufferSize:int(50), prodCost:int(25), consCost:int(25), numItemsPerProducer:int(5000)) { - - reaction(startup) {= - printBenchmarkInfo("ProdConsBenchmark"); - // printArgs("numIterations", self->numIterations, "bufferSize", self->bufferSize, "prodCost", self->prodCost, "consCost", self>consCost, "numItemsPerProducer", self->numItemsPerProducer, "numProducers", 40, "numConsumers", 40); - printf("bufferSize: %d.\n", self->bufferSize); - printf("prodCost: %d.\n", self->prodCost); - printf("consCost: %d.\n", self->consCost); - printf("numItemsPerProducer: %d.\n", self->numItemsPerProducer); - printf("numProducers: %d.\n", 40); - printf("numConsumers: %d.\n", 40); - - printSystemInfo(); - =} - manager = new Manager(bufferSize=bufferSize, numProducers=40, numConsumers=40); - producers = new[40] Producer(numItemsToProduce=numItemsPerProducer, prodCost=prodCost); - consumers = new[40] Consumer(numItemsToConsume=numItemsPerProducer, consCost=consCost); - //prodRelayers = new[40] ProducerRelayer(); - //consRelayers = new[40] ConsumerRelayer(); - - // FIXME: We should use physical connections along the data path. - // These are not supported yet. We could use banks of reactors that schedule a phys. action. - - manager.produceNext -> producers.next; - manager.consumerData -> consumers.data; - producers.data -> manager.producerData; - //prodRelayers.data_in; - //prodRelayers.data_out -> manager.producerData; - consumers.ready -> manager.consumerReady; //consRelayers.data_in; - //consRelayers.data_out -> manager.consumerReady; - -} diff --git a/benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerSync.lf b/benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerSync.lf deleted file mode 100644 index a42638b527..0000000000 --- a/benchmark/C/Savina/src/concurrency/bndbuffer/ProducerConsumerSync.lf +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Concurrency benchmark from the Savina benchmark suite. - * See https://shamsimam.github.io/papers/2014-agere-savina.pdf. - * @author Hannes Klein - * @author Marten Lohstroh - * @author Soroush Bateni - * @author Shaokai Lin - */ - -target C { - flags: "-lm", - //logging: DEBUG, - keepalive: true, - files: ../include/PseudoRandom.h, - threads: 24 -}; - -//import BenchmarkRunner from "../BenchmarkRunner.lf"; - -preamble {= - #include - #include - #include - #include "PseudoRandom.h" - - static double processItem(const double curTerm, const int cost) { - double res = curTerm; - struct PseudoRandom random; - initPseudoRandom(&random, cost); - if(cost > 0) { - for(int i = 0; i < cost; i++) { - for(int j = 0; j < 100; j++) { - res += log(fabs(nextDouble(random)) + 0.01); - } - } - } else { - res += log(fabs(nextDouble(random)) + 0.01); - } - return res; - } - - void printBenchmarkInfo(char* benchmarkId) { - printf("Benchmark: %s\n", benchmarkId); - } - - void printSystemInfo() { - - printf("System information\n"); - printf("O/S Name: "); - - #ifdef _WIN32 - printf("Windows 32-bit"); - #elif _WIN64 - printf("Windows 64-bit"); - #elif __APPLE__ || __MACH__ - printf("Mac OSX"); - #elif __linux__ - printf("Linux"); - #elif __FreeBSD__ - printf("FreeBSD"); - #elif __unix || __unix__ - printf("Unix"); - #else - printf("Other"); - #endif - - printf("\n"); - } - -=} - -reactor Producer(numItemsToProduce:int(1000), prodCost:int(25)) { - - state prodItem:double(0.0); - state itemsProduced:int(0); - - logical action next; - output data:double; - - reaction(startup, next) -> next, data {= - if (self->itemsProduced == self->numItemsToProduce) { - return; - } else { - schedule(next, 0); - SET(data, processItem(self->prodItem, self->prodCost)); - self->itemsProduced += 1; - } - =} -} - -reactor Consumer(numItemsToConsume:int(1000), consCost:int(25)) { - - state consItem:double(0.0); - state itemsConsumed:int(0); - input data:double; - - reaction(data) {= - self->consItem = processItem(self->consItem + data->value, self->consCost); - self->itemsConsumed += 1; - if (self->itemsConsumed == self->numItemsToConsume) { - request_stop(); - } - =} -} - -main reactor ProducerConsumerSync(bufferSize:int(50), prodCost:int(25), consCost:int(25), numItemsPerProducer:int(1000)) { - - reaction(startup) {= - printBenchmarkInfo("ProdConsBenchmark"); - // printArgs("numIterations", self->numIterations, "bufferSize", self->bufferSize, "prodCost", self->prodCost, "consCost", self>consCost, "numItemsPerProducer", self->numItemsPerProducer, "numProducers", 40, "numConsumers", 40); - printf("bufferSize: %d.\n", self->bufferSize); - printf("prodCost: %d.\n", self->prodCost); - printf("consCost: %d.\n", self->consCost); - printf("numItemsPerProducer: %d.\n", self->numItemsPerProducer); - printf("numProducers: %d.\n", 40); - printf("numConsumers: %d.\n", 40); - - printSystemInfo(); - =} - - producers = new[40] Producer(numItemsToProduce=numItemsPerProducer, prodCost=prodCost); - consumers = new[40] Consumer(numItemsToConsume=numItemsPerProducer, consCost=consCost); - - producers.data -> consumers.data; - -} From 94aed83b1b8aa6fcaffdfec7359fbbf319016368 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 22 Nov 2021 17:44:54 -0800 Subject: [PATCH 030/221] Added isParentOf method. --- .../org/lflang/generator/ReactorInstance.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 5c43394164..10c8b9c4f9 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -460,6 +460,24 @@ public boolean isMainOrFederated() { && (reactorDefinition.isMain() || reactorDefinition.isFederated()); } + /** + * Return true if this reactor is a parent + * (possibly indirect) of the specified instance. + */ + public boolean isParentOf(NamedInstance other) { + + if (other == null) return false; + + ReactorInstance otherReactor = (other instanceof ReactorInstance)? + (ReactorInstance)other : other.parent; + + while (otherReactor != null) { + if (otherReactor == this) return true; + otherReactor = otherReactor.parent; + } + return false; + } + /////////////////////////////////////////////////// //// Methods for finding instances in this reactor given an AST node. From 400856524b99aa26d6d8ce4db389706c8dd11c97 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 22 Nov 2021 17:45:31 -0800 Subject: [PATCH 031/221] Renamed and refined defineSelfStruct method --- .../org/lflang/generator/c/CGenerator.xtend | 86 ++++++++++++++----- 1 file changed, 63 insertions(+), 23 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index cfa6ad64d7..115a4db844 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -45,10 +45,10 @@ import org.eclipse.xtext.generator.IGeneratorContext import org.eclipse.xtext.nodemodel.util.NodeModelUtils import org.eclipse.xtext.util.CancelIndicator import org.lflang.ASTUtils -import org.lflang.JavaAstUtils import org.lflang.ErrorReporter import org.lflang.FileConfig import org.lflang.InferredType +import org.lflang.JavaAstUtils import org.lflang.Target import org.lflang.TargetConfig import org.lflang.TargetProperty @@ -65,6 +65,7 @@ import org.lflang.federated.serialization.SupportedSerializers import org.lflang.generator.ActionInstance import org.lflang.generator.GeneratorBase import org.lflang.generator.JavaGeneratorUtils +import org.lflang.generator.NamedInstance import org.lflang.generator.ParameterInstance import org.lflang.generator.PortInstance import org.lflang.generator.ReactionInstance @@ -3096,7 +3097,8 @@ class CGenerator extends GeneratorBase { var foundOne = false; val temp = new StringBuilder(); var nameOfSelfStruct = CUtil.selfRef(child) - getSelfStruct(temp, child); + + defineSelfStruct(temp, child); startScopedBlock(temp, child); for (input : child.inputs) { @@ -3108,7 +3110,7 @@ class CGenerator extends GeneratorBase { _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token = &«nameOfSelfStruct»->_lf_«input.name»[i]->token; _lf_tokens_with_ref_count[«startTimeStepTokens» + i].status - = (port_status_t*)&«nameOfSelfStruct»->_lf_«input.name»[i]->is_present; + = (port_status_t*)&«nameOfSelfStruct»->_lf_«input.name»[i].is_present; _lf_tokens_with_ref_count[«startTimeStepTokens» + i].reset_is_present = false; } ''') @@ -3118,7 +3120,7 @@ class CGenerator extends GeneratorBase { _lf_tokens_with_ref_count[«startTimeStepTokens»].token = &«nameOfSelfStruct»->_lf_«input.name»->token; _lf_tokens_with_ref_count[«startTimeStepTokens»].status - = (port_status_t*)&«nameOfSelfStruct»->_lf_«input.name»->is_present; + = (port_status_t*)&«nameOfSelfStruct»->_lf_«input.name».is_present; _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = false; ''') startTimeStepTokens++ @@ -3135,7 +3137,7 @@ class CGenerator extends GeneratorBase { var foundOne = false; val temp = new StringBuilder(); var containerSelfStructName = CUtil.selfRef(instance) - getSelfStruct(temp, instance); + defineSelfStruct(temp, instance); startScopedBlock(temp, instance); // Handle inputs that get sent data from a reaction rather than from @@ -3156,11 +3158,12 @@ class CGenerator extends GeneratorBase { // struct is a pointer. Otherwise, it is the struct itself. if (port.isMultiport) { foundOne = true; + pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. for (int i = 0; i < «port.width»; i++) { _lf_is_present_fields[«startTimeStepIsPresentCount» + i] - = &«containerSelfStructName»->_lf_«port.parent.name».«port.name»[i]->is_present; + = &«CUtil.sourceRef(port)»[i]->is_present; } ''') if (isFederatedAndDecentralized) { @@ -3169,7 +3172,7 @@ class CGenerator extends GeneratorBase { // Add port «port.getFullName» to array of is_present fields. for (int i = 0; i < «port.width»; i++) { _lf_intended_tag_fields[«startTimeStepIsPresentCount» + i] - = &«containerSelfStructName»->_lf_«port.parent.name».«port.name»[i]->intended_tag; + = &«CUtil.sourceRef(port)»[i]->intended_tag; } ''') } @@ -3179,14 +3182,14 @@ class CGenerator extends GeneratorBase { pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. _lf_is_present_fields[«startTimeStepIsPresentCount»] - = &«containerSelfStructName»->_lf_«port.parent.name».«port.name».is_present; + = &«CUtil.sourceRef(port)».is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution. pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. _lf_intended_tag_fields[«startTimeStepIsPresentCount»] - = &«containerSelfStructName»->_lf_«port.parent.name».«port.name».intended_tag; + = &«CUtil.sourceRef(port)».intended_tag; ''') } startTimeStepIsPresentCount++ @@ -3254,7 +3257,7 @@ class CGenerator extends GeneratorBase { // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { - getSelfStruct(startTimeStep, child); + defineSelfStruct(startTimeStep, child); startScopedBlock(startTimeStep, child); for (output : child.outputs) { @@ -3602,6 +3605,9 @@ class CGenerator extends GeneratorBase { // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. if (instance.isBank) { + // Need an extra layer of scoping for the array of self structs. + startScopedBlock(initializeTriggerObjects, null) + // Array is the self struct name, but without the indexing. var selfStructArrayName = instance.uniqueID + "_self" @@ -3732,6 +3738,10 @@ class CGenerator extends GeneratorBase { generateStartTimeStep(instance) endScopedBlock(initializeTriggerObjects); + + if (instance.isBank) { + endScopedBlock(initializeTriggerObjects); + } pr(initializeTriggerObjects, "//***** End initializing " + fullName) } @@ -3991,7 +4001,7 @@ class CGenerator extends GeneratorBase { def void setReactionPriorities(ReactorInstance reactor, FederateInstance federate) { val temp = new StringBuilder(); var foundOne = false; - getSelfStruct(temp, reactor); + defineSelfStruct(temp, reactor); startScopedBlock(temp, reactor); for (r : reactor.reactions) { @@ -4678,17 +4688,49 @@ class CGenerator extends GeneratorBase { /** * For the specified reactor, print code to the specified builder * that defines a pointer to the self struct of the specified - * reactor. If the specified reactor is a bank, then the pointer - * is to the array of pointers to the self structs for that bank + * reactor. + * + * If the specified reactor is a bank, then the pointer + * is to the array of pointers to the self structs for that bank. + * * @param builder The string builder into which to write. - * @param reactor The reactor instance. + * @param reactor The reactor instance for which to provide a self struct. */ - private def void getSelfStruct(StringBuilder builder, ReactorInstance reactor) { + private def void defineSelfStruct( + StringBuilder builder, ReactorInstance reactor + ) { + defineSelfStruct(builder, reactor, null); + } + + /** + * For the specified reactor, if necessary, print code to the specified builder + * that defines a pointer to the self struct of the specified + * reactor. + * + * The self struct is necessary if the specified reactor is not main nor + * a parent (even indirectly) of the specified instance (or if the + * specified instance is null. + * + * If the specified reactor is a bank, then the pointer + * is to the array of pointers to the self structs for that bank. + * + * @param builder The string builder into which to write. + * @param reactor The reactor instance for which to provide a self struct. + * @param instance The + */ + private def void defineSelfStruct( + StringBuilder builder, ReactorInstance reactor, NamedInstance instance + ) { + if (reactor.isParentOf(instance) || reactor == main) { + // Assume the self struct is already in scope because the reactor + // is a parent of the instance. + return; + } var nameOfSelfStruct = reactor.uniqueID() + "_self"; var structType = CUtil.selfType(reactor) if (reactor.isBank) { pr(builder, ''' - «structType»** «nameOfSelfStruct» = &(«structType»*)selfStructs[ + «structType»** «nameOfSelfStruct» = («structType»**)&selfStructs[ «CUtil.indexExpression(reactor.parent)» + «reactor.getIndexOffset»]; ''') } else { @@ -5305,7 +5347,7 @@ class CGenerator extends GeneratorBase { } pr('''// deferredInitialize for «reactor.getFullName()»''') - getSelfStruct(code, reactor); + defineSelfStruct(code, reactor); startScopedBlock(code, reactor); // Initialize the num_destinations fields of port structs on the self struct. @@ -5701,11 +5743,9 @@ class CGenerator extends GeneratorBase { && currentFederate.contains(dominatingReaction.definition) && currentFederate.contains(dominatingReaction.parent) ) { - if (dominatingReaction.parent != reaction.parent) { - // Define the destination struct pointer. - // FIXME: If the destination is a bank, need to define the bank_index variable. - getSelfStruct(code, dominatingReaction.parent); - } + // Define the destination struct pointer, if needed. + // FIXME: If the destination is a bank, need to define the bank_index variable. + defineSelfStruct(code, dominatingReaction.parent, reaction); startScopedBlock(code, null); val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' @@ -5893,7 +5933,7 @@ class CGenerator extends GeneratorBase { // Define the destination struct pointer. // FIXME: If the destination is a bank, need to define the bank_index variable. - getSelfStruct(temp, destination.parent); + defineSelfStruct(temp, destination.parent); if (destination.isOutput) { // Include this destination port only if it has at least one From 0cf19b5882809b0b51597e13fbaeee4aeb8c8704 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 23 Nov 2021 07:57:30 -0800 Subject: [PATCH 032/221] Added test of nested banks. --- test/C/src/multiport/NestedBanks.lf | 69 +++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 test/C/src/multiport/NestedBanks.lf diff --git a/test/C/src/multiport/NestedBanks.lf b/test/C/src/multiport/NestedBanks.lf new file mode 100644 index 0000000000..eecbf4bc55 --- /dev/null +++ b/test/C/src/multiport/NestedBanks.lf @@ -0,0 +1,69 @@ +/** + * Test of nested banks with multiports. + * @author Edward A. Lee + */ +target C; +main reactor { + a = new[2] A(); + c = new[3] C(); + d = new D(); + e = new E(); + + (a.x)+ -> c.z, d.u, e.t; +} +reactor A(bank_index:int(0)) { + output[4] x:int; + b = new[2] B(a_bank_index = bank_index); + b.y -> x; +} +reactor B(a_bank_index:int(0), bank_index:int(0)) { + output[2] y:int; + reaction(startup) -> y {= + int base = self->a_bank_index * 4 + self->bank_index * 2; + SET(y[0], base); + SET(y[1], base + 1); + =} +} +reactor C(bank_index:int(0)) { + input[2] z:int; + f = new F(c_bank_index = bank_index); + g = new G(c_bank_index = bank_index); + z -> f.w, g.s; +} +reactor D { + input[2] u:int; + reaction(u) {= + for (int i = 0; i < u_width; i++) { + info_print("d.u[%d] received %d.", i, u[i]->value); + if (u[i]->value != 6 + i) { + error_print_and_exit("Expected %d but received %d.", 6 + i, u[i]->value); + } + } + =} +} +reactor E { + input[8] t:int; + reaction(t) {= + for (int i = 0; i < t_width; i++) { + info_print("e.t[%d] received %d.", i, t[i]->value); + } + =} +} +reactor F(c_bank_index:int(0)) { + input w:int; + reaction(w) {= + info_print("c[%d].f.w received %d.", self->c_bank_index, w->value); + if (w->value != self->c_bank_index * 2) { + error_print_and_exit("Expected %d but received %d.", self->c_bank_index * 2, w->value); + } + =} +} +reactor G(c_bank_index:int(0)) { + input s:int; + reaction(s) {= + info_print("c[%d].g.s received %d.", self->c_bank_index, s->value); + if (s->value != self->c_bank_index * 2 + 1) { + error_print_and_exit("Expected %d but received %d.", self->c_bank_index * 2 + 1, s->value); + } + =} +} \ No newline at end of file From 28a3b60ce51b3858e56101ed73543c8f7543d560 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 23 Nov 2021 13:28:44 -0800 Subject: [PATCH 033/221] Checkpoint where basic models work. --- .../org/lflang/generator/NamedInstance.java | 53 ++++++- .../lflang/generator/ParameterInstance.java | 8 -- .../lflang/generator/ReactionInstance.java | 8 -- .../org/lflang/generator/ReactorInstance.java | 55 +------- .../org/lflang/generator/TriggerInstance.java | 8 -- .../org/lflang/generator/c/CGenerator.xtend | 131 ++++++++++++++++-- 6 files changed, 175 insertions(+), 88 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index 9aa3e7495a..d110eb3604 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -26,7 +26,10 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; + import org.eclipse.emf.ecore.EObject; /** @@ -49,6 +52,14 @@ public abstract class NamedInstance { protected NamedInstance(T definition, ReactorInstance parent) { this.definition = definition; this.parent = parent; + + // Calculate the depth. + this.depth = 0; + ReactorInstance p = parent; + while (p != null) { + p = p.parent; + this.depth++; + } } ////////////////////////////////////////////////////// @@ -67,6 +78,14 @@ public T getDefinition() { return definition; } + /** + * Get the depth of the reactor instance. This is 0 for the main reactor, + * 1 for reactors immediately contained therein, etc. + */ + public int getDepth() { + return depth; + } + /** * Return the full name of this instance, which has the form * "a.b.c", where "c" is the name of this instance, "b" is the name @@ -103,6 +122,23 @@ public ReactorInstance getParent() { return parent; } + /** + * Return a list of all the parents starting with the root(). + */ + public List parents() { + List result = new ArrayList(depth + 1); + if (this instanceof ReactorInstance && parent == null) { + // This is the top level, so it must be a reactor. + result.add((ReactorInstance) this); + } + ReactorInstance container = parent; + while (container != null) { + result.add(container); + container = container.parent; + } + return result; + } + /** * Return the root reactor if it is marked as as main or federated, * and otherwise return null. @@ -120,7 +156,13 @@ public ReactorInstance main() { * Return the root reactor, which is the top-level parent. * @return The top-level parent. */ - public abstract ReactorInstance root(); + public ReactorInstance root() { + if (parent != null) { + return parent.root(); + } else { + return (ReactorInstance)this; + } + } /** * Return an identifier for this instance, which has the form "a_b_c" @@ -210,6 +252,15 @@ protected String getFullNameWithJoiner(String joiner) { } } + ////////////////////////////////////////////////////// + //// Protected fields. + + /** + * The depth in the hierarchy of this reactor instance. + * This is 0 for main or federated, 1 for the reactors immediately contained, etc. + */ + protected int depth = 0; + ////////////////////////////////////////////////////// //// Private fields. diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index 8acc079777..d4b9765136 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -93,14 +93,6 @@ public Assignment getOverride() { return assignment.get(); } - /** - * {@inheritDoc} - */ - @Override - public ReactorInstance root() { - return parent.root(); - } - /** Return a descriptive string. */ @Override public String toString() { diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 07a669566d..d40ba47f38 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -407,14 +407,6 @@ public void removePortInstance(PortInstance portInstance) { portInstance.clearCaches(); } - /** - * {@inheritDoc} - */ - @Override - public ReactorInstance root() { - return parent.root(); - } - /** * Return a descriptive string. */ diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 10c8b9c4f9..93f72e37c4 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -102,36 +102,30 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, Set un //// Public fields. /** The action instances belonging to this reactor instance. */ - public List actions = new ArrayList(); + public final List actions = new ArrayList(); - /** - * For the convenience of code generators, this public field can - * be used to annotate the reactor instance. - */ - public String annotation; - /** * The contained reactor instances, in order of declaration. * For banks of reactors, this includes both the bank definition * Reactor (which has bankIndex == -2) followed by each of the * bank members (which have bankIndex >= 0). */ - public List children = new ArrayList(); + public final List children = new ArrayList(); /** The input port instances belonging to this reactor instance. */ - public List inputs = new ArrayList(); + public final List inputs = new ArrayList(); /** The output port instances belonging to this reactor instance. */ - public List outputs = new ArrayList(); + public final List outputs = new ArrayList(); /** The parameters of this instance. */ - public List parameters = new ArrayList(); + public final List parameters = new ArrayList(); /** List of reaction instances for this reactor instance. */ - public List reactions = new ArrayList(); + public final List reactions = new ArrayList(); /** The timer instances belonging to this reactor instance. */ - public List timers = new ArrayList(); + public final List timers = new ArrayList(); /** The reactor definition in the AST. */ public final Reactor reactorDefinition; @@ -248,14 +242,6 @@ public Map>> getConnections() { return connections; } - /** - * Get the depth of the reactor instance. This is 0 for the main reactor, - * 1 for reactors immediately contained therein, etc. - */ - public int getDepth() { - return depth; - } - /** * Return an integer is equal to one plus the total number of reactor instances * (including bank members) that have been instantiated before this one @@ -606,18 +592,6 @@ public TimerInstance lookupTimerInstance(Timer timer) { return null; } - /** - * {@inheritDoc} - */ - @Override - public ReactorInstance root() { - if (parent != null) { - return parent.root(); - } else { - return this; - } - } - /** * Return the set of ports in that are sources in connections in this reactor. * These may be input ports of this reactor or output ports of contained reactors. @@ -723,12 +697,6 @@ public int width() { /** The nested list of instantiations that created this reactor instance. */ protected List _instantiations; - /** - * The depth in the hierarchy of this reactor instance. - * This is 0 for main or federated, 1 for the reactors immediately contained, etc. - */ - protected int depth = 0; - ////////////////////////////////////////////////////// //// Protected methods. @@ -857,7 +825,6 @@ protected void transitiveClosure( * @param definition The instantiation statement in the AST. * @param parent The parent, or null for the main rector. * @param reporter An error reporter. - * @param depth The depth of this reactor in the hierarchy. * @param desiredDepth The depth to which to expand the hierarchy. * @param unorderedReactions A list of reactions that should be treated as unordered. * It can be passed as null. @@ -872,14 +839,6 @@ private ReactorInstance( this.reporter = reporter; this.reactorDefinition = ASTUtils.toDefinition(definition.getReactorClass()); - // Calculate the depth. - this.depth = 0; - ReactorInstance p = parent; - while (p != null) { - p = p.parent; - this.depth++; - } - if (unorderedReactions != null) { this.unorderedReactions = unorderedReactions; } diff --git a/org.lflang/src/org/lflang/generator/TriggerInstance.java b/org.lflang/src/org/lflang/generator/TriggerInstance.java index 29e27fae63..a9e7b9039b 100644 --- a/org.lflang/src/org/lflang/generator/TriggerInstance.java +++ b/org.lflang/src/org/lflang/generator/TriggerInstance.java @@ -128,14 +128,6 @@ public boolean isBuiltinTrigger() { return builtinTriggerType != null; } - /** - * {@inheritDoc} - */ - @Override - public ReactorInstance root() { - return parent.root(); - } - ///////////////////////////////////////////// //// Protected Fields diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 115a4db844..73b020d73c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1010,6 +1010,10 @@ class CGenerator extends GeneratorBase { deferredInitialize(main, reactionsInFederate) + // Next, for every input port, populate its "self" struct + // fields with pointers to the output port that sends it data. + deferredConnectInputsToOutputs(main) + // Put the code here to set up the tables that drive resetting is_present and // decrementing reference counts between time steps. This code has to appear // in _lf_initialize_trigger_objects() after the code that makes connections @@ -3098,6 +3102,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var nameOfSelfStruct = CUtil.selfRef(child) + startScopedBlock(temp, null); defineSelfStruct(temp, child); startScopedBlock(temp, child); @@ -3128,6 +3133,8 @@ class CGenerator extends GeneratorBase { } } endScopedBlock(temp); + endScopedBlock(temp); + if (foundOne) { pr(startTimeStep, temp.toString()); } @@ -3137,6 +3144,8 @@ class CGenerator extends GeneratorBase { var foundOne = false; val temp = new StringBuilder(); var containerSelfStructName = CUtil.selfRef(instance) + + startScopedBlock(temp, null); defineSelfStruct(temp, instance); startScopedBlock(temp, instance); @@ -3252,11 +3261,15 @@ class CGenerator extends GeneratorBase { } endScopedBlock(temp); + endScopedBlock(temp); + if (foundOne) pr(startTimeStep, temp.toString()); // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { + + startScopedBlock(startTimeStep, null); defineSelfStruct(startTimeStep, child); startScopedBlock(startTimeStep, child); @@ -3301,6 +3314,7 @@ class CGenerator extends GeneratorBase { } } endScopedBlock(startTimeStep); + endScopedBlock(startTimeStep); } } } @@ -4001,6 +4015,8 @@ class CGenerator extends GeneratorBase { def void setReactionPriorities(ReactorInstance reactor, FederateInstance federate) { val temp = new StringBuilder(); var foundOne = false; + + startScopedBlock(temp, null); defineSelfStruct(temp, reactor); startScopedBlock(temp, reactor); @@ -4022,6 +4038,8 @@ class CGenerator extends GeneratorBase { } } endScopedBlock(temp); + endScopedBlock(temp); + if (foundOne) pr(temp.toString()); for (child : reactor.children) { @@ -4653,6 +4671,85 @@ class CGenerator extends GeneratorBase { // ////////////////////////////////////////// // // Private methods. + /** + * Enclose the specified code in a potentially nested iteration over + * bank indices for the specified connection from the given source to the + * given destination. The code may include references to bank indices of + * containers of either the source or destination as returned by + * {@link CUtil.bankIndex(ReactorInstance)}. It may also include + * "srcChannel" and "dstChannel" to refer to the source an destination + * channel indices if those are multiports. + */ + private def void encloseInBankAndMultiportIteration( + StringBuilder builder, + NamedInstance source, + NamedInstance destination, + StringBuilder toEnclose + ) { + // Start a scoped block so we can define bank index variables without + // resulting in them being multiply defined. + startScopedBlock(builder, null); + defineSelfStruct(builder, source.parent); + if (destination.parent != source.parent) { + defineSelfStruct(builder, destination.parent); + } + + val sourceParents = source.parents; + // Initialize parent bank indices and construct a reversed list. + val sourceParentBanksReversed = new LinkedList(); + for (s : sourceParents) { + if (s.isBank) { + pr(builder, ''' + int «CUtil.bankIndex(s)» = 0; + ''') + sourceParentBanksReversed.push(s); + } + } + pr(builder, "int _lf_channels_satisfied = 0;"); + + // Generate iterations over banks of the destination containers. + for (destinationContainer : destination.parents) { + if (destinationContainer.isBank) { + val index = CUtil.bankIndex(destinationContainer); + pr(builder, ''' + // Iterate over destination bank members. + for (int «index» = 0; «index» < «destinationContainer.width»; «index»++) { + ''') + indent(builder); + } + } + + pr(builder, toEnclose); + + // Increment the source bank variables as needed. + if (!sourceParentBanksReversed.isEmpty) { + var s = sourceParentBanksReversed.pop(); + var i = CUtil.bankIndex(s); + var indents = 1; + pr(builder, ''' + «i»++; + if («i» >= «s.width») { + «i» = 0; + ''') + while (!sourceParentBanksReversed.isEmpty) { + s = sourceParentBanksReversed.pop(); + i = CUtil.bankIndex(s); + indent(builder); + indents++; + pr(builder, ''' + if («i» >= «s.width») { + «i» = 0; + ''') + } + while(indents-- > 0) { + unindent(builder); + pr(builder, "}"); + } + } + + endScopedBlock(builder) + } + /** * Start a scoped block for the specified reactor. * If the reactor is a bank, then this starts a for loop @@ -4716,7 +4813,7 @@ class CGenerator extends GeneratorBase { * * @param builder The string builder into which to write. * @param reactor The reactor instance for which to provide a self struct. - * @param instance The + * @param instance The current object whose parents are all in scope. */ private def void defineSelfStruct( StringBuilder builder, ReactorInstance reactor, NamedInstance instance @@ -4745,7 +4842,7 @@ class CGenerator extends GeneratorBase { * Generate assignments of pointers in the "self" struct of a destination * port's reactor to the appropriate entries in the "self" struct of the * source reactor. - * @param instance A port with dependant reactions. + * @param instance A port with dependent reactions. */ private def void connectPortToEventualSource(PortInstance port) { // Find the sources that send data to this port, @@ -4757,6 +4854,7 @@ class CGenerator extends GeneratorBase { for (eventualSource: port.eventualSources()) { val src = eventualSource.portInstance; if (src != port && currentFederate.contains(src.parent)) { + val temp = new StringBuilder(); // The eventual source is different from the port and is in the federate. val destStructType = variableStructType( port.definition as TypedVariable, @@ -4773,7 +4871,7 @@ class CGenerator extends GeneratorBase { if (port.isMultiport()) { // Source and destination are both multiports. - pr(''' + pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» { // To scope variable j int j = «eventualSource.startChannel»; @@ -4785,25 +4883,26 @@ class CGenerator extends GeneratorBase { startChannel += eventualSource.channelWidth; } else { // Source is a multiport, destination is a single port. - pr(''' + pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» «CUtil.destinationRef(port)» = («destStructType»*)«modifier»«CUtil.sourceRef(src)»[«eventualSource.startChannel»]; ''') } } else if (port.isMultiport()) { // Source is a single port, Destination is a multiport. - pr(''' + pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» «CUtil.destinationRef(port)»[«startChannel»] = («destStructType»*)&«CUtil.sourceRef(src)»; ''') startChannel++; } else { // Both ports are single ports. - pr(''' + pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(src)»; ''') } + encloseInBankAndMultiportIteration(code, src, port, temp); } } } @@ -5347,6 +5446,7 @@ class CGenerator extends GeneratorBase { } pr('''// deferredInitialize for «reactor.getFullName()»''') + startScopedBlock(code, null); defineSelfStruct(code, reactor); startScopedBlock(code, reactor); @@ -5360,10 +5460,6 @@ class CGenerator extends GeneratorBase { // For outputs that are not primitive types (of form type* or type[]), // create a default token on the self struct. deferredCreateDefaultTokens(reactor); - - // Next, for every input port, populate its "self" struct - // fields with pointers to the output port that sends it data. - deferredConnectInputsToOutputs(reactor) deferredAllocationForEffectsOnOutputs(reactor); @@ -5383,6 +5479,7 @@ class CGenerator extends GeneratorBase { deferredConnectReactionsToPorts(reactor) endScopedBlock(code) + endScopedBlock(code) } /** @@ -5393,7 +5490,8 @@ class CGenerator extends GeneratorBase { * @param instance The reactor instance. */ private def void deferredConnectInputsToOutputs(ReactorInstance instance) { - pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') + pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') + // Iterate over all ports of this reactor that have dependent reactions. for (input : instance.inputs) { if (!input.dependentReactions.isEmpty()) { @@ -5408,6 +5506,9 @@ class CGenerator extends GeneratorBase { connectPortToEventualSource(output); } } + for (child: instance.children) { + deferredConnectInputsToOutputs(child); + } } /** @@ -5745,15 +5846,15 @@ class CGenerator extends GeneratorBase { ) { // Define the destination struct pointer, if needed. // FIXME: If the destination is a bank, need to define the bank_index variable. - defineSelfStruct(code, dominatingReaction.parent, reaction); - startScopedBlock(code, null); + val temp = new StringBuilder(); val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' - pr(''' + pr(temp, ''' // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); ''') - endScopedBlock(code); + + encloseInBankAndMultiportIteration(code, dominatingReaction, reaction, temp); } else { pr(''' // Reaction «reactionNumber» of «reactorInstance.getFullName» does not depend on one maximal upstream reaction. From 9834eee4eaf4a861e7c60773fc90947cce3f99d4 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 23 Nov 2021 14:11:02 -0800 Subject: [PATCH 034/221] Refactor scope block generation. --- .../org/lflang/generator/c/CGenerator.xtend | 65 +++++++++++-------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 73b020d73c..4c8f776ff7 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3102,9 +3102,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var nameOfSelfStruct = CUtil.selfRef(child) - startScopedBlock(temp, null); - defineSelfStruct(temp, child); - startScopedBlock(temp, child); + startScopedReactorBlock(temp, child); for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { @@ -3132,8 +3130,7 @@ class CGenerator extends GeneratorBase { } } } - endScopedBlock(temp); - endScopedBlock(temp); + endScopedReactorBlock(temp); if (foundOne) { pr(startTimeStep, temp.toString()); @@ -3145,9 +3142,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var containerSelfStructName = CUtil.selfRef(instance) - startScopedBlock(temp, null); - defineSelfStruct(temp, instance); - startScopedBlock(temp, instance); + startScopedReactorBlock(temp, instance); // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an @@ -3260,8 +3255,7 @@ class CGenerator extends GeneratorBase { } } - endScopedBlock(temp); - endScopedBlock(temp); + endScopedReactorBlock(temp); if (foundOne) pr(startTimeStep, temp.toString()); @@ -3269,9 +3263,7 @@ class CGenerator extends GeneratorBase { for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { - startScopedBlock(startTimeStep, null); - defineSelfStruct(startTimeStep, child); - startScopedBlock(startTimeStep, child); + startScopedReactorBlock(startTimeStep, child); for (output : child.outputs) { if (output.isMultiport()) { @@ -3313,8 +3305,7 @@ class CGenerator extends GeneratorBase { startTimeStepIsPresentCount++ } } - endScopedBlock(startTimeStep); - endScopedBlock(startTimeStep); + endScopedReactorBlock(startTimeStep); } } } @@ -4016,9 +4007,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var foundOne = false; - startScopedBlock(temp, null); - defineSelfStruct(temp, reactor); - startScopedBlock(temp, reactor); + startScopedReactorBlock(temp, reactor); for (r : reactor.reactions) { if (federate === null || federate.contains( @@ -4037,8 +4026,7 @@ class CGenerator extends GeneratorBase { ''') } } - endScopedBlock(temp); - endScopedBlock(temp); + endScopedReactorBlock(temp); if (foundOne) pr(temp.toString()); @@ -4756,7 +4744,7 @@ class CGenerator extends GeneratorBase { * that iterates over the bank members using a standard index * variable. If the reactor is null or is not a bank, then this simply * starts a scoped block by printing an opening curly brace. - * This must be followed by an endScopedBlock(). + * This must be followed by an {@link endScopedBlock(StringBuilder)}. * @param builder The string builder into which to write. * @param reactor The reactor instance. */ @@ -4773,6 +4761,25 @@ class CGenerator extends GeneratorBase { indent(builder); } + /** + * Start a scoped block for the specified reactor in situations where + * the self struct for the specified reactor is not already in scope. + * If the reactor is a bank, then this starts a for loop + * that iterates over the bank members using a standard index + * variable. If the reactor is null or is not a bank, then this simply + * starts a scoped block by printing an opening curly brace. + * This must be followed by an {@link endScopedReactorBlock(StringBuilder)}. + * @param builder The string builder into which to write. + * @param reactor The reactor instance. + */ + private def void startScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { + // The first creates a scope in which we can define a pointer to the self + // struct without fear of redefining. + startScopedBlock(builder, null); + defineSelfStruct(builder, reactor); + startScopedBlock(builder, reactor); + } + /** * End a scoped block. * @param builder The string builder into which to write. @@ -4782,6 +4789,15 @@ class CGenerator extends GeneratorBase { pr(builder, "}"); } + /** + * End a scoped reactor block. + * @param builder The string builder into which to write. + */ + private def void endScopedReactorBlock(StringBuilder builder) { + endScopedBlock(builder); + endScopedBlock(builder); + } + /** * For the specified reactor, print code to the specified builder * that defines a pointer to the self struct of the specified @@ -5446,9 +5462,7 @@ class CGenerator extends GeneratorBase { } pr('''// deferredInitialize for «reactor.getFullName()»''') - startScopedBlock(code, null); - defineSelfStruct(code, reactor); - startScopedBlock(code, reactor); + startScopedReactorBlock(code, reactor); // Initialize the num_destinations fields of port structs on the self struct. deferredOutputNumDestinations(reactor); // NOTE: Not done for top level. @@ -5478,8 +5492,7 @@ class CGenerator extends GeneratorBase { // output of a contained reactor. deferredConnectReactionsToPorts(reactor) - endScopedBlock(code) - endScopedBlock(code) + endScopedReactorBlock(code) } /** From fbb04e44f151d8d489b01fb92e50a8306e2b9f62 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 23 Nov 2021 17:06:58 -0800 Subject: [PATCH 035/221] Got banked code to compile. Doesn't work yet. --- .../org/lflang/generator/NamedInstance.java | 16 +++ .../org/lflang/generator/ReactorInstance.java | 18 --- .../org/lflang/generator/c/CGenerator.xtend | 121 +++++++++++------- 3 files changed, 90 insertions(+), 65 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index d110eb3604..370b0c686f 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -122,6 +122,22 @@ public ReactorInstance getParent() { return parent; } + /** + * Return true if this instance has the specified parent + * (possibly indirectly, anywhere up the hierarchy). + */ + public boolean hasParent(ReactorInstance container) { + + ReactorInstance p = parent; + + while (p != null) { + if (p == container) return true; + p = p.parent; + } + return false; + } + + /** * Return a list of all the parents starting with the root(). */ diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 93f72e37c4..3f59d9cf9a 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -446,24 +446,6 @@ public boolean isMainOrFederated() { && (reactorDefinition.isMain() || reactorDefinition.isFederated()); } - /** - * Return true if this reactor is a parent - * (possibly indirect) of the specified instance. - */ - public boolean isParentOf(NamedInstance other) { - - if (other == null) return false; - - ReactorInstance otherReactor = (other instanceof ReactorInstance)? - (ReactorInstance)other : other.parent; - - while (otherReactor != null) { - if (otherReactor == this) return true; - otherReactor = otherReactor.parent; - } - return false; - } - /////////////////////////////////////////////////// //// Methods for finding instances in this reactor given an AST node. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 4c8f776ff7..982d683cab 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3158,6 +3158,9 @@ class CGenerator extends GeneratorBase { // This reaction is sending to an input. Must be // the input of a contained reactor in the federate. if (currentFederate.contains(port.parent)) { + + startScopedReactorBlock(temp, port.parent); + // If this is a multiport, then the port struct on the self // struct is a pointer. Otherwise, it is the struct itself. if (port.isMultiport) { @@ -3198,6 +3201,7 @@ class CGenerator extends GeneratorBase { } startTimeStepIsPresentCount++ } + endScopedReactorBlock(temp); } } } @@ -4663,47 +4667,60 @@ class CGenerator extends GeneratorBase { * Enclose the specified code in a potentially nested iteration over * bank indices for the specified connection from the given source to the * given destination. The code may include references to bank indices of - * containers of either the source or destination as returned by - * {@link CUtil.bankIndex(ReactorInstance)}. It may also include - * "srcChannel" and "dstChannel" to refer to the source an destination - * channel indices if those are multiports. + * parents of either the source or destination. The names of these reference + * variables are the string returned by {@link CUtil.bankIndex(ReactorInstance)}. + * These variables are also defined if the parent of either the source or + * destination (or both) is not a parent of the context argument (even + * indirectly). + * + * This returns the total number of iterations, which is never less than 1. */ - private def void encloseInBankAndMultiportIteration( + private def int encloseInBankIteration( StringBuilder builder, NamedInstance source, NamedInstance destination, - StringBuilder toEnclose + StringBuilder toEnclose, + NamedInstance context ) { + val sourceParentBanksReversed = new LinkedList(); + var result = 1; + // Start a scoped block so we can define bank index variables without // resulting in them being multiply defined. startScopedBlock(builder, null); - defineSelfStruct(builder, source.parent); - if (destination.parent != source.parent) { - defineSelfStruct(builder, destination.parent); - } + // The following define the self structs only if the parents are not a parent of context. + defineSelfStruct(builder, source.parent, context); + + if (source.parent != destination.parent) { + defineSelfStruct(builder, destination.parent, context); - val sourceParents = source.parents; - // Initialize parent bank indices and construct a reversed list. - val sourceParentBanksReversed = new LinkedList(); - for (s : sourceParents) { - if (s.isBank) { - pr(builder, ''' - int «CUtil.bankIndex(s)» = 0; - ''') - sourceParentBanksReversed.push(s); + // Initialize parent bank indices and construct a reversed list. + val sourceParents = source.parents; + for (s : sourceParents) { + if (s.isBank) { + pr(builder, ''' + int «CUtil.bankIndex(s)» = 0; + ''') + sourceParentBanksReversed.push(s); + } } } - pr(builder, "int _lf_channels_satisfied = 0;"); // Generate iterations over banks of the destination containers. + var indents = 0; for (destinationContainer : destination.parents) { if (destinationContainer.isBank) { + // Inserting a new for loop, so the total number of iterations + // becomes the previous total multiplied by the number here. + result *= destinationContainer.width; + val index = CUtil.bankIndex(destinationContainer); pr(builder, ''' // Iterate over destination bank members. for (int «index» = 0; «index» < «destinationContainer.width»; «index»++) { ''') indent(builder); + indents++; } } @@ -4713,7 +4730,6 @@ class CGenerator extends GeneratorBase { if (!sourceParentBanksReversed.isEmpty) { var s = sourceParentBanksReversed.pop(); var i = CUtil.bankIndex(s); - var indents = 1; pr(builder, ''' «i»++; if («i» >= «s.width») { @@ -4729,13 +4745,15 @@ class CGenerator extends GeneratorBase { «i» = 0; ''') } - while(indents-- > 0) { - unindent(builder); - pr(builder, "}"); - } + } + while(indents > 0) { + indents--; + unindent(builder); + pr(builder, "}"); } - endScopedBlock(builder) + endScopedBlock(builder); + return result; } /** @@ -4834,7 +4852,7 @@ class CGenerator extends GeneratorBase { private def void defineSelfStruct( StringBuilder builder, ReactorInstance reactor, NamedInstance instance ) { - if (reactor.isParentOf(instance) || reactor == main) { + if ((instance !== null && instance.hasParent(reactor)) || reactor == main) { // Assume the self struct is already in scope because the reactor // is a parent of the instance. return; @@ -4918,7 +4936,9 @@ class CGenerator extends GeneratorBase { «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(src)»; ''') } - encloseInBankAndMultiportIteration(code, src, port, temp); + // The null argument ensures that both src and port self struct + // variables are defined. + encloseInBankIteration(code, src, port, temp, null); } } } @@ -5543,6 +5563,9 @@ class CGenerator extends GeneratorBase { port.definition as TypedVariable, port.parent.definition.reactorClass ) + // The port belongs to a contained reactor, which may be a bank. + startScopedReactorBlock(code, port.parent); + if (port.isMultiport()) { pr(''' // Connect «port», which gets data from reaction «reaction.index» @@ -5558,6 +5581,7 @@ class CGenerator extends GeneratorBase { «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(port)»; ''') } + endScopedReactorBlock(code); } } } @@ -5771,20 +5795,25 @@ class CGenerator extends GeneratorBase { // Port is a multiport input that the parent's reaction is writing to. portsHandled.add(effect); - val nameOfSelfStruct = CUtil.selfRef(reactor.parent); - var containerName = CUtil.reactorRef(reactor); val portStructType = variableStructType( - effect.definition, reactor.definition.reactorClass); + effect.definition, reactor.definition.reactorClass + ); + + startScopedBlock(code, reactor); + + val effectRef = CUtil.sourceRef(effect); pr(''' - «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width = «effect.width»; + «effectRef»_width = «effect.width»; // Allocate memory to store output of reaction feeding a multiport input of a contained reactor. - «nameOfSelfStruct»->_lf_«containerName».«effect.name» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width); - for (int i = 0; i < «nameOfSelfStruct»->_lf_«containerName».«effect.name»_width; i++) { - «nameOfSelfStruct»->_lf_«containerName».«effect.name»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); + «effectRef» = («portStructType»**)malloc(sizeof(«portStructType»*) + * «effectRef»_width); + for (int i = 0; i < «effectRef»_width; i++) { + «effectRef»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); } ''') + + endScopedBlock(code); } } } @@ -5867,7 +5896,7 @@ class CGenerator extends GeneratorBase { «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); ''') - encloseInBankAndMultiportIteration(code, dominatingReaction, reaction, temp); + encloseInBankIteration(code, dominatingReaction, reaction, temp, reaction); } else { pr(''' // Reaction «reactionNumber» of «reactorInstance.getFullName» does not depend on one maximal upstream reaction. @@ -6041,13 +6070,11 @@ class CGenerator extends GeneratorBase { // so that we can simultaneously calculate the size of the total array. for (PortInstance.SendRange range : port.eventualDestinations()) { val temp = new StringBuilder(); + pr(temp, "int _lf_trigger_index = 0;"); var destRangeCount = 0; for (destinationRange : range.destinations) { val destination = destinationRange.getPortInstance(); - - // Define the destination struct pointer. - // FIXME: If the destination is a bank, need to define the bank_index variable. - defineSelfStruct(temp, destination.parent); + val temp2 = new StringBuilder(); if (destination.isOutput) { // Include this destination port only if it has at least one @@ -6059,25 +6086,25 @@ class CGenerator extends GeneratorBase { } } if (belongs) { - pr(temp, ''' + pr(temp2, ''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. - triggerArray[«destRangeCount»] = &«triggerStructName( + triggerArray[_lf_trigger_index++] = &«triggerStructName( destination, destination.parent.parent )»; ''') // One array entry for each destination range is sufficient. - destRangeCount++; + destRangeCount += encloseInBankIteration(temp, port, destination, temp2, reaction); } } else { // Destination is an input port. - pr(temp, ''' + pr(temp2, ''' // Point to destination port «destination.getFullName»'s trigger struct. - triggerArray[«destRangeCount»] = &«triggerStructName(destination)»; + triggerArray[_lf_trigger_index++] = &«triggerStructName(destination)»; ''') // One array entry for each destination range is sufficient. - destRangeCount++; + destRangeCount += encloseInBankIteration(temp, port, destination, temp2, reaction); } } From ec0dde07f15339e1faf22978d195130fd3f897dd Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 23 Nov 2021 17:24:36 -0800 Subject: [PATCH 036/221] Prefer composition + parameterization by strategies over inheritance + overriding. --- org.lflang/src/org/lflang/ASTUtils.xtend | 2 +- org.lflang/src/org/lflang/JavaAstUtils.java | 67 ------- .../org/lflang/generator/GeneratorBase.xtend | 143 +-------------- .../org/lflang/generator/ValueGenerator.java | 173 ++++++++++++++++++ .../org/lflang/generator/c/CGenerator.xtend | 44 ++--- .../src/org/lflang/generator/c/CUtil.java | 92 ++++++++++ .../generator/cpp/CppParameterGenerator.kt | 2 +- .../lflang/generator/cpp/CppStateGenerator.kt | 2 +- .../generator/python/PythonGenerator.xtend | 6 +- .../org/lflang/generator/ts/TSGenerator.kt | 24 ++- .../lflang/generator/ts/TSStateGenerator.kt | 1 - 11 files changed, 311 insertions(+), 245 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/ValueGenerator.java diff --git a/org.lflang/src/org/lflang/ASTUtils.xtend b/org.lflang/src/org/lflang/ASTUtils.xtend index 647a7d7cd0..973260b401 100644 --- a/org.lflang/src/org/lflang/ASTUtils.xtend +++ b/org.lflang/src/org/lflang/ASTUtils.xtend @@ -1292,7 +1292,7 @@ class ASTUtils { * evaluated to the extent possible given the instantiations list. * * The instantiations list is as in - * {@link initialValue(Parameter, List}. + * {@link initialValue(Parameter, List)}. * If the spec belongs to an instantiation (for a bank of reactors), * then the first element on this list should be the instantiation * that contains this instantiation. If the spec belongs to a port, diff --git a/org.lflang/src/org/lflang/JavaAstUtils.java b/org.lflang/src/org/lflang/JavaAstUtils.java index 25d313055a..5b88398449 100644 --- a/org.lflang/src/org/lflang/JavaAstUtils.java +++ b/org.lflang/src/org/lflang/JavaAstUtils.java @@ -212,71 +212,4 @@ public static String generatePortRef(VarRef reference, Integer bankIndex, Intege } return prefix + reference.getVariable().getName() + multiport; } - - /** - * Given a representation of time that may include units, return - * a string that the target language can recognize as a value. - * If units are given, e.g. "msec", then we convert the units to upper - * case and return an expression of the form "MSEC(value)". - * @param time A TimeValue that represents a time. - * @return A string, such as "MSEC(100)" for 100 milliseconds. - */ - public static String getTargetTime(TimeValue time) { - // The following apply to other methods in this section. - // FIXME: This is only used in a few code generators. Does the code reuse achieved justify the - // coupling that results from having this common dependency, in comparison to having separate - // implementations sequestered in the appropriate packages? - // FIXME: In Kotlin, this would be done more concisely in the style of AstExtensions.kt. - if (time != null) { - if (time.unit != TimeUnit.NONE) { - return time.unit.name() + '(' + time.time + ')'; - } else { - return String.valueOf(time.time); - } - } - return "0"; // FIXME: do this or throw exception? - } - - /** - * Return the time specified by {@code t}, expressed as - * code that is valid for some target languages. - */ - public static String getTargetTime(Time t) { - return getTargetTime(new TimeValue(t.getInterval(), t.getUnit())); - } - - /** - * Return the time specified by {@code d}, expressed as - * code that is valid for some target languages. - */ - public static String getTargetTime(Delay d) { - return d.getParameter() != null ? ASTUtils.toText(d) : getTargetTime( - ASTUtils.getInitialTimeValue(d.getParameter()) // The time is given as a parameter reference. - ); - } - - /** - * Return the time specified by {@code v}, expressed as - * code that is valid for some target languages. - */ - public static String getTargetTime(Value v) { - if (v.getTime() != null) return getTargetTime(v.getTime()); - if (ASTUtils.isZero(v)) return getTargetTime(new TimeValue(0, TimeUnit.NONE)); - return ASTUtils.toText(v); - } - - /** - * Get textual representation of a value in the target language. - * - * If the value evaluates to 0, it is interpreted as a normal value. - * - * @param v A time AST node - * @return A time string in the target language - */ - public static String getTargetValue(Value v) { - // FIXME: This is practically the same as getTargetTime, and it is almost always used for times - // (at least in the TypeScript generator). Perhaps it can be eliminated. - if (v.getTime() != null) return getTargetTime(v.getTime()); - return ASTUtils.toText(v); - } } diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 52e3bafce8..259e0dd61f 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -73,7 +73,6 @@ import org.lflang.lf.Reactor import org.lflang.lf.StateVar import org.lflang.lf.TimeUnit import org.lflang.lf.VarRef -import org.lflang.lf.Variable import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* @@ -811,6 +810,8 @@ abstract class GeneratorBase extends JavaGeneratorBase { * @param stderr The output on standard error of executing a command. */ def reportCommandErrors(String stderr) { + // NOTE: If the VS Code branch passes code review, then this function, + // parseCommandOutput, and ErrorFileAndLine will be deleted soon after. // First, split the message into lines. val lines = stderr.split("\\r?\\n") var message = new StringBuilder() @@ -932,146 +933,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - /** - * Create a list of default parameter initializers in target code. - * - * @param param The parameter to create initializers for - * @return A list of initializers in target code - */ - protected def getInitializerList(Parameter param) { - var list = new ArrayList(); - - for (i : param?.init) { - if (param.isOfTimeType) { - list.add(JavaAstUtils.getTargetTime(i)) - } else { - list.add(JavaAstUtils.getTargetValue(i)) - } - } - return list - } - - /** - * Create a list of state initializers in target code. - * - * @param state The state variable to create initializers for - * @return A list of initializers in target code - */ - protected def List getInitializerList(StateVar state) { - if (!state.isInitialized) { - return null - } - - var list = new ArrayList(); - - for (i : state?.init) { - if (i.parameter !== null) { - list.add(i.parameter.targetReference) - } else if (state.isOfTimeType) { - list.add(JavaAstUtils.getTargetTime(i)) - } else { - list.add(JavaAstUtils.getTargetValue(i)) - } - } - return list - } - - /** - * Create a list of parameter initializers in target code in the context - * of an reactor instantiation. - * - * This respects the parameter assignments given in the reactor - * instantiation and falls back to the reactors default initializers - * if no value is assigned to it. - * - * @param param The parameter to create initializers for - * @return A list of initializers in target code - */ - protected def getInitializerList(Parameter param, Instantiation i) { - if (i === null || param === null) { - return null - } - - val assignments = i.parameters.filter[p|p.lhs === param] - - if (assignments.size == 0) { - // the parameter was not overwritten in the instantiation - return param.initializerList - } else { - // the parameter was overwritten in the instantiation - var list = new ArrayList(); - for (init : assignments.get(0)?.rhs) { - // FIXME: This pattern of checking if it is a time and then calling - // the appropriate function is used repetitively. Factor out? - // It only seems to be necessary because of the special case where the value - // is zero, with no units. Otherwise, values would know whether or not they - // are times -- we would not need to ask their parent nodes. - if (param.isOfTimeType) { - list.add(JavaAstUtils.getTargetTime(init)) - } else { - list.add(JavaAstUtils.getTargetValue(init)) - } - } - return list - } - } - - /** - * Generate target code for a parameter reference. - * - * @param param The parameter to generate code for - * @return Parameter reference in target code - */ - protected def String getTargetReference(Parameter param) { - return param.name - } - - // // Utility functions supporting multiports. - /** - * If the argument is a multiport, return a list of strings - * describing the width of the port, and otherwise, return null. - * If the list is empty, then the width is variable (specified - * as '[]'). Otherwise, it is a list of integers and/or parameter - * references obtained by getTargetReference(). - * @param variable The port. - * @return The width specification for a multiport or null if it is - * not a multiport. - */ - protected def List multiportWidthSpec(Variable variable) { - var result = null as List - if (variable instanceof Port) { - if (variable.widthSpec !== null) { - result = new ArrayList() - if (!variable.widthSpec.ofVariableLength) { - for (term : variable.widthSpec.terms) { - if (term.parameter !== null) { - result.add(getTargetReference(term.parameter)) - } else { - result.add('' + term.width) - } - } - } - } - } - return result - } - - /** - * If the argument is a multiport, then return a string that - * gives the width as an expression, and otherwise, return null. - * The string will be empty if the width is variable (specified - * as '[]'). Otherwise, if is a single term or a sum of terms - * (separated by '+'), where each term is either an integer - * or a parameter reference in the target language. - */ - protected def String multiportWidthExpression(Variable variable) { - val spec = multiportWidthSpec(variable) - if (spec !== null) { - return spec.join(' + ') - } - return null - } - /** * Return true if the specified port is a multiport. * @param port The port. diff --git a/org.lflang/src/org/lflang/generator/ValueGenerator.java b/org.lflang/src/org/lflang/generator/ValueGenerator.java new file mode 100644 index 0000000000..9dfa59bda3 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/ValueGenerator.java @@ -0,0 +1,173 @@ +package org.lflang.generator; + +import java.util.List; +import java.util.ArrayList; +import java.util.stream.Collectors; + +import org.lflang.ASTUtils; +import org.lflang.TimeValue; +import org.lflang.lf.Assignment; +import org.lflang.lf.Delay; +import org.lflang.lf.Instantiation; +import org.lflang.lf.Parameter; +import org.lflang.lf.StateVar; +import org.lflang.lf.Time; +import org.lflang.lf.TimeUnit; +import org.lflang.lf.Value; + +/** + * Encapsulates logic for representing {@code Value}s in a + * target language. + */ +public final class ValueGenerator { + + /** + * A {@code TimeInTargetLanguage} is a + * target-language-specific time representation + * strategy. + */ + public interface TimeInTargetLanguage { + String apply(TimeValue t); + } + + /** + * A {@code GetTargetReference} is a + * target-language-specific parameter reference + * representation strategy. + */ + public interface GetTargetReference { + String apply(Parameter param); + } + + private final TimeInTargetLanguage timeInTargetLanguage; + private final GetTargetReference getTargetReference; + + /** + * Instantiates a target-language-specific + * ValueGenerator parameterized by {@code f}. + * @param f a time representation strategy + */ + public ValueGenerator(TimeInTargetLanguage f, GetTargetReference g) { + this.timeInTargetLanguage = f; + this.getTargetReference = g; + } + + /** + * Create a list of state initializers in target code. + * + * @param state The state variable to create initializers for + * @return A list of initializers in target code + */ + public List getInitializerList(StateVar state) { + List list = new ArrayList<>(); + // FIXME: Previously, we returned null if it was not initialized, which would have caused an + // NPE in TSStateGenerator. Is this the desired behavior? + if (!ASTUtils.isInitialized(state)) return list; + for (Value v : state.getInit()) { + if (v.getParameter() != null) { + list.add(getTargetReference.apply(v.getParameter())); + } else { + list.add(getTargetValue(v, ASTUtils.isOfTimeType(state))); + } + } + return list; + } + + /** + * Create a list of default parameter initializers in target code. + * + * @param param The parameter to create initializers for + * @return A list of initializers in target code + */ + public List getInitializerList(Parameter param) { + List list = new ArrayList<>(); + if (param == null) return list; + for (Value v : param.getInit()) + list.add(getTargetValue(v, ASTUtils.isOfTimeType(param))); + return list; + } + + /** + * Create a list of parameter initializers in target code in the context + * of an reactor instantiation. + * + * This respects the parameter assignments given in the reactor + * instantiation and falls back to the reactors default initializers + * if no value is assigned to it. + * + * @param param The parameter to create initializers for + * @return A list of initializers in target code + */ + public List getInitializerList(Parameter param, Instantiation i) { + List assignments = i.getParameters().stream() + .filter(it -> it.getLhs() == param) + .collect(Collectors.toList()); + if (assignments.isEmpty()) // Case 0: The parameter was not overwritten in the instantiation + return getInitializerList(param); + // Case 1: The parameter was overwritten in the instantiation + List list = new ArrayList<>(); + if (assignments.get(0) == null) return list; + for (Value init : assignments.get(0).getRhs()) + list.add(getTargetValue(init, ASTUtils.isOfTimeType(param))); + return list; + } + + /** + * Return the time specified by {@code t}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(TimeValue t) { + return timeInTargetLanguage.apply(t); + } + + /** + * Return the time specified by {@code t}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(Time t) { + return timeInTargetLanguage.apply(new TimeValue(t.getInterval(), t.getUnit())); + } + + /** + * Return the time specified by {@code d}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(Delay d) { + return d.getParameter() != null ? ASTUtils.toText(d) : timeInTargetLanguage.apply( + ASTUtils.getInitialTimeValue(d.getParameter()) // The time is given as a parameter reference. + ); + } + + /** + * Return the time specified by {@code v}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(Value v) { + return getTargetValue(v, true); + } + + /** + * Get textual representation of a value in the target language. + * + * If the value evaluates to 0, it is interpreted as a normal value. + * + * @param v A time AST node + * @return A time string in the target language + */ + public String getTargetValue(Value v) { + return getTargetValue(v, false); + } + + /** + * Get textual representation of a value in the target language. + * + * @param v A time AST node + * @param isTime Whether {@code v} is expected to be a time + * @return A time string in the target language + */ + public String getTargetValue(Value v, boolean isTime) { + if (v.getTime() != null) return getTargetTime(v.getTime()); + if (isTime && ASTUtils.isZero(v)) return timeInTargetLanguage.apply(new TimeValue(0, TimeUnit.NONE)); + return ASTUtils.toText(v); + } +} diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 982d683cab..5590ae99d5 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1376,7 +1376,7 @@ class CGenerator extends GeneratorBase { // Insert the #defines at the beginning code.insert(0, ''' #define _LF_CLOCK_SYNC_INITIAL - #define _LF_CLOCK_SYNC_PERIOD_NS «JavaAstUtils.getTargetTime(targetConfig.clockSyncOptions.period)» + #define _LF_CLOCK_SYNC_PERIOD_NS «CUtil.VG.getTargetTime(targetConfig.clockSyncOptions.period)» #define _LF_CLOCK_SYNC_EXCHANGES_PER_INTERVAL «targetConfig.clockSyncOptions.trials» #define _LF_CLOCK_SYNC_ATTENUATION «targetConfig.clockSyncOptions.attenuation» ''') @@ -1428,7 +1428,7 @@ class CGenerator extends GeneratorBase { val stp = param.init.get(0).getTimeValue if (stp !== null) { pr(''' - set_stp_offset(«JavaAstUtils.getTargetTime(stp)»); + set_stp_offset(«CUtil.VG.getTargetTime(stp)»); ''') } } @@ -1599,7 +1599,7 @@ class CGenerator extends GeneratorBase { candidate_tmp = NEVER; ''') } else { - var delayTime = JavaAstUtils.getTargetTime(delay) + var delayTime = CUtil.VG.getTargetTime(delay) pr(rtiCode, ''' if («delayTime» < candidate_tmp) { candidate_tmp = «delayTime»; @@ -3327,9 +3327,9 @@ class CGenerator extends GeneratorBase { var minDelay = action.minDelay var minSpacing = action.minSpacing pr(initializeTriggerObjects, ''' - «triggerStructName».offset = «JavaAstUtils.getTargetTime(minDelay)»; + «triggerStructName».offset = «CUtil.VG.getTargetTime(minDelay)»; «IF minSpacing !== null» - «triggerStructName».period = «JavaAstUtils.getTargetTime(minSpacing)»; + «triggerStructName».period = «CUtil.VG.getTargetTime(minSpacing)»; «ELSE» «triggerStructName».period = «CGenerator.UNDEFINED_MIN_SPACING»; «ENDIF» @@ -3352,8 +3352,8 @@ class CGenerator extends GeneratorBase { for (timer : timers) { if (!timer.isStartup) { var triggerStructName = triggerStructName(timer) - val offset = JavaAstUtils.getTargetTime(timer.offset) - val period = JavaAstUtils.getTargetTime(timer.period) + val offset = CUtil.VG.getTargetTime(timer.offset) + val period = CUtil.VG.getTargetTime(timer.period) pr(initializeTriggerObjects, ''' «triggerStructName».offset = «offset»; «triggerStructName».period = «period»; @@ -3734,7 +3734,7 @@ class CGenerator extends GeneratorBase { parameter coordination-options with a value like {advance-message-interval: 10 msec}"''') } pr(initializeTriggerObjects, ''' - _fed.min_delay_from_physical_action_to_federate_output = «JavaAstUtils.getTargetTime(minDelay)»; + _fed.min_delay_from_physical_action_to_federate_output = «CUtil.VG.getTargetTime(minDelay)»; ''') } } @@ -3884,7 +3884,7 @@ class CGenerator extends GeneratorBase { var deadline = reaction.declaredDeadline.maxDelay val reactionStructName = '''«CUtil.selfRef(reaction.parent)»->_lf__reaction_«reaction.index»''' pr(initializeTriggerObjects, ''' - «reactionStructName».deadline = «JavaAstUtils.getTargetTime(deadline)»; + «reactionStructName».deadline = «CUtil.VG.getTargetTime(deadline)»; ''') } } @@ -3966,7 +3966,7 @@ class CGenerator extends GeneratorBase { if (term.parameter !== null) { result.append(selfStruct) result.append('->') - result.append(getTargetReference(term.parameter)) + result.append(CUtil.getTargetReference(term.parameter)) } else { count += term.width } @@ -3989,9 +3989,9 @@ class CGenerator extends GeneratorBase { if (i.parameter !== null) { list.add(CUtil.selfRef(parent) + "->" + i.parameter.name) } else if (state.isOfTimeType) { - list.add(JavaAstUtils.getTargetTime(i)) + list.add(CUtil.VG.getTargetTime(i)) } else { - list.add(JavaAstUtils.getTargetValue(i)) + list.add(CUtil.VG.getTargetValue(i)) } } @@ -4364,7 +4364,7 @@ class CGenerator extends GeneratorBase { // Find the maximum STP for decentralized coordination if(isFederatedAndDecentralized) { result.append(''' - max_STP = «JavaAstUtils.getTargetTime(maxSTP)»; + max_STP = «CUtil.VG.getTargetTime(maxSTP)»; ''') } @@ -4517,7 +4517,7 @@ class CGenerator extends GeneratorBase { pr('#define TARGET_FILES_DIRECTORY "' + fileConfig.srcGenPath + '"'); if (targetConfig.coordinationOptions.advance_message_interval !== null) { - pr('#define ADVANCE_MESSAGE_INTERVAL ' + JavaAstUtils.getTargetTime(targetConfig.coordinationOptions.advance_message_interval)) + pr('#define ADVANCE_MESSAGE_INTERVAL ' + CUtil.VG.getTargetTime(targetConfig.coordinationOptions.advance_message_interval)) } includeTargetLanguageSourceFiles() @@ -5062,9 +5062,9 @@ class CGenerator extends GeneratorBase { pr(builder, ''' // Mutable multiport input, so copy the input structs // into an array of temporary variables on the stack. - «structType» _lf_tmp_«input.name»[«input.multiportWidthExpression»]; - «structType»* «input.name»[«input.multiportWidthExpression»]; - for (int i = 0; i < «input.multiportWidthExpression»; i++) { + «structType» _lf_tmp_«input.name»[«CUtil.multiportWidthExpression(input)»]; + «structType»* «input.name»[«CUtil.multiportWidthExpression(input)»]; + for (int i = 0; i < «CUtil.multiportWidthExpression(input)»; i++) { «input.name»[i] = &_lf_tmp_«input.name»[i]; _lf_tmp_«input.name»[i] = *(self->_lf_«input.name»[i]); // If necessary, copy the tokens. @@ -5091,9 +5091,9 @@ class CGenerator extends GeneratorBase { pr(builder, ''' // Mutable multiport input, so copy the input structs // into an array of temporary variables on the stack. - «structType» _lf_tmp_«input.name»[«input.multiportWidthExpression»]; - «structType»* «input.name»[«input.multiportWidthExpression»]; - for (int i = 0; i < «input.multiportWidthExpression»; i++) { + «structType» _lf_tmp_«input.name»[«CUtil.multiportWidthExpression(input)»]; + «structType»* «input.name»[«CUtil.multiportWidthExpression(input)»]; + for (int i = 0; i < «CUtil.multiportWidthExpression(input)»; i++) { «input.name»[i] = &_lf_tmp_«input.name»[i]; // Copy the struct, which includes the value. _lf_tmp_«input.name»[i] = *(self->_lf_«input.name»[i]); @@ -5447,9 +5447,9 @@ class CGenerator extends GeneratorBase { protected def String getInitializer(ParameterInstance p) { if (p.type.isList && p.init.size > 1) { - return p.init.join('{', ', ', '}', [JavaAstUtils.getTargetValue(it)]) + return p.init.join('{', ', ', '}', [CUtil.VG.getTargetValue(it)]) } else { - return JavaAstUtils.getTargetValue(p.init.get(0)) + return CUtil.VG.getTargetValue(p.init.get(0)) } } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index a9e52a5942..f332fa215d 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -26,9 +26,19 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator.c; +import java.util.ArrayList; +import java.util.List; + +import org.lflang.TimeValue; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.ValueGenerator; +import org.lflang.lf.Parameter; +import org.lflang.lf.Port; import org.lflang.lf.ReactorDecl; +import org.lflang.lf.TimeUnit; +import org.lflang.lf.Variable; +import org.lflang.lf.WidthTerm; /** * A collection of utilties for C code generation. @@ -38,6 +48,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ public class CUtil { + ////////////////////////////////////////////////////// + //// Public fields. + + public static final ValueGenerator VG = new ValueGenerator(CUtil::timeInTargetLanguage, CUtil::getTargetReference); + ////////////////////////////////////////////////////// //// Public methods. @@ -237,4 +252,81 @@ static public String sourceRef(PortInstance port) { } } } + + ////////////////////////////////////////////////////// + //// Private methods. + + /** + * Given a representation of time that may include units, return + * a string that the target language can recognize as a value. + * If units are given, e.g. "msec", then we convert the units to upper + * case and return an expression of the form "MSEC(value)". + * @param time A TimeValue that represents a time. + * @return A string, such as "MSEC(100)" for 100 milliseconds. + */ + public static String timeInTargetLanguage(TimeValue time) { + if (time != null) { + if (time.unit != TimeUnit.NONE) { + return time.unit.name() + '(' + time.time + ')'; + } else { + return String.valueOf(time.time); + } + } + return "0"; // FIXME: do this or throw exception? + } + + /** + * Generate target code for a parameter reference. + * + * @param param The parameter to generate code for + * @return Parameter reference in target code + */ + public static String getTargetReference(Parameter param) { + return param.getName(); + } + + /** + * If the argument is a multiport, then return a string that + * gives the width as an expression, and otherwise, return null. + * The string will be empty if the width is variable (specified + * as '[]'). Otherwise, if is a single term or a sum of terms + * (separated by '+'), where each term is either an integer + * or a parameter reference in the target language. + */ + public static String multiportWidthExpression(Variable variable) { + List spec = multiportWidthTerms(variable); + return spec == null ? null : String.join(" + ", spec); + } + + ////////////////////////////////////////////////////// + //// Private methods. + + /** + * If the argument is a multiport, return a list of strings + * describing the width of the port, and otherwise, return null. + * If the list is empty, then the width is variable (specified + * as '[]'). Otherwise, it is a list of integers and/or parameter + * references. + * @param variable The port. + * @return The width specification for a multiport or null if it is + * not a multiport. + */ + private static List multiportWidthTerms(Variable variable) { + List result = null; + if (variable instanceof Port) { + if (((Port) variable).getWidthSpec() != null) { + result = new ArrayList<>(); + if (!((Port) variable).getWidthSpec().isOfVariableLength()) { + for (WidthTerm term : ((Port) variable).getWidthSpec().getTerms()) { + if (term.getParameter() != null) { + result.add(getTargetReference(term.getParameter())); + } else { + result.add("" + term.getWidth()); + } + } + } + } + } + return result; + } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt index 3ce256c870..c330a83fcc 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppParameterGenerator.kt @@ -37,7 +37,7 @@ class CppParameterGenerator(private val reactor: Reactor) { /** * Create a list of initializers for the given parameter * - * TODO This is redundant to GeneratorBase.getInitializerList + * TODO This is redundant to ValueGenerator.getInitializerList */ private fun Parameter.getInitializerList() = init.map { if (isOfTimeType) it.toTime() diff --git a/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt index 1783b4fe3e..d7e62102dd 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppStateGenerator.kt @@ -36,7 +36,7 @@ class CppStateGenerator(private val reactor: Reactor) { /** * Create a list of state initializers in target code. * - * TODO This is redundant to GeneratorBase.getInitializerList + * TODO This is redundant to ValueGenerator.getInitializerList */ private fun getInitializerList(state: StateVar) = state.init.map { when { diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 99f4e8f9f1..23a56c5999 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -231,7 +231,7 @@ class PythonGenerator extends CGenerator { switch(v.toText) { case "false": returnValue = "False" case "true": returnValue = "True" - default: returnValue = JavaAstUtils.getTargetValue(v) + default: returnValue = CUtil.VG.getTargetValue(v) } // Parameters in Python are always prepended with a 'self.' @@ -259,9 +259,9 @@ class PythonGenerator extends CGenerator { for (i : state?.init) { if (i.parameter !== null) { - list.add(i.parameter.targetReference) + list.add(CUtil.getTargetReference(i.parameter)) } else if (state.isOfTimeType) { - list.add(i.targetTime) + list.add(CUtil.VG.getTargetTime(i)) } else { list.add(i.pythonTargetValue) } diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index ca482debba..9ded7934e4 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -41,6 +41,7 @@ import java.nio.file.Files import java.util.* import org.lflang.federated.serialization.SupportedSerializers import org.lflang.generator.JavaGeneratorUtils +import org.lflang.generator.ValueGenerator /** * Generator for TypeScript target. @@ -75,6 +76,17 @@ class TSGenerator( "command-line-usage.d.ts", "component.ts", "federation.ts", "reaction.ts", "reactor.ts", "microtime.d.ts", "nanotimer.d.ts", "time.ts", "ulog.d.ts", "util.ts") + + private val VG = ValueGenerator(::timeInTargetLanguage) { param -> "this.${param.name}.get()" } + + private fun timeInTargetLanguage(value: TimeValue): String { + return if (value.unit != TimeUnit.NONE) { + "TimeValue.${value.unit}(${value.time})" + } else { + // The value must be zero. + "TimeValue.zero()" + } + } } init { @@ -86,15 +98,15 @@ class TSGenerator( // Wrappers to expose GeneratorBase methods. fun federationRTIPropertiesW() = federationRTIProperties - fun getTargetValueW(v: Value): String = JavaAstUtils.getTargetValue(v) + fun getTargetValueW(v: Value): String = VG.getTargetValue(v) fun getTargetTypeW(p: Parameter): String = getTargetType(p.inferredType) fun getTargetTypeW(state: StateVar): String = getTargetType(state) fun getTargetTypeW(t: Type): String = getTargetType(t) - fun getInitializerListW(state: StateVar): List = getInitializerList(state) - fun getInitializerListW(param: Parameter): List = getInitializerList(param) + fun getInitializerListW(state: StateVar): List = VG.getInitializerList(state) + fun getInitializerListW(param: Parameter): List = VG.getInitializerList(param) fun getInitializerListW(param: Parameter, i: Instantiation): List = - getInitializerList(param, i) + VG.getInitializerList(param, i) /** Generate TypeScript code from the Lingua Franca model contained by the * specified resource. This is the main entry point for code @@ -357,10 +369,6 @@ class TSGenerator( } } - override fun getTargetReference(param: Parameter): String { - return "this.${param.name}.get()" - } - /** * Generate code for the body of a reaction that handles the * action that is triggered by receiving a message from a remote diff --git a/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt index e662aeb799..58ed7d4eee 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSStateGenerator.kt @@ -1,7 +1,6 @@ package org.lflang.generator.ts import org.lflang.ASTUtils -import org.lflang.generator.PrependOperator import org.lflang.lf.StateVar import java.util.* From 5351569c335757d86822223ae6e279e464f3461f Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 24 Nov 2021 08:04:52 -0800 Subject: [PATCH 037/221] Eliminated unnecessary extra array on the stack for banks. --- .../org/lflang/generator/c/CGenerator.xtend | 66 +++++++------------ .../src/org/lflang/generator/c/CUtil.java | 19 ++++-- .../generator/python/PythonGenerator.xtend | 2 +- 3 files changed, 37 insertions(+), 50 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 5590ae99d5..d8591005cc 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -2140,7 +2140,7 @@ class CGenerator extends GeneratorBase { ''') } } else { - // Must be an output entry. + // Must be an output port. // Outputs of contained reactors are pointers to the source of data on the // self struct of the container. if (!port.isMultiport) { @@ -2168,9 +2168,11 @@ class CGenerator extends GeneratorBase { ''') indent(constructorCode) } + val portOnSelf = '''self->_lf_«containedReactor.name»«reactorIndex».«port.name»''' + if (isFederatedAndDecentralized) { pr(port, constructorCode, ''' - self->_lf_«containedReactor.name»«reactorIndex».«port.name»_trigger.intended_tag = (tag_t) { .time = NEVER, .microstep = 0u}; + «portOnSelf»_trigger.intended_tag = (tag_t) { .time = NEVER, .microstep = 0u}; ''') } val triggered = contained.reactionsTriggered(containedReactor, port) @@ -2181,11 +2183,11 @@ class CGenerator extends GeneratorBase { var triggeredCount = 0 for (index : triggered) { pr(port, constructorCode, ''' - self->_lf_«containedReactor.name»«reactorIndex».«port.name»_reactions[«triggeredCount++»] = &self->_lf__reaction_«index»; + «portOnSelf»_reactions[«triggeredCount++»] = &self->_lf__reaction_«index»; ''') } pr(port, constructorCode, ''' - self->_lf_«containedReactor.name»«reactorIndex».«port.name»_trigger.reactions = self->_lf_«containedReactor.name»«reactorIndex».«port.name»_reactions; + «portOnSelf»_trigger.reactions = «portOnSelf»_reactions; ''') } else { // Since the self struct is created using calloc, there is no need to set @@ -2200,14 +2202,14 @@ class CGenerator extends GeneratorBase { // self->_lf_«containedReactor.name».«port.name»_trigger.element_size = 0; // self->_lf_«containedReactor.name».«port.name»_trigger.intended_tag = (0, 0); pr(port, constructorCode, ''' - self->_lf_«containedReactor.name»«reactorIndex».«port.name»_trigger.last = NULL; - self->_lf_«containedReactor.name»«reactorIndex».«port.name»_trigger.number_of_reactions = «triggered.size»; + «portOnSelf»_trigger.last = NULL; + «portOnSelf»_trigger.number_of_reactions = «triggered.size»; ''') if (isFederated) { // Set the physical_time_of_arrival pr(port, constructorCode, ''' - self->_lf_«containedReactor.name»«reactorIndex».«port.name»_trigger.physical_time_of_arrival = NEVER; + «portOnSelf»_trigger.physical_time_of_arrival = NEVER; ''') } if (containedReactor.widthSpec !== null) { @@ -2664,7 +2666,7 @@ class CGenerator extends GeneratorBase { // trigger downstream reactions. for (reaction : reactions) { val instance = reaction.parent; - val nameOfSelfStruct = CUtil.selfRef(instance) + val nameOfSelfStruct = CUtil.selfName(instance) // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. @@ -3323,7 +3325,7 @@ class CGenerator extends GeneratorBase { private def generateActionInitializations(Iterable actions) { for (action : actions) { if (!action.isShutdown) { - var triggerStructName = triggerStructName(action) + val triggerStructName = CUtil.selfName(action.parent) + "->_lf__" + action.name; var minDelay = action.minDelay var minSpacing = action.minSpacing pr(initializeTriggerObjects, ''' @@ -3351,7 +3353,7 @@ class CGenerator extends GeneratorBase { private def generateTimerInitializations(Iterable timers) { for (timer : timers) { if (!timer.isStartup) { - var triggerStructName = triggerStructName(timer) + val triggerStructName = CUtil.selfName(timer.parent) + "->_lf__" + timer.name; val offset = CUtil.VG.getTargetTime(timer.offset) val period = CUtil.VG.getTargetTime(timer.period) pr(initializeTriggerObjects, ''' @@ -3518,7 +3520,7 @@ class CGenerator extends GeneratorBase { // the header information in the trace file. if (targetConfig.tracing !== null) { var description = getShortenedName(instance) - var nameOfSelfStruct = CUtil.selfRef(instance) + var nameOfSelfStruct = CUtil.selfName(instance) pr(initializeTriggerObjects, ''' _lf_register_trace_event(«nameOfSelfStruct», NULL, trace_reactor, "«description»"); ''') @@ -3565,8 +3567,8 @@ class CGenerator extends GeneratorBase { // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' - «CUtil.selfType(main)»* «CUtil.selfRef(main)» = new_«main.name»(); - selfStructs[0] = «CUtil.selfRef(main)»; + «CUtil.selfType(main)»* «CUtil.selfName(main)» = new_«main.name»(); + selfStructs[0] = «CUtil.selfName(main)»; ''') // Generate code for top-level parameters, actions, timers, and reactions that @@ -3608,37 +3610,20 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, '// ************* Instance ' + fullName + ' of class ' + reactorClass.name) - var nameOfSelfStruct = CUtil.selfRef(instance) + var nameOfSelfStruct = CUtil.selfName(instance) var structType = CUtil.selfType(reactorClass) // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. - if (instance.isBank) { - // Need an extra layer of scoping for the array of self structs. - startScopedBlock(initializeTriggerObjects, null) - - // Array is the self struct name, but without the indexing. - var selfStructArrayName = instance.uniqueID + "_self" - - pr(initializeTriggerObjects, ''' - «structType»* «selfStructArrayName»[«instance.width»]; - // Create bank members. - ''') - } startScopedBlock(initializeTriggerObjects, instance); // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). The form is slightly different // depending on whether its in a bank of reactors. - if (instance.isBank) { - pr(initializeTriggerObjects, ''' - «nameOfSelfStruct» = new_«reactorClass.name»(); - ''') - } else { - pr(initializeTriggerObjects, ''' - «structType»* «nameOfSelfStruct» = new_«reactorClass.name»(); - ''') - } + pr(initializeTriggerObjects, ''' + «structType»* «nameOfSelfStruct» = new_«reactorClass.name»(); + ''') + // Record the self struct on the big array of self structs. pr(initializeTriggerObjects, ''' selfStructs[«CUtil.indexExpression(instance)»] = «nameOfSelfStruct»; @@ -3748,9 +3733,6 @@ class CGenerator extends GeneratorBase { endScopedBlock(initializeTriggerObjects); - if (instance.isBank) { - endScopedBlock(initializeTriggerObjects); - } pr(initializeTriggerObjects, "//***** End initializing " + fullName) } @@ -3780,7 +3762,7 @@ class CGenerator extends GeneratorBase { } } - var nameOfSelfStruct = CUtil.selfRef(action.parent); + var nameOfSelfStruct = CUtil.selfName(action.parent); // Create a reference token initialized to the payload size. // This token is marked to not be freed so that the trigger_t struct @@ -3831,7 +3813,7 @@ class CGenerator extends GeneratorBase { */ def generateStateVariableInitializations(ReactorInstance instance) { val reactorClass = instance.definition.reactorClass - val nameOfSelfStruct = CUtil.selfRef(instance) + val nameOfSelfStruct = CUtil.selfName(instance) for (stateVar : reactorClass.toDefinition.stateVars) { val initializer = getInitializer(stateVar, instance) @@ -3882,7 +3864,7 @@ class CGenerator extends GeneratorBase { for (reaction : reactions) { if (reaction.declaredDeadline !== null) { var deadline = reaction.declaredDeadline.maxDelay - val reactionStructName = '''«CUtil.selfRef(reaction.parent)»->_lf__reaction_«reaction.index»''' + val reactionStructName = '''«CUtil.selfName(reaction.parent)»->_lf__reaction_«reaction.index»''' pr(initializeTriggerObjects, ''' «reactionStructName».deadline = «CUtil.VG.getTargetTime(deadline)»; ''') @@ -3895,7 +3877,7 @@ class CGenerator extends GeneratorBase { * @param instance The reactor instance. */ def void generateParameterInitialization(ReactorInstance instance) { - var nameOfSelfStruct = CUtil.selfRef(instance) + var nameOfSelfStruct = CUtil.selfName(instance) // Array type parameters have to be handled specially. // Use the superclass getTargetType to avoid replacing the [] with *. for (parameter : instance.parameters) { diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index f332fa215d..11eef3c8b9 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -171,22 +171,27 @@ static public String reactorRef(ReactorInstance instance) { } } + /** + * Return the unique name for the "self" struct of the specified + * reactor instance. + * @param instance The reactor instance. + * @return A name for the self struct. + */ + static public String selfName(ReactorInstance instance) { + return instance.uniqueID() + "_self"; + } + /** * Return the unique reference for the "self" struct of the specified * reactor instance. If the instance is a bank of reactors, this returns * something of the form name_self[bankIndex], where bankIndex is the - * returned by {@link bankIndex(ReactorInstance)}. This assumes that the resulting string - * will be used in a context that defines a variable with name bankIndex. The result has - * one of the following forms: - * - * * uniqueID - * * uniqueID[bankIndex] + * returned by {@link bankIndex(ReactorInstance)}. * * @param instance The reactor instance. * @return A reference to the self struct. */ static public String selfRef(ReactorInstance instance) { - var result = instance.uniqueID() + "_self"; + var result = selfName(instance); // If this reactor is a member of a bank of reactors, then change // the name of its self struct to append [index]. if (instance.isBank()) { diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 23a56c5999..37a8a6a9de 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1664,7 +1664,7 @@ class PythonGenerator extends CGenerator { override void generateReactorInstanceExtension( ReactorInstance instance, Iterable reactions ) { - var nameOfSelfStruct = CUtil.selfRef(instance) + var nameOfSelfStruct = CUtil.selfName(instance) var reactor = instance.definition.reactorClass.toDefinition // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C From 2530232da7999cc40608dc855986f47e5f639a59 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 24 Nov 2021 12:11:11 -0800 Subject: [PATCH 038/221] Factor more out of GeneratorBase. --- org.lflang/src/org/lflang/AstExtensions.kt | 4 +- org.lflang/src/org/lflang/JavaAstUtils.java | 9 ++++ .../lflang/federated/CGeneratorExtension.java | 3 +- .../org/lflang/generator/GeneratorBase.xtend | 39 +++----------- .../lflang/generator/JavaGeneratorUtils.java | 14 +++++ .../org/lflang/generator/PortInstance.java | 4 +- .../org/lflang/generator/c/CGenerator.xtend | 52 +++++++++---------- .../generator/python/PythonGenerator.xtend | 28 +++++----- 8 files changed, 77 insertions(+), 76 deletions(-) diff --git a/org.lflang/src/org/lflang/AstExtensions.kt b/org.lflang/src/org/lflang/AstExtensions.kt index 3840823b6a..447b9a4914 100644 --- a/org.lflang/src/org/lflang/AstExtensions.kt +++ b/org.lflang/src/org/lflang/AstExtensions.kt @@ -24,7 +24,6 @@ package org.lflang -import org.eclipse.emf.common.util.EList import org.eclipse.emf.ecore.EObject import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.nodemodel.util.NodeModelUtils @@ -369,9 +368,8 @@ val Action.isPhysical get() = this.origin == ActionOrigin.PHYSICAL /** * Return true if the receiving is a multiport. - * FIXME This is a duplicate of GeneratorBase.isMultiport */ -val Port.isMultiport get() = this.widthSpec != null +val Port.isMultiport get() = JavaAstUtils.isMultiport(this) /** Get the reactor that is instantiated in the receiving instantiation. */ val Instantiation.reactor get() = this.reactorClass.toDefinition() diff --git a/org.lflang/src/org/lflang/JavaAstUtils.java b/org.lflang/src/org/lflang/JavaAstUtils.java index 5b88398449..ba92a808f4 100644 --- a/org.lflang/src/org/lflang/JavaAstUtils.java +++ b/org.lflang/src/org/lflang/JavaAstUtils.java @@ -166,6 +166,15 @@ public static String addZeroToLeadingDot(String literal) { return literal; } + /** + * Return true if the specified port is a multiport. + * @param port The port. + * @return True if the port is a multiport. + */ + public static boolean isMultiport(Port port) { + return port.getWidthSpec() != null; + } + //////////////////////////////// //// Utility functions for translating AST nodes into text // This is a continuation of a large section of ASTUtils.xtend diff --git a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java index 6300ad0e0c..74249e5441 100644 --- a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java @@ -27,6 +27,7 @@ package org.lflang.federated; import org.lflang.ASTUtils; +import org.lflang.JavaAstUtils; import org.lflang.TimeValue; import org.lflang.generator.ReactorInstance; import org.lflang.generator.c.CGenerator; @@ -194,7 +195,7 @@ public static String createPortStatusFieldForInput(Input input, CGenerator generator) { StringBuilder builder = new StringBuilder(); // Check if the port is a multiport - if (generator.isMultiport(input)) { + if (JavaAstUtils.isMultiport(input)) { // If it is a multiport, then create an auxiliary list of port // triggers for each channel of // the multiport to keep track of the status of each channel diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 259e0dd61f..31bede6243 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -1,5 +1,3 @@ -/* Generator base class for shared code between code generators. */ - /************* * Copyright (c) 2019-2020, The University of California at Berkeley. @@ -27,7 +25,6 @@ package org.lflang.generator import java.io.File -import java.io.FileOutputStream import java.nio.file.Files import java.nio.file.Paths import java.util.ArrayList @@ -47,7 +44,6 @@ import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGeneratorContext import org.eclipse.xtext.util.CancelIndicator import org.lflang.ASTUtils -import org.lflang.JavaAstUtils import org.lflang.ErrorReporter import org.lflang.FileConfig import org.lflang.InferredType @@ -78,8 +74,9 @@ import static extension org.lflang.ASTUtils.* import static extension org.lflang.JavaAstUtils.* /** - * Generator base class for shared code between code generators. - * + * Generator base class for specifying core functionality + * that all code generators should have. + * * @author{Edward A. Lee } * @author{Marten Lohstroh } * @author{Christian Menard { /** * Create a runtime instance from the specified definition * and with the specified parent that instantiated it. - * @param instance The Instance statement in the AST. + * @param definition The Instance statement in the AST. * @param parent The parent. */ public PortInstance(Port definition, ReactorInstance parent) { @@ -62,7 +62,7 @@ public PortInstance(Port definition, ReactorInstance parent) { /** * Create a port instance from the specified definition * and with the specified parent that instantiated it. - * @param instance The Instance statement in the AST. + * @param definition The Instance statement in the AST. * @param parent The parent. * @param errorReporter An error reporter, or null to throw exceptions. */ diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d8591005cc..fd5d1c0606 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -831,15 +831,15 @@ class CGenerator extends GeneratorBase { } } val targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename - writeSourceCodeToFile(getCode().getBytes(), targetFile) + JavaGeneratorUtils.writeSourceCodeToFile(getCode().getBytes(), targetFile) if (targetConfig.useCmake) { // If cmake is requested, generated the CMakeLists.txt val cmakeGenerator = new CCmakeGenerator(targetConfig, fileConfig) val cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt" - writeSourceCodeToFile( - cmakeGenerator.generateCMakeCode( + JavaGeneratorUtils.writeSourceCodeToFile( + cmakeGenerator.generateCMakeCode( #[cFilename], topLevelName, errorReporter, @@ -882,7 +882,7 @@ class CGenerator extends GeneratorBase { // If compilation failed, remove any bin files that may have been created. threadFileConfig.deleteBinFiles() } - writeSourceCodeToFile(cleanCode, targetFile) + JavaGeneratorUtils.writeSourceCodeToFile(cleanCode, targetFile) } }); } @@ -1303,7 +1303,7 @@ class CGenerator extends GeneratorBase { # Use ENTRYPOINT not CMD so that command-line arguments go through ENTRYPOINT ["./bin/«topLevelName»"] ''') - writeSourceCodeToFile(contents.toString.getBytes, dockerFile) + JavaGeneratorUtils.writeSourceCodeToFile(contents.toString.getBytes, dockerFile) println('''Dockerfile for «topLevelName» written to ''' + dockerFile) println(''' ##################################### @@ -1349,7 +1349,7 @@ class CGenerator extends GeneratorBase { # Use ENTRYPOINT not CMD so that command-line arguments go through ENTRYPOINT ["./build/RTI"] ''') - writeSourceCodeToFile(contents.toString.getBytes, dockerFile) + JavaGeneratorUtils.writeSourceCodeToFile(contents.toString.getBytes, dockerFile) println("Dockerfile for RTI written to " + dockerFile) println(''' ##################################### @@ -1981,7 +1981,7 @@ class CGenerator extends GeneratorBase { // pointers that will be allocated separately for each instance // because the sizes may be different. Otherwise, it is a simple // pointer. - if (input.isMultiport) { + if (JavaAstUtils.isMultiport(input)) { pr(input, body, ''' // Multiport input array will be malloc'd later. «variableStructType(input, decl)»** _lf_«input.name»; @@ -2016,7 +2016,7 @@ class CGenerator extends GeneratorBase { if (federate === null || federate.contains(output as Port)) { // If the port is a multiport, create an array to be allocated // at instantiation. - if (output.isMultiport) { + if (JavaAstUtils.isMultiport(output)) { pr(output, body, ''' // Array of output ports. «variableStructType(output, decl)»* _lf_«output.name»; @@ -2126,7 +2126,7 @@ class CGenerator extends GeneratorBase { if (port instanceof Input) { // If the variable is a multiport, then the place to store the data has // to be malloc'd at initialization. - if (!port.isMultiport) { + if (!JavaAstUtils.isMultiport(port)) { // Not a multiport. pr(port, body, ''' «variableStructType(port, containedReactor.reactorClass)» «port.name»; @@ -2143,7 +2143,7 @@ class CGenerator extends GeneratorBase { // Must be an output port. // Outputs of contained reactors are pointers to the source of data on the // self struct of the container. - if (!port.isMultiport) { + if (!JavaAstUtils.isMultiport(port)) { // Not a multiport. pr(port, body, ''' «variableStructType(port, containedReactor.reactorClass)»* «port.name»; @@ -2217,7 +2217,7 @@ class CGenerator extends GeneratorBase { pr(constructorCode, "}") } } - if (port.isMultiport) { + if (JavaAstUtils.isMultiport(port)) { // Add to the destructor code to free the malloc'd memory. if (containedReactor.widthSpec !== null) { pr(port, destructorCode, ''' @@ -2733,7 +2733,7 @@ class CGenerator extends GeneratorBase { if (inputTrigger.variable instanceof Output) { // Output from a contained reactor val outputPort = inputTrigger.variable as Output - if (outputPort.isMultiport) { + if (JavaAstUtils.isMultiport(outputPort)) { pr(intendedTagInheritenceCode, ''' for (int i=0; i < «inputTrigger.container.name».«inputTrigger.variable.name»_width; i++) { if (compare_tags(«inputTrigger.container.name».«inputTrigger.variable.name»[i]->intended_tag, @@ -2753,7 +2753,7 @@ class CGenerator extends GeneratorBase { } else if (inputTrigger.variable instanceof Port) { // Input port val inputPort = inputTrigger.variable as Port - if (inputPort.isMultiport) { + if (JavaAstUtils.isMultiport(inputPort)) { pr(intendedTagInheritenceCode, ''' for (int i=0; i < «inputTrigger.variable.name»_width; i++) { if (compare_tags(«inputTrigger.variable.name»[i]->intended_tag, inherited_min_intended_tag) < 0) { @@ -2804,7 +2804,7 @@ class CGenerator extends GeneratorBase { indent(intendedTagInheritenceCode); for (effect : reaction.effects ?: emptyList) { if (effect.variable instanceof Input) { - if ((effect.variable as Port).isMultiport) { + if (JavaAstUtils.isMultiport(effect.variable as Port)) { pr(intendedTagInheritenceCode, ''' for(int i=0; i < «effect.container.name».«effect.variable.name»_width; i++) { «effect.container.name».«effect.variable.name»[i]->intended_tag = inherited_min_intended_tag; @@ -3642,7 +3642,7 @@ class CGenerator extends GeneratorBase { // NOTE: Not done for top level. for (output : reactorClass.toDefinition.outputs) { // If the port is a multiport, create an array. - if (output.isMultiport) { + if (JavaAstUtils.isMultiport(output)) { initializeOutputMultiport(initializeTriggerObjects, output, nameOfSelfStruct, instance) } else { pr(initializeTriggerObjects, ''' @@ -3658,7 +3658,7 @@ class CGenerator extends GeneratorBase { // NOTE: Not done for top level. for (input : reactorClass.toDefinition.inputs) { // If the port is a multiport, create an array. - if (input.isMultiport) { + if (JavaAstUtils.isMultiport(input)) { pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->_lf_«input.name»_width = «multiportWidthSpecInC(input, null, instance)»; // Allocate memory for multiport inputs. @@ -4987,12 +4987,12 @@ class CGenerator extends GeneratorBase { // depending on whether the input is mutable, whether it is a multiport, // and whether it is a token type. // Easy case first. - if (!input.isMutable && !inputType.isTokenType && !input.isMultiport) { + if (!input.isMutable && !inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { // Non-mutable, non-multiport, primitive type. pr(builder, ''' «structType»* «input.name» = self->_lf_«input.name»; ''') - } else if (input.isMutable && !inputType.isTokenType && !input.isMultiport) { + } else if (input.isMutable && !inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { // Mutable, non-multiport, primitive type. pr(builder, ''' // Mutable input, so copy the input into a temporary variable. @@ -5000,7 +5000,7 @@ class CGenerator extends GeneratorBase { «structType» _lf_tmp_«input.name» = *(self->_lf_«input.name»); «structType»* «input.name» = &_lf_tmp_«input.name»; ''') - } else if (!input.isMutable && inputType.isTokenType && !input.isMultiport) { + } else if (!input.isMutable && inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { // Non-mutable, non-multiport, token type. pr(builder, ''' «structType»* «input.name» = self->_lf_«input.name»; @@ -5011,7 +5011,7 @@ class CGenerator extends GeneratorBase { «input.name»->length = 0; } ''') - } else if (input.isMutable && inputType.isTokenType && !input.isMultiport) { + } else if (input.isMutable && inputType.isTokenType && !JavaAstUtils.isMultiport(input)) { // Mutable, non-multiport, token type. pr(builder, ''' // Mutable input, so copy the input struct into a temporary variable. @@ -5034,7 +5034,7 @@ class CGenerator extends GeneratorBase { «input.name»->length = 0; } ''') - } else if (!input.isMutable && input.isMultiport) { + } else if (!input.isMutable && JavaAstUtils.isMultiport(input)) { // Non-mutable, multiport, primitive or token type. pr(builder, ''' «structType»** «input.name» = self->_lf_«input.name»; @@ -5125,7 +5125,7 @@ class CGenerator extends GeneratorBase { val reactorName = port.container.name // First define the struct containing the output value and indicator // of its presence. - if (!output.isMultiport) { + if (!JavaAstUtils.isMultiport(output)) { // Output is not a multiport. pr(structBuilder, ''' «portStructType»* «output.name»; @@ -5146,7 +5146,7 @@ class CGenerator extends GeneratorBase { «reactorName»[i].«output.name» = self->_lf_«reactorName»[i].«output.name»; } ''') - if (output.isMultiport) { + if (JavaAstUtils.isMultiport(output)) { pr(builder, ''' for (int i = 0; i < «port.container.name»_width; i++) { «reactorName»[i].«output.name»_width = self->_lf_«reactorName»[i].«output.name»_width; @@ -5158,7 +5158,7 @@ class CGenerator extends GeneratorBase { pr(builder, ''' «reactorName».«output.name» = self->_lf_«reactorName».«output.name»; ''') - if (output.isMultiport) { + if (JavaAstUtils.isMultiport(output)) { pr(builder, ''' «reactorName».«output.name»_width = self->_lf_«reactorName».«output.name»_width; ''') @@ -5190,7 +5190,7 @@ class CGenerator extends GeneratorBase { variableStructType(output, decl) : variableStructType(output, effect.container.reactorClass) - if (!output.isMultiport) { + if (!JavaAstUtils.isMultiport(output)) { // Output port is not a multiport. pr(builder, ''' «outputStructType»* «output.name» = &self->_lf_«output.name»; @@ -5234,7 +5234,7 @@ class CGenerator extends GeneratorBase { structs.put(definition, structBuilder) } val inputStructType = variableStructType(input, definition.reactorClass) - if (!input.isMultiport) { + if (!JavaAstUtils.isMultiport(input)) { // Contained reactor's input is not a multiport. pr(structBuilder, ''' «inputStructType»* «input.name»; diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 37a8a6a9de..5af4842616 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -48,6 +48,7 @@ import org.lflang.federated.PythonGeneratorExtension import org.lflang.federated.launcher.FedPyLauncher import org.lflang.federated.serialization.FedNativePythonSerialization import org.lflang.federated.serialization.SupportedSerializers +import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.ParameterInstance import org.lflang.generator.ReactionInstance import org.lflang.generator.ReactorInstance @@ -341,7 +342,7 @@ class PythonGenerator extends CGenerator { generatedParams.add('''mutable_«trigger.variable.name»''') // Create a deep copy - if ((trigger.variable as Input).isMultiport) { + if (JavaAstUtils.isMultiport(trigger.variable as Input)) { inits. append('''«trigger.variable.name» = [Make() for i in range(len(mutable_«trigger.variable.name»))] ''') @@ -419,7 +420,7 @@ class PythonGenerator extends CGenerator { } else { generatedParams.add(effect.variable.name) if (effect.variable instanceof Port) { - if (isMultiport(effect.variable as Port)) { + if (JavaAstUtils.isMultiport(effect.variable as Port)) { // Handle multiports } } @@ -818,7 +819,7 @@ class PythonGenerator extends CGenerator { if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } - writeSourceCodeToFile(generatePythonCode(federate).toString.bytes, file.absolutePath) + JavaGeneratorUtils.writeSourceCodeToFile(generatePythonCode(federate).toString.bytes, file.absolutePath) val setupPath = fileConfig.getSrcGenPath.resolve("setup.py") // Handle Python setup @@ -830,7 +831,7 @@ class PythonGenerator extends CGenerator { } // Create the setup file - writeSourceCodeToFile(generatePythonSetupFile.toString.bytes, setupPath.toString) + JavaGeneratorUtils.writeSourceCodeToFile(generatePythonSetupFile.toString.bytes, setupPath.toString) } @@ -1774,7 +1775,7 @@ class PythonGenerator extends CGenerator { } else { - if(!(port.variable as Port).isMultiport) + if(!JavaAstUtils.isMultiport(port.variable as Port)) { pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«port.container.name».«port.variable.name», -2)''') @@ -1808,10 +1809,13 @@ class PythonGenerator extends CGenerator { // but what we have on the self struct is an array of output structs. // So we have to handle multiports specially here a construct that // array of pointers. - if (!output.isMultiport) { + // FIXME: The C Generator also has this awkwardness. It makes the code generators + // unnecessarily difficult to maintain, and it may have performance consequences as well. + // Maybe we should change the SET macros. + if (!JavaAstUtils.isMultiport(output)) { pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«output.name», -2)''') - } else if (output.isMultiport) { + } else { // Set the _width variable. pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«output.name»,«output.name»_width) ''') @@ -1833,7 +1837,7 @@ class PythonGenerator extends CGenerator { ReactorDecl decl ) { - if(input.isMultiport) + if(JavaAstUtils.isMultiport(input)) { pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«definition.name».«input.name», «definition.name».«input.name»_width)''') @@ -1869,16 +1873,16 @@ class PythonGenerator extends CGenerator { // depending on whether the input is mutable, whether it is a multiport, // and whether it is a token type. // Easy case first. - if (!input.isMutable && !input.isMultiport) { + if (!input.isMutable && !JavaAstUtils.isMultiport(input)) { // Non-mutable, non-multiport, primitive type. pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«input.name», «input.name»_width)''') - } else if (input.isMutable && !input.isMultiport) { + } else if (input.isMutable && !JavaAstUtils.isMultiport(input)) { // Mutable, non-multiport, primitive type. // TODO: handle mutable pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«input.name», «input.name»_width)''') - } else if (!input.isMutable && input.isMultiport) { + } else if (!input.isMutable && JavaAstUtils.isMultiport(input)) { // Non-mutable, multiport, primitive. // TODO: support multiports pyObjectDescriptor.append("O") @@ -1924,7 +1928,7 @@ class PythonGenerator extends CGenerator { && apk del gcc musl-dev ENTRYPOINT ["python3", "src-gen/«filename».py"] ''') - writeSourceCodeToFile(contents.toString.getBytes, dockerFile) + JavaGeneratorUtils.writeSourceCodeToFile(contents.toString.getBytes, dockerFile) println("Dockerfile written to " + dockerFile) println(''' ##################################### From 9e40c4bf89eb761e15798f25212b5c0a49c3ba81 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 24 Nov 2021 16:16:21 -0800 Subject: [PATCH 039/221] Delete unused method. --- .../org/lflang/generator/GeneratorBase.xtend | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 31bede6243..116532af0b 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -932,27 +932,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { // ////////////////////////////////////////////////// // // Private functions - /** - * Get textual representation of a time in the target language - * in an RTI-compatible form. - * - * @param d A time AST node - * @return An RTI-compatible (ie. C target) time string - */ - protected def getRTITime(Delay d) { - var TimeValue time - if (d.parameter !== null) { - return d.toText - } - - time = new TimeValue(d.interval, d.unit) - - if (time.unit != TimeUnit.NONE) { - return time.unit.name() + '(' + time.time + ')' - } else { - return time.time.toString() - } - } /** * Remove triggers in each federates' network reactions that are defined in remote federates. From 4268d17ead3418f8fc04f48b36693aefe53c2988 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 24 Nov 2021 18:59:58 -0800 Subject: [PATCH 040/221] Factor out runBuildCommand. --- .../org/lflang/generator/GeneratorBase.xtend | 48 ----------- .../org/lflang/generator/c/CGenerator.xtend | 4 +- .../src/org/lflang/generator/c/CUtil.java | 80 ++++++++++++++++++- 3 files changed, 81 insertions(+), 51 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 116532af0b..946df54eb4 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -529,54 +529,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { return reactionBankIndices.get(reaction) } - /** - * Run the custom build command specified with the "build" parameter. - * This command is executed in the same directory as the source file. - * - * The following environment variables will be available to the command: - * - * * LF_CURRENT_WORKING_DIRECTORY: The directory in which the command is invoked. - * * LF_SOURCE_DIRECTORY: The directory containing the .lf file being compiled. - * * LF_SOURCE_GEN_DIRECTORY: The directory in which generated files are placed. - * * LF_BIN_DIRECTORY: The directory into which to put binaries. - * - */ - protected def runBuildCommand() { - var commands = new ArrayList - for (cmd : targetConfig.buildCommands) { - val tokens = newArrayList(cmd.split("\\s+")) - if (tokens.size > 0) { - val buildCommand = commandFactory.createCommand( - tokens.head, - tokens.tail.toList, - this.fileConfig.srcPath - ) - // If the build command could not be found, abort. - // An error has already been reported in createCommand. - if (buildCommand === null) { - return - } - commands.add(buildCommand) - } - } - - for (cmd : commands) { - // execute the command - val returnCode = cmd.run() - - if (returnCode != 0 && fileConfig.compilerMode !== Mode.INTEGRATED) { - errorReporter.reportError('''Build command "«targetConfig.buildCommands»" returns error code «returnCode»''') - return - } - // For warnings (vs. errors), the return code is 0. - // But we still want to mark the IDE. - if (cmd.errors.toString.length > 0 && fileConfig.compilerMode === Mode.INTEGRATED) { - reportCommandErrors(cmd.errors.toString()) - return - } - } - } - // ////////////////////////////////////////// // // Protected methods. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index fd5d1c0606..65f366115f 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -903,7 +903,9 @@ class CGenerator extends GeneratorBase { // Note that the code does not get cleaned in this case. if (!targetConfig.noCompile) { if (!targetConfig.buildCommands.nullOrEmpty) { - runBuildCommand() + CUtil.runBuildCommand( + fileConfig, targetConfig, commandFactory, errorReporter, [it | reportCommandErrors(it)] + ) } } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 11eef3c8b9..498db23a65 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -26,10 +26,18 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator.c; +import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import org.lflang.ErrorReporter; +import org.lflang.FileConfig; +import org.lflang.TargetConfig; +import org.lflang.TargetConfig.Mode; import org.lflang.TimeValue; +import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.ValueGenerator; @@ -39,6 +47,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.TimeUnit; import org.lflang.lf.Variable; import org.lflang.lf.WidthTerm; +import org.lflang.util.LFCommand; /** * A collection of utilties for C code generation. @@ -258,8 +267,75 @@ static public String sourceRef(PortInstance port) { } } - ////////////////////////////////////////////////////// - //// Private methods. + /** + * FIXME: The following functional interface is throwaway + * code, intended only to be used while the C Generator + * undergoes a transition period. + */ + public interface ReportCommandErrors { + void report(String errors); + } + + /** + * Run the custom build command specified with the "build" parameter. + * This command is executed in the same directory as the source file. + * + * The following environment variables will be available to the command: + * + * * LF_CURRENT_WORKING_DIRECTORY: The directory in which the command is invoked. + * * LF_SOURCE_DIRECTORY: The directory containing the .lf file being compiled. + * * LF_SOURCE_GEN_DIRECTORY: The directory in which generated files are placed. + * * LF_BIN_DIRECTORY: The directory into which to put binaries. + * + */ + public static void runBuildCommand( + FileConfig fileConfig, + TargetConfig targetConfig, + GeneratorCommandFactory commandFactory, + ErrorReporter errorReporter, + ReportCommandErrors reportCommandErrors + ) { + List commands = getCommands(targetConfig.buildCommands, commandFactory, fileConfig.srcPath); + // If the build command could not be found, abort. + // An error has already been reported in createCommand. + if (commands.stream().anyMatch(Objects::isNull)) return; + + for (LFCommand cmd : commands) { + int returnCode = cmd.run(); + if (returnCode != 0 && fileConfig.getCompilerMode() != Mode.INTEGRATED) { + errorReporter.reportError(String.format( + // FIXME: Why is the content of stderr not provided to the user in this error message? + "Build command \"%s\" failed with error code %d.", + targetConfig.buildCommands, returnCode + )); + return; + } + // For warnings (vs. errors), the return code is 0. + // But we still want to mark the IDE. + if (!cmd.getErrors().toString().isEmpty() && fileConfig.getCompilerMode() == Mode.INTEGRATED) { + reportCommandErrors.report(cmd.getErrors().toString()); + return; // FIXME: Why do we return here? Even if there are warnings, the build process should proceed. + } + } + } + + /** + * Converts the given commands from strings to their LFCommand + * representation. + * @param commands A list of commands. + * @param factory A command factory. + * @param dir The directory in which the commands should be executed. + * @return The LFCommand representations of the given commands, + * where {@code null} is a placeholder for commands that cannot be + * executed. + */ + private static List getCommands(List commands, GeneratorCommandFactory factory, Path dir) { + return commands.stream() + .map(cmd -> List.of(cmd.split("\\s+"))) + .filter(tokens -> tokens.size() > 0) + .map(tokens -> factory.createCommand(tokens.get(0), tokens.subList(1, tokens.size()), dir)) + .collect(Collectors.toList()); + } /** * Given a representation of time that may include units, return From 9aea8a079b334b3d120cf4fc15cda113f9e63e9d Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 25 Nov 2021 17:05:03 -0800 Subject: [PATCH 041/221] Factor refreshProject out of GeneratorBase. --- .../org/lflang/generator/GeneratorBase.xtend | 35 ---------------- .../lflang/generator/JavaGeneratorUtils.java | 42 +++++++++++++++++++ .../org/lflang/generator/c/CGenerator.xtend | 2 +- .../org/lflang/generator/ts/TSGenerator.kt | 2 +- 4 files changed, 44 insertions(+), 37 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 946df54eb4..70ce4a1e84 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -847,41 +847,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - /** If the mode is INTEGRATED (the code generator is running in an - * an Eclipse IDE), then refresh the project. This will ensure that - * any generated files become visible in the project. - */ - protected def refreshProject() { - if (fileConfig.compilerMode == Mode.INTEGRATED) { - // Find name of current project - val id = "((:?[a-z]|[A-Z]|_\\w)*)"; - var pattern = if (File.separator.equals("/")) { // Linux/Mac file separator - Pattern.compile("platform:" + File.separator + "resource" + File.separator + id + File.separator); - } else { // Windows file separator - Pattern.compile( - "platform:" + File.separator + File.separator + "resource" + File.separator + File.separator + - id + File.separator + File.separator); - } - val matcher = pattern.matcher(code); - var projName = "" - if (matcher.find()) { - projName = matcher.group(1) - } - try { - val members = ResourcesPlugin.getWorkspace().root.members - for (member : members) { - // Refresh current project, or simply entire workspace if project name was not found - if (projName == "" || projName.equals(member.fullPath.toString.substring(1))) { - member.refreshLocal(IResource.DEPTH_INFINITE, null) - println("Refreshed " + member.fullPath.toString) - } - } - } catch (IllegalStateException e) { - println("Unable to refresh workspace: " + e) - } - } - } - // ////////////////////////////////////////////////// // // Private functions diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index 56013b19e8..3bdf845980 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -7,7 +7,12 @@ import java.util.HashSet; import java.util.Iterator; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.CoreException; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.generator.IFileSystemAccess2; @@ -20,6 +25,7 @@ import org.lflang.FileConfig; import org.lflang.Target; import org.lflang.TargetConfig; +import org.lflang.TargetConfig.Mode; import org.lflang.TargetProperty; import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Action; @@ -300,4 +306,40 @@ public static void writeSourceCodeToFile(byte[] code, String path) throws IOExce fOut.write(code); fOut.close(); } + + /** If the mode is INTEGRATED (the code generator is running in an + * an Eclipse IDE), then refresh the project. This will ensure that + * any generated files become visible in the project. + */ + public static void refreshProject(Mode compilerMode, String code) { + if (compilerMode == Mode.INTEGRATED) { + // Find name of current project + String id = "((:?[a-z]|[A-Z]|_\\w)*)"; + Pattern pattern; + if (File.separator.equals("/")) { // Linux/Mac file separator + pattern = Pattern.compile("platform:" + File.separator + "resource" + File.separator + id + File.separator); + } else { // Windows file separator + pattern = Pattern.compile( + "platform:" + File.separator + File.separator + "resource" + File.separator + File.separator + + id + File.separator + File.separator); + } + Matcher matcher = pattern.matcher(code); + String projName = ""; + if (matcher.find()) { + projName = matcher.group(1); + } + try { + IResource[] members = ResourcesPlugin.getWorkspace().getRoot().members(); + for (IResource member : members) { + // Refresh current project, or simply entire workspace if project name was not found + if (projName.isEmpty() || projName.equals(member.getFullPath().toString().substring(1))) { + member.refreshLocal(IResource.DEPTH_INFINITE, null); + System.out.println("Refreshed " + member.getFullPath()); + } + } + } catch (IllegalStateException | CoreException e) { + System.err.println("Unable to refresh workspace: " + e); + } + } + } } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 65f366115f..3aa66aab48 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -910,7 +910,7 @@ class CGenerator extends GeneratorBase { } // In case we are in Eclipse, make sure the generated code is visible. - refreshProject() + JavaGeneratorUtils.refreshProject(fileConfig.compilerMode, code.toString) } /** diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 9ded7934e4..7887b72b51 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -238,7 +238,7 @@ class TSGenerator( } } - refreshProject() + JavaGeneratorUtils.refreshProject(fileConfig.compilerMode, code.toString()) // Invoke the protocol buffers compiler on all .proto files in the project directory // Assumes protoc compiler has been installed on this machine From 289a886a133ff373c24fd81f433e4c07b945474e Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 27 Nov 2021 13:41:52 -0800 Subject: [PATCH 042/221] Fix a bug that was introduced by 218f859. --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 70ce4a1e84..063ee9e2e7 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -333,8 +333,8 @@ abstract class GeneratorBase extends JavaGeneratorBase { JavaGeneratorUtils.validateImports(context, fileConfig, instantiationGraph, errorReporter) val allResources = JavaGeneratorUtils.getResources(reactors) - resources.addAll(allResources.stream() - .filter [it | it != fileConfig.resource] + resources.addAll(allResources.stream() // FIXME: This filter reproduces the behavior of the method it replaces. But why must it be so complicated? Why are we worried about weird corner cases like this? + .filter [it | it != fileConfig.resource || (mainDef !== null && it === mainDef.reactorClass.eResource)] .map [it | JavaGeneratorUtils.getLFResource(it, fsa, context, errorReporter)] .collect(Collectors.toList()) ) From 2e30bee0d71dfc1b0add20efc2ce3d9dda13a014 Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 25 Nov 2021 10:03:22 -0800 Subject: [PATCH 043/221] Trimmed unnecessary scoping --- .../org/lflang/generator/NamedInstance.java | 2 +- .../org/lflang/generator/c/CGenerator.xtend | 205 ++++++++++-------- 2 files changed, 112 insertions(+), 95 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index 370b0c686f..fdfd0ee789 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -272,7 +272,7 @@ protected String getFullNameWithJoiner(String joiner) { //// Protected fields. /** - * The depth in the hierarchy of this reactor instance. + * The depth in the hierarchy of this instance. * This is 0 for main or federated, 1 for the reactors immediately contained, etc. */ protected int depth = 0; diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 3aa66aab48..007f51e8f2 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3134,7 +3134,7 @@ class CGenerator extends GeneratorBase { } } } - endScopedReactorBlock(temp); + endScopedReactorBlock(temp, child); if (foundOne) { pr(startTimeStep, temp.toString()); @@ -3205,7 +3205,7 @@ class CGenerator extends GeneratorBase { } startTimeStepIsPresentCount++ } - endScopedReactorBlock(temp); + endScopedReactorBlock(temp, port.parent); } } } @@ -3263,7 +3263,7 @@ class CGenerator extends GeneratorBase { } } - endScopedReactorBlock(temp); + endScopedReactorBlock(temp, instance); if (foundOne) pr(startTimeStep, temp.toString()); @@ -3313,7 +3313,7 @@ class CGenerator extends GeneratorBase { startTimeStepIsPresentCount++ } } - endScopedReactorBlock(startTimeStep); + endScopedReactorBlock(startTimeStep, child); } } } @@ -4014,7 +4014,7 @@ class CGenerator extends GeneratorBase { ''') } } - endScopedReactorBlock(temp); + endScopedReactorBlock(temp, reactor); if (foundOne) pr(temp.toString()); @@ -4754,7 +4754,7 @@ class CGenerator extends GeneratorBase { if (reactor !== null && reactor.isBank) { val index = CUtil.bankIndex(reactor); pr(builder, ''' - // Initialize bank members. + // Reactor is a bank. Iterate over bank members. for (int «index» = 0; «index» < «reactor.width»; «index»++) { ''') } else { @@ -4777,9 +4777,14 @@ class CGenerator extends GeneratorBase { private def void startScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. - startScopedBlock(builder, null); - defineSelfStruct(builder, reactor); - startScopedBlock(builder, reactor); + if (reactor != main) { + startScopedBlock(builder, null); + defineSelfStruct(builder, reactor); + if (reactor.isBank()) { + // Generate of for loop to iterate over the bank members. + startScopedBlock(builder, reactor); + } + } } /** @@ -4794,10 +4799,16 @@ class CGenerator extends GeneratorBase { /** * End a scoped reactor block. * @param builder The string builder into which to write. + * @param reactor The reactor instance. */ - private def void endScopedReactorBlock(StringBuilder builder) { - endScopedBlock(builder); - endScopedBlock(builder); + private def void endScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { + if (reactor != main) { + if (reactor.isBank()) { + // Close the for loop iterating over bank members. + endScopedBlock(builder); + } + endScopedBlock(builder); + } } /** @@ -5465,9 +5476,16 @@ class CGenerator extends GeneratorBase { return; } - pr('''// deferredInitialize for «reactor.getFullName()»''') + pr('''// **** Start deferredInitialize for «reactor.getFullName()»''') startScopedReactorBlock(code, reactor); + // If the child has a multiport that is an effect of some reaction in its container, + // then we have to generate code to allocate memory for arrays pointing to + // its data. If the child is a bank, then memory is allocated for the entire + // bank width because a reaction cannot specify which bank members it writes + // to so we have to assume it can write to any. + deferredAllocationForEffectsOnInputs(reactor); + // Initialize the num_destinations fields of port structs on the self struct. deferredOutputNumDestinations(reactor); // NOTE: Not done for top level. deferredInputNumDestinations(reactions); @@ -5482,13 +5500,6 @@ class CGenerator extends GeneratorBase { deferredAllocationForEffectsOnOutputs(reactor); for (child: reactor.children) { - // If the child has a multiport that is an effect of some reaction in its container, - // then we have to generate code to allocate memory for arrays pointing to - // its data. If the child is a bank, then memory is allocated for the entire - // bank width because a reaction cannot specify which bank members it writes - // to so we have to assume it can write to any. - deferredAllocationForEffectsOnInputs(child); - deferredInitialize(child, child.reactions); } // Handle inputs that get sent data from a reaction rather than from @@ -5496,7 +5507,8 @@ class CGenerator extends GeneratorBase { // output of a contained reactor. deferredConnectReactionsToPorts(reactor) - endScopedReactorBlock(code) + pr('''// **** End of deferredInitialize for «reactor.getFullName()»''') + endScopedReactorBlock(code, reactor) } /** @@ -5565,7 +5577,7 @@ class CGenerator extends GeneratorBase { «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(port)»; ''') } - endScopedReactorBlock(code); + endScopedReactorBlock(code, port.parent); } } } @@ -5679,6 +5691,7 @@ class CGenerator extends GeneratorBase { // instance because it may have a reaction to an output of this instance. for (output : reactor.outputs) { for (sendingRange : output.eventualDestinations) { + pr("// For reference counting, set num_destinations for port " + output.name); // Syntax is slightly difference for a multiport output vs. single port. // For a single port, there should be only one sendingRange. if (output.isMultiport()) { @@ -5732,6 +5745,9 @@ class CGenerator extends GeneratorBase { // Port is an input of a contained reactor that gets data from a reaction of this reactor. portsHandled.add(port); + pr(''' + // For reference counting, set num_destinations for port «port.parent.name».«port.name». + ''') startScopedBlock(code, port.parent); // The input port may itself have multiple destinations. @@ -5759,13 +5775,12 @@ class CGenerator extends GeneratorBase { } /** - * If any input port of the specified reactor is a multiport - * and is mentioned as an effect of a reaction in its reactors's parent - * (the reaction provides input to a contained reactor), then generate - * code to allocate memory to store the data produced by those reactions. + * If any reaction of the specified reactor provides input + * to a contained reactor, then generate code to allocate + * memory to store the data produced by those reactions. * The allocated memory is pointed to by a field called - * `_lf_containername.portname` on the self struct of the reactor's parent. - * @param reactor A contained reactor. + * `_lf_containername.portname` on the self struct of the reactor. + * @param reactor The reactor. */ private def void deferredAllocationForEffectsOnInputs(ReactorInstance reactor) { // Keep track of ports already handled. There may be more than one reaction @@ -5773,17 +5788,21 @@ class CGenerator extends GeneratorBase { val portsHandled = new HashSet(); // Find parent reactions that mention multiport inputs of this reactor. - for (reaction : reactor.parent.reactions) { + for (reaction : reactor.reactions) { for (effect : reaction.effects.filter(PortInstance)) { - if (effect.isMultiport && reactor.inputs.contains(effect) && !portsHandled.contains(effect)) { - // Port is a multiport input that the parent's reaction is writing to. + if (effect.parent.depth > reactor.depth // port of a contained reactor. + && effect.isMultiport + && !portsHandled.contains(effect) + && currentFederate.contains(effect.parent) + ) { + pr("// A reaction writes to a multiport of a child. Allocate memory.") portsHandled.add(effect); val portStructType = variableStructType( - effect.definition, reactor.definition.reactorClass + effect.definition, effect.parent.definition.reactorClass ); - startScopedBlock(code, reactor); + startScopedBlock(code, effect.parent); val effectRef = CUtil.sourceRef(effect); @@ -5905,26 +5924,24 @@ class CGenerator extends GeneratorBase { // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. - for (trigger : reaction.triggers) { - if (trigger instanceof PortInstance) { - // If the port is a multiport, then we need to create an entry for each - // individual port. - if (trigger.isMultiport() && trigger.parent !== null && trigger.isOutput) { - // If the width is given as a numeric constant, then add that constant - // to the output count. Otherwise, assume it is a reference to one or more parameters. - val width = trigger.width; - val containerName = trigger.parent.name - val portStructType = variableStructType(trigger.definition, - trigger.parent.definition.reactorClass) - - // FIXME: What if the effect is a bank? Need to index the container. - pr(''' - «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width = «width»; - // Allocate memory to store pointers to the multiport outputs of a contained reactor. - «nameOfSelfStruct»->_lf_«containerName».«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width); - ''') - } + for (trigger : reaction.triggers.filter(PortInstance)) { + // If the port is a multiport, then we need to create an entry for each + // individual port. + if (trigger.isMultiport() && trigger.parent !== null && trigger.isOutput) { + // If the width is given as a numeric constant, then add that constant + // to the output count. Otherwise, assume it is a reference to one or more parameters. + val width = trigger.width; + val containerName = trigger.parent.name + val portStructType = variableStructType(trigger.definition, + trigger.parent.definition.reactorClass) + + // FIXME: What if the port is in a bank? Need to index the container. + pr(''' + «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width = «width»; + // Allocate memory to store pointers to the multiport outputs of a contained reactor. + «nameOfSelfStruct»->_lf_«containerName».«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) + * «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width); + ''') } } } @@ -5949,49 +5966,50 @@ class CGenerator extends GeneratorBase { // These statements must be inserted after the array is malloc'd, // but we construct them while we are counting outputs. var outputCount = 0; - val initialization = new StringBuilder() + val init = new StringBuilder() - for (effect : reaction.effects) { - if (effect instanceof PortInstance) { - // Effect is a port. - // Create the entry in the output_produced array for this port. - // If the port is a multiport, then we need to create an entry for each - // individual channel. - - // If the port is an input of a contained reactor, then, if that - // contained reactor is a bank, we will have to iterate over bank - // members. - if (effect.isInput) { - startScopedBlock(initialization, effect.parent); - } + for (effect : reaction.effects.filter(PortInstance)) { + // Create the entry in the output_produced array for this port. + // If the port is a multiport, then we need to create an entry for each + // individual channel. + + // If the port is an input of a contained reactor, then, if that + // contained reactor is a bank, we will have to iterate over bank + // members. + var bankWidth = 1; + if (effect.isInput) { + pr(init, "// Reaction writes to an input of a contained reactor.") + bankWidth = effect.parent.width; + } + startScopedBlock(init, effect.parent); + pr(init, "int count = 0;") + + if (effect.isMultiport()) { + // Form is slightly different for inputs vs. outputs. + var connector = "."; + if (effect.isInput) connector = "->"; - if (effect.isMultiport()) { - // Form is slightly different for inputs vs. outputs. - var connector = "."; - if (effect.isInput) connector = "->"; - - // Point the output_produced field to where the is_present field of the port is. - pr(initialization, ''' - for (int i = 0; i < «effect.width»; i++) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount» + i] - = &«CUtil.sourceRef(effect)»[i]«connector»is_present; - } - ''') - outputCount += effect.getWidth(); - } else { - // The effect is not a multiport nor a port contained by a multiport. - pr(initialization, ''' - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[«outputCount»] - = &«CUtil.sourceRef(effect)».is_present; - ''') - outputCount++ - } - if (effect.isInput) { - endScopedBlock(initialization); - } + // Point the output_produced field to where the is_present field of the port is. + pr(init, ''' + for (int i = 0; i < «effect.width»; i++) { + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[i + count] + = &«CUtil.sourceRef(effect)»[i]«connector»is_present; + } + count += «effect.getWidth()»; + ''') + outputCount += effect.width * bankWidth; + } else { + // The effect is not a multiport nor a port contained by a multiport. + pr(init, ''' + «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[count++] + = &«CUtil.sourceRef(effect)».is_present; + ''') + outputCount += bankWidth; } + endScopedBlock(init); } pr(''' + // ** Start initialization for reaction «reaction.index» // Total number of outputs (single ports and multiport channels) produced by the reaction. «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs = «outputCount»; // Allocate arrays for triggering downstream reactions. @@ -6003,10 +6021,9 @@ class CGenerator extends GeneratorBase { «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggered_sizes = (int*)calloc(«nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs, sizeof(int)); } - ''') - pr(''' // Initialize the output_produced array. - «initialization.toString» + «init.toString» + // ** End initialization for reaction «reaction.index» ''') } From 4f98b6e6455daf7a900efd9e418ddd5051723360 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Haye=C3=9F?= Date: Tue, 16 Nov 2021 16:33:43 +0100 Subject: [PATCH 044/221] Removes unnecessary start input and associated reaction --- benchmark/Cpp/Savina/src/micro/ThreadRing.lf | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/benchmark/Cpp/Savina/src/micro/ThreadRing.lf b/benchmark/Cpp/Savina/src/micro/ThreadRing.lf index ca65dc2b42..36d2783610 100644 --- a/benchmark/Cpp/Savina/src/micro/ThreadRing.lf +++ b/benchmark/Cpp/Savina/src/micro/ThreadRing.lf @@ -49,7 +49,6 @@ reactor ThreadRingReactor { output outNextReactor:{=PingMessage=}; input inPrevReactor:{=PingMessage=}; - input start:{=PingMessage=}; output finished:void; reaction(inPrevReactor) -> outNextReactor, finished {= @@ -60,15 +59,6 @@ reactor ThreadRingReactor { reactor::log::Debug() << "Finished with count " << inPrevReactor.get()->getPingsLeft(); } =} - - reaction(start) -> outNextReactor, finished {= - if (start.get()->hasNext()) { - outNextReactor.set(start.get()->next()); - reactor::log::Debug() << "Starting with count " << start.get()->getPingsLeft(); - } else { - finished.set(); - } - =} } reactor ThreadRingReactorLoopOpener { From d2bc10472390d31f805d067b76ae1d11bd8b39d5 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 24 Nov 2021 10:17:41 +0100 Subject: [PATCH 045/221] updated reacto-cpp --- org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 555934f3bc..14f752e8f7 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -54,7 +54,7 @@ class CppGenerator( const val libDir = "/lib/cpp" /** Default version of the reactor-cpp runtime to be used during compilation */ - const val defaultRuntimeVersion = "007143225dbc198a5fee233ce125c3584a9541d8" + const val defaultRuntimeVersion = "1d5ef9d9dc25bcf30bc4eb94b2316b32f456aaa2" } override fun doGenerate(resource: Resource, fsa: IFileSystemAccess2, context: IGeneratorContext) { From 425501370aba29251b96e821e2dbc0ee9e44c0c5 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 24 Nov 2021 10:31:26 +0100 Subject: [PATCH 046/221] fix default paramters in PingPong benchmark --- benchmark/C/Savina/src/micro/PingPong.lf | 4 ++-- benchmark/Cpp/Savina/src/micro/PingPong.lf | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/benchmark/C/Savina/src/micro/PingPong.lf b/benchmark/C/Savina/src/micro/PingPong.lf index 3fc69ef762..a8df1a48d7 100644 --- a/benchmark/C/Savina/src/micro/PingPong.lf +++ b/benchmark/C/Savina/src/micro/PingPong.lf @@ -33,7 +33,7 @@ target C { /// [[[end]]] fast: true }; -reactor Ping(count:int(10000000)) { +reactor Ping(count:int(1000000)) { input receive:int; output send:int; state pingsLeft:int(count); @@ -49,7 +49,7 @@ reactor Ping(count:int(10000000)) { } =} } -reactor Pong(expected:int(10000000)) { +reactor Pong(expected:int(1000000)) { input receive:int; output send:int; state count:int(0); diff --git a/benchmark/Cpp/Savina/src/micro/PingPong.lf b/benchmark/Cpp/Savina/src/micro/PingPong.lf index 5bfe90b5d6..3c31d06467 100644 --- a/benchmark/Cpp/Savina/src/micro/PingPong.lf +++ b/benchmark/Cpp/Savina/src/micro/PingPong.lf @@ -27,12 +27,12 @@ */ target Cpp { - build-type : RelWithDebInfo + build-type : RelWithDebInfo, }; import BenchmarkRunner from "../BenchmarkRunner.lf"; -reactor Ping(count:unsigned(40000)) { +reactor Ping(count:unsigned(1000000)) { state pings_left:unsigned(0); @@ -79,7 +79,7 @@ reactor Pong { } -main reactor (numIterations:int(12), count:unsigned(40000)) { +main reactor (numIterations:int(12), count:unsigned(1000000)) { ping = new Ping(count=count); runner = new BenchmarkRunner(numIterations=numIterations); From eeebfc08253bd052218d997d98dd676b4f51d1a5 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 24 Nov 2021 13:43:09 +0100 Subject: [PATCH 047/221] cpp: explicitly check the cmake version --- .../org/lflang/generator/cpp/CppGenerator.kt | 38 +++++++++++++++---- 1 file changed, 31 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 14f752e8f7..ef5061ea45 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -127,6 +127,28 @@ class CppGenerator( fsa.generateFile(relSrcGenPath.resolve("CMakeLists.txt").toString(), cmakeGenerator.generateCode(cppSources)) } + fun getCmakeVersion(buildPath: Path): String? { + val cmd = commandFactory.createCommand("cmake", listOf("--version"), buildPath) + val res = cmd.run() + if (res == 0) { + val regex = "\\d+(\\.\\d+)+".toRegex() + val version = regex.find(cmd.output.toString()) + return version?.value + } + return null + } + + private fun String.compareVersion(other: String): Int { + val a = this.split(".").map { it.toInt() } + val b = other.split(".").map { it.toInt() } + for (x in (a zip b)) { + val res = x.first.compareTo(x.second) + if (res != 0) + return res + } + return 0 + } + fun doCompile(context: IGeneratorContext) { val outPath = fileConfig.outPath @@ -136,6 +158,15 @@ class CppGenerator( // make sure the build directory exists Files.createDirectories(buildPath) + val version = getCmakeVersion(buildPath) + if (version == null || version.compareVersion("3.5.0") < 0) { + errorReporter.reportError( + "The C++ target requires CMAKE >= 3.5.0 to compile the generated code. " + + "Auto-compiling can be disabled using the \"no-compile: true\" target property." + ) + return + } + val cores = Runtime.getRuntime().availableProcessors() val makeCommand = commandFactory.createCommand( @@ -156,13 +187,6 @@ class CppGenerator( ), buildPath ) - if (makeCommand == null || cmakeCommand == null) { - errorReporter.reportError( - "The C++ target requires CMAKE >= 3.02 to compile the generated code. " + - "Auto-compiling can be disabled using the \"no-compile: true\" target property." - ) - return - } // prepare cmake if (targetConfig.compiler != null) { From ac45bba62d7383f39e5aafa5611a3460825b2667 Mon Sep 17 00:00:00 2001 From: Christian Menard Date: Wed, 24 Nov 2021 13:59:14 +0100 Subject: [PATCH 048/221] cpp: avoid --parallel for cmake versions below 3.12 --- .../org/lflang/generator/cpp/CppGenerator.kt | 93 +++++++++++-------- 1 file changed, 56 insertions(+), 37 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index ef5061ea45..e032544ad6 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -37,6 +37,7 @@ import org.lflang.lf.Action import org.lflang.lf.TimeUnit import org.lflang.lf.VarRef import org.lflang.scoping.LFGlobalScopeProvider +import org.lflang.util.LFCommand import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -138,17 +139,6 @@ class CppGenerator( return null } - private fun String.compareVersion(other: String): Int { - val a = this.split(".").map { it.toInt() } - val b = other.split(".").map { it.toInt() } - for (x in (a zip b)) { - val res = x.first.compareTo(x.second) - if (res != 0) - return res - } - return 0 - } - fun doCompile(context: IGeneratorContext) { val outPath = fileConfig.outPath @@ -158,6 +148,7 @@ class CppGenerator( // make sure the build directory exists Files.createDirectories(buildPath) + // get the installed cmake version and make sure it is at least 3.5 val version = getCmakeVersion(buildPath) if (version == null || version.compareVersion("3.5.0") < 0) { errorReporter.reportError( @@ -167,37 +158,13 @@ class CppGenerator( return } - val cores = Runtime.getRuntime().availableProcessors() - - val makeCommand = commandFactory.createCommand( - "cmake", - listOf( - "--build", ".", "--target", "install", "--parallel", cores.toString(), "--config", - targetConfig.cmakeBuildType?.toString() ?: "Release" - ), - buildPath - ) - - val cmakeCommand = commandFactory.createCommand( - "cmake", listOf( - "-DCMAKE_INSTALL_PREFIX=${outPath.toUnixString()}", - "-DREACTOR_CPP_BUILD_DIR=${reactorCppPath.toUnixString()}", - "-DCMAKE_INSTALL_BINDIR=${outPath.relativize(fileConfig.binPath).toUnixString()}", - fileConfig.srcGenPath.toUnixString() - ), - buildPath - ) - - // prepare cmake - if (targetConfig.compiler != null) { - cmakeCommand.setEnvironmentVariable("CXX", targetConfig.compiler) - } - // run cmake + val cmakeCommand = createCmakeCommand(buildPath, outPath, reactorCppPath) val cmakeReturnCode = cmakeCommand.run(context.cancelIndicator) if (cmakeReturnCode == 0) { // If cmake succeeded, run make + val makeCommand = createMakeCommand(buildPath, version) val makeReturnCode = makeCommand.run(context.cancelIndicator) if (makeReturnCode == 0) { @@ -212,6 +179,58 @@ class CppGenerator( } } + private fun String.compareVersion(other: String): Int { + val a = this.split(".").map { it.toInt() } + val b = other.split(".").map { it.toInt() } + for (x in (a zip b)) { + val res = x.first.compareTo(x.second) + if (res != 0) + return res + } + return 0 + } + + private fun createMakeCommand(buildPath: Path, version: String): LFCommand { + val makeArgs: List + if (version.compareVersion("3.12.0") < 0) { + errorReporter.reportWarning("CMAKE is older than version 3.12. Parallel building is not supported.") + makeArgs = + listOf("--build", ".", "--target", "install", "--config", targetConfig.cmakeBuildType?.toString() ?: "Release") + } else { + val cores = Runtime.getRuntime().availableProcessors() + makeArgs = listOf( + "--build", + ".", + "--target", + "install", + "--parallel", + cores.toString(), + "--config", + targetConfig.cmakeBuildType?.toString() ?: "Release" + ) + } + + return commandFactory.createCommand("cmake", makeArgs, buildPath) + } + + private fun createCmakeCommand(buildPath: Path, outPath: Path, reactorCppPath: Path): LFCommand { + val cmd = commandFactory.createCommand( + "cmake", listOf( + "-DCMAKE_INSTALL_PREFIX=${outPath.toUnixString()}", + "-DREACTOR_CPP_BUILD_DIR=${reactorCppPath.toUnixString()}", + "-DCMAKE_INSTALL_BINDIR=${outPath.relativize(fileConfig.binPath).toUnixString()}", + fileConfig.srcGenPath.toUnixString() + ), + buildPath + ) + + // prepare cmake + if (targetConfig.compiler != null) { + cmd.setEnvironmentVariable("CXX", targetConfig.compiler) + } + return cmd + } + /** * Generate code for the body of a reaction that takes an input and * schedules an action with the value of that input. From 6d625d523aeb4cff55d83dedb7ab4a02e5ef35fa Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 27 Nov 2021 14:51:12 -0800 Subject: [PATCH 049/221] Major refactoring, got everything to compile, got diagrams working again except for nested banks. --- .../synthesis/LinguaFrancaSynthesis.xtend | 49 +- org.lflang/src/org/lflang/ModelInfo.java | 5 - .../org/lflang/generator/ActionInstance.java | 4 +- .../org/lflang/generator/GeneratorBase.xtend | 20 +- .../lflang/generator/ParameterInstance.java | 15 +- .../org/lflang/generator/PortInstance.java | 475 +++++++++--------- .../lflang/generator/ReactionInstance.java | 4 +- .../generator/ReactionInstanceGraph.xtend | 4 +- .../org/lflang/generator/ReactorInstance.java | 363 +++++-------- .../org/lflang/generator/c/CGenerator.xtend | 145 ++++-- .../generator/python/PythonGenerator.xtend | 6 +- .../src/org/lflang/graph/TopologyGraph.java | 33 +- .../C/src/multiport/NestedInterleavedBanks.lf | 36 ++ 13 files changed, 577 insertions(+), 582 deletions(-) create mode 100644 test/C/src/multiport/NestedInterleavedBanks.lf diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index d81c8552a5..702eeba0d5 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -50,6 +50,7 @@ import de.cau.cs.kieler.klighd.syntheses.AbstractDiagramSynthesis import de.cau.cs.kieler.klighd.util.KlighdProperties import java.util.Collection import java.util.EnumSet +import java.util.LinkedList import java.util.List import java.util.Map import javax.inject.Inject @@ -728,28 +729,28 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } // Transform connections. - // The connections data structure maps connections to their connections as they appear - // in a visualization of the program. For each connection, there is map - // from source ports (single ports and multiports) on the left side of the - // connection to a set of destination ports (single ports and multiports) - // on the right side of the connection. The ports contained by the multiports - // are not represented. - for (Connection connection : reactorInstance.connections.keySet) { - // TODO check banks - val connections = reactorInstance.connections.get(connection); - for (leftPort : connections.keySet) { - val rightPorts = connections.get(leftPort); - for (rightPort : rightPorts) { - val source = if (leftPort.parent == reactorInstance) { - parentInputPorts.get(leftPort) - } else { - outputPorts.get(leftPort.parent, leftPort) - } - val target = if (rightPort.parent == reactorInstance) { - parentOutputPorts.get(rightPort) - } else { - inputPorts.get(rightPort.parent, rightPort) - } + // First, collect all the source ports. + val sourcePorts = new LinkedList(reactorInstance.inputs); + for (child : reactorInstance.children) { + sourcePorts.addAll(child.outputs); + } + + for (leftPort : sourcePorts) { + for (rightRange : leftPort.dependentPorts) { + val source = if (leftPort.parent == reactorInstance) { + parentInputPorts.get(leftPort) + } else { + outputPorts.get(leftPort.parent, leftPort) + } + val rightPort = rightRange.getPort(); + val target = if (rightPort.parent == reactorInstance) { + parentOutputPorts.get(rightPort) + } else { + inputPorts.get(rightPort.parent, rightPort) + } + // There should be a connection, but skip if not. + val connection = rightRange.connection; + if (connection !== null) { val edge = createIODependencyEdge(connection, leftPort.isMultiport() || rightPort.isMultiport()) if (connection.delay !== null) { edge.addCenterEdgeLabel(connection.delay.toText) => [ @@ -896,8 +897,8 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { if (!t.nullOrEmpty) { b.append(":").append(t) } - if (!param.init.nullOrEmpty) { - b.append("(").append(param.init.join(", ", [it.toText])).append(")") + if (!param.getInitialValue.nullOrEmpty) { + b.append("(").append(param.getInitialValue.join(", ", [it.toText])).append(")") } return b.toString() } diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index a3584e9910..1385b7b203 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -86,11 +86,6 @@ public class ModelInfo { */ public Set overflowingParameters; - /** - * A graph of ports and reactions. - */ - public TopologyGraph topologyGraph; - public List topLevelReactorInstances; /** Cycles found during topology analysis. */ diff --git a/org.lflang/src/org/lflang/generator/ActionInstance.java b/org.lflang/src/org/lflang/generator/ActionInstance.java index 5f5e6b991f..13cde5c744 100644 --- a/org.lflang/src/org/lflang/generator/ActionInstance.java +++ b/org.lflang/src/org/lflang/generator/ActionInstance.java @@ -56,7 +56,7 @@ public ActionInstance(Action definition, ReactorInstance parent) { Parameter parm = definition.getMinDelay().getParameter(); if (parm != null) { this.minDelay = ASTUtils.getTimeValue( - parent.lookupParameterInstance(parm).init.get(0)); + parent.initialParameterValue(parm).get(0)); } else { this.minDelay = ASTUtils.getTimeValue(definition.getMinDelay()); } @@ -65,7 +65,7 @@ public ActionInstance(Action definition, ReactorInstance parent) { Parameter parm = definition.getMinSpacing().getParameter(); if (parm != null) { this.minSpacing = ASTUtils.getTimeValue( - parent.lookupParameterInstance(parm).init.get(0)); + parent.initialParameterValue(parm).get(0)); } else { this.minSpacing = ASTUtils.getTimeValue(definition.getMinSpacing()); } diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 70ce4a1e84..62b9add187 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -34,8 +34,8 @@ import java.util.LinkedHashSet import java.util.List import java.util.Map import java.util.Set -import java.util.stream.Collectors import java.util.regex.Pattern +import java.util.stream.Collectors import org.eclipse.core.resources.IMarker import org.eclipse.core.resources.IResource import org.eclipse.core.resources.ResourcesPlugin @@ -67,7 +67,6 @@ import org.lflang.lf.Port import org.lflang.lf.Reaction import org.lflang.lf.Reactor import org.lflang.lf.StateVar -import org.lflang.lf.TimeUnit import org.lflang.lf.VarRef import static extension org.lflang.ASTUtils.* @@ -1044,15 +1043,15 @@ abstract class GeneratorBase extends JavaGeneratorBase { var channel = 0; // Next input channel to be replaced. // If the port is not an input, ignore it. if (input.isInput) { - for (source : input.immediateSources()) { + for (source : input.dependsOnPorts) { // FIXME: How to determine the bank index of the source? // Need ReactorInstance support for ranges. - // val sourceBankIndex = (source.getPortInstance().parent.bankIndex >= 0) ? source.getPortInstance().parent.bankIndex : 0 + // val sourceBankIndex = (source.getPort().parent.bankIndex >= 0) ? source.getPort().parent.bankIndex : 0 val sourceBankIndex = 0 - val sourceFederate = federatesByInstantiation.get(source.getPortInstance().parent.definition).get(sourceBankIndex); + val sourceFederate = federatesByInstantiation.get(source.getPort().parent.definition).get(sourceBankIndex); // Set up dependency information. - var connection = mainInstance.getConnection(source.getPortInstance(), input) + var connection = source.connection; if (connection === null) { // This should not happen. errorReporter.reportError(input.definition, "Unexpected error. Cannot find input connection for port") @@ -1091,14 +1090,13 @@ abstract class GeneratorBase extends JavaGeneratorBase { // Make one communication for each channel. // FIXME: There is an opportunity for optimization here by aggregating channels. - // FIXME: bank indices not handled yet. - for (var i = 0; i < source.channelWidth; i++) { + for (var i = 0; i < source.getTotalWidth(); i++) { FedASTUtils.makeCommunication( - source.getPortInstance(), + source.getPort(), input, connection, sourceFederate, - 0, // FIXME: source.getPortInstance().parent.bankIndex, + 0, // FIXME: source.getPort().parent.bankIndex, source.startChannel + i, destinationFederate, 0, // FIXME: input.parent.bankIndex, @@ -1107,7 +1105,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { targetConfig.coordination ); } - channel += source.channelWidth; + channel += source.getTotalWidth(); } } } diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index d4b9765136..6837504ea3 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -60,19 +60,26 @@ public ParameterInstance(Parameter definition, ReactorInstance parent) { } this.type = JavaAstUtils.getInferredType(definition); - this.init = parent.initialParameterValue(definition); } ///////////////////////////////////////////// //// Public Fields - - public List init; - + public InferredType type; ///////////////////////////////////////////// //// Public Methods + /** + * Get the initial value(s) of this parameter as a list of + * Value objects, where each Value is either an instance + * of Time, Literal, or Code. That is, references to other + * parameters have been replaced with their initial values. + */ + public List getInitialValue() { + return parent.initialParameterValue(this.definition); + } + /** * Return the name of this parameter. * @return The name of this parameter. diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 388185a8cb..425b2ec8c4 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -27,12 +27,13 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.HashSet; -import java.util.LinkedList; +import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; import java.util.Set; import org.lflang.ErrorReporter; +import org.lflang.lf.Connection; import org.lflang.lf.Input; import org.lflang.lf.Output; import org.lflang.lf.Parameter; @@ -92,13 +93,13 @@ public void clearCaches() { try { if (eventualSourceRanges != null) { for (Range sourceRange : eventualSourceRanges) { - sourceRange.getPortInstance().clearCaches(); + sourceRange.getPort().clearCaches(); } } if (eventualDestinationRanges != null) { for (SendRange sendRange : eventualDestinationRanges) { for (Range destinationRange : sendRange.destinations) { - destinationRange.getPortInstance().clearCaches(); + destinationRange.getPort().clearCaches(); } } } @@ -118,7 +119,7 @@ public void clearCaches() { * not relay ports that the data may go through on the way. * * If this port itself has dependent reactions, - * that this port will be included as a destination in all items + * then this port will be included as a destination in all items * on the returned list. * * Each item in the returned list has the following fields: @@ -137,125 +138,135 @@ public List eventualDestinations() { if (eventualDestinationRanges != null) { return eventualDestinationRanges; } - + // Getting the destinations is more complex than getting the sources // because of multicast, where there is more than one connection statement // for a source of data. The strategy we follow here is to first get all - // the ports that this port sends to. Then use eventualSource to get - // the ranges of this port that send to each destination. + // the ports that this port eventually sends to. Then, if needed, split + // the resulting ranges so that each source range width matches all the + // destination range widths. We make two passes. First, we build + // a queue of ranges that may overlap, then we split those ranges + // and consolidate their destinations. PriorityQueue result = new PriorityQueue(); - - Set destinationPorts = null; - if (isOutput()) { - // For an output, obtain the destination ports from the parent - // of the port's parent. - ReactorInstance container = parent.getParent(); - // If the port's parent has no parent, then there are no destinations. - if (container == null) { - return new LinkedList(); - } - - destinationPorts = container.transitiveClosure(this); - } else { - // For an input, obtain the destination ports from the parent of this port. - // The port will be included in the returned set because it is an input. - destinationPorts = parent.transitiveClosure(this); - } - // If this port has dependent reactions, then add an entry for this port. - if (dependentReactions.size() > 0) { - SendRange thisPort = new SendRange(0, width); - thisPort.destinations.add(new Range(0, width)); - result.add(thisPort); + // If this port has dependent reactions, then add it to the result. + if (!dependentReactions.isEmpty()) { + // This will be the final result if there are no connections. + SendRange candidate = new SendRange(0, 0, width * parent.width(), false, null); + candidate.destinations.add(newRange(0, 0, width * parent.width(), false, null)); + result.add(candidate); } - - for (PortInstance destinationPort: destinationPorts) { - // If the destination port has no dependent reactions, skip it. - // Also skip this port. - if (destinationPort.dependentReactions.isEmpty() || destinationPort == this) { - continue; - } - // Get the destination's source ranges and find the one(s) that match this port. - int destinationChannel = 0; - for (Range source : destinationPort.eventualSources()) { - if (source.getPortInstance() == this) { - // This destinationPort receives data from the channel range - // given by source of port. Add to the result list. - SendRange sendingRange = new SendRange( - source.startChannel, source.channelWidth - ); - Range receivingRange = destinationPort.newRange( - destinationChannel, source.channelWidth); - sendingRange.destinations.add(receivingRange); - result.add(sendingRange); + + // Next, look at downstream ports. + int sourceWidthCovered = 0; + int totalWidth = width * parent.width(); + + Iterator destinations = dependentPorts.iterator(); + while(destinations.hasNext()) { + Range dst = destinations.next(); + + // Recursively get the send ranges of that destination port. + Iterator dstSendIterator = dst.getPort().eventualDestinations().iterator(); + + if (dstSendIterator.hasNext()) { + SendRange dstSend = dstSendIterator.next(); + while (true) { + if (dstSend.getTotalWidth() <= totalWidth - sourceWidthCovered) { + // Destination range fits in current multicast iteration. + result.add(dstSend); + if (!dstSendIterator.hasNext()) break; + dstSend = dstSendIterator.next(); + sourceWidthCovered += dstSend.getTotalWidth(); + } else { + // Destination range spills over into the next multicast iteration. + // Need to split the destination range. + SendRange residualDst = (SendRange)dstSend.tail(totalWidth - sourceWidthCovered); + dstSend.truncate(totalWidth - sourceWidthCovered); + result.add(dstSend); + dstSend = residualDst; + sourceWidthCovered = 0; + } } - destinationChannel += source.channelWidth; } } - + // Now check for overlapping ranges, constructing a new result. eventualDestinationRanges = new ArrayList(result.size()); SendRange candidate = result.poll(); - while (!result.isEmpty()) { - SendRange next = result.poll(); + SendRange next = result.poll(); + while (candidate != null) { + if (next == null) { + // No more candidates. We are done. + eventualDestinationRanges.add(candidate); + break; + } if (candidate.startChannel == next.startChannel) { - // Ranges have the same starting point. - if (candidate.channelWidth <= next.channelWidth) { - // Can use all of the channels. Import the destinations. + // Ranges have the same starting point. Need to merge them. + if (candidate.getTotalWidth() <= next.getTotalWidth()) { + // Can use all of the channels of candidate. + // Import the destinations of next and split it. candidate.destinations.addAll(next.destinations); - if (candidate.channelWidth < next.channelWidth) { + if (candidate.getTotalWidth() < next.getTotalWidth()) { // The next range has more channels connected to this sender. - next.startChannel += candidate.channelWidth; - next.channelWidth -= candidate.channelWidth; - result.add(next); - } // else we are done with next and can discard it. + next.truncate(candidate.getTotalWidth()); + // Truncate the destinations just imported. + candidate.truncate(candidate.getTotalWidth()); + } else { + // We are done with next and can discard it. + next = result.poll(); + } } else { - // candidate is wider than next. - // Use next as the new candidate and split candidate. - candidate.startChannel += next.channelWidth; - candidate.channelWidth -= next.channelWidth; - result.add(candidate); + // candidate is wider than next. Switch them and continue. + SendRange temp = candidate; candidate = next; + next = temp; } } else { // Because the result list is sorted, next starts at // a higher channel than candidate. - if (candidate.startChannel + candidate.channelWidth <= next.startChannel) { + if (candidate.startChannel + candidate.getTotalWidth() <= next.startChannel) { // Can use candidate as is and make next the new candidate. eventualDestinationRanges.add(candidate); candidate = next; + next = result.poll(); } else { // Ranges overlap. Have to split candidate. - SendRange candidateTail = new SendRange( - next.startChannel, - candidate.channelWidth - (next.startChannel - candidate.startChannel) - ); - candidateTail.destinations.addAll(candidate.destinations); - result.add(candidateTail); - candidate.channelWidth -= candidateTail.channelWidth; - // Put next back on the list. - result.add(next); + SendRange candidateTail = (SendRange)candidate.tail(next.startChannel); + candidate.truncate(next.startChannel); + result.add(candidate); + candidate = candidateTail; } } } - if (candidate != null) eventualDestinationRanges.add(candidate); - return eventualDestinationRanges; } /** - * Return a list of ports that send data to this port annotated - * with the channel ranges of each source port. If this is not - * a multiport, then the list will have only one item and the - * channel range will contain only one channel. - * Otherwise, it will have enough items so that the ranges - * add up to the width of this multiport. + * Return a list of ports that send data to this port annotated with the channel + * and bank ranges of each source port. If this port is directly written to by + * one more more reactions, then it is its own eventual source an only this port + * will be represented in the result. + * + * If this is not a multiport and is not within a bank, then the list will have + * only one item and the range will have a total width of one. Otherwise, it will + * have enough items so that the range widths add up to the width of this + * multiport multiplied by the total number of instances within containing banks. + * * The ports listed are only ports that are written to by reactions, * not relay ports that the data may go through on the way. */ public List eventualSources() { if (eventualSourceRanges == null) { - eventualSourceRanges = eventualSources(0, width); + // Cached result has not been created. + eventualSourceRanges = new ArrayList(); + + if (!dependsOnReactions.isEmpty()) { + eventualSourceRanges.add(newRange(0, 0, width * parent.width(), false, null)); + } else { + for (Range sourceRange : dependsOnPorts) { + eventualSourceRanges.addAll(sourceRange.getPort().eventualSources()); + } + } } return eventualSourceRanges; } @@ -285,51 +296,6 @@ public int getWidth() { return width; } - /** - * Return a list of ports connected to this port together with - * the ranges of channels from those source ports. If this is not - * a multiport, then there will be only one element in the returned - * list and it will have width one. Otherwise, the widths of the - * elements in the returned list will sum to the width of this port. - * - * If this port is an output port that has dependent reactions - * and no upstream ports, then a singleton list containing - * this port with its full range is returned. - */ - public List immediateSources() { - List result = null; - if (!isMultiport) { - result = new ArrayList(1); - } else { - result = new ArrayList(); - } - if (isOutput() - && !dependentReactions.isEmpty() - && dependsOnPorts.isEmpty() - ) { - result.add(newRange(0, width)); - } - int channelsProvided = 0; - for (Range sourceRange : dependsOnPorts) { - // sourceRange.channelWidth is the number of channels this source has to offer. - // If we get here, the source can provide some channels. How many? - int srcStart = sourceRange.startChannel; - int srcWidth = sourceRange.channelWidth; // Candidate width if we can use them all. - if (channelsProvided + srcWidth > width) { - // Can't use all the source channels. - srcWidth = width - channelsProvided; - } - PortInstance src = sourceRange.getPortInstance(); - result.add(src.newRange(srcStart, srcWidth)); - channelsProvided += srcWidth; - if (channelsProvided >= width) { - // Done. - break; - } - } - return result; - } - /** * Return true if the port is an input. */ @@ -375,87 +341,19 @@ public String toString() { ////////////////////////////////////////////////////// //// Protected methods. - /** - * Return a list of ports that send data to the specified channels of - * this port. The ports returned are annotated with the channel - * ranges of each source port. - * The ports listed are only ports that are written to by reactions, - * not relay ports that the data may go through on the way. - * - * If this port is an output port that has dependent reactions - * and no upstream ports, then a singleton list containing - * this port with its full range is returned. - * - * @param startRange The channel index for the start of the range of interest. - * @param rangeWidth The number of channels to find sources for. - */ - protected List eventualSources(int startRange, int rangeWidth) { - List result = null; - if (!isMultiport) { - result = new ArrayList(1); - } else { - result = new ArrayList(); - } - if (isOutput() - && !dependentReactions.isEmpty() - && dependsOnPorts.isEmpty() - ) { - result.add(newRange(startRange, rangeWidth)); - } - int channelsToSkip = startRange; - int channelsProvided = 0; - for (Range sourceRange : dependsOnPorts) { - // sourceRange.channelWidth is the number of channels this source has to offer. - if (sourceRange.channelWidth <= channelsToSkip) { - // No useful channels in this port. Skip it. - channelsToSkip -= sourceRange.channelWidth; - continue; - } - // If we get here, the source can provide some channels. How many? - int srcStart = sourceRange.startChannel + channelsToSkip; - int srcWidth = sourceRange.channelWidth - channelsToSkip; // Candidate width if we can use them all. - if (channelsProvided + srcWidth > rangeWidth) { - // Can't use all the source channels. - srcWidth = rangeWidth - channelsProvided; - } - PortInstance src = sourceRange.getPortInstance(); - // If this source depends on reactions, then include it in the result. - // Otherwise, keep looking upstream from it. - if (src.dependsOnReactions.isEmpty()) { - // Keep looking. - result.addAll(src.eventualSources(srcStart, srcWidth)); - } else { - result.add(src.newRange(srcStart, srcWidth)); - } - channelsProvided += srcWidth; - // No need to skip any more channels. - channelsToSkip = 0; - if (channelsProvided >= rangeWidth) { - // Done. - break; - } - } - return result; - } - - /** - * Create a SendingChannelRange representing a subset of the channels of this port. - * @param startChannel The lower end of the channel range. - * @param channelWidth The width of the range. - * @return A new instance of Range. - */ - protected SendRange newDestinationRange(int startChannel, int channelWidth) { - return new SendRange(startChannel, channelWidth); - } - /** * Create a Range representing a subset of the channels of this port. * @param startChannel The lower end of the channel range. - * @param channelWidth The width of the range. + * @param startBank The lower end of the bank range (or 0 for non-banks). + * @param width The width of the range (total number of connections). + * @param interleaved Marker whether bank and multiport iteration should be reversed. + * @param connection The connection associated with this range or null if none. * @return A new instance of Range. */ - protected Range newRange(int startChannel, int channelWidth) { - return new Range(startChannel, channelWidth); + protected Range newRange( + int startChannel, int startBank, int width, boolean interleaved, Connection connection + ) { + return new Range(startChannel, startBank, width, interleaved, connection); } ////////////////////////////////////////////////////// @@ -495,7 +393,7 @@ protected Range newRange(int startChannel, int channelWidth) { ////////////////////////////////////////////////////// //// Private methods. - + /** * Set the initial multiport width, if this is a multiport, from the widthSpec * in the definition. @@ -562,14 +460,8 @@ private void setInitialWidth(ErrorReporter errorReporter) { */ public class SendRange extends Range { - public SendRange(int startChannel, int channelWidth) { - super(startChannel, channelWidth); - - if (PortInstance.this.isMultiport) { - destinations = new ArrayList(); - } else { - destinations = new ArrayList(1); - } + public SendRange(int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection) { + super(startChannel, startBank, totalWidth, interleaved, connection); } public int getNumberOfDestinationReactors() { @@ -577,14 +469,57 @@ public int getNumberOfDestinationReactors() { // Has not been calculate before. Calculate now. Set destinations = new HashSet(); for (Range destination : this.destinations) { - destinations.add(destination.getPortInstance().getParent()); + destinations.add(destination.getPort().getParent()); } _numberOfDestinationReactors = destinations.size(); } return _numberOfDestinationReactors; } - public List destinations; + public List destinations = new ArrayList(); + + /** + * Override the base class to return a SendRange where + * each of the destinations is the tail of the original destinations. + * @param offset The number of channels to consume. + */ + @Override + public Range tail(int offset) { + SendRange result = (SendRange)super.tail(offset); + for (Range destination : destinations) { + result.destinations.add(destination.tail(offset)); + } + return result; + } + + /** + * Override the base class to return a SendRange rather than Range. + */ + @Override + protected Range newRange( + int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection + ) { + return new SendRange( + startChannel, + startBank, + totalWidth, + interleaved, + connection + ); + } + + /** + * Override the base class to also trucate the destinations. + * @param newWidth The new width. + */ + @Override + protected void truncate(int newWidth) { + super.truncate(newWidth); + for (Range destination : destinations) { + destination.truncate(newWidth); + } + } + private int _numberOfDestinationReactors = -1; // Never access this directly. } @@ -594,22 +529,36 @@ public int getNumberOfDestinationReactors() { * be (0,1). */ public class Range implements Comparable { - public Range(int startChannel, int channelWidth) { + + public Range( + int startChannel, + int startBank, + int totalWidth, + boolean interleaved, + Connection connection + ) { + int widthLimit = width * parent.width(); // Some targets determine widths at runtime, in which case a // width of 0 is reported here. Tolerate that. - if (channelWidth != 0 - && (startChannel < 0 || startChannel >= width - || channelWidth < 0 || startChannel + channelWidth > width)) { + if (totalWidth > 0 + && (startChannel < 0 || startChannel >= widthLimit + || totalWidth < 0 || startChannel + totalWidth > widthLimit)) { throw new RuntimeException("Invalid range of port channels."); } this.startChannel = startChannel; - this.channelWidth = channelWidth; + this.startBank = startBank; + this.totalWidth = totalWidth; + this.interleaved = interleaved; + this.connection = connection; } - public int startChannel; - public int channelWidth; - public PortInstance getPortInstance() { + public final int startChannel; + public final int startBank; + public final boolean interleaved; + public final Connection connection; + public PortInstance getPort() { return PortInstance.this; } + /** * Compare the ranges by just comparing their startChannel index. */ @@ -623,5 +572,81 @@ public int compareTo(Range o) { return 1; } } + + /** + * Return the total width of the range. + */ + public int getTotalWidth() { + return totalWidth; + } + + /** + * Return a new range that represents the leftover channels + * starting at the specified offset. Depending on + * whether this range is interleaved, this will consume from + * multiport channels first (if not interleaved) or banks first + * (if interleaved). The offset is required to be less + * than the total width or an exception will be thrown. + * @param offset The number of channels to consume. + */ + public Range tail(int offset) { + if (offset >= totalWidth) { + throw new RuntimeException("Insufficient channels in range."); + } + int channelWidth = PortInstance.this.width; + int bankWidth = PortInstance.this.parent.width(); + + int banksToConsume, channelsToConsume; + if (interleaved) { + banksToConsume = offset / bankWidth; + channelsToConsume = offset % bankWidth; + } else { + banksToConsume = offset / channelWidth; + channelsToConsume = offset % channelWidth; + } + return newRange( + startChannel + channelsToConsume, + startBank + banksToConsume, + totalWidth - offset, + interleaved, + connection + ); + } + + @Override + public String toString() { + return String.format( + "%s(channel %d, bank %d, width %d)", + PortInstance.this.getFullName(), + startChannel, + startBank, + totalWidth + ); + } + + /** + * Modify this range to reduce its total width to the specified width. + * @param newWidth The new width. + */ + protected void truncate(int newWidth) { + totalWidth = newWidth; + } + + /** + * Create a new Range with the specified parameters. + */ + protected Range newRange( + int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection + ) { + return new Range( + startChannel, + startBank, + totalWidth, + interleaved, + connection + ); + } + + private int totalWidth; // Allow modification. } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index d40ba47f38..4a17635bd9 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -325,7 +325,7 @@ public Set dependentReactions() { for (PortInstance.Range destinationRange : senderRange.destinations) { dependentReactionsCache.addAll( - destinationRange.getPortInstance().dependentReactions); + destinationRange.getPort().dependentReactions); } } } @@ -363,7 +363,7 @@ public Set dependsOnReactions() { // First, add reactions that send data through an intermediate port. for (PortInstance.Range senderRange : ((PortInstance)source).eventualSources()) { - dependsOnReactionsCache.addAll(senderRange.getPortInstance().dependsOnReactions); + dependsOnReactionsCache.addAll(senderRange.getPort().dependsOnReactions); } // Then, add reactions that send directly to this port. dependsOnReactionsCache.addAll(source.dependsOnReactions); diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend index 238cd21d7f..0b3ecda5d7 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend @@ -302,7 +302,7 @@ class ReactionInstanceGraph extends DirectedGraph { port.dependsOnReactions.forEach[this.addEdge(reaction, it)] // Reactions in upstream reactors. for (upstreamPort : port.dependsOnPorts) { - addUpstreamReactions(upstreamPort.portInstance, reaction) + addUpstreamReactions(upstreamPort.getPort(), reaction) } } @@ -318,7 +318,7 @@ class ReactionInstanceGraph extends DirectedGraph { port.dependentReactions.forEach[this.addEdge(it, reaction)] // Reactions in downstream reactors. for (downstreamPort : port.dependentPorts) { - addDownstreamReactions(downstreamPort.portInstance, reaction) + addDownstreamReactions(downstreamPort.getPort(), reaction) } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 3f59d9cf9a..09ffbc1ef7 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -30,10 +30,8 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.Deque; import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; -import java.util.Map; import java.util.Set; import org.lflang.ASTUtils; @@ -191,19 +189,6 @@ public boolean assignLevels() { return true; } } - - /** - * Return the destinations of the specified port. - * The result is a set (albeit an ordered set) of ports that are destinations - * in connections. This will return null if the source has no destinations. - */ - public Set destinations(PortInstance source) { - Map map = connectionTable.get(source); - if (map != null) { - return map.keySet(); - } - return null; - } /** * Return the instance of a child rector created by the specified @@ -219,34 +204,14 @@ public ReactorInstance getChildReactorInstance(Instantiation definition) { return null; } - /** - * Return the Connection that created the link between the specified source - * and destination, or null if there is no such link. - */ - public Connection getConnection(PortInstance source, PortInstance destination) { - var table = connectionTable.get(source); - if (table != null) { - return table.get(destination); - } - return null; - } - - /** - * Get a map of connections as they appear in a visualization of the program. - * For each connection, there is map from source ports (single ports and multiports) - * on the left side of the connection to a set of destination ports (single ports - * and multiports) on the right side of the connection. For banks of reactors, - * this includes only the connections to and from the first element of the bank. - */ - public Map>> getConnections() { - return connections; - } - /** * Return an integer is equal to one plus the total number of reactor instances * (including bank members) that have been instantiated before this one - * within the same parent. - * This can be used, for example, to index into an array of data structures + * within the same parent. If the number of instances cannot be determined, + * this will be 0. + * + * When this number can be determined, it can be used, for example, + * to index into an array of data structures * representing instances of reactors in generated code. * This assumes that index 0 refers to the parent, hence the "one plus." */ @@ -278,12 +243,10 @@ public String getName() { } /** - * Get the number of reactor instances associated with this - * reactor. This is 1 plus the total number of reactors - * in any contained reactors, as returned by getTotalNumReactorInstances(). - * If this is a bank, then this number does not account for the bank width. - * Use getTotalNumberOfReactorInstances() to take into - * account bank width. + * Return one plus the number of contained reactor instances in this + * reactor, if this can be determined, or -1 if not. This is 1 plus + * the total number of reactors in any contained reactor instances, + * taking into account their bank widths if necessary. */ public int getNumReactorInstances() { return numReactorInstances; @@ -331,13 +294,14 @@ public TriggerInstance getShutdownTrigger() { } /** - * Get the total number of reactor instances associated with - * this reactor. This is equal to the result of - * getNumReactorInstances() times the bank width, as returned - * by width(). + * Return the total number of reactor instances associated with + * this reactor, if it can be determined, or -1 if not. This is equal + * to the result of getNumReactorInstances() times the bank width, + * as returned by width(). */ public int getTotalNumReactorInstances() { - return totalNumReactorInstances; + if (width() < 0 || numReactorInstances < 0) return -1; + return width() * numReactorInstances; } /** @@ -574,14 +538,6 @@ public TimerInstance lookupTimerInstance(Timer timer) { return null; } - /** - * Return the set of ports in that are sources in connections in this reactor. - * These may be input ports of this reactor or output ports of contained reactors. - */ - public Set sources() { - return connectionTable.keySet(); - } - /** * Return a descriptive string. */ @@ -638,16 +594,6 @@ public int width() { ////////////////////////////////////////////////////// //// Protected fields. - /** - * Table recording connections and which connection created a link between - * a source and destination. Use a source port as a key to obtain a Map. - * The key set of the obtained Map is the set of destination ports. - * The value of the obtained Map is the connection that established the - * connection. - */ - protected Map> connectionTable - = new LinkedHashMap>(); - /** * For a root reactor instance only, this will be a queue of reactions * that either have been assigned an initial level or are ready to be @@ -774,26 +720,19 @@ protected void transitiveClosure( this + " nor any of its children." ); } - // If the port is a multiport, then iterate over its contained ordinary ports instead. // If the port is an input port, then include it in the result. if (source.isInput()) { destinations.add(source); } - Map map = connectionTable.get(source); - if (map != null) { - Set localDestinations = map.keySet(); - - if (localDestinations != null) { - for (PortInstance destination : localDestinations) { - destinations.add(destination); - if (destination.isInput()) { - // Destination may have further destinations lower in the hierarchy. - destination.parent.transitiveClosure(destination, destinations); - } else if (destination.parent.parent != null) { - // Destination may have further destinations higher in the hierarchy. - destination.parent.parent.transitiveClosure(destination, destinations); - } - } + for (PortInstance.Range dst : source.dependentPorts) { + PortInstance destination = dst.getPort(); + destinations.add(destination); + if (destination.isInput()) { + // Destination may have further destinations lower in the hierarchy. + destination.parent.transitiveClosure(destination, destinations); + } else if (destination.parent.parent != null) { + // Destination may have further destinations higher in the hierarchy. + destination.parent.parent.transitiveClosure(destination, destinations); } } } @@ -880,11 +819,17 @@ private ReactorInstance( this.unorderedReactions ); this.children.add(childInstance); - this.numReactorInstances += childInstance.getTotalNumReactorInstances(); - childInstance.indexOffset = offset; - // Next child will have an offset augmented by the total number of - // reactor instances in this one. - offset += childInstance.getTotalNumReactorInstances(); + int numChildReactors = childInstance.getTotalNumReactorInstances(); + if (this.numReactorInstances >= 0 && numChildReactors > 0) { + this.numReactorInstances += numChildReactors; + + childInstance.indexOffset = offset; + // Next child will have an offset augmented by the total number of + // reactor instances in this one. + offset += childInstance.getTotalNumReactorInstances(); + } else { + numReactorInstances = -1; + } } // Instantiate timers for this reactor instance @@ -904,8 +849,6 @@ private ReactorInstance( // Note that this can only happen _after_ the children, // port, action, and timer instances have been created. createReactionInstances(); - - this.totalNumReactorInstances = width() * this.numReactorInstances; } } @@ -913,77 +856,20 @@ private ReactorInstance( //// Private methods. /** - * Record the connection from the source port to the destination port in the - * connectionTable map. - * @param source The source port. - * @param destination The destination port. - * @param connection The connection AST node creating the connection. - */ - private void addDestination(PortInstance source, PortInstance destination, Connection connection) { - Map srcConnections = connectionTable.get(source); - if (srcConnections == null) { - srcConnections = new LinkedHashMap(); - connectionTable.put(source, srcConnections); - } - srcConnections.put(destination, connection); - } - - /** - * Connect the given left port instance to the given right port instance. - * These may be multiports. - * @param connection The connection statement creating this connection. - * @param srcInstance The source instance (the left port). - * @param srcChannel The starting channel number for the source. - * @param dstInstance The destination instance (the right port). - * @param dstChannel The starting channel number for the destination. - * @param width The width of this connection. + * Connect the given left port range to the given right port range. + * @param src The source range. + * @param dst The destination range. */ private void connectPortInstances( - Connection connection, - PortInstance srcInstance, - int srcChannel, - PortInstance dstInstance, - int dstChannel, - int width + PortInstance.Range src, + PortInstance.Range dst ) { - PortInstance.Range dstRange = dstInstance.newRange(dstChannel, width); - srcInstance.dependentPorts.add(dstRange); - - PortInstance.Range srcRange = srcInstance.newRange(srcChannel, width); - dstInstance.dependsOnPorts.add(srcRange); - - // Record the connection in the connection table. - // Original cryptic xtend code: - // this.destinations.compute(srcInstance, [key, set| CollectionUtil.plus(set, dstInstance)]) - // this.connectionTable.compute(srcInstance, [key, map| CollectionUtil.plus(map, dstInstance, connection)]) - addDestination(srcInstance, dstInstance, connection); - - // The following is support for the diagram visualization. - - // Record this representative connection for visualization in the - // connections map. - Map> map = connections.get(connection); - if (map == null) { - map = new LinkedHashMap>(); - connections.put(connection, map); - } - Set destinations = map.get(srcInstance); - if (destinations == null) { - destinations = new LinkedHashSet(); - map.put(srcInstance, destinations); - } - destinations.add(dstInstance); - - // Original cryptic xtend code below. - // val src2 = src - // val dst2 = dst - // this.connections.compute(connection, [_, links| { - // CollectionUtil.compute(links, src2, [_2, destinations| CollectionUtil.plus(destinations, dst2)]) - // }]) + src.getPort().dependentPorts.add(dst); + dst.getPort().dependsOnPorts.add(src); } /** - * Populate destinations map and the connectivity information in the port instances. + * Populate connectivity information in the port instances. * Note that this can only happen _after_ the children and port instances have been created. * Unfortunately, we have to do some complicated things here * to support multiport-to-multiport, multiport-to-bank, @@ -993,60 +879,72 @@ private void connectPortInstances( */ private void establishPortConnections() { for (Connection connection : ASTUtils.allConnections(reactorDefinition)) { - List leftRanges = listPortInstances(connection.getLeftPorts()); - List rightRanges = listPortInstances(connection.getRightPorts()); - - // Check widths. FIXME: This duplicates validator checks! - int leftWidth = 0; - for (PortInstance.Range range: leftRanges) { - leftWidth += range.channelWidth; - } - int rightWidth = 0; - for (PortInstance.Range range: rightRanges) { - rightWidth += range.channelWidth; - } - if (leftWidth > rightWidth) { - reporter.reportWarning(connection, - "Source is wider than the destination. Outputs will be lost."); - } else if (leftWidth < rightWidth && !connection.isIterated()) { - reporter.reportWarning(connection, - "Destination is wider than the source. Inputs will be missing."); + List leftPorts = listPortInstances(connection.getLeftPorts()); + Iterator srcRanges = leftPorts.iterator(); + Iterator dstRanges = listPortInstances(connection.getRightPorts()).iterator(); + + // Check for empty lists. + if (!srcRanges.hasNext()) { + if (dstRanges.hasNext()) { + reporter.reportWarning(connection, "No sources to provide inputs."); + } + return; + } else if (!dstRanges.hasNext()) { + reporter.reportWarning(connection, "No destination. Outputs will be lost."); + return; } - - // If any of these ports is a multiport, then things can complicated depending - // on how they overlap. Keep track of how much of the current left and right - // multiports have already been used. - Iterator leftIterator = leftRanges.iterator(); - PortInstance.Range leftRange = leftIterator.next(); - int leftUsedChannels = 0; - for (PortInstance.Range rightRange : rightRanges) { - int rightUsedChannels = 0; - while (rightUsedChannels < rightRange.channelWidth && leftRange != null) { - // Figure out how much of each port we have used (in case it is a multiport). - // This is the minimum of the two remaining widths. - int connectionWidth = leftRange.channelWidth - leftUsedChannels; - if (rightRange.channelWidth - rightUsedChannels < connectionWidth) { - connectionWidth = rightRange.channelWidth - rightUsedChannels; + PortInstance.Range src = srcRanges.next(); + PortInstance.Range dst = dstRanges.next(); + + while(true) { + if (dst.getTotalWidth() == src.getTotalWidth()) { + connectPortInstances(src, dst); + if (!dstRanges.hasNext()) { + if (srcRanges.hasNext()) { + reporter.reportWarning(connection, + "Source is wider than the destination. Outputs will be lost."); + } + break; + } else if (!srcRanges.hasNext()) { + if (connection.isIterated()) { + srcRanges = leftPorts.iterator(); + } else { + reporter.reportWarning(connection, + "Destination is wider than the source. Inputs will be missing."); + } + break; + } + dst = dstRanges.next(); + src = srcRanges.next(); + } else if (dst.getTotalWidth() < src.getTotalWidth()) { + // Split the left range in two. + PortInstance.Range remaining = src.tail(dst.getTotalWidth()); + src.truncate(dst.getTotalWidth()); // Shorten source range to match destination. + connectPortInstances(src, dst); + src = remaining; + if (!dstRanges.hasNext()) { + reporter.reportWarning(connection, + "Source is wider than the destination. Outputs will be lost."); + break; } - connectPortInstances( - connection, - leftRange.getPortInstance(), leftRange.startChannel + leftUsedChannels, - rightRange.getPortInstance(), rightRange.startChannel + rightUsedChannels, - connectionWidth); - leftUsedChannels += connectionWidth; - rightUsedChannels += connectionWidth; - if (leftUsedChannels >= leftRange.channelWidth) { - if (leftIterator.hasNext()) { - leftRange = leftIterator.next(); - } else if (connection.isIterated()) { - leftIterator = leftRanges.iterator(); - leftRange = leftIterator.next(); + dst = dstRanges.next(); + } else if (src.getTotalWidth() < dst.getTotalWidth()) { + // Split the right range in two. + PortInstance.Range remaining = dst.tail(src.getTotalWidth()); + dst.truncate(src.getTotalWidth()); + connectPortInstances(src, dst); + dst = remaining; + if (!srcRanges.hasNext()) { + if (connection.isIterated()) { + srcRanges = leftPorts.iterator(); } else { - leftRange = null; + reporter.reportWarning(connection, + "Destination is wider than the source. Inputs will be missing."); } - leftUsedChannels = 0; + break; } + src = srcRanges.next(); } } } @@ -1055,17 +953,17 @@ private void establishPortConnections() { /** * Given a list of port references, as found on either side of a connection, * return a list of the port instances referenced. These may be multiports, - * so the returned list includes ranges of channels. - * If the port reference has the form `c.x`, where `c` is a bank of reactors, - * then the list will contain the port instances belonging to each bank member. + * and may be ports of a contained bank (a port representing ports of the bank + * members) so the returned list includes ranges of banks and channels. * - * If a given port reference `b.m`, where `b` is a bank and `m` is a multiport, - * is unqualified, this function iterates over bank members first, then ports. - * E.g., if `b` and `m` have width 2, it returns `[b0.m0, b0.m1, b1.m0, b1.m1]`. - * If instead a given port reference has the form `interleaved(b.m)`, where `b` is a - * bank and `m` is a multiport, this function iterates over ports first, - * then bank members. E.g., if `b` and `m` have width 2, it returns - * `[b0.m0, b1.m0, b0.m1, b1.m1]`. + * If a given port reference has the form `interleaved(b.m)`, where `b` is + * a bank and `m` is a multiport, then the corresponding range in the returned + * list is marked interleaved. + * + * For example, if `b` and `m` have width 2, without the interleaved keyword, + * the returned range represents the sequence `[b0.m0, b0.m1, b1.m0, b1.m1]`. + * With the interleaved marking, the returned range represents the sequence + * `[b0.m0, b1.m0, b0.m1, b1.m1]`. Both ranges will have width 4. */ private List listPortInstances(List references) { List result = new ArrayList(); @@ -1077,8 +975,7 @@ private List listPortInstances(List references) { } // First, figure out which reactor we are dealing with. // The reactor we want is the container of the port. - // If the port reference has no container, then the reactor is this one, - // or if this one is a bank, the next available bank member. + // If the port reference has no container, then the reactor is this one. var reactor = this; if (portRef.getContainer() != null) { reactor = getChildReactorInstance(portRef.getContainer()); @@ -1087,7 +984,19 @@ private List listPortInstances(List references) { // Skip this portRef so that diagram synthesis can complete. if (reactor != null) { PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); - PortInstance.Range range = portInstance.newRange(0, portInstance.width); + + // If the reactor is a contained reactor that is a bank, get its width. + int bankWidth = 1; + if (reactor != this) bankWidth = reactor.width(); + + Connection connection = null; + if (portRef.eContainer() instanceof Connection) { + connection = (Connection)portRef.eContainer(); + } + + PortInstance.Range range = portInstance.newRange( + 0, 0, portInstance.width * bankWidth, portRef.isInterleaved(), connection + ); result.add(range); } } @@ -1097,16 +1006,6 @@ private List listPortInstances(List references) { ////////////////////////////////////////////////////// //// Private fields. - /** - * A map of connections as they appear in a visualization of the program. - * For each connection, there is map from source ports (single ports and multiports) - * on the left side of the connection to a set of destination ports (single ports - * and multiports) on the right side of the connection. For banks of reactors, - * this includes only the connections to and from the first element of the bank - */ - private Map>> connections - = new LinkedHashMap>>(); - /** * Indicator of whether levels have already been assigned. * This has value 0 if no attempt has been made, 1 if levels have been @@ -1115,7 +1014,10 @@ private List listPortInstances(List references) { */ private int levelsAssignedAlready = 0; - /** Number of reactor instances (if a bank, in a bank element). */ + /** + * One plus the number of contained reactor instances + * (if a bank, in a bank element). + */ private int numReactorInstances = 1; /** @@ -1131,7 +1033,4 @@ private List listPortInstances(List references) { * this reactor and its contained reactors. */ private int totalNumberOfReactionsCache = -1; - - /** Total number of reactor instances, including bank width. */ - private int totalNumReactorInstances = 1; } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 007f51e8f2..976476d8f7 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -74,6 +74,7 @@ import org.lflang.generator.TimerInstance import org.lflang.generator.TriggerInstance import org.lflang.lf.Action import org.lflang.lf.ActionOrigin +import org.lflang.lf.Assignment import org.lflang.lf.Code import org.lflang.lf.Delay import org.lflang.lf.Input @@ -1427,7 +1428,7 @@ class CGenerator extends GeneratorBase { val reactorInstance = main.getChildReactorInstance(federate.instantiation) for (param : reactorInstance.parameters) { if (param.name.equalsIgnoreCase("STP_offset") && param.type.isTime) { - val stp = param.init.get(0).getTimeValue + val stp = param.getInitialValue.get(0).getTimeValue if (stp !== null) { pr(''' set_stp_offset(«CUtil.VG.getTargetTime(stp)»); @@ -3564,13 +3565,16 @@ class CGenerator extends GeneratorBase { // The type of each struct type is different, so the type of this // array is vague. pr(initializeTriggerObjects, ''' - void* selfStructs[«main.getTotalNumReactorInstances()»]; + void* self_structs[«main.getTotalNumReactorInstances()»]; ''') + pr(initializeTriggerObjects, + '// ***** Start initializing ' + main.name) + // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' «CUtil.selfType(main)»* «CUtil.selfName(main)» = new_«main.name»(); - selfStructs[0] = «CUtil.selfName(main)»; + self_structs[0] = «CUtil.selfName(main)»; ''') // Generate code for top-level parameters, actions, timers, and reactions that @@ -3593,7 +3597,7 @@ class CGenerator extends GeneratorBase { generateSetDeadline(reactionsInFederate); generateStartTimeStep(main); - pr(initializeTriggerObjects, "//***** End initializing " + main.name); + pr(initializeTriggerObjects, "// ***** End initializing " + main.name); } /** @@ -3610,7 +3614,7 @@ class CGenerator extends GeneratorBase { var reactorClass = instance.definition.reactorClass var fullName = instance.fullName pr(initializeTriggerObjects, - '// ************* Instance ' + fullName + ' of class ' + reactorClass.name) + '// ***** Start initializing ' + fullName + ' of class ' + reactorClass.name) var nameOfSelfStruct = CUtil.selfName(instance) var structType = CUtil.selfType(reactorClass) @@ -3628,12 +3632,11 @@ class CGenerator extends GeneratorBase { // Record the self struct on the big array of self structs. pr(initializeTriggerObjects, ''' - selfStructs[«CUtil.indexExpression(instance)»] = «nameOfSelfStruct»; + self_structs[«CUtil.indexExpression(instance)»] = «nameOfSelfStruct»; ''') // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. - pr(initializeTriggerObjects, "//***** Start initializing " + fullName) generateTraceTableEntries(instance, instance.actions, instance.timers) generateReactorInstanceExtension(instance, instance.reactions) @@ -3966,25 +3969,6 @@ class CGenerator extends GeneratorBase { return result.toString } - protected def getInitializer(StateVar state, ReactorInstance parent) { - var list = new LinkedList(); - - for (i : state?.init) { - if (i.parameter !== null) { - list.add(CUtil.selfRef(parent) + "->" + i.parameter.name) - } else if (state.isOfTimeType) { - list.add(CUtil.VG.getTargetTime(i)) - } else { - list.add(CUtil.VG.getTargetValue(i)) - } - } - - if (list.size == 1) - return list.get(0) - else - return list.join('{', ', ', '}', [it]) - } - /** * Set the reaction priorities based on dependency analysis. * @param reactor The reactor on which to do this. @@ -4856,12 +4840,12 @@ class CGenerator extends GeneratorBase { var structType = CUtil.selfType(reactor) if (reactor.isBank) { pr(builder, ''' - «structType»** «nameOfSelfStruct» = («structType»**)&selfStructs[ + «structType»** «nameOfSelfStruct» = («structType»**)&self_structs[ «CUtil.indexExpression(reactor.parent)» + «reactor.getIndexOffset»]; ''') } else { pr(builder, ''' - «structType»* «nameOfSelfStruct» = («structType»*)selfStructs[ + «structType»* «nameOfSelfStruct» = («structType»*)self_structs[ «CUtil.indexExpression(reactor)»]; ''') } @@ -4881,7 +4865,7 @@ class CGenerator extends GeneratorBase { // the range of channels. var startChannel = 0; for (eventualSource: port.eventualSources()) { - val src = eventualSource.portInstance; + val src = eventualSource.getPort(); if (src != port && currentFederate.contains(src.parent)) { val temp = new StringBuilder(); // The eventual source is different from the port and is in the federate. @@ -4904,12 +4888,12 @@ class CGenerator extends GeneratorBase { // Connect «src.getFullName» to port «port.getFullName» { // To scope variable j int j = «eventualSource.startChannel»; - for (int i = «startChannel»; i < «eventualSource.channelWidth» + «startChannel»; i++) { + for (int i = «startChannel»; i < «eventualSource.totalWidth» + «startChannel»; i++) { «CUtil.destinationRef(port)»[i] = («destStructType»*)«modifier»«CUtil.sourceRef(src)»[j++]; } } ''') - startChannel += eventualSource.channelWidth; + startChannel += eventualSource.totalWidth; } else { // Source is a multiport, destination is a single port. pr(temp, ''' @@ -5439,14 +5423,83 @@ class CGenerator extends GeneratorBase { override getNetworkBufferType() '''uint8_t*''' - protected def String getInitializer(ParameterInstance p) { - - if (p.type.isList && p.init.size > 1) { - return p.init.join('{', ', ', '}', [CUtil.VG.getTargetValue(it)]) + /** + * Return a C expression that can be used to initialize the specified + * state variable within the specified parent. If the state variable + * initializer refers to parameters of the parent, then those parameter + * references are replaced with accesses to the self struct of the parent. + */ + protected def String getInitializer(StateVar state, ReactorInstance parent) { + var list = new LinkedList(); + + for (i : state?.init) { + if (i.parameter !== null) { + list.add(CUtil.selfRef(parent) + "->" + i.parameter.name) + } else if (state.isOfTimeType) { + list.add(CUtil.VG.getTargetTime(i)) } else { - return CUtil.VG.getTargetValue(p.init.get(0)) + list.add(CUtil.VG.getTargetValue(i)) } + } + + if (list.size == 1) { + return list.get(0) + } else { + return list.join('{', ', ', '}', [it]) + } + } + + /** + * Return a C expression that can be used to initialize the specified + * parameter instance. If the parameter initializer refers to other + * parameters, then those parameter references are replaced with + * accesses to the self struct of the parents of those parameters. + */ + protected def String getInitializer(ParameterInstance p) { + // Handle the bank_index parameter. + if (p.name.equals("bank_instance")) { + return CUtil.bankIndex(p.parent); + } + // Handle overrides in the intantiation. + // In case there is more than one assignment to this parameter, we need to + // find the last one. + var lastAssignment = null as Assignment; + for (assignment: p.parent.definition.parameters) { + if (assignment.lhs == p.definition) { + lastAssignment = assignment; + } + } + var list = new LinkedList(); + if (lastAssignment !== null) { + // The parameter has an assignment. + // Right hand side can be a list. Collect the entries. + for (value: lastAssignment.rhs) { + if (value.parameter !== null) { + // The parameter is being assigned a parameter value. + // Assume that parameter belongs to the parent's parent. + // This should have been checked by the validator. + list.add(CUtil.selfRef(p.parent.parent) + "->" + value.parameter.name); + } else { + list.add(CUtil.VG.getTargetValue(value)) + } + } + } else { + // there was no assignment in the instantiation. So just use the + // parameter's initial value. + for (i : p.parent.initialParameterValue(p.definition)) { + if (p.definition.isOfTimeType) { + list.add(CUtil.VG.getTargetTime(i)) + } else { + list.add(CUtil.VG.getTargetValue(i)) + } + } + } + if (list.size == 1) { + return list.get(0) + } else { + return list.join('{', ', ', '}', [it]) + } } override supportsGenerics() { @@ -5595,17 +5648,17 @@ class CGenerator extends GeneratorBase { // The port may be deeper in the hierarchy. var portChannelCount = 0; for (eventualSource: port.eventualSources()) { - val sourcePort = eventualSource.portInstance + val sourcePort = eventualSource.getPort(); if (sourcePort.isMultiport && port.isMultiport) { // Both source and destination are multiports. pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - for (int i = 0; i < «eventualSource.channelWidth»; i++) { + for (int i = 0; i < «eventualSource.totalWidth»; i++) { «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«CUtil.sourceRef(sourcePort)»[i + «eventualSource.startChannel»]; } ''') - portChannelCount += eventualSource.channelWidth; + portChannelCount += eventualSource.totalWidth; } else if (sourcePort.isMultiport) { // Destination is not a multiport, so the channelWidth of the source port should be 1. pr(''' @@ -5696,10 +5749,10 @@ class CGenerator extends GeneratorBase { // For a single port, there should be only one sendingRange. if (output.isMultiport()) { val start = sendingRange.startChannel; - val end = sendingRange.startChannel + sendingRange.channelWidth; + val end = sendingRange.startChannel + sendingRange.totalWidth; // Eliminate the for loop for the case where range.channelWidth == 1, // a common situation on multiport to bank messaging. - if (sendingRange.channelWidth == 1) { + if (sendingRange.totalWidth == 1) { pr(''' «CUtil.sourceRef(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') @@ -5756,7 +5809,7 @@ class CGenerator extends GeneratorBase { // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { val start = sendingRange.startChannel; - val end = sendingRange.startChannel + sendingRange.channelWidth; + val end = sendingRange.startChannel + sendingRange.totalWidth; pr(''' for (int i = «start»; i < «end»; i++) { «CUtil.sourceRef(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; @@ -6074,7 +6127,7 @@ class CGenerator extends GeneratorBase { pr(temp, "int _lf_trigger_index = 0;"); var destRangeCount = 0; for (destinationRange : range.destinations) { - val destination = destinationRange.getPortInstance(); + val destination = destinationRange.getPort(); val temp2 = new StringBuilder(); if (destination.isOutput) { @@ -6111,7 +6164,7 @@ class CGenerator extends GeneratorBase { // Record the total size of the array. pr(''' - for (int i = 0; i < «range.channelWidth»; i++) { + for (int i = 0; i < «range.totalWidth»; i++) { // Reaction «reaction.index» of «name» triggers «channelCount» // downstream reactions through port «port.getFullName»[«channelCount» + i]. «selfStruct»->_lf__reaction_«reaction.index».triggered_sizes[«channelCount» + i] = «destRangeCount»; @@ -6124,14 +6177,14 @@ class CGenerator extends GeneratorBase { // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» trigger_t** triggerArray = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); - for (int i = 0; i < «range.channelWidth»; i++) { + for (int i = 0; i < «range.totalWidth»; i++) { «selfStruct»->_lf__reaction_«reaction.index».triggers[«channelCount» + i] = triggerArray; } // Fill the trigger array. «temp.toString()» } ''') - channelCount += range.channelWidth; + channelCount += range.totalWidth; } } else { // Count the port even if it is not contained in the federate because effect diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 5af4842616..f334da3d48 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -295,11 +295,11 @@ class PythonGenerator extends CGenerator { * @return Initialization code */ protected def String getPythonInitializer(ParameterInstance p) { - if (p.init.size > 1) { + if (p.getInitialValue.size > 1) { // parameters are initialized as immutable tuples - return p.init.join('(', ', ', ')', [it.pythonTargetValue]) + return p.getInitialValue.join('(', ', ', ')', [it.pythonTargetValue]) } else { - return p.init.get(0).getPythonTargetValue + return p.getInitialValue.get(0).getPythonTargetValue } } diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index a73e1733c7..49517ffabe 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -112,7 +112,7 @@ private void addEffects(ReactionInstance reaction) { addEdge(effect, reaction); PortInstance orig = (PortInstance) effect; orig.getDependentPorts().forEach(dest -> { - recordDependency(reaction, orig, dest.getPortInstance()); + recordDependency(reaction, orig, dest.getPort(), dest.connection); }); } } @@ -132,7 +132,7 @@ private void addSources(ReactionInstance reaction) { addEdge(reaction, source); PortInstance dest = (PortInstance) source; dest.getDependsOnPorts().forEach(orig -> { - recordDependency(reaction, orig.getPortInstance(), dest); + recordDependency(reaction, orig.getPort(), dest, orig.connection); }); } } @@ -142,39 +142,20 @@ private void addSources(ReactionInstance reaction) { * Record a dependency between two port instances, but only if there is a * zero-delay path from origin to destination. * - * @param reaction A reaction that has one of the given port as a source or + * @param reaction A reaction that has one of the given ports as a source or * effect. * @param orig The upstream port. * @param dest The downstream port. + * @param connection The connection creating this dependency or null if not + * created by a connection. */ private void recordDependency(ReactionInstance reaction, PortInstance orig, - PortInstance dest) { - // Note: a reaction always has a parent, but it might not have a - // grandparent. Hence, the first argument given to getConnection might - // be null. - if (!dependencyBroken( - getConnection(reaction.getParent().getParent(), orig, dest))) { + PortInstance dest, Connection connection) { + if (!dependencyBroken(connection)) { addEdge(dest, orig); } } - /** - * Look up the AST node that describes the connection between the two given - * port instances and return it if there is one. - * - * @param container The reactor instance in which to perform the look up. - * @param orig The upstream port. - * @param dest The downstream port. - * @return The corresponding Connection object or null if there is none. - */ - private Connection getConnection(ReactorInstance container, - PortInstance orig, PortInstance dest) { - if (container == null) { - return null; - } - return container.getConnection(orig, dest); - } - /** * Report whether or not the given connection breaks dependencies or not. * diff --git a/test/C/src/multiport/NestedInterleavedBanks.lf b/test/C/src/multiport/NestedInterleavedBanks.lf new file mode 100644 index 0000000000..be4f087576 --- /dev/null +++ b/test/C/src/multiport/NestedInterleavedBanks.lf @@ -0,0 +1,36 @@ +/** + * Test nested banks with interleaving. + * @author Edward A. Lee + */ +target C; +reactor A(bank_index:int(0), outer_bank_index:int(0)) { + output[2] p:int; + reaction(startup) -> p {= + for (int i = 0; i < p_width; i++) { + SET(p[i], self->outer_bank_index * 4 + self->bank_index * 2 + i + 1); + info_print("A sending %d.", p[i]->value); + } + =} +} +reactor B(bank_index:int(0)) { + output[4] q:int; + a = new[2] A(outer_bank_index = bank_index); + interleaved(a.p) -> q; +} +reactor C { + input[8] i:int; + reaction(i) {= + int expected[] = {1, 3, 2, 4, 5, 7, 6, 8}; + for(int j = 0; j < i_width; j++) { + info_print("C received %d.", i[j]->value); + if (i[j]->value != expected[j]) { + error_print_and_exit("Expected %d.", expected[j]); + } + } + =} +} +main reactor { + b = new[2] B(); + c = new C(); + b.q -> c.i; +} \ No newline at end of file From 6948a72d66c3977da694ae1ed70f7a9310966eac Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 27 Nov 2021 16:12:32 -0800 Subject: [PATCH 050/221] Get diagrams working even with nested banks. --- .../styles/LinguaFrancaShapeExtensions.xtend | 2 +- .../org/lflang/generator/PortInstance.java | 24 ++++++++++--------- .../org/lflang/generator/ReactorInstance.java | 16 +++++-------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend index a51683f029..b8f40c5dc1 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend @@ -294,7 +294,7 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { if (SHOW_REACTION_LEVEL.booleanValue) { // Force calculation of levels for reactions. This calculation // will only be done once. Note that if this fails due to a causality loop, - // then some reactions will have level -1.s + // then some reactions will have level -1. reaction.root().assignLevels(); contentContainer.addText("level: " + Long.toString(reaction.level)) => [ fontBold = false diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 425b2ec8c4..f8dd98957d 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -181,7 +181,7 @@ public List eventualDestinations() { // Destination range spills over into the next multicast iteration. // Need to split the destination range. SendRange residualDst = (SendRange)dstSend.tail(totalWidth - sourceWidthCovered); - dstSend.truncate(totalWidth - sourceWidthCovered); + dstSend = dstSend.truncate(totalWidth - sourceWidthCovered); result.add(dstSend); dstSend = residualDst; sourceWidthCovered = 0; @@ -208,9 +208,9 @@ public List eventualDestinations() { candidate.destinations.addAll(next.destinations); if (candidate.getTotalWidth() < next.getTotalWidth()) { // The next range has more channels connected to this sender. - next.truncate(candidate.getTotalWidth()); + next = next.truncate(candidate.getTotalWidth()); // Truncate the destinations just imported. - candidate.truncate(candidate.getTotalWidth()); + candidate = candidate.truncate(candidate.getTotalWidth()); } else { // We are done with next and can discard it. next = result.poll(); @@ -232,7 +232,7 @@ public List eventualDestinations() { } else { // Ranges overlap. Have to split candidate. SendRange candidateTail = (SendRange)candidate.tail(next.startChannel); - candidate.truncate(next.startChannel); + candidate = candidate.truncate(next.startChannel); result.add(candidate); candidate = candidateTail; } @@ -509,15 +509,16 @@ protected Range newRange( } /** - * Override the base class to also trucate the destinations. + * Override the base class to also truncate the destinations. * @param newWidth The new width. */ @Override - protected void truncate(int newWidth) { - super.truncate(newWidth); + protected SendRange truncate(int newWidth) { + SendRange result = (SendRange)super.truncate(newWidth); for (Range destination : destinations) { - destination.truncate(newWidth); + result.destinations.add(destination.truncate(newWidth)); } + return result; } private int _numberOfDestinationReactors = -1; // Never access this directly. @@ -625,11 +626,12 @@ public String toString() { } /** - * Modify this range to reduce its total width to the specified width. + * Return a new range that is identical to this range but + * with a reduced total width to the specified width. * @param newWidth The new width. */ - protected void truncate(int newWidth) { - totalWidth = newWidth; + protected Range truncate(int newWidth) { + return newRange(startChannel, startBank, newWidth, interleaved, connection); } /** diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 09ffbc1ef7..735074e6be 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -912,17 +912,15 @@ private void establishPortConnections() { } else { reporter.reportWarning(connection, "Destination is wider than the source. Inputs will be missing."); + break; } - break; } dst = dstRanges.next(); src = srcRanges.next(); } else if (dst.getTotalWidth() < src.getTotalWidth()) { // Split the left range in two. - PortInstance.Range remaining = src.tail(dst.getTotalWidth()); - src.truncate(dst.getTotalWidth()); // Shorten source range to match destination. - connectPortInstances(src, dst); - src = remaining; + connectPortInstances(src.truncate(dst.getTotalWidth()), dst); + src = src.tail(dst.getTotalWidth()); if (!dstRanges.hasNext()) { reporter.reportWarning(connection, "Source is wider than the destination. Outputs will be lost."); @@ -931,18 +929,16 @@ private void establishPortConnections() { dst = dstRanges.next(); } else if (src.getTotalWidth() < dst.getTotalWidth()) { // Split the right range in two. - PortInstance.Range remaining = dst.tail(src.getTotalWidth()); - dst.truncate(src.getTotalWidth()); - connectPortInstances(src, dst); - dst = remaining; + connectPortInstances(src, dst.truncate(src.getTotalWidth())); + dst = dst.tail(src.getTotalWidth()); if (!srcRanges.hasNext()) { if (connection.isIterated()) { srcRanges = leftPorts.iterator(); } else { reporter.reportWarning(connection, "Destination is wider than the source. Inputs will be missing."); + break; } - break; } src = srcRanges.next(); } From 09771720ecedf1ce9ff5c666710f90ebc4b24c3a Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 27 Nov 2021 16:43:25 -0800 Subject: [PATCH 051/221] Composition instead of inheritance for TargetTypes. These changes are made directly in response to a TODO that previously existed in TargetTypes: "currently, {@link GeneratorBase} implements this interface, it should instead contain an instance." --- org.lflang/src/org/lflang/ASTUtils.xtend | 10 +- .../src/org/lflang/federated/FedASTUtils.java | 2 +- .../org/lflang/generator/GeneratorBase.xtend | 21 +- .../lflang/generator/JavaGeneratorBase.java | 2 +- .../src/org/lflang/generator/TargetTypes.java | 39 +++- .../org/lflang/generator/c/CGenerator.xtend | 195 +++++------------- .../src/org/lflang/generator/c/CTypes.java | 99 +++++++++ .../org/lflang/generator/cpp/CppGenerator.kt | 5 +- .../generator/python/PythonGenerator.xtend | 47 ++--- .../lflang/generator/python/PythonTypes.java | 59 ++++++ .../lflang/generator/rust/RustGenerator.kt | 4 +- .../org/lflang/generator/ts/TSGenerator.kt | 46 +---- .../src/org/lflang/generator/ts/TSTypes.kt | 41 ++++ 13 files changed, 322 insertions(+), 248 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/c/CTypes.java create mode 100644 org.lflang/src/org/lflang/generator/python/PythonTypes.java create mode 100644 org.lflang/src/org/lflang/generator/ts/TSTypes.kt diff --git a/org.lflang/src/org/lflang/ASTUtils.xtend b/org.lflang/src/org/lflang/ASTUtils.xtend index 973260b401..765adaaf8f 100644 --- a/org.lflang/src/org/lflang/ASTUtils.xtend +++ b/org.lflang/src/org/lflang/ASTUtils.xtend @@ -108,8 +108,8 @@ class ASTUtils { // Assume all the types are the same, so just use the first on the right. val type = (connection.rightPorts.get(0).variable as Port).type val delayClass = getDelayClass(type, generator) - val generic = generator.supportsGenerics - ? generator.getTargetType(InferredType.fromAST(type)) + val generic = generator.getTargetTypes().supportsGenerics + ? generator.getTargetTypes().getTargetType(InferredType.fromAST(type)) : "" val delayInstance = getDelayInstance(delayClass, connection, generic, !generator.generateAfterDelaysWithVariableWidth) @@ -302,7 +302,7 @@ class ASTUtils { * @param generator A code generator. */ private static def Reactor getDelayClass(Type type, GeneratorBase generator) { - val className = generator.supportsGenerics ? + val className = generator.getTargetTypes().supportsGenerics ? GeneratorBase.GEN_DELAY_CLASS_NAME : { val id = Integer.toHexString( InferredType.fromAST(type).toText.hashCode) @@ -345,7 +345,7 @@ class ASTUtils { action.minDelay.parameter = delayParameter action.origin = ActionOrigin.LOGICAL - if (generator.supportsGenerics) { + if (generator.getTargetTypes().supportsGenerics) { action.type = factory.createType action.type.id = "T" } else { @@ -391,7 +391,7 @@ class ASTUtils { delayClass.reactions.add(r1) // Add a type parameter if the target supports it. - if (generator.supportsGenerics) { + if (generator.getTargetTypes().supportsGenerics) { val parm = factory.createTypeParm parm.literal = generator.generateDelayGeneric() delayClass.typeParms.add(parm) diff --git a/org.lflang/src/org/lflang/federated/FedASTUtils.java b/org.lflang/src/org/lflang/federated/FedASTUtils.java index 6d099524bb..fe2fce119c 100644 --- a/org.lflang/src/org/lflang/federated/FedASTUtils.java +++ b/org.lflang/src/org/lflang/federated/FedASTUtils.java @@ -628,7 +628,7 @@ private static void addNetworkOutputControlReaction( // The input needs a type. All targets have a Time type, so we use that. Type portType = factory.createType(); - portType.setId(generator.getTargetTimeType()); + portType.setId(generator.getTargetTypes().getTargetTimeType()); newTriggerForControlReactionVariable.setType(portType); top.getInputs().add(newTriggerForControlReactionVariable); diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 063ee9e2e7..5c55e2f177 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -438,6 +438,11 @@ abstract class GeneratorBase extends JavaGeneratorBase { return errorReporter.getErrorsOccurred(); } + /* + * Return the TargetTypes instance associated with this. + */ + abstract def TargetTypes getTargetTypes(); + /** * Generate code for the body of a reaction that takes an input and * schedules an action with the value of that input. @@ -1146,20 +1151,4 @@ abstract class GeneratorBase extends JavaGeneratorBase { * Return the Targets enum for the current target */ abstract def Target getTarget() - - protected def getTargetType(Parameter p) { - return p.inferredType.targetType - } - - protected def getTargetType(StateVar s) { - return s.inferredType.targetType - } - - protected def getTargetType(Action a) { - return a.inferredType.targetType - } - - protected def getTargetType(Port p) { - return p.inferredType.targetType - } } diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java b/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java index 23f3c330bd..5e045468f7 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorBase.java @@ -14,7 +14,7 @@ * Generator base class for shared code between code generators. * This is created to ease our migration from Xtend. */ -public abstract class JavaGeneratorBase extends AbstractLFValidator implements TargetTypes { +public abstract class JavaGeneratorBase extends AbstractLFValidator { //////////////////////////////////////////// //// Protected fields. diff --git a/org.lflang/src/org/lflang/generator/TargetTypes.java b/org.lflang/src/org/lflang/generator/TargetTypes.java index 042d8e6ad7..8006396c5c 100644 --- a/org.lflang/src/org/lflang/generator/TargetTypes.java +++ b/org.lflang/src/org/lflang/generator/TargetTypes.java @@ -8,6 +8,10 @@ import org.lflang.InferredType; import org.lflang.JavaAstUtils; import org.lflang.TimeValue; +import org.lflang.lf.Action; +import org.lflang.lf.Parameter; +import org.lflang.lf.Port; +import org.lflang.lf.StateVar; import org.lflang.lf.Time; import org.lflang.lf.TimeUnit; import org.lflang.lf.Type; @@ -18,9 +22,6 @@ * utilities to convert LF expressions and types to the target * language. Each code generator is expected to use at least one * language-specific instance of this interface. - * - * TODO currently, {@link GeneratorBase} implements this interface, - * it should instead contain an instance. */ public interface TargetTypes { @@ -159,6 +160,38 @@ default String getTargetType(InferredType type) { return type.toText(); } + /** + * Return a string representing the type of the given + * parameter. + */ + default String getTargetType(Parameter p) { + return getTargetType(JavaAstUtils.getInferredType(p)); + } + + /** + * Return a string representing the type of the given + * state variable. + */ + default String getTargetType(StateVar s) { + return getTargetType(JavaAstUtils.getInferredType(s)); + } + + /** + * Return a string representing the type of the given + * action. + */ + default String getTargetType(Action a) { + return getTargetType(JavaAstUtils.getInferredType(a)); + } + + /** + * Return a string representing the type of the given + * port. + */ + default String getTargetType(Port p) { + return getTargetType(JavaAstUtils.getInferredType(p)); + } + /** * Returns the representation of the given initializer * expression in target code. The given type, if non-null, diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 3aa66aab48..edfa5dc048 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -358,9 +358,12 @@ class CGenerator extends GeneratorBase { // Indicate whether the generator is in Cpp mode or not var boolean CCppMode = false; + var CTypes types; + new(FileConfig fileConfig, ErrorReporter errorReporter, boolean CCppMode) { super(fileConfig, errorReporter) this.CCppMode = CCppMode; + types = new CTypes(errorReporter) } new(FileConfig fileConfig, ErrorReporter errorReporter) { @@ -1784,12 +1787,12 @@ class CGenerator extends GeneratorBase { var StringBuilder federatedExtension = new StringBuilder(); if (isFederatedAndDecentralized) { federatedExtension.append(''' - «targetTagType» intended_tag; + «types.getTargetTagType» intended_tag; '''); } if (isFederated) { federatedExtension.append(''' - «targetTimeType» physical_time_of_arrival; + «types.getTargetTimeType» physical_time_of_arrival; '''); } // First, handle inputs. @@ -1877,21 +1880,7 @@ class CGenerator extends GeneratorBase { } // Do not convert to lf_token_t* using lfTypeToTokenType because there // will be a separate field pointing to the token. - // val portType = lfTypeToTokenType(port.inferredType) - val portType = port.inferredType.targetType - // If the port type has the form type[number], then treat it specially - // to get a valid C type. - val matcher = arrayPatternFixed.matcher(portType) - if (matcher.find()) { - // for int[10], the first match is int, the second [10]. - // The following results in: int* _lf_foo[10]; - // if the port is an input and not a multiport. - // An output multiport will result in, for example - // int _lf_out[4][10]; - return '''«matcher.group(1)» value«matcher.group(2)»;'''; - } else { - return '''«portType» value;''' - } + return types.getVariableDeclaration(port.inferredType, "value") + ";" } /** @@ -1911,23 +1900,7 @@ class CGenerator extends GeneratorBase { } // Do not convert to lf_token_t* using lfTypeToTokenType because there // will be a separate field pointing to the token. - val actionType = action.inferredType.targetType - // If the input type has the form type[number], then treat it specially - // to get a valid C type. - val matcher = arrayPatternFixed.matcher(actionType) - if (matcher.find()) { - // for int[10], the first match is int, the second [10]. - // The following results in: int* foo; - return '''«matcher.group(1)»* value;'''; - } else { - val matcher2 = arrayPatternVariable.matcher(actionType) - if (matcher2.find()) { - // for int[], the first match is int. - // The following results in: int* foo; - return '''«matcher2.group(1)»* value;'''; - } - return '''«actionType» value;''' - } + return types.getTargetType(action) + " value;" } /** @@ -2268,7 +2241,7 @@ class CGenerator extends GeneratorBase { def generateParametersForReactor(StringBuilder builder, Reactor reactor) { for (parameter : reactor.allParameters) { prSourceLineNumber(builder, parameter) - pr(builder, parameter.getInferredType.targetType + ' ' + parameter.name + ';'); + pr(builder, types.getTargetType(parameter) + ' ' + parameter.name + ';'); } } @@ -2281,7 +2254,7 @@ class CGenerator extends GeneratorBase { def generateStateVariablesForReactor(StringBuilder builder, Reactor reactor) { for (stateVar : reactor.allStateVars) { prSourceLineNumber(builder, stateVar) - pr(builder, stateVar.getInferredType.targetType + ' ' + stateVar.name + ';'); + pr(builder, types.getTargetType(stateVar) + ' ' + stateVar.name + ';'); } } @@ -2478,8 +2451,8 @@ class CGenerator extends GeneratorBase { var elementSize = "0" // If the action type is 'void', we need to avoid generating the code // 'sizeof(void)', which some compilers reject. - if (action.type !== null && action.targetType.rootType != 'void') { - elementSize = '''sizeof(«action.targetType.rootType»)''' + if (action.type !== null && types.getTargetType(action).rootType != 'void') { + elementSize = '''sizeof(«types.getTargetType(action).rootType»)''' } // Since the self struct is allocated using calloc, there is no need to set: @@ -2558,7 +2531,7 @@ class CGenerator extends GeneratorBase { } } if (variable instanceof Input) { - val rootType = variable.targetType.rootType + val rootType = types.getTargetType(variable).rootType // Since the self struct is allocated using calloc, there is no need to set: // self->_lf__«input.name».is_timer = false; // self->_lf__«input.name».offset = 0LL; @@ -2723,7 +2696,7 @@ class CGenerator extends GeneratorBase { // Inherited intended tag. This will take the minimum // intended_tag of all input triggers - «targetTagType» inherited_min_intended_tag = («targetTagType») { .time = FOREVER, .microstep = UINT_MAX }; + «types.getTargetTagType» inherited_min_intended_tag = («types.getTargetTagType») { .time = FOREVER, .microstep = UINT_MAX }; ''') pr(intendedTagInheritenceCode, ''' // Find the minimum intended tag @@ -3753,11 +3726,9 @@ class CGenerator extends GeneratorBase { var payloadSize = "0" if (!type.isUndefined) { - var String typeStr = type.targetType + var String typeStr = types.getTargetType(type) if (isTokenType(type)) { typeStr = typeStr.rootType - } else { - typeStr = type.targetType } if (typeStr !== null && !typeStr.equals("") && !typeStr.equals("void")) { payloadSize = '''sizeof(«typeStr»)''' @@ -3832,22 +3803,9 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, nameOfSelfStruct + "->" + stateVar.name + " = " + initializer + ";") } else { - // Array type has to be handled specially because C doesn't accept - // type[] as a type designator. - // Use the superclass to avoid [] being replaced by *. - var type = super.getTargetType(stateVar.inferredType) - val matcher = arrayPatternVariable.matcher(type) - - var declaration = type + " _initial"; - if (matcher.find()) { - // If the state type ends in [], then we have to move the [] - // because C is very picky about where this goes. It has to go - // after the variable name. - declaration = matcher.group(1) + " _initial[]" - } pr(initializeTriggerObjects, ''' { // For scoping - static «declaration» = «initializer»; + static «types.getVariableDeclaration(stateVar.inferredType, "_initial")» = «initializer»; «nameOfSelfStruct»->«stateVar.name» = _initial; } // End scoping. ''' @@ -3880,27 +3838,17 @@ class CGenerator extends GeneratorBase { */ def void generateParameterInitialization(ReactorInstance instance) { var nameOfSelfStruct = CUtil.selfName(instance) - // Array type parameters have to be handled specially. - // Use the superclass getTargetType to avoid replacing the [] with *. for (parameter : instance.parameters) { // NOTE: we now use the resolved literal value. For better efficiency, we could // store constants in a global array and refer to its elements to avoid duplicate // memory allocations. - val targetType = super.getTargetType(parameter.type) - val matcher = arrayPatternVariable.matcher(targetType) - if (matcher.find()) { - // Use an intermediate temporary variable so that parameter dependencies - // are resolved correctly. - val temporaryVariableName = parameter.uniqueID - pr(initializeTriggerObjects, ''' - static «matcher.group(1)» «temporaryVariableName»[] = «parameter.getInitializer»; - «nameOfSelfStruct»->«parameter.name» = «temporaryVariableName»; - ''') - } else { - pr(initializeTriggerObjects, ''' - «nameOfSelfStruct»->«parameter.name» = «parameter.getInitializer»; - ''') - } + // Use an intermediate temporary variable so that parameter dependencies + // are resolved correctly. + val temporaryVariableName = parameter.uniqueID + pr(initializeTriggerObjects, ''' + static «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; + «nameOfSelfStruct»->«parameter.name» = «temporaryVariableName»; + ''') } } @@ -4025,6 +3973,10 @@ class CGenerator extends GeneratorBase { } } + override getTargetTypes() { + return types; + } + // ////////////////////////////////////////// // // Protected methods. @@ -4069,7 +4021,7 @@ class CGenerator extends GeneratorBase { // by both the action handling code and the input handling code. ''' «DISABLE_REACTION_INITIALIZATION_MARKER» - self->_lf_«outputName».value = («action.inferredType.targetType»)self->_lf__«action.name».token->value; + self->_lf_«outputName».value = («types.getTargetType(action)»)self->_lf__«action.name».token->value; self->_lf_«outputName».token = (lf_token_t*)self->_lf__«action.name».token; ((lf_token_t*)self->_lf__«action.name».token)->ref_count++; self->_lf_«outputName».is_present = true; @@ -4114,11 +4066,17 @@ class CGenerator extends GeneratorBase { // If it is "string", then change it to "char*". // This string is dynamically allocated, and type 'string' is to be // used only for statically allocated strings. - if (action.type.targetType == "string") { + // FIXME: Is the getTargetType method not responsible for generating the desired C code + // (e.g., char* rather than string)? If not, what exactly is that method + // responsible for? If generateNetworkReceiverBody has different requirements + // than those that the method was designed to satisfy, should we use a different + // method? The best course of action is not obvious, but we have a pattern of adding + // downstream patches to generated strings rather than fixing them at their source. + if (types.getTargetType(action) == "string") { action.type.code = null action.type.id = "char*" } - if ((receivingPort.variable as Port).type.targetType == "string") { + if (types.getTargetType(receivingPort.variable as Port) == "string") { (receivingPort.variable as Port).type.code = null (receivingPort.variable as Port).type.id = "char*" } @@ -4153,11 +4111,11 @@ class CGenerator extends GeneratorBase { } case SupportedSerializers.ROS2: { val portType = (receivingPort.variable as Port).inferredType - var portTypeStr = portType.targetType + var portTypeStr = types.getTargetType(portType) if (isTokenType(portType)) { throw new UnsupportedOperationException("Cannot handle ROS serialization when ports are pointers."); } else if (isSharedPtrType(portType)) { - val matcher = sharedPointerVariable.matcher(portType.targetType) + val matcher = sharedPointerVariable.matcher(portTypeStr) if (matcher.find()) { portTypeStr = matcher.group(1); } @@ -4278,12 +4236,12 @@ class CGenerator extends GeneratorBase { // string types need to be dealt with specially because they are hidden pointers. // void type is odd, but it avoids generating non-standard expression sizeof(void), // which some compilers reject. - lengthExpression = switch(type.targetType) { + lengthExpression = switch(types.getTargetType(type)) { case 'string': '''strlen(«sendRef»->value) + 1''' case 'void': '0' - default: '''sizeof(«type.targetType»)''' + default: '''sizeof(«types.getTargetType(type)»)''' } - pointerExpression = switch(type.targetType) { + pointerExpression = switch(types.getTargetType(type)) { case 'string': '''(unsigned char*) «sendRef»->value''' default: '''(unsigned char*)&«sendRef»->value''' } @@ -4298,11 +4256,11 @@ class CGenerator extends GeneratorBase { } case SupportedSerializers.ROS2: { var variableToSerialize = sendRef; - var typeStr = type.targetType + var typeStr = types.getTargetType(type) if (isTokenType(type)) { throw new UnsupportedOperationException("Cannot handle ROS serialization when ports are pointers."); } else if (isSharedPtrType(type)) { - val matcher = sharedPointerVariable.matcher(type.targetType) + val matcher = sharedPointerVariable.matcher(typeStr) if (matcher.find()) { typeStr = matcher.group(1); } @@ -4957,9 +4915,9 @@ class CGenerator extends GeneratorBase { pr(action, builder, ''' if («action.name»->has_value) { «IF type.isTokenType» - «action.name»->value = («type.targetType»)«tokenPointer»->value; + «action.name»->value = («types.getTargetType(type)»)«tokenPointer»->value; «ELSE» - «action.name»->value = *(«type.targetType»*)«tokenPointer»->value; + «action.name»->value = *(«types.getTargetType(type)»*)«tokenPointer»->value; «ENDIF» } ''') @@ -5008,7 +4966,7 @@ class CGenerator extends GeneratorBase { «structType»* «input.name» = self->_lf_«input.name»; if («input.name»->is_present) { «input.name»->length = «input.name»->token->length; - «input.name»->value = («inputType.targetType»)«input.name»->token->value; + «input.name»->value = («types.getTargetType(inputType)»)«input.name»->token->value; } else { «input.name»->length = 0; } @@ -5031,7 +4989,7 @@ class CGenerator extends GeneratorBase { «input.name»->token->next_free = _lf_more_tokens_with_ref_count; _lf_more_tokens_with_ref_count = «input.name»->token; } - «input.name»->value = («inputType.targetType»)«input.name»->token->value; + «input.name»->value = («types.getTargetType(inputType)»)«input.name»->token->value; } else { «input.name»->length = 0; } @@ -5064,7 +5022,7 @@ class CGenerator extends GeneratorBase { «input.name»[i]->token->next_free = _lf_more_tokens_with_ref_count; _lf_more_tokens_with_ref_count = «input.name»[i]->token; } - «input.name»[i]->value = («inputType.targetType»)«input.name»[i]->token->value; + «input.name»[i]->value = («types.getTargetType(inputType)»)«input.name»[i]->token->value; } else { «input.name»[i]->length = 0; } @@ -5277,30 +5235,9 @@ class CGenerator extends GeneratorBase { } } } - - /** - * Override the base class to replace a type of form type[] with type*. - * @param type The type. - */ - override String getTargetType(InferredType type) { - var result = super.getTargetType(type) - val matcher = arrayPatternVariable.matcher(result) - if (matcher.find()) { - return matcher.group(1) + '*' - } - return result - } protected def isSharedPtrType(InferredType type) { - if (type.isUndefined) - return false - val targetType = type.targetType - val matcher = sharedPointerVariable.matcher(targetType) - if (matcher.find()) { - true - } else { - false - } + return !type.isUndefined && sharedPointerVariable.matcher(types.getTargetType(type)).find() } /** Given a type for an input or output, return true if it should be @@ -5312,7 +5249,7 @@ class CGenerator extends GeneratorBase { protected def isTokenType(InferredType type) { if (type.isUndefined) return false - val targetType = type.targetType + val targetType = types.getTargetType(type) if (targetType.trim.matches("^\\w*\\[\\s*\\]$") || targetType.trim.endsWith('*')) { true } else { @@ -5320,10 +5257,8 @@ class CGenerator extends GeneratorBase { } } - /** If the type specification of the form type[] or - * type*, return the type. Otherwise remove the code delimiter, - * if there is one, and otherwise just return the argument - * unmodified. + /** If the type specification of the form {@code type[]}, + * {@code type*}, or {@code type}, return the type. * @param type A string describing the type. */ private def rootType(String type) { @@ -5384,16 +5319,6 @@ class CGenerator extends GeneratorBase { pr(builder, line) } } - - // Regular expression pattern for array types with specified length. - // \s is whitespace, \w is a word character (letter, number, or underscore). - // For example, for "foo[10]", the first match will be "foo" and the second "[10]". - static final Pattern arrayPatternFixed = Pattern.compile("^\\s*+(\\w+)\\s*(\\[[0-9]+\\])\\s*$"); - - // Regular expression pattern for array types with unspecified length. - // \s is whitespace, \w is a word character (letter, number, or underscore). - // For example, for "foo[]", the first match will be "foo". - static final Pattern arrayPatternVariable = Pattern.compile("^\\s*+(\\w+)\\s*\\[\\]\\s*$"); // Regular expression pattern for shared_ptr types. static final Pattern sharedPointerVariable = Pattern.compile("^std::shared_ptr<(\\S+)>$"); @@ -5413,19 +5338,7 @@ class CGenerator extends GeneratorBase { override getTarget() { return Target.C } - - override getTargetTimeType() '''interval_t''' - - override getTargetTagType() '''tag_t''' - - override getTargetUndefinedType() '''/* «errorReporter.reportError("undefined type")» */''' - override getTargetFixedSizeListType(String baseType, int size) '''«baseType»[«size»]''' - - override String getTargetVariableSizeListType( - String baseType) '''«baseType»[]''' - - override getNetworkBufferType() '''uint8_t*''' protected def String getInitializer(ParameterInstance p) { @@ -5438,10 +5351,6 @@ class CGenerator extends GeneratorBase { } - override supportsGenerics() { - return false - } - override generateDelayGeneric() { throw new UnsupportedOperationException("TODO: auto-generated method stub") } @@ -5640,7 +5549,7 @@ class CGenerator extends GeneratorBase { if (type.isTokenType) { // Create the template token that goes in the trigger struct. // Its reference count is zero, enabling it to be used immediately. - var rootType = type.targetType.rootType; + var rootType = types.getTargetType(type).rootType; // If the rootType is 'void', we need to avoid generating the code // 'sizeof(void)', which some compilers reject. val size = (rootType == 'void') ? '0' : '''sizeof(«rootType»)''' diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java new file mode 100644 index 0000000000..4e998ae99d --- /dev/null +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -0,0 +1,99 @@ +package org.lflang.generator.c; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.lflang.ErrorReporter; +import org.lflang.InferredType; +import org.lflang.generator.TargetTypes; + +public class CTypes implements TargetTypes { + + // Regular expression pattern for array types. + // For example, for "foo[10]", the first match will be "foo" and the second "[10]". + // For "foo[]", the first match will be "foo" and the second "". + static final Pattern arrayPattern = Pattern.compile("^\\s*+(\\w+)\\s*(\\[[0-9]*])\\s*$"); + + // FIXME: Instead of using the ErrorReporter, we should be raising assertion errors or + // UnsupportedOperationExceptions or some other non-recoverable errors. + private final ErrorReporter errorReporter; + + /** + * Initializes a {@code CTargetTypes} with the given + * error reporter. + * @param errorReporter The error reporter for any + * errors raised in the code + * generation process. + */ + public CTypes(ErrorReporter errorReporter) { + this.errorReporter = errorReporter; + } + + @Override + public boolean supportsGenerics() { + return false; + } + + @Override + public String getTargetTimeType() { + return "interval_t"; + } + + @Override + public String getTargetTagType() { + return "tag_t"; + } + + @Override + public String getTargetFixedSizeListType(String baseType, int size) { + return String.format("%s[%d]", baseType, size); + } + + @Override + public String getTargetVariableSizeListType(String baseType) { + return String.format("%s[]", baseType); + } + + @Override + public String getTargetUndefinedType() { + return String.format("/* %s */", errorReporter.reportError("undefined type")); + } + + @Override + public String getTargetType(InferredType type) { + var result = TargetTypes.super.getTargetType(type); + // FIXME: This is brittle. Rather than patching up the results of almost-right calls + // to other methods, we should have a more general implementation of the method that + // takes more parameters. + Matcher matcher = arrayPattern.matcher(result); + if (matcher.find()) { + return matcher.group(1) + '*'; + } + return result; + } + + /** + * Provides the same functionality as getTargetType, except with + * the array syntax {@code []} preferred over the pointer syntax + * {@code *} (if applicable), and with the variable name + * included. + * The result is of the form {@code type variable_name[]} or + * {@code type variable_name} depending on whether the given type + * is an array type. + */ + public String getVariableDeclaration(InferredType type, String variableName) { + // FIXME: Same as for getTargetType. + // Array type has to be handled specially because C doesn't accept + // type[] as a type designator. + String t = TargetTypes.super.getTargetType(type); + Matcher matcher = arrayPattern.matcher(t); + String declaration = String.format("%s %s", t, variableName); + if (matcher.find()) { + // If the state type ends in [], then we have to move the [] + // because C is very picky about where this goes. It has to go + // after the variable name. + declaration = String.format("%s %s[%s]", matcher.group(1), variableName, matcher.group(2)); + } + return declaration; + } +} diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 555934f3bc..8c821c49b5 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -46,8 +46,7 @@ class CppGenerator( errorReporter: ErrorReporter, private val scopeProvider: LFGlobalScopeProvider ) : - GeneratorBase(cppFileConfig, errorReporter), - TargetTypes by CppTypes { + GeneratorBase(cppFileConfig, errorReporter) { companion object { /** Path to the Cpp lib directory (relative to class path) */ @@ -225,6 +224,8 @@ class CppGenerator( override fun generateAfterDelaysWithVariableWidth() = false override fun getTarget() = Target.CPP + + override fun getTargetTypes(): TargetTypes = CppTypes } object CppTypes : TargetTypes { diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 5af4842616..26d2ab7346 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -81,7 +81,7 @@ import static extension org.lflang.JavaAstUtils.* * Each class will contain all the reaction functions defined by the user in order, with the necessary ports/actions given as parameters. * Moreover, each class will contain all state variables in native Python format. * - * A backend is also generated using the CGenrator that interacts with the C code library (see CGenerator.xtend). + * A backend is also generated using the CGenerator that interacts with the C code library (see CGenerator.xtend). * The backend is responsible for passing arguments to the Python reactor functions. * * @author{Soroush Bateni } @@ -94,12 +94,15 @@ class PythonGenerator extends CGenerator { // Used to add module requirements to setup.py (delimited with ,) var pythonRequiredModules = new StringBuilder() + var PythonTypes types; + new(FileConfig fileConfig, ErrorReporter errorReporter) { super(fileConfig, errorReporter) // set defaults targetConfig.compiler = "gcc" targetConfig.compilerFlags = newArrayList // -Wall -Wconversion" targetConfig.linkerFlags = "" + types = new PythonTypes(errorReporter) } /** @@ -151,15 +154,10 @@ class PythonGenerator extends CGenerator { */ val generic_action_type = "generic_action_instance_struct" - override getTargetUndefinedType() '''PyObject*''' - /** Returns the Target enum for this generator */ override getTarget() { return Target.Python } - - // Regular expression pattern for pointer types. The star at the end has to be visible. - static final Pattern pointerPatternVariable = Pattern.compile("^\\s*+(\\w+)\\s*\\*\\s*$"); //////////////////////////////////////////// //// Public methods @@ -215,6 +213,10 @@ class PythonGenerator extends CGenerator { ##################################### '''); } + + override getTargetTypes() { + return types; + } //////////////////////////////////////////// //// Protected methods @@ -564,10 +566,10 @@ class PythonGenerator extends CGenerator { ''') for (param : decl.toDefinition.allParameters) { - if (!param.inferredType.targetType.equals("PyObject*")) { + if (!types.getTargetType(param).equals("PyObject*")) { // If type is given, use it temporary_code. - append(''' self._«param.name»:«param.inferredType.pythonType» = «param.pythonInitializer» + append(''' self._«param.name»:«types.getPythonType(param.inferredType)» = «param.pythonInitializer» ''') } else { // If type is not given, just pass along the initialization @@ -589,10 +591,10 @@ class PythonGenerator extends CGenerator { ''') // Next, handle state variables for (stateVar : reactor.allStateVars) { - if (!stateVar.inferredType.targetType.equals("PyObject*")) { + if (!types.getTargetType(stateVar).equals("PyObject*")) { // If type is given, use it temporary_code. - append(''' self.«stateVar.name»:«stateVar.inferredType.pythonType» = «stateVar.pythonInitializer» + append(''' self.«stateVar.name»:«types.getPythonType(stateVar.inferredType)» = «stateVar.pythonInitializer» ''') } else if (stateVar.isInitialized) { // If type is not given, pass along the initialization directly if it is present @@ -651,31 +653,6 @@ class PythonGenerator extends CGenerator { «ENDFOR» ''' - /** - * This generator inherits types from the CGenerator. - * This function reverts them back to Python types - * For example, the types double is converted to float, - * the * for pointer types is removed, etc. - * @param type The type - * @return The Python equivalent of a C type - */ - def getPythonType(InferredType type) { - var result = super.getTargetType(type) - - switch(result){ - case "double": result = "float" - case "string": result = "object" - default: result = result - } - - val matcher = pointerPatternVariable.matcher(result) - if(matcher.find()) { - return matcher.group(1) - } - - return result - } - /** * Instantiate classes in Python. * Instances are always instantiated as a list of className = [_className, _className, ...] depending on the size of the bank. diff --git a/org.lflang/src/org/lflang/generator/python/PythonTypes.java b/org.lflang/src/org/lflang/generator/python/PythonTypes.java new file mode 100644 index 0000000000..641dc0d057 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/python/PythonTypes.java @@ -0,0 +1,59 @@ +package org.lflang.generator.python; + +import java.util.regex.Pattern; + +import org.lflang.ErrorReporter; +import org.lflang.InferredType; +import org.lflang.generator.c.CTypes; + +// FIXME: Extending CTargetTypes replicates the behavior that +// existed in the previous implementation of PythonTypes +// (where the generator literally was the TargetTypes instance, +// and so the PythonGenerator inherited the CGenerator behavior), +// but this definitely does not seem like the right thing to do. +public class PythonTypes extends CTypes { + + // Regular expression pattern for pointer types. The star at the end has to be visible. + static final Pattern pointerPatternVariable = Pattern.compile("^\\s*+(\\w+)\\s*\\*\\s*$"); + + /** + * Initializes a {@code CTargetTypes} with the given + * error reporter. + * + * @param errorReporter The error reporter for any + * errors raised in the code + * generation process. + */ + public PythonTypes(ErrorReporter errorReporter) { + super(errorReporter); + } + + @Override + public String getTargetUndefinedType() { + return "PyObject*"; + } + + /** + * This generator inherits types from the CGenerator. + * This function reverts them back to Python types + * For example, the types double is converted to float, + * the * for pointer types is removed, etc. + * @param type The type + * @return The Python equivalent of a C type + */ + public String getPythonType(InferredType type) { + var result = super.getTargetType(type); + + switch(result){ + case "double": result = "float"; + case "string": result = "object"; + } + + var matcher = pointerPatternVariable.matcher(result); + if(matcher.find()) { + return matcher.group(1); + } + + return result; + } +} diff --git a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt index cf50a7a5bb..436baa031f 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt @@ -55,8 +55,7 @@ class RustGenerator( fileConfig: RustFileConfig, errorReporter: ErrorReporter, @Suppress("UNUSED_PARAMETER") unused: LFGlobalScopeProvider -) : GeneratorBase(fileConfig, errorReporter), - TargetTypes by RustTypes { +) : GeneratorBase(fileConfig, errorReporter) { override fun doGenerate(resource: Resource, fsa: IFileSystemAccess2, context: IGeneratorContext) { super.doGenerate(resource, fsa, context) @@ -133,6 +132,7 @@ class RustGenerator( override fun getTarget(): Target = Target.Rust + override fun getTargetTypes(): TargetTypes = RustTypes override fun generateDelayBody(action: Action, port: VarRef): String { TODO("Not yet implemented") diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 7887b72b51..c34d68b0f0 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -33,15 +33,12 @@ import org.lflang.ASTUtils.isInitialized import org.lflang.Target import org.lflang.federated.launcher.FedTSLauncher import org.lflang.federated.FederateInstance -import org.lflang.generator.GeneratorBase -import org.lflang.generator.PrependOperator import org.lflang.lf.* import org.lflang.scoping.LFGlobalScopeProvider import java.nio.file.Files import java.util.* import org.lflang.federated.serialization.SupportedSerializers -import org.lflang.generator.JavaGeneratorUtils -import org.lflang.generator.ValueGenerator +import org.lflang.generator.* /** * Generator for TypeScript target. @@ -99,9 +96,9 @@ class TSGenerator( fun federationRTIPropertiesW() = federationRTIProperties fun getTargetValueW(v: Value): String = VG.getTargetValue(v) - fun getTargetTypeW(p: Parameter): String = getTargetType(p.inferredType) - fun getTargetTypeW(state: StateVar): String = getTargetType(state) - fun getTargetTypeW(t: Type): String = getTargetType(t) + fun getTargetTypeW(p: Parameter): String = TSTypes.getTargetType(p.inferredType) + fun getTargetTypeW(state: StateVar): String = TSTypes.getTargetType(state) + fun getTargetTypeW(t: Type): String = TSTypes.getTargetType(t) fun getInitializerListW(state: StateVar): List = VG.getInitializerList(state) fun getInitializerListW(param: Parameter): List = VG.getInitializerList(param) @@ -354,20 +351,13 @@ class TSGenerator( */ private fun getActionType(action: Action): String { return if (action.type != null) { - getTargetType(action.type) + TSTypes.getTargetType(action.type) } else { "Present" } } - override fun getTargetType(s: StateVar): String { - val type = super.getTargetType(s) - return if (!isInitialized(s)) { - "$type | undefined" - } else { - type - } - } + override fun getTargetTypes(): TargetTypes = TSTypes /** * Generate code for the body of a reaction that handles the @@ -499,30 +489,6 @@ class TSGenerator( return "T extends Present" } - override fun supportsGenerics(): Boolean { - return true - } - - override fun getTargetTimeType(): String { - return "TimeValue" - } - - override fun getTargetTagType(): String { - return "TimeValue" - } - - override fun getTargetUndefinedType(): String { - return "Present" - } - - override fun getTargetFixedSizeListType(baseType: String, size: Int): String { - return "Array($size)<$baseType>" - } - - override fun getTargetVariableSizeListType(baseType: String): String { - return "Array<$baseType>" - } - override fun getTarget(): Target { return Target.TS } diff --git a/org.lflang/src/org/lflang/generator/ts/TSTypes.kt b/org.lflang/src/org/lflang/generator/ts/TSTypes.kt new file mode 100644 index 0000000000..d996f5638e --- /dev/null +++ b/org.lflang/src/org/lflang/generator/ts/TSTypes.kt @@ -0,0 +1,41 @@ +package org.lflang.generator.ts + +import org.lflang.ASTUtils +import org.lflang.generator.TargetTypes +import org.lflang.lf.StateVar + +object TSTypes : TargetTypes { + + override fun getTargetType(s: StateVar): String { + val type = super.getTargetType(s) + return if (!ASTUtils.isInitialized(s)) { + "$type | undefined" + } else { + type + } + } + + override fun supportsGenerics(): Boolean { + return true + } + + override fun getTargetTimeType(): String { + return "TimeValue" + } + + override fun getTargetTagType(): String { + return "TimeValue" + } + + override fun getTargetUndefinedType(): String { + return "Present" + } + + override fun getTargetFixedSizeListType(baseType: String, size: Int): String { + return "Array($size)<$baseType>" + } + + override fun getTargetVariableSizeListType(baseType: String): String { + return "Array<$baseType>" + } +} \ No newline at end of file From b7e9b2929f5c0cc2d3c08617d385104d0ffaab22 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 27 Nov 2021 17:52:21 -0800 Subject: [PATCH 052/221] First working bank written to by a reaction of a container. --- .../org/lflang/generator/PortInstance.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index f8dd98957d..3bc09a82f2 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -152,9 +152,22 @@ public List eventualDestinations() { // If this port has dependent reactions, then add it to the result. if (!dependentReactions.isEmpty()) { // This will be the final result if there are no connections. - SendRange candidate = new SendRange(0, 0, width * parent.width(), false, null); - candidate.destinations.add(newRange(0, 0, width * parent.width(), false, null)); - result.add(candidate); + // The width depends on whether this is an input port or an output port. + // For an input port (of a contained reactor), the width is just the width + // of the port. For an output port, that width is multiplied by the the bank width. + if (isInput()) { + SendRange candidate = new SendRange(0, 0, width, false, null); + // Broadcast to bank members. + for (int i = 0; i < parent.width(); i++) { + candidate.destinations.add(newRange(0, i, width, false, null)); + } + result.add(candidate); + } else { + // For an output port, the entire bank is treated as a send range + // because every channel can carry different data. + SendRange candidate = new SendRange(0, 0, width * parent.width(), false, null); + candidate.destinations.add(newRange(0, 0, width * parent.width(), false, null)); + result.add(candidate); } } // Next, look at downstream ports. From ab7e0dc47edc6815e7c7f0d86c2fcb6a9d797ac9 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 28 Nov 2021 08:32:35 -0800 Subject: [PATCH 053/221] Removed unnecessary broadcast. --- org.lflang/src/org/lflang/generator/PortInstance.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 3bc09a82f2..0dc2b5061e 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -157,10 +157,7 @@ public List eventualDestinations() { // of the port. For an output port, that width is multiplied by the the bank width. if (isInput()) { SendRange candidate = new SendRange(0, 0, width, false, null); - // Broadcast to bank members. - for (int i = 0; i < parent.width(); i++) { - candidate.destinations.add(newRange(0, i, width, false, null)); - } + candidate.destinations.add(newRange(0, 0, width, false, null)); result.add(candidate); } else { // For an output port, the entire bank is treated as a send range From c9e62ba5ec9f892f744c264b42c0226e101d2c22 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sun, 28 Nov 2021 23:07:43 -0800 Subject: [PATCH 054/221] Fix a few issues caused by refactoring. --- .../src/org/lflang/generator/c/CGenerator.xtend | 12 +++++------- org.lflang/src/org/lflang/generator/c/CTypes.java | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 23f0a477a4..d925658894 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3849,7 +3849,7 @@ class CGenerator extends GeneratorBase { // are resolved correctly. val temporaryVariableName = parameter.uniqueID pr(initializeTriggerObjects, ''' - static «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; + «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; «nameOfSelfStruct»->«parameter.name» = «temporaryVariableName»; ''') } @@ -4660,6 +4660,7 @@ class CGenerator extends GeneratorBase { «i»++; if («i» >= «s.width») { «i» = 0; + } ''') while (!sourceParentBanksReversed.isEmpty) { s = sourceParentBanksReversed.pop(); @@ -4669,6 +4670,7 @@ class CGenerator extends GeneratorBase { pr(builder, ''' if («i» >= «s.width») { «i» = 0; + } ''') } } @@ -5244,12 +5246,8 @@ class CGenerator extends GeneratorBase { protected def isTokenType(InferredType type) { if (type.isUndefined) return false - val targetType = types.getTargetType(type) - if (targetType.trim.matches("^\\w*\\[\\s*\\]$") || targetType.trim.endsWith('*')) { - true - } else { - false - } + val targetType = types.getVariableDeclaration(type, "") // This is a hacky way to do this. It is now considered to be a bug (#657) + return type.isVariableSizeList || targetType.contains("*") } /** If the type specification of the form {@code type[]}, diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java index 4e998ae99d..c2c7320050 100644 --- a/org.lflang/src/org/lflang/generator/c/CTypes.java +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -12,9 +12,9 @@ public class CTypes implements TargetTypes { // Regular expression pattern for array types. // For example, for "foo[10]", the first match will be "foo" and the second "[10]". // For "foo[]", the first match will be "foo" and the second "". - static final Pattern arrayPattern = Pattern.compile("^\\s*+(\\w+)\\s*(\\[[0-9]*])\\s*$"); + static final Pattern arrayPattern = Pattern.compile("^\\s*(\\w+)\\s*\\[([0-9]*)]\\s*$"); - // FIXME: Instead of using the ErrorReporter, we should be raising assertion errors or + // FIXME: Instead of using the ErrorReporter, perhaps we should be raising assertion errors or // UnsupportedOperationExceptions or some other non-recoverable errors. private final ErrorReporter errorReporter; From f3e5fb187d313b324ce321b0ab069a6ceda7bee0 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 30 Nov 2021 07:54:26 -0800 Subject: [PATCH 055/221] Address banks in one more place (reactions sending to contained banks). --- .../src/org/lflang/generator/c/CGenerator.xtend | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d925658894..271c2984e0 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5877,9 +5877,6 @@ class CGenerator extends GeneratorBase { // For each reaction instance, allocate the arrays that will be used to // trigger downstream reactions. for (reaction : reactions) { - val instance = reaction.parent; - val nameOfSelfStruct = CUtil.selfRef(instance) - deferredReactionOutputs(reaction); // Next handle triggers of the reaction that come from a multiport output @@ -5888,20 +5885,23 @@ class CGenerator extends GeneratorBase { // If the port is a multiport, then we need to create an entry for each // individual port. if (trigger.isMultiport() && trigger.parent !== null && trigger.isOutput) { + // Trigger is an output of a contained reactor or bank. + startScopedReactorBlock(code, trigger.parent); + // If the width is given as a numeric constant, then add that constant // to the output count. Otherwise, assume it is a reference to one or more parameters. val width = trigger.width; - val containerName = trigger.parent.name val portStructType = variableStructType(trigger.definition, trigger.parent.definition.reactorClass) - // FIXME: What if the port is in a bank? Need to index the container. pr(''' - «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width = «width»; // Allocate memory to store pointers to the multiport outputs of a contained reactor. - «nameOfSelfStruct»->_lf_«containerName».«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«containerName».«trigger.name»_width); + «CUtil.selfRef(trigger.parent)»->«trigger.name»_width = «width»; + «CUtil.selfRef(trigger.parent)»->«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) + * «CUtil.selfRef(trigger.parent)»->«trigger.name»_width); ''') + + endScopedReactorBlock(code, trigger.parent); } } } From 0db8c5af91a4a2a842cbe487ef860758a630fa20 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 1 Dec 2021 11:20:10 -0800 Subject: [PATCH 056/221] Checkpoint before significant refactoring of utility functions. --- .../org/lflang/generator/c/CGenerator.xtend | 55 +++++++++++++++++-- .../src/org/lflang/generator/c/CUtil.java | 17 ++++++ 2 files changed, 67 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 271c2984e0..24f0d67e95 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3441,7 +3441,7 @@ class CGenerator extends GeneratorBase { * of the container of the reaction. */ static def triggerStructName(PortInstance port, ReactorInstance reactor) { - return '''«CUtil.selfRef(reactor)»->_lf_«port.parent.name».«port.name»_trigger''' + return '''«CUtil.containedReactorRef(port.parent)».«port.name»_trigger''' } /** @@ -4730,6 +4730,30 @@ class CGenerator extends GeneratorBase { } } } + + /** + * Start a deep scoped block for the specified reactor in situations where + * the self struct for the specified reactor is not already in scope + * and the self struct of some of its containers may also not be in scope. + * Specifically, the reference reactor is assumed to deeply contain the + * specified reactor. A scoped block is created for each intermediate + * container. If the reference reactor does not deeply contain the specified + * reactor, then nested scoped blocks are created all the way to the top level, + * not including the top level. + * This must be followed by an {@link endDeepScopedReactorBlock(StringBuilder)}. + * @param builder The string builder into which to write. + * @param reactor The reactor instance. + * @param reference The container. + */ + private def void startDeepScopedReactorBlock( + StringBuilder builder, ReactorInstance reactor, ReactorInstance reference + ) { + startScopedReactorBlock(builder, reactor); + val container = reactor.parent; + if (container != reference && container.depth > 1) { + startDeepScopedReactorBlock(builder, container, reference); + } + } /** * End a scoped block. @@ -4755,6 +4779,22 @@ class CGenerator extends GeneratorBase { } } + /** + * End a deep scoped block. + * @param builder The string builder into which to write. + * @param reactor The reactor instance. + * @param reference The container. + */ + private def void endDeepScopedReactorBlock( + StringBuilder builder, ReactorInstance reactor, ReactorInstance reference + ) { + endScopedReactorBlock(builder, reactor); + val container = reactor.parent; + if (container != reference && container.depth > 1) { + endDeepScopedReactorBlock(builder, container, reference); + } + } + /** * For the specified reactor, print code to the specified builder * that defines a pointer to the self struct of the specified @@ -5556,6 +5596,9 @@ class CGenerator extends GeneratorBase { var portChannelCount = 0; for (eventualSource: port.eventualSources()) { val sourcePort = eventualSource.getPort(); + + startDeepScopedReactorBlock(code, sourcePort.parent, instance); + if (sourcePort.isMultiport && port.isMultiport) { // Both source and destination are multiports. pr(''' @@ -5591,6 +5634,8 @@ class CGenerator extends GeneratorBase { ''') portChannelCount++; } + + endDeepScopedReactorBlock(code, sourcePort.parent, instance); } } } @@ -5870,7 +5915,7 @@ class CGenerator extends GeneratorBase { /** * Generate code to allocate the memory needed by reactions for triggering - * downstream reactions. Also, record startup and shutdown reactions. + * downstream reactions. * @param reactions A list of reactions. */ private def void deferredReactionMemory(Iterable reactions) { @@ -5896,9 +5941,9 @@ class CGenerator extends GeneratorBase { pr(''' // Allocate memory to store pointers to the multiport outputs of a contained reactor. - «CUtil.selfRef(trigger.parent)»->«trigger.name»_width = «width»; - «CUtil.selfRef(trigger.parent)»->«trigger.name» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «CUtil.selfRef(trigger.parent)»->«trigger.name»_width); + «CUtil.containedReactorRef(trigger.parent)».«trigger.name»_width = «width»; + «CUtil.containedReactorRef(trigger.parent)».«trigger.name» = («portStructType»**)malloc( + sizeof(«portStructType»*) * «width»); ''') endScopedReactorBlock(code, trigger.parent); diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 498db23a65..d41dd8c498 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -95,6 +95,23 @@ static public String containedPortRef(PortInstance port) { return destStruct + "->_lf_" + reactorRef(port.getParent()) + "." + port.getName(); } + /** + * For situations where a reaction reacts to or reads from an output + * of a contained reactor or sends to an input of a contained reactor, + * then the container's self struct will have a field + * (or an array of fields if the contained reactor is a bank) that is + * a struct with fields corresponding to those inputs and outputs. + * This method returns a reference to that struct or array of structs. + * @param reactor The contained reactor. + */ + static public String containedReactorRef(ReactorInstance reactor) { + String result = selfRef(reactor.getParent()) + "->_lf_" + reactor.getName(); + if (reactor.isBank()) { + result += "[" + bankIndex(reactor) + "]"; + } + return result; + } + /** * Return a string for referencing the struct with the value and is_present * fields of the specified port. This is used for establishing the destination of From 65bbdeab581da24e6acd2cab60a84763e151bb1a Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 1 Dec 2021 12:31:22 -0800 Subject: [PATCH 057/221] Consolidated some functions for getting port references and gave them better names --- .../org/lflang/generator/c/CGenerator.xtend | 52 +++--- .../src/org/lflang/generator/c/CUtil.java | 157 ++++++++---------- 2 files changed, 94 insertions(+), 115 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 24f0d67e95..d10b75886a 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3148,7 +3148,7 @@ class CGenerator extends GeneratorBase { // Add port «port.getFullName» to array of is_present fields. for (int i = 0; i < «port.width»; i++) { _lf_is_present_fields[«startTimeStepIsPresentCount» + i] - = &«CUtil.sourceRef(port)»[i]->is_present; + = &«CUtil.portRefSource(port)»[i]->is_present; } ''') if (isFederatedAndDecentralized) { @@ -3157,7 +3157,7 @@ class CGenerator extends GeneratorBase { // Add port «port.getFullName» to array of is_present fields. for (int i = 0; i < «port.width»; i++) { _lf_intended_tag_fields[«startTimeStepIsPresentCount» + i] - = &«CUtil.sourceRef(port)»[i]->intended_tag; + = &«CUtil.portRefSource(port)»[i]->intended_tag; } ''') } @@ -3167,14 +3167,14 @@ class CGenerator extends GeneratorBase { pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. _lf_is_present_fields[«startTimeStepIsPresentCount»] - = &«CUtil.sourceRef(port)».is_present; + = &«CUtil.portRefSource(port)».is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution. pr(temp, ''' // Add port «port.getFullName» to array of is_present fields. _lf_intended_tag_fields[«startTimeStepIsPresentCount»] - = &«CUtil.sourceRef(port)».intended_tag; + = &«CUtil.portRefSource(port)».intended_tag; ''') } startTimeStepIsPresentCount++ @@ -3254,7 +3254,7 @@ class CGenerator extends GeneratorBase { { // Scope to avoid collisions with variable names. int i = «startTimeStepIsPresentCount»; for (int j = 0; j < «output.width»; j++) { - _lf_is_present_fields[i++] = &«CUtil.sourceRef(output)»[j].is_present; + _lf_is_present_fields[i++] = &«CUtil.portRefSource(output)»[j].is_present; } } ''') @@ -3265,7 +3265,7 @@ class CGenerator extends GeneratorBase { { // Scope to avoid collisions with variable names. int i = «startTimeStepIsPresentCount»; for (int j = 0; j < «output.width»; j++) { - _lf_intended_tag_fields[i++] = &«CUtil.sourceRef(output)»[j].intended_tag; + _lf_intended_tag_fields[i++] = &«CUtil.portRefSource(output)»[j].intended_tag; } } ''') @@ -3275,13 +3275,13 @@ class CGenerator extends GeneratorBase { // Output is not a multiport. pr(startTimeStep, ''' // Add port «output.getFullName» to array of is_present fields. - _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«CUtil.sourceRef(output)».is_present; + _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«CUtil.portRefSource(output)».is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution with decentralized coordination. pr(startTimeStep, ''' // Add port «output.getFullName» to array of Intended_tag fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«CUtil.sourceRef(output)».intended_tag; + _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«CUtil.portRefSource(output)».intended_tag; ''') } startTimeStepIsPresentCount++ @@ -4889,7 +4889,7 @@ class CGenerator extends GeneratorBase { { // To scope variable j int j = «eventualSource.startChannel»; for (int i = «startChannel»; i < «eventualSource.totalWidth» + «startChannel»; i++) { - «CUtil.destinationRef(port)»[i] = («destStructType»*)«modifier»«CUtil.sourceRef(src)»[j++]; + «CUtil.portRefDestination(port)»[i] = («destStructType»*)«modifier»«CUtil.portRefSource(src)»[j++]; } } ''') @@ -4898,21 +4898,21 @@ class CGenerator extends GeneratorBase { // Source is a multiport, destination is a single port. pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» - «CUtil.destinationRef(port)» = («destStructType»*)«modifier»«CUtil.sourceRef(src)»[«eventualSource.startChannel»]; + «CUtil.portRefDestination(port)» = («destStructType»*)«modifier»«CUtil.portRefSource(src)»[«eventualSource.startChannel»]; ''') } } else if (port.isMultiport()) { // Source is a single port, Destination is a multiport. pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» - «CUtil.destinationRef(port)»[«startChannel»] = («destStructType»*)&«CUtil.sourceRef(src)»; + «CUtil.portRefDestination(port)»[«startChannel»] = («destStructType»*)&«CUtil.portRefSource(src)»; ''') startChannel++; } else { // Both ports are single ports. pr(temp, ''' // Connect «src.getFullName» to port «port.getFullName» - «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(src)»; + «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(src)»; ''') } // The null argument ensures that both src and port self struct @@ -5567,14 +5567,14 @@ class CGenerator extends GeneratorBase { // Connect «port», which gets data from reaction «reaction.index» // of «instance.getFullName», to «port.getFullName». for (int i = 0; i < «port.width»; i++) { - «CUtil.destinationRef(port)»[i] = («destStructType»*)«CUtil.sourceRef(port)»[i]; + «CUtil.portRefDestination(port)»[i] = («destStructType»*)«CUtil.portRefSource(port)»[i]; } ''') } else { pr(''' // Connect «port», which gets data from reaction «reaction.index» // of «instance.getFullName», to «port.getFullName». - «CUtil.destinationRef(port)» = («destStructType»*)&«CUtil.sourceRef(port)»; + «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(port)»; ''') } endScopedReactorBlock(code, port.parent); @@ -5605,7 +5605,7 @@ class CGenerator extends GeneratorBase { // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. for (int i = 0; i < «eventualSource.totalWidth»; i++) { - «CUtil.containedPortRef(port)»[i + «portChannelCount»] = («destStructType»*)&«CUtil.sourceRef(sourcePort)»[i + «eventualSource.startChannel»]; + «CUtil.portRefDestination(port)»[i + «portChannelCount»] = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[i + «eventualSource.startChannel»]; } ''') portChannelCount += eventualSource.totalWidth; @@ -5614,7 +5614,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)» = («destStructType»*)&«CUtil.sourceRef(sourcePort)»[«eventualSource.startChannel»]; + «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[«eventualSource.startChannel»]; ''') portChannelCount++; } else if (port.isMultiport) { @@ -5622,7 +5622,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)»[«portChannelCount»] = («destStructType»*)&«CUtil.sourceRef(sourcePort)»; + «CUtil.portRefDestination(port)»[«portChannelCount»] = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; ''') portChannelCount++; } else { @@ -5630,7 +5630,7 @@ class CGenerator extends GeneratorBase { pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.containedPortRef(port)» = («destStructType»*)&«CUtil.sourceRef(sourcePort)»; + «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; ''') portChannelCount++; } @@ -5706,18 +5706,18 @@ class CGenerator extends GeneratorBase { // a common situation on multiport to bank messaging. if (sendingRange.totalWidth == 1) { pr(''' - «CUtil.sourceRef(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRefSource(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') } else { pr(''' for (int i = «start»; i < «end»; i++) { - «CUtil.sourceRef(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRefSource(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; } ''') } } else { pr(''' - «CUtil.sourceRef(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefSource(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } } @@ -5764,12 +5764,12 @@ class CGenerator extends GeneratorBase { val end = sendingRange.startChannel + sendingRange.totalWidth; pr(''' for (int i = «start»; i < «end»; i++) { - «CUtil.sourceRef(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefSource(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; } ''') } else { pr(''' - «CUtil.sourceRef(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefSource(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } } @@ -5809,7 +5809,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(code, effect.parent); - val effectRef = CUtil.sourceRef(effect); + val effectRef = CUtil.portRefSource(effect); pr(''' «effectRef»_width = «effect.width»; @@ -5998,7 +5998,7 @@ class CGenerator extends GeneratorBase { pr(init, ''' for (int i = 0; i < «effect.width»; i++) { «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[i + count] - = &«CUtil.sourceRef(effect)»[i]«connector»is_present; + = &«CUtil.portRefSource(effect)»[i]«connector»is_present; } count += «effect.getWidth()»; ''') @@ -6007,7 +6007,7 @@ class CGenerator extends GeneratorBase { // The effect is not a multiport nor a port contained by a multiport. pr(init, ''' «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[count++] - = &«CUtil.sourceRef(effect)».is_present; + = &«CUtil.portRefSource(effect)».is_present; ''') outputCount += bankWidth; } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index d41dd8c498..b3da67bc05 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -77,24 +77,81 @@ static public String bankIndex(ReactorInstance instance) { } /** - * Return a string for referencing the port struct (which has the value - * and is_present fields) in the self struct of the port's parent's - * parent. This is used by reactions that are triggered by an output - * of a contained reactor and by reactions that send data to inputs - * of a contained reactor. This will have one of the following forms: + * Return a reference to the specified port on the self struct of the specified + * container reactor. The port is required to have the reactor as either its + * parent or its parent parent or an exception will be thrown. * - * * selfStructRef->_lf_reactorName.portName - * * selfStructRef->_lf_reactorName[id].portName + * The returned string will have one of the following forms: * - * Where the selfStructRef is as returned by selfRef(). + * * selfStruct->_lf_portName + * * selfStruct->_lf_parent.portName + * + * where the first is returned if the container directly contains the port. + * The selfStruct points to the port's parent or parent's parent, and if + * that parent is a bank, then it will have the form selfStruct[bankIndex], + * where bankIndex is the string returned by {@link bankIndex(ReactorInstance)}. * + * If the container is port's parent's parent, and the port's parent is a bank, + * then "_lf_parent" will be replaced by "_lf_parent[bankIndex]", where + * bank_index is the string returned by bankIndex(port.parent). + * * @param port The port. + * @param container The container. */ - static public String containedPortRef(PortInstance port) { - var destStruct = CUtil.selfRef(port.getParent().getParent()); - return destStruct + "->_lf_" + reactorRef(port.getParent()) + "." + port.getName(); + static public String portRef(PortInstance port, ReactorInstance container) { + if (port.getParent() == container) { + String sourceStruct = CUtil.selfRef(port.getParent()); + return sourceStruct + "->_lf_" + port.getName(); + } else if (port.getParent().getParent() == container) { + String sourceStruct = CUtil.selfRef(port.getParent().getParent()); + // The form is slightly different depending on whether the port belongs to a bank. + if (port.getParent().isBank()) { + return sourceStruct + "->_lf_" + port.getParent().getName() + + "[" + bankIndex(port.getParent()) + "]." + port.getName(); + } else { + return sourceStruct + "->_lf_" + port.getParent().getName() + "." + port.getName(); + } + } else { + throw new IllegalArgumentException( + "Port " + port.getFullName() + " is not visible to " + container.getFullName() + ); + } } + /** + * This is a special case of {@link portRef(PortInstance, ReactorInstance) + * where it is know that the reference required for the port is to a + * sink of data, not a source. This customizes portRef() by figuring out + * what the appropriate container. + * + * @param port An instance of the port to be referenced. + */ + static public String portRefDestination(PortInstance port) { + if (port.isOutput()) { + return portRef(port, port.getParent().getParent()); + } + return portRef(port, port.getParent()); + } + + /** + * This is a special case of {@link portRef(PortInstance, ReactorInstance) + * where it is know that the reference required for the port is to a + * source of data, not a sink. This customizes portRef() by figuring out + * what the appropriate container. + * + * @param port An instance of the port to be referenced. + */ + static public String portRefSource(PortInstance port) { + if (port.isInput()) { + return portRef(port, port.getParent().getParent()); + } + return portRef(port, port.getParent()); + } + + + ////////////////////////////////////////////////////// + //// FIXME: Get rid of the following. + /** * For situations where a reaction reacts to or reads from an output * of a contained reactor or sends to an input of a contained reactor, @@ -112,23 +169,6 @@ static public String containedReactorRef(ReactorInstance reactor) { return result; } - /** - * Return a string for referencing the struct with the value and is_present - * fields of the specified port. This is used for establishing the destination of - * data for a connection between ports. - * This will have the following form: - * - * * selfStruct->_lf_portName - * - * @param port An instance of a destination input port. - */ - static public String destinationRef(PortInstance port) { - // Note that if the port is an output, then it must - // have dependent reactions, otherwise it would not - // be a destination. - return selfRef(port.getParent()) + "->_lf_" + port.getName(); - } - /** * Return an expression that, when evaluated in a context with * bank index variables defined for the specified reactor and @@ -174,29 +214,6 @@ static public String indexExpression(ReactorInstance instance) { } } - /** - * Return a reference to the specified reactor instance within a self - * struct. The result has one of the following forms: - * - * * instanceName - * * instanceName[id] - * - * where "id" is a variable referring to the bank index if the reactor - * is a bank. - * - * @param instance The reactor instance. - * @return A reference to the reactor within a self struct. - */ - static public String reactorRef(ReactorInstance instance) { - // If this reactor is a member of a bank of reactors, then change - // the name of its self struct to append [index]. - if (instance.isBank()) { - return instance.getName() + "[" + bankIndex(instance) + "]"; - } else { - return instance.getName(); - } - } - /** * Return the unique name for the "self" struct of the specified * reactor instance. @@ -246,44 +263,6 @@ static public String selfType(ReactorInstance instance) { return selfType(instance.getDefinition().getReactorClass()); } - /** - * Return a string for referencing the data, width, or is_present value of - * the specified port that is a source of data. - * This will have one of the following forms: - * - * * selfStruct->_lf_portName - * * selfStruct->_lf_parentName.portName - * * selfStruct->_lf_parentName[bankIndex].portName - * - * The selfStruct points to the port's parent (for an output) or the - * port's parent's parent (for an input), and it that parent - * is a bank, then it will have the form selfStruct[bankIndex], - * where bankIndex is the string returned by {@link bankIndex(ReactorInstance)}. - * - * If the port is an output, then the first form is returned. - * The second and third forms are returned for an input, in which case - * the "source" is a reaction port's parent's parent. If that parent - * is a bank, then the third form results, and bankIndex will be the - * value returned by {@link bankIndex(ReactorInstance)} for that parent. - * - * @param port An instance of the port to be referenced. - */ - static public String sourceRef(PortInstance port) { - if (port.isOutput()) { - String sourceStruct = CUtil.selfRef(port.getParent()); - return sourceStruct + "->_lf_" + port.getName(); - } else { - String sourceStruct = CUtil.selfRef(port.getParent().getParent()); - // The form is slightly different depending on whether the port belongs to a bank. - if (port.getParent().isBank()) { - return sourceStruct + "->_lf_" + port.getParent().getName() - + "[" + bankIndex(port.getParent()) + "]." + port.getName(); - } else { - return sourceStruct + "->_lf_" + port.getParent().getName() + "." + port.getName(); - } - } - } - /** * FIXME: The following functional interface is throwaway * code, intended only to be used while the C Generator From 5bcc8a2461b45c5dfbbfe2568c9377bd70a0f214 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 1 Dec 2021 14:30:35 -0800 Subject: [PATCH 058/221] A bit of refactoring of CUtil to organize methods better. --- .../lflang/federated/CGeneratorExtension.java | 4 +- .../org/lflang/generator/c/CGenerator.xtend | 110 ++++---- .../src/org/lflang/generator/c/CUtil.java | 255 +++++++++--------- .../generator/python/PythonGenerator.xtend | 10 +- 4 files changed, 192 insertions(+), 187 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java index 74249e5441..f0fa41f56e 100644 --- a/org.lflang/src/org/lflang/federated/CGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/CGeneratorExtension.java @@ -145,7 +145,7 @@ public static StringBuilder initializeTriggerForControlReactions( ReactorDecl reactorClass = instance.getDefinition().getReactorClass(); Reactor reactor = ASTUtils.toDefinition(reactorClass); - String nameOfSelfStruct = CUtil.selfRef(instance); + String nameOfSelfStruct = CUtil.reactorRef(instance); // Initialize triggers for network input control reactions for (Port trigger : federate.networkInputControlReactionsTriggers) { @@ -171,7 +171,7 @@ public static StringBuilder initializeTriggerForControlReactions( } } - nameOfSelfStruct = CUtil.selfRef(instance); + nameOfSelfStruct = CUtil.reactorRef(instance); // Initialize the trigger for network output control reactions if it doesn't exists if (federate.networkOutputControlReactionsTrigger != null) { diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d10b75886a..62983cba81 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -2642,7 +2642,7 @@ class CGenerator extends GeneratorBase { // trigger downstream reactions. for (reaction : reactions) { val instance = reaction.parent; - val nameOfSelfStruct = CUtil.selfName(instance) + val nameOfSelfStruct = CUtil.reactorRef(instance) // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. @@ -3078,7 +3078,7 @@ class CGenerator extends GeneratorBase { // Avoid generating code if not needed. var foundOne = false; val temp = new StringBuilder(); - var nameOfSelfStruct = CUtil.selfRef(child) + var nameOfSelfStruct = CUtil.reactorRef(child) startScopedReactorBlock(temp, child); @@ -3118,7 +3118,7 @@ class CGenerator extends GeneratorBase { // Avoid generating dead code if nothing is relevant. var foundOne = false; val temp = new StringBuilder(); - var containerSelfStructName = CUtil.selfRef(instance) + var containerSelfStructName = CUtil.reactorRef(instance) startScopedReactorBlock(temp, instance); @@ -3301,7 +3301,7 @@ class CGenerator extends GeneratorBase { private def generateActionInitializations(Iterable actions) { for (action : actions) { if (!action.isShutdown) { - val triggerStructName = CUtil.selfName(action.parent) + "->_lf__" + action.name; + val triggerStructName = CUtil.reactorRef(action.parent) + "->_lf__" + action.name; var minDelay = action.minDelay var minSpacing = action.minSpacing pr(initializeTriggerObjects, ''' @@ -3329,7 +3329,7 @@ class CGenerator extends GeneratorBase { private def generateTimerInitializations(Iterable timers) { for (timer : timers) { if (!timer.isStartup) { - val triggerStructName = CUtil.selfName(timer.parent) + "->_lf__" + timer.name; + val triggerStructName = CUtil.reactorRef(timer.parent) + "->_lf__" + timer.name; val offset = CUtil.VG.getTargetTime(timer.offset) val period = CUtil.VG.getTargetTime(timer.period) pr(initializeTriggerObjects, ''' @@ -3428,7 +3428,7 @@ class CGenerator extends GeneratorBase { * @return The name of the trigger struct. */ static def triggerStructName(TriggerInstance instance) { - return CUtil.selfRef(instance.parent) + return CUtil.reactorRef(instance.parent) + '''->_lf__''' + instance.name } @@ -3441,7 +3441,7 @@ class CGenerator extends GeneratorBase { * of the container of the reaction. */ static def triggerStructName(PortInstance port, ReactorInstance reactor) { - return '''«CUtil.containedReactorRef(port.parent)».«port.name»_trigger''' + return '''«CUtil.reactorRefContained(port.parent)».«port.name»_trigger''' } /** @@ -3496,18 +3496,18 @@ class CGenerator extends GeneratorBase { // the header information in the trace file. if (targetConfig.tracing !== null) { var description = getShortenedName(instance) - var nameOfSelfStruct = CUtil.selfName(instance) + var selfStruct = CUtil.reactorRef(instance) pr(initializeTriggerObjects, ''' - _lf_register_trace_event(«nameOfSelfStruct», NULL, trace_reactor, "«description»"); + _lf_register_trace_event(«selfStruct», NULL, trace_reactor, "«description»"); ''') for (action : actions) { pr(initializeTriggerObjects, ''' - _lf_register_trace_event(«nameOfSelfStruct», &(«nameOfSelfStruct»->_lf__«action.name»), trace_trigger, "«description».«action.name»"); + _lf_register_trace_event(«selfStruct», &(«selfStruct»->_lf__«action.name»), trace_trigger, "«description».«action.name»"); ''') } for (timer : timers) { pr(initializeTriggerObjects, ''' - _lf_register_trace_event(«nameOfSelfStruct», &(«nameOfSelfStruct»->_lf__«timer.name»), trace_trigger, "«description».«timer.name»"); + _lf_register_trace_event(«selfStruct», &(«selfStruct»->_lf__«timer.name»), trace_trigger, "«description».«timer.name»"); ''') } } @@ -3546,8 +3546,8 @@ class CGenerator extends GeneratorBase { // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' - «CUtil.selfType(main)»* «CUtil.selfName(main)» = new_«main.name»(); - self_structs[0] = «CUtil.selfName(main)»; + «CUtil.selfType(main)»* «CUtil.reactorRef(main)» = new_«main.name»(); + self_structs[0] = «CUtil.reactorRef(main)»; ''') // Generate code for top-level parameters, actions, timers, and reactions that @@ -3589,7 +3589,7 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, '// ***** Start initializing ' + fullName + ' of class ' + reactorClass.name) - var nameOfSelfStruct = CUtil.selfName(instance) + var selfStruct = CUtil.reactorRef(instance) var structType = CUtil.selfType(reactorClass) // If this reactor is a placeholder for a bank of reactors, then generate @@ -3600,12 +3600,12 @@ class CGenerator extends GeneratorBase { // and outputs (the "self" struct). The form is slightly different // depending on whether its in a bank of reactors. pr(initializeTriggerObjects, ''' - «structType»* «nameOfSelfStruct» = new_«reactorClass.name»(); + «structType»* «selfStruct» = new_«reactorClass.name»(); ''') // Record the self struct on the big array of self structs. pr(initializeTriggerObjects, ''' - self_structs[«CUtil.indexExpression(instance)»] = «nameOfSelfStruct»; + self_structs[«CUtil.indexExpression(instance)»] = «selfStruct»; ''') // Generate code to initialize the "self" struct in the @@ -3621,11 +3621,11 @@ class CGenerator extends GeneratorBase { for (output : reactorClass.toDefinition.outputs) { // If the port is a multiport, create an array. if (JavaAstUtils.isMultiport(output)) { - initializeOutputMultiport(initializeTriggerObjects, output, nameOfSelfStruct, instance) + initializeOutputMultiport(initializeTriggerObjects, output, selfStruct, instance) } else { pr(initializeTriggerObjects, ''' // width of -2 indicates that it is not a multiport. - «nameOfSelfStruct»->_lf_«output.name»_width = -2; + «selfStruct»->_lf_«output.name»_width = -2; ''') } } @@ -3638,18 +3638,18 @@ class CGenerator extends GeneratorBase { // If the port is a multiport, create an array. if (JavaAstUtils.isMultiport(input)) { pr(initializeTriggerObjects, ''' - «nameOfSelfStruct»->_lf_«input.name»_width = «multiportWidthSpecInC(input, null, instance)»; + «selfStruct»->_lf_«input.name»_width = «multiportWidthSpecInC(input, null, instance)»; // Allocate memory for multiport inputs. - «nameOfSelfStruct»->_lf_«input.name» = («variableStructType(input, reactorClass)»**)malloc(sizeof(«variableStructType(input, reactorClass)»*) * «nameOfSelfStruct»->_lf_«input.name»_width); + «selfStruct»->_lf_«input.name» = («variableStructType(input, reactorClass)»**)malloc(sizeof(«variableStructType(input, reactorClass)»*) * «selfStruct»->_lf_«input.name»_width); // Set inputs by default to an always absent default input. - for (int i = 0; i < «nameOfSelfStruct»->_lf_«input.name»_width; i++) { - «nameOfSelfStruct»->_lf_«input.name»[i] = &«nameOfSelfStruct»->_lf_default__«input.name»; + for (int i = 0; i < «selfStruct»->_lf_«input.name»_width; i++) { + «selfStruct»->_lf_«input.name»[i] = &«selfStruct»->_lf_default__«input.name»; } ''') } else { pr(initializeTriggerObjects, ''' // width of -2 indicates that it is not a multiport. - «nameOfSelfStruct»->_lf_«input.name»_width = -2; + «selfStruct»->_lf_«input.name»_width = -2; ''') } } @@ -3738,15 +3738,15 @@ class CGenerator extends GeneratorBase { } } - var nameOfSelfStruct = CUtil.selfName(action.parent); + var selfStruct = CUtil.reactorRef(action.parent); // Create a reference token initialized to the payload size. // This token is marked to not be freed so that the trigger_t struct // always has a reference token. pr(initializeTriggerObjects, ''' - «nameOfSelfStruct»->_lf__«action.name».token = _lf_create_token(«payloadSize»); - «nameOfSelfStruct»->_lf__«action.name».status = absent; + «selfStruct»->_lf__«action.name».token = _lf_create_token(«payloadSize»); + «selfStruct»->_lf__«action.name».status = absent; ''' ) // At the start of each time step, we need to initialize the is_present field @@ -3754,9 +3754,9 @@ class CGenerator extends GeneratorBase { // allocated token if appropriate. This code sets up the table that does that. pr(initializeTriggerObjects, ''' _lf_tokens_with_ref_count[«startTimeStepTokens»].token - = &«nameOfSelfStruct»->_lf__«action.name».token; + = &«selfStruct»->_lf__«action.name».token; _lf_tokens_with_ref_count[«startTimeStepTokens»].status - = &«nameOfSelfStruct»->_lf__«action.name».status; + = &«selfStruct»->_lf__«action.name».status; _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = true; ''') startTimeStepTokens++ @@ -3789,13 +3789,13 @@ class CGenerator extends GeneratorBase { */ def generateStateVariableInitializations(ReactorInstance instance) { val reactorClass = instance.definition.reactorClass - val nameOfSelfStruct = CUtil.selfName(instance) + val selfRef = CUtil.reactorRef(instance) for (stateVar : reactorClass.toDefinition.stateVars) { val initializer = getInitializer(stateVar, instance) if (stateVar.initialized) { if (stateVar.isOfTimeType) { - pr(initializeTriggerObjects, nameOfSelfStruct + "->" + stateVar.name + " = " + initializer + ";") + pr(initializeTriggerObjects, selfRef + "->" + stateVar.name + " = " + initializer + ";") } else { // If the state is initialized with a parameter, then do not use // a temporary variable. Otherwise, do, because @@ -3804,12 +3804,12 @@ class CGenerator extends GeneratorBase { // is a struct. if (stateVar.isParameterized && stateVar.init.size > 0) { pr(initializeTriggerObjects, - nameOfSelfStruct + "->" + stateVar.name + " = " + initializer + ";") + selfRef + "->" + stateVar.name + " = " + initializer + ";") } else { pr(initializeTriggerObjects, ''' { // For scoping static «types.getVariableDeclaration(stateVar.inferredType, "_initial")» = «initializer»; - «nameOfSelfStruct»->«stateVar.name» = _initial; + «selfRef»->«stateVar.name» = _initial; } // End scoping. ''' ) @@ -3827,9 +3827,9 @@ class CGenerator extends GeneratorBase { for (reaction : reactions) { if (reaction.declaredDeadline !== null) { var deadline = reaction.declaredDeadline.maxDelay - val reactionStructName = '''«CUtil.selfName(reaction.parent)»->_lf__reaction_«reaction.index»''' + val selfRef = '''«CUtil.reactorRef(reaction.parent)»->_lf__reaction_«reaction.index»''' pr(initializeTriggerObjects, ''' - «reactionStructName».deadline = «CUtil.VG.getTargetTime(deadline)»; + «selfRef».deadline = «CUtil.VG.getTargetTime(deadline)»; ''') } } @@ -3840,7 +3840,7 @@ class CGenerator extends GeneratorBase { * @param instance The reactor instance. */ def void generateParameterInitialization(ReactorInstance instance) { - var nameOfSelfStruct = CUtil.selfName(instance) + var selfRef = CUtil.reactorRef(instance) for (parameter : instance.parameters) { // NOTE: we now use the resolved literal value. For better efficiency, we could // store constants in a global array and refer to its elements to avoid duplicate @@ -3850,7 +3850,7 @@ class CGenerator extends GeneratorBase { val temporaryVariableName = parameter.uniqueID pr(initializeTriggerObjects, ''' «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; - «nameOfSelfStruct»->«parameter.name» = «temporaryVariableName»; + «selfRef»->«parameter.name» = «temporaryVariableName»; ''') } } @@ -3885,23 +3885,23 @@ class CGenerator extends GeneratorBase { protected def String multiportWidthSpecInC(Port port, Instantiation contained, ReactorInstance reactorInstance) { var result = new StringBuilder() var count = 0 - var selfStruct = "self" + var selfRef = "self" if (reactorInstance !== null) { if (contained !== null) { // Caution: If port belongs to a contained reactor, the self struct needs to be that // of the contained reactor instance, not this container - selfStruct = CUtil.selfRef(reactorInstance.getChildReactorInstance(contained)) + selfRef = CUtil.reactorRef(reactorInstance.getChildReactorInstance(contained)) } else { - selfStruct =CUtil.selfRef(reactorInstance); + selfRef =CUtil.reactorRef(reactorInstance); } } if (port.widthSpec !== null) { if (!port.widthSpec.ofVariableLength) { for (term : port.widthSpec.terms) { if (term.parameter !== null) { - result.append(selfStruct) + result.append(selfRef) result.append('->') - result.append(CUtil.getTargetReference(term.parameter)) + result.append(term.parameter.name) } else { count += term.width } @@ -3934,7 +3934,7 @@ class CGenerator extends GeneratorBase { r.definition )) { foundOne = true; - val reactionStructName = '''«CUtil.selfRef(reactor)»->_lf__reaction_«r.index»''' + val reactionStructName = '''«CUtil.reactorRef(reactor)»->_lf__reaction_«r.index»''' // xtend doesn't support bitwise operators... val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, r.level) val reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL" @@ -4718,7 +4718,7 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. * @param reactor The reactor instance. */ - private def void startScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { + protected def void startScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. if (reactor != main) { @@ -4769,7 +4769,7 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. * @param reactor The reactor instance. */ - private def void endScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { + protected def void endScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { if (reactor != main) { if (reactor.isBank()) { // Close the for loop iterating over bank members. @@ -5385,7 +5385,7 @@ class CGenerator extends GeneratorBase { for (i : state?.init) { if (i.parameter !== null) { - list.add(CUtil.selfRef(parent) + "->" + i.parameter.name) + list.add(CUtil.reactorRef(parent) + "->" + i.parameter.name) } else if (state.isOfTimeType) { list.add(CUtil.VG.getTargetTime(i)) } else { @@ -5430,7 +5430,7 @@ class CGenerator extends GeneratorBase { // The parameter is being assigned a parameter value. // Assume that parameter belongs to the parent's parent. // This should have been checked by the validator. - list.add(CUtil.selfRef(p.parent.parent) + "->" + value.parameter.name); + list.add(CUtil.reactorRef(p.parent.parent) + "->" + value.parameter.name); } else { list.add(CUtil.VG.getTargetValue(value)) } @@ -5649,7 +5649,7 @@ class CGenerator extends GeneratorBase { * @param parent The reactor. */ private def void deferredCreateDefaultTokens(ReactorInstance reactor) { - var nameOfSelfStruct = CUtil.selfRef(reactor); + var nameOfSelfStruct = CUtil.reactorRef(reactor); // Look for outputs with token types. for (output : reactor.outputs) { @@ -5839,7 +5839,7 @@ class CGenerator extends GeneratorBase { // Port is an effect of a parent's reaction. // That is, the port belongs to the same reactor as the reaction. // The reaction is writing to an output of its container reactor. - val nameOfSelfStruct = CUtil.selfRef(port.parent); + val nameOfSelfStruct = CUtil.reactorRef(port.parent); val portStructType = variableStructType( port.definition, port.parent.definition.reactorClass @@ -5877,7 +5877,7 @@ class CGenerator extends GeneratorBase { int reactionNumber ) { val reactorInstance = reaction.parent; - val selfStruct = CUtil.selfRef(reactorInstance) + val selfStruct = CUtil.reactorRef(reactorInstance) // Record the number of reactions that this reaction depends on. // This is used for optimization. When that number is 1, the reaction can @@ -5898,7 +5898,7 @@ class CGenerator extends GeneratorBase { // FIXME: If the destination is a bank, need to define the bank_index variable. val temp = new StringBuilder(); - val upstreamReaction = '''«CUtil.selfRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' + val upstreamReaction = '''«CUtil.reactorRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' pr(temp, ''' // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); @@ -5935,14 +5935,14 @@ class CGenerator extends GeneratorBase { // If the width is given as a numeric constant, then add that constant // to the output count. Otherwise, assume it is a reference to one or more parameters. - val width = trigger.width; + val width = trigger.width * trigger.parent.width; val portStructType = variableStructType(trigger.definition, trigger.parent.definition.reactorClass) pr(''' // Allocate memory to store pointers to the multiport outputs of a contained reactor. - «CUtil.containedReactorRef(trigger.parent)».«trigger.name»_width = «width»; - «CUtil.containedReactorRef(trigger.parent)».«trigger.name» = («portStructType»**)malloc( + «CUtil.reactorRefContained(trigger.parent)».«trigger.name»_width = «width»; + «CUtil.reactorRefContained(trigger.parent)».«trigger.name» = («portStructType»**)malloc( sizeof(«portStructType»*) * «width»); ''') @@ -5962,7 +5962,7 @@ class CGenerator extends GeneratorBase { * @param The reaction instance. */ private def void deferredReactionOutputs(ReactionInstance reaction) { - val nameOfSelfStruct = CUtil.selfRef(reaction.parent); + val nameOfSelfStruct = CUtil.reactorRef(reaction.parent); // Count the output ports and inputs of contained reactors that // may be set by this reaction. This ignores actions in the effects. @@ -6049,7 +6049,7 @@ class CGenerator extends GeneratorBase { */ private def deferredRemoteTriggerTable(Iterable reactions) { for (reaction : reactions) { - val selfStruct = CUtil.selfRef(reaction.parent); + val selfStruct = CUtil.reactorRef(reaction.parent); val name = reaction.parent.getFullName; var channelCount = 0 diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index b3da67bc05..d7e7de1ff3 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -60,7 +60,14 @@ public class CUtil { ////////////////////////////////////////////////////// //// Public fields. - public static final ValueGenerator VG = new ValueGenerator(CUtil::timeInTargetLanguage, CUtil::getTargetReference); + /** + * Encapsulation of C-specific functions for representing values + * in C. This can be overridden in targets that derive from CGenerator. + */ + public static final ValueGenerator VG = new ValueGenerator( + CUtil::timeInTargetLanguage, // Time value readable in C. + CUtil::getTargetReference // Name of a parameter. FIXME: Not used. + ); ////////////////////////////////////////////////////// //// Public methods. @@ -76,6 +83,51 @@ static public String bankIndex(ReactorInstance instance) { return instance.uniqueID() + "_bank_index"; } + /** + * Return an expression that, when evaluated in a context with + * bank index variables defined for the specified reactor and + * any container(s) that are also banks, returns a unique index + * for a runtime reactor instance. This can be used to maintain an + * array of runtime instance objects, each of which will have a + * unique index. + * + * This is rather complicated because + * this reactor instance and any of its parents may actually + * represent a bank of runtime reactor instances rather a single + * runtime instance. This method returns an expression that should + * be evaluatable in any target language that uses + for addition + * and * for multiplication and has defined variables in the context + * in which this will be evaluated that specify which bank member is + * desired for this reactor instance and any of its parents that is + * a bank. The names of these variables need to be values returned + * by bankIndex(). + * + * If this is a top-level reactor, this returns "0". + * + * @see bankIndex(ReactorInstance) + * @param prefix The prefix used for index variables for bank members. + */ + static public String indexExpression(ReactorInstance instance) { + if (instance.getDepth() == 0) return("0"); + if (instance.isBank()) { + return( + // Position of the bank member relative to the bank. + bankIndex(instance) + " * " + instance.getNumReactorInstances() + // Position of the bank within its parent. + + " + " + instance.getIndexOffset() + // Position of the parent. + + " + " + indexExpression(instance.getParent()) + ); + } else { + return( + // Position within the parent. + instance.getIndexOffset() + // Position of the parent. + + " + " + indexExpression(instance.getParent()) + ); + } + } + /** * Return a reference to the specified port on the self struct of the specified * container reactor. The port is required to have the reactor as either its @@ -100,10 +152,10 @@ static public String bankIndex(ReactorInstance instance) { */ static public String portRef(PortInstance port, ReactorInstance container) { if (port.getParent() == container) { - String sourceStruct = CUtil.selfRef(port.getParent()); + String sourceStruct = CUtil.reactorRef(port.getParent()); return sourceStruct + "->_lf_" + port.getName(); } else if (port.getParent().getParent() == container) { - String sourceStruct = CUtil.selfRef(port.getParent().getParent()); + String sourceStruct = CUtil.reactorRef(port.getParent().getParent()); // The form is slightly different depending on whether the port belongs to a bank. if (port.getParent().isBank()) { return sourceStruct + "->_lf_" + port.getParent().getName() @@ -148,82 +200,6 @@ static public String portRefSource(PortInstance port) { return portRef(port, port.getParent()); } - - ////////////////////////////////////////////////////// - //// FIXME: Get rid of the following. - - /** - * For situations where a reaction reacts to or reads from an output - * of a contained reactor or sends to an input of a contained reactor, - * then the container's self struct will have a field - * (or an array of fields if the contained reactor is a bank) that is - * a struct with fields corresponding to those inputs and outputs. - * This method returns a reference to that struct or array of structs. - * @param reactor The contained reactor. - */ - static public String containedReactorRef(ReactorInstance reactor) { - String result = selfRef(reactor.getParent()) + "->_lf_" + reactor.getName(); - if (reactor.isBank()) { - result += "[" + bankIndex(reactor) + "]"; - } - return result; - } - - /** - * Return an expression that, when evaluated in a context with - * bank index variables defined for the specified reactor and - * any container(s) that are also banks, returns a unique index - * for a runtime reactor instance. This can be used to maintain an - * array of runtime instance objects, each of which will have a - * unique index. - * - * This is rather complicated because - * this reactor instance and any of its parents may actually - * represent a bank of runtime reactor instances rather a single - * runtime instance. This method returns an expression that should - * be evaluatable in any target language that uses + for addition - * and * for multiplication and has defined variables in the context - * in which this will be evaluated that specify which bank member is - * desired for this reactor instance and any of its parents that is - * a bank. The names of these variables need to be values returned - * by bankIndex(). - * - * If this is a top-level reactor, this returns "0". - * - * @see bankIndex(ReactorInstance) - * @param prefix The prefix used for index variables for bank members. - */ - static public String indexExpression(ReactorInstance instance) { - if (instance.getDepth() == 0) return("0"); - if (instance.isBank()) { - return( - // Position of the bank member relative to the bank. - bankIndex(instance) + " * " + instance.getNumReactorInstances() - // Position of the bank within its parent. - + " + " + instance.getIndexOffset() - // Position of the parent. - + " + " + indexExpression(instance.getParent()) - ); - } else { - return( - // Position within the parent. - instance.getIndexOffset() - // Position of the parent. - + " + " + indexExpression(instance.getParent()) - ); - } - } - - /** - * Return the unique name for the "self" struct of the specified - * reactor instance. - * @param instance The reactor instance. - * @return A name for the self struct. - */ - static public String selfName(ReactorInstance instance) { - return instance.uniqueID() + "_self"; - } - /** * Return the unique reference for the "self" struct of the specified * reactor instance. If the instance is a bank of reactors, this returns @@ -233,8 +209,8 @@ static public String selfName(ReactorInstance instance) { * @param instance The reactor instance. * @return A reference to the self struct. */ - static public String selfRef(ReactorInstance instance) { - var result = selfName(instance); + static public String reactorRef(ReactorInstance instance) { + var result = instance.uniqueID() + "_self"; // If this reactor is a member of a bank of reactors, then change // the name of its self struct to append [index]. if (instance.isBank()) { @@ -243,6 +219,26 @@ static public String selfRef(ReactorInstance instance) { return result; } + /** + * For situations where a reaction reacts to or reads from an output + * of a contained reactor or sends to an input of a contained reactor, + * then the container's self struct will have a field + * (or an array of fields if the contained reactor is a bank) that is + * a struct with fields corresponding to those inputs and outputs. + * This method returns a reference to that struct or array of structs. + * Note that the returned reference is not to the self struct of the + * contained reactor. Use {@link reactorRef(ReactorInstance)} for that. + * + * @param reactor The contained reactor. + */ + static public String reactorRefContained(ReactorInstance reactor) { + String result = reactorRef(reactor.getParent()) + "->_lf_" + reactor.getName(); + if (reactor.isBank()) { + result += "[" + bankIndex(reactor) + "]"; + } + return result; + } + /** * Return a unique type for the "self" struct of the specified * reactor class from the reactor class. @@ -263,7 +259,10 @@ static public String selfType(ReactorInstance instance) { return selfType(instance.getDefinition().getReactorClass()); } - /** + ////////////////////////////////////////////////////// + //// FIXME: Not clear what the strategy is with the following inner interface. + + /** * FIXME: The following functional interface is throwaway * code, intended only to be used while the C Generator * undergoes a transition period. @@ -315,15 +314,34 @@ public static void runBuildCommand( } } + ////////////////////////////////////////////////////// + //// Private functions. + /** - * Converts the given commands from strings to their LFCommand - * representation. - * @param commands A list of commands. + * If the argument is a multiport, then return a string that + * gives the width as an expression, and otherwise, return null. + * The string will be empty if the width is variable (specified + * as '[]'). Otherwise, if is a single term or a sum of terms + * (separated by '+'), where each term is either an integer + * or a parameter reference in the target language. + */ + public static String multiportWidthExpression(Variable variable) { + List spec = multiportWidthTerms(variable); + return spec == null ? null : String.join(" + ", spec); + } + + ////////////////////////////////////////////////////// + //// Private methods. + + /** + * Convert the given commands from strings to their LFCommand + * representation and return a list of LFCommand. + * @param commands A list of commands as strings. * @param factory A command factory. * @param dir The directory in which the commands should be executed. * @return The LFCommand representations of the given commands, - * where {@code null} is a placeholder for commands that cannot be - * executed. + * where {@code null} is a placeholder for commands that cannot be + * executed. */ private static List getCommands(List commands, GeneratorCommandFactory factory, Path dir) { return commands.stream() @@ -334,50 +352,16 @@ private static List getCommands(List commands, GeneratorComma } /** - * Given a representation of time that may include units, return - * a string that the target language can recognize as a value. - * If units are given, e.g. "msec", then we convert the units to upper - * case and return an expression of the form "MSEC(value)". - * @param time A TimeValue that represents a time. - * @return A string, such as "MSEC(100)" for 100 milliseconds. - */ - public static String timeInTargetLanguage(TimeValue time) { - if (time != null) { - if (time.unit != TimeUnit.NONE) { - return time.unit.name() + '(' + time.time + ')'; - } else { - return String.valueOf(time.time); - } - } - return "0"; // FIXME: do this or throw exception? - } - - /** - * Generate target code for a parameter reference. + * Return target code for a parameter reference, which in + * this case is just the parameter name. * - * @param param The parameter to generate code for - * @return Parameter reference in target code + * @param param The parameter to generate code for. + * @return Parameter reference in target code. */ - public static String getTargetReference(Parameter param) { + private static String getTargetReference(Parameter param) { return param.getName(); } - /** - * If the argument is a multiport, then return a string that - * gives the width as an expression, and otherwise, return null. - * The string will be empty if the width is variable (specified - * as '[]'). Otherwise, if is a single term or a sum of terms - * (separated by '+'), where each term is either an integer - * or a parameter reference in the target language. - */ - public static String multiportWidthExpression(Variable variable) { - List spec = multiportWidthTerms(variable); - return spec == null ? null : String.join(" + ", spec); - } - - ////////////////////////////////////////////////////// - //// Private methods. - /** * If the argument is a multiport, return a list of strings * describing the width of the port, and otherwise, return null. @@ -406,4 +390,23 @@ private static List multiportWidthTerms(Variable variable) { } return result; } + + /** + * Given a representation of time that may include units, return + * a string that the target language can recognize as a value. + * If units are given, e.g. "msec", then we convert the units to upper + * case and return an expression of the form "MSEC(value)". + * @param time A TimeValue that represents a time. + * @return A string, such as "MSEC(100)" for 100 milliseconds. + */ + private static String timeInTargetLanguage(TimeValue time) { + if (time != null) { + if (time.unit != TimeUnit.NONE) { + return time.unit.name() + '(' + time.time + ')'; + } else { + return String.valueOf(time.time); + } + } + return "0"; // FIXME: do this or throw exception? + } } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 5b984ecae2..9e57a47f51 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -30,7 +30,6 @@ import java.io.File import java.util.ArrayList import java.util.LinkedHashSet import java.util.List -import java.util.regex.Pattern import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGeneratorContext @@ -262,7 +261,7 @@ class PythonGenerator extends CGenerator { for (i : state?.init) { if (i.parameter !== null) { - list.add(CUtil.getTargetReference(i.parameter)) + list.add(i.parameter.name) } else if (state.isOfTimeType) { list.add(CUtil.VG.getTargetTime(i)) } else { @@ -1596,7 +1595,7 @@ class PythonGenerator extends CGenerator { // Here, we attempt to convert the parameter value to // integer. If it succeeds, we also initialize it in C. // If it fails, we defer the initialization to Python. - var nameOfSelfStruct = CUtil.selfRef(instance) + var nameOfSelfStruct = CUtil.reactorRef(instance) for (parameter : instance.parameters) { val initializer = parameter.getInitializer try { @@ -1642,7 +1641,7 @@ class PythonGenerator extends CGenerator { override void generateReactorInstanceExtension( ReactorInstance instance, Iterable reactions ) { - var nameOfSelfStruct = CUtil.selfName(instance) + var nameOfSelfStruct = CUtil.reactorRef(instance) var reactor = instance.definition.reactorClass.toDefinition // Delay reactors and top-level reactions used in the top-level reactor(s) in federated execution are generated in C @@ -1653,6 +1652,8 @@ class PythonGenerator extends CGenerator { return } + startScopedReactorBlock(initializeTriggerObjects, instance); + // Initialize the name field to the unique name of the instance pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->_lf_name = "«instance.uniqueID»_lf"; @@ -1679,6 +1680,7 @@ class PythonGenerator extends CGenerator { ''') } } + endScopedReactorBlock(initializeTriggerObjects, instance); } From 7d274df06580da862ccdd8982ed04ba3235fec41 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 2 Dec 2021 21:06:44 -0800 Subject: [PATCH 059/221] Comments only. --- .../org/lflang/generator/ValueGenerator.java | 7 +++--- .../src/org/lflang/generator/c/CUtil.java | 25 +++++++++++++------ .../lflang/generator/python/PythonTypes.java | 2 +- 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ValueGenerator.java b/org.lflang/src/org/lflang/generator/ValueGenerator.java index 9dfa59bda3..19091428ed 100644 --- a/org.lflang/src/org/lflang/generator/ValueGenerator.java +++ b/org.lflang/src/org/lflang/generator/ValueGenerator.java @@ -31,9 +31,10 @@ public interface TimeInTargetLanguage { } /** - * A {@code GetTargetReference} is a - * target-language-specific parameter reference - * representation strategy. + * A {@code GetTargetReference} instance is a + * target-language-specific function. It provides the + * target language code that refers to the given + * parameter {@code param}. */ public interface GetTargetReference { String apply(Parameter param); diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index d7e7de1ff3..511c9bbd1a 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -50,7 +50,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.util.LFCommand; /** - * A collection of utilties for C code generation. + * A collection of utilities for C code generation. * This class codifies the coding conventions for the C target code generator. * I.e., it defines how variables are named and referenced. * @author{Edward A. Lee } @@ -62,11 +62,12 @@ public class CUtil { /** * Encapsulation of C-specific functions for representing values - * in C. This can be overridden in targets that derive from CGenerator. + * in C. Targets that derive from CGenerator must either reuse this + * ValueGenerator or create their own ValueGenerator instances. */ public static final ValueGenerator VG = new ValueGenerator( CUtil::timeInTargetLanguage, // Time value readable in C. - CUtil::getTargetReference // Name of a parameter. FIXME: Not used. + CUtil::getTargetReference // Name of a parameter. FIXME: Not used. Edit: getTargetReference.apply is called in the implementation of ValueGenerator, so it is used? ); ////////////////////////////////////////////////////// @@ -261,11 +262,21 @@ static public String selfType(ReactorInstance instance) { ////////////////////////////////////////////////////// //// FIXME: Not clear what the strategy is with the following inner interface. + // The {@code ReportCommandErrors} interface allows the + // method runBuildCommand to call a protected + // method from the CGenerator if that method is passed + // using a method reference. The method that is passed + // is then interpreted as a ReportCommandErrors instance. + // This is a convenient way to factor out part of the + // internals of the CGenerator while maintaining + // encapsulation, even though the internals of the CGenerator + // might seem to be tightly coupled. FIXME: Delete this comment - /** - * FIXME: The following functional interface is throwaway - * code, intended only to be used while the C Generator - * undergoes a transition period. + /** + * A {@code ReportCommandErrors} is a way to analyze command + * output and report any errors that it describes. + * FIXME: If the VSCode branch passes code review + * without significant revision, this mechanism will probably be replaced. */ public interface ReportCommandErrors { void report(String errors); diff --git a/org.lflang/src/org/lflang/generator/python/PythonTypes.java b/org.lflang/src/org/lflang/generator/python/PythonTypes.java index 641dc0d057..89b7fd8f70 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonTypes.java +++ b/org.lflang/src/org/lflang/generator/python/PythonTypes.java @@ -6,7 +6,7 @@ import org.lflang.InferredType; import org.lflang.generator.c.CTypes; -// FIXME: Extending CTargetTypes replicates the behavior that +// FIXME: Extending CTypes replicates the behavior that // existed in the previous implementation of PythonTypes // (where the generator literally was the TargetTypes instance, // and so the PythonGenerator inherited the CGenerator behavior), From 8029cd5bcbcc4c3866e934b4b425c0c34f06b0ac Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 5 Dec 2021 21:50:21 -0800 Subject: [PATCH 060/221] Interrim checkin --- .../org/lflang/generator/PortInstance.java | 57 ++- .../org/lflang/generator/c/CGenerator.xtend | 440 ++++++++++++------ .../src/org/lflang/generator/c/CUtil.java | 10 + test/C/src/BankReactionsInContainer.lf | 44 ++ 4 files changed, 381 insertions(+), 170 deletions(-) create mode 100644 test/C/src/BankReactionsInContainer.lf diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 0dc2b5061e..c3effe0967 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -28,6 +28,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.Set; @@ -156,13 +157,13 @@ public List eventualDestinations() { // For an input port (of a contained reactor), the width is just the width // of the port. For an output port, that width is multiplied by the the bank width. if (isInput()) { - SendRange candidate = new SendRange(0, 0, width, false, null); + SendRange candidate = new SendRange(0, 0, width); candidate.destinations.add(newRange(0, 0, width, false, null)); result.add(candidate); } else { // For an output port, the entire bank is treated as a send range // because every channel can carry different data. - SendRange candidate = new SendRange(0, 0, width * parent.width(), false, null); + SendRange candidate = new SendRange(0, 0, width * parent.width()); candidate.destinations.add(newRange(0, 0, width * parent.width(), false, null)); result.add(candidate); } } @@ -181,16 +182,18 @@ public List eventualDestinations() { if (dstSendIterator.hasNext()) { SendRange dstSend = dstSendIterator.next(); while (true) { - if (dstSend.getTotalWidth() <= totalWidth - sourceWidthCovered) { + SendRange residualDst = (SendRange)dstSend.tail(totalWidth - sourceWidthCovered); + if (residualDst == null) { // Destination range fits in current multicast iteration. - result.add(dstSend); + SendRange candidate = new SendRange(0, 0, dstSend.getTotalWidth()); + candidate.destinations.addAll(dstSend.destinations); + result.add(candidate); if (!dstSendIterator.hasNext()) break; dstSend = dstSendIterator.next(); sourceWidthCovered += dstSend.getTotalWidth(); } else { // Destination range spills over into the next multicast iteration. // Need to split the destination range. - SendRange residualDst = (SendRange)dstSend.tail(totalWidth - sourceWidthCovered); dstSend = dstSend.truncate(totalWidth - sourceWidthCovered); result.add(dstSend); dstSend = residualDst; @@ -251,6 +254,7 @@ public List eventualDestinations() { return eventualDestinationRanges; } + /** * Return a list of ports that send data to this port annotated with the channel * and bank ranges of each source port. If this port is directly written to by @@ -462,16 +466,20 @@ private void setInitialWidth(ErrorReporter errorReporter) { /** * Class representing a range of channels of this port that broadcast to some * number of destination ports' channels. All ranges have the same - * width, but not necessarily the same start index. - * This class extends its base class with a list destination channel ranges, + * width, but not necessarily the same start indices. + * This class extends its base class with a list of destination channel ranges, * all of which have the same width as this channel range. * It also includes a field representing the number of destination * reactors. + * + * Note that the interleaved status of the base class is meaningless for a + * SendRange because of the destinations may be interleaved and some not. + * Also, the connection argument makes no sense. */ public class SendRange extends Range { - public SendRange(int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection) { - super(startChannel, startBank, totalWidth, interleaved, connection); + public SendRange(int startChannel, int startBank, int totalWidth) { + super(startChannel, startBank, totalWidth, false, null); } public int getNumberOfDestinationReactors() { @@ -486,8 +494,8 @@ public int getNumberOfDestinationReactors() { return _numberOfDestinationReactors; } - public List destinations = new ArrayList(); - + public final List destinations = new ArrayList(); + /** * Override the base class to return a SendRange where * each of the destinations is the tail of the original destinations. @@ -496,6 +504,7 @@ public int getNumberOfDestinationReactors() { @Override public Range tail(int offset) { SendRange result = (SendRange)super.tail(offset); + if (result == null) return null; for (Range destination : destinations) { result.destinations.add(destination.tail(offset)); } @@ -505,16 +514,13 @@ public Range tail(int offset) { /** * Override the base class to return a SendRange rather than Range. */ - @Override protected Range newRange( - int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection + int startChannel, int startBank, int totalWidth ) { return new SendRange( startChannel, startBank, - totalWidth, - interleaved, - connection + totalWidth ); } @@ -584,6 +590,19 @@ public int compareTo(Range o) { } } + /** + * Return the starting offset of the range (i.e., + * how many total banks and channels come before + * it within its bank/multiport). + */ + public int getStartOffset() { + if (interleaved) { + return PortInstance.this.parent.width() * startChannel + startBank; + } else { + return PortInstance.this.getWidth() * startBank + startChannel; + } + } + /** * Return the total width of the range. */ @@ -596,13 +615,13 @@ public int getTotalWidth() { * starting at the specified offset. Depending on * whether this range is interleaved, this will consume from * multiport channels first (if not interleaved) or banks first - * (if interleaved). The offset is required to be less - * than the total width or an exception will be thrown. + * (if interleaved). If the offset is greater than or equal to + * the total width, then this returns null. * @param offset The number of channels to consume. */ public Range tail(int offset) { if (offset >= totalWidth) { - throw new RuntimeException("Insufficient channels in range."); + return null; } int channelWidth = PortInstance.this.width; int bankWidth = PortInstance.this.parent.width(); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 62983cba81..0cd43430f4 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -268,7 +268,7 @@ import static extension org.lflang.JavaAstUtils.* * `out->is_present` field of the output reactor's self struct. * * In addition, the `reaction_i` struct on the self struct has a `triggers` - * field that records all the trigger_t structs for ports and reactions + * field that records all the trigger_t structs for ports and actions * that are triggered by the i-th reaction. The triggers field is * an array of arrays of pointers to trigger_t structs. * The length of the outer array is the number of output channels @@ -3597,16 +3597,25 @@ class CGenerator extends GeneratorBase { startScopedBlock(initializeTriggerObjects, instance); // Generate the instance self struct containing parameters, state variables, - // and outputs (the "self" struct). The form is slightly different - // depending on whether its in a bank of reactors. - pr(initializeTriggerObjects, ''' - «structType»* «selfStruct» = new_«reactorClass.name»(); - ''') - + // and outputs (the "self" struct). // Record the self struct on the big array of self structs. + // FIXME: only works for banks. pr(initializeTriggerObjects, ''' - self_structs[«CUtil.indexExpression(instance)»] = «selfStruct»; + self_structs[«CUtil.indexExpression(instance)»] = new_«reactorClass.name»(); ''') + + // Create a variable that will point to this new self struct in + // this scope and nested scopes. The form is slightly different + // depending on whether its in a bank of reactors. + if (instance.isBank) { + pr(initializeTriggerObjects, ''' + «structType»** «instance.uniqueID()»_self = («structType»**)&self_structs[«CUtil.indexExpression(instance)»]; + ''') + } else { + pr(initializeTriggerObjects, ''' + «structType»* «instance.uniqueID()»_self = («structType»*)self_structs[«CUtil.indexExpression(instance)»]; + ''') + } // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. @@ -4614,7 +4623,7 @@ class CGenerator extends GeneratorBase { // Start a scoped block so we can define bank index variables without // resulting in them being multiply defined. startScopedBlock(builder, null); - // The following define the self structs only if the parents are not a parent of context. + // The following defines the self structs only if the parents are not a parent of context. defineSelfStruct(builder, source.parent, context); if (source.parent != destination.parent) { @@ -4707,6 +4716,15 @@ class CGenerator extends GeneratorBase { indent(builder); } + /** + * End a scoped block. + * @param builder The string builder into which to write. + */ + private def void endScopedBlock(StringBuilder builder) { + unindent(builder); + pr(builder, "}"); + } + /** * Start a scoped block for the specified reactor in situations where * the self struct for the specified reactor is not already in scope. @@ -4731,6 +4749,133 @@ class CGenerator extends GeneratorBase { } } + /** + * End a scoped reactor block. + * @param builder The string builder into which to write. + * @param reactor The reactor instance. + */ + protected def void endScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { + if (reactor != main) { + if (reactor.isBank()) { + // Close the for loop iterating over bank members. + endScopedBlock(builder); + } + endScopedBlock(builder); + } + } + + /** + * Start a scoped block for the specified range. + * + * If the port of the range is contained by a bank, then this + * generates an iteration that iterates over the range. + * If the port is also a multiport, then the iteration is over bank + * members and channels, where the order depends on whether the + * range is interleaved. + * If the range is interleaved, then it iterates first over + * channels (with index "channel") and then over banks (with + * index given by {@link CUtil.bankIndex(ReactorInstance)}. + * Otherwise, it iterates first over banks and then over + * channels. + * + * If the port is a multiport but its container is not a bank, + * then the generated iteration is only over channels (with + * index variable named "channel"). + * + * If the port is not a multiport nor is its parent a bank, + * then this just generates a scoped section that defines + * a pointer to the self struct of the parent of the range's port. + * + * In all cases, it stops the iteration when the + * total width of the range has been covered. + * + * This must be followed by a call to + * {@link endScopedRangeBlock(StringBuilder, PortInstance.Range)}. + * + * @param builder The string builder into which to write. + * @param range The send range. + */ + protected def void startScopedRangeBlock(StringBuilder builder, PortInstance.Range range) { + val port = range.port; + val reactor = port.parent; + + pr(builder, ''' + // Iterate over range of «range.port.fullName» with starting channel «range.startChannel», + // starting bank «range.startBank», and total width «range.totalWidth». + ''') + + // The first creates a scope in which we can define a pointer to the self + // struct without fear of redefining. + startScopedBlock(builder, null); + defineSelfStruct(builder, reactor); + + // If the reactor is not a bank, no need to do anything further. + if (reactor.isBank) { + val bankIndex = CUtil.bankIndex(reactor); + if (port.isMultiport) { + pr(builder, ''' + int range_count = 0; + int channel = «range.startChannel»; + int «bankIndex» = «range.startBank»; + while (range_count++ < «range.totalWidth») { + ''') + } else { + // Bank, but not a multiport. + pr(builder, ''' + // Send range covers bank member(s). Iterate over bank members. + for (int «bankIndex» = «range.startBank»; «bankIndex» < «range.startBank» + «range.totalWidth»; «bankIndex»++) { + ''') + } + } else if (range.port.isMultiport) { + // Reactor is not a bank, but port is a multiport. + pr(builder, ''' + // Send range covers channels of a multiport. Iterate over channels. + for (int channel = «range.startChannel»; channel < «range.startChannel» + «range.totalWidth»; channel++) { + ''') + } else { + // Not a multiport nor a bank. + // For consistent depth of nesting, generate a scoped block. + pr(builder, "{"); + } + indent(builder); + } + + /** + * End a scoped block for the specified send range. + * @param builder The string builder into which to write. + * @param range The send range. + */ + protected def void endScopedRangeBlock(StringBuilder builder, PortInstance.Range range) { + val port = range.port; + val reactor = port.parent; + if (reactor.isBank && range.port.isMultiport) { + val bankIndex = CUtil.bankIndex(reactor); + if (range.interleaved) { + // Interleaved. Iterate over channels + // then bank members that are within the range. + pr(builder, ''' + «bankIndex»++; + if («bankIndex» >= «reactor.width») { + channel++; + «bankIndex» = 0; + } + ''') + } else { + // Not interleaved. Iterate over the bank members + // then channels that are within the range. + pr(builder, ''' + channel++; + if (channel >= «port.width») { + «bankIndex»++; + channel = 0; + } + ''') + } + } + endScopedBlock(builder); + endScopedBlock(builder); + } + /** * Start a deep scoped block for the specified reactor in situations where * the self struct for the specified reactor is not already in scope @@ -4755,30 +4900,6 @@ class CGenerator extends GeneratorBase { } } - /** - * End a scoped block. - * @param builder The string builder into which to write. - */ - private def void endScopedBlock(StringBuilder builder) { - unindent(builder); - pr(builder, "}"); - } - - /** - * End a scoped reactor block. - * @param builder The string builder into which to write. - * @param reactor The reactor instance. - */ - protected def void endScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { - if (reactor != main) { - if (reactor.isBank()) { - // Close the for loop iterating over bank members. - endScopedBlock(builder); - } - endScopedBlock(builder); - } - } - /** * End a deep scoped block. * @param builder The string builder into which to write. @@ -4840,13 +4961,11 @@ class CGenerator extends GeneratorBase { var structType = CUtil.selfType(reactor) if (reactor.isBank) { pr(builder, ''' - «structType»** «nameOfSelfStruct» = («structType»**)&self_structs[ - «CUtil.indexExpression(reactor.parent)» + «reactor.getIndexOffset»]; + «structType»** «nameOfSelfStruct» = («structType»**)&self_structs[«CUtil.indexExpression(reactor.parent)» + «reactor.getIndexOffset»]; ''') } else { pr(builder, ''' - «structType»* «nameOfSelfStruct» = («structType»*)self_structs[ - «CUtil.indexExpression(reactor)»]; + «structType»* «nameOfSelfStruct» = («structType»*)self_structs[«CUtil.indexExpression(reactor)»]; ''') } } @@ -5477,6 +5596,9 @@ class CGenerator extends GeneratorBase { } pr('''// **** Start deferredInitialize for «reactor.getFullName()»''') + + // First batch of initializations is within a for loop iterating + // over bank members for the reactor's parent. startScopedReactorBlock(code, reactor); // If the child has a multiport that is an effect of some reaction in its container, @@ -5487,11 +5609,9 @@ class CGenerator extends GeneratorBase { deferredAllocationForEffectsOnInputs(reactor); // Initialize the num_destinations fields of port structs on the self struct. - deferredOutputNumDestinations(reactor); // NOTE: Not done for top level. deferredInputNumDestinations(reactions); deferredReactionMemory(reactions); - deferredRemoteTriggerTable(reactions); // For outputs that are not primitive types (of form type* or type[]), // create a default token on the self struct. @@ -5507,8 +5627,15 @@ class CGenerator extends GeneratorBase { // output of a contained reactor. deferredConnectReactionsToPorts(reactor) - pr('''// **** End of deferredInitialize for «reactor.getFullName()»''') endScopedReactorBlock(code, reactor) + + // Second batch of initializes cannot be within a for loop + // iterating over bank members because they iterate over send + // ranges which may span bank members. + deferredOutputNumDestinations(reactor); // NOTE: Does nothing for top level. + deferredFillTriggerTable(reactions); + + pr('''// **** End of deferredInitialize for «reactor.getFullName()»''') } /** @@ -5592,50 +5719,68 @@ class CGenerator extends GeneratorBase { port.definition as TypedVariable, port.parent.definition.reactorClass ) - // The port may be deeper in the hierarchy. - var portChannelCount = 0; + // The port may be deeper in the hierarchy, in which + // case, the source port will not be the same as the + // destination port, and there may be multiple sources + // if the destination is a multiport. + if (port.isMultiport) { + pr("int dst_channel = 0;"); + } for (eventualSource: port.eventualSources()) { val sourcePort = eventualSource.getPort(); - startDeepScopedReactorBlock(code, sourcePort.parent, instance); + if (sourcePort != port) { + // sourcePort is deeper in the hierarchy. Need a + // pointer to the self struct of the destination port. + pr("{"); + indent(); + defineSelfStruct(code, port.parent); + } + + startScopedRangeBlock(code, eventualSource); if (sourcePort.isMultiport && port.isMultiport) { // Both source and destination are multiports. pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - for (int i = 0; i < «eventualSource.totalWidth»; i++) { - «CUtil.portRefDestination(port)»[i + «portChannelCount»] = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[i + «eventualSource.startChannel»]; - } + «CUtil.portRefDestination(port)»[dst_channel++] + = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[channel]; + if (dst_channel >= «port.width») dst_channel = 0; ''') - portChannelCount += eventualSource.totalWidth; } else if (sourcePort.isMultiport) { // Destination is not a multiport, so the channelWidth of the source port should be 1. pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[«eventualSource.startChannel»]; + «CUtil.portRefDestination(port)» + = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[channel]; ''') - portChannelCount++; } else if (port.isMultiport) { // Source is not a multiport, but the destination is. pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)»[«portChannelCount»] = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; + «CUtil.portRefDestination(port)»[dst_channel++] + = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; + if (dst_channel >= «port.width») dst_channel = 0; ''') - portChannelCount++; } else { // Neither is a multiport. pr(''' // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; + «CUtil.portRefDestination(port)» + = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; ''') - portChannelCount++; } - endDeepScopedReactorBlock(code, sourcePort.parent, instance); + endScopedRangeBlock(code, eventualSource); + + if (sourcePort != port) { + unindent(); + pr("}"); + } } } } @@ -5697,29 +5842,22 @@ class CGenerator extends GeneratorBase { for (output : reactor.outputs) { for (sendingRange : output.eventualDestinations) { pr("// For reference counting, set num_destinations for port " + output.name); + + startScopedRangeBlock(code, sendingRange); + // Syntax is slightly difference for a multiport output vs. single port. // For a single port, there should be only one sendingRange. if (output.isMultiport()) { - val start = sendingRange.startChannel; - val end = sendingRange.startChannel + sendingRange.totalWidth; - // Eliminate the for loop for the case where range.channelWidth == 1, - // a common situation on multiport to bank messaging. - if (sendingRange.totalWidth == 1) { - pr(''' - «CUtil.portRefSource(output)»[«start»].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; - ''') - } else { - pr(''' - for (int i = «start»; i < «end»; i++) { - «CUtil.portRefSource(output)»[i].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; - } - ''') - } + pr(''' + «CUtil.portRefSource(output)»[channel].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + ''') } else { pr(''' «CUtil.portRefSource(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } + + endScopedRangeBlock(code, sendingRange); } } } @@ -5814,9 +5952,8 @@ class CGenerator extends GeneratorBase { pr(''' «effectRef»_width = «effect.width»; // Allocate memory to store output of reaction feeding a multiport input of a contained reactor. - «effectRef» = («portStructType»**)malloc(sizeof(«portStructType»*) - * «effectRef»_width); - for (int i = 0; i < «effectRef»_width; i++) { + «effectRef» = («portStructType»**)malloc(sizeof(«portStructType»*) * «effect.width»); + for (int i = 0; i < «effect.width»; i++) { «effectRef»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); } ''') @@ -5931,6 +6068,10 @@ class CGenerator extends GeneratorBase { // individual port. if (trigger.isMultiport() && trigger.parent !== null && trigger.isOutput) { // Trigger is an output of a contained reactor or bank. + pr(''' + // Allocate memory to store pointers to the multiport output «trigger.name» + // of a contained reactor «trigger.parent.getFullName» + ''') startScopedReactorBlock(code, trigger.parent); // If the width is given as a numeric constant, then add that constant @@ -5940,7 +6081,6 @@ class CGenerator extends GeneratorBase { trigger.parent.definition.reactorClass) pr(''' - // Allocate memory to store pointers to the multiport outputs of a contained reactor. «CUtil.reactorRefContained(trigger.parent)».«trigger.name»_width = «width»; «CUtil.reactorRefContained(trigger.parent)».«trigger.name» = («portStructType»**)malloc( sizeof(«portStructType»*) * «width»); @@ -5962,7 +6102,14 @@ class CGenerator extends GeneratorBase { * @param The reaction instance. */ private def void deferredReactionOutputs(ReactionInstance reaction) { - val nameOfSelfStruct = CUtil.reactorRef(reaction.parent); + // val selfRef = CUtil.reactorRef(reaction.parent); + val name = reaction.parent.getFullName; + // Insert a string name to facilitate debugging. + if (targetConfig.logLevel >= LogLevel.LOG) { + pr(''' + «CUtil.reactionRef(reaction)».name = "«name» reaction «reaction.index»"; + ''') + } // Count the output ports and inputs of contained reactors that // may be set by this reaction. This ignores actions in the effects. @@ -5985,8 +6132,8 @@ class CGenerator extends GeneratorBase { if (effect.isInput) { pr(init, "// Reaction writes to an input of a contained reactor.") bankWidth = effect.parent.width; + startScopedBlock(init, effect.parent); } - startScopedBlock(init, effect.parent); pr(init, "int count = 0;") if (effect.isMultiport()) { @@ -5997,73 +6144,59 @@ class CGenerator extends GeneratorBase { // Point the output_produced field to where the is_present field of the port is. pr(init, ''' for (int i = 0; i < «effect.width»; i++) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[i + count] + «CUtil.reactionRef(reaction)».output_produced[i + count] = &«CUtil.portRefSource(effect)»[i]«connector»is_present; } count += «effect.getWidth()»; ''') outputCount += effect.width * bankWidth; } else { - // The effect is not a multiport nor a port contained by a multiport. + // The effect is not a multiport. pr(init, ''' - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced[count++] + «CUtil.reactionRef(reaction)».output_produced[count++] = &«CUtil.portRefSource(effect)».is_present; ''') outputCount += bankWidth; } - endScopedBlock(init); + if (effect.isInput) { + endScopedBlock(init); + } } pr(''' - // ** Start initialization for reaction «reaction.index» - // Total number of outputs (single ports and multiport channels) produced by the reaction. - «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs = «outputCount»; - // Allocate arrays for triggering downstream reactions. - if («nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs > 0) { - «nameOfSelfStruct»->_lf__reaction_«reaction.index».output_produced - = (bool**)malloc(sizeof(bool*) * «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs); - «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggers - = (trigger_t***)malloc(sizeof(trigger_t**) * «nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs); - «nameOfSelfStruct»->_lf__reaction_«reaction.index».triggered_sizes - = (int*)calloc(«nameOfSelfStruct»->_lf__reaction_«reaction.index».num_outputs, sizeof(int)); - } - // Initialize the output_produced array. + // Total number of outputs (single ports and multiport channels) + // produced by reaction «reaction.index» of «name». + «CUtil.reactionRef(reaction)».num_outputs = «outputCount»; + // Allocate memory for triggers[] and triggered_sizes[] on the reaction_t + // struct for this reaction. + «CUtil.reactionRef(reaction)».triggers = (trigger_t***)malloc(«outputCount» * sizeof(trigger_t**)); + «CUtil.reactionRef(reaction)».triggered_sizes = (int*)malloc(«outputCount» * sizeof(int)); + «CUtil.reactionRef(reaction)».output_produced = (bool**)malloc(«outputCount» * sizeof(bool*)); + ''') + + deferredOptimizeForSingleDominatingReaction(reaction, reaction.index); + + pr(''' «init.toString» - // ** End initialization for reaction «reaction.index» + // ** End initialization for reaction «reaction.index» of «name» ''') - } + } - /** - * Generate code to create the trigger table for each given reaction. - * Each table lists the triggers that the reaction - * execution may trigger. Each table is an array of arrays - * of pointers to the trigger_t structs representing the downstream inputs - * (or outputs of the container reactor) that are triggered by the reaction. - * Each trigger table goes into the reaction's reaction_t triggers field. - * That reaction_t struct is assumed to be on the self struct of the reactor - * instance with name "_lf__reaction_i", where i is the index of the reaction. - * The generated code will also set the values of the triggered_sizes array - * on the reaction_t struct to indicate the size of each array of trigger_t - * pointers. The generated code will malloc each of these arrays, and the - * destructor for the reactor instance will free them. + /** + * For the specified reaction, for ports that it writes to, + * fill the trigger table for triggering downstream reactions. + * * @param reactions The reactions. */ - private def deferredRemoteTriggerTable(Iterable reactions) { - for (reaction : reactions) { - val selfStruct = CUtil.reactorRef(reaction.parent); + private def void deferredFillTriggerTable(Iterable reactions) { + for (reaction: reactions) { val name = reaction.parent.getFullName; - var channelCount = 0 - - deferredOptimizeForSingleDominatingReaction(reaction, reaction.index); - - // Insert a string name to facilitate debugging. - if (targetConfig.logLevel >= LogLevel.LOG) { - pr(''' - // Reaction «reaction.index» of «name». - «selfStruct»->_lf__reaction_«reaction.index».name = "«name» reaction «reaction.index»"; - ''') - } + // Count the total number of channels in the effects of this reaction + // so that we can allocate memory for triggers[] and triggered_sizes[] + // on the reaction_t struct. + var channelCount = 0; for (port : reaction.effects.filter(PortInstance)) { + var foundDestinations = false; // Skip ports whose parent is not in the federation. // This can happen with reactions in the top-level that have // as an effect a port in a bank. @@ -6072,16 +6205,19 @@ class CGenerator extends GeneratorBase { // If the port is a multiport, then its channels may have different sets // of destinations. For ordinary ports, there will be only one range and // its width will be 1. - // We generate the code to fill the triggers array first in a temporary buffer, + // We generate the code to fill the triggers array first in a temporary code buffer, // so that we can simultaneously calculate the size of the total array. for (PortInstance.SendRange range : port.eventualDestinations()) { + + startScopedRangeBlock(code, range); + val temp = new StringBuilder(); - pr(temp, "int _lf_trigger_index = 0;"); + pr("int destination_index = 0;"); var destRangeCount = 0; for (destinationRange : range.destinations) { + foundDestinations = true; val destination = destinationRange.getPort(); - val temp2 = new StringBuilder(); - + if (destination.isOutput) { // Include this destination port only if it has at least one // reaction in the federation. @@ -6092,62 +6228,64 @@ class CGenerator extends GeneratorBase { } } if (belongs) { - pr(temp2, ''' + pr(temp, ''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. - triggerArray[_lf_trigger_index++] = &«triggerStructName( + trigger_array[destination_index++] = &«triggerStructName( destination, destination.parent.parent )»; ''') // One array entry for each destination range is sufficient. - destRangeCount += encloseInBankIteration(temp, port, destination, temp2, reaction); + destRangeCount++; } } else { // Destination is an input port. - pr(temp2, ''' + pr(temp, ''' // Point to destination port «destination.getFullName»'s trigger struct. - triggerArray[_lf_trigger_index++] = &«triggerStructName(destination)»; + trigger_array[destination_index++] = &«triggerStructName(destination)»; ''') // One array entry for each destination range is sufficient. - destRangeCount += encloseInBankIteration(temp, port, destination, temp2, reaction); + destRangeCount++; } } // Record the total size of the array. pr(''' - for (int i = 0; i < «range.totalWidth»; i++) { - // Reaction «reaction.index» of «name» triggers «channelCount» - // downstream reactions through port «port.getFullName»[«channelCount» + i]. - «selfStruct»->_lf__reaction_«reaction.index».triggered_sizes[«channelCount» + i] = «destRangeCount»; - } + // Reaction «reaction.index» of «name» triggers «destRangeCount» downstream reactions + // through port «port.getFullName»[channel]. + «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + channel] = «destRangeCount»; ''') // Malloc the memory for the arrays. pr(''' - { // For scoping - // For reaction «reaction.index» of «name», allocate an - // array of trigger pointers for downstream reactions through port «port.getFullName» - trigger_t** triggerArray = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); - for (int i = 0; i < «range.totalWidth»; i++) { - «selfStruct»->_lf__reaction_«reaction.index».triggers[«channelCount» + i] = triggerArray; - } - // Fill the trigger array. - «temp.toString()» - } + // For reaction «reaction.index» of «name», allocate an + // array of trigger pointers for downstream reactions through port «port.getFullName» + trigger_t** trigger_array = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); + «CUtil.reactionRef(reaction)».triggers[«channelCount» + channel] = trigger_array; + // Fill the trigger array. + «temp.toString()» ''') channelCount += range.totalWidth; + + endScopedRangeBlock(code, range); } - } else { - // Count the port even if it is not contained in the federate because effect - // may be a bank (it can't be an instance of a bank), so an empty placeholder - // will be needed for each member of the bank that is not in the federate. + } + if (!foundDestinations) { + // Port is not in the federate or has no destinations. channelCount += port.width; + + // Also, set the triggered_width fields to 0. + pr(''' + for (int i = 0; i < «port.width»; i++) { + «CUtil.reactionRef(reaction)».triggered_sizes[destination_index++] = 0; + } + ''') } } } } - + ////////////////////////////////////////////////////////////// // Inner class diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 511c9bbd1a..9b2bf4f8ba 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -39,6 +39,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.TimeValue; import org.lflang.generator.GeneratorCommandFactory; import org.lflang.generator.PortInstance; +import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.ValueGenerator; import org.lflang.lf.Parameter; @@ -200,6 +201,15 @@ static public String portRefSource(PortInstance port) { } return portRef(port, port.getParent()); } + + /** + * Return a reference to the reaction entry on the self struct + * of the parent of the specified reaction. + * @param reaction The reaction. + */ + static public String reactionRef(ReactionInstance reaction) { + return reactorRef(reaction.getParent()) + "->_lf__reaction_" + reaction.index; + } /** * Return the unique reference for the "self" struct of the specified diff --git a/test/C/src/BankReactionsInContainer.lf b/test/C/src/BankReactionsInContainer.lf new file mode 100644 index 0000000000..c52a281c40 --- /dev/null +++ b/test/C/src/BankReactionsInContainer.lf @@ -0,0 +1,44 @@ +/** + * This tests an output that is broadcast back to a multiport input of a bank. + */ +target C { + timeout: 1 sec, +}; +reactor R { + output[2] out:int; + input[2] in:int; + + reaction(startup) -> out {= + info_print("Sending 42"); + SET(out[0], 42); + SET(out[1], 43); + info_print("Sent"); + =} + + reaction(in) {= + for (int i = 0; i < in_width; i++) { + if (in[i]->is_present) { + info_print("Received %d on channel %d", in[i]->value, i); + } + } + =} +} +main reactor { + s = new[2] R(); + + reaction(startup) -> s.in {= + int count = 0; + for (int i = 0; i < s_width; i++) { + for (int j = 0; j < s[i].in_width; j++) { + SET(s[i].in[j], count++); + } + } + =} + reaction(s.out) {= + for (int j = 0; j < s_width; j++) { + for (int i = 0; i < s[j].out_width; i++) { + info_print("Outer received %d on channel %d from bank member %d.", s[j].out[i]->value, i, j); + } + } + =} +} From 4c6fa97b2a9a4bf28371243f90e8e528b7314a5e Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 6 Dec 2021 15:10:02 -0800 Subject: [PATCH 061/221] Bank-savy ranges now work for basic models. --- .../org/lflang/generator/PortInstance.java | 287 ++++++++++-------- .../org/lflang/generator/ReactorInstance.java | 2 +- .../org/lflang/generator/c/CGenerator.xtend | 57 +--- 3 files changed, 182 insertions(+), 164 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index c3effe0967..f02c8a0f87 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -28,7 +28,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.PriorityQueue; import java.util.Set; @@ -140,6 +139,37 @@ public List eventualDestinations() { return eventualDestinationRanges; } + // Construct the full range for this port. + // If this is an output, then the width of the range + // includes any bank the port is within. + Range range; + if (isInput()) { + range = new Range(0, 0, width, false, null); + } else { + // For an output port, the entire bank is treated as a send range + // because every channel can carry different data. + range = new Range(0, 0, width * parent.width(), false, null); + } + eventualDestinationRanges = eventualDestinations(range); + return eventualDestinationRanges; + } + + /** + * Given a Range, return a list of SendRange that describes + * the eventual destinations of the given range. + * The sum of the total widths of the send ranges on the returned list + * will equal the total width of the specified range. + * The returned list will be non-overlapping ranges in + * the order in which they should be traversed (channels, then + * banks if the specified range is not interleaved, or banks + * then channels otherwise). Each returned SendRange has a list + * of destinations Range, each of which represents a port that + * has dependent reactions. Intermediate ports with no dependent + * reactions are not listed. + * @param srcRange The source range. + */ + public static List eventualDestinations(Range srcRange) { + // Getting the destinations is more complex than getting the sources // because of multicast, where there is more than one connection statement // for a source of data. The strategy we follow here is to first get all @@ -148,72 +178,80 @@ public List eventualDestinations() { // destination range widths. We make two passes. First, we build // a queue of ranges that may overlap, then we split those ranges // and consolidate their destinations. - PriorityQueue result = new PriorityQueue(); + + List result = new ArrayList(); + PriorityQueue queue = new PriorityQueue(); + PortInstance srcPort = srcRange.getPort(); - // If this port has dependent reactions, then add it to the result. - if (!dependentReactions.isEmpty()) { + // Start with, if this port has dependent reactions, then add it to + // every range of the result. + if (!srcRange.getPort().dependentReactions.isEmpty()) { // This will be the final result if there are no connections. - // The width depends on whether this is an input port or an output port. - // For an input port (of a contained reactor), the width is just the width - // of the port. For an output port, that width is multiplied by the the bank width. - if (isInput()) { - SendRange candidate = new SendRange(0, 0, width); - candidate.destinations.add(newRange(0, 0, width, false, null)); - result.add(candidate); - } else { - // For an output port, the entire bank is treated as a send range - // because every channel can carry different data. - SendRange candidate = new SendRange(0, 0, width * parent.width()); - candidate.destinations.add(newRange(0, 0, width * parent.width(), false, null)); - result.add(candidate); } + SendRange candidate = srcPort.new SendRange( + srcRange.startChannel, srcRange.startBank, srcRange.totalWidth + ); + candidate.destinations.add(srcRange); + result.add(candidate); } - - // Next, look at downstream ports. - int sourceWidthCovered = 0; - int totalWidth = width * parent.width(); - - Iterator destinations = dependentPorts.iterator(); - while(destinations.hasNext()) { - Range dst = destinations.next(); - - // Recursively get the send ranges of that destination port. - Iterator dstSendIterator = dst.getPort().eventualDestinations().iterator(); - - if (dstSendIterator.hasNext()) { - SendRange dstSend = dstSendIterator.next(); - while (true) { - SendRange residualDst = (SendRange)dstSend.tail(totalWidth - sourceWidthCovered); - if (residualDst == null) { - // Destination range fits in current multicast iteration. - SendRange candidate = new SendRange(0, 0, dstSend.getTotalWidth()); - candidate.destinations.addAll(dstSend.destinations); - result.add(candidate); - if (!dstSendIterator.hasNext()) break; - dstSend = dstSendIterator.next(); - sourceWidthCovered += dstSend.getTotalWidth(); - } else { - // Destination range spills over into the next multicast iteration. - // Need to split the destination range. - dstSend = dstSend.truncate(totalWidth - sourceWidthCovered); - result.add(dstSend); - dstSend = residualDst; - sourceWidthCovered = 0; - } + + // Start with ports that are downstream of the range. + int srcWidthCovered = 0; + int depWidthCovered = 0; + Iterator dependentPorts = srcPort.dependentPorts.iterator(); + if (dependentPorts.hasNext()) { + Range dep = dependentPorts.next(); + while(srcWidthCovered < srcRange.getTotalWidth()) { + if (srcRange.getStartOffset() >= depWidthCovered + dep.totalWidth) { + // Destination is fully before this range. + depWidthCovered += dep.totalWidth; + if (!dependentPorts.hasNext()) break; // This should be an error. + dep = dependentPorts.next(); + continue; + } + if (depWidthCovered >= srcRange.getStartOffset() + srcRange.totalWidth) { + // Source range is covered. We are finished. + break; + } + // Dependent port overlaps the range of interest. + // Get a new range that possibly subsets the target range. + // Modify the "interleaved" status by combining that of + // the source range and the destination. + // Argument is guaranteed by above checks to be less than + // dep.totalWidth, so the result will not be null. + Range subDep = dep.tail(srcRange.getStartOffset() - depWidthCovered); + // The following argument is guaranteed to be greater than + // depWidthCovered - srcRange.getStartOffset(). + subDep = subDep.truncate(srcRange.getTotalWidth()); + + // At this point, dep is the subrange of the dependent port of interest. + // Recursively get the send ranges of that destination port. + List dstSendRanges = eventualDestinations(subDep); + + // For each returned SendRange, convert it to a SendRange + // for the srcRange port rather than the dep port. + for (SendRange dstSend : dstSendRanges) { + queue.add(dstSend.newSendRange(srcRange)); + } + depWidthCovered += subDep.totalWidth; + srcWidthCovered += subDep.totalWidth; + if (dep.getStartOffset() + dep.totalWidth <= subDep.getStartOffset() + subDep.totalWidth) { + // dep range is exhausted. Get another one. + if (!dependentPorts.hasNext()) break; // This should be an error. + dep = dependentPorts.next(); } } } - + // Now check for overlapping ranges, constructing a new result. - eventualDestinationRanges = new ArrayList(result.size()); - SendRange candidate = result.poll(); - SendRange next = result.poll(); + SendRange candidate = queue.poll(); + SendRange next = queue.poll(); while (candidate != null) { if (next == null) { // No more candidates. We are done. - eventualDestinationRanges.add(candidate); + result.add(candidate); break; } - if (candidate.startChannel == next.startChannel) { + if (candidate.getStartOffset() == next.getStartOffset()) { // Ranges have the same starting point. Need to merge them. if (candidate.getTotalWidth() <= next.getTotalWidth()) { // Can use all of the channels of candidate. @@ -221,12 +259,12 @@ public List eventualDestinations() { candidate.destinations.addAll(next.destinations); if (candidate.getTotalWidth() < next.getTotalWidth()) { // The next range has more channels connected to this sender. - next = next.truncate(candidate.getTotalWidth()); + next = (SendRange)next.tail(candidate.getTotalWidth()); // Truncate the destinations just imported. candidate = candidate.truncate(candidate.getTotalWidth()); } else { // We are done with next and can discard it. - next = result.poll(); + next = queue.poll(); } } else { // candidate is wider than next. Switch them and continue. @@ -237,23 +275,22 @@ public List eventualDestinations() { } else { // Because the result list is sorted, next starts at // a higher channel than candidate. - if (candidate.startChannel + candidate.getTotalWidth() <= next.startChannel) { + if (candidate.getStartOffset() + candidate.getTotalWidth() <= next.getStartOffset()) { // Can use candidate as is and make next the new candidate. - eventualDestinationRanges.add(candidate); + result.add(candidate); candidate = next; - next = result.poll(); + next = queue.poll(); } else { - // Ranges overlap. Have to split candidate. - SendRange candidateTail = (SendRange)candidate.tail(next.startChannel); - candidate = candidate.truncate(next.startChannel); - result.add(candidate); - candidate = candidateTail; + // Ranges overlap. Can use a truncated candidate and make its + // truncated version the new candidate. + result.add(candidate.truncate(next.getStartOffset())); + candidate = (SendRange)candidate.tail(next.getStartOffset()); } } } - return eventualDestinationRanges; + + return result; } - /** * Return a list of ports that send data to this port annotated with the channel @@ -275,7 +312,7 @@ public List eventualSources() { eventualSourceRanges = new ArrayList(); if (!dependsOnReactions.isEmpty()) { - eventualSourceRanges.add(newRange(0, 0, width * parent.width(), false, null)); + eventualSourceRanges.add(new Range(0, 0, width * parent.width(), false, null)); } else { for (Range sourceRange : dependsOnPorts) { eventualSourceRanges.addAll(sourceRange.getPort().eventualSources()); @@ -350,25 +387,7 @@ public int numDestinationReactors() { @Override public String toString() { return "PortInstance " + getFullName(); - } - - ////////////////////////////////////////////////////// - //// Protected methods. - - /** - * Create a Range representing a subset of the channels of this port. - * @param startChannel The lower end of the channel range. - * @param startBank The lower end of the bank range (or 0 for non-banks). - * @param width The width of the range (total number of connections). - * @param interleaved Marker whether bank and multiport iteration should be reversed. - * @param connection The connection associated with this range or null if none. - * @return A new instance of Range. - */ - protected Range newRange( - int startChannel, int startBank, int width, boolean interleaved, Connection connection - ) { - return new Range(startChannel, startBank, width, interleaved, connection); - } + } ////////////////////////////////////////////////////// //// Protected fields. @@ -512,16 +531,32 @@ public Range tail(int offset) { } /** - * Override the base class to return a SendRange rather than Range. + * Return a new SendRange that is converted to belong to the + * port in the specified range. If the total widths are not + * the same, then the minimum of the two widths is returned. + * If the specified srcRange is interleaved, then the interleaved + * property of each of the returned destinations will be toggled. + * @param srcRange A new source range. */ - protected Range newRange( - int startChannel, int startBank, int totalWidth - ) { - return new SendRange( - startChannel, - startBank, - totalWidth + protected SendRange newSendRange(Range srcRange) { + SendRange reference = this; + if (srcRange.totalWidth > totalWidth) { + srcRange = srcRange.truncate(totalWidth); + } else if (srcRange.totalWidth < totalWidth) { + reference = truncate(srcRange.totalWidth); + } + SendRange result = srcRange.getPort().new SendRange( + srcRange.startChannel, srcRange.startBank, totalWidth ); + if (srcRange.interleaved) { + // Toggle the destination interleaved status. + for (Range dst : reference.destinations) { + result.destinations.add(dst.toggleInterleaved()); + } + } else { + result.destinations.addAll(reference.destinations); + } + return result; } /** @@ -577,14 +612,21 @@ public PortInstance getPort() { } /** - * Compare the ranges by just comparing their startChannel index. + * Compare the ranges by just comparing their start offset + * and then by total width. */ @Override public int compareTo(Range o) { - if (startChannel < o.startChannel) { + if (getStartOffset() < o.getStartOffset()) { return -1; - } else if (startChannel == o.startChannel) { - return 0; + } else if (getStartOffset() == o.getStartOffset()) { + if (totalWidth < o.totalWidth) { + return -1; + } else if (totalWidth == o.totalWidth) { + return 0; + } else { + return 1; + } } else { return 1; } @@ -617,12 +659,13 @@ public int getTotalWidth() { * multiport channels first (if not interleaved) or banks first * (if interleaved). If the offset is greater than or equal to * the total width, then this returns null. + * If this offset is 0 then this returns this width unmodified. * @param offset The number of channels to consume. */ public Range tail(int offset) { - if (offset >= totalWidth) { - return null; - } + if (offset == 0) return this; + if (offset >= totalWidth) return null; + int channelWidth = PortInstance.this.width; int bankWidth = PortInstance.this.parent.width(); @@ -634,7 +677,7 @@ public Range tail(int offset) { banksToConsume = offset / channelWidth; channelsToConsume = offset % channelWidth; } - return newRange( + return new Range( startChannel + channelsToConsume, startBank + banksToConsume, totalWidth - offset, @@ -643,6 +686,20 @@ public Range tail(int offset) { ); } + /** + * Return a new range identical to this one except with the + * interleaved flag toggled. + */ + public Range toggleInterleaved() { + return new Range( + startChannel, + startBank, + totalWidth, + !interleaved, + connection + ); + } + @Override public String toString() { return String.format( @@ -657,27 +714,15 @@ public String toString() { /** * Return a new range that is identical to this range but * with a reduced total width to the specified width. + * If the new width is greater than or equal to the width + * of this range, then return this range. * @param newWidth The new width. */ protected Range truncate(int newWidth) { - return newRange(startChannel, startBank, newWidth, interleaved, connection); - } - - /** - * Create a new Range with the specified parameters. - */ - protected Range newRange( - int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection - ) { - return new Range( - startChannel, - startBank, - totalWidth, - interleaved, - connection - ); + if (newWidth >= totalWidth) return this; + return new Range(startChannel, startBank, newWidth, interleaved, connection); } - - private int totalWidth; // Allow modification. + + protected int totalWidth; } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 735074e6be..48250f0eb6 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -990,7 +990,7 @@ private List listPortInstances(List references) { connection = (Connection)portRef.eContainer(); } - PortInstance.Range range = portInstance.newRange( + PortInstance.Range range = portInstance.new Range( 0, 0, portInstance.width * bankWidth, portRef.isInterleaved(), connection ); result.add(range); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 0cd43430f4..0f1ae10198 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4809,7 +4809,6 @@ class CGenerator extends GeneratorBase { startScopedBlock(builder, null); defineSelfStruct(builder, reactor); - // If the reactor is not a bank, no need to do anything further. if (reactor.isBank) { val bankIndex = CUtil.bankIndex(reactor); if (port.isMultiport) { @@ -4825,6 +4824,8 @@ class CGenerator extends GeneratorBase { // Send range covers bank member(s). Iterate over bank members. for (int «bankIndex» = «range.startBank»; «bankIndex» < «range.startBank» + «range.totalWidth»; «bankIndex»++) { ''') + indent(builder); + pr(builder, "int channel = 0;"); } } else if (range.port.isMultiport) { // Reactor is not a bank, but port is a multiport. @@ -4832,12 +4833,14 @@ class CGenerator extends GeneratorBase { // Send range covers channels of a multiport. Iterate over channels. for (int channel = «range.startChannel»; channel < «range.startChannel» + «range.totalWidth»; channel++) { ''') + indent(builder); } else { // Not a multiport nor a bank. // For consistent depth of nesting, generate a scoped block. pr(builder, "{"); + indent(builder); + pr(builder, "int channel = 0;"); } - indent(builder); } /** @@ -4876,46 +4879,6 @@ class CGenerator extends GeneratorBase { endScopedBlock(builder); } - /** - * Start a deep scoped block for the specified reactor in situations where - * the self struct for the specified reactor is not already in scope - * and the self struct of some of its containers may also not be in scope. - * Specifically, the reference reactor is assumed to deeply contain the - * specified reactor. A scoped block is created for each intermediate - * container. If the reference reactor does not deeply contain the specified - * reactor, then nested scoped blocks are created all the way to the top level, - * not including the top level. - * This must be followed by an {@link endDeepScopedReactorBlock(StringBuilder)}. - * @param builder The string builder into which to write. - * @param reactor The reactor instance. - * @param reference The container. - */ - private def void startDeepScopedReactorBlock( - StringBuilder builder, ReactorInstance reactor, ReactorInstance reference - ) { - startScopedReactorBlock(builder, reactor); - val container = reactor.parent; - if (container != reference && container.depth > 1) { - startDeepScopedReactorBlock(builder, container, reference); - } - } - - /** - * End a deep scoped block. - * @param builder The string builder into which to write. - * @param reactor The reactor instance. - * @param reference The container. - */ - private def void endDeepScopedReactorBlock( - StringBuilder builder, ReactorInstance reactor, ReactorInstance reference - ) { - endScopedReactorBlock(builder, reactor); - val container = reactor.parent; - if (container != reference && container.depth > 1) { - endDeepScopedReactorBlock(builder, container, reference); - } - } - /** * For the specified reactor, print code to the specified builder * that defines a pointer to the self struct of the specified @@ -6211,12 +6174,22 @@ class CGenerator extends GeneratorBase { startScopedRangeBlock(code, range); + // Keep track of which reactors have their self struct declared in scope. + val declared = new HashSet(); + declared.add(port.parent); + val temp = new StringBuilder(); pr("int destination_index = 0;"); var destRangeCount = 0; for (destinationRange : range.destinations) { foundDestinations = true; val destination = destinationRange.getPort(); + + if (!declared.contains(destination.parent)) { + // Need to declare the self struct. + declared.add(destination.parent); + defineSelfStruct(temp, destination.parent); + } if (destination.isOutput) { // Include this destination port only if it has at least one From 7ffaf0eee55685d9bbb199a5d72926be79b6f3bd Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 6 Dec 2021 16:22:56 -0800 Subject: [PATCH 062/221] Interrim checkin --- .../org/lflang/generator/c/CGenerator.xtend | 40 ++++++++++--------- .../BankReactionsInContainer.lf | 0 2 files changed, 22 insertions(+), 18 deletions(-) rename test/C/src/{ => multiport}/BankReactionsInContainer.lf (100%) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 0f1ae10198..4813492304 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3590,8 +3590,11 @@ class CGenerator extends GeneratorBase { '// ***** Start initializing ' + fullName + ' of class ' + reactorClass.name) var selfStruct = CUtil.reactorRef(instance) - var structType = CUtil.selfType(reactorClass) + // Create a variable that will point to this new self struct in + // this scope and nested scopes. + defineSelfStruct(initializeTriggerObjects, instance); + // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. startScopedBlock(initializeTriggerObjects, instance); @@ -3601,22 +3604,9 @@ class CGenerator extends GeneratorBase { // Record the self struct on the big array of self structs. // FIXME: only works for banks. pr(initializeTriggerObjects, ''' - self_structs[«CUtil.indexExpression(instance)»] = new_«reactorClass.name»(); + «selfStruct» = new_«reactorClass.name»(); ''') - - // Create a variable that will point to this new self struct in - // this scope and nested scopes. The form is slightly different - // depending on whether its in a bank of reactors. - if (instance.isBank) { - pr(initializeTriggerObjects, ''' - «structType»** «instance.uniqueID()»_self = («structType»**)&self_structs[«CUtil.indexExpression(instance)»]; - ''') - } else { - pr(initializeTriggerObjects, ''' - «structType»* «instance.uniqueID()»_self = («structType»*)self_structs[«CUtil.indexExpression(instance)»]; - ''') - } - + // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. @@ -3649,7 +3639,7 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, ''' «selfStruct»->_lf_«input.name»_width = «multiportWidthSpecInC(input, null, instance)»; // Allocate memory for multiport inputs. - «selfStruct»->_lf_«input.name» = («variableStructType(input, reactorClass)»**)malloc(sizeof(«variableStructType(input, reactorClass)»*) * «selfStruct»->_lf_«input.name»_width); + «selfStruct»->_lf_«input.name» = («variableStructType(input, reactorClass)»**)malloc(sizeof(«variableStructType(input, reactorClass)»*) * «multiportWidthSpecInC(input, null, instance)»); // Set inputs by default to an always absent default input. for (int i = 0; i < «selfStruct»->_lf_«input.name»_width; i++) { «selfStruct»->_lf_«input.name»[i] = &«selfStruct»->_lf_default__«input.name»; @@ -4818,6 +4808,7 @@ class CGenerator extends GeneratorBase { int «bankIndex» = «range.startBank»; while (range_count++ < «range.totalWidth») { ''') + indent(builder); } else { // Bank, but not a multiport. pr(builder, ''' @@ -5652,6 +5643,11 @@ class CGenerator extends GeneratorBase { // The port belongs to a contained reactor, which may be a bank. startScopedReactorBlock(code, port.parent); + // If the port is an input, then the port reference may in the parent's parent. + if (port.isInput() && port.parent.parent !== null) { + defineSelfStruct(code, port.parent.parent); + } + if (port.isMultiport()) { pr(''' // Connect «port», which gets data from reaction «reaction.index» @@ -5687,7 +5683,11 @@ class CGenerator extends GeneratorBase { // destination port, and there may be multiple sources // if the destination is a multiport. if (port.isMultiport) { - pr("int dst_channel = 0;"); + pr(''' + { + int dst_channel = 0; + '''); + indent(code); } for (eventualSource: port.eventualSources()) { val sourcePort = eventualSource.getPort(); @@ -5745,6 +5745,10 @@ class CGenerator extends GeneratorBase { pr("}"); } } + if (port.isMultiport) { + unindent(code); + pr("}"); + } } } } diff --git a/test/C/src/BankReactionsInContainer.lf b/test/C/src/multiport/BankReactionsInContainer.lf similarity index 100% rename from test/C/src/BankReactionsInContainer.lf rename to test/C/src/multiport/BankReactionsInContainer.lf From 65c24da749e36755471044924b23d32916e844be Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 7 Dec 2021 05:08:53 -0800 Subject: [PATCH 063/221] Interrim checkin --- .../org/lflang/generator/c/CGenerator.xtend | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 4813492304..8cefa7fd32 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3591,10 +3591,6 @@ class CGenerator extends GeneratorBase { var selfStruct = CUtil.reactorRef(instance) - // Create a variable that will point to this new self struct in - // this scope and nested scopes. - defineSelfStruct(initializeTriggerObjects, instance); - // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. startScopedBlock(initializeTriggerObjects, instance); @@ -3604,8 +3600,12 @@ class CGenerator extends GeneratorBase { // Record the self struct on the big array of self structs. // FIXME: only works for banks. pr(initializeTriggerObjects, ''' - «selfStruct» = new_«reactorClass.name»(); + self_structs[«CUtil.indexExpression(instance)»] = new_«reactorClass.name»(); ''') + + // Create a variable that will point to this new self struct in + // this scope and nested scopes. + defineSelfStruct(initializeTriggerObjects, instance); // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. @@ -4612,7 +4612,7 @@ class CGenerator extends GeneratorBase { // Start a scoped block so we can define bank index variables without // resulting in them being multiply defined. - startScopedBlock(builder, null); + startScopedBlock(builder); // The following defines the self structs only if the parents are not a parent of context. defineSelfStruct(builder, source.parent, context); @@ -4683,6 +4683,16 @@ class CGenerator extends GeneratorBase { return result; } + /** + * Start a scoped block, which is a section of code + * surrounded by curley braces and indented. + * This must be followed by an {@link endScopedBlock(StringBuilder)}. + * @param builder The string builder into which to write. + */ + private def void startScopedBlock(StringBuilder builder) { + startScopedBlock(builder, null); + } + /** * Start a scoped block for the specified reactor. * If the reactor is a bank, then this starts a for loop @@ -4730,7 +4740,7 @@ class CGenerator extends GeneratorBase { // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. if (reactor != main) { - startScopedBlock(builder, null); + startScopedBlock(builder); defineSelfStruct(builder, reactor); if (reactor.isBank()) { // Generate of for loop to iterate over the bank members. @@ -4796,7 +4806,7 @@ class CGenerator extends GeneratorBase { // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. - startScopedBlock(builder, null); + startScopedBlock(builder); defineSelfStruct(builder, reactor); if (reactor.isBank) { @@ -6100,6 +6110,8 @@ class CGenerator extends GeneratorBase { pr(init, "// Reaction writes to an input of a contained reactor.") bankWidth = effect.parent.width; startScopedBlock(init, effect.parent); + } else { + startScopedBlock(init); } pr(init, "int count = 0;") @@ -6125,9 +6137,7 @@ class CGenerator extends GeneratorBase { ''') outputCount += bankWidth; } - if (effect.isInput) { - endScopedBlock(init); - } + endScopedBlock(init); } pr(''' // Total number of outputs (single ports and multiport channels) From 43ad017b9d801f7b69c09f2feae098afc11d37bb Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 7 Dec 2021 07:06:54 -0800 Subject: [PATCH 064/221] Simplyfying refactoring and removing dependence on adjacent self structs for banks. --- .../org/lflang/generator/c/CGenerator.xtend | 348 ++++++++---------- .../src/org/lflang/generator/c/CUtil.java | 25 +- 2 files changed, 162 insertions(+), 211 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 8cefa7fd32..8096de128c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -87,7 +87,6 @@ import org.lflang.lf.Reactor import org.lflang.lf.ReactorDecl import org.lflang.lf.StateVar import org.lflang.lf.TriggerRef -import org.lflang.lf.TypedVariable import org.lflang.lf.VarRef import org.lflang.lf.Variable import org.lflang.util.XtendUtil @@ -3080,7 +3079,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var nameOfSelfStruct = CUtil.reactorRef(child) - startScopedReactorBlock(temp, child); + startScopedBlock(temp, child); for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { @@ -3108,7 +3107,7 @@ class CGenerator extends GeneratorBase { } } } - endScopedReactorBlock(temp, child); + endScopedBlock(temp); if (foundOne) { pr(startTimeStep, temp.toString()); @@ -3120,7 +3119,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var containerSelfStructName = CUtil.reactorRef(instance) - startScopedReactorBlock(temp, instance); + startScopedBlock(temp, instance); // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an @@ -3137,7 +3136,7 @@ class CGenerator extends GeneratorBase { // the input of a contained reactor in the federate. if (currentFederate.contains(port.parent)) { - startScopedReactorBlock(temp, port.parent); + startScopedBlock(temp, port.parent); // If this is a multiport, then the port struct on the self // struct is a pointer. Otherwise, it is the struct itself. @@ -3179,7 +3178,7 @@ class CGenerator extends GeneratorBase { } startTimeStepIsPresentCount++ } - endScopedReactorBlock(temp, port.parent); + endScopedBlock(temp); } } } @@ -3237,7 +3236,7 @@ class CGenerator extends GeneratorBase { } } - endScopedReactorBlock(temp, instance); + endScopedBlock(temp); if (foundOne) pr(startTimeStep, temp.toString()); @@ -3245,7 +3244,7 @@ class CGenerator extends GeneratorBase { for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { - startScopedReactorBlock(startTimeStep, child); + startScopedBlock(startTimeStep, child); for (output : child.outputs) { if (output.isMultiport()) { @@ -3287,7 +3286,7 @@ class CGenerator extends GeneratorBase { startTimeStepIsPresentCount++ } } - endScopedReactorBlock(startTimeStep, child); + endScopedBlock(startTimeStep); } } } @@ -3402,6 +3401,8 @@ class CGenerator extends GeneratorBase { /** * 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)}. * @param variable The variable. * @param reactor The reactor class. * @return The name of the self struct. @@ -3409,6 +3410,18 @@ class CGenerator extends GeneratorBase { static def variableStructType(Variable variable, ReactorDecl reactor) { '''«reactor.name.toLowerCase»_«variable.name»_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, ReactorDecl)}. + * @param portOrAction The port or action instance. + * @return The name of the self struct. + */ + static def variableStructType(TriggerInstance portOrAction) { + '''«portOrAction.parent.reactorDefinition.name.toLowerCase»_«portOrAction.name»_t''' + } /** * Return the function name for specified reaction of the @@ -3588,12 +3601,10 @@ class CGenerator extends GeneratorBase { var fullName = instance.fullName pr(initializeTriggerObjects, '// ***** Start initializing ' + fullName + ' of class ' + reactorClass.name) - - var selfStruct = CUtil.reactorRef(instance) // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. - startScopedBlock(initializeTriggerObjects, instance); + startScopedBlock(initializeTriggerObjects, instance, false); // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). @@ -3613,46 +3624,12 @@ class CGenerator extends GeneratorBase { generateTraceTableEntries(instance, instance.actions, instance.timers) generateReactorInstanceExtension(instance, instance.reactions) generateParameterInitialization(instance) - - // Once parameters are done, we can allocate memory for any multiports. - // Allocate memory for outputs. - // NOTE: Not done for top level. - for (output : reactorClass.toDefinition.outputs) { - // If the port is a multiport, create an array. - if (JavaAstUtils.isMultiport(output)) { - initializeOutputMultiport(initializeTriggerObjects, output, selfStruct, instance) - } else { - pr(initializeTriggerObjects, ''' - // width of -2 indicates that it is not a multiport. - «selfStruct»->_lf_«output.name»_width = -2; - ''') - } - } - + initializeOutputMultiports(instance) + recordStartupAndShutdown(instance.reactions); - // Next, allocate memory for input. - // NOTE: Not done for top level. - for (input : reactorClass.toDefinition.inputs) { - // If the port is a multiport, create an array. - if (JavaAstUtils.isMultiport(input)) { - pr(initializeTriggerObjects, ''' - «selfStruct»->_lf_«input.name»_width = «multiportWidthSpecInC(input, null, instance)»; - // Allocate memory for multiport inputs. - «selfStruct»->_lf_«input.name» = («variableStructType(input, reactorClass)»**)malloc(sizeof(«variableStructType(input, reactorClass)»*) * «multiportWidthSpecInC(input, null, instance)»); - // Set inputs by default to an always absent default input. - for (int i = 0; i < «selfStruct»->_lf_«input.name»_width; i++) { - «selfStruct»->_lf_«input.name»[i] = &«selfStruct»->_lf_default__«input.name»; - } - ''') - } else { - pr(initializeTriggerObjects, ''' - // width of -2 indicates that it is not a multiport. - «selfStruct»->_lf_«input.name»_width = -2; - ''') - } - } - + initializeInputs(instance) + // Next, initialize the "self" struct with state variables. // These values may be expressions that refer to the parameter values defined above. generateStateVariableInitializations(instance); @@ -3855,18 +3832,52 @@ class CGenerator extends GeneratorBase { } /** - * Generate code that malloc's memory for an output multiport. - * @param builder The generated code is put into builder - * @param output The output port to be initialized - * @name + * Generate code that mallocs memory for any output multiports. + * @param reactor The reactor instance. */ - def initializeOutputMultiport(StringBuilder builder, Output output, String nameOfSelfStruct, ReactorInstance instance) { - val reactor = instance.definition.reactorClass - pr(builder, ''' - «nameOfSelfStruct»->_lf_«output.name»_width = «multiportWidthSpecInC(output, null, instance)»; - // Allocate memory for multiport output. - «nameOfSelfStruct»->_lf_«output.name» = («variableStructType(output, reactor)»*)calloc(«nameOfSelfStruct»->_lf_«output.name»_width, sizeof(«variableStructType(output, reactor)»)); - ''') + def initializeOutputMultiports(ReactorInstance reactor) { + for (output : reactor.outputs) { + // If the port is a multiport, create an array. + if (output.isMultiport) { + pr(initializeTriggerObjects, ''' + «CUtil.portRef(output)»_width = «output.width»; + // Allocate memory for multiport output. + «CUtil.portRef(output)» = («variableStructType(output)»*)calloc(«CUtil.portRef(output)»_width, sizeof(«variableStructType(output)»)); + ''') + } else { + pr(initializeTriggerObjects, ''' + // width of -2 indicates that it is not a multiport. + «CUtil.portRefSource(output)»_width = -2; + ''') + } + } + } + + /** + * Allocate memory for inputs. + * @param reactor The reactor. + */ + def initializeInputs(ReactorInstance reactor) { + for (input : reactor.inputs) { + val portRef = CUtil.portRef(input) + // If the port is a multiport, create an array. + if (input.isMultiport) { + pr(initializeTriggerObjects, ''' + «portRef»_width = «input.width»; + // Allocate memory for multiport inputs. + «portRef» = («variableStructType(input)»**)malloc(sizeof(«variableStructType(input)»*) * «input.width»); + // Set inputs by default to an always absent default input. + for (int i = 0; i < «input.width»; i++) { + «portRef»[i] = &«CUtil.reactorRef(reactor)»->_lf_default__«input.name»; + } + ''') + } else { + pr(initializeTriggerObjects, ''' + // width of -2 indicates that it is not a multiport. + «portRef»_width = -2; + ''') + } + } } /** @@ -3926,7 +3937,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var foundOne = false; - startScopedReactorBlock(temp, reactor); + startScopedBlock(temp, reactor); for (r : reactor.reactions) { if (federate === null || federate.contains( @@ -3945,7 +3956,7 @@ class CGenerator extends GeneratorBase { ''') } } - endScopedReactorBlock(temp, reactor); + endScopedBlock(temp); if (foundOne) pr(temp.toString()); @@ -4604,8 +4615,7 @@ class CGenerator extends GeneratorBase { StringBuilder builder, NamedInstance source, NamedInstance destination, - StringBuilder toEnclose, - NamedInstance context + StringBuilder toEnclose ) { val sourceParentBanksReversed = new LinkedList(); var result = 1; @@ -4613,11 +4623,9 @@ class CGenerator extends GeneratorBase { // Start a scoped block so we can define bank index variables without // resulting in them being multiply defined. startScopedBlock(builder); - // The following defines the self structs only if the parents are not a parent of context. - defineSelfStruct(builder, source.parent, context); if (source.parent != destination.parent) { - defineSelfStruct(builder, destination.parent, context); + defineSelfStruct(builder, destination.parent); // Initialize parent bank indices and construct a reversed list. val sourceParents = source.parents; @@ -4690,7 +4698,7 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. */ private def void startScopedBlock(StringBuilder builder) { - startScopedBlock(builder, null); + startScopedBlock(builder, null, true); } /** @@ -4699,11 +4707,31 @@ class CGenerator extends GeneratorBase { * that iterates over the bank members using a standard index * variable. If the reactor is null or is not a bank, then this simply * starts a scoped block by printing an opening curly brace. + * This also adds a declaration of a pointer to the self + * struct of the reactor or bank member. * This must be followed by an {@link endScopedBlock(StringBuilder)}. * @param builder The string builder into which to write. * @param reactor The reactor instance. */ private def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { + startScopedBlock(builder, reactor, true) + } + + /** + * Start a scoped block for the specified reactor. + * If the reactor is a bank, then this starts a for loop + * that iterates over the bank members using a standard index + * variable. If the reactor is null or is not a bank, then this simply + * starts a scoped block by printing an opening curly brace. + * If the declare arguement is true, then this also + * adds a declaration of a pointer to the self + * struct of the reactor or bank member. + * This must be followed by an {@link endScopedBlock(StringBuilder)}. + * @param builder The string builder into which to write. + * @param reactor The reactor instance or null for a simple scoped block. + * @param declare True to declare a pointer to the self struct + */ + private def void startScopedBlock(StringBuilder builder, ReactorInstance reactor, boolean declare) { if (reactor !== null && reactor.isBank) { val index = CUtil.bankIndex(reactor); pr(builder, ''' @@ -4714,6 +4742,7 @@ class CGenerator extends GeneratorBase { pr(builder, "{"); } indent(builder); + if (declare && reactor != null) defineSelfStruct(builder, reactor); } /** @@ -4725,45 +4754,6 @@ class CGenerator extends GeneratorBase { pr(builder, "}"); } - /** - * Start a scoped block for the specified reactor in situations where - * the self struct for the specified reactor is not already in scope. - * If the reactor is a bank, then this starts a for loop - * that iterates over the bank members using a standard index - * variable. If the reactor is null or is not a bank, then this simply - * starts a scoped block by printing an opening curly brace. - * This must be followed by an {@link endScopedReactorBlock(StringBuilder)}. - * @param builder The string builder into which to write. - * @param reactor The reactor instance. - */ - protected def void startScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { - // The first creates a scope in which we can define a pointer to the self - // struct without fear of redefining. - if (reactor != main) { - startScopedBlock(builder); - defineSelfStruct(builder, reactor); - if (reactor.isBank()) { - // Generate of for loop to iterate over the bank members. - startScopedBlock(builder, reactor); - } - } - } - - /** - * End a scoped reactor block. - * @param builder The string builder into which to write. - * @param reactor The reactor instance. - */ - protected def void endScopedReactorBlock(StringBuilder builder, ReactorInstance reactor) { - if (reactor != main) { - if (reactor.isBank()) { - // Close the for loop iterating over bank members. - endScopedBlock(builder); - } - endScopedBlock(builder); - } - } - /** * Start a scoped block for the specified range. * @@ -4807,7 +4797,6 @@ class CGenerator extends GeneratorBase { // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. startScopedBlock(builder); - defineSelfStruct(builder, reactor); if (reactor.isBank) { val bankIndex = CUtil.bankIndex(reactor); @@ -4819,28 +4808,32 @@ class CGenerator extends GeneratorBase { while (range_count++ < «range.totalWidth») { ''') indent(builder); + defineSelfStruct(builder, reactor); } else { // Bank, but not a multiport. pr(builder, ''' // Send range covers bank member(s). Iterate over bank members. for (int «bankIndex» = «range.startBank»; «bankIndex» < «range.startBank» + «range.totalWidth»; «bankIndex»++) { ''') - indent(builder); - pr(builder, "int channel = 0;"); + indent(builder); + pr(builder, "int channel = 0;"); + defineSelfStruct(builder, reactor); } } else if (range.port.isMultiport) { // Reactor is not a bank, but port is a multiport. - pr(builder, ''' + defineSelfStruct(builder, reactor); + pr(builder, ''' // Send range covers channels of a multiport. Iterate over channels. for (int channel = «range.startChannel»; channel < «range.startChannel» + «range.totalWidth»; channel++) { ''') - indent(builder); + indent(builder); } else { // Not a multiport nor a bank. // For consistent depth of nesting, generate a scoped block. pr(builder, "{"); indent(builder); pr(builder, "int channel = 0;"); + defineSelfStruct(builder, reactor); } } @@ -4894,45 +4887,12 @@ class CGenerator extends GeneratorBase { private def void defineSelfStruct( StringBuilder builder, ReactorInstance reactor ) { - defineSelfStruct(builder, reactor, null); - } - - /** - * For the specified reactor, if necessary, print code to the specified builder - * that defines a pointer to the self struct of the specified - * reactor. - * - * The self struct is necessary if the specified reactor is not main nor - * a parent (even indirectly) of the specified instance (or if the - * specified instance is null. - * - * If the specified reactor is a bank, then the pointer - * is to the array of pointers to the self structs for that bank. - * - * @param builder The string builder into which to write. - * @param reactor The reactor instance for which to provide a self struct. - * @param instance The current object whose parents are all in scope. - */ - private def void defineSelfStruct( - StringBuilder builder, ReactorInstance reactor, NamedInstance instance - ) { - if ((instance !== null && instance.hasParent(reactor)) || reactor == main) { - // Assume the self struct is already in scope because the reactor - // is a parent of the instance. - return; - } var nameOfSelfStruct = reactor.uniqueID() + "_self"; var structType = CUtil.selfType(reactor) - if (reactor.isBank) { - pr(builder, ''' - «structType»** «nameOfSelfStruct» = («structType»**)&self_structs[«CUtil.indexExpression(reactor.parent)» + «reactor.getIndexOffset»]; - ''') - } else { - pr(builder, ''' - «structType»* «nameOfSelfStruct» = («structType»*)self_structs[«CUtil.indexExpression(reactor)»]; - ''') - } - } + pr(builder, ''' + «structType»* «nameOfSelfStruct» = («structType»*)self_structs[«CUtil.indexExpression(reactor)»]; + ''') + } /** * Generate assignments of pointers in the "self" struct of a destination @@ -4952,11 +4912,13 @@ class CGenerator extends GeneratorBase { if (src != port && currentFederate.contains(src.parent)) { val temp = new StringBuilder(); // The eventual source is different from the port and is in the federate. - val destStructType = variableStructType( - port.definition as TypedVariable, - port.parent.definition.reactorClass - ) + val destStructType = variableStructType(port) + // If the src self struct pointer is not already declared, declare it. + if (src.parent != port.parent) { + defineSelfStruct(temp, src.parent) + } + // There are four cases, depending on whether the source or // destination or both are multiports. if (src.isMultiport()) { @@ -4964,7 +4926,7 @@ class CGenerator extends GeneratorBase { // address, whereas if it's an output port, we do. var modifier = "&"; if (src.isInput()) modifier = ""; - + if (port.isMultiport()) { // Source and destination are both multiports. pr(temp, ''' @@ -5000,7 +4962,7 @@ class CGenerator extends GeneratorBase { } // The null argument ensures that both src and port self struct // variables are defined. - encloseInBankIteration(code, src, port, temp, null); + encloseInBankIteration(code, src, port, temp); } } } @@ -5563,7 +5525,7 @@ class CGenerator extends GeneratorBase { // First batch of initializations is within a for loop iterating // over bank members for the reactor's parent. - startScopedReactorBlock(code, reactor); + startScopedBlock(code, reactor); // If the child has a multiport that is an effect of some reaction in its container, // then we have to generate code to allocate memory for arrays pointing to @@ -5591,7 +5553,7 @@ class CGenerator extends GeneratorBase { // output of a contained reactor. deferredConnectReactionsToPorts(reactor) - endScopedReactorBlock(code, reactor) + endScopedBlock(code) // Second batch of initializes cannot be within a for loop // iterating over bank members because they iterate over send @@ -5646,12 +5608,9 @@ class CGenerator extends GeneratorBase { // the input of a contained reactor. If the contained reactor is // not in the federate, then we don't do anything here. if (currentFederate.contains(port.parent)) { - val destStructType = variableStructType( - port.definition as TypedVariable, - port.parent.definition.reactorClass - ) + val destStructType = variableStructType(port) // The port belongs to a contained reactor, which may be a bank. - startScopedReactorBlock(code, port.parent); + startScopedBlock(code, port.parent); // If the port is an input, then the port reference may in the parent's parent. if (port.isInput() && port.parent.parent !== null) { @@ -5673,7 +5632,7 @@ class CGenerator extends GeneratorBase { «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(port)»; ''') } - endScopedReactorBlock(code, port.parent); + endScopedBlock(code); } } } @@ -5684,10 +5643,8 @@ class CGenerator extends GeneratorBase { // of a contained reactor. If the contained reactor is // not in the federate, then we don't do anything here. if (currentFederate.contains(port.parent)) { - val destStructType = variableStructType( - port.definition as TypedVariable, - port.parent.definition.reactorClass - ) + val destStructType = variableStructType(port) + // The port may be deeper in the hierarchy, in which // case, the source port will not be the same as the // destination port, and there may be multiple sources @@ -5704,7 +5661,7 @@ class CGenerator extends GeneratorBase { if (sourcePort != port) { // sourcePort is deeper in the hierarchy. Need a - // pointer to the self struct of the destination port. + // pointer to the self struct of the destination port's parent. pr("{"); indent(); defineSelfStruct(code, port.parent); @@ -5918,9 +5875,7 @@ class CGenerator extends GeneratorBase { pr("// A reaction writes to a multiport of a child. Allocate memory.") portsHandled.add(effect); - val portStructType = variableStructType( - effect.definition, effect.parent.definition.reactorClass - ); + val portStructType = variableStructType(effect) startScopedBlock(code, effect.parent); @@ -5953,22 +5908,17 @@ class CGenerator extends GeneratorBase { // Port is an effect of a parent's reaction. // That is, the port belongs to the same reactor as the reaction. // The reaction is writing to an output of its container reactor. - val nameOfSelfStruct = CUtil.reactorRef(port.parent); - val portStructType = variableStructType( - port.definition, - port.parent.definition.reactorClass - ) + val portRef = CUtil.portRef(port); + val portStructType = variableStructType(port) pr(''' - «nameOfSelfStruct»->_lf_«port.name»_width = «port.width»; + «portRef»_width = «port.width»; // Allocate memory to store output of reaction. - «nameOfSelfStruct»->_lf_«port.name» = («portStructType»*)calloc(«nameOfSelfStruct»->_lf_«port.name»_width, - sizeof(«portStructType»)); - «nameOfSelfStruct»->_lf_«port.name»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) - * «nameOfSelfStruct»->_lf_«port.name»_width); + «portRef» = («portStructType»*)calloc(«port.width», sizeof(«portStructType»)); + «portRef»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) * «port.width»); // Assign each output port pointer to be used in reactions to facilitate user access to output ports - for(int i=0; i < «nameOfSelfStruct»->_lf_«port.name»_width; i++) { - «nameOfSelfStruct»->_lf_«port.name»_pointers[i] = &(«nameOfSelfStruct»->_lf_«port.name»[i]); + for(int i=0; i < «port.width»; i++) { + «portRef»_pointers[i] = &(«portRef»[i]); } ''') // There may be more reactions writing to this port, @@ -5990,8 +5940,7 @@ class CGenerator extends GeneratorBase { ReactionInstance reaction, int reactionNumber ) { - val reactorInstance = reaction.parent; - val selfStruct = CUtil.reactorRef(reactorInstance) + val reactor = reaction.parent; // Record the number of reactions that this reaction depends on. // This is used for optimization. When that number is 1, the reaction can @@ -6004,25 +5953,27 @@ class CGenerator extends GeneratorBase { ) { dominatingReaction = dominatingReaction.findSingleDominatingReaction(); } + val reactionRef = CUtil.reactionRef(reaction); if (dominatingReaction !== null && currentFederate.contains(dominatingReaction.definition) && currentFederate.contains(dominatingReaction.parent) ) { - // Define the destination struct pointer, if needed. - // FIXME: If the destination is a bank, need to define the bank_index variable. val temp = new StringBuilder(); - val upstreamReaction = '''«CUtil.reactorRef(dominatingReaction.parent)»->_lf__reaction_«dominatingReaction.index»''' + val dominatingReactionRef = CUtil.reactionRef(dominatingReaction) + if (dominatingReaction.parent != reaction.parent) { + defineSelfStruct(temp, dominatingReaction.parent) + } pr(temp, ''' - // Reaction «reactionNumber» of «reactorInstance.getFullName» depends on one maximal upstream reaction. - «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = &(«upstreamReaction»); + // Reaction «reactionNumber» of «reactor.getFullName» depends on one maximal upstream reaction. + «reactionRef».last_enabling_reaction = &(«dominatingReactionRef»); ''') - encloseInBankIteration(code, dominatingReaction, reaction, temp, reaction); + encloseInBankIteration(code, dominatingReaction, reaction, temp); } else { pr(''' - // Reaction «reactionNumber» of «reactorInstance.getFullName» does not depend on one maximal upstream reaction. - «selfStruct»->_lf__reaction_«reactionNumber».last_enabling_reaction = NULL; + // Reaction «reactionNumber» of «reactor.getFullName» does not depend on one maximal upstream reaction. + «reactionRef».last_enabling_reaction = NULL; ''') } } @@ -6049,13 +6000,12 @@ class CGenerator extends GeneratorBase { // Allocate memory to store pointers to the multiport output «trigger.name» // of a contained reactor «trigger.parent.getFullName» ''') - startScopedReactorBlock(code, trigger.parent); + startScopedBlock(code, trigger.parent); // If the width is given as a numeric constant, then add that constant // to the output count. Otherwise, assume it is a reference to one or more parameters. val width = trigger.width * trigger.parent.width; - val portStructType = variableStructType(trigger.definition, - trigger.parent.definition.reactorClass) + val portStructType = variableStructType(trigger) pr(''' «CUtil.reactorRefContained(trigger.parent)».«trigger.name»_width = «width»; @@ -6063,7 +6013,7 @@ class CGenerator extends GeneratorBase { sizeof(«portStructType»*) * «width»); ''') - endScopedReactorBlock(code, trigger.parent); + endScopedBlock(code); } } } @@ -6200,7 +6150,7 @@ class CGenerator extends GeneratorBase { val destination = destinationRange.getPort(); if (!declared.contains(destination.parent)) { - // Need to declare the self struct. + // Need to declare the self struct of the destination's parent. declared.add(destination.parent); defineSelfStruct(temp, destination.parent); } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 9b2bf4f8ba..5bf6e25956 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -171,7 +171,16 @@ static public String portRef(PortInstance port, ReactorInstance container) { ); } } - + + /** + * This is a special case of {@link portRef(PortInstance, ReactorInstance) + * where the second argument is the parent of the first. + * @param port An instance of the port to be referenced. + */ + static public String portRef(PortInstance port) { + return portRef(port, port.getParent()); + } + /** * This is a special case of {@link portRef(PortInstance, ReactorInstance) * where it is know that the reference required for the port is to a @@ -212,21 +221,13 @@ static public String reactionRef(ReactionInstance reaction) { } /** - * Return the unique reference for the "self" struct of the specified - * reactor instance. If the instance is a bank of reactors, this returns - * something of the form name_self[bankIndex], where bankIndex is the - * returned by {@link bankIndex(ReactorInstance)}. - * + * Return a name for a pointer to the "self" struct of the specified + * reactor instance. * @param instance The reactor instance. - * @return A reference to the self struct. + * @return A name to use for a pointer to the self struct. */ static public String reactorRef(ReactorInstance instance) { var result = instance.uniqueID() + "_self"; - // If this reactor is a member of a bank of reactors, then change - // the name of its self struct to append [index]. - if (instance.isBank()) { - result += "[" + bankIndex(instance) + "]"; - } return result; } From 6f447a52d1a06599ad348984e8e7439773753dd3 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 7 Dec 2021 08:33:20 -0800 Subject: [PATCH 065/221] Interrim checkin --- .../org/lflang/generator/PortInstance.java | 11 +---- .../org/lflang/generator/c/CGenerator.xtend | 42 ++++++++++++------- .../src/multiport/BankReactionsInContainer.lf | 2 + 3 files changed, 30 insertions(+), 25 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index f02c8a0f87..3660fee214 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -140,16 +140,7 @@ public List eventualDestinations() { } // Construct the full range for this port. - // If this is an output, then the width of the range - // includes any bank the port is within. - Range range; - if (isInput()) { - range = new Range(0, 0, width, false, null); - } else { - // For an output port, the entire bank is treated as a send range - // because every channel can carry different data. - range = new Range(0, 0, width * parent.width(), false, null); - } + Range range = new Range(0, 0, width * parent.width(), false, null); eventualDestinationRanges = eventualDestinations(range); return eventualDestinationRanges; } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 8096de128c..95f2b78f44 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4805,7 +4805,7 @@ class CGenerator extends GeneratorBase { int range_count = 0; int channel = «range.startChannel»; int «bankIndex» = «range.startBank»; - while (range_count++ < «range.totalWidth») { + while (range_count < «range.totalWidth») { ''') indent(builder); defineSelfStruct(builder, reactor); @@ -4851,6 +4851,7 @@ class CGenerator extends GeneratorBase { // Interleaved. Iterate over channels // then bank members that are within the range. pr(builder, ''' + range_count++; «bankIndex»++; if («bankIndex» >= «reactor.width») { channel++; @@ -4861,6 +4862,7 @@ class CGenerator extends GeneratorBase { // Not interleaved. Iterate over the bank members // then channels that are within the range. pr(builder, ''' + range_count++; channel++; if (channel >= «port.width») { «bankIndex»++; @@ -5825,27 +5827,24 @@ class CGenerator extends GeneratorBase { pr(''' // For reference counting, set num_destinations for port «port.parent.name».«port.name». ''') - startScopedBlock(code, port.parent); // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { + startScopedRangeBlock(code, sendingRange); + // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { - val start = sendingRange.startChannel; - val end = sendingRange.startChannel + sendingRange.totalWidth; pr(''' - for (int i = «start»; i < «end»; i++) { - «CUtil.portRefSource(port)»[i]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; - } + «CUtil.portRefSource(port)»[channel]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' «CUtil.portRefSource(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } + endScopedRangeBlock(code, sendingRange); } - endScopedBlock(code); } } } @@ -6093,12 +6092,16 @@ class CGenerator extends GeneratorBase { // Total number of outputs (single ports and multiport channels) // produced by reaction «reaction.index» of «name». «CUtil.reactionRef(reaction)».num_outputs = «outputCount»; - // Allocate memory for triggers[] and triggered_sizes[] on the reaction_t - // struct for this reaction. - «CUtil.reactionRef(reaction)».triggers = (trigger_t***)malloc(«outputCount» * sizeof(trigger_t**)); - «CUtil.reactionRef(reaction)».triggered_sizes = (int*)malloc(«outputCount» * sizeof(int)); - «CUtil.reactionRef(reaction)».output_produced = (bool**)malloc(«outputCount» * sizeof(bool*)); ''') + if (outputCount > 0) { + pr(''' + // Allocate memory for triggers[] and triggered_sizes[] on the reaction_t + // struct for this reaction. + «CUtil.reactionRef(reaction)».triggers = (trigger_t***)malloc(«outputCount» * sizeof(trigger_t**)); + «CUtil.reactionRef(reaction)».triggered_sizes = (int*)malloc(«outputCount» * sizeof(int)); + «CUtil.reactionRef(reaction)».output_produced = (bool**)malloc(«outputCount» * sizeof(bool*)); + ''') + } deferredOptimizeForSingleDominatingReaction(reaction, reaction.index); @@ -6186,12 +6189,21 @@ class CGenerator extends GeneratorBase { destRangeCount++; } } + + var index = "channel"; + if (reaction.parent != port.parent) { + // Reaction is writing to the input port of a contained reactor. + // In this case, the triggered_sizes array size includes the bank + // width, and hence we need to index using range_count rather than + // channel. + index = "range_count"; + } // Record the total size of the array. pr(''' // Reaction «reaction.index» of «name» triggers «destRangeCount» downstream reactions // through port «port.getFullName»[channel]. - «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + channel] = «destRangeCount»; + «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «index»] = «destRangeCount»; ''') // Malloc the memory for the arrays. @@ -6199,7 +6211,7 @@ class CGenerator extends GeneratorBase { // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» trigger_t** trigger_array = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); - «CUtil.reactionRef(reaction)».triggers[«channelCount» + channel] = trigger_array; + «CUtil.reactionRef(reaction)».triggers[«channelCount» + «index»] = trigger_array; // Fill the trigger array. «temp.toString()» ''') diff --git a/test/C/src/multiport/BankReactionsInContainer.lf b/test/C/src/multiport/BankReactionsInContainer.lf index c52a281c40..297824cf8c 100644 --- a/test/C/src/multiport/BankReactionsInContainer.lf +++ b/test/C/src/multiport/BankReactionsInContainer.lf @@ -3,6 +3,7 @@ */ target C { timeout: 1 sec, + logging: DEBUG }; reactor R { output[2] out:int; @@ -30,6 +31,7 @@ main reactor { int count = 0; for (int i = 0; i < s_width; i++) { for (int j = 0; j < s[i].in_width; j++) { + info_print("Sending %d to bank %d channel %d.", count, i, j); SET(s[i].in[j], count++); } } From e087355d04a1432773aafc255e7d0fa55073479b Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 7 Dec 2021 09:11:07 -0800 Subject: [PATCH 066/221] Reaction to contained bank working --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 4 +++- test/C/src/multiport/BankReactionsInContainer.lf | 1 - 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 95f2b78f44..d40478f41c 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6054,6 +6054,8 @@ class CGenerator extends GeneratorBase { // If the port is an input of a contained reactor, then, if that // contained reactor is a bank, we will have to iterate over bank // members. + startScopedBlock(init); + pr(init, "int count = 0;") var bankWidth = 1; if (effect.isInput) { pr(init, "// Reaction writes to an input of a contained reactor.") @@ -6062,7 +6064,6 @@ class CGenerator extends GeneratorBase { } else { startScopedBlock(init); } - pr(init, "int count = 0;") if (effect.isMultiport()) { // Form is slightly different for inputs vs. outputs. @@ -6087,6 +6088,7 @@ class CGenerator extends GeneratorBase { outputCount += bankWidth; } endScopedBlock(init); + endScopedBlock(init); } pr(''' // Total number of outputs (single ports and multiport channels) diff --git a/test/C/src/multiport/BankReactionsInContainer.lf b/test/C/src/multiport/BankReactionsInContainer.lf index 297824cf8c..0d3a18320c 100644 --- a/test/C/src/multiport/BankReactionsInContainer.lf +++ b/test/C/src/multiport/BankReactionsInContainer.lf @@ -3,7 +3,6 @@ */ target C { timeout: 1 sec, - logging: DEBUG }; reactor R { output[2] out:int; From 65c7a1773a0df2ecb94b52aff36af4eda665ec98 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 7 Dec 2021 15:31:31 -0800 Subject: [PATCH 067/221] Getting closer --- .../org/lflang/generator/c/CGenerator.xtend | 69 ++++++------------- .../src/org/lflang/generator/c/CUtil.java | 4 +- .../generator/python/PythonGenerator.xtend | 4 +- .../src/multiport/BankReactionsInContainer.lf | 13 ++-- 4 files changed, 31 insertions(+), 59 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d40478f41c..ea81daedad 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -2137,9 +2137,9 @@ class CGenerator extends GeneratorBase { ''') var reactorIndex = '' if (containedReactor.widthSpec !== null) { - reactorIndex = '[reactorIndex]' + reactorIndex = '[reactor_index]' pr(constructorCode, ''' - for (int reactorIndex = 0; reactorIndex < self->_lf_«containedReactor.name»_width; reactorIndex++) { + for (int reactor_index = 0; reactor_index < self->_lf_«containedReactor.name»_width; reactor_index++) { ''') indent(constructorCode) } @@ -2969,6 +2969,8 @@ class CGenerator extends GeneratorBase { pr(''' int «containedReactor.name»_width = self->_lf_«containedReactor.name»_width; ''') + // Windows does not support variables in arrays declared on the stack, + // so we use the maximum size over all bank members. array = '''[«maxContainedReactorBankWidth(containedReactor, null, 0)»]'''; } pr(''' @@ -3837,12 +3839,19 @@ class CGenerator extends GeneratorBase { */ def initializeOutputMultiports(ReactorInstance reactor) { for (output : reactor.outputs) { + val portRef = CUtil.portRef(output); // If the port is a multiport, create an array. if (output.isMultiport) { + val portStructType = variableStructType(output); pr(initializeTriggerObjects, ''' - «CUtil.portRef(output)»_width = «output.width»; + «portRef»_width = «output.width»; // Allocate memory for multiport output. - «CUtil.portRef(output)» = («variableStructType(output)»*)calloc(«CUtil.portRef(output)»_width, sizeof(«variableStructType(output)»)); + «CUtil.portRef(output)» = («portStructType»*)calloc(«output.width», sizeof(«portStructType»)); + «portRef»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) * «output.width»); + // Assign each output port pointer to be used in reactions to facilitate user access to output ports + for(int i=0; i < «output.width»; i++) { + «portRef»_pointers[i] = &(«portRef»[i]); + } ''') } else { pr(initializeTriggerObjects, ''' @@ -4697,7 +4706,7 @@ class CGenerator extends GeneratorBase { * This must be followed by an {@link endScopedBlock(StringBuilder)}. * @param builder The string builder into which to write. */ - private def void startScopedBlock(StringBuilder builder) { + protected def void startScopedBlock(StringBuilder builder) { startScopedBlock(builder, null, true); } @@ -4713,7 +4722,7 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. * @param reactor The reactor instance. */ - private def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { + protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { startScopedBlock(builder, reactor, true) } @@ -4731,7 +4740,7 @@ class CGenerator extends GeneratorBase { * @param reactor The reactor instance or null for a simple scoped block. * @param declare True to declare a pointer to the self struct */ - private def void startScopedBlock(StringBuilder builder, ReactorInstance reactor, boolean declare) { + protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor, boolean declare) { if (reactor !== null && reactor.isBank) { val index = CUtil.bankIndex(reactor); pr(builder, ''' @@ -4742,14 +4751,14 @@ class CGenerator extends GeneratorBase { pr(builder, "{"); } indent(builder); - if (declare && reactor != null) defineSelfStruct(builder, reactor); + if (declare && reactor !== null) defineSelfStruct(builder, reactor); } /** * End a scoped block. * @param builder The string builder into which to write. */ - private def void endScopedBlock(StringBuilder builder) { + protected def void endScopedBlock(StringBuilder builder) { unindent(builder); pr(builder, "}"); } @@ -5455,7 +5464,7 @@ class CGenerator extends GeneratorBase { */ protected def String getInitializer(ParameterInstance p) { // Handle the bank_index parameter. - if (p.name.equals("bank_instance")) { + if (p.name.equals("bank_index")) { return CUtil.bankIndex(p.parent); } @@ -5544,9 +5553,7 @@ class CGenerator extends GeneratorBase { // For outputs that are not primitive types (of form type* or type[]), // create a default token on the self struct. deferredCreateDefaultTokens(reactor); - - deferredAllocationForEffectsOnOutputs(reactor); - + for (child: reactor.children) { deferredInitialize(child, child.reactions); } @@ -5895,38 +5902,6 @@ class CGenerator extends GeneratorBase { } } - /** - * If any output port of the specified reactor is a multiport, then generate code to - * allocate memory to store the data produced by those reactions. - * The allocated memory is pointed to by a field called `_lf_portname`. - * @param reactor A reactor instance. - */ - private def void deferredAllocationForEffectsOnOutputs(ReactorInstance reactor) { - for (port : reactor.outputs) { - if (port.isMultiport) { - // Port is an effect of a parent's reaction. - // That is, the port belongs to the same reactor as the reaction. - // The reaction is writing to an output of its container reactor. - val portRef = CUtil.portRef(port); - val portStructType = variableStructType(port) - - pr(''' - «portRef»_width = «port.width»; - // Allocate memory to store output of reaction. - «portRef» = («portStructType»*)calloc(«port.width», sizeof(«portStructType»)); - «portRef»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) * «port.width»); - // Assign each output port pointer to be used in reactions to facilitate user access to output ports - for(int i=0; i < «port.width»; i++) { - «portRef»_pointers[i] = &(«portRef»[i]); - } - ''') - // There may be more reactions writing to this port, - // but once we have done the allocation, no need to do anything for those. - return; - } - } - } - /** * Set the last_enabling_reaction field of the reaction struct to point * to the single dominating upstream reaction, if there is one, or to be @@ -6001,9 +5976,7 @@ class CGenerator extends GeneratorBase { ''') startScopedBlock(code, trigger.parent); - // If the width is given as a numeric constant, then add that constant - // to the output count. Otherwise, assume it is a reference to one or more parameters. - val width = trigger.width * trigger.parent.width; + val width = trigger.width; val portStructType = variableStructType(trigger) pr(''' diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 5bf6e25956..90aa111ed4 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -82,7 +82,7 @@ public class CUtil { * @param instance A reactor instance. */ static public String bankIndex(ReactorInstance instance) { - return instance.uniqueID() + "_bank_index"; + return instance.uniqueID() + "_i"; } /** @@ -146,7 +146,7 @@ static public String indexExpression(ReactorInstance instance) { * where bankIndex is the string returned by {@link bankIndex(ReactorInstance)}. * * If the container is port's parent's parent, and the port's parent is a bank, - * then "_lf_parent" will be replaced by "_lf_parent[bankIndex]", where + * then "_lf_parent" will be replaced by "_lf_parent[bank_index]", where * bank_index is the string returned by bankIndex(port.parent). * * @param port The port. diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 9e57a47f51..b70958051e 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1652,7 +1652,7 @@ class PythonGenerator extends CGenerator { return } - startScopedReactorBlock(initializeTriggerObjects, instance); + startScopedBlock(initializeTriggerObjects, instance); // Initialize the name field to the unique name of the instance pr(initializeTriggerObjects, ''' @@ -1680,7 +1680,7 @@ class PythonGenerator extends CGenerator { ''') } } - endScopedReactorBlock(initializeTriggerObjects, instance); + endScopedBlock(initializeTriggerObjects); } diff --git a/test/C/src/multiport/BankReactionsInContainer.lf b/test/C/src/multiport/BankReactionsInContainer.lf index 0d3a18320c..2103f87216 100644 --- a/test/C/src/multiport/BankReactionsInContainer.lf +++ b/test/C/src/multiport/BankReactionsInContainer.lf @@ -4,21 +4,20 @@ target C { timeout: 1 sec, }; -reactor R { +reactor R (bank_index:int(0)) { output[2] out:int; input[2] in:int; reaction(startup) -> out {= - info_print("Sending 42"); - SET(out[0], 42); - SET(out[1], 43); - info_print("Sent"); + for (int i = 0; i < out_width; i++) { + SET(out[0], self->bank_index * i + i); + } =} reaction(in) {= for (int i = 0; i < in_width; i++) { if (in[i]->is_present) { - info_print("Received %d on channel %d", in[i]->value, i); + info_print("Inner received %d in bank %d, channel %d", in[i]->value, self->bank_index, i); } } =} @@ -38,7 +37,7 @@ main reactor { reaction(s.out) {= for (int j = 0; j < s_width; j++) { for (int i = 0; i < s[j].out_width; i++) { - info_print("Outer received %d on channel %d from bank member %d.", s[j].out[i]->value, i, j); + info_print("Outer received %d on channel %d, bank member %d.", s[j].out[i]->value, i, j); } } =} From 90a083ff51f74175185576aafa5beccef51a5820 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 7 Dec 2021 18:06:13 -0800 Subject: [PATCH 068/221] Finally got reactions sending and receiving from contained banks. --- .../org/lflang/generator/c/CGenerator.xtend | 23 +++++++++---- .../src/multiport/BankReactionsInContainer.lf | 32 +++++++++++++++++-- 2 files changed, 46 insertions(+), 9 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index ea81daedad..c58cd19764 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -701,7 +701,7 @@ class CGenerator extends GeneratorBase { // If there are startup reactions, store them in an array. if (startupReactionCount > 0) { pr(''' - // Array of pointers to timer triggers to be scheduled in _lf_trigger_startup_reactions(). + // Array of pointers to reactions to be scheduled in _lf_trigger_startup_reactions(). reaction_t* _lf_startup_reactions[«startupReactionCount»]; ''') } else { @@ -2640,30 +2640,39 @@ class CGenerator extends GeneratorBase { // For each reaction instance, allocate the arrays that will be used to // trigger downstream reactions. for (reaction : reactions) { - val instance = reaction.parent; - val nameOfSelfStruct = CUtil.reactorRef(instance) + val reactor = reaction.parent; + + startScopedBlock(initializeTriggerObjects); + pr(initializeTriggerObjects, "int count = 0;"); + startScopedBlock(initializeTriggerObjects, reactor); + val reactionRef = CUtil.reactionRef(reaction) // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. for (trigger : reaction.triggers) { if (trigger.isStartup) { pr(initializeTriggerObjects, ''' - _lf_startup_reactions[«startupReactionCount++»] = &«nameOfSelfStruct»->_lf__reaction_«reaction.index»; + _lf_startup_reactions[«startupReactionCount» + count++] = &«reactionRef»; ''') + startupReactionCount += reactor.width; } else if (trigger.isShutdown) { pr(initializeTriggerObjects, ''' - _lf_shutdown_reactions[«shutdownReactionCount++»] = &«nameOfSelfStruct»->_lf__reaction_«reaction.index»; + _lf_shutdown_reactions[«shutdownReactionCount» + count++] = &«reactionRef»; ''') + shutdownReactionCount += reactor.width; if (targetConfig.tracing !== null) { - val description = getShortenedName(instance) + val description = getShortenedName(reactor) + val reactorRef = CUtil.reactorRef(reactor) pr(initializeTriggerObjects, ''' - _lf_register_trace_event(«nameOfSelfStruct», &(«nameOfSelfStruct»->_lf__shutdown), + _lf_register_trace_event(«reactorRef», &(«reactorRef»->_lf__shutdown), trace_trigger, "«description».shutdown"); ''') } } } + endScopedBlock(initializeTriggerObjects); + endScopedBlock(initializeTriggerObjects); } } diff --git a/test/C/src/multiport/BankReactionsInContainer.lf b/test/C/src/multiport/BankReactionsInContainer.lf index 2103f87216..3a5886196e 100644 --- a/test/C/src/multiport/BankReactionsInContainer.lf +++ b/test/C/src/multiport/BankReactionsInContainer.lf @@ -7,10 +7,15 @@ target C { reactor R (bank_index:int(0)) { output[2] out:int; input[2] in:int; + state received:bool(false); reaction(startup) -> out {= for (int i = 0; i < out_width; i++) { - SET(out[0], self->bank_index * i + i); + int value = self->bank_index * 2 + i; + SET(out[i], value); + info_print("Inner sending %d to bank %d channel %d.", + value, self->bank_index, i + ); } =} @@ -18,12 +23,23 @@ reactor R (bank_index:int(0)) { for (int i = 0; i < in_width; i++) { if (in[i]->is_present) { info_print("Inner received %d in bank %d, channel %d", in[i]->value, self->bank_index, i); + self->received = true; + if (in[i]->value != self->bank_index * 2 + i) { + error_print_and_exit("Expected %d.", self->bank_index * 2 + i); + } } } =} + reaction(shutdown) {= + info_print("Inner shutdown invoked."); + if (!self->received) { + error_print_and_exit("Received no input."); + } + =} } main reactor { s = new[2] R(); + state received:bool(false); reaction(startup) -> s.in {= int count = 0; @@ -37,8 +53,20 @@ main reactor { reaction(s.out) {= for (int j = 0; j < s_width; j++) { for (int i = 0; i < s[j].out_width; i++) { - info_print("Outer received %d on channel %d, bank member %d.", s[j].out[i]->value, i, j); + if (s[j].out[i]->is_present) { + info_print("Outer received %d on bank %d channel %d.", s[j].out[i]->value, j, i); + self->received = true; + if (s[j].out[i]->value != j * 2 + i) { + error_print_and_exit("Expected %d.", j * 2 + i); + } + } } } =} + reaction(shutdown) {= + info_print("Outer shutdown invoked."); + if (!self->received) { + error_print_and_exit("Received no input."); + } + =} } From 53eb7f95dd6eafc1c3fad7b8ba62740b81c47806 Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 10 Dec 2021 16:18:02 -0800 Subject: [PATCH 069/221] Interrim checkin --- .../org/lflang/generator/TriggerInstance.java | 30 +- .../org/lflang/generator/c/CGenerator.xtend | 771 +++++++++++------- .../src/org/lflang/generator/c/CUtil.java | 143 ++-- 3 files changed, 595 insertions(+), 349 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/TriggerInstance.java b/org.lflang/src/org/lflang/generator/TriggerInstance.java index a9e7b9039b..b17f15f710 100644 --- a/org.lflang/src/org/lflang/generator/TriggerInstance.java +++ b/org.lflang/src/org/lflang/generator/TriggerInstance.java @@ -88,12 +88,23 @@ public BuiltinTrigger getBuiltinTriggerType() { return builtinTriggerType; } - /** Return the reaction instances that are triggered by this trigger. */ + /** + * Return the reaction instances that are triggered by this trigger. + * If this port is an output, then the reaction instances + * belong to the parent of the port's parent. If the port + * is an input, then the reaction instances belong to the + * port's parent. + */ public Set getDependentReactions() { return dependentReactions; } - /** Reaction instances that may send outputs via this port. */ + /** + * Return the reaction instances that may send data via this port. + * If this port is an input, then the reaction instance + * belongs to parent of the port's parent. If it is an output, + * the the reaction instance belongs to the port's parent. + */ public Set getDependsOnReactions() { return dependsOnReactions; }; @@ -133,10 +144,21 @@ public boolean isBuiltinTrigger() { BuiltinTrigger builtinTriggerType = null; - /** Reaction instances that are triggered by this trigger. */ + /** + * Reaction instances that are triggered by this trigger. + * If this port is an output, then the reaction instances + * belong to the parent of the port's parent. If the port + * is an input, then the reaction instances belong to the + * port's parent. + */ Set dependentReactions = new LinkedHashSet(); - /** Reaction instances that may send outputs via this port. */ + /** + * Reaction instances that may send data via this port. + * If this port is an input, then the reaction instance + * belongs to parent of the port's parent. If it is an output, + * the the reaction instance belongs to the port's parent. + */ Set dependsOnReactions = new LinkedHashSet(); ///////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index c58cd19764..c16d754ebf 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -2642,37 +2642,43 @@ class CGenerator extends GeneratorBase { for (reaction : reactions) { val reactor = reaction.parent; - startScopedBlock(initializeTriggerObjects); - pr(initializeTriggerObjects, "int count = 0;"); - startScopedBlock(initializeTriggerObjects, reactor); + val temp = new StringBuilder(); + var foundOne = false; + + startScopedBlock(temp); + pr(temp, "int count = 0;"); + startScopedBlock(temp, reactor); val reactionRef = CUtil.reactionRef(reaction) // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. for (trigger : reaction.triggers) { if (trigger.isStartup) { - pr(initializeTriggerObjects, ''' + pr(temp, ''' _lf_startup_reactions[«startupReactionCount» + count++] = &«reactionRef»; ''') startupReactionCount += reactor.width; + foundOne = true; } else if (trigger.isShutdown) { - pr(initializeTriggerObjects, ''' + pr(temp, ''' _lf_shutdown_reactions[«shutdownReactionCount» + count++] = &«reactionRef»; ''') + foundOne = true; shutdownReactionCount += reactor.width; if (targetConfig.tracing !== null) { val description = getShortenedName(reactor) val reactorRef = CUtil.reactorRef(reactor) - pr(initializeTriggerObjects, ''' + pr(temp, ''' _lf_register_trace_event(«reactorRef», &(«reactorRef»->_lf__shutdown), trace_trigger, "«description».shutdown"); ''') } } } - endScopedBlock(initializeTriggerObjects); - endScopedBlock(initializeTriggerObjects); + endScopedBlock(temp); + endScopedBlock(temp); + if (foundOne) pr(initializeTriggerObjects, temp.toString); } } @@ -3088,20 +3094,20 @@ class CGenerator extends GeneratorBase { // Avoid generating code if not needed. var foundOne = false; val temp = new StringBuilder(); - var nameOfSelfStruct = CUtil.reactorRef(child) startScopedBlock(temp, child); for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { foundOne = true; + val portRef = CUtil.portRefName(input); if (input.isMultiport()) { pr(temp, ''' for (int i = 0; i < «input.width»; i++) { _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token - = &«nameOfSelfStruct»->_lf_«input.name»[i]->token; + = &«portRef»[i]->token; _lf_tokens_with_ref_count[«startTimeStepTokens» + i].status - = (port_status_t*)&«nameOfSelfStruct»->_lf_«input.name»[i].is_present; + = (port_status_t*)&«portRef»[i].is_present; _lf_tokens_with_ref_count[«startTimeStepTokens» + i].reset_is_present = false; } ''') @@ -3109,9 +3115,9 @@ class CGenerator extends GeneratorBase { } else { pr(temp, ''' _lf_tokens_with_ref_count[«startTimeStepTokens»].token - = &«nameOfSelfStruct»->_lf_«input.name»->token; + = &«portRef»->token; _lf_tokens_with_ref_count[«startTimeStepTokens»].status - = (port_status_t*)&«nameOfSelfStruct»->_lf_«input.name».is_present; + = (port_status_t*)&«portRef».is_present; _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = false; ''') startTimeStepTokens++ @@ -3127,11 +3133,9 @@ class CGenerator extends GeneratorBase { } // Avoid generating dead code if nothing is relevant. var foundOne = false; - val temp = new StringBuilder(); + var temp = new StringBuilder(); var containerSelfStructName = CUtil.reactorRef(instance) - startScopedBlock(temp, instance); - // Handle inputs that get sent data from a reaction rather than from // another contained reactor and reactions that are triggered by an // output of a contained reactor. @@ -3146,160 +3150,138 @@ class CGenerator extends GeneratorBase { // This reaction is sending to an input. Must be // the input of a contained reactor in the federate. if (currentFederate.contains(port.parent)) { + foundOne = true; + + pr(temp, ''' + // Add port «port.getFullName» to array of is_present fields. + ''') - startScopedBlock(temp, port.parent); + if (port.parent != instance) { + // The port belongs to contained reactor, so we also have + // iterate over the instance bank members. + startScopedBlock(temp, instance); + } + startScopedBankChannelIteration(temp, port, "count"); + val portRef = CUtil.portRefNested(port); - // If this is a multiport, then the port struct on the self - // struct is a pointer. Otherwise, it is the struct itself. - if (port.isMultiport) { - foundOne = true; - - pr(temp, ''' - // Add port «port.getFullName» to array of is_present fields. - for (int i = 0; i < «port.width»; i++) { - _lf_is_present_fields[«startTimeStepIsPresentCount» + i] - = &«CUtil.portRefSource(port)»[i]->is_present; - } - ''') - if (isFederatedAndDecentralized) { - // Intended_tag is only applicable to ports in federated execution. - pr(temp, ''' - // Add port «port.getFullName» to array of is_present fields. - for (int i = 0; i < «port.width»; i++) { - _lf_intended_tag_fields[«startTimeStepIsPresentCount» + i] - = &«CUtil.portRefSource(port)»[i]->intended_tag; - } - ''') - } - startTimeStepIsPresentCount += port.width; - } else { - foundOne = true; + pr(temp, ''' + _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«portRef»->is_present; + ''') + if (isFederatedAndDecentralized) { + // Intended_tag is only applicable to ports in federated execution. pr(temp, ''' - // Add port «port.getFullName» to array of is_present fields. - _lf_is_present_fields[«startTimeStepIsPresentCount»] - = &«CUtil.portRefSource(port)».is_present; + _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«portRef»->intended_tag; ''') - if (isFederatedAndDecentralized) { - // Intended_tag is only applicable to ports in federated execution. - pr(temp, ''' - // Add port «port.getFullName» to array of is_present fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount»] - = &«CUtil.portRefSource(port)».intended_tag; - ''') - } - startTimeStepIsPresentCount++ } - endScopedBlock(temp); - } + startTimeStepIsPresentCount += port.width * port.parent.width; + + endScopedBankChannelIteration(temp, port, "count"); + if (port.parent != instance) endScopedBlock(temp); + } } } // Find outputs of contained reactors that have token types and therefore // need to have their reference counts decremented. - for (port : reaction.sources) { - if (port.definition instanceof Output && !portsSeen.contains(port)) { - val output = port as PortInstance; - portsSeen.add(output) + for (port : reaction.sources.filter(PortInstance)) { + if (port.isOutput && !portsSeen.contains(port)) { + portsSeen.add(port) // This reaction is receiving data from the port. - if (isTokenType((output.definition as Output).inferredType)) { + if (isTokenType((port.definition as Output).inferredType)) { foundOne = true; - if (output.isMultiport()) { - pr(temp, ''' - for (int i = 0; i < «output.width»; i++) { - _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token - = &«containerSelfStructName»->_lf_«output.parent.name».«output.name»[i]->token; - _lf_tokens_with_ref_count[«startTimeStepTokens» + i].status - = (port_status_t*)&«containerSelfStructName»->_lf_«output.parent.name».«output.name»[i]->is_present; - _lf_tokens_with_ref_count[«startTimeStepTokens» + i].reset_is_present = false; - } - ''') - startTimeStepTokens += output.width - } else { - pr(temp, ''' - _lf_tokens_with_ref_count[«startTimeStepTokens»].token - = &«containerSelfStructName»->_lf_«output.parent.name».«output.name»->token; - _lf_tokens_with_ref_count[«startTimeStepTokens»].status - = (port_status_t*)&«containerSelfStructName»->_lf_«output.parent.name».«output.name»->is_present; - _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = false; - ''') - startTimeStepTokens++ - } + + pr(temp, ''' + // Add port «port.getFullName» to array _lf_tokens_with_ref_count. + ''') + + // Potentially have to iterate over bank members of the instance + // (parent of the reaction), bank members of the contained reactor (if a bank), + // and channels of the multiport (if multiport). + startScopedBlock(temp, instance); + startScopedBankChannelIteration(temp, port, "count"); + + val portRef = CUtil.portRef(port, true, true); + + pr(temp, ''' + _lf_tokens_with_ref_count[«startTimeStepTokens» + count].token = &«portRef»->token; + _lf_tokens_with_ref_count[«startTimeStepTokens» + count].status = (port_status_t*)&«portRef»->is_present; + _lf_tokens_with_ref_count[«startTimeStepTokens» + count].reset_is_present = false; + ''') + startTimeStepTokens += port.width * port.parent.width; + + endScopedBankChannelIteration(temp, port, "count"); + endScopedBlock(temp); } } } } } + if (foundOne) pr(startTimeStep, temp.toString()); + temp = new StringBuilder(); + foundOne = false; + for (action : instance.actions) { if (currentFederate === null || currentFederate.contains(action.definition)) { - pr(startTimeStep, ''' + foundOne = true; + startScopedBlock(temp, instance); + + pr(temp, ''' // Add action «action.getFullName» to array of is_present fields. _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«containerSelfStructName»->_lf_«action.name».is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to actions in federated execution with decentralized coordination. - pr(startTimeStep, ''' + pr(temp, ''' // Add action «action.getFullName» to array of intended_tag fields. _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«containerSelfStructName»->_lf_«action.name».intended_tag; ''') } startTimeStepIsPresentCount++ + endScopedBlock(temp); } } - - endScopedBlock(temp); - if (foundOne) pr(startTimeStep, temp.toString()); + temp = new StringBuilder(); + foundOne = false; // Next, set up the table to mark each output of each contained reactor absent. for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { - startScopedBlock(startTimeStep, child); + startScopedBlock(temp, child); for (output : child.outputs) { - if (output.isMultiport()) { - pr(startTimeStep, ''' - // Add port «output.getFullName» to array of is_present fields. - { // Scope to avoid collisions with variable names. - int i = «startTimeStepIsPresentCount»; - for (int j = 0; j < «output.width»; j++) { - _lf_is_present_fields[i++] = &«CUtil.portRefSource(output)»[j].is_present; - } - } - ''') - if (isFederatedAndDecentralized) { - // Intended_tag is only applicable to ports in federated execution with decentralized coordination. - pr(startTimeStep, ''' - // Add port «output.getFullName» to array of intended_tag fields. - { // Scope to avoid collisions with variable names. - int i = «startTimeStepIsPresentCount»; - for (int j = 0; j < «output.width»; j++) { - _lf_intended_tag_fields[i++] = &«CUtil.portRefSource(output)»[j].intended_tag; - } - } - ''') - } - startTimeStepIsPresentCount += output.width; - } else { - // Output is not a multiport. - pr(startTimeStep, ''' - // Add port «output.getFullName» to array of is_present fields. - _lf_is_present_fields[«startTimeStepIsPresentCount»] = &«CUtil.portRefSource(output)».is_present; + foundOne = true; + + pr(temp, ''' + // Add port «output.getFullName» to array of is_present fields. + ''') + startScopedBlock(temp); + pr(temp, '''int count = 0;'''); + startChannelIteration(temp, output); + + pr(temp, ''' + _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)».is_present; + ''') + + if (isFederatedAndDecentralized) { + // Intended_tag is only applicable to ports in federated execution with decentralized coordination. + pr(temp, ''' + // Add port «output.getFullName» to array of intended_tag fields. + _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)»[j].intended_tag; ''') - if (isFederatedAndDecentralized) { - // Intended_tag is only applicable to ports in federated execution with decentralized coordination. - pr(startTimeStep, ''' - // Add port «output.getFullName» to array of Intended_tag fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount»] = &«CUtil.portRefSource(output)».intended_tag; - ''') - } - startTimeStepIsPresentCount++ } + startTimeStepIsPresentCount += output.width; + + pr(temp, '''count++;'''); + endChannelIteration(temp, output); + endScopedBlock(temp); } - endScopedBlock(startTimeStep); + endScopedBlock(temp); } } + if (foundOne) pr(startTimeStep, temp.toString()); } /** @@ -3309,12 +3291,15 @@ class CGenerator extends GeneratorBase { * @param actions The actions. */ private def generateActionInitializations(Iterable actions) { + var foundOne = null as ActionInstance; + val temp = new StringBuilder(); for (action : actions) { if (!action.isShutdown) { + foundOne = action; val triggerStructName = CUtil.reactorRef(action.parent) + "->_lf__" + action.name; var minDelay = action.minDelay var minSpacing = action.minSpacing - pr(initializeTriggerObjects, ''' + pr(temp, ''' «triggerStructName».offset = «CUtil.VG.getTargetTime(minDelay)»; «IF minSpacing !== null» «triggerStructName».period = «CUtil.VG.getTargetTime(minSpacing)»; @@ -3323,7 +3308,12 @@ class CGenerator extends GeneratorBase { «ENDIF» ''') } - triggerCount++ + triggerCount += action.parent.width; + } + if (foundOne !== null) { + startScopedBlock(initializeTriggerObjects, foundOne.parent); + pr(initializeTriggerObjects, temp.toString); + endScopedBlock(initializeTriggerObjects); } } @@ -3337,19 +3327,31 @@ class CGenerator extends GeneratorBase { * @param timers The timers. */ private def generateTimerInitializations(Iterable timers) { + var foundOne = null as TimerInstance; + val temp = new StringBuilder(); for (timer : timers) { if (!timer.isStartup) { + foundOne = timer; val triggerStructName = CUtil.reactorRef(timer.parent) + "->_lf__" + timer.name; val offset = CUtil.VG.getTargetTime(timer.offset) val period = CUtil.VG.getTargetTime(timer.period) - pr(initializeTriggerObjects, ''' + pr(temp, ''' «triggerStructName».offset = «offset»; «triggerStructName».period = «period»; - _lf_timer_triggers[«timerCount»] = &«triggerStructName»; + _lf_timer_triggers[«timerCount» + count] = &«triggerStructName»; ''') - timerCount++ + timerCount += timer.parent.width; } - triggerCount++ + triggerCount += timer.parent.width; + } + if (foundOne !== null) { + startScopedBlock(initializeTriggerObjects); + pr(initializeTriggerObjects, "int count = 0;"); + startScopedBlock(initializeTriggerObjects, foundOne.parent); + pr(initializeTriggerObjects, temp.toString); + pr(initializeTriggerObjects, "count++;"); + endScopedBlock(initializeTriggerObjects); + endScopedBlock(initializeTriggerObjects); } } @@ -3848,24 +3850,24 @@ class CGenerator extends GeneratorBase { */ def initializeOutputMultiports(ReactorInstance reactor) { for (output : reactor.outputs) { - val portRef = CUtil.portRef(output); + val portRefName = CUtil.portRefName(output); // If the port is a multiport, create an array. if (output.isMultiport) { val portStructType = variableStructType(output); pr(initializeTriggerObjects, ''' - «portRef»_width = «output.width»; + «portRefName»_width = «output.width»; // Allocate memory for multiport output. - «CUtil.portRef(output)» = («portStructType»*)calloc(«output.width», sizeof(«portStructType»)); - «portRef»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) * «output.width»); + «portRefName» = («portStructType»*)calloc(«output.width», sizeof(«portStructType»)); + «portRefName»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) * «output.width»); // Assign each output port pointer to be used in reactions to facilitate user access to output ports for(int i=0; i < «output.width»; i++) { - «portRef»_pointers[i] = &(«portRef»[i]); + «portRefName»_pointers[i] = &(«portRefName»[i]); } ''') } else { pr(initializeTriggerObjects, ''' // width of -2 indicates that it is not a multiport. - «CUtil.portRefSource(output)»_width = -2; + «CUtil.portRefName(output)»_width = -2; ''') } } @@ -3876,26 +3878,33 @@ class CGenerator extends GeneratorBase { * @param reactor The reactor. */ def initializeInputs(ReactorInstance reactor) { + if (!reactor.inputs.isEmpty()) { + startScopedBlock(initializeTriggerObjects, reactor); + } for (input : reactor.inputs) { val portRef = CUtil.portRef(input) + val portRefName = CUtil.portRefName(input) // If the port is a multiport, create an array. if (input.isMultiport) { pr(initializeTriggerObjects, ''' - «portRef»_width = «input.width»; + «portRefName»_width = «input.width»; // Allocate memory for multiport inputs. «portRef» = («variableStructType(input)»**)malloc(sizeof(«variableStructType(input)»*) * «input.width»); // Set inputs by default to an always absent default input. for (int i = 0; i < «input.width»; i++) { - «portRef»[i] = &«CUtil.reactorRef(reactor)»->_lf_default__«input.name»; + «portRefName»[i] = &«CUtil.reactorRef(reactor)»->_lf_default__«input.name»; } ''') } else { pr(initializeTriggerObjects, ''' // width of -2 indicates that it is not a multiport. - «portRef»_width = -2; + «portRefName»_width = -2; ''') } } + if (!reactor.inputs.isEmpty()) { + endScopedBlock(initializeTriggerObjects); + } } /** @@ -4709,6 +4718,41 @@ class CGenerator extends GeneratorBase { return result; } + /** + * If the specified port is a multiport, then start a specified iteration + * over the channels of the multiport using as the channel index the + * variable name returned by {@link CUtil.channelIndex(PortInstance)}. + * If the port is not a multiport, do nothing. + * This is required to be followed by {@link endChannelIteration(StringBuilder, PortInstance}. + * @param builder Where to write the code. + * @param port The port. + */ + protected def void startChannelIteration(StringBuilder builder, PortInstance port) { + if (port.isMultiport) { + val channel = CUtil.channelIndex(port); + pr(builder, ''' + for (int «channel» = 0; «channel» < «port.width»; «channel»++) { + ''') + indent(builder); + } + } + + /** + * If the specified port is a multiport, then start a specified iteration + * over the channels of the multiport using as the channel index the + * variable name returned by {@link CUtil.channelIndex(PortInstance)}. + * If the port is not a multiport, do nothing. + * This is required to be followed by {@link endChannelIteration(StringBuilder, PortInstance}. + * @param builder Where to write the code. + * @param port The port. + */ + protected def void endChannelIteration(StringBuilder builder, PortInstance port) { + if (port.isMultiport) { + unindent(builder); + pr(builder, "}"); + } + } + /** * Start a scoped block, which is a section of code * surrounded by curley braces and indented. @@ -4716,18 +4760,21 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. */ protected def void startScopedBlock(StringBuilder builder) { - startScopedBlock(builder, null, true); + startScopedBlock(builder, null, false); } /** * Start a scoped block for the specified reactor. * If the reactor is a bank, then this starts a for loop * that iterates over the bank members using a standard index - * variable. If the reactor is null or is not a bank, then this simply + * variable whose name is that returned by {@link CUtil.bankIndex(ReactorInstance)}. + * If the reactor is null or is not a bank, then this simply * starts a scoped block by printing an opening curly brace. * This also adds a declaration of a pointer to the self * struct of the reactor or bank member. + * * This must be followed by an {@link endScopedBlock(StringBuilder)}. + * * @param builder The string builder into which to write. * @param reactor The reactor instance. */ @@ -4739,12 +4786,15 @@ class CGenerator extends GeneratorBase { * Start a scoped block for the specified reactor. * If the reactor is a bank, then this starts a for loop * that iterates over the bank members using a standard index - * variable. If the reactor is null or is not a bank, then this simply + * variable whose name is that returned by {@link CUtil.bankIndex(ReactorInstance)}. + * If the reactor is null or is not a bank, then this simply * starts a scoped block by printing an opening curly brace. - * If the declare arguement is true, then this also + * If the declare argument is true, then this also * adds a declaration of a pointer to the self * struct of the reactor or bank member. + * * This must be followed by an {@link endScopedBlock(StringBuilder)}. + * * @param builder The string builder into which to write. * @param reactor The reactor instance or null for a simple scoped block. * @param declare True to declare a pointer to the self struct @@ -4772,6 +4822,53 @@ class CGenerator extends GeneratorBase { pr(builder, "}"); } + /** + * Start a scoped block to iterate over bank members and + * channels for the specified port with a a variable with + * the name given by count counting the iterations. + * If this port is a multiport, then the channel index + * variable name is that returned by {@link CUtil.channelIndex(PortInstance)}. + * + * This is required to be followed by a call to + * {@link endScopedBankChannelIteration(StringBuilder, PortInstance, String)}. + * @param builder Where to write the code. + * @param port The port. + * @param count The variable name to use for the counter, or + * null to not provide a counter. + */ + protected def void startScopedBankChannelIteration( + StringBuilder builder, PortInstance port, String count + ) { + if (count !== null) { + startScopedBlock(builder); + pr(builder, '''int «count» = 0;'''); + } + startScopedBlock(builder, port.parent); + startChannelIteration(builder, port); + } + + /** + * End a scoped block to iterate over bank members and + * channels for the specified port with a a variable with + * the name given by count counting the iterations. + * @param builder Where to write the code. + * @param port The port. + * @param count The variable name to use for the counter, or + * null to not provide a counter. + */ + protected def void endScopedBankChannelIteration( + StringBuilder builder, PortInstance port, String count + ) { + if (count !== null) { + pr(builder, count + "++;"); + } + endChannelIteration(builder, port); + endScopedBlock(builder); + if (count !== null) { + endScopedBlock(builder); + } + } + /** * Start a scoped block for the specified range. * @@ -4781,14 +4878,15 @@ class CGenerator extends GeneratorBase { * members and channels, where the order depends on whether the * range is interleaved. * If the range is interleaved, then it iterates first over - * channels (with index "channel") and then over banks (with + * channels (with index given by {@link CUtil.channelIndex(PortInstance)}) + * and then over banks (with * index given by {@link CUtil.bankIndex(ReactorInstance)}. * Otherwise, it iterates first over banks and then over * channels. * * If the port is a multiport but its container is not a bank, * then the generated iteration is only over channels (with - * index variable named "channel"). + * index variable named given by {@link CUtil.channelIndex(PortInstance)}). * * If the port is not a multiport nor is its parent a bank, * then this just generates a scoped section that defines @@ -4803,7 +4901,9 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. * @param range The send range. */ - protected def void startScopedRangeBlock(StringBuilder builder, PortInstance.Range range) { + protected def void startScopedRangeBlock( + StringBuilder builder, PortInstance.Range range + ) { val port = range.port; val reactor = port.parent; @@ -4815,13 +4915,15 @@ class CGenerator extends GeneratorBase { // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. startScopedBlock(builder); + + val channelVariable = CUtil.channelIndex(port); if (reactor.isBank) { val bankIndex = CUtil.bankIndex(reactor); if (port.isMultiport) { pr(builder, ''' int range_count = 0; - int channel = «range.startChannel»; + int «channelVariable» = «range.startChannel»; int «bankIndex» = «range.startBank»; while (range_count < «range.totalWidth») { ''') @@ -4831,18 +4933,20 @@ class CGenerator extends GeneratorBase { // Bank, but not a multiport. pr(builder, ''' // Send range covers bank member(s). Iterate over bank members. + int range_count = 0; for (int «bankIndex» = «range.startBank»; «bankIndex» < «range.startBank» + «range.totalWidth»; «bankIndex»++) { ''') indent(builder); - pr(builder, "int channel = 0;"); + pr(builder, '''int «channelVariable» = 0;'''); defineSelfStruct(builder, reactor); } - } else if (range.port.isMultiport) { + } else if (port.isMultiport) { // Reactor is not a bank, but port is a multiport. defineSelfStruct(builder, reactor); pr(builder, ''' // Send range covers channels of a multiport. Iterate over channels. - for (int channel = «range.startChannel»; channel < «range.startChannel» + «range.totalWidth»; channel++) { + int range_count = 0; + for (int «channelVariable» = «range.startChannel»; «channelVariable» < «range.startChannel» + «range.totalWidth»; «channelVariable»++) { ''') indent(builder); } else { @@ -4850,19 +4954,25 @@ class CGenerator extends GeneratorBase { // For consistent depth of nesting, generate a scoped block. pr(builder, "{"); indent(builder); - pr(builder, "int channel = 0;"); + // Include range_count variable so generated code can safely use it. + pr(builder, ''' + int range_count = 0; + '''); defineSelfStruct(builder, reactor); } } - + /** - * End a scoped block for the specified send range. + * End a scoped block for the specified range. * @param builder The string builder into which to write. * @param range The send range. */ - protected def void endScopedRangeBlock(StringBuilder builder, PortInstance.Range range) { + protected def void endScopedRangeBlock( + StringBuilder builder, PortInstance.Range range + ) { val port = range.port; val reactor = port.parent; + val channelVariable = CUtil.channelIndex(port); if (reactor.isBank && range.port.isMultiport) { val bankIndex = CUtil.bankIndex(reactor); if (range.interleaved) { @@ -4872,7 +4982,7 @@ class CGenerator extends GeneratorBase { range_count++; «bankIndex»++; if («bankIndex» >= «reactor.width») { - channel++; + «channelVariable»++; «bankIndex» = 0; } ''') @@ -4881,18 +4991,140 @@ class CGenerator extends GeneratorBase { // then channels that are within the range. pr(builder, ''' range_count++; - channel++; - if (channel >= «port.width») { + «channelVariable»++; + if («channelVariable» >= «port.width») { «bankIndex»++; - channel = 0; + «channelVariable» = 0; } ''') } + } else if (reactor.isBank || range.port.isMultiport) { + pr(builder, ''' + range_count++; + ''') } endScopedBlock(builder); endScopedBlock(builder); } + /** + * Start a scoped block for the specified pair of ranges. This is like + * {@link startScopedRangeBlock(StringBuilder, PortInstance.Range), + * except that it also defines bank and channel indices for the second + * range and iterates over the minimum width of the two ranges. + * This must be followed by a call to + * {@link endScopedRangeBlock(StringBuilder, PortInstance.Range, PortInstance.Range)}. + * + * To allow for the possibility that the srcRange and dstRange + * refer to ports with the same parent, the variables used to + * refer to bank indices have to be different. To get the pointer + * to the self struct for the srcRange port, use + * {@link CUtil.portRef(PortInstance)}, and + * to get a pointer to the self struct for the destination, + * use {@link CUtil.portRefDestination(PortInstance)}. + * + * @param builder The string builder into which to write. + * @param srcRange The source range. + * @param dstRange The destination range. + */ + protected def void startScopedRangeBlock( + StringBuilder builder, PortInstance.Range srcRange, PortInstance.Range dstRange + ) { + val dst = dstRange.port; + val src = srcRange.port; + val width = (srcRange.totalWidth <= dstRange.totalWidth)? srcRange.totalWidth : dstRange.totalWidth; + + pr(builder, ''' + // Iterate over range1 of «srcRange.port.fullName» with starting channel «srcRange.startChannel» + // and starting bank «srcRange.startBank», and range2 of «dstRange.port.fullName» with starting + // channel «dstRange.startChannel» and starting bank «dstRange.startBank», with total width «width». + ''') + + // Define additional variables for range2. + startScopedBlock(builder); + + // FIXME FIXME: This will fail if the destination is deeply nested within a bank + // or if it is nested within multiple banks. + // Need to change SendRange so that startBank is an array of length equal to + // the depth of the port's container. + + if (dst.parent.isBank && dst.isInput) { + pr(builder, ''' + int «CUtil.bankIndexDst(dst.parent)» = «dstRange.startBank»; + ''') + } + if (dst.isMultiport) { + pr(builder, ''' + int «CUtil.channelIndex(dst)» = «dstRange.startChannel»; + ''') + } + startScopedRangeBlock(builder, srcRange); + if (dst.parent != src.parent) { + defineSelfStruct(builder, dst.parent); + } + } + + /** + * End a scoped block for the specified send range. + * @param builder The string builder into which to write. + * @param srcRange The source range. + * @param dstRange The destination range. + */ + protected def void endScopedRangeBlock( + StringBuilder builder, PortInstance.Range srcRange, PortInstance.Range dstRange + ) { + val src = srcRange.port; + val dst = dstRange.port; + // If an output port is triggering a reaction in its parent's parent, + // then the destination and source may be the same port. In that case, + // we want to avoid double incrementing the bank and channel variables. + if (dst != src) { + // Sadly, xtend doesn't seem to support XOR operator in Java. + // val interleaved = srcRange.interleaved ^ dstRange.interleaved; + val interleaved = (srcRange.interleaved || dstRange.interleaved) + && !(srcRange.interleaved && dstRange.interleaved); + if (interleaved) { + if (dst.parent.isBank && dst.isInput) { + pr(builder, ''' + «CUtil.bankIndexDst(dst.parent)»++; + ''') + if (dst.isMultiport) { + pr(builder, ''' + if («CUtil.bankIndexDst(dst.parent)» >= «dst.parent.width») { + «CUtil.bankIndexDst(dst.parent)» = 0; + «CUtil.channelIndex(dst)»++; + } + ''') + } + } else if (dst.isMultiport) { + pr(builder, ''' + «CUtil.channelIndex(dst)»++; + ''') + } + } else { + if (dst.isMultiport) { + pr(builder, ''' + «CUtil.channelIndex(dst)»++; + ''') + if (dst.parent.isBank && dst.isInput) { + pr(builder, ''' + if («CUtil.channelIndex(dst)» >= «dst.width») { + «CUtil.channelIndex(dst)» = 0; + «CUtil.bankIndexDst(dst.parent)»++; + } + ''') + } + } else if (dst.parent.isBank && dst.isInput) { + pr(builder, ''' + «CUtil.bankIndexDst(dst.parent)»++; + ''') + } + } + } + endScopedRangeBlock(builder, srcRange); + endScopedBlock(builder); + } + /** * For the specified reactor, print code to the specified builder * that defines a pointer to the self struct of the specified @@ -4917,72 +5149,40 @@ class CGenerator extends GeneratorBase { /** * Generate assignments of pointers in the "self" struct of a destination * port's reactor to the appropriate entries in the "self" struct of the - * source reactor. - * @param instance A port with dependent reactions. + * source reactor. If this port is an input, then it is being written + * to by a reaction belonging to the parent of the port's parent. + * If it is an output, then it is being written to by a reaction belonging + * to the port's parent. + * @param port A port that is written to by reactions. */ - private def void connectPortToEventualSource(PortInstance port) { - // Find the sources that send data to this port, - // which could be the same port if it is an input port written to by a reaction - // or it could be an upstream output port. - // If the port is a multiport, then there may be multiple sources covering - // the range of channels. - var startChannel = 0; - for (eventualSource: port.eventualSources()) { - val src = eventualSource.getPort(); - if (src != port && currentFederate.contains(src.parent)) { - val temp = new StringBuilder(); - // The eventual source is different from the port and is in the federate. - val destStructType = variableStructType(port) - - // If the src self struct pointer is not already declared, declare it. - if (src.parent != port.parent) { - defineSelfStruct(temp, src.parent) - } - - // There are four cases, depending on whether the source or - // destination or both are multiports. - if (src.isMultiport()) { - // If the source port is an input port, then we don't want to use the - // address, whereas if it's an output port, we do. - var modifier = "&"; - if (src.isInput()) modifier = ""; - - if (port.isMultiport()) { - // Source and destination are both multiports. - pr(temp, ''' - // Connect «src.getFullName» to port «port.getFullName» - { // To scope variable j - int j = «eventualSource.startChannel»; - for (int i = «startChannel»; i < «eventualSource.totalWidth» + «startChannel»; i++) { - «CUtil.portRefDestination(port)»[i] = («destStructType»*)«modifier»«CUtil.portRefSource(src)»[j++]; - } - } + private def void connectPortToEventualDestinations(PortInstance src) { + if (!currentFederate.contains(src.parent)) return; + for (srcRange: src.eventualDestinations()) { + for (dstRange : srcRange.destinations) { + val dst = dstRange.port; + val destStructType = variableStructType(dst) + if (currentFederate.contains(dst.parent)) { + startScopedRangeBlock(code, srcRange, dstRange); + if (src.isInput) { + // Source port is written to by reaction in port's parent's parent. + pr(''' + // Connect «src.getFullName» to port «dst.getFullName» + «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src)»; + ''') + } else if (src == dst) { + // An output port of a contained reactor is triggering a reaction. + pr(''' + // Connect «src.getFullName» to port «dst.getFullName» + «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src)»; ''') - startChannel += eventualSource.totalWidth; } else { - // Source is a multiport, destination is a single port. - pr(temp, ''' - // Connect «src.getFullName» to port «port.getFullName» - «CUtil.portRefDestination(port)» = («destStructType»*)«modifier»«CUtil.portRefSource(src)»[«eventualSource.startChannel»]; + pr(''' + // Connect «src.getFullName» to port «dst.getFullName» + «CUtil.portRef(dst)» = («destStructType»*)&«CUtil.portRef(src)»; ''') } - } else if (port.isMultiport()) { - // Source is a single port, Destination is a multiport. - pr(temp, ''' - // Connect «src.getFullName» to port «port.getFullName» - «CUtil.portRefDestination(port)»[«startChannel»] = («destStructType»*)&«CUtil.portRefSource(src)»; - ''') - startChannel++; - } else { - // Both ports are single ports. - pr(temp, ''' - // Connect «src.getFullName» to port «port.getFullName» - «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(src)»; - ''') + endScopedRangeBlock(code, srcRange, dstRange); } - // The null argument ensures that both src and port self struct - // variables are defined. - encloseInBankIteration(code, src, port, temp); } } } @@ -5566,10 +5766,6 @@ class CGenerator extends GeneratorBase { for (child: reactor.children) { deferredInitialize(child, child.reactions); } - // Handle inputs that get sent data from a reaction rather than from - // another contained reactor and reactions that are triggered by an - // output of a contained reactor. - deferredConnectReactionsToPorts(reactor) endScopedBlock(code) @@ -5592,18 +5788,17 @@ class CGenerator extends GeneratorBase { private def void deferredConnectInputsToOutputs(ReactorInstance instance) { pr('''// Connect inputs and outputs for reactor «instance.getFullName».''') - // Iterate over all ports of this reactor that have dependent reactions. + // Iterate over all ports of this reactor that depend on reactions. for (input : instance.inputs) { - if (!input.dependentReactions.isEmpty()) { - // Input has reactions. Connect it to its eventual source. - connectPortToEventualSource(input); + if (!input.dependsOnReactions.isEmpty()) { + // Input is written to by reactions in the parent of the port's parent. + connectPortToEventualDestinations(input); } } for (output : instance.outputs) { - if (!output.dependentReactions.isEmpty() && output.dependsOnPorts.isEmpty()) { - // Output has reactions and no upstream ports. - // Connect it to its eventual source. - connectPortToEventualSource(output); + if (!output.dependsOnReactions.isEmpty()) { + // Output is written to by reactions in the port's parent. + connectPortToEventualDestinations(output); } } for (child: instance.children) { @@ -5618,6 +5813,9 @@ class CGenerator extends GeneratorBase { * @param instance The reactor instance that contains the reactions. */ private def deferredConnectReactionsToPorts(ReactorInstance instance) { + var foundOne = true; + val temp = new StringBuilder(); + for (reaction : instance.reactions) { // First handle the effects that are inputs of contained reactors. for (port : reaction.effects.filter(PortInstance)) { @@ -5626,42 +5824,42 @@ class CGenerator extends GeneratorBase { // the input of a contained reactor. If the contained reactor is // not in the federate, then we don't do anything here. if (currentFederate.contains(port.parent)) { + foundOne = true; + + pr(temp, ''' + // Connect «port», which gets data from reaction «reaction.index» + // of «instance.getFullName», to «port.getFullName». + ''') + val destStructType = variableStructType(port) // The port belongs to a contained reactor, which may be a bank. - startScopedBlock(code, port.parent); - - // If the port is an input, then the port reference may in the parent's parent. - if (port.isInput() && port.parent.parent !== null) { - defineSelfStruct(code, port.parent.parent); - } - - if (port.isMultiport()) { - pr(''' - // Connect «port», which gets data from reaction «reaction.index» - // of «instance.getFullName», to «port.getFullName». - for (int i = 0; i < «port.width»; i++) { - «CUtil.portRefDestination(port)»[i] = («destStructType»*)«CUtil.portRefSource(port)»[i]; - } - ''') - } else { - pr(''' - // Connect «port», which gets data from reaction «reaction.index» - // of «instance.getFullName», to «port.getFullName». - «CUtil.portRefDestination(port)» = («destStructType»*)&«CUtil.portRefSource(port)»; - ''') - } - endScopedBlock(code); + startScopedBlock(temp, port.parent); + startChannelIteration(temp, port); + + pr(temp, ''' + «CUtil.portRef(port)» = («destStructType»*)«CUtil.portRefNested(port)»; + ''') + endChannelIteration(temp, port); + endScopedBlock(temp); } } } // Next handle the sources that are outputs of contained reactors. for (port : reaction.sources.filter(PortInstance)) { - if (port.definition instanceof Output) { + if (port.isOutput) { // This reaction is receiving data from an output // of a contained reactor. If the contained reactor is // not in the federate, then we don't do anything here. if (currentFederate.contains(port.parent)) { - val destStructType = variableStructType(port) + foundOne = true; + + val destStructType = variableStructType(port); + + + // FIXME FIXME FIXME + // startScopedRangeBlock() + // Also maybe use eventualDestinations instead of EventualSources. + // The port may be deeper in the hierarchy, in which // case, the source port will not be the same as the @@ -5675,6 +5873,7 @@ class CGenerator extends GeneratorBase { indent(code); } for (eventualSource: port.eventualSources()) { + // FIXME // write to temp val sourcePort = eventualSource.getPort(); if (sourcePort != port) { @@ -5686,7 +5885,7 @@ class CGenerator extends GeneratorBase { } startScopedRangeBlock(code, eventualSource); - + /* FIXME if (sourcePort.isMultiport && port.isMultiport) { // Both source and destination are multiports. pr(''' @@ -5722,6 +5921,8 @@ class CGenerator extends GeneratorBase { = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; ''') } + * + */ endScopedRangeBlock(code, eventualSource); @@ -5738,6 +5939,7 @@ class CGenerator extends GeneratorBase { } } } + if (foundOne) pr(temp.toString) } /** @@ -5746,8 +5948,6 @@ class CGenerator extends GeneratorBase { * @param parent The reactor. */ private def void deferredCreateDefaultTokens(ReactorInstance reactor) { - var nameOfSelfStruct = CUtil.reactorRef(reactor); - // Look for outputs with token types. for (output : reactor.outputs) { val type = (output.definition as Output).inferredType; @@ -5758,17 +5958,11 @@ class CGenerator extends GeneratorBase { // If the rootType is 'void', we need to avoid generating the code // 'sizeof(void)', which some compilers reject. val size = (rootType == 'void') ? '0' : '''sizeof(«rootType»)''' - if (output.isMultiport()) { - pr(''' - for (int i = 0; i < «output.width»; i++) { - «nameOfSelfStruct»->_lf_«output.name»[i].token = _lf_create_token(«size»); - } - ''') - } else { - pr(''' - «nameOfSelfStruct»->_lf_«output.name».token = _lf_create_token(«size»); - ''') - } + startChannelIteration(code, output); + pr(''' + «CUtil.portRef(output)».token = _lf_create_token(«size»); + ''') + endChannelIteration(code, output); } } } @@ -5797,17 +5991,9 @@ class CGenerator extends GeneratorBase { startScopedRangeBlock(code, sendingRange); - // Syntax is slightly difference for a multiport output vs. single port. - // For a single port, there should be only one sendingRange. - if (output.isMultiport()) { - pr(''' - «CUtil.portRefSource(output)»[channel].num_destinations = «sendingRange.getNumberOfDestinationReactors()»; - ''') - } else { - pr(''' - «CUtil.portRefSource(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; - ''') - } + pr(''' + «CUtil.portRef(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + ''') endScopedRangeBlock(code, sendingRange); } @@ -5852,11 +6038,11 @@ class CGenerator extends GeneratorBase { // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { pr(''' - «CUtil.portRefSource(port)»[channel]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port)»[channel]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' - «CUtil.portRefSource(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } endScopedRangeBlock(code, sendingRange); @@ -5894,7 +6080,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(code, effect.parent); - val effectRef = CUtil.portRefSource(effect); + val effectRef = CUtil.portRefNested(effect); pr(''' «effectRef»_width = «effect.width»; @@ -6039,12 +6225,15 @@ class CGenerator extends GeneratorBase { startScopedBlock(init); pr(init, "int count = 0;") var bankWidth = 1; + var portRef = ""; if (effect.isInput) { pr(init, "// Reaction writes to an input of a contained reactor.") bankWidth = effect.parent.width; startScopedBlock(init, effect.parent); + portRef = CUtil.portRefNestedName(effect); } else { startScopedBlock(init); + portRef = CUtil.portRefName(effect); } if (effect.isMultiport()) { @@ -6056,7 +6245,7 @@ class CGenerator extends GeneratorBase { pr(init, ''' for (int i = 0; i < «effect.width»; i++) { «CUtil.reactionRef(reaction)».output_produced[i + count] - = &«CUtil.portRefSource(effect)»[i]«connector»is_present; + = &«portRef»[i]«connector»is_present; } count += «effect.getWidth()»; ''') @@ -6065,7 +6254,7 @@ class CGenerator extends GeneratorBase { // The effect is not a multiport. pr(init, ''' «CUtil.reactionRef(reaction)».output_produced[count++] - = &«CUtil.portRefSource(effect)».is_present; + = &«portRef».is_present; ''') outputCount += bankWidth; } @@ -6173,20 +6362,16 @@ class CGenerator extends GeneratorBase { destRangeCount++; } } - - var index = "channel"; + var index = CUtil.channelIndex(port); if (reaction.parent != port.parent) { - // Reaction is writing to the input port of a contained reactor. - // In this case, the triggered_sizes array size includes the bank - // width, and hence we need to index using range_count rather than - // channel. + // The port may be an input of a contained reactor bank. index = "range_count"; } // Record the total size of the array. pr(''' // Reaction «reaction.index» of «name» triggers «destRangeCount» downstream reactions - // through port «port.getFullName»[channel]. + // through port «port.getFullName». «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «index»] = «destRangeCount»; ''') diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 90aa111ed4..5d0bf50761 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -76,7 +76,7 @@ public class CUtil { /** * Return a name of a variable to refer to the bank index of a reactor - * in a bank. This is has the form uniqueID_bank_index where uniqueID + * in a bank. This is has the form uniqueID_i where uniqueID * is an identifier for the instance that is guaranteed to be different * from the ID of any other instance in the program. * @param instance A reactor instance. @@ -84,7 +84,31 @@ public class CUtil { static public String bankIndex(ReactorInstance instance) { return instance.uniqueID() + "_i"; } - + + /** + * Variant of {@link bankIndex(ReactorInstance) that generates a + * slightly different variable name. This is intended to be used as + * a second variable when simultaneously iterating over two banks that + * may in fact be the same bank. This is has the form uniqueID_j where uniqueID + * is an identifier for the instance that is guaranteed to be different + * from the ID of any other instance in the program. + * @param instance A reactor instance. + */ + static public String bankIndexDst(ReactorInstance instance) { + return instance.uniqueID() + "_j"; + } + + /** + * Return a name of a variable to refer to the channel index of a port + * in a bank. This is has the form uniqueID_c where uniqueID + * is an identifier for the instance that is guaranteed to be different + * from the ID of any other instance in the program. + * @param instance A reactor instance. + */ + static public String channelIndex(PortInstance port) { + return port.uniqueID() + "_c"; + } + /** * Return an expression that, when evaluated in a context with * bank index variables defined for the specified reactor and @@ -138,79 +162,94 @@ static public String indexExpression(ReactorInstance instance) { * The returned string will have one of the following forms: * * * selfStruct->_lf_portName + * * selfStruct->_lf_portName[i] * * selfStruct->_lf_parent.portName + * * selfStruct->_lf_parent.portName[i] + * * selfStruct->_lf_parent[j].portName + * * selfStruct->_lf_parent[j].portName[i] * - * where the first is returned if the container directly contains the port. - * The selfStruct points to the port's parent or parent's parent, and if - * that parent is a bank, then it will have the form selfStruct[bankIndex], - * where bankIndex is the string returned by {@link bankIndex(ReactorInstance)}. + * where the index j is present if the parent is a bank and is + * the string returned by {@link bankIndex(ReactorInstance)}, and + * the index i is present if the port is a multiport and is + * the string returned by {@link channelIndex(PortInstance)}. * - * If the container is port's parent's parent, and the port's parent is a bank, - * then "_lf_parent" will be replaced by "_lf_parent[bank_index]", where - * bank_index is the string returned by bankIndex(port.parent). + * The first two forms are used if isNested is false, + * and the remaining four are used if isNested is true. + * Set isNested to true when referencing a port belonging + * to a contained reactor. * * @param port The port. - * @param container The container. + * @param isNested True to return a reference relative to the parent's parent. + * @param includeChannelIndex True to include the channel index at the end. */ - static public String portRef(PortInstance port, ReactorInstance container) { - if (port.getParent() == container) { - String sourceStruct = CUtil.reactorRef(port.getParent()); - return sourceStruct + "->_lf_" + port.getName(); - } else if (port.getParent().getParent() == container) { - String sourceStruct = CUtil.reactorRef(port.getParent().getParent()); - // The form is slightly different depending on whether the port belongs to a bank. - if (port.getParent().isBank()) { - return sourceStruct + "->_lf_" + port.getParent().getName() - + "[" + bankIndex(port.getParent()) + "]." + port.getName(); - } else { - return sourceStruct + "->_lf_" + port.getParent().getName() + "." + port.getName(); - } + static public String portRef( + PortInstance port, boolean isNested, boolean includeChannelIndex + ) { + String channel = ""; + if (port.isMultiport() && includeChannelIndex) { + channel = "[" + channelIndex(port) + "]"; + } + if (isNested) { + return reactorRefContained(port.getParent()) + "." + port.getName() + channel; } else { - throw new IllegalArgumentException( - "Port " + port.getFullName() + " is not visible to " + container.getFullName() - ); + String sourceStruct = CUtil.reactorRef(port.getParent()); + return sourceStruct + "->_lf_" + port.getName() + channel; } } - + /** - * This is a special case of {@link portRef(PortInstance, ReactorInstance) - * where the second argument is the parent of the first. + * Special case of {@link portRef(PortInstance, boolean, boolean)} + * that provides a reference to the port on the self struct of the + * port's parent. This is used when an input port triggers a reaction + * in the port's parent or when an output port is written to by + * a reaction in the port's parent. + * This is equivalent to calling `portRef(port, false, true)`. * @param port An instance of the port to be referenced. */ static public String portRef(PortInstance port) { - return portRef(port, port.getParent()); + return portRef(port, false, true); } /** - * This is a special case of {@link portRef(PortInstance, ReactorInstance) - * where it is know that the reference required for the port is to a - * sink of data, not a source. This customizes portRef() by figuring out - * what the appropriate container. - * + * Return the portRef without the channel indexing. + * This is useful for deriving a reference to the _width variable. * @param port An instance of the port to be referenced. */ - static public String portRefDestination(PortInstance port) { - if (port.isOutput()) { - return portRef(port, port.getParent().getParent()); - } - return portRef(port, port.getParent()); + static public String portRefName(PortInstance port) { + return portRef(port, false, false); } /** - * This is a special case of {@link portRef(PortInstance, ReactorInstance) - * where it is know that the reference required for the port is to a - * source of data, not a sink. This customizes portRef() by figuring out - * what the appropriate container. - * - * @param port An instance of the port to be referenced. + * Special case of {@link portRef(PortInstance, boolean, boolean)} + * that provides a reference to the port on the self struct of the + * parent of the port's parent. This is used when an input port + * is written to by a reaction in the parent of the port's parent, + * or when an output port triggers a reaction in the parent of the + * port's parent. + * This is equivalent to calling `portRef(port, true, true)`. + * + * @param port The port. */ - static public String portRefSource(PortInstance port) { - if (port.isInput()) { - return portRef(port, port.getParent().getParent()); - } - return portRef(port, port.getParent()); + static public String portRefNested(PortInstance port) { + return portRef(port, true, true); } - + + /** + * Special case of {@link portRef(PortInstance, boolean, boolean)} + * that provides a reference to the port on the self struct of the + * parent of the port's parent, but without the channel indexing, + * even if it is a multiport. This is used when an input port + * is written to by a reaction in the parent of the port's parent, + * or when an output port triggers a reaction in the parent of the + * port's parent. + * This is equivalent to calling `portRef(port, true, false)`. + * + * @param port The port. + */ + static public String portRefNestedName(PortInstance port) { + return portRef(port, true, false); + } + /** * Return a reference to the reaction entry on the self struct * of the parent of the specified reaction. From 840675411e1a922ae24b5c0deda5341dea09162e Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 11 Dec 2021 08:45:52 -0800 Subject: [PATCH 070/221] Interrim checkin --- .../org/lflang/generator/PortInstance.java | 5 +- .../org/lflang/generator/c/CGenerator.xtend | 178 +++--------------- .../src/org/lflang/generator/c/CUtil.java | 1 + 3 files changed, 25 insertions(+), 159 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 3660fee214..0e8f157d18 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -205,11 +205,9 @@ public static List eventualDestinations(Range srcRange) { } // Dependent port overlaps the range of interest. // Get a new range that possibly subsets the target range. - // Modify the "interleaved" status by combining that of - // the source range and the destination. // Argument is guaranteed by above checks to be less than // dep.totalWidth, so the result will not be null. - Range subDep = dep.tail(srcRange.getStartOffset() - depWidthCovered); + Range subDep = dep.tail(depWidthCovered - srcRange.getStartOffset()); // The following argument is guaranteed to be greater than // depWidthCovered - srcRange.getStartOffset(). subDep = subDep.truncate(srcRange.getTotalWidth()); @@ -229,6 +227,7 @@ public static List eventualDestinations(Range srcRange) { // dep range is exhausted. Get another one. if (!dependentPorts.hasNext()) break; // This should be an error. dep = dependentPorts.next(); + depWidthCovered = 0; } } } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index c16d754ebf..fc75c9d115 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3163,14 +3163,15 @@ class CGenerator extends GeneratorBase { } startScopedBankChannelIteration(temp, port, "count"); val portRef = CUtil.portRefNested(port); + val con = (port.isMultiport)? "->" : "."; pr(temp, ''' - _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«portRef»->is_present; + _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«portRef»«con»is_present; ''') if (isFederatedAndDecentralized) { // Intended_tag is only applicable to ports in federated execution. pr(temp, ''' - _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«portRef»->intended_tag; + _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«portRef»«con»intended_tag; ''') } startTimeStepIsPresentCount += port.width * port.parent.width; @@ -3637,12 +3638,12 @@ class CGenerator extends GeneratorBase { generateTraceTableEntries(instance, instance.actions, instance.timers) generateReactorInstanceExtension(instance, instance.reactions) generateParameterInitialization(instance) + initializeOutputMultiports(instance) - + initializeInputMultiports(instance) + recordStartupAndShutdown(instance.reactions); - initializeInputs(instance) - // Next, initialize the "self" struct with state variables. // These values may be expressions that refer to the parameter values defined above. generateStateVariableInitializations(instance); @@ -3877,19 +3878,15 @@ class CGenerator extends GeneratorBase { * Allocate memory for inputs. * @param reactor The reactor. */ - def initializeInputs(ReactorInstance reactor) { - if (!reactor.inputs.isEmpty()) { - startScopedBlock(initializeTriggerObjects, reactor); - } + def initializeInputMultiports(ReactorInstance reactor) { for (input : reactor.inputs) { - val portRef = CUtil.portRef(input) val portRefName = CUtil.portRefName(input) // If the port is a multiport, create an array. if (input.isMultiport) { pr(initializeTriggerObjects, ''' «portRefName»_width = «input.width»; // Allocate memory for multiport inputs. - «portRef» = («variableStructType(input)»**)malloc(sizeof(«variableStructType(input)»*) * «input.width»); + «portRefName» = («variableStructType(input)»**)malloc(sizeof(«variableStructType(input)»*) * «input.width»); // Set inputs by default to an always absent default input. for (int i = 0; i < «input.width»; i++) { «portRefName»[i] = &«CUtil.reactorRef(reactor)»->_lf_default__«input.name»; @@ -3902,9 +3899,6 @@ class CGenerator extends GeneratorBase { ''') } } - if (!reactor.inputs.isEmpty()) { - endScopedBlock(initializeTriggerObjects); - } } /** @@ -5139,6 +5133,7 @@ class CGenerator extends GeneratorBase { private def void defineSelfStruct( StringBuilder builder, ReactorInstance reactor ) { + if (reactor === null) return; var nameOfSelfStruct = reactor.uniqueID() + "_self"; var structType = CUtil.selfType(reactor) pr(builder, ''' @@ -5162,12 +5157,16 @@ class CGenerator extends GeneratorBase { val dst = dstRange.port; val destStructType = variableStructType(dst) if (currentFederate.contains(dst.parent)) { + + val mod = (dst.isMultiport)? "" : "&"; + startScopedRangeBlock(code, srcRange, dstRange); if (src.isInput) { - // Source port is written to by reaction in port's parent's parent. + // Source port is written to by reaction in port's parent's parent + // and ultimate destination is further downstream. pr(''' // Connect «src.getFullName» to port «dst.getFullName» - «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src)»; + «CUtil.portRef(dst)» = («destStructType»*)«mod»«CUtil.portRefNested(src)»; ''') } else if (src == dst) { // An output port of a contained reactor is triggering a reaction. @@ -5806,142 +5805,6 @@ class CGenerator extends GeneratorBase { } } - /** - * Connect inputs that get sent data from a reaction rather than from - * another contained reactor and reactions that are triggered by an - * output of a contained reactor. - * @param instance The reactor instance that contains the reactions. - */ - private def deferredConnectReactionsToPorts(ReactorInstance instance) { - var foundOne = true; - val temp = new StringBuilder(); - - for (reaction : instance.reactions) { - // First handle the effects that are inputs of contained reactors. - for (port : reaction.effects.filter(PortInstance)) { - if (port.definition instanceof Input) { - // This reaction is sending to an input. Must be - // the input of a contained reactor. If the contained reactor is - // not in the federate, then we don't do anything here. - if (currentFederate.contains(port.parent)) { - foundOne = true; - - pr(temp, ''' - // Connect «port», which gets data from reaction «reaction.index» - // of «instance.getFullName», to «port.getFullName». - ''') - - val destStructType = variableStructType(port) - // The port belongs to a contained reactor, which may be a bank. - startScopedBlock(temp, port.parent); - startChannelIteration(temp, port); - - pr(temp, ''' - «CUtil.portRef(port)» = («destStructType»*)«CUtil.portRefNested(port)»; - ''') - endChannelIteration(temp, port); - endScopedBlock(temp); - } - } - } - // Next handle the sources that are outputs of contained reactors. - for (port : reaction.sources.filter(PortInstance)) { - if (port.isOutput) { - // This reaction is receiving data from an output - // of a contained reactor. If the contained reactor is - // not in the federate, then we don't do anything here. - if (currentFederate.contains(port.parent)) { - foundOne = true; - - val destStructType = variableStructType(port); - - - // FIXME FIXME FIXME - // startScopedRangeBlock() - // Also maybe use eventualDestinations instead of EventualSources. - - - // The port may be deeper in the hierarchy, in which - // case, the source port will not be the same as the - // destination port, and there may be multiple sources - // if the destination is a multiport. - if (port.isMultiport) { - pr(''' - { - int dst_channel = 0; - '''); - indent(code); - } - for (eventualSource: port.eventualSources()) { - // FIXME // write to temp - val sourcePort = eventualSource.getPort(); - - if (sourcePort != port) { - // sourcePort is deeper in the hierarchy. Need a - // pointer to the self struct of the destination port's parent. - pr("{"); - indent(); - defineSelfStruct(code, port.parent); - } - - startScopedRangeBlock(code, eventualSource); - /* FIXME - if (sourcePort.isMultiport && port.isMultiport) { - // Both source and destination are multiports. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)»[dst_channel++] - = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[channel]; - if (dst_channel >= «port.width») dst_channel = 0; - ''') - } else if (sourcePort.isMultiport) { - // Destination is not a multiport, so the channelWidth of the source port should be 1. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)» - = («destStructType»*)&«CUtil.portRefSource(sourcePort)»[channel]; - ''') - } else if (port.isMultiport) { - // Source is not a multiport, but the destination is. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)»[dst_channel++] - = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; - if (dst_channel >= «port.width») dst_channel = 0; - ''') - } else { - // Neither is a multiport. - pr(''' - // Record output «sourcePort.getFullName», which triggers reaction «reaction.index» - // of «instance.getFullName», on its self struct. - «CUtil.portRefDestination(port)» - = («destStructType»*)&«CUtil.portRefSource(sourcePort)»; - ''') - } - * - */ - - endScopedRangeBlock(code, eventualSource); - - if (sourcePort != port) { - unindent(); - pr("}"); - } - } - if (port.isMultiport) { - unindent(code); - pr("}"); - } - } - } - } - } - if (foundOne) pr(temp.toString) - } - /** * For each output of the specified reactor that has a token type * (type* or type[]), create a default token and put it on the self struct. @@ -6038,7 +5901,7 @@ class CGenerator extends GeneratorBase { // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { pr(''' - «CUtil.portRefNested(port)»[channel]->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' @@ -6080,7 +5943,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(code, effect.parent); - val effectRef = CUtil.portRefNested(effect); + val effectRef = CUtil.portRefNestedName(effect); pr(''' «effectRef»_width = «effect.width»; @@ -6362,8 +6225,11 @@ class CGenerator extends GeneratorBase { destRangeCount++; } } - var index = CUtil.channelIndex(port); - if (reaction.parent != port.parent) { + var index = "0"; + if (port.isMultiport) { + index = CUtil.channelIndex(port); + } + if (reaction.parent != port.parent && port.parent.isBank) { // The port may be an input of a contained reactor bank. index = "range_count"; } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index 5d0bf50761..cdefa699c6 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -82,6 +82,7 @@ public class CUtil { * @param instance A reactor instance. */ static public String bankIndex(ReactorInstance instance) { + if (!instance.isBank()) return "0"; return instance.uniqueID() + "_i"; } From 7233de7ea8fe3b4a35e8abbf0b1afab5b6c0f450 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 11 Dec 2021 09:31:28 -0800 Subject: [PATCH 071/221] Interrim checkin before work on interleaving. --- .../org/lflang/generator/PortInstance.java | 31 ++++++++++++++++--- .../org/lflang/generator/c/CGenerator.xtend | 6 ++-- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 0e8f157d18..9d6cb21f0a 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -512,8 +512,27 @@ public int getNumberOfDestinationReactors() { */ @Override public Range tail(int offset) { - SendRange result = (SendRange)super.tail(offset); - if (result == null) return null; + // NOTE: Cannot use the superclass because it returns a Range, not a SendRange. + if (offset == 0) return this; + if (offset >= totalWidth) return null; + + int channelWidth = PortInstance.this.width; + int bankWidth = PortInstance.this.parent.width(); + + int banksToConsume, channelsToConsume; + if (interleaved) { + banksToConsume = offset / bankWidth; + channelsToConsume = offset % bankWidth; + } else { + banksToConsume = offset / channelWidth; + channelsToConsume = offset % channelWidth; + } + SendRange result = new SendRange( + startChannel + channelsToConsume, + startBank + banksToConsume, + totalWidth - offset + ); + for (Range destination : destinations) { result.destinations.add(destination.tail(offset)); } @@ -521,8 +540,9 @@ public Range tail(int offset) { } /** - * Return a new SendRange that is converted to belong to the - * port in the specified range. If the total widths are not + * Return a new SendRange that is like this one, but + * converted to belong to the port in the specified range. + * If the total widths are not * the same, then the minimum of the two widths is returned. * If the specified srcRange is interleaved, then the interleaved * property of each of the returned destinations will be toggled. @@ -555,7 +575,8 @@ protected SendRange newSendRange(Range srcRange) { */ @Override protected SendRange truncate(int newWidth) { - SendRange result = (SendRange)super.truncate(newWidth); + if (newWidth >= totalWidth) return this; + SendRange result = new SendRange(startChannel, startBank, newWidth); for (Range destination : destinations) { result.destinations.add(destination.truncate(newWidth)); } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index fc75c9d115..fcd8877b1d 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5160,23 +5160,23 @@ class CGenerator extends GeneratorBase { val mod = (dst.isMultiport)? "" : "&"; + pr(''' + // Connect «src.getFullName» to port «dst.getFullName» + ''') startScopedRangeBlock(code, srcRange, dstRange); if (src.isInput) { // Source port is written to by reaction in port's parent's parent // and ultimate destination is further downstream. pr(''' - // Connect «src.getFullName» to port «dst.getFullName» «CUtil.portRef(dst)» = («destStructType»*)«mod»«CUtil.portRefNested(src)»; ''') } else if (src == dst) { // An output port of a contained reactor is triggering a reaction. pr(''' - // Connect «src.getFullName» to port «dst.getFullName» «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src)»; ''') } else { pr(''' - // Connect «src.getFullName» to port «dst.getFullName» «CUtil.portRef(dst)» = («destStructType»*)&«CUtil.portRef(src)»; ''') } From 600925730c3fc294ce6a8a6620031761d5f8d11b Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 11 Dec 2021 16:37:01 -0800 Subject: [PATCH 072/221] Interrim checkin --- .../org/lflang/generator/PortInstance.java | 24 +-- .../org/lflang/generator/ReactorInstance.java | 10 +- .../org/lflang/generator/c/CGenerator.xtend | 154 +++++++++------- .../src/org/lflang/generator/c/CUtil.java | 165 +++++++++++++++--- 4 files changed, 260 insertions(+), 93 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 9d6cb21f0a..c6ff555b7b 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -179,7 +179,11 @@ public static List eventualDestinations(Range srcRange) { if (!srcRange.getPort().dependentReactions.isEmpty()) { // This will be the final result if there are no connections. SendRange candidate = srcPort.new SendRange( - srcRange.startChannel, srcRange.startBank, srcRange.totalWidth + srcRange.startChannel, + srcRange.startBank, + srcRange.totalWidth, + srcRange.interleaved, + srcRange.connection ); candidate.destinations.add(srcRange); result.add(candidate); @@ -480,15 +484,13 @@ private void setInitialWidth(ErrorReporter errorReporter) { * all of which have the same width as this channel range. * It also includes a field representing the number of destination * reactors. - * - * Note that the interleaved status of the base class is meaningless for a - * SendRange because of the destinations may be interleaved and some not. - * Also, the connection argument makes no sense. */ public class SendRange extends Range { - public SendRange(int startChannel, int startBank, int totalWidth) { - super(startChannel, startBank, totalWidth, false, null); + public SendRange( + int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection + ) { + super(startChannel, startBank, totalWidth, interleaved, connection); } public int getNumberOfDestinationReactors() { @@ -530,7 +532,9 @@ public Range tail(int offset) { SendRange result = new SendRange( startChannel + channelsToConsume, startBank + banksToConsume, - totalWidth - offset + totalWidth - offset, + interleaved, + connection ); for (Range destination : destinations) { @@ -556,7 +560,7 @@ protected SendRange newSendRange(Range srcRange) { reference = truncate(srcRange.totalWidth); } SendRange result = srcRange.getPort().new SendRange( - srcRange.startChannel, srcRange.startBank, totalWidth + srcRange.startChannel, srcRange.startBank, totalWidth, interleaved, connection ); if (srcRange.interleaved) { // Toggle the destination interleaved status. @@ -576,7 +580,7 @@ protected SendRange newSendRange(Range srcRange) { @Override protected SendRange truncate(int newWidth) { if (newWidth >= totalWidth) return this; - SendRange result = new SendRange(startChannel, startBank, newWidth); + SendRange result = new SendRange(startChannel, startBank, newWidth, interleaved, connection); for (Range destination : destinations) { result.destinations.add(destination.truncate(newWidth)); } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 48250f0eb6..a351c5b357 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -857,6 +857,10 @@ private ReactorInstance( /** * Connect the given left port range to the given right port range. + * This method consolidates the interleaved state on the destination + * side. That is, it sets the interleaved state of the destination + * to the exclusive OR of the interleaved state of the two ranges, + * and sets the interleaved state of the source to false. * @param src The source range. * @param dst The destination range. */ @@ -864,6 +868,10 @@ private void connectPortInstances( PortInstance.Range src, PortInstance.Range dst ) { + if (src.interleaved) { + dst = dst.toggleInterleaved(); + src = src.toggleInterleaved(); + } src.getPort().dependentPorts.add(dst); dst.getPort().dependsOnPorts.add(src); } @@ -948,7 +956,7 @@ private void establishPortConnections() { /** * Given a list of port references, as found on either side of a connection, - * return a list of the port instances referenced. These may be multiports, + * return a list of the port instance ranges referenced. These may be multiports, * and may be ports of a contained bank (a port representing ports of the bank * members) so the returned list includes ranges of banks and channels. * diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index fcd8877b1d..3659364ee2 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3159,9 +3159,13 @@ class CGenerator extends GeneratorBase { if (port.parent != instance) { // The port belongs to contained reactor, so we also have // iterate over the instance bank members. + startScopedBlock(temp); + pr(temp, "int count = 0;"); startScopedBlock(temp, instance); + startScopedBankChannelIteration(temp, port, null); + } else { + startScopedBankChannelIteration(temp, port, "count"); } - startScopedBankChannelIteration(temp, port, "count"); val portRef = CUtil.portRefNested(port); val con = (port.isMultiport)? "->" : "."; @@ -3174,10 +3178,17 @@ class CGenerator extends GeneratorBase { _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«portRef»«con»intended_tag; ''') } - startTimeStepIsPresentCount += port.width * port.parent.width; - endScopedBankChannelIteration(temp, port, "count"); - if (port.parent != instance) endScopedBlock(temp); + if (port.parent != instance) { + startTimeStepIsPresentCount += port.width * port.parent.width * instance.width; + pr(temp, "count++;"); + endScopedBlock(temp); + endScopedBlock(temp); + endScopedBankChannelIteration(temp, port, null); + } else { + startTimeStepIsPresentCount += port.width * port.parent.width; + endScopedBankChannelIteration(temp, port, "count"); + } } } } @@ -3200,7 +3211,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(temp, instance); startScopedBankChannelIteration(temp, port, "count"); - val portRef = CUtil.portRef(port, true, true); + val portRef = CUtil.portRef(port, true, true, null); pr(temp, ''' _lf_tokens_with_ref_count[«startTimeStepTokens» + count].token = &«portRef»->token; @@ -3250,35 +3261,39 @@ class CGenerator extends GeneratorBase { for (child : instance.children) { if (currentFederate.contains(child) && child.outputs.size > 0) { + startScopedBlock(temp); + pr(temp, '''int count = 0;'''); startScopedBlock(temp, child); + var channelCount = 0; for (output : child.outputs) { - foundOne = true; - - pr(temp, ''' - // Add port «output.getFullName» to array of is_present fields. - ''') - startScopedBlock(temp); - pr(temp, '''int count = 0;'''); - startChannelIteration(temp, output); - - pr(temp, ''' - _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)».is_present; - ''') - - if (isFederatedAndDecentralized) { - // Intended_tag is only applicable to ports in federated execution with decentralized coordination. + if (!output.dependsOnReactions.isEmpty){ + foundOne = true; + pr(temp, ''' - // Add port «output.getFullName» to array of intended_tag fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)»[j].intended_tag; + // Add port «output.getFullName» to array of is_present fields. ''') + startChannelIteration(temp, output); + + pr(temp, ''' + _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)».is_present; + ''') + + if (isFederatedAndDecentralized) { + // Intended_tag is only applicable to ports in federated execution with decentralized coordination. + pr(temp, ''' + // Add port «output.getFullName» to array of intended_tag fields. + _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)»[j].intended_tag; + ''') + } + + pr(temp, '''count++;'''); + channelCount += output.width; + endChannelIteration(temp, output); } - startTimeStepIsPresentCount += output.width; - - pr(temp, '''count++;'''); - endChannelIteration(temp, output); - endScopedBlock(temp); } + startTimeStepIsPresentCount += channelCount * child.width; + endScopedBlock(temp); endScopedBlock(temp); } } @@ -5024,7 +5039,6 @@ class CGenerator extends GeneratorBase { protected def void startScopedRangeBlock( StringBuilder builder, PortInstance.Range srcRange, PortInstance.Range dstRange ) { - val dst = dstRange.port; val src = srcRange.port; val width = (srcRange.totalWidth <= dstRange.totalWidth)? srcRange.totalWidth : dstRange.totalWidth; @@ -5042,20 +5056,19 @@ class CGenerator extends GeneratorBase { // Need to change SendRange so that startBank is an array of length equal to // the depth of the port's container. - if (dst.parent.isBank && dst.isInput) { + if (src.parent.isBank && src.isOutput) { + // Need to specify a suffix to avoid a variable name collision. pr(builder, ''' - int «CUtil.bankIndexDst(dst.parent)» = «dstRange.startBank»; + int «CUtil.bankIndex(src.parent, "_src")» = «srcRange.startBank»; ''') } - if (dst.isMultiport) { + if (src.isMultiport) { pr(builder, ''' - int «CUtil.channelIndex(dst)» = «dstRange.startChannel»; + int «CUtil.channelIndex(src)» = «srcRange.startChannel»; ''') } - startScopedRangeBlock(builder, srcRange); - if (dst.parent != src.parent) { - defineSelfStruct(builder, dst.parent); - } + startScopedRangeBlock(builder, dstRange); + defineSelfStruct(builder, src.parent, "_src"); } /** @@ -5069,53 +5082,54 @@ class CGenerator extends GeneratorBase { ) { val src = srcRange.port; val dst = dstRange.port; + val sfx = "_src"; // If an output port is triggering a reaction in its parent's parent, // then the destination and source may be the same port. In that case, // we want to avoid double incrementing the bank and channel variables. if (dst != src) { - // Sadly, xtend doesn't seem to support XOR operator in Java. - // val interleaved = srcRange.interleaved ^ dstRange.interleaved; + // Interleaved status is normally stored in the destination only, + // but just in case, calculate the XOR. val interleaved = (srcRange.interleaved || dstRange.interleaved) && !(srcRange.interleaved && dstRange.interleaved); if (interleaved) { - if (dst.parent.isBank && dst.isInput) { + if (src.parent.isBank && src.isOutput) { pr(builder, ''' - «CUtil.bankIndexDst(dst.parent)»++; + «CUtil.bankIndex(src.parent, sfx)»++; ''') - if (dst.isMultiport) { + if (src.isMultiport) { pr(builder, ''' - if («CUtil.bankIndexDst(dst.parent)» >= «dst.parent.width») { - «CUtil.bankIndexDst(dst.parent)» = 0; - «CUtil.channelIndex(dst)»++; + if («CUtil.bankIndex(src.parent, sfx)» >= «src.parent.width») { + «CUtil.bankIndex(src.parent, sfx)» = 0; + «CUtil.channelIndex(src)»++; } ''') } - } else if (dst.isMultiport) { + } else if (src.isMultiport) { pr(builder, ''' - «CUtil.channelIndex(dst)»++; + «CUtil.channelIndex(src)»++; ''') } } else { - if (dst.isMultiport) { + if (src.isMultiport) { pr(builder, ''' - «CUtil.channelIndex(dst)»++; + «CUtil.channelIndex(src)»++; ''') - if (dst.parent.isBank && dst.isInput) { + if (src.parent.isBank && src.isOutput) { pr(builder, ''' - if («CUtil.channelIndex(dst)» >= «dst.width») { - «CUtil.channelIndex(dst)» = 0; - «CUtil.bankIndexDst(dst.parent)»++; + if («CUtil.channelIndex(src)» >= «src.width») { + «CUtil.channelIndex(src)» = 0; + «CUtil.bankIndex(src.parent, sfx)»++; } ''') } - } else if (dst.parent.isBank && dst.isInput) { + } else if (src.parent.isBank && src.isInput) { pr(builder, ''' - «CUtil.bankIndexDst(dst.parent)»++; + «CUtil.bankIndex(src.parent, sfx)»++; ''') } } } - endScopedRangeBlock(builder, srcRange); + endScopedRangeBlock(builder, dstRange); endScopedBlock(builder); } @@ -5132,12 +5146,31 @@ class CGenerator extends GeneratorBase { */ private def void defineSelfStruct( StringBuilder builder, ReactorInstance reactor + ) { + defineSelfStruct(builder, reactor, null); + } + + /** + * For the specified reactor, print code to the specified builder + * that defines a pointer to the self struct of the specified + * reactor. + * + * If the specified reactor is a bank, then the pointer + * is to the array of pointers to the self structs for that bank. + * + * @param builder The string builder into which to write. + * @param reactor The reactor instance for which to provide a self struct. + * @param suffix An optional suffix to append to the variable name. + */ + private def void defineSelfStruct( + StringBuilder builder, ReactorInstance reactor, String suffix ) { if (reactor === null) return; var nameOfSelfStruct = reactor.uniqueID() + "_self"; + if (suffix !== null) nameOfSelfStruct += suffix; var structType = CUtil.selfType(reactor) pr(builder, ''' - «structType»* «nameOfSelfStruct» = («structType»*)self_structs[«CUtil.indexExpression(reactor)»]; + «structType»* «nameOfSelfStruct» = («structType»*)self_structs[«CUtil.indexExpression(reactor, suffix)»]; ''') } @@ -5164,20 +5197,23 @@ class CGenerator extends GeneratorBase { // Connect «src.getFullName» to port «dst.getFullName» ''') startScopedRangeBlock(code, srcRange, dstRange); + // Above uses suffix "_src" for the self struct for the source range. + val sfx = "_src"; + if (src.isInput) { // Source port is written to by reaction in port's parent's parent // and ultimate destination is further downstream. pr(''' - «CUtil.portRef(dst)» = («destStructType»*)«mod»«CUtil.portRefNested(src)»; + «CUtil.portRef(dst)» = («destStructType»*)«mod»«CUtil.portRefNested(src, sfx)»; ''') } else if (src == dst) { // An output port of a contained reactor is triggering a reaction. pr(''' - «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src)»; + «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src, sfx)»; ''') } else { pr(''' - «CUtil.portRef(dst)» = («destStructType»*)&«CUtil.portRef(src)»; + «CUtil.portRef(dst)» = («destStructType»*)&«CUtil.portRef(src, sfx)»; ''') } endScopedRangeBlock(code, srcRange, dstRange); diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index cdefa699c6..c06c079a8c 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -79,24 +79,27 @@ public class CUtil { * in a bank. This is has the form uniqueID_i where uniqueID * is an identifier for the instance that is guaranteed to be different * from the ID of any other instance in the program. + * If the instance is not a bank, return "0". * @param instance A reactor instance. */ static public String bankIndex(ReactorInstance instance) { - if (!instance.isBank()) return "0"; - return instance.uniqueID() + "_i"; + return bankIndex(instance, null); } /** - * Variant of {@link bankIndex(ReactorInstance) that generates a - * slightly different variable name. This is intended to be used as - * a second variable when simultaneously iterating over two banks that - * may in fact be the same bank. This is has the form uniqueID_j where uniqueID + * Return a name of a variable to refer to the bank index of a reactor + * in a bank. This is has the form uniqueID_suffix where uniqueID * is an identifier for the instance that is guaranteed to be different - * from the ID of any other instance in the program. + * from the ID of any other instance in the program and suffix is the + * specified suffix. If the suffix is null, then "_i" is used. + * If the instance is not a bank, return "0". * @param instance A reactor instance. + * @param suffix The suffix or null to use a default suffix. */ - static public String bankIndexDst(ReactorInstance instance) { - return instance.uniqueID() + "_j"; + static public String bankIndex(ReactorInstance instance, String suffix) { + if (!instance.isBank()) return "0"; + if (suffix == null) suffix = "_i"; + return instance.uniqueID() + suffix; } /** @@ -135,22 +138,51 @@ static public String channelIndex(PortInstance port) { * @param prefix The prefix used for index variables for bank members. */ static public String indexExpression(ReactorInstance instance) { + return indexExpression(instance, null); + } + + /** + * Return an expression that, when evaluated in a context with + * bank index variables defined for the specified reactor and + * any container(s) that are also banks, returns a unique index + * for a runtime reactor instance. This can be used to maintain an + * array of runtime instance objects, each of which will have a + * unique index. + * + * This is rather complicated because + * this reactor instance and any of its parents may actually + * represent a bank of runtime reactor instances rather a single + * runtime instance. This method returns an expression that should + * be evaluatable in any target language that uses + for addition + * and * for multiplication and has defined variables in the context + * in which this will be evaluated that specify which bank member is + * desired for this reactor instance and any of its parents that is + * a bank. The names of these variables need to be values returned + * by {@link bankIndex(ReactorInstance, String)}), where the second + * argument is the specified suffix. + * + * If this is a top-level reactor, this returns "0". + * + * @param prefix The prefix used for index variables for bank members. + * @param suffix The suffix to use for bank indices, or null to use the default. + */ + static public String indexExpression(ReactorInstance instance, String suffix) { if (instance.getDepth() == 0) return("0"); if (instance.isBank()) { return( // Position of the bank member relative to the bank. - bankIndex(instance) + " * " + instance.getNumReactorInstances() + bankIndex(instance, suffix) + " * " + instance.getNumReactorInstances() // Position of the bank within its parent. + " + " + instance.getIndexOffset() // Position of the parent. - + " + " + indexExpression(instance.getParent()) + + " + " + indexExpression(instance.getParent(), suffix) ); } else { return( // Position within the parent. instance.getIndexOffset() // Position of the parent. - + " + " + indexExpression(instance.getParent()) + + " + " + indexExpression(instance.getParent(), suffix) ); } } @@ -182,18 +214,20 @@ static public String indexExpression(ReactorInstance instance) { * @param port The port. * @param isNested True to return a reference relative to the parent's parent. * @param includeChannelIndex True to include the channel index at the end. + * @param suffix An optional suffix to append to the struct variable name. */ static public String portRef( - PortInstance port, boolean isNested, boolean includeChannelIndex + PortInstance port, boolean isNested, boolean includeChannelIndex, String suffix ) { String channel = ""; + if (suffix == null) suffix = ""; if (port.isMultiport() && includeChannelIndex) { channel = "[" + channelIndex(port) + "]"; } if (isNested) { - return reactorRefContained(port.getParent()) + "." + port.getName() + channel; + return reactorRefContained(port.getParent(), suffix) + "." + port.getName() + channel; } else { - String sourceStruct = CUtil.reactorRef(port.getParent()); + String sourceStruct = CUtil.reactorRef(port.getParent(), suffix); return sourceStruct + "->_lf_" + port.getName() + channel; } } @@ -204,11 +238,25 @@ static public String portRef( * port's parent. This is used when an input port triggers a reaction * in the port's parent or when an output port is written to by * a reaction in the port's parent. - * This is equivalent to calling `portRef(port, false, true)`. + * This is equivalent to calling `portRef(port, false, true, null)`. * @param port An instance of the port to be referenced. */ static public String portRef(PortInstance port) { - return portRef(port, false, true); + return portRef(port, false, true, null); + } + + /** + * Special case of {@link portRef(PortInstance, boolean, boolean)} + * that provides a reference to the port on the self struct of the + * port's parent. This is used when an input port triggers a reaction + * in the port's parent or when an output port is written to by + * a reaction in the port's parent. + * This is equivalent to calling `portRef(port, false, true, suffix)`. + * @param port An instance of the port to be referenced. + * @param suffix An optional suffix to append to the struct variable name. + */ + static public String portRef(PortInstance port, String suffix) { + return portRef(port, false, true, suffix); } /** @@ -217,9 +265,19 @@ static public String portRef(PortInstance port) { * @param port An instance of the port to be referenced. */ static public String portRefName(PortInstance port) { - return portRef(port, false, false); + return portRef(port, false, false, null); } + /** + * Return the portRef without the channel indexing. + * This is useful for deriving a reference to the _width variable. + * @param port An instance of the port to be referenced. + * @param suffix An optional suffix to append to the struct variable name. + */ + static public String portRefName(PortInstance port, String suffix) { + return portRef(port, false, false, suffix); + } + /** * Special case of {@link portRef(PortInstance, boolean, boolean)} * that provides a reference to the port on the self struct of the @@ -232,7 +290,23 @@ static public String portRefName(PortInstance port) { * @param port The port. */ static public String portRefNested(PortInstance port) { - return portRef(port, true, true); + return portRef(port, true, true, null); + } + + /** + * Special case of {@link portRef(PortInstance, boolean, boolean)} + * that provides a reference to the port on the self struct of the + * parent of the port's parent. This is used when an input port + * is written to by a reaction in the parent of the port's parent, + * or when an output port triggers a reaction in the parent of the + * port's parent. + * This is equivalent to calling `portRef(port, true, true)`. + * + * @param port The port. + * @param suffix An optional suffix to append to the struct variable name. + */ + static public String portRefNested(PortInstance port, String suffix) { + return portRef(port, true, true, suffix); } /** @@ -248,7 +322,24 @@ static public String portRefNested(PortInstance port) { * @param port The port. */ static public String portRefNestedName(PortInstance port) { - return portRef(port, true, false); + return portRef(port, true, false, null); + } + + /** + * Special case of {@link portRef(PortInstance, boolean, boolean)} + * that provides a reference to the port on the self struct of the + * parent of the port's parent, but without the channel indexing, + * even if it is a multiport. This is used when an input port + * is written to by a reaction in the parent of the port's parent, + * or when an output port triggers a reaction in the parent of the + * port's parent. + * This is equivalent to calling `portRef(port, true, false)`. + * + * @param port The port. + * @param suffix An optional suffix to append to the struct variable name. + */ + static public String portRefNestedName(PortInstance port, String suffix) { + return portRef(port, true, false, suffix); } /** @@ -267,8 +358,19 @@ static public String reactionRef(ReactionInstance reaction) { * @return A name to use for a pointer to the self struct. */ static public String reactorRef(ReactorInstance instance) { - var result = instance.uniqueID() + "_self"; - return result; + return reactorRef(instance, null); + } + + /** + * Return a name for a pointer to the "self" struct of the specified + * reactor instance. + * @param instance The reactor instance. + * @return A name to use for a pointer to the self struct. + * @param suffix An optional suffix to append to the struct variable name. + */ + static public String reactorRef(ReactorInstance instance, String suffix) { + if (suffix == null) suffix = ""; + return instance.uniqueID() + "_self" + suffix; } /** @@ -284,7 +386,24 @@ static public String reactorRef(ReactorInstance instance) { * @param reactor The contained reactor. */ static public String reactorRefContained(ReactorInstance reactor) { - String result = reactorRef(reactor.getParent()) + "->_lf_" + reactor.getName(); + return reactorRefContained(reactor, null); + } + + /** + * For situations where a reaction reacts to or reads from an output + * of a contained reactor or sends to an input of a contained reactor, + * then the container's self struct will have a field + * (or an array of fields if the contained reactor is a bank) that is + * a struct with fields corresponding to those inputs and outputs. + * This method returns a reference to that struct or array of structs. + * Note that the returned reference is not to the self struct of the + * contained reactor. Use {@link reactorRef(ReactorInstance)} for that. + * + * @param reactor The contained reactor. + * @param suffix An optional suffix to append to the struct variable name. + */ + static public String reactorRefContained(ReactorInstance reactor, String suffix) { + String result = reactorRef(reactor.getParent(), suffix) + "->_lf_" + reactor.getName(); if (reactor.isBank()) { result += "[" + bankIndex(reactor) + "]"; } From 45d6c93b8a6616408944117b233418a9f45dded5 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 13 Dec 2021 17:29:36 -0800 Subject: [PATCH 073/221] Interrim checking towards supporting deeply nested banks. Problem now is that Ranges are not associated with Connections. --- .../synthesis/LinguaFrancaSynthesis.xtend | 2 +- .../org/lflang/generator/GeneratorBase.xtend | 21 +- .../org/lflang/generator/NamedInstance.java | 14 + .../org/lflang/generator/PortInstance.java | 367 ++---------------- .../lflang/generator/ReactionInstance.java | 10 +- .../generator/ReactionInstanceGraph.xtend | 4 +- .../org/lflang/generator/ReactorInstance.java | 96 +++-- .../org/lflang/generator/c/CGenerator.xtend | 180 ++++----- .../src/org/lflang/graph/TopologyGraph.java | 4 +- 9 files changed, 184 insertions(+), 514 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 702eeba0d5..a6cdcb5ac1 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -742,7 +742,7 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } else { outputPorts.get(leftPort.parent, leftPort) } - val rightPort = rightRange.getPort(); + val rightPort = rightRange.instance; val target = if (rightPort.parent == reactorInstance) { parentOutputPorts.get(rightPort) } else { diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 96d4b3599d..a3b5d83416 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -34,11 +34,8 @@ import java.util.LinkedHashSet import java.util.List import java.util.Map import java.util.Set -import java.util.regex.Pattern import java.util.stream.Collectors import org.eclipse.core.resources.IMarker -import org.eclipse.core.resources.IResource -import org.eclipse.core.resources.ResourcesPlugin import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IFileSystemAccess2 import org.eclipse.xtext.generator.IGeneratorContext @@ -50,7 +47,6 @@ import org.lflang.InferredType import org.lflang.MainConflictChecker import org.lflang.Target import org.lflang.TargetConfig -import org.lflang.TargetConfig.Mode import org.lflang.TargetProperty.CoordinationType import org.lflang.TimeValue import org.lflang.federated.FedASTUtils @@ -58,19 +54,16 @@ import org.lflang.federated.FederateInstance import org.lflang.federated.serialization.SupportedSerializers import org.lflang.graph.InstantiationGraph import org.lflang.lf.Action +import org.lflang.lf.Connection import org.lflang.lf.Delay import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory import org.lflang.lf.Model -import org.lflang.lf.Parameter -import org.lflang.lf.Port import org.lflang.lf.Reaction import org.lflang.lf.Reactor -import org.lflang.lf.StateVar import org.lflang.lf.VarRef import static extension org.lflang.ASTUtils.* -import static extension org.lflang.JavaAstUtils.* /** * Generator base class for specifying core functionality @@ -1053,10 +1046,10 @@ abstract class GeneratorBase extends JavaGeneratorBase { // Need ReactorInstance support for ranges. // val sourceBankIndex = (source.getPort().parent.bankIndex >= 0) ? source.getPort().parent.bankIndex : 0 val sourceBankIndex = 0 - val sourceFederate = federatesByInstantiation.get(source.getPort().parent.definition).get(sourceBankIndex); + val sourceFederate = federatesByInstantiation.get(source.instance.parent.definition).get(sourceBankIndex); // Set up dependency information. - var connection = source.connection; + var connection = null as Connection// FIXME FIXME source.connection; if (connection === null) { // This should not happen. errorReporter.reportError(input.definition, "Unexpected error. Cannot find input connection for port") @@ -1095,14 +1088,14 @@ abstract class GeneratorBase extends JavaGeneratorBase { // Make one communication for each channel. // FIXME: There is an opportunity for optimization here by aggregating channels. - for (var i = 0; i < source.getTotalWidth(); i++) { + for (var i = 0; i < source.totalWidth; i++) { FedASTUtils.makeCommunication( - source.getPort(), + source.instance, input, connection, sourceFederate, 0, // FIXME: source.getPort().parent.bankIndex, - source.startChannel + i, + source.start + i, destinationFederate, 0, // FIXME: input.parent.bankIndex, channel + i, @@ -1110,7 +1103,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { targetConfig.coordination ); } - channel += source.getTotalWidth(); + channel += source.totalWidth; } } } diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index fdfd0ee789..02799c0753 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -122,6 +122,13 @@ public ReactorInstance getParent() { return parent; } + /** + * Return the width of this port, which in this base class is 1. + */ + public int getWidth() { + return width; + } + /** * Return true if this instance has the specified parent * (possibly indirectly, anywhere up the hierarchy). @@ -248,6 +255,13 @@ public String uniqueID() { */ HashMap uniqueIDCount; + /** + * The width of this instance. This is 1 for everything + * except a PortInstance representing a multiport and a + * ReactorInstance representing a bank. + */ + int width = 1; + ////////////////////////////////////////////////////// //// Protected methods. diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index c6ff555b7b..cb11c86900 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -26,14 +26,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; -import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.PriorityQueue; -import java.util.Set; import org.lflang.ErrorReporter; -import org.lflang.lf.Connection; import org.lflang.lf.Input; import org.lflang.lf.Output; import org.lflang.lf.Parameter; @@ -92,14 +89,14 @@ public void clearCaches() { clearingCaches = true; try { if (eventualSourceRanges != null) { - for (Range sourceRange : eventualSourceRanges) { - sourceRange.getPort().clearCaches(); + for (Range sourceRange : eventualSourceRanges) { + sourceRange.instance.clearCaches(); } } if (eventualDestinationRanges != null) { for (SendRange sendRange : eventualDestinationRanges) { - for (Range destinationRange : sendRange.destinations) { - destinationRange.getPort().clearCaches(); + for (Range destinationRange : sendRange.destinations) { + destinationRange.instance.clearCaches(); } } } @@ -140,7 +137,7 @@ public List eventualDestinations() { } // Construct the full range for this port. - Range range = new Range(0, 0, width * parent.width(), false, null); + Range range = new Range.Port(this); eventualDestinationRanges = eventualDestinations(range); return eventualDestinationRanges; } @@ -159,7 +156,7 @@ public List eventualDestinations() { * reactions are not listed. * @param srcRange The source range. */ - public static List eventualDestinations(Range srcRange) { + public static List eventualDestinations(Range srcRange) { // Getting the destinations is more complex than getting the sources // because of multicast, where there is more than one connection statement @@ -172,18 +169,16 @@ public static List eventualDestinations(Range srcRange) { List result = new ArrayList(); PriorityQueue queue = new PriorityQueue(); - PortInstance srcPort = srcRange.getPort(); + PortInstance srcPort = srcRange.instance; // Start with, if this port has dependent reactions, then add it to // every range of the result. - if (!srcRange.getPort().dependentReactions.isEmpty()) { + if (!srcRange.instance.dependentReactions.isEmpty()) { // This will be the final result if there are no connections. - SendRange candidate = srcPort.new SendRange( - srcRange.startChannel, - srcRange.startBank, - srcRange.totalWidth, - srcRange.interleaved, - srcRange.connection + SendRange candidate = new SendRange( + srcRange.instance, + srcRange.start, + srcRange.width ); candidate.destinations.add(srcRange); result.add(candidate); @@ -192,18 +187,18 @@ public static List eventualDestinations(Range srcRange) { // Start with ports that are downstream of the range. int srcWidthCovered = 0; int depWidthCovered = 0; - Iterator dependentPorts = srcPort.dependentPorts.iterator(); + Iterator> dependentPorts = srcPort.dependentPorts.iterator(); if (dependentPorts.hasNext()) { - Range dep = dependentPorts.next(); - while(srcWidthCovered < srcRange.getTotalWidth()) { - if (srcRange.getStartOffset() >= depWidthCovered + dep.totalWidth) { + Range dep = dependentPorts.next(); + while(srcWidthCovered < srcRange.totalWidth) { + if (srcRange.start >= depWidthCovered + dep.totalWidth) { // Destination is fully before this range. depWidthCovered += dep.totalWidth; if (!dependentPorts.hasNext()) break; // This should be an error. dep = dependentPorts.next(); continue; } - if (depWidthCovered >= srcRange.getStartOffset() + srcRange.totalWidth) { + if (depWidthCovered >= srcRange.start + srcRange.totalWidth) { // Source range is covered. We are finished. break; } @@ -211,10 +206,10 @@ public static List eventualDestinations(Range srcRange) { // Get a new range that possibly subsets the target range. // Argument is guaranteed by above checks to be less than // dep.totalWidth, so the result will not be null. - Range subDep = dep.tail(depWidthCovered - srcRange.getStartOffset()); + Range subDep = dep.tail(depWidthCovered - srcRange.start); // The following argument is guaranteed to be greater than // depWidthCovered - srcRange.getStartOffset(). - subDep = subDep.truncate(srcRange.getTotalWidth()); + subDep = subDep.head(srcRange.totalWidth); // At this point, dep is the subrange of the dependent port of interest. // Recursively get the send ranges of that destination port. @@ -223,11 +218,12 @@ public static List eventualDestinations(Range srcRange) { // For each returned SendRange, convert it to a SendRange // for the srcRange port rather than the dep port. for (SendRange dstSend : dstSendRanges) { + // FIXME: Need to figure out interleaved level! queue.add(dstSend.newSendRange(srcRange)); } depWidthCovered += subDep.totalWidth; srcWidthCovered += subDep.totalWidth; - if (dep.getStartOffset() + dep.totalWidth <= subDep.getStartOffset() + subDep.totalWidth) { + if (dep.start + dep.totalWidth <= subDep.start + subDep.totalWidth) { // dep range is exhausted. Get another one. if (!dependentPorts.hasNext()) break; // This should be an error. dep = dependentPorts.next(); @@ -245,17 +241,17 @@ public static List eventualDestinations(Range srcRange) { result.add(candidate); break; } - if (candidate.getStartOffset() == next.getStartOffset()) { + if (candidate.start == next.start) { // Ranges have the same starting point. Need to merge them. - if (candidate.getTotalWidth() <= next.getTotalWidth()) { + if (candidate.totalWidth <= next.totalWidth) { // Can use all of the channels of candidate. // Import the destinations of next and split it. candidate.destinations.addAll(next.destinations); - if (candidate.getTotalWidth() < next.getTotalWidth()) { + if (candidate.totalWidth < next.totalWidth) { // The next range has more channels connected to this sender. - next = (SendRange)next.tail(candidate.getTotalWidth()); + next = (SendRange)next.tail(candidate.totalWidth); // Truncate the destinations just imported. - candidate = candidate.truncate(candidate.getTotalWidth()); + candidate = candidate.head(candidate.totalWidth); } else { // We are done with next and can discard it. next = queue.poll(); @@ -269,7 +265,7 @@ public static List eventualDestinations(Range srcRange) { } else { // Because the result list is sorted, next starts at // a higher channel than candidate. - if (candidate.getStartOffset() + candidate.getTotalWidth() <= next.getStartOffset()) { + if (candidate.start + candidate.start <= next.start) { // Can use candidate as is and make next the new candidate. result.add(candidate); candidate = next; @@ -277,8 +273,8 @@ public static List eventualDestinations(Range srcRange) { } else { // Ranges overlap. Can use a truncated candidate and make its // truncated version the new candidate. - result.add(candidate.truncate(next.getStartOffset())); - candidate = (SendRange)candidate.tail(next.getStartOffset()); + result.add(candidate.head(next.start)); + candidate = (SendRange)candidate.tail(next.start); } } } @@ -289,7 +285,7 @@ public static List eventualDestinations(Range srcRange) { /** * Return a list of ports that send data to this port annotated with the channel * and bank ranges of each source port. If this port is directly written to by - * one more more reactions, then it is its own eventual source an only this port + * one more more reactions, then it is its own eventual source and only this port * will be represented in the result. * * If this is not a multiport and is not within a bank, then the list will have @@ -300,16 +296,17 @@ public static List eventualDestinations(Range srcRange) { * The ports listed are only ports that are written to by reactions, * not relay ports that the data may go through on the way. */ - public List eventualSources() { + public List> eventualSources() { if (eventualSourceRanges == null) { // Cached result has not been created. - eventualSourceRanges = new ArrayList(); + eventualSourceRanges = new ArrayList>(); if (!dependsOnReactions.isEmpty()) { - eventualSourceRanges.add(new Range(0, 0, width * parent.width(), false, null)); + eventualSourceRanges.add(new Range.Port(this)); } else { - for (Range sourceRange : dependsOnPorts) { - eventualSourceRanges.addAll(sourceRange.getPort().eventualSources()); + for (Range sourceRange : dependsOnPorts) { + // FIXME FIXME This needs to use the sourceRange! + eventualSourceRanges.addAll(sourceRange.instance.eventualSources()); } } } @@ -320,7 +317,7 @@ public List eventualSources() { * Return the list of downstream ports that are connected to this port * or an empty list if there are none. */ - public List getDependentPorts() { + public List> getDependentPorts() { return dependentPorts; } @@ -330,17 +327,10 @@ public List getDependentPorts() { * For an ordinary port, this list will have length 0 or 1. * For a multiport, it can have a larger size. */ - public List getDependsOnPorts() { + public List> getDependsOnPorts() { return dependsOnPorts; } - /** - * Return the width of this port, which in this base class is 1. - */ - public int getWidth() { - return width; - } - /** * Return true if the port is an input. */ @@ -398,7 +388,7 @@ public String toString() { * by the validator). Each channel of this port will be broadcast * to N recipients. */ - List dependentPorts = new ArrayList(); + List> dependentPorts = new ArrayList>(); /** * Upstream ports that are connected directly to this port, if there are any. @@ -406,18 +396,11 @@ public String toString() { * For a multiport, it can have a larger size. * This initially has capacity 1 because that is by far the most common case. */ - List dependsOnPorts = new ArrayList(1); + List> dependsOnPorts = new ArrayList>(1); /** Indicator of whether this is a multiport. */ boolean isMultiport = false; - /** - * The width of this port instance. - * For an ordinary port, this is 1. - * For a multiport, it may be larger than 1. - */ - int width = 1; - ////////////////////////////////////////////////////// //// Private methods. @@ -468,276 +451,8 @@ private void setInitialWidth(ErrorReporter errorReporter) { private List eventualDestinationRanges; /** Cached list of source ports with channel ranges. */ - private List eventualSourceRanges; + private List> eventualSourceRanges; /** Indicator that we are clearing the caches. */ private boolean clearingCaches = false; - - ////////////////////////////////////////////////////// - //// Inner classes. - - /** - * Class representing a range of channels of this port that broadcast to some - * number of destination ports' channels. All ranges have the same - * width, but not necessarily the same start indices. - * This class extends its base class with a list of destination channel ranges, - * all of which have the same width as this channel range. - * It also includes a field representing the number of destination - * reactors. - */ - public class SendRange extends Range { - - public SendRange( - int startChannel, int startBank, int totalWidth, boolean interleaved, Connection connection - ) { - super(startChannel, startBank, totalWidth, interleaved, connection); - } - - public int getNumberOfDestinationReactors() { - if (_numberOfDestinationReactors < 0) { - // Has not been calculate before. Calculate now. - Set destinations = new HashSet(); - for (Range destination : this.destinations) { - destinations.add(destination.getPort().getParent()); - } - _numberOfDestinationReactors = destinations.size(); - } - return _numberOfDestinationReactors; - } - - public final List destinations = new ArrayList(); - - /** - * Override the base class to return a SendRange where - * each of the destinations is the tail of the original destinations. - * @param offset The number of channels to consume. - */ - @Override - public Range tail(int offset) { - // NOTE: Cannot use the superclass because it returns a Range, not a SendRange. - if (offset == 0) return this; - if (offset >= totalWidth) return null; - - int channelWidth = PortInstance.this.width; - int bankWidth = PortInstance.this.parent.width(); - - int banksToConsume, channelsToConsume; - if (interleaved) { - banksToConsume = offset / bankWidth; - channelsToConsume = offset % bankWidth; - } else { - banksToConsume = offset / channelWidth; - channelsToConsume = offset % channelWidth; - } - SendRange result = new SendRange( - startChannel + channelsToConsume, - startBank + banksToConsume, - totalWidth - offset, - interleaved, - connection - ); - - for (Range destination : destinations) { - result.destinations.add(destination.tail(offset)); - } - return result; - } - - /** - * Return a new SendRange that is like this one, but - * converted to belong to the port in the specified range. - * If the total widths are not - * the same, then the minimum of the two widths is returned. - * If the specified srcRange is interleaved, then the interleaved - * property of each of the returned destinations will be toggled. - * @param srcRange A new source range. - */ - protected SendRange newSendRange(Range srcRange) { - SendRange reference = this; - if (srcRange.totalWidth > totalWidth) { - srcRange = srcRange.truncate(totalWidth); - } else if (srcRange.totalWidth < totalWidth) { - reference = truncate(srcRange.totalWidth); - } - SendRange result = srcRange.getPort().new SendRange( - srcRange.startChannel, srcRange.startBank, totalWidth, interleaved, connection - ); - if (srcRange.interleaved) { - // Toggle the destination interleaved status. - for (Range dst : reference.destinations) { - result.destinations.add(dst.toggleInterleaved()); - } - } else { - result.destinations.addAll(reference.destinations); - } - return result; - } - - /** - * Override the base class to also truncate the destinations. - * @param newWidth The new width. - */ - @Override - protected SendRange truncate(int newWidth) { - if (newWidth >= totalWidth) return this; - SendRange result = new SendRange(startChannel, startBank, newWidth, interleaved, connection); - for (Range destination : destinations) { - result.destinations.add(destination.truncate(newWidth)); - } - return result; - } - - private int _numberOfDestinationReactors = -1; // Never access this directly. - } - - /** - * Class representing a range of channels of the enclosing port instance. - * If the enclosing port instance is not a multiport, this range will - * be (0,1). - */ - public class Range implements Comparable { - - public Range( - int startChannel, - int startBank, - int totalWidth, - boolean interleaved, - Connection connection - ) { - int widthLimit = width * parent.width(); - // Some targets determine widths at runtime, in which case a - // width of 0 is reported here. Tolerate that. - if (totalWidth > 0 - && (startChannel < 0 || startChannel >= widthLimit - || totalWidth < 0 || startChannel + totalWidth > widthLimit)) { - throw new RuntimeException("Invalid range of port channels."); - } - this.startChannel = startChannel; - this.startBank = startBank; - this.totalWidth = totalWidth; - this.interleaved = interleaved; - this.connection = connection; - } - public final int startChannel; - public final int startBank; - public final boolean interleaved; - public final Connection connection; - public PortInstance getPort() { - return PortInstance.this; - } - - /** - * Compare the ranges by just comparing their start offset - * and then by total width. - */ - @Override - public int compareTo(Range o) { - if (getStartOffset() < o.getStartOffset()) { - return -1; - } else if (getStartOffset() == o.getStartOffset()) { - if (totalWidth < o.totalWidth) { - return -1; - } else if (totalWidth == o.totalWidth) { - return 0; - } else { - return 1; - } - } else { - return 1; - } - } - - /** - * Return the starting offset of the range (i.e., - * how many total banks and channels come before - * it within its bank/multiport). - */ - public int getStartOffset() { - if (interleaved) { - return PortInstance.this.parent.width() * startChannel + startBank; - } else { - return PortInstance.this.getWidth() * startBank + startChannel; - } - } - - /** - * Return the total width of the range. - */ - public int getTotalWidth() { - return totalWidth; - } - - /** - * Return a new range that represents the leftover channels - * starting at the specified offset. Depending on - * whether this range is interleaved, this will consume from - * multiport channels first (if not interleaved) or banks first - * (if interleaved). If the offset is greater than or equal to - * the total width, then this returns null. - * If this offset is 0 then this returns this width unmodified. - * @param offset The number of channels to consume. - */ - public Range tail(int offset) { - if (offset == 0) return this; - if (offset >= totalWidth) return null; - - int channelWidth = PortInstance.this.width; - int bankWidth = PortInstance.this.parent.width(); - - int banksToConsume, channelsToConsume; - if (interleaved) { - banksToConsume = offset / bankWidth; - channelsToConsume = offset % bankWidth; - } else { - banksToConsume = offset / channelWidth; - channelsToConsume = offset % channelWidth; - } - return new Range( - startChannel + channelsToConsume, - startBank + banksToConsume, - totalWidth - offset, - interleaved, - connection - ); - } - - /** - * Return a new range identical to this one except with the - * interleaved flag toggled. - */ - public Range toggleInterleaved() { - return new Range( - startChannel, - startBank, - totalWidth, - !interleaved, - connection - ); - } - - @Override - public String toString() { - return String.format( - "%s(channel %d, bank %d, width %d)", - PortInstance.this.getFullName(), - startChannel, - startBank, - totalWidth - ); - } - - /** - * Return a new range that is identical to this range but - * with a reduced total width to the specified width. - * If the new width is greater than or equal to the width - * of this range, then return this range. - * @param newWidth The new width. - */ - protected Range truncate(int newWidth) { - if (newWidth >= totalWidth) return this; - return new Range(startChannel, startBank, newWidth, interleaved, connection); - } - - protected int totalWidth; - } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 4a17635bd9..2dff7d167a 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -320,12 +320,12 @@ public Set dependentReactions() { // Next, add reactions that get data from this one via a port. for (TriggerInstance effect : effects) { if (effect instanceof PortInstance) { - for (PortInstance.SendRange senderRange + for (SendRange senderRange : ((PortInstance)effect).eventualDestinations()) { - for (PortInstance.Range destinationRange + for (Range destinationRange : senderRange.destinations) { dependentReactionsCache.addAll( - destinationRange.getPort().dependentReactions); + destinationRange.instance.dependentReactions); } } } @@ -361,9 +361,9 @@ public Set dependsOnReactions() { for (TriggerInstance source : sources) { if (source instanceof PortInstance) { // First, add reactions that send data through an intermediate port. - for (PortInstance.Range senderRange + for (Range senderRange : ((PortInstance)source).eventualSources()) { - dependsOnReactionsCache.addAll(senderRange.getPort().dependsOnReactions); + dependsOnReactionsCache.addAll(senderRange.instance.dependsOnReactions); } // Then, add reactions that send directly to this port. dependsOnReactionsCache.addAll(source.dependsOnReactions); diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend index 0b3ecda5d7..a534690b36 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend @@ -302,7 +302,7 @@ class ReactionInstanceGraph extends DirectedGraph { port.dependsOnReactions.forEach[this.addEdge(reaction, it)] // Reactions in upstream reactors. for (upstreamPort : port.dependsOnPorts) { - addUpstreamReactions(upstreamPort.getPort(), reaction) + addUpstreamReactions(upstreamPort.instance, reaction) } } @@ -318,7 +318,7 @@ class ReactionInstanceGraph extends DirectedGraph { port.dependentReactions.forEach[this.addEdge(it, reaction)] // Reactions in downstream reactors. for (downstreamPort : port.dependentPorts) { - addDownstreamReactions(downstreamPort.getPort(), reaction) + addDownstreamReactions(downstreamPort.instance, reaction) } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index a351c5b357..9157e1b2a9 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -300,8 +300,8 @@ public TriggerInstance getShutdownTrigger() { * as returned by width(). */ public int getTotalNumReactorInstances() { - if (width() < 0 || numReactorInstances < 0) return -1; - return width() * numReactorInstances; + if (width < 0 || numReactorInstances < 0) return -1; + return width * numReactorInstances; } /** @@ -575,21 +575,6 @@ public Set transitiveClosure(PortInstance source) { transitiveClosure(source, result); return result; } - - /** - * If this is a bank of reactors, return the width or -1 if it cannot - * be determined. Otherwise, return 1. - * @return The width, or -1 if it cannot be determined. - */ - public int width() { - WidthSpec widthSpec = definition.getWidthSpec(); - if (widthSpec != null) { - // We need the instantiations list of the containing reactor, - // not this one. - return ASTUtils.width(widthSpec, parent.instantiations()); - } - return 1; - } ////////////////////////////////////////////////////// //// Protected fields. @@ -724,8 +709,8 @@ protected void transitiveClosure( if (source.isInput()) { destinations.add(source); } - for (PortInstance.Range dst : source.dependentPorts) { - PortInstance destination = dst.getPort(); + for (Range dst : source.dependentPorts) { + PortInstance destination = dst.instance; destinations.add(destination); if (destination.isInput()) { // Destination may have further destinations lower in the hierarchy. @@ -790,6 +775,8 @@ private ReactorInstance( return; } + setInitialWidth(); + // Apply overrides and instantiate parameters for this reactor instance. for (Parameter parameter : ASTUtils.allParameters(reactorDefinition)) { this.parameters.add(new ParameterInstance(parameter, this)); @@ -865,15 +852,11 @@ private ReactorInstance( * @param dst The destination range. */ private void connectPortInstances( - PortInstance.Range src, - PortInstance.Range dst + Range src, + Range dst ) { - if (src.interleaved) { - dst = dst.toggleInterleaved(); - src = src.toggleInterleaved(); - } - src.getPort().dependentPorts.add(dst); - dst.getPort().dependsOnPorts.add(src); + src.instance.dependentPorts.add(dst); + dst.instance.dependsOnPorts.add(src); } /** @@ -887,9 +870,9 @@ private void connectPortInstances( */ private void establishPortConnections() { for (Connection connection : ASTUtils.allConnections(reactorDefinition)) { - List leftPorts = listPortInstances(connection.getLeftPorts()); - Iterator srcRanges = leftPorts.iterator(); - Iterator dstRanges = listPortInstances(connection.getRightPorts()).iterator(); + List> leftPorts = listPortInstances(connection.getLeftPorts()); + Iterator> srcRanges = leftPorts.iterator(); + Iterator> dstRanges = listPortInstances(connection.getRightPorts()).iterator(); // Check for empty lists. if (!srcRanges.hasNext()) { @@ -902,11 +885,11 @@ private void establishPortConnections() { return; } - PortInstance.Range src = srcRanges.next(); - PortInstance.Range dst = dstRanges.next(); + Range src = srcRanges.next(); + Range dst = dstRanges.next(); while(true) { - if (dst.getTotalWidth() == src.getTotalWidth()) { + if (dst.totalWidth == src.totalWidth) { connectPortInstances(src, dst); if (!dstRanges.hasNext()) { if (srcRanges.hasNext()) { @@ -925,20 +908,20 @@ private void establishPortConnections() { } dst = dstRanges.next(); src = srcRanges.next(); - } else if (dst.getTotalWidth() < src.getTotalWidth()) { + } else if (dst.totalWidth < src.totalWidth) { // Split the left range in two. - connectPortInstances(src.truncate(dst.getTotalWidth()), dst); - src = src.tail(dst.getTotalWidth()); + connectPortInstances(src.head(dst.totalWidth), dst); + src = src.tail(dst.totalWidth); if (!dstRanges.hasNext()) { reporter.reportWarning(connection, "Source is wider than the destination. Outputs will be lost."); break; } dst = dstRanges.next(); - } else if (src.getTotalWidth() < dst.getTotalWidth()) { + } else if (src.totalWidth < dst.totalWidth) { // Split the right range in two. - connectPortInstances(src, dst.truncate(src.getTotalWidth())); - dst = dst.tail(src.getTotalWidth()); + connectPortInstances(src, dst.head(src.totalWidth)); + dst = dst.tail(src.totalWidth); if (!srcRanges.hasNext()) { if (connection.isIterated()) { srcRanges = leftPorts.iterator(); @@ -969,8 +952,8 @@ private void establishPortConnections() { * With the interleaved marking, the returned range represents the sequence * `[b0.m0, b1.m0, b0.m1, b1.m1]`. Both ranges will have width 4. */ - private List listPortInstances(List references) { - List result = new ArrayList(); + private List> listPortInstances(List references) { + List> result = new ArrayList>(); for (VarRef portRef : references) { // Simple error checking first. if (!(portRef.getVariable() instanceof Port)) { @@ -989,24 +972,33 @@ private List listPortInstances(List references) { if (reactor != null) { PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); - // If the reactor is a contained reactor that is a bank, get its width. - int bankWidth = 1; - if (reactor != this) bankWidth = reactor.width(); - - Connection connection = null; - if (portRef.eContainer() instanceof Connection) { - connection = (Connection)portRef.eContainer(); + Range range = new Range.Port(portInstance); + if (portRef.isInterleaved()) { + // Toggle interleaving at the depth of this reactor, which + // contains the Connection. This is not necessarily the reactor + // that is the parent of the port. + range = range.toggleInterleaved(this); } - - PortInstance.Range range = portInstance.new Range( - 0, 0, portInstance.width * bankWidth, portRef.isInterleaved(), connection - ); result.add(range); } } return result; } + /** + * If this is a bank of reactors, set the width. + * It will be set to -1 if it cannot + * be determined. + */ + private void setInitialWidth() { + WidthSpec widthSpec = definition.getWidthSpec(); + if (widthSpec != null) { + // We need the instantiations list of the containing reactor, + // not this one. + width = ASTUtils.width(widthSpec, parent.instantiations()); + } + } + ////////////////////////////////////////////////////// //// Private fields. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 3659364ee2..66f02d64de 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -68,8 +68,10 @@ import org.lflang.generator.JavaGeneratorUtils import org.lflang.generator.NamedInstance import org.lflang.generator.ParameterInstance import org.lflang.generator.PortInstance +import org.lflang.generator.Range import org.lflang.generator.ReactionInstance import org.lflang.generator.ReactorInstance +import org.lflang.generator.SendRange import org.lflang.generator.TimerInstance import org.lflang.generator.TriggerInstance import org.lflang.lf.Action @@ -4881,94 +4883,58 @@ class CGenerator extends GeneratorBase { /** * Start a scoped block for the specified range. * - * If the port of the range is contained by a bank, then this - * generates an iteration that iterates over the range. + * If the port of the range is contained by any banks, then this + * generates iterations over the range of bank members. * If the port is also a multiport, then the iteration is over bank * members and channels, where the order depends on whether the * range is interleaved. - * If the range is interleaved, then it iterates first over - * channels (with index given by {@link CUtil.channelIndex(PortInstance)}) - * and then over banks (with - * index given by {@link CUtil.bankIndex(ReactorInstance)}. - * Otherwise, it iterates first over banks and then over - * channels. - * - * If the port is a multiport but its container is not a bank, - * then the generated iteration is only over channels (with - * index variable named given by {@link CUtil.channelIndex(PortInstance)}). - * - * If the port is not a multiport nor is its parent a bank, - * then this just generates a scoped section that defines - * a pointer to the self struct of the parent of the range's port. + * The order of the iterations is specified by the range. * * In all cases, it stops the iteration when the * total width of the range has been covered. * * This must be followed by a call to - * {@link endScopedRangeBlock(StringBuilder, PortInstance.Range)}. + * {@link endScopedRangeBlock(StringBuilder, Range)}. * * @param builder The string builder into which to write. * @param range The send range. */ protected def void startScopedRangeBlock( - StringBuilder builder, PortInstance.Range range + StringBuilder builder, Range range ) { - val port = range.port; - val reactor = port.parent; + val port = range.instance; pr(builder, ''' - // Iterate over range of «range.port.fullName» with starting channel «range.startChannel», - // starting bank «range.startBank», and total width «range.totalWidth». + // Iterate over range of «port.fullName» with starting offset «range.start», + // and total width «range.totalWidth». ''') // The first creates a scope in which we can define a pointer to the self // struct without fear of redefining. startScopedBlock(builder); - val channelVariable = CUtil.channelIndex(port); - - if (reactor.isBank) { - val bankIndex = CUtil.bankIndex(reactor); - if (port.isMultiport) { - pr(builder, ''' - int range_count = 0; - int «channelVariable» = «range.startChannel»; - int «bankIndex» = «range.startBank»; - while (range_count < «range.totalWidth») { - ''') - indent(builder); - defineSelfStruct(builder, reactor); + // Include range_count variable so generated code can safely use it. + pr(builder, ''' + int range_count = 0; + '''); + + val iterationOrder = range.iterationOrder(); + + // We need to iterate backwards over the iteration order. + for (var i = iterationOrder.size() - 1; i >= 0; i--) { + val instance = iterationOrder.get(i); + if (instance === port) { + startChannelIteration(builder, port); } else { - // Bank, but not a multiport. - pr(builder, ''' - // Send range covers bank member(s). Iterate over bank members. - int range_count = 0; - for (int «bankIndex» = «range.startBank»; «bankIndex» < «range.startBank» + «range.totalWidth»; «bankIndex»++) { - ''') - indent(builder); - pr(builder, '''int «channelVariable» = 0;'''); - defineSelfStruct(builder, reactor); + // Instance is a parent reactor. + startScopedBlock(builder, instance as ReactorInstance); } - } else if (port.isMultiport) { - // Reactor is not a bank, but port is a multiport. - defineSelfStruct(builder, reactor); - pr(builder, ''' - // Send range covers channels of a multiport. Iterate over channels. - int range_count = 0; - for (int «channelVariable» = «range.startChannel»; «channelVariable» < «range.startChannel» + «range.totalWidth»; «channelVariable»++) { - ''') - indent(builder); - } else { - // Not a multiport nor a bank. - // For consistent depth of nesting, generate a scoped block. - pr(builder, "{"); - indent(builder); - // Include range_count variable so generated code can safely use it. - pr(builder, ''' - int range_count = 0; - '''); - defineSelfStruct(builder, reactor); } + // Allow code to execute only if we are in range. + pr(builder, ''' + if (range_count >= «range.start») { + ''') + indent(builder); } /** @@ -4977,52 +4943,42 @@ class CGenerator extends GeneratorBase { * @param range The send range. */ protected def void endScopedRangeBlock( - StringBuilder builder, PortInstance.Range range + StringBuilder builder, Range range ) { - val port = range.port; - val reactor = port.parent; - val channelVariable = CUtil.channelIndex(port); - if (reactor.isBank && range.port.isMultiport) { - val bankIndex = CUtil.bankIndex(reactor); - if (range.interleaved) { - // Interleaved. Iterate over channels - // then bank members that are within the range. - pr(builder, ''' - range_count++; - «bankIndex»++; - if («bankIndex» >= «reactor.width») { - «channelVariable»++; - «bankIndex» = 0; - } - ''') - } else { - // Not interleaved. Iterate over the bank members - // then channels that are within the range. - pr(builder, ''' - range_count++; - «channelVariable»++; - if («channelVariable» >= «port.width») { - «bankIndex»++; - «channelVariable» = 0; - } - ''') - } - } else if (reactor.isBank || range.port.isMultiport) { + val port = range.instance; + + pr(builder, "}"); // End of predicate on range_count. + unindent(builder); + pr(builder, ''' + range_count++; + if (range_count >= «range.start» + «range.width») break; + ''') + + val iterationOrder = range.iterationOrder(); + + // We need to iterate backwards over the iteration order. + for (var i = iterationOrder.size() - 1; i >= 0; i--) { + val instance = iterationOrder.get(i); pr(builder, ''' - range_count++; + if (range_count >= «range.start» + «range.width») break; ''') + if (instance === port) { + endChannelIteration(builder, port); + } else { + // Instance is a parent reactor. + endScopedBlock(builder); + } } endScopedBlock(builder); - endScopedBlock(builder); } /** * Start a scoped block for the specified pair of ranges. This is like - * {@link startScopedRangeBlock(StringBuilder, PortInstance.Range), + * {@link startScopedRangeBlock(StringBuilder, Range), * except that it also defines bank and channel indices for the second * range and iterates over the minimum width of the two ranges. * This must be followed by a call to - * {@link endScopedRangeBlock(StringBuilder, PortInstance.Range, PortInstance.Range)}. + * {@link endScopedRangeBlock(StringBuilder, Range, Range)}. * * To allow for the possibility that the srcRange and dstRange * refer to ports with the same parent, the variables used to @@ -5037,15 +4993,15 @@ class CGenerator extends GeneratorBase { * @param dstRange The destination range. */ protected def void startScopedRangeBlock( - StringBuilder builder, PortInstance.Range srcRange, PortInstance.Range dstRange + StringBuilder builder, Range srcRange, Range dstRange ) { - val src = srcRange.port; + val src = srcRange.instance; val width = (srcRange.totalWidth <= dstRange.totalWidth)? srcRange.totalWidth : dstRange.totalWidth; pr(builder, ''' - // Iterate over range1 of «srcRange.port.fullName» with starting channel «srcRange.startChannel» - // and starting bank «srcRange.startBank», and range2 of «dstRange.port.fullName» with starting - // channel «dstRange.startChannel» and starting bank «dstRange.startBank», with total width «width». + // Iterate over range1 of «src.fullName» with starting offset «srcRange.start», + // and range2 of «dstRange.instance.fullName» with starting offset «dstRange.start», + // with total width «width». ''') // Define additional variables for range2. @@ -5059,12 +5015,12 @@ class CGenerator extends GeneratorBase { if (src.parent.isBank && src.isOutput) { // Need to specify a suffix to avoid a variable name collision. pr(builder, ''' - int «CUtil.bankIndex(src.parent, "_src")» = «srcRange.startBank»; + int «CUtil.bankIndex(src.parent, "_src")» = «srcRange.start»; ''') } if (src.isMultiport) { pr(builder, ''' - int «CUtil.channelIndex(src)» = «srcRange.startChannel»; + int «CUtil.channelIndex(src)» = «srcRange.start»; ''') } startScopedRangeBlock(builder, dstRange); @@ -5078,10 +5034,10 @@ class CGenerator extends GeneratorBase { * @param dstRange The destination range. */ protected def void endScopedRangeBlock( - StringBuilder builder, PortInstance.Range srcRange, PortInstance.Range dstRange + StringBuilder builder, Range srcRange, Range dstRange ) { - val src = srcRange.port; - val dst = dstRange.port; + val src = srcRange.instance; + val dst = dstRange.instance; val sfx = "_src"; // If an output port is triggering a reaction in its parent's parent, // then the destination and source may be the same port. In that case, @@ -5089,8 +5045,8 @@ class CGenerator extends GeneratorBase { if (dst != src) { // Interleaved status is normally stored in the destination only, // but just in case, calculate the XOR. - val interleaved = (srcRange.interleaved || dstRange.interleaved) - && !(srcRange.interleaved && dstRange.interleaved); + val interleaved = false; + // FIXME FIXME (srcRange.interleaved || dstRange.interleaved) && !(srcRange.interleaved && dstRange.interleaved); if (interleaved) { if (src.parent.isBank && src.isOutput) { pr(builder, ''' @@ -5187,7 +5143,7 @@ class CGenerator extends GeneratorBase { if (!currentFederate.contains(src.parent)) return; for (srcRange: src.eventualDestinations()) { for (dstRange : srcRange.destinations) { - val dst = dstRange.port; + val dst = dstRange.instance; val destStructType = variableStructType(dst) if (currentFederate.contains(dst.parent)) { @@ -6209,7 +6165,7 @@ class CGenerator extends GeneratorBase { // its width will be 1. // We generate the code to fill the triggers array first in a temporary code buffer, // so that we can simultaneously calculate the size of the total array. - for (PortInstance.SendRange range : port.eventualDestinations()) { + for (SendRange range : port.eventualDestinations()) { startScopedRangeBlock(code, range); @@ -6222,7 +6178,7 @@ class CGenerator extends GeneratorBase { var destRangeCount = 0; for (destinationRange : range.destinations) { foundDestinations = true; - val destination = destinationRange.getPort(); + val destination = destinationRange.instance; if (!declared.contains(destination.parent)) { // Need to declare the self struct of the destination's parent. diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index 49517ffabe..a8dc2c8deb 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -112,7 +112,7 @@ private void addEffects(ReactionInstance reaction) { addEdge(effect, reaction); PortInstance orig = (PortInstance) effect; orig.getDependentPorts().forEach(dest -> { - recordDependency(reaction, orig, dest.getPort(), dest.connection); + recordDependency(reaction, orig, dest.instance, dest.connection); }); } } @@ -132,7 +132,7 @@ private void addSources(ReactionInstance reaction) { addEdge(reaction, source); PortInstance dest = (PortInstance) source; dest.getDependsOnPorts().forEach(orig -> { - recordDependency(reaction, orig.getPort(), dest, orig.connection); + recordDependency(reaction, orig.instance, dest, orig.connection); }); } } From b0734e181192f2574a85a7c1be15483cf478cf31 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 14 Dec 2021 04:20:23 -0800 Subject: [PATCH 074/221] Checkpoint checkin with nested banks and everything compiling. --- .../org/lflang/generator/ChannelRange.java | 78 ++++ .../org/lflang/generator/GeneratorBase.xtend | 3 +- .../org/lflang/generator/PortInstance.java | 220 ++++++------ .../src/org/lflang/generator/Range.java | 333 ++++++++++++++++++ .../org/lflang/generator/ReactorInstance.java | 14 +- .../src/org/lflang/generator/SendRange.java | 168 +++++++++ .../org/lflang/generator/c/CGenerator.xtend | 105 +++--- 7 files changed, 767 insertions(+), 154 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/ChannelRange.java create mode 100644 org.lflang/src/org/lflang/generator/Range.java create mode 100644 org.lflang/src/org/lflang/generator/SendRange.java diff --git a/org.lflang/src/org/lflang/generator/ChannelRange.java b/org.lflang/src/org/lflang/generator/ChannelRange.java new file mode 100644 index 0000000000..caffc732af --- /dev/null +++ b/org.lflang/src/org/lflang/generator/ChannelRange.java @@ -0,0 +1,78 @@ +/** A data structure for a port instance. */ + +/************* +Copyright (c) 2019, 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; + + +/** + * Class representing a range of channels of a port instance. + * If the enclosing port instance is not a multiport, this range will + * be (0,1). + * + * @author Edward A. Lee + */ +public class ChannelRange implements Comparable { + public ChannelRange(PortInstance port, int startChannel, int channelWidth) { + // Some targets determine widths at runtime, in which case a + // width of 0 is reported here. Tolerate that. + if (channelWidth != 0 + && (startChannel < 0 || startChannel >= port.width + || channelWidth < 0 || startChannel + channelWidth > port.width)) { + throw new RuntimeException("Invalid range of port channels."); + } + this.port = port; + this.startChannel = startChannel; + this.channelWidth = channelWidth; + } + + ////////////////////////////////////////////////////// + //// Public methods + + /** The port that contains these channels. */ + public final PortInstance port; + + /** The starting channel index (starting at 0). */ + public final int startChannel; + + /** The width of the range (0 means unknown). */ + public final int channelWidth; + + ////////////////////////////////////////////////////// + //// Public methods + + /** + * Compare the ranges by just comparing their startChannel index. + */ + @Override + public int compareTo(ChannelRange o) { + if (startChannel < o.startChannel) { + return -1; + } else if (startChannel == o.startChannel) { + return 0; + } else { + return 1; + } + } +} diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index a3b5d83416..3a5d0c511c 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -54,7 +54,6 @@ import org.lflang.federated.FederateInstance import org.lflang.federated.serialization.SupportedSerializers import org.lflang.graph.InstantiationGraph import org.lflang.lf.Action -import org.lflang.lf.Connection import org.lflang.lf.Delay import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory @@ -1049,7 +1048,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { val sourceFederate = federatesByInstantiation.get(source.instance.parent.definition).get(sourceBankIndex); // Set up dependency information. - var connection = null as Connection// FIXME FIXME source.connection; + var connection = source.connection; if (connection === null) { // This should not happen. errorReporter.reportError(input.definition, "Unexpected error. Cannot find input connection for port") diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index cb11c86900..d70b4f615a 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -142,6 +142,115 @@ public List eventualDestinations() { return eventualDestinationRanges; } + /** + * 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 represented in the result. + * + * If this is not a multiport and is not within a bank, then the list will have + * only one item and the range will have a total width of one. Otherwise, it will + * have enough items so that the range widths add up to the width of this + * multiport multiplied by the total number of instances within containing banks. + * + * The ports listed are only ports that are written to by reactions, + * not relay ports that the data may go through on the way. + */ + public List> eventualSources() { + return eventualSources(new Range.Port(this)); + } + + /** + * Return the list of downstream ports that are connected to this port + * or an empty list if there are none. + */ + public List> getDependentPorts() { + return dependentPorts; + } + + /** + * Return the list of upstream ports that are connected to this port, + * or an empty set if there are none. + * For an ordinary port, this list will have length 0 or 1. + * For a multiport, it can have a larger size. + */ + public List> getDependsOnPorts() { + return dependsOnPorts; + } + + /** + * Return true if the port is an input. + */ + public boolean isInput() { + return (definition instanceof Input); + } + + /** + * Return true if this is a multiport. + */ + public boolean isMultiport() { + return isMultiport; + } + + /** + * Return true if the port is an output. + */ + public boolean isOutput() { + return (definition instanceof Output); + } + + /** + * Return the number of destination reactors for this port instance. + * This can be used to initialize reference counting, but not for + * multiport. For multiports, the number of destinations can vary + * by channel, and hence must be obtained from the ranges reported + * by eventualDestinations(); + */ + public int numDestinationReactors() { + List sourceChannelRanges = eventualDestinations(); + int result = 0; + for (SendRange ranges : sourceChannelRanges) { + result += ranges.getNumberOfDestinationReactors(); + } + return result; + } + + @Override + public String toString() { + return "PortInstance " + getFullName(); + } + + ////////////////////////////////////////////////////// + //// Protected fields. + + /** + * Downstream ports that are connected directly to this port. + * These are listed in the order they appear in connections. + * If this port is input port, then the connections are those + * in the parent reactor of this port (inside connections). + * If the port is an output port, then the connections are those + * in the parent's parent (outside connections). + * The sum of the widths of the dependent ports is required to + * be an integer multiple N of the width of this port (this is checked + * by the validator). Each channel of this port will be broadcast + * to N recipients. + */ + List> dependentPorts = new ArrayList>(); + + /** + * Upstream ports that are connected directly to this port, if there are any. + * For an ordinary port, this set will have size 0 or 1. + * For a multiport, it can have a larger size. + * This initially has capacity 1 because that is by far the most common case. + */ + List> dependsOnPorts = new ArrayList>(1); + + /** Indicator of whether this is a multiport. */ + boolean isMultiport = false; + + ////////////////////////////////////////////////////// + //// Private methods. + /** * Given a Range, return a list of SendRange that describes * the eventual destinations of the given range. @@ -156,7 +265,7 @@ public List eventualDestinations() { * reactions are not listed. * @param srcRange The source range. */ - public static List eventualDestinations(Range srcRange) { + private static List eventualDestinations(Range srcRange) { // Getting the destinations is more complex than getting the sources // because of multicast, where there is more than one connection statement @@ -218,7 +327,6 @@ public static List eventualDestinations(Range srcRange) // For each returned SendRange, convert it to a SendRange // for the srcRange port rather than the dep port. for (SendRange dstSend : dstSendRanges) { - // FIXME: Need to figure out interleaved level! queue.add(dstSend.newSendRange(srcRange)); } depWidthCovered += subDep.totalWidth; @@ -283,9 +391,9 @@ public static List eventualDestinations(Range srcRange) } /** - * Return a list of ports that send data to this port annotated with the channel - * and bank ranges of each source port. If this port is directly written to by - * one more more reactions, then it is its own eventual source and only this port + * Return a list of ranges of ports that send data to this port within the + * specified range. 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 represented in the result. * * If this is not a multiport and is not within a bank, then the list will have @@ -296,7 +404,7 @@ public static List eventualDestinations(Range srcRange) * The ports listed are only ports that are written to by reactions, * not relay ports that the data may go through on the way. */ - public List> eventualSources() { + private List> eventualSources(Range range) { if (eventualSourceRanges == null) { // Cached result has not been created. eventualSourceRanges = new ArrayList>(); @@ -304,106 +412,20 @@ public List> eventualSources() { if (!dependsOnReactions.isEmpty()) { eventualSourceRanges.add(new Range.Port(this)); } else { + var channelsCovered = 0; for (Range sourceRange : dependsOnPorts) { - // FIXME FIXME This needs to use the sourceRange! - eventualSourceRanges.addAll(sourceRange.instance.eventualSources()); + // Check whether the sourceRange overlaps with the range. + if (channelsCovered + sourceRange.width >= range.start + && channelsCovered < range.start + range.width) { + eventualSourceRanges.addAll(sourceRange.instance.eventualSources(sourceRange)); + } + channelsCovered += sourceRange.width; } } } return eventualSourceRanges; } - /** - * Return the list of downstream ports that are connected to this port - * or an empty list if there are none. - */ - public List> getDependentPorts() { - return dependentPorts; - } - - /** - * Return the list of upstream ports that are connected to this port, - * or an empty set if there are none. - * For an ordinary port, this list will have length 0 or 1. - * For a multiport, it can have a larger size. - */ - public List> getDependsOnPorts() { - return dependsOnPorts; - } - - /** - * Return true if the port is an input. - */ - public boolean isInput() { - return (definition instanceof Input); - } - - /** - * Return true if this is a multiport. - */ - public boolean isMultiport() { - return isMultiport; - } - - /** - * Return true if the port is an output. - */ - public boolean isOutput() { - return (definition instanceof Output); - } - - /** - * Return the number of destination reactors for this port instance. - * This can be used to initialize reference counting, but not for - * multiport. For multiports, the number of destinations can vary - * by channel, and hence must be obtained from the ranges reported - * by eventualDestinations(); - */ - public int numDestinationReactors() { - List sourceChannelRanges = eventualDestinations(); - int result = 0; - for (SendRange ranges : sourceChannelRanges) { - result += ranges.getNumberOfDestinationReactors(); - } - return result; - } - - @Override - public String toString() { - return "PortInstance " + getFullName(); - } - - ////////////////////////////////////////////////////// - //// Protected fields. - - /** - * Downstream ports that are connected directly to this port. - * These are listed in the order they appear in connections. - * If this port is input port, then the connections are those - * in the parent reactor of this port (inside connections). - * If the port is an output port, then the connections are those - * in the parent's parent (outside connections). - * The sum of the widths of the dependent ports is required to - * be an integer multiple N of the width of this port (this is checked - * by the validator). Each channel of this port will be broadcast - * to N recipients. - */ - List> dependentPorts = new ArrayList>(); - - /** - * Upstream ports that are connected directly to this port, if there are any. - * For an ordinary port, this set will have size 0 or 1. - * For a multiport, it can have a larger size. - * This initially has capacity 1 because that is by far the most common case. - */ - List> dependsOnPorts = new ArrayList>(1); - - /** Indicator of whether this is a multiport. */ - boolean isMultiport = false; - - ////////////////////////////////////////////////////// - //// Private methods. - /** * Set the initial multiport width, if this is a multiport, from the widthSpec * in the definition. diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java new file mode 100644 index 0000000000..fc07ab2a49 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -0,0 +1,333 @@ +/* Abstract class for ranges of NamedInstance. */ + +/* +Copyright (c) 2019-2021, 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; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.lflang.lf.Connection; + +/** + * Class representing a range of runtime instance objects + * (port channels, reactions, etc.). This class and its derived classes + * have the most detailed information about the structure of a Lingua Franca + * program. There are three levels of detail: + * + * * The abstract syntax tree (AST). + * * The instantiation graph (IG). + * * The runtime instance graph (RIG). + * + * In the AST, each reactor class is represented once. + * In the IG, each reactor class is represented as many times as it is + * instantiated, except that a bank has only one representation (as + * in the graphical rendition). Equivalently, each IG node has a unique + * full name, even though it may represent many runtime instances. + * The IG is represented by + * {@link NamedInstance} and its derived classes. + * In the RIG, each bank is expanded so each bank member and + * each port channel is represented. + * + * In general, determining dependencies between reactions requires analysis + * at the level of the RIG. But a brute-force representation of the RIG + * can get very large, and for most programs it has a great deal of + * redundancy. In a fully detailed representation of the RIG, for example, + * a bank of width N that contains a bank of width M within which there + * is a reactor R with port P will have N*M runtime instances of the port. + * If the port is a multiport with width L, then there are N*M*L + * edges connected to instances of that port, each of which may go + * to a distinct set of other remote runtime port instances. + * + * This class and its subclasses give a more compact representation of the + * RIG in most common cases where collections of runtime instances all have + * the same dependencies. + * + * A Range represents an adjacent set of RIG objects (port channels, reactions + * reactors). Specifically, it is a list of RIG objects, each of which may have + * a width greater than one, listed in the order in which they should be iterated + * over, plus a start offset into the expanded list and a width of the range. + * + * The simplest Ranges are those where the corresponding IG node represents + * only one runtime instance (its ig_instance is not (deeply) within a bank + * and is not a multiport). In this case, the Range and all its RIG objects + * will have width = 1. + * + * In a more complex instance, consider a bank A of width 2 that contains a + * bank B of width 2 that contains a port instance P with width 2. . + * + * There are a total of 8 instances of P, which we can name: + * + * A0.B0.P0 + * A0.B0.P1 + * A0.B1.P0 + * A0.B1.P1 + * A1.B0.P0 + * A1.B0.P1 + * A1.B1.P0 + * A1.B1.P1 + * + * If there is no interleaving, the list of RIG objects will be P, B, A, + * indicating that they should be iterated by incrementing the index of P + * first, then the index of B, then the index of A. + * + * If the connection within B to port P is interleaved, then the order + * of iteration will be B, P, A, resulting in the list: + * + * A0.B0.P0 + * A0.B1.P0 + * A0.B0.P1 + * A0.B1.P1 + * A1.B0.P0 + * A1.B1.P0 + * A1.B0.P1 + * A1.B1.P1 + * + * If the connection within A to B is also interleaved, then the order + * will be A, B, P, resulting in the list: + * + * A0.B0.P0 + * A1.B0.P0 + * A0.B1.P0 + * A1.B1.P0 + * A0.B0.P1 + * A1.B0.P1 + * A0.B1.P1 + * A1.B1.P1 + * + * Finally, if the connection within A to B is interleaved, but not the + * connection within B to P, then the order will be A, P, B, resulting in + * the list: + * + * A0.B0.P0 + * A1.B0.P0 + * A0.B0.P1 + * A1.B0.P1 + * A0.B1.P0 + * A1.B1.P0 + * A0.B1.P1 + * A1.B1.P1 + * + * A Range is a contiguous subset of one of the above lists, given by + * a start offset and a width. + * + * The head and tail functions split such a range. + * + * This class and subclasses are designed to be immutable. + * Modifications always return a new Range. + * + * @author{Edward A. Lee } +*/ +public class Range> implements Comparable> { + + /** + * Create a new range representing the full width of the specified instance + * with no interleaving. The instances will be a list with the specified instance + * first, its parent next, and on up the hierarchy until the depth 1 parent (the + * top-level reactor is not included because it can never be a bank). + * @param instance The instance. + * @param connection The connection establishing this range or null if not unique. + */ + public Range( + T instance, + Connection connection + ) { + this(instance, 0, 0, connection); + } + + /** + * Create a new range representing a range of the specified instance + * with no interleaving. The instances will be a list with the specified instance + * first, its parent next, and on up the hierarchy until the depth 1 parent (the + * top-level reactor is not included because it can never be a bank). + * @param instance The instance over which this is a range (port, reaction, etc.) + * @param start The starting index for the range. + * @param width The width of the range or 0 to specify the maximum possible width. + * @param connection The connection establishing this range or null if not unique. + */ + public Range( + T instance, + int start, + int width, + Connection connection + ) { + this.instance = instance; + int totalWidth = instance.width; + NamedInstance parent = instance.parent; + while (parent.depth > 0) { + totalWidth *= parent.width; + parent = parent.parent; + } + this.start = start; + if (width > 0 && width + start < totalWidth) { + this.width = width; + } else { + this.width = totalWidth - start; + } + this.totalWidth = totalWidth; + this.connection = connection; + } + + ////////////////////////////////////////////////////////// + //// Public variables + + /** The connection establishing this range or null if not unique. */ + public final Connection connection; + + /** The instance that this is a range of. */ + public final T instance; + + /** The start offset of this range. */ + public final int start; + + /** The total width of the list of instances. */ + public final int totalWidth; + + /** The width of this range. */ + public final int width; + + ////////////////////////////////////////////////////////// + //// Public methods + + /** + * Compare ranges by first comparing their start offset, and then, + * if these are equal, comparing their widths. This comparison is + * meaningful only for ranges that have the same instances. + */ + @Override + public int compareTo(Range o) { + if (start < o.start) { + return -1; + } else if (start == o.start) { + if (width < o.width) { + return -1; + } else if (width == o.width) { + return 0; + } else { + return 1; + } + } else { + return 1; + } + } + + /** + * Return a new Range that is identical to this range but + * with width reduced to the specified width. + * If the new width is greater than or equal to the width + * of this range, then return this range. + * If the newWidth is 0 or negative, return null. + * @param newWidth The new width. + */ + public Range head(int newWidth) { + if (newWidth >= width) return this; + if (newWidth <= 0) return null; + return new Range(instance, start, newWidth, connection); + } + + /** + * Return a list containing the instance for this range and all of its + * parents, not including the top level reactor, in the order in which + * their banks and multiport channels should be iterated. + * For each depth at which the connection is interleaved, that parent + * will appear before this instance in depth order (shallower to deeper). + * For each depth at which the connection is not interleaved, that parent + * will appear after this instance in reverse depth order (deeper to + * shallower). + */ + public List> iterationOrder() { + LinkedList> result = new LinkedList>(); + result.add(instance); + ReactorInstance parent = instance.parent; + while (parent.depth > 0) { + if (_interleaved.contains(parent)) { + // Put the parent at the head of the list. + result.add(0, parent); + } else { + result.add(parent); + } + } + return result; + } + + /** + * Return a new range that represents the leftover elements + * starting at the specified offset. If the offset is greater + * than or equal to the width, then this returns null. + * If this offset is 0 then this returns this range unmodified. + * @param offset The number of elements to consume. + */ + public Range tail(int offset) { + if (offset == 0) return this; + if (offset >= width) return null; + return new Range(instance, start + offset, width - offset, connection); + } + + /** + * Toggle the interleaved status of the specified reactor, which is assumed + * to be a parent of the instance of this range. + * If it was previously interleaved, make it not interleaved + * and vice versa. This returns a new Range. + * @param reactor The parent reactor at which to toggle interleaving. + */ + public Range toggleInterleaved(ReactorInstance reactor) { + Set newInterleaved = new HashSet(_interleaved); + if (newInterleaved.contains(reactor)) { + newInterleaved.remove(reactor); + } else { + newInterleaved.add(reactor); + } + Range result = new Range(instance, start, width, connection); + result._interleaved = newInterleaved; + return result; + } + + @Override + public String toString() { + return instance.getFullName() + "(" + start + "," + width + ")"; + } + + ////////////////////////////////////////////////////////// + //// Public inner classes + + public static class Port extends Range { + public Port(PortInstance instance) { + super(instance, null); + } + public Port(PortInstance instance, Connection connection) { + super(instance, connection); + } + public Port(PortInstance instance, int start, int width, Connection connection) { + super(instance, start, width, connection); + } + } + + ////////////////////////////////////////////////////////// + //// Protected variables + + /** Record of which levels are interleaved. */ + Set _interleaved = new HashSet(); +} diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 9157e1b2a9..53051db662 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -870,9 +870,10 @@ private void connectPortInstances( */ private void establishPortConnections() { for (Connection connection : ASTUtils.allConnections(reactorDefinition)) { - List> leftPorts = listPortInstances(connection.getLeftPorts()); + List> leftPorts = listPortInstances(connection.getLeftPorts(), connection); Iterator> srcRanges = leftPorts.iterator(); - Iterator> dstRanges = listPortInstances(connection.getRightPorts()).iterator(); + Iterator> dstRanges + = listPortInstances(connection.getRightPorts(), connection).iterator(); // Check for empty lists. if (!srcRanges.hasNext()) { @@ -951,8 +952,13 @@ private void establishPortConnections() { * the returned range represents the sequence `[b0.m0, b0.m1, b1.m0, b1.m1]`. * With the interleaved marking, the returned range represents the sequence * `[b0.m0, b1.m0, b0.m1, b1.m1]`. Both ranges will have width 4. + * + * @param references The variable references on one side of the connection. + * @param connection The connection. */ - private List> listPortInstances(List references) { + private List> listPortInstances( + List references, Connection connection + ) { List> result = new ArrayList>(); for (VarRef portRef : references) { // Simple error checking first. @@ -972,7 +978,7 @@ private List> listPortInstances(List references) { if (reactor != null) { PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); - Range range = new Range.Port(portInstance); + Range range = new Range.Port(portInstance, connection); if (portRef.isInterleaved()) { // Toggle interleaving at the depth of this reactor, which // contains the Connection. This is not necessarily the reactor diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java new file mode 100644 index 0000000000..69cec3c3f3 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -0,0 +1,168 @@ +/* Abstract class for ranges of NamedInstance. */ + +/* +Copyright (c) 2019-2021, 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; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Class representing a range of a port that sources data + * together with a list of destination ranges of other ports that should all + * receive the same data sent in this range. + * All ranges in the destinations list have the same width as this range, + * but not necessarily the same start offsets. + * This class also includes a field representing the number of destination + * reactors. + * + * This class and subclasses are designed to be immutable. + * Modifications always return a new Range. + * + * @author{Edward A. Lee } +*/ +public class SendRange extends Range.Port { + + /** + * Create a new send range. + * @param instance The instance over which this is a range of. + * @param start The starting index. + * @param width The width. + */ + public SendRange( + PortInstance instance, + int start, + int width + ) { + super(instance, start, width, null); + } + + ////////////////////////////////////////////////////////// + //// Public variables + + /** The list of destination ranges to which this broadcasts. */ + public final List> destinations = new ArrayList>(); + + ////////////////////////////////////////////////////////// + //// Public methods + + /** + * Return the total number of destination reactors. Specifically, this + * is the number of distinct reactors that have one or more ports + * with dependent reactions in the reactor. + */ + public int getNumberOfDestinationReactors() { + if (_numberOfDestinationReactors < 0) { + // Has not been calculated before. Calculate now. + Set result = new HashSet(); + for (Range destination : this.destinations) { + for (NamedInstance instance : destination.iterationOrder()) { + if (instance instanceof PortInstance + && !((PortInstance)instance).dependentReactions.isEmpty()) { + result.add(instance.getParent()); + } + } + } + _numberOfDestinationReactors = result.size(); + } + return _numberOfDestinationReactors; + } + + /** + * Return a new SendRange that is identical to this range but + * with width reduced to the specified width. + * If the new width is greater than or equal to the width + * of this range, then return this range. + * If the newWidth is 0 or negative, return null. + * This overrides the base class to also apply head() + * to the destination list. + * @param newWidth The new width. + */ + @Override + public SendRange head(int newWidth) { + // NOTE: Cannot use the superclass because it returns a Range, not a SendRange. + if (newWidth >= width) return this; + if (newWidth <= 0) return null; + + SendRange result = new SendRange(instance, start, newWidth); + + for (Range destination : destinations) { + result.destinations.add(destination.head(newWidth)); + } + return result; + } + + /** + * Return a new SendRange that is like this one, but + * converted to the specified upstream range. The returned + * SendRange inherits the destinations of this range. + * + * Normally, the total width of the specified range is + * the same as that of this range, but if it is not, + * then the minimum of the two widths is returned. + * + * @param srcRange A new source range. + */ + protected SendRange newSendRange(Range srcRange) { + SendRange reference = this; + if (srcRange.totalWidth > totalWidth) { + srcRange = srcRange.head(totalWidth); + } else if (srcRange.totalWidth < totalWidth) { + reference = head(srcRange.totalWidth); + } + SendRange result = new SendRange(srcRange.instance, srcRange.start, srcRange.width); + + result.destinations.addAll(reference.destinations); + + return result; + } + + /** + * Return a new SendRange that represents the leftover elements + * starting at the specified offset. If the offset is greater + * than or equal to the width, then this returns null. + * If this offset is 0 then this returns this range unmodified. + * This overrides the base class to also apply tail() + * to the destination list. + * @param offset The number of elements to consume. + */ + @Override + public SendRange tail(int offset) { + if (offset == 0) return this; + if (offset >= width) return null; + SendRange result = new SendRange(instance, start + offset, width - offset); + + for (Range destination : destinations) { + result.destinations.add(destination.tail(offset)); + } + return result; + } + + ////////////////////////////////////////////////////////// + //// Private variables + + private int _numberOfDestinationReactors = -1; // Never access this directly. +} diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 66f02d64de..ef1a9c6567 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4997,6 +4997,7 @@ class CGenerator extends GeneratorBase { ) { val src = srcRange.instance; val width = (srcRange.totalWidth <= dstRange.totalWidth)? srcRange.totalWidth : dstRange.totalWidth; + val sfx = "_src"; pr(builder, ''' // Iterate over range1 of «src.fullName» with starting offset «srcRange.start», @@ -5007,24 +5008,31 @@ class CGenerator extends GeneratorBase { // Define additional variables for range2. startScopedBlock(builder); - // FIXME FIXME: This will fail if the destination is deeply nested within a bank - // or if it is nested within multiple banks. - // Need to change SendRange so that startBank is an array of length equal to - // the depth of the port's container. - - if (src.parent.isBank && src.isOutput) { - // Need to specify a suffix to avoid a variable name collision. - pr(builder, ''' - int «CUtil.bankIndex(src.parent, "_src")» = «srcRange.start»; - ''') - } - if (src.isMultiport) { - pr(builder, ''' - int «CUtil.channelIndex(src)» = «srcRange.start»; - ''') + // Declare the variables needed for the source range. + // These need to be initialized to starting indices. + val srcIteration = srcRange.iterationOrder(); + var factor = 1; + for (i : srcIteration) { + val init = (srcRange.start / factor) % i.width; + if (i instanceof PortInstance) { + pr(builder, ''' + int «CUtil.channelIndex(i)» = «init»; + ''') + } else { + pr(builder, ''' + int «CUtil.bankIndex(i as ReactorInstance, sfx)» = «init»; + ''') + } + factor *= i.width; } + startScopedRangeBlock(builder, dstRange); - defineSelfStruct(builder, src.parent, "_src"); + + for (i : srcIteration) { + if (i instanceof ReactorInstance) { + defineSelfStruct(builder, i, sfx); + } + } } /** @@ -5043,45 +5051,44 @@ class CGenerator extends GeneratorBase { // then the destination and source may be the same port. In that case, // we want to avoid double incrementing the bank and channel variables. if (dst != src) { - // Interleaved status is normally stored in the destination only, - // but just in case, calculate the XOR. - val interleaved = false; - // FIXME FIXME (srcRange.interleaved || dstRange.interleaved) && !(srcRange.interleaved && dstRange.interleaved); - if (interleaved) { - if (src.parent.isBank && src.isOutput) { - pr(builder, ''' - «CUtil.bankIndex(src.parent, sfx)»++; - ''') - if (src.isMultiport) { + val srcIteration = srcRange.iterationOrder(); + for (i : srcIteration) { + if (i instanceof PortInstance) { + // No need to do anything if it's not a multiport. + if (i.isMultiport) { pr(builder, ''' - if («CUtil.bankIndex(src.parent, sfx)» >= «src.parent.width») { - «CUtil.bankIndex(src.parent, sfx)» = 0; - «CUtil.channelIndex(src)»++; - } + «CUtil.channelIndex(i)»++; + if («CUtil.channelIndex(i)» >= «i.width») { + «CUtil.channelIndex(i)» = 0; ''') + indent(builder); } - } else if (src.isMultiport) { - pr(builder, ''' - «CUtil.channelIndex(src)»++; - ''') - } - } else { - if (src.isMultiport) { - pr(builder, ''' - «CUtil.channelIndex(src)»++; - ''') - if (src.parent.isBank && src.isOutput) { + } else if (i instanceof ReactorInstance) { + // No need to do anything if it's not a bank. + if (i.isBank) { pr(builder, ''' - if («CUtil.channelIndex(src)» >= «src.width») { - «CUtil.channelIndex(src)» = 0; - «CUtil.bankIndex(src.parent, sfx)»++; - } + «CUtil.bankIndex(i, sfx)»++; + if («CUtil.bankIndex(i, sfx)» >= «i.width») { + «CUtil.bankIndex(i, sfx)» = 0; ''') + indent(builder); + } + } + } + // Now need to close all these if blocks. + for (i : srcIteration) { + if (i instanceof PortInstance) { + // No need to do anything if it's not a multiport. + if (i.isMultiport) { + unindent(builder); + pr(builder, "}"); + } + } else if (i instanceof ReactorInstance) { + // No need to do anything if it's not a bank. + if (i.isBank) { + unindent(builder); + pr(builder, "}"); } - } else if (src.parent.isBank && src.isInput) { - pr(builder, ''' - «CUtil.bankIndex(src.parent, sfx)»++; - ''') } } } From 85b6e1622dfa718c81215656b9d8bbf0111b9ec4 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 14 Dec 2021 11:03:26 -0800 Subject: [PATCH 075/221] Checkpoint checkin. Interleaving works on the right, not left. Levels and domainating reaction still not right. --- .../org/lflang/generator/GeneratorBase.xtend | 4 +- .../org/lflang/generator/PortInstance.java | 26 +-- .../src/org/lflang/generator/Range.java | 82 +++++---- .../org/lflang/generator/ReactorInstance.java | 38 +++-- .../src/org/lflang/generator/SendRange.java | 54 +++--- .../org/lflang/generator/c/CGenerator.xtend | 155 +++++++++++------- 6 files changed, 210 insertions(+), 149 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 3a5d0c511c..4b538f700c 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -1087,7 +1087,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { // Make one communication for each channel. // FIXME: There is an opportunity for optimization here by aggregating channels. - for (var i = 0; i < source.totalWidth; i++) { + for (var i = 0; i < source.width; i++) { FedASTUtils.makeCommunication( source.instance, input, @@ -1102,7 +1102,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { targetConfig.coordination ); } - channel += source.totalWidth; + channel += source.width; } } } diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index d70b4f615a..357bc88438 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -299,26 +299,26 @@ private static List eventualDestinations(Range srcRange Iterator> dependentPorts = srcPort.dependentPorts.iterator(); if (dependentPorts.hasNext()) { Range dep = dependentPorts.next(); - while(srcWidthCovered < srcRange.totalWidth) { - if (srcRange.start >= depWidthCovered + dep.totalWidth) { + while(srcWidthCovered < srcRange.width) { + if (srcRange.start >= depWidthCovered + dep.width) { // Destination is fully before this range. - depWidthCovered += dep.totalWidth; + depWidthCovered += dep.width; if (!dependentPorts.hasNext()) break; // This should be an error. dep = dependentPorts.next(); continue; } - if (depWidthCovered >= srcRange.start + srcRange.totalWidth) { + if (depWidthCovered >= srcRange.start + srcRange.width) { // Source range is covered. We are finished. break; } // Dependent port overlaps the range of interest. // Get a new range that possibly subsets the target range. // Argument is guaranteed by above checks to be less than - // dep.totalWidth, so the result will not be null. + // dep.width, so the result will not be null. Range subDep = dep.tail(depWidthCovered - srcRange.start); // The following argument is guaranteed to be greater than // depWidthCovered - srcRange.getStartOffset(). - subDep = subDep.head(srcRange.totalWidth); + subDep = subDep.head(srcRange.width); // At this point, dep is the subrange of the dependent port of interest. // Recursively get the send ranges of that destination port. @@ -329,9 +329,9 @@ private static List eventualDestinations(Range srcRange for (SendRange dstSend : dstSendRanges) { queue.add(dstSend.newSendRange(srcRange)); } - depWidthCovered += subDep.totalWidth; - srcWidthCovered += subDep.totalWidth; - if (dep.start + dep.totalWidth <= subDep.start + subDep.totalWidth) { + depWidthCovered += subDep.width; + srcWidthCovered += subDep.width; + if (dep.start + dep.width <= subDep.start + subDep.width) { // dep range is exhausted. Get another one. if (!dependentPorts.hasNext()) break; // This should be an error. dep = dependentPorts.next(); @@ -351,15 +351,15 @@ private static List eventualDestinations(Range srcRange } if (candidate.start == next.start) { // Ranges have the same starting point. Need to merge them. - if (candidate.totalWidth <= next.totalWidth) { + if (candidate.width <= next.width) { // Can use all of the channels of candidate. // Import the destinations of next and split it. candidate.destinations.addAll(next.destinations); - if (candidate.totalWidth < next.totalWidth) { + if (candidate.width < next.width) { // The next range has more channels connected to this sender. - next = (SendRange)next.tail(candidate.totalWidth); + next = (SendRange)next.tail(candidate.width); // Truncate the destinations just imported. - candidate = candidate.head(candidate.totalWidth); + candidate = candidate.head(candidate.width); } else { // We are done with next and can discard it. next = queue.poll(); diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index fc07ab2a49..8ed588547a 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -39,15 +39,15 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * program. There are three levels of detail: * * * The abstract syntax tree (AST). - * * The instantiation graph (IG). + * * The compile-time instance graph (CIG). * * The runtime instance graph (RIG). * * In the AST, each reactor class is represented once. - * In the IG, each reactor class is represented as many times as it is + * In the CIG, each reactor class is represented as many times as it is * instantiated, except that a bank has only one representation (as - * in the graphical rendition). Equivalently, each IG node has a unique + * in the graphical rendition). Equivalently, each CIG node has a unique * full name, even though it may represent many runtime instances. - * The IG is represented by + * The CIG is represented by * {@link NamedInstance} and its derived classes. * In the RIG, each bank is expanded so each bank member and * each port channel is represented. @@ -67,18 +67,32 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * the same dependencies. * * A Range represents an adjacent set of RIG objects (port channels, reactions - * reactors). Specifically, it is a list of RIG objects, each of which may have - * a width greater than one, listed in the order in which they should be iterated - * over, plus a start offset into the expanded list and a width of the range. + * reactors). For example, it can represent port channels 2 through 5 in a multiport + * of width 10. The width in this case is 4. If such a port is + * contained by one or more banks of reactors, then channels 2 through 5 + * of one bank member form a contiguous range. If you want channels 2 through 5 + * of all bank members, then this needs to be represented with multiple ranges. * - * The simplest Ranges are those where the corresponding IG node represents - * only one runtime instance (its ig_instance is not (deeply) within a bank - * and is not a multiport). In this case, the Range and all its RIG objects - * will have width = 1. + * The maxWidth is the width of the instance multiplied by the widths of + * each of its containers. For example, if a port of width 4 is contained by + * a bank of width 2 that is then contained by a bank of width 3, then + * the maxWidth will be 2*3*4 = 24. + * + * The function iterationOrder returns a list that includes the instance + * of this range and all its containers, except the top-level reactor (main + * or federated). The order of this list is the order in which an + * iteration over the RIG objects represented by this range should be + * iterated. If the instance is a PortInstance, then this order will + * depend on whether connections at any level of the hierarchy are + * interleaved. + * + * The simplest Ranges are those where the corresponding CIG node represents + * only one runtime instance (its instance is not (deeply) within a bank + * and is not a multiport). In this case, the Range and all the objects + * returned by iterationOrder will have width 1. * * In a more complex instance, consider a bank A of width 2 that contains a * bank B of width 2 that contains a port instance P with width 2. . - * * There are a total of 8 instances of P, which we can name: * * A0.B0.P0 @@ -90,12 +104,12 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * A1.B1.P0 * A1.B1.P1 * - * If there is no interleaving, the list of RIG objects will be P, B, A, + * If there is no interleaving, iterationOrder() returns [P, B, A], * indicating that they should be iterated by incrementing the index of P - * first, then the index of B, then the index of A. + * first, then the index of B, then the index of A, as done above. * * If the connection within B to port P is interleaved, then the order - * of iteration will be B, P, A, resulting in the list: + * of iteration order will be [B, P, A], resulting in the list: * * A0.B0.P0 * A0.B1.P0 @@ -107,7 +121,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * A1.B1.P1 * * If the connection within A to B is also interleaved, then the order - * will be A, B, P, resulting in the list: + * will be [A, B, P], resulting in the list: * * A0.B0.P0 * A1.B0.P0 @@ -119,7 +133,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * A1.B1.P1 * * Finally, if the connection within A to B is interleaved, but not the - * connection within B to P, then the order will be A, P, B, resulting in + * connection within B to P, then the order will be [A, P, B], resulting in * the list: * * A0.B0.P0 @@ -132,9 +146,10 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * A1.B1.P1 * * A Range is a contiguous subset of one of the above lists, given by - * a start offset and a width. + * a start offset and a width that is less than or equal to maxWidth. * - * The head and tail functions split such a range. + * For a Range with width greater than 1, + * the head() and tail() functions split the range. * * This class and subclasses are designed to be immutable. * Modifications always return a new Range. @@ -175,20 +190,22 @@ public Range( Connection connection ) { this.instance = instance; - int totalWidth = instance.width; + this.start = start; + this.connection = connection; + + int maxWidth = instance.width; // Initial value. NamedInstance parent = instance.parent; while (parent.depth > 0) { - totalWidth *= parent.width; + maxWidth *= parent.width; parent = parent.parent; } - this.start = start; - if (width > 0 && width + start < totalWidth) { + this.maxWidth = maxWidth; + + if (width > 0 && width + start < maxWidth) { this.width = width; } else { - this.width = totalWidth - start; + this.width = maxWidth - start; } - this.totalWidth = totalWidth; - this.connection = connection; } ////////////////////////////////////////////////////////// @@ -203,8 +220,8 @@ public Range( /** The start offset of this range. */ public final int start; - /** The total width of the list of instances. */ - public final int totalWidth; + /** The maximum width of any range with this instance. */ + public final int maxWidth; /** The width of this range. */ public final int width; @@ -269,20 +286,21 @@ public List> iterationOrder() { } else { result.add(parent); } + parent = parent.parent; } return result; } /** * Return a new range that represents the leftover elements - * starting at the specified offset. If the offset is greater - * than or equal to the width, then this returns null. + * starting at the specified offset relative to start. + * If start + offset is greater than or equal to the maxWidth, then this returns null. * If this offset is 0 then this returns this range unmodified. * @param offset The number of elements to consume. */ public Range tail(int offset) { if (offset == 0) return this; - if (offset >= width) return null; + if (start + offset >= maxWidth) return null; return new Range(instance, start + offset, width - offset, connection); } @@ -295,7 +313,7 @@ public Range tail(int offset) { */ public Range toggleInterleaved(ReactorInstance reactor) { Set newInterleaved = new HashSet(_interleaved); - if (newInterleaved.contains(reactor)) { + if (_interleaved.contains(reactor)) { newInterleaved.remove(reactor); } else { newInterleaved.add(reactor); diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 53051db662..b629423f1f 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -890,39 +890,45 @@ private void establishPortConnections() { Range dst = dstRanges.next(); while(true) { - if (dst.totalWidth == src.totalWidth) { + if (dst.width == src.width) { connectPortInstances(src, dst); if (!dstRanges.hasNext()) { if (srcRanges.hasNext()) { + // Should not happen (checked by the validator). reporter.reportWarning(connection, "Source is wider than the destination. Outputs will be lost."); } break; - } else if (!srcRanges.hasNext()) { + } + if (!srcRanges.hasNext()) { if (connection.isIterated()) { srcRanges = leftPorts.iterator(); } else { - reporter.reportWarning(connection, - "Destination is wider than the source. Inputs will be missing."); + if (dstRanges.hasNext()) { + // Should not happen (checked by the validator). + reporter.reportWarning(connection, + "Destination is wider than the source. Inputs will be missing."); + } break; } } dst = dstRanges.next(); src = srcRanges.next(); - } else if (dst.totalWidth < src.totalWidth) { - // Split the left range in two. - connectPortInstances(src.head(dst.totalWidth), dst); - src = src.tail(dst.totalWidth); + } else if (dst.width < src.width) { + // Split the left (src) range in two. + connectPortInstances(src.head(dst.width), dst); + src = src.tail(dst.width); if (!dstRanges.hasNext()) { + // Should not happen (checked by the validator). reporter.reportWarning(connection, "Source is wider than the destination. Outputs will be lost."); break; } dst = dstRanges.next(); - } else if (src.totalWidth < dst.totalWidth) { - // Split the right range in two. - connectPortInstances(src, dst.head(src.totalWidth)); - dst = dst.tail(src.totalWidth); + } else if (src.width < dst.width) { + // Split the right (dst) range in two. + connectPortInstances(src, dst.head(src.width)); + dst = dst.tail(src.width); if (!srcRanges.hasNext()) { if (connection.isIterated()) { srcRanges = leftPorts.iterator(); @@ -980,10 +986,12 @@ private List> listPortInstances( Range range = new Range.Port(portInstance, connection); if (portRef.isInterleaved()) { - // Toggle interleaving at the depth of this reactor, which - // contains the Connection. This is not necessarily the reactor + // Toggle interleaving at the depth of the reactor // that is the parent of the port. - range = range.toggleInterleaved(this); + // FIXME: Here, we are assuming that the interleaved() + // keyword is only allowed on the multiports contained by + // contained reactors. + range = range.toggleInterleaved(portInstance.parent); } result.add(range); } diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index 69cec3c3f3..625395f86a 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -115,31 +115,6 @@ public SendRange head(int newWidth) { return result; } - /** - * Return a new SendRange that is like this one, but - * converted to the specified upstream range. The returned - * SendRange inherits the destinations of this range. - * - * Normally, the total width of the specified range is - * the same as that of this range, but if it is not, - * then the minimum of the two widths is returned. - * - * @param srcRange A new source range. - */ - protected SendRange newSendRange(Range srcRange) { - SendRange reference = this; - if (srcRange.totalWidth > totalWidth) { - srcRange = srcRange.head(totalWidth); - } else if (srcRange.totalWidth < totalWidth) { - reference = head(srcRange.totalWidth); - } - SendRange result = new SendRange(srcRange.instance, srcRange.start, srcRange.width); - - result.destinations.addAll(reference.destinations); - - return result; - } - /** * Return a new SendRange that represents the leftover elements * starting at the specified offset. If the offset is greater @@ -161,6 +136,35 @@ public SendRange tail(int offset) { return result; } + ////////////////////////////////////////////////////////// + //// Protected methods + + /** + * Return a new SendRange that is like this one, but + * converted to the specified upstream range. The returned + * SendRange inherits the destinations of this range. + * + * Normally, the width of the specified range is + * the same as that of this range, but if it is not, + * then the minimum of the two widths is returned. + * + * @param srcRange A new source range. + */ + protected SendRange newSendRange(Range srcRange) { + SendRange reference = this; + if (srcRange.width > width) { + srcRange = srcRange.head(width); + } else if (srcRange.width < width) { + reference = head(srcRange.width); + } + SendRange result = new SendRange(srcRange.instance, srcRange.start, srcRange.width); + + for (Range dst : reference.destinations) { + result.destinations.add(dst.head(srcRange.width)); + } + return result; + } + ////////////////////////////////////////////////////////// //// Private variables diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index ef1a9c6567..3e3adc381a 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4742,6 +4742,7 @@ class CGenerator extends GeneratorBase { if (port.isMultiport) { val channel = CUtil.channelIndex(port); pr(builder, ''' + // Port «port.fullName» is a multiport. Iterate over its channels. for (int «channel» = 0; «channel» < «port.width»; «channel»++) { ''') indent(builder); @@ -4890,7 +4891,7 @@ class CGenerator extends GeneratorBase { * range is interleaved. * The order of the iterations is specified by the range. * - * In all cases, it stops the iteration when the + * In all cases, this stops the iteration when the * total width of the range has been covered. * * This must be followed by a call to @@ -4906,7 +4907,7 @@ class CGenerator extends GeneratorBase { pr(builder, ''' // Iterate over range of «port.fullName» with starting offset «range.start», - // and total width «range.totalWidth». + // and width «range.width». ''') // The first creates a scope in which we can define a pointer to the self @@ -4923,18 +4924,20 @@ class CGenerator extends GeneratorBase { // We need to iterate backwards over the iteration order. for (var i = iterationOrder.size() - 1; i >= 0; i--) { val instance = iterationOrder.get(i); - if (instance === port) { + if (instance === port && port.isMultiport) { startChannelIteration(builder, port); - } else { + } else if (instance instanceof ReactorInstance) { // Instance is a parent reactor. - startScopedBlock(builder, instance as ReactorInstance); + startScopedBlock(builder, instance); } } // Allow code to execute only if we are in range. - pr(builder, ''' - if (range_count >= «range.start») { - ''') - indent(builder); + if (range.start > 0) { + pr(builder, ''' + if (range_count >= «range.start») { + ''') + indent(builder); + } } /** @@ -4947,24 +4950,32 @@ class CGenerator extends GeneratorBase { ) { val port = range.instance; - pr(builder, "}"); // End of predicate on range_count. - unindent(builder); - pr(builder, ''' - range_count++; - if (range_count >= «range.start» + «range.width») break; - ''') + if (range.start > 0) { + unindent(builder); + pr(builder, "}"); // End of predicate on range_count. + } + if (range.width > 1) { + pr(builder, ''' + range_count++; + ''') + } val iterationOrder = range.iterationOrder(); // We need to iterate backwards over the iteration order. for (var i = iterationOrder.size() - 1; i >= 0; i--) { val instance = iterationOrder.get(i); - pr(builder, ''' - if (range_count >= «range.start» + «range.width») break; - ''') - if (instance === port) { + if (instance === port && port.isMultiport) { + pr(builder, ''' + if (range_count >= «range.start» + «range.width») break; + ''') endChannelIteration(builder, port); - } else { + } else if (instance instanceof ReactorInstance) { + if (instance.isBank) { + pr(builder, ''' + if (range_count >= «range.start» + «range.width») break; + ''') + } // Instance is a parent reactor. endScopedBlock(builder); } @@ -4996,7 +5007,7 @@ class CGenerator extends GeneratorBase { StringBuilder builder, Range srcRange, Range dstRange ) { val src = srcRange.instance; - val width = (srcRange.totalWidth <= dstRange.totalWidth)? srcRange.totalWidth : dstRange.totalWidth; + val width = (srcRange.width <= dstRange.width)? srcRange.width : dstRange.width; val sfx = "_src"; pr(builder, ''' @@ -5008,6 +5019,10 @@ class CGenerator extends GeneratorBase { // Define additional variables for range2. startScopedBlock(builder); + // Define the self struct for the top-level, in case there are top-level reactions + // that send to inputs of contained reactors. + defineSelfStruct(builder, src.root, sfx); + // Declare the variables needed for the source range. // These need to be initialized to starting indices. val srcIteration = srcRange.iterationOrder(); @@ -5015,13 +5030,17 @@ class CGenerator extends GeneratorBase { for (i : srcIteration) { val init = (srcRange.start / factor) % i.width; if (i instanceof PortInstance) { - pr(builder, ''' - int «CUtil.channelIndex(i)» = «init»; - ''') - } else { - pr(builder, ''' - int «CUtil.bankIndex(i as ReactorInstance, sfx)» = «init»; - ''') + if (i.isMultiport && i != dstRange.instance) { + pr(builder, ''' + int «CUtil.channelIndex(i)» = «init»; + ''') + } + } else if (i instanceof ReactorInstance) { + if (i.isBank) { + pr(builder, ''' + int «CUtil.bankIndex(i, sfx)» = «init»; + ''') + } } factor *= i.width; } @@ -5047,48 +5066,60 @@ class CGenerator extends GeneratorBase { val src = srcRange.instance; val dst = dstRange.instance; val sfx = "_src"; - // If an output port is triggering a reaction in its parent's parent, - // then the destination and source may be the same port. In that case, - // we want to avoid double incrementing the bank and channel variables. - if (dst != src) { - val srcIteration = srcRange.iterationOrder(); - for (i : srcIteration) { - if (i instanceof PortInstance) { - // No need to do anything if it's not a multiport. - if (i.isMultiport) { + + val srcIteration = srcRange.iterationOrder(); + for (i : srcIteration) { + if (i instanceof PortInstance) { + // No need to do anything if it's not a multiport. + if (i.isMultiport) { + if (dst == src) { + // If the source and destination ports are the same, then we do not + // need to increment or reset the channel index. But we do still need + // to check whether to increment the next bank variable. + // If an output port is triggering a reaction in its parent's parent, + // then the destination and source may be the same port. pr(builder, ''' - «CUtil.channelIndex(i)»++; - if («CUtil.channelIndex(i)» >= «i.width») { - «CUtil.channelIndex(i)» = 0; + if («CUtil.channelIndex(i)» >= «i.width» - 1) { // At end. ''') indent(builder); - } - } else if (i instanceof ReactorInstance) { - // No need to do anything if it's not a bank. - if (i.isBank) { + } else { pr(builder, ''' - «CUtil.bankIndex(i, sfx)»++; - if («CUtil.bankIndex(i, sfx)» >= «i.width») { - «CUtil.bankIndex(i, sfx)» = 0; + «CUtil.channelIndex(i)»++; + if («CUtil.channelIndex(i)» >= «i.width») { + «CUtil.channelIndex(i)» = 0; ''') indent(builder); } } + } else if (i instanceof ReactorInstance) { + // Need to increment these indices even if the source and + // destination are the same port because these variables have a + // different suffix from those being used to index the destination. + // No need to do anything if it's not a bank. + if (i.isBank) { + pr(builder, ''' + «CUtil.bankIndex(i, sfx)»++; + if («CUtil.bankIndex(i, sfx)» >= «i.width») { + «CUtil.bankIndex(i, sfx)» = 0; + ''') + indent(builder); + } } - // Now need to close all these if blocks. - for (i : srcIteration) { - if (i instanceof PortInstance) { - // No need to do anything if it's not a multiport. - if (i.isMultiport) { - unindent(builder); - pr(builder, "}"); - } - } else if (i instanceof ReactorInstance) { - // No need to do anything if it's not a bank. - if (i.isBank) { - unindent(builder); - pr(builder, "}"); - } + } + // Now need to close all these if blocks. + for (i : srcIteration) { + if (i instanceof PortInstance) { + // No need to do anything if it's not a multiport or if + // the source and destination ports are the same. + if (i.isMultiport) { + unindent(builder); + pr(builder, "}"); + } + } else if (i instanceof ReactorInstance) { + // No need to do anything if it's not a bank. + if (i.isBank) { + unindent(builder); + pr(builder, "}"); } } } @@ -6249,7 +6280,7 @@ class CGenerator extends GeneratorBase { // Fill the trigger array. «temp.toString()» ''') - channelCount += range.totalWidth; + channelCount += range.width; endScopedRangeBlock(code, range); } From 1921977528557c199fc76b9dd8634d715c99b2a9 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 14 Dec 2021 17:48:31 -0800 Subject: [PATCH 076/221] Deleted obsolete file and minor tuning --- .../org/lflang/generator/ChannelRange.java | 78 ------------------- .../org/lflang/generator/NamedInstance.java | 4 +- .../generator/ReactionInstanceGraph.xtend | 2 +- 3 files changed, 4 insertions(+), 80 deletions(-) delete mode 100644 org.lflang/src/org/lflang/generator/ChannelRange.java diff --git a/org.lflang/src/org/lflang/generator/ChannelRange.java b/org.lflang/src/org/lflang/generator/ChannelRange.java deleted file mode 100644 index caffc732af..0000000000 --- a/org.lflang/src/org/lflang/generator/ChannelRange.java +++ /dev/null @@ -1,78 +0,0 @@ -/** A data structure for a port instance. */ - -/************* -Copyright (c) 2019, 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; - - -/** - * Class representing a range of channels of a port instance. - * If the enclosing port instance is not a multiport, this range will - * be (0,1). - * - * @author Edward A. Lee - */ -public class ChannelRange implements Comparable { - public ChannelRange(PortInstance port, int startChannel, int channelWidth) { - // Some targets determine widths at runtime, in which case a - // width of 0 is reported here. Tolerate that. - if (channelWidth != 0 - && (startChannel < 0 || startChannel >= port.width - || channelWidth < 0 || startChannel + channelWidth > port.width)) { - throw new RuntimeException("Invalid range of port channels."); - } - this.port = port; - this.startChannel = startChannel; - this.channelWidth = channelWidth; - } - - ////////////////////////////////////////////////////// - //// Public methods - - /** The port that contains these channels. */ - public final PortInstance port; - - /** The starting channel index (starting at 0). */ - public final int startChannel; - - /** The width of the range (0 means unknown). */ - public final int channelWidth; - - ////////////////////////////////////////////////////// - //// Public methods - - /** - * Compare the ranges by just comparing their startChannel index. - */ - @Override - public int compareTo(ChannelRange o) { - if (startChannel < o.startChannel) { - return -1; - } else if (startChannel == o.startChannel) { - return 0; - } else { - return 1; - } - } -} diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index 02799c0753..8abfdc1510 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -123,7 +123,9 @@ public ReactorInstance getParent() { } /** - * Return the width of this port, which in this base class is 1. + * Return the width of this instance, which in this base class is 1. + * Subclasses PortInstance and ReactorInstance change this to the + * multiport and bank widths respectively. */ public int getWidth() { return width; diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend index a534690b36..1e8923603f 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend @@ -61,7 +61,7 @@ class ReactionInstanceGraph extends DirectedGraph { } /** - * Rebuild this graph by clearing is an repeating the traversal that + * Rebuild this graph by clearing and repeating the traversal that * adds all the nodes and edges. */ def rebuild() { From 09f334f24a6b307fd4296b3443a9b3bfee460b85 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 15 Dec 2021 07:40:42 -0800 Subject: [PATCH 077/221] Start towards porting ReactionInstanceGraph to Java --- .../lflang/generator/ReactionInstance.java | 21 +++ ...Graph.xtend => ReactionInstanceGraph.java} | 176 ++++++++++-------- 2 files changed, 116 insertions(+), 81 deletions(-) rename org.lflang/src/org/lflang/generator/{ReactionInstanceGraph.xtend => ReactionInstanceGraph.java} (91%) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 2dff7d167a..29db673c11 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -423,4 +423,25 @@ public String toString() { /** Cache of the set of upstream reactions. */ private Set dependsOnReactionsCache; + + /** + * Array of runtime instances of this reaction. + * This has length 1 unless the reaction is contained + * by one or more banks. Suppose that this reaction + * has depth 3, with full name r0.r1.r2.r. The top-level + * reactor is r0, which contains r1, which contains r2, + * which contains this reaction r. Suppose the widths + * of the containing reactors are w0, w1, and w2, and + * we are interested in the instance at bank indexes + * b0, b1, and b2. That instance is in this array at + * location FIXME + */ + FIXME + + /////////////////////////////////////////////////////////// + //// Inner classes + + /** Inner class representing a runtime instance. */ + public static class Runtime { + } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java similarity index 91% rename from org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend rename to org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 1e8923603f..9cde4b1fce 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.xtend +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -24,12 +24,12 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ***************/ -package org.lflang.generator +package org.lflang.generator; -import java.util.ArrayList -import java.util.LinkedHashSet -import java.util.Set -import org.lflang.graph.DirectedGraph +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Set; +import org.lflang.graph.DirectedGraph; /** * This graph represents the dependencies between reaction instances. @@ -39,37 +39,38 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * @author{Marten Lohstroh } * @author{Edward A. Lee } */ -class ReactionInstanceGraph extends DirectedGraph { - - /** - * The main reactor instance that this graph is associated with. - */ - var ReactorInstance main - - /** - * Count of the number of chains while assigning chainIDs. - */ - int branchCount = 1 +class ReactionInstanceGraph extends DirectedGraph { /** * Create a new graph by traversing the maps in the named instances * embedded in the hierarchy of the program. */ - new(ReactorInstance main) { - this.main = main - rebuild() + public ReactionInstanceGraph(ReactorInstance main) { + this.main = main; + rebuild(); } + + /////////////////////////////////////////////////////////// + //// Public fields + + /** + * The main reactor instance that this graph is associated with. + */ + public final ReactorInstance main; + + /////////////////////////////////////////////////////////// + //// Public methods /** * Rebuild this graph by clearing and repeating the traversal that * adds all the nodes and edges. */ - def rebuild() { - this.clear() - addNodesAndEdges(main) + public void rebuild() { + this.clear(); + addNodesAndEdges(main); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. - val leftoverReactions = assignLevels() + val leftoverReactions = assignLevels(); if (leftoverReactions.nodeCount != 0) { // The validator should have caught cycles, but if there is a bug in some // AST transform such that it introduces cycles, then it is possible to have them @@ -79,18 +80,17 @@ def rebuild() { } // Traverse the graph again, now starting from the leaves, // to set the chain IDs. - assignChainIDs(false) + assignChainIDs(false); // Propagate any declared deadline upstream. - propagateDeadlines() - + propagateDeadlines(); } /** * Return the single dominating reaction if the given reaction has one, or * null otherwise. */ - def findSingleDominatingReaction(ReactionInstance reaction) { + public findSingleDominatingReaction(ReactionInstance reaction) { val reactions = getUpstreamAdjacentNodes(reaction) if (reactions.size == 1) { return reactions.get(0) @@ -120,60 +120,6 @@ protected def Set maximal(Set reactions, return result } - /** - * Analyze the dependencies between reactions and assign each reaction - * instance a level. - * This procedure is based on Kahn's algorithm for topological sorting. - * Rather than establishing a total order, we establish a partial order. - * In this order, the level of each reaction is the least upper bound of - * the levels of the reactions it depends on. - * - * @return If any cycles are present in the dependency graph, then a graph - * containing the nodes in the cycle is returned. Otherwise, null is - * returned. - */ - private def DirectedGraph assignLevels() { - val graph = this.copy - var start = new ArrayList(graph.rootNodes) - - // All root nodes start with level 0. - for (origin : start) { - origin.level = 0 - } - - // No need to do any of this if there are no root nodes; - // the graph must be cyclic. - if (!graph.rootNodes.isEmpty) { - while (!start.empty) { - val origin = start.remove(0) - val toRemove = new LinkedHashSet() - // Visit effect nodes. - for (effect : graph.getDownstreamAdjacentNodes(origin)) { - // Stage edge between origin and effect for removal. - toRemove.add(effect) - - // Update level of downstream node. - effect.level = Math.max(effect.level, origin.level+1) - } - // Remove visited edges. - for (effect : toRemove) { - graph.removeEdge(effect, origin) - // If the effect node has no more incoming edges, - // then move it in the start set. - if (graph.getUpstreamAdjacentNodes(effect).size == 0) { - start.add(effect) - } - } - - // Remove visited origin. - graph.removeNode(origin) - - } - } - // If, after all of this, there are still any nodes left, - // then the graph must be cyclic. - return graph - } /** * Analyze the dependencies between reactions and assign each reaction @@ -322,6 +268,8 @@ protected def void addDownstreamReactions(PortInstance port, } } + /////////////////////////////////////////////////////////// + //// Protected methods /** * Build the graph by adding nodes and edges based on the given reactor @@ -361,4 +309,70 @@ protected def void addNodesAndEdges(ReactorInstance reactor) { addNodesAndEdges(child) } } + + /////////////////////////////////////////////////////////// + //// Protected fields + + /** + * Count of the number of chains while assigning chainIDs. + */ + int branchCount = 1; + + /////////////////////////////////////////////////////////// + //// Private methods + + /** + * Analyze the dependencies between reactions and assign each reaction + * instance a level. + * This procedure is based on Kahn's algorithm for topological sorting. + * Rather than establishing a total order, we establish a partial order. + * In this order, the level of each reaction is the least upper bound of + * the levels of the reactions it depends on. + * + * @return If any cycles are present in the dependency graph, then a graph + * containing the nodes in the cycle is returned. Otherwise, null is + * returned. + */ + private DirectedGraph assignLevels() { + val graph = this.copy; + var start = new ArrayList(graph.rootNodes); + + // All root nodes start with level 0. + for (origin : start) { + origin.level = 0; + } + + // No need to do any of this if there are no root nodes; + // the graph must be cyclic. + if (!graph.rootNodes.isEmpty) { + while (!start.empty) { + val origin = start.remove(0) + val toRemove = new LinkedHashSet() + // Visit effect nodes. + for (effect : graph.getDownstreamAdjacentNodes(origin)) { + // Stage edge between origin and effect for removal. + toRemove.add(effect) + + // Update level of downstream node. + effect.level = Math.max(effect.level, origin.level+1) + } + // Remove visited edges. + for (effect : toRemove) { + graph.removeEdge(effect, origin) + // If the effect node has no more incoming edges, + // then move it in the start set. + if (graph.getUpstreamAdjacentNodes(effect).size == 0) { + start.add(effect) + } + } + + // Remove visited origin. + graph.removeNode(origin) + + } + } + // If, after all of this, there are still any nodes left, + // then the graph must be cyclic. + return graph; + } } From 083c841ff20600875b32bd73c371526bca72e231 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 18 Dec 2021 11:43:24 -0800 Subject: [PATCH 078/221] Added MixedRadixInt class plus tests, used in Range class to provide indices of runtime instances in the Range. --- .../tests/compiler/MixedRadixIntTest.java | 87 ++++++ .../org/lflang/tests/compiler/RangeTests.java | 76 ++++++ .../org/lflang/generator/MixedRadixInt.java | 209 +++++++++++++++ .../org/lflang/generator/NamedInstance.java | 10 +- .../src/org/lflang/generator/Range.java | 88 +++++- .../lflang/generator/ReactionInstance.java | 65 ++++- .../generator/ReactionInstanceGraph.java | 253 ++++-------------- .../org/lflang/generator/ReactorInstance.java | 12 + .../org/lflang/generator/c/CGenerator.xtend | 12 +- 9 files changed, 591 insertions(+), 221 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java create mode 100644 org.lflang/src/org/lflang/generator/MixedRadixInt.java diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java new file mode 100644 index 0000000000..510eed6c75 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java @@ -0,0 +1,87 @@ +package org.lflang.tests.compiler; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import org.lflang.generator.MixedRadixInt; + +public class MixedRadixIntTest { + + // Constants used many times below. + List radixes = new ArrayList(List.of(2, 3, 4, 5)); + List digits = new ArrayList(List.of(1, 2, 3, 4)); + + @Test + public void create() throws Exception { + MixedRadixInt num = new MixedRadixInt(digits, radixes); + Assertions.assertEquals("1%2, 2%3, 3%4, 4%5", num.toString()); + Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); + + List altDigits = new ArrayList(List.of(1, 2, 1)); + MixedRadixInt altNum = new MixedRadixInt(altDigits, radixes); + Assertions.assertEquals(11, altNum.get()); + } + + @Test + public void createWithInfinity() throws Exception { + List radixes = new ArrayList(List.of(2, 3, 4)); + MixedRadixInt num = new MixedRadixInt(digits, radixes); + Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); + } + + @Test + public void createWithError() throws Exception { + List radixes = new ArrayList(List.of(2, 3)); + try { + new MixedRadixInt(digits, radixes); + } catch (IllegalArgumentException ex) { + Assertions.assertTrue(ex.getMessage().startsWith("Invalid")); + return; + } + Assertions.assertTrue(false, "Expected exception did not occur."); + } + + @Test + public void createWithNullAndSet() throws Exception { + MixedRadixInt num = new MixedRadixInt(null, radixes); + Assertions.assertEquals(0, num.get()); + Assertions.assertEquals("0%2", num.toString()); + num.set(1 + 2*2 + 3*6 + 4*24); + Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); + } + + @Test + public void testPermutation() throws Exception { + List radixes = new ArrayList(List.of(2, 5)); + List digits = new ArrayList(List.of(1, 2)); + MixedRadixInt num = new MixedRadixInt(digits, radixes); + Assertions.assertEquals(5, num.get()); + + List permutation = new ArrayList(List.of(1, 0)); + MixedRadixInt pnum = num.permute(permutation); + Assertions.assertEquals(7, pnum.get()); + + digits = new ArrayList(List.of(1, 2, 1)); + num = new MixedRadixInt(digits, radixes); + Assertions.assertEquals(15, num.get()); + + pnum = num.permute(permutation); + Assertions.assertEquals(17, pnum.get()); + } + + @Test + public void testIncrement() throws Exception { + List radixes = new ArrayList(List.of(2, 3)); + List digits = new ArrayList(List.of(0, 2)); + MixedRadixInt num = new MixedRadixInt(digits, radixes); + num.increment(); + Assertions.assertEquals(5, num.get()); + Assertions.assertEquals(2, digits.size()); + num.increment(); + Assertions.assertEquals(6, num.get()); + Assertions.assertEquals(3, digits.size()); + } +} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java new file mode 100644 index 0000000000..0dfa922af8 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -0,0 +1,76 @@ +package org.lflang.tests.compiler; + +import java.util.Set; + +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lflang.DefaultErrorReporter; +import org.lflang.ErrorReporter; +import org.lflang.generator.PortInstance; +import org.lflang.generator.Range; +import org.lflang.generator.ReactorInstance; +import org.lflang.lf.LfFactory; +import org.lflang.lf.Port; +import org.lflang.lf.Reactor; +import org.lflang.tests.LFInjectorProvider; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) +public class RangeTests { + + private ErrorReporter reporter = new DefaultErrorReporter(); + private static LfFactory factory = LfFactory.eINSTANCE; + + @Test + public void createRange() throws Exception { + Reactor main = factory.createReactor(); + ReactorInstance maini = new ReactorInstance(main, reporter); + + Reactor a = factory.createReactor(); + a.setName("A"); + ReactorInstance ai = new ReactorInstance(a, reporter, maini); + ai.setWidth(2); + + Reactor b = factory.createReactor(); + b.setName("B"); + ReactorInstance bi = new ReactorInstance(b, reporter, ai); + bi.setWidth(2); + + Port p = factory.createPort(); + p.setName("P"); + PortInstance pi = new PortInstance(p, bi, reporter); + pi.setWidth(2); + + Assertions.assertEquals(".A.B.P", pi.getFullName()); + + Range range = new Range.Port(pi, 3, 4, null); + + Assertions.assertEquals(8, range.maxWidth); + + // The results expected below are derived from the class comment for Range, + // which includes this example. + Set instances = range.instances(pi); + Assertions.assertEquals(Set.of(3, 4, 5, 6), instances); + + instances = range.instances(bi); + Assertions.assertEquals(Set.of(1, 2, 3), instances); + + instances = range.instances(ai); + Assertions.assertEquals(Set.of(0, 1), instances); + + range = range.toggleInterleaved(bi); + instances = range.instances(pi); + Assertions.assertEquals(Set.of(3, 4, 6, 5), instances); + + range = range.toggleInterleaved(ai); + instances = range.instances(pi); + Assertions.assertEquals(Set.of(6, 1, 5, 3), instances); + + range = range.toggleInterleaved(bi); + instances = range.instances(pi); + Assertions.assertEquals(Set.of(5, 2, 6, 3), instances); + } +} diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java new file mode 100644 index 0000000000..c44071f61f --- /dev/null +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -0,0 +1,209 @@ +/* A representation for a mixed radix number. */ + +/* +Copyright (c) 2019-2021, 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; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Representation of a mixed radix integer. + * A mixed radix number is a number representation where each digit can have + * a distinct radix. The radixes are given by a list of numbers, r0, r1, ... , rn, + * where r0 is the radix of the lowest-order digit and rn is the radix of the + * highest order digit that has a specified radix. There is an additional implict + * radix after rn of infinity, which means that the mixed radix number may have a + * total n + 2 digits, where the last digit is unbounded. + * + * The {@link #toString()} method gives a string representation of the number + * where each digit is represented by the string "d%r", where d is the digit + * and r is the radix. For example, the number "1%2, 2%3, 1%4" has value 11, + * 1 + (2*2) + (1*2*3). + * + * @author{Edward A. Lee } + */ +public class MixedRadixInt { + + /** + * Create a mixed radix number with the specified digits and radixes, + * which are given low-order digits first. + * If there is one more digit than radixes, then the last digit is + * assumed to have infinite radix. If there are more digits than that, + * throw an exception. + * @param digits The digits. + * @param radixes The radixes. + */ + public MixedRadixInt(List digits, List radixes) { + if (radixes == null || (digits != null && digits.size() > radixes.size() + 1)) { + throw new IllegalArgumentException("Invalid constructor arguments."); + } + this.radixes = radixes; + if (digits != null) { + this.digits = digits; + } else { + this.digits = new ArrayList(1); + this.digits.add(0); + } + } + + ////////////////////////////////////////////////////////// + //// Public methods + + /** + * Get the value as an integer. + */ + public int get() { + int result = 0; + int scale = 1; + Iterator radixIterator = radixes.iterator(); + for (int digit : digits) { + result += digit * scale; + if (radixIterator.hasNext()) { + scale *= radixIterator.next(); + } + } + return result; + } + + /** + * Increment the number by one. + */ + public void increment() { + increment(0); + } + + /** + * Permute the digits of the number and return a new number. + * The argument is required to be a list of indices from 0 to L-1, + * where L is less than or equal to the length of the radixes list + * specified in the constructor. Each index in this list specifies + * the index from which the corresponding digit should be taken. + * + * For example, if this number is "1%2, 2%5", which has value 5 = 1 + 2*2, + * then permute([1, 0]) will return the number "2%5, 1%2", which + * has value 7 = 2 + 1*5. + * + * If this number has a final radix-infinity term, then that term + * will be included in the result as a radix-infinity term. + * + * @param permutation The permutation array. + * @throws IllegalArgumentException If the argument size does not equal + * the radixes size. + */ + public MixedRadixInt permute(List permutation) { + if (permutation.size() > radixes.size()) { + throw new IllegalArgumentException( + "Permutation list cannot be larger than the radixes, " + + radixes.size()); + } + List newRadixes = new ArrayList(radixes.size()); + List newDigits = new ArrayList(digits.size()); + for (int p : permutation) { + newRadixes.add(radixes.get(p)); + newDigits.add(digits.get(p)); + } + // Handle possible radix-infinity digit. + if (digits.size() > radixes.size()) { + newDigits.add(digits.get(digits.size() - 1)); + } + return new MixedRadixInt(newDigits, newRadixes); + } + + /** + * Set the value of this number to equal that of the specified integer. + * @param v The ordinary integer value of this number. + */ + public void set(int v) { + int temp = v; + int count = 0; + for (int radix : radixes) { + if (count >= digits.size()) { + digits.add(temp % radix); + } else { + digits.set(count, temp % radix); + } + count++; + temp = temp / radix; + } + } + + /** + * Give a string representation of the number, where each digit is + * represented as n%r, where r is the radix. + */ + @Override + public String toString() { + List pieces = new LinkedList(); + Iterator radixIterator = radixes.iterator(); + for (int digit : digits) { + if (! radixIterator.hasNext()) { + pieces.add(digit + "%infinity"); + } else { + pieces.add(digit + "%" + radixIterator.next()); + } + } + return String.join(", ", pieces); + } + + ////////////////////////////////////////////////////////// + //// Private methods + + /** + * Increment the specified digit by one. If an overflow occurs, + * then a radix-infinity digit will be added to the digits array + * if there isn't one there already. + * @param d The digit to increment, which assumed to be less than + * the length of digits. + */ + public void increment(int d) { + int value = digits.get(d) + 1; + if (d >= radixes.size()) { + // We are at a radix infinity digit. + digits.set(d, value); + return; + } + if (value < radixes.get(d)) { + digits.set(d, value); + return; + } else { + digits.set(d, 0); + if (d < digits.size() - 1) { + increment(d + 1); + } else { + digits.add(1); + } + return; + } + } + + ////////////////////////////////////////////////////////// + //// Private variables + + private List radixes; + private List digits; +} diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index 8abfdc1510..db73dd29e7 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -146,7 +146,6 @@ public boolean hasParent(ReactorInstance container) { return false; } - /** * Return a list of all the parents starting with the root(). */ @@ -189,6 +188,15 @@ public ReactorInstance root() { } } + /** + * Set the width. This method is here for testing only and should + * not be used for any other purpose. + * @param width The new width. + */ + public void setWidth(int width) { + this.width = width; + } + /** * Return an identifier for this instance, which has the form "a_b_c" * or "a_b_c_n", where "c" is the name of this instance, "b" is the name diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index 8ed588547a..5b670e92e9 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -1,4 +1,4 @@ -/* Abstract class for ranges of NamedInstance. */ +/* A representation of a range of runtime instances for a NamedInstance. */ /* Copyright (c) 2019-2021, The University of California at Berkeley. @@ -25,8 +25,9 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY */ package org.lflang.generator; +import java.util.ArrayList; import java.util.HashSet; -import java.util.LinkedList; +import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -155,7 +156,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * Modifications always return a new Range. * * @author{Edward A. Lee } -*/ + */ public class Range> implements Comparable> { /** @@ -251,6 +252,82 @@ public int compareTo(Range o) { } } + /** + * Return a set of identifiers for runtime instances of the specified instance + * that lie within this range. If the specified instance is not this Range's + * instance nor any of its containers, then the returned result be + * an empty list. Otherwise, it will a list of **natural identifiers**, + * as defined below, for the instances within the range. + * + * Each **natural identifier** is the integer value of a mixed-radix number + * defined as follows: + * + * * The low-order digit is the is the index of the runtime instance of + * the specified NamedInstance within its container. If the NamedInstance + * is a PortInstance, this will be the multiport channel or 0 if it is not a + * multiport. If the NamedInstance is a ReactorInstance, then it will be the bank + * index or 0 if the reactor is not a bank. The radix for this digit will be + * the multiport width or bank width or 1 if the NamedInstance is neither a + * multiport nor a bank. + * + * * The next digit will be the bank index of the container of the specified + * NamedInstance or 0 if it is not a bank. + * + * * The remaining digits will be bank indices of containers up to but not + * including the top-level reactor (there is no point in including the top-level + * reactor because it is never a bank. + * + * Each index that is returned can be used as an index into an array of + * runtime instances that is assumed to be in a **natural order**. + */ + public Set instances(NamedInstance i) { + Set result = new LinkedHashSet(width); + + List radixes = new ArrayList(i.depth); + List digits = new ArrayList(i.depth); + List permutation = new ArrayList(i.depth); + + // Pre-fill the permutation array with the natural order. + for (int j = 0; j < i.depth; j++) permutation.add(j); + + // Construct the radix and permutation arrays. + // Meanwhile, check whether the specified instance is indeed in the list. + boolean foundMatch = false; + int stride = 1; + int count = 0; + for (NamedInstance io : iterationOrder()) { + if (io.depth <= i.depth) { + // Instance is shallower than or equal to the desired one. + radixes.add(io.width); + permutation.set(i.depth - io.depth, count); + digits.add(0); + if (i == io) foundMatch = true; + count++; + } else { + // Instance is deeper than the desired one. + stride *= io.width; + } + } + if (!foundMatch) return result; + + // Iterate over the range in its own order. + count = 0; + MixedRadixInt indices = new MixedRadixInt(digits, radixes); + while (count < start + width) { + if (count >= start) { + // Found an instance to include in the list. + // Permute it to get natural order. + MixedRadixInt natural = indices.permute(permutation); + result.add(natural.get()); + } + count++; + if (count % stride == 0) { + indices.increment(); + } + } + return result; + } + /** * Return a new Range that is identical to this range but * with width reduced to the specified width. @@ -276,7 +353,7 @@ public Range head(int newWidth) { * shallower). */ public List> iterationOrder() { - LinkedList> result = new LinkedList>(); + ArrayList> result = new ArrayList>(); result.add(instance); ReactorInstance parent = instance.parent; while (parent.depth > 0) { @@ -331,6 +408,9 @@ public String toString() { ////////////////////////////////////////////////////////// //// Public inner classes + /** + * Special case of Range for PortInstance. + */ public static class Port extends Range { public Port(PortInstance instance) { super(instance, null); diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 29db673c11..417b5cd9be 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -26,8 +26,10 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.lflang.TimeValue; @@ -275,18 +277,6 @@ protected ReactionInstance( public Set> triggers = new LinkedHashSet>(); - /** - * Sources through which this reaction instance has been visited. - */ - public Set visited = new LinkedHashSet(); - - /** - * Counter that indicates how many times this node has been visited during - * the graph traversal that sets the chainIDs. Only when this counter hits zero - * shall the traversal continue to explore chains beyond this node. - */ - public int visitsLeft = 0; - ////////////////////////////////////////////////////// //// Public methods. @@ -393,7 +383,7 @@ public ReactionInstance findSingleDominatingReaction() { public String getName() { return "reaction_" + this.index; } - + /** * Purge 'portInstance' from this reaction, removing it from the list * of triggers, sources, effects, and reads. @@ -415,6 +405,51 @@ public String toString() { return getName() + " of " + parent.getFullName(); } + ////////////////////////////////////////////////////// + //// Protected methods. + + /** + * Return an array of runtime instances of this reaction in a + * **natural order**, defined as follows. + * + * The size of the array is the product of the widths of all of the + * container ReactorInstance objects. If none of these is a bank, + * then the size will be 1. Otherwise, the array is indexed as + * follows. Definitions: + * + * * The containers are c0 (the top level), c1 (contained + * by the top level), through cn (the immediate container). + * * The widths of these containers are w0 (equal to 1, since the top-level + * cannot be a bank), w1, through wn. + * * The bank indices within these containers are b0 (always equal to 0) + * through bn. + * + * Then the index is + * + * bn + wn * ( ... (b2 + w2 * (b1 + w1 * (b0 + w0))) ... ) + * + * Since b0 + w0 = 1, that last part is not needed. + * + * This method creates this array the first time it is called, but then + * holds on to it. The array is used by {@link ReactionInstanceGraph} + * to determine and record levels and deadline for runtime instances + * of reactors. + */ + protected List getRuntimeInstances() { + if (runtimeInstances != null) return runtimeInstances; + int size = 1; + ReactorInstance p = parent; + while (p != null) { + size *= p.width; + p = p.parent; + } + runtimeInstances = new ArrayList(size); + for (int i = 0; i < size; i++) { + runtimeInstances.add(new Runtime()); + } + return runtimeInstances; + } + ////////////////////////////////////////////////////// //// Private variables. @@ -436,12 +471,14 @@ public String toString() { * b0, b1, and b2. That instance is in this array at * location FIXME */ - FIXME + private List runtimeInstances; /////////////////////////////////////////////////////////// //// Inner classes /** Inner class representing a runtime instance. */ public static class Runtime { + public int level = 0; + public TimeValue deadline = TimeValue.MAX_VALUE; } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 9cde4b1fce..2de23da82a 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -32,14 +32,21 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.graph.DirectedGraph; /** - * This graph represents the dependencies between reaction instances. - * Upon creation, reactions are assigned levels, chainIDs, and their - * deadlines are propagated. + * This graph represents the dependencies between reaction runtime instances. + * For each ReactionInstance, there may be more than one runtime instance because + * the ReactionInstance may be nested within one or more banks. + * In the worst case, of these runtime instances may have distinct dependencies, + * and hence distinct levels in the graph. Moreover, some of these instances + * may be involved in cycles while others are not. + * + * Upon creation, the runtime instances are created if necessary and stored + * in the ReactionInstance. These instances assigned levels (maximum number of + * upstream reaction instances). * * @author{Marten Lohstroh } * @author{Edward A. Lee } */ -class ReactionInstanceGraph extends DirectedGraph { +class ReactionInstanceGraph extends DirectedGraph { /** * Create a new graph by traversing the maps in the named instances @@ -61,6 +68,20 @@ public ReactionInstanceGraph(ReactorInstance main) { /////////////////////////////////////////////////////////// //// Public methods + /** + * Return the single dominating reaction if the given reaction has one, or + * null otherwise. + */ + public ReactionInstance.Runtime findSingleDominatingReaction(ReactionInstance.Runtime r) { + Set reactions = getUpstreamAdjacentNodes(r); + if (reactions.size() == 1) { + for(ReactionInstance.Runtime reaction : reactions) { + return reaction; + } + } + return null; + } + /** * Rebuild this graph by clearing and repeating the traversal that * adds all the nodes and edges. @@ -70,196 +91,26 @@ public void rebuild() { addNodesAndEdges(main); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. - val leftoverReactions = assignLevels(); - if (leftoverReactions.nodeCount != 0) { + Set leftoverReactions = assignLevels(); + if (leftoverReactions.size() != 0) { // The validator should have caught cycles, but if there is a bug in some // AST transform such that it introduces cycles, then it is possible to have them // only detected here. An end user should never see this. main.reporter.reportError("Reactions form a cycle! " + leftoverReactions.toString()); - throw new InvalidSourceException("Reactions form a cycle!") - } - // Traverse the graph again, now starting from the leaves, - // to set the chain IDs. - assignChainIDs(false); - - // Propagate any declared deadline upstream. - propagateDeadlines(); - } - - /** - * Return the single dominating reaction if the given reaction has one, or - * null otherwise. - */ - public findSingleDominatingReaction(ReactionInstance reaction) { - val reactions = getUpstreamAdjacentNodes(reaction) - if (reactions.size == 1) { - return reactions.get(0) - } - return null - } - - /** - * From the given set of reactions, return the subset that is maximal. - * A reaction in the set is maximal if there is no other reaction in - * the set that depends on it, directly or indirectly. If the argument - * is an empty set, return an empty set. - * @param reactions A set of reaction instances. - * @param minLevel The lowest level reaction to visit. - */ - protected def Set maximal(Set reactions, - long minLevel) { - var result = new LinkedHashSet(reactions) - for (reaction : reactions) { - if (reaction.level >= minLevel && reaction.visitsLeft > 0) { - reaction.visitsLeft-- - val upstream = getUpstreamAdjacentNodes(reaction) - result.removeAll(upstream) - result.removeAll(maximal(upstream.toSet, minLevel)) - } - } - return result - } - - - /** - * Analyze the dependencies between reactions and assign each reaction - * instance a chain identifier. The assigned IDs are such that the - * bitwise conjunction between two chain IDs is always nonzero if there - * exists a dependency between them. This facilitates runtime checks - * to determine whether a reaction is ready to execute or has to wait - * for an upstream reaction to complete. - * @param graph The dependency graph. - * @param optimize Whether or not make assignments that maximize the - * amount of parallelism. If false, just assign 1 to every node. - */ - protected def assignChainIDs(boolean optimize) { - val leafs = this.leafNodes - this.branchCount = 0 - for (node : this.nodes) { - node.visitsLeft = this.getDownstreamAdjacentNodes(node).size - } - if (optimize) { - // Start propagation from the leaf nodes, - // ordered by level from high to low. - for (node : leafs.sortBy[-level]) { - this.propagateUp(node, 1 << (this.branchCount++ % 64)) - } - } else { - for (node: this.nodes) { - node.chainID = 1 - } + throw new InvalidSourceException("Reactions form a cycle!"); } } - /** - * Propagate the given chain ID up one chain, propagate fresh IDs to - * other upstream neighbors, and return a mask that overlaps with all the - * chain IDs that were set upstream as a result of this method invocation. - * The result of propagation is that each node has an ID that overlaps with - * all upstream nodes that can reach it. This means that if a node has a - * lower level than another node, but the two nodes do not have overlapping - * chain IDs, the nodes are nonetheless independent from one another. - * @param current The current node that is being visited. - * @param graph The graph that encodes the dependencies between reactions. - * @param chainID The current chain ID. - */ - private def long propagateUp(ReactionInstance current, long chainID) { - val origins = this.getUpstreamAdjacentNodes(current) - var mask = chainID - var first = true - var id = current.chainID.bitwiseOr(chainID) - current.visitsLeft-- - if (current.visitsLeft > 0) { - current.chainID = id - return chainID; - } - // Iterate over the upstream neighbors by level from high to low. - for (upstream : origins.sortBy[-level]) { - if (first) { - // Stay on the same chain the first time. - first = false - } else { - // Create a new chain ID. - id = 1 << (this.branchCount++ % 64) - } - // Propagate the ID upstream and add all returned bits - // to the mask. - mask = mask.bitwiseOr( - propagateUp(upstream, id)) - } - // Apply the mask to the current chain ID. - // If there were no upstream neighbors, the mask will - // just be the chainID that was passed as an argument. - current.chainID = current.chainID.bitwiseOr(mask) + /////////////////////////////////////////////////////////// + //// Protected methods - return mask - } - - /** - * Iterate over all reactions that have a declared deadline, update their - * inferred deadline, as well as the inferred deadlines of any reactions - * upstream. - */ - def propagateDeadlines() { - val reactionsWithDeadline = this.nodes.filter[it.definition.deadline !== null] - // Assume the graph is acyclic. - for (r : reactionsWithDeadline) { - if (r.declaredDeadline !== null && - r.declaredDeadline.maxDelay !== null) { - // Only lower the inferred deadline (which is set to the max by default), - // if the declared deadline is earlier than the inferred one (based on - // some other downstream deadline). - if (r.declaredDeadline.maxDelay !== null - && r.declaredDeadline.maxDelay.isEarlierThan(r.deadline) - ) { - r.deadline = r.declaredDeadline.maxDelay - } - } - propagateDeadline(r) - } - } - - /** - * Given a reaction instance, propagate its inferred deadline upstream. - * @param downstream Reaction instance with an inferred deadline that - * is to be propagated upstream. - */ - def void propagateDeadline(ReactionInstance downstream) { - for (upstream : this.getUpstreamAdjacentNodes(downstream)) { - // Only lower the inferred deadline (which is set to the max by default), - // if downstream deadline is earlier than the inferred one (based on - // some other downstream deadline). - if (downstream.deadline.isEarlierThan(upstream.deadline)) { - upstream.deadline = downstream.deadline - } - propagateDeadline(upstream) - } - } - - /** - * Add to the graph edges between the given reaction and all the reactions - * that the specified port depends on. - * @param port The port that the given reaction as as a source. - * @param reaction The reaction to relate upstream reactions to. - */ - protected def void addUpstreamReactions(PortInstance port, - ReactionInstance reaction) { - // Reactions in the containing reactor. - port.dependsOnReactions.forEach[this.addEdge(reaction, it)] - // Reactions in upstream reactors. - for (upstreamPort : port.dependsOnPorts) { - addUpstreamReactions(upstreamPort.instance, reaction) - } - } - /** * Add to the graph edges between the given reaction and all the reactions * that depend on the specified port. * @param port The port that the given reaction as as an effect. * @param reaction The reaction to relate downstream reactions to. */ - protected def void addDownstreamReactions(PortInstance port, - ReactionInstance reaction) { + protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { // Reactions in the containing reactor. port.dependentReactions.forEach[this.addEdge(it, reaction)] // Reactions in downstream reactors. @@ -268,19 +119,16 @@ protected def void addDownstreamReactions(PortInstance port, } } - /////////////////////////////////////////////////////////// - //// Protected methods - /** * Build the graph by adding nodes and edges based on the given reactor * instance. * @param reactor The reactor on the basis of which to add nodes and edges. */ - protected def void addNodesAndEdges(ReactorInstance reactor) { - var ReactionInstance previousReaction = null - for (reaction : reactor.reactions) { + protected void addNodesAndEdges(ReactorInstance reactor) { + ReactionInstance previousReaction = null; + for (ReactionInstance reaction : reactor.reactions) { // Add reactions of this reactor. - this.addNode(reaction) + this.addNode(reaction); // Reactions that depend on a port that this reaction writes to // also, by transitivity, depend on this reaction instance. @@ -305,11 +153,26 @@ protected def void addNodesAndEdges(ReactorInstance reactor) { } } // Recursively add nodes and edges from contained reactors. - for (child : reactor.children) { - addNodesAndEdges(child) + for (ReactorInstance child : reactor.children) { + addNodesAndEdges(child); } } + /** + * Add to the graph edges between the given reaction and all the reactions + * that the specified port depends on. + * @param port The port that the given reaction as as a source. + * @param reaction The reaction to relate upstream reactions to. + */ + protected void addUpstreamReactions(PortInstance port, ReactionInstance.Runtime reaction) { + // Reactions in the containing reactor. + port.dependsOnReactions.forEach[this.addEdge(reaction, it)]; + // Reactions in upstream reactors. + for (upstreamPort : port.dependsOnPorts) { + addUpstreamReactions(upstreamPort.instance, reaction) + } + } + /////////////////////////////////////////////////////////// //// Protected fields @@ -333,12 +196,12 @@ protected def void addNodesAndEdges(ReactorInstance reactor) { * containing the nodes in the cycle is returned. Otherwise, null is * returned. */ - private DirectedGraph assignLevels() { - val graph = this.copy; - var start = new ArrayList(graph.rootNodes); + private Set assignLevels() { + ReactionInstanceGraph graph = this.copy; + List start = new ArrayList(graph.rootNodes()); // All root nodes start with level 0. - for (origin : start) { + for (ReactionInstance.Runtime origin : start) { origin.level = 0; } @@ -346,8 +209,8 @@ private DirectedGraph assignLevels() { // the graph must be cyclic. if (!graph.rootNodes.isEmpty) { while (!start.empty) { - val origin = start.remove(0) - val toRemove = new LinkedHashSet() + val origin = start.remove(0); + val toRemove = new LinkedHashSet(); // Visit effect nodes. for (effect : graph.getDownstreamAdjacentNodes(origin)) { // Stage edge between origin and effect for removal. diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index b629423f1f..60b2c39387 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -96,6 +96,18 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, Set un this(ASTUtils.createInstantiation(reactor), null, reporter, -1, unorderedReactions); } + /** + * Create a new instantiation with the specified parent. + * This constructor is here to allow for unit tests. + * It should not be used for any other purpose. + * @param reactor The top-level reactor. + * @param reporter The error reporter. + * @param parent The parent reactor instance. + */ + public ReactorInstance(Reactor reactor, ErrorReporter reporter, ReactorInstance parent) { + this(ASTUtils.createInstantiation(reactor), parent, reporter, -1, null); + } + ////////////////////////////////////////////////////// //// Public fields. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 3e3adc381a..398ddf6c4a 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1027,7 +1027,7 @@ class CGenerator extends GeneratorBase { // between inputs and outputs. pr(startTimeStep.toString) - setReactionPriorities(main, federate) + setReactionPriorities(main) initializeFederate(federate) unindent() @@ -3971,16 +3971,14 @@ class CGenerator extends GeneratorBase { * @param federate A federate to conditionally generate code for * contained reactors or null if there are no federates. */ - def void setReactionPriorities(ReactorInstance reactor, FederateInstance federate) { + def void setReactionPriorities(ReactorInstance reactor) { val temp = new StringBuilder(); var foundOne = false; startScopedBlock(temp, reactor); for (r : reactor.reactions) { - if (federate === null || federate.contains( - r.definition - )) { + if (currentFederate.contains(r.definition)) { foundOne = true; val reactionStructName = '''«CUtil.reactorRef(reactor)»->_lf__reaction_«r.index»''' // xtend doesn't support bitwise operators... @@ -3999,8 +3997,8 @@ class CGenerator extends GeneratorBase { if (foundOne) pr(temp.toString()); for (child : reactor.children) { - if (federate.contains(child)) { - setReactionPriorities(child, federate) + if (currentFederate.contains(child)) { + setReactionPriorities(child) } } } From ed61b49345aa3d7ae871cad3f9e0742c387b158e Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 18 Dec 2021 14:47:59 -0800 Subject: [PATCH 079/221] Converted ReactionInstanceGraph to java, made it self destructive, updated assignment of levels, deadlines, and single dominating reactions. --- org.lflang/src/org/lflang/ModelInfo.java | 2 +- .../src/org/lflang/generator/Range.java | 2 +- .../lflang/generator/ReactionInstance.java | 34 ++- .../generator/ReactionInstanceGraph.java | 201 +++++++++--------- 4 files changed, 115 insertions(+), 124 deletions(-) diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index 1385b7b203..f5a2629e12 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -114,7 +114,7 @@ public void update(Model model, ErrorReporter reporter) { topLevelReactorInstances.add(inst); } else { model.getReactors().forEach( - it -> topLevelReactorInstances.add(new ReactorInstance(it, reporter, null)) + it -> topLevelReactorInstances.add(new ReactorInstance(it, reporter)) ); } // don't store the graph into a field, only the cycles. diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index 5b670e92e9..add7b3fe26 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -255,7 +255,7 @@ public int compareTo(Range o) { /** * Return a set of identifiers for runtime instances of the specified instance * that lie within this range. If the specified instance is not this Range's - * instance nor any of its containers, then the returned result be + * instance nor any of its parents, then the returned result be * an empty list. Otherwise, it will a list of **natural identifiers**, * as defined below, for the instances within the range. * diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 417b5cd9be..5370963877 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -410,25 +410,18 @@ public String toString() { /** * Return an array of runtime instances of this reaction in a - * **natural order**, defined as follows. + * **natural order**, defined as follows. The position within the + * returned list of the runtime instance is given by a mixed-radix + * number where the low-order digit is the bank index within the + * container reactor (or 0 if it is not a bank), the second low order + * digit is the bank index of the container's container (or 0 if + * it is not a bank), etc., until the container that is directly + * contained by the top level (the top-level reactor need not be + * included because its index is always 0). * - * The size of the array is the product of the widths of all of the + * The size of the returned array is the product of the widths of all of the * container ReactorInstance objects. If none of these is a bank, - * then the size will be 1. Otherwise, the array is indexed as - * follows. Definitions: - * - * * The containers are c0 (the top level), c1 (contained - * by the top level), through cn (the immediate container). - * * The widths of these containers are w0 (equal to 1, since the top-level - * cannot be a bank), w1, through wn. - * * The bank indices within these containers are b0 (always equal to 0) - * through bn. - * - * Then the index is - * - * bn + wn * ( ... (b2 + w2 * (b1 + w1 * (b0 + w0))) ... ) - * - * Since b0 + w0 = 1, that last part is not needed. + * then the size will be 1. * * This method creates this array the first time it is called, but then * holds on to it. The array is used by {@link ReactionInstanceGraph} @@ -445,7 +438,11 @@ protected List getRuntimeInstances() { } runtimeInstances = new ArrayList(size); for (int i = 0; i < size; i++) { - runtimeInstances.add(new Runtime()); + Runtime r = new Runtime(); + if (declaredDeadline != null) { + r.deadline = declaredDeadline.maxDelay; + } + runtimeInstances.add(r); } return runtimeInstances; } @@ -480,5 +477,6 @@ protected List getRuntimeInstances() { public static class Runtime { public int level = 0; public TimeValue deadline = TimeValue.MAX_VALUE; + public Runtime dominatingReaction = null; } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 2de23da82a..9cceda5d28 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -1,4 +1,4 @@ -/** A graph that represents the dependencies between reaction instances. */ +/** A graph that represents causality cycles formed by reaction instances. */ /************* Copyright (c) 2021, The University of California at Berkeley. @@ -28,20 +28,28 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; + +import org.lflang.generator.ReactionInstance.Runtime; import org.lflang.graph.DirectedGraph; +import org.lflang.lf.Variable; /** - * This graph represents the dependencies between reaction runtime instances. + * This class analyzes the dependencies between reaction runtime instances. * For each ReactionInstance, there may be more than one runtime instance because * the ReactionInstance may be nested within one or more banks. * In the worst case, of these runtime instances may have distinct dependencies, * and hence distinct levels in the graph. Moreover, some of these instances * may be involved in cycles while others are not. * - * Upon creation, the runtime instances are created if necessary and stored - * in the ReactionInstance. These instances assigned levels (maximum number of - * upstream reaction instances). + * Upon construction of this class, the runtime instances are created if necessary, + * stored each ReactionInstance, and assigned levels (maximum number of + * upstream reaction instances), deadlines, and single dominating reactions. + * + * After creation, the resulting graph will be empty unless there are causality + * cycles, in which case, the resulting graph is a graph of runtime reaction + * instances that form cycles. * * @author{Marten Lohstroh } * @author{Edward A. Lee } @@ -68,20 +76,6 @@ public ReactionInstanceGraph(ReactorInstance main) { /////////////////////////////////////////////////////////// //// Public methods - /** - * Return the single dominating reaction if the given reaction has one, or - * null otherwise. - */ - public ReactionInstance.Runtime findSingleDominatingReaction(ReactionInstance.Runtime r) { - Set reactions = getUpstreamAdjacentNodes(r); - if (reactions.size() == 1) { - for(ReactionInstance.Runtime reaction : reactions) { - return reaction; - } - } - return null; - } - /** * Rebuild this graph by clearing and repeating the traversal that * adds all the nodes and edges. @@ -91,12 +85,10 @@ public void rebuild() { addNodesAndEdges(main); // Assign a level to each reaction. // If there are cycles present in the graph, it will be detected here. - Set leftoverReactions = assignLevels(); - if (leftoverReactions.size() != 0) { - // The validator should have caught cycles, but if there is a bug in some - // AST transform such that it introduces cycles, then it is possible to have them - // only detected here. An end user should never see this. - main.reporter.reportError("Reactions form a cycle! " + leftoverReactions.toString()); + assignLevels(); + if (nodeCount() != 0) { + // The graph has cycles. + main.reporter.reportError("Reactions form a cycle! " + toString()); throw new InvalidSourceException("Reactions form a cycle!"); } } @@ -111,11 +103,37 @@ public void rebuild() { * @param reaction The reaction to relate downstream reactions to. */ protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { - // Reactions in the containing reactor. - port.dependentReactions.forEach[this.addEdge(it, reaction)] - // Reactions in downstream reactors. - for (downstreamPort : port.dependentPorts) { - addDownstreamReactions(downstreamPort.instance, reaction) + // This is a scary piece of code because of the richness of possible + // interconnections using banks and multiports. For any program without + // banks and multiports, each of these for loops iterates exactly once. + List srcRuntimes = reaction.getRuntimeInstances(); + for (SendRange sendRange : port.eventualDestinations()) { + for (int srcIndex : sendRange.instances(reaction.parent)) { + for (Range dstRange : sendRange.destinations) { + for (int dstIndex : dstRange.instances(dstRange.instance.parent)) { + for (ReactionInstance dstReaction : dstRange.instance.dependsOnReactions) { + List dstRuntimes = dstReaction.getRuntimeInstances(); + Runtime srcRuntime = srcRuntimes.get(srcIndex); + Runtime dstRuntime = dstRuntimes.get(dstIndex); + addEdge(srcRuntime, dstRuntime); + + // Propagate the deadlines, if any. + if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { + srcRuntime.deadline = dstRuntime.deadline; + } + + // If this seems to be a single dominating reaction, set it. + // If another upstream reaction shows up, then this will be + // reset to null. + if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { + dstRuntime.dominatingReaction = srcRuntime; + } else { + dstRuntime.dominatingReaction = null; + } + } + } + } + } } } @@ -127,30 +145,37 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti protected void addNodesAndEdges(ReactorInstance reactor) { ReactionInstance previousReaction = null; for (ReactionInstance reaction : reactor.reactions) { - // Add reactions of this reactor. - this.addNode(reaction); + List runtimes = reaction.getRuntimeInstances(); - // Reactions that depend on a port that this reaction writes to - // also, by transitivity, depend on this reaction instance. - reaction.effects.filter(PortInstance).forEach [ effect | - addDownstreamReactions(effect, reaction) - ] + // Add reactions of this reactor. + for (Runtime runtime : runtimes) { + this.addNode(runtime); + } - // Reactions that write to such a port are also reactions that - // that this reaction depends on, by transitivity. - reaction.sources.filter(PortInstance).forEach [ source | - addUpstreamReactions(source, reaction) - ] // If this is not an unordered reaction, then create a dependency // on any previously defined reaction. if (!reaction.isUnordered) { // If there is an earlier reaction in this same reactor, then - // create a link in the reaction graph. - if (previousReaction !== null) { - this.addEdge(reaction, previousReaction) + // create a link in the reaction graph for all runtime instances. + if (previousReaction != null) { + List previousRuntimes = previousReaction.getRuntimeInstances(); + int count = 0; + for (Runtime runtime : runtimes) { + this.addEdge(runtime, previousRuntimes.get(count)); + count++; + } } previousReaction = reaction; } + + // Add downstream reactions. Note that this is sufficient. + // We don't need to also add upstream reactions because this reaction + // will be downstream of those upstream reactions. + for (TriggerInstance effect : reaction.effects) { + if (effect instanceof PortInstance) { + addDownstreamReactions((PortInstance)effect, reaction); + } + } } // Recursively add nodes and edges from contained reactors. for (ReactorInstance child : reactor.children) { @@ -158,84 +183,52 @@ protected void addNodesAndEdges(ReactorInstance reactor) { } } - /** - * Add to the graph edges between the given reaction and all the reactions - * that the specified port depends on. - * @param port The port that the given reaction as as a source. - * @param reaction The reaction to relate upstream reactions to. - */ - protected void addUpstreamReactions(PortInstance port, ReactionInstance.Runtime reaction) { - // Reactions in the containing reactor. - port.dependsOnReactions.forEach[this.addEdge(reaction, it)]; - // Reactions in upstream reactors. - for (upstreamPort : port.dependsOnPorts) { - addUpstreamReactions(upstreamPort.instance, reaction) - } - } - - /////////////////////////////////////////////////////////// - //// Protected fields - - /** - * Count of the number of chains while assigning chainIDs. - */ - int branchCount = 1; - /////////////////////////////////////////////////////////// //// Private methods /** * Analyze the dependencies between reactions and assign each reaction - * instance a level. + * instance a level. This method removes nodes from this graph as it + * assigns levels. Any remaining nodes are part of causality cycles. + * * This procedure is based on Kahn's algorithm for topological sorting. * Rather than establishing a total order, we establish a partial order. * In this order, the level of each reaction is the least upper bound of * the levels of the reactions it depends on. - * - * @return If any cycles are present in the dependency graph, then a graph - * containing the nodes in the cycle is returned. Otherwise, null is - * returned. */ - private Set assignLevels() { - ReactionInstanceGraph graph = this.copy; - List start = new ArrayList(graph.rootNodes()); + private void assignLevels() { + List start = new ArrayList(rootNodes()); // All root nodes start with level 0. - for (ReactionInstance.Runtime origin : start) { + for (Runtime origin : start) { origin.level = 0; } // No need to do any of this if there are no root nodes; // the graph must be cyclic. - if (!graph.rootNodes.isEmpty) { - while (!start.empty) { - val origin = start.remove(0); - val toRemove = new LinkedHashSet(); - // Visit effect nodes. - for (effect : graph.getDownstreamAdjacentNodes(origin)) { - // Stage edge between origin and effect for removal. - toRemove.add(effect) - - // Update level of downstream node. - effect.level = Math.max(effect.level, origin.level+1) - } - // Remove visited edges. - for (effect : toRemove) { - graph.removeEdge(effect, origin) - // If the effect node has no more incoming edges, - // then move it in the start set. - if (graph.getUpstreamAdjacentNodes(effect).size == 0) { - start.add(effect) - } - } - - // Remove visited origin. - graph.removeNode(origin) + while (!start.isEmpty()) { + Runtime origin = start.remove(0); + Set toRemove = new LinkedHashSet(); + // Visit effect nodes. + for (Runtime effect : getDownstreamAdjacentNodes(origin)) { + // Stage edge between origin and effect for removal. + toRemove.add(effect); + // Update level of downstream node. + effect.level = Math.max(effect.level, origin.level+1); + } + // Remove visited edges. + for (Runtime effect : toRemove) { + removeEdge(effect, origin); + // If the effect node has no more incoming edges, + // then move it in the start set. + if (getUpstreamAdjacentNodes(effect).size() == 0) { + start.add(effect); + } } + + // Remove visited origin. + removeNode(origin); } - // If, after all of this, there are still any nodes left, - // then the graph must be cyclic. - return graph; } } From 718eada8151ecffc89df42a5e67c611484134a39 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 18 Dec 2021 15:27:00 -0800 Subject: [PATCH 080/221] Removed spurious level calculations no longer needed (nor correct). --- .../styles/LinguaFrancaShapeExtensions.xtend | 4 +- .../LinguaFrancaDependencyAnalysisTest.xtend | 3 +- .../lflang/generator/ReactionInstance.java | 54 +++------- .../org/lflang/generator/ReactorInstance.java | 98 ++++--------------- 4 files changed, 36 insertions(+), 123 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend index b8f40c5dc1..8330852301 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend @@ -295,8 +295,8 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { // Force calculation of levels for reactions. This calculation // will only be done once. Note that if this fails due to a causality loop, // then some reactions will have level -1. - reaction.root().assignLevels(); - contentContainer.addText("level: " + Long.toString(reaction.level)) => [ + val levels = reaction.getLevels().join(", "); + contentContainer.addText("level(s): " + levels) => [ fontBold = false noSelectionStyle suppressSelectability diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend index 5de81cb1b2..1706b2864f 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend @@ -36,7 +36,6 @@ import org.junit.jupiter.api.^extension.ExtendWith import org.lflang.DefaultErrorReporter import org.lflang.ModelInfo import org.lflang.generator.InvalidSourceException -import org.lflang.generator.ReactionInstanceGraph import org.lflang.generator.ReactorInstance import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory @@ -105,7 +104,7 @@ class LinguaFrancaDependencyAnalysisTest { } try { - val instance = new ReactorInstance(mainDef.reactorClass.toDefinition, new DefaultErrorReporter()) + val instance = new ReactorInstance(mainDef.reactorClass.toDefinition, new DefaultErrorReporter()); new ReactionInstanceGraph(instance) Assertions.fail("No cycle detected") } catch(InvalidSourceException e) { diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 5370963877..2d054ed677 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -72,12 +72,6 @@ protected ReactionInstance( this.index = index; this.isUnordered = isUnordered; - // If the reaction has no port triggers or sources, then - // we can immediately assign it a level. - // We also record it in the root reactor instance - // so that other reactions can be assigned levels as well. - boolean dependsOnPorts = false; - // Identify the dependencies for this reaction. // First handle the triggers. for (TriggerRef trigger : definition.getTriggers()) { @@ -105,15 +99,6 @@ protected ReactionInstance( } } } - // Mark this reaction as depending on a port and therefore not - // eligible to be assigned level 0. However, - // if the port is not connected and doesn't depend on any reactions, - // then it does not interfere with this reaction being given level 0. - if (portInstance != null - && (portInstance.dependsOnPorts.size() > 0 - || portInstance.dependsOnReactions.size() > 0)) { - dependsOnPorts = true; - } } else if (variable instanceof Action) { var actionInstance = parent.lookupActionInstance( (Action)((VarRef)trigger).getVariable()); @@ -158,27 +143,9 @@ protected ReactionInstance( } } } - // Mark this reaction as depending on a port and therefore not - // eligible to be assigned level 0. However, - // if the port is not connected and doesn't depend on any reactions, - // then it does not interfere with this reaction being given level 0. - if (portInstance != null - && (portInstance.dependsOnPorts.size() > 0 - || portInstance.dependsOnReactions.size() > 0)) { - dependsOnPorts = true; - } } } - // Initialize the root's readyReactions queue, which it uses - // to compute levels. - if (!dependsOnPorts) { - if (isUnordered || index == 0) { - level = 0L; - } - root().reactionsWithLevels.add(this); - } - // Finally, handle the effects. for (VarRef effect : definition.getEffects()) { Variable variable = effect.getVariable(); @@ -246,12 +213,6 @@ protected ReactionInstance( */ public TimeValue deadline = new TimeValue(TimeValue.MAX_LONG_DEADLINE, TimeUnit.NSEC); - /** - * The level in the dependence graph. -1 indicates that the level - * has not yet been assigned. - */ - public long level = -1L; - /** * Index of order of occurrence within the reactor definition. * The first reaction has index 0, the second index 1, etc. @@ -373,6 +334,21 @@ public ReactionInstance findSingleDominatingReaction() { } return null; } + + /** + * Return a set of levels that 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 through multiports. + */ + public Set getLevels() { + Set result = new LinkedHashSet(); + // Force calculation of levels if it has not been done. + parent.assignLevels(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.level); + } + return result; + } /** * Return the name of this reaction, which is 'reaction_n', diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 60b2c39387..0b6e6596e9 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -26,9 +26,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; -import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Deque; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; @@ -157,49 +155,15 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, ReactorInstance * in V + E, where V is the number of vertices (reactions) and E * is the number of edges (dependencies between reactions). * - * @return True if successful and false if a causality cycle exists. + * @return An empty graph if successful and otherwise a graph + * with runtime reaction instances that form cycles. */ - public boolean assignLevels() { - if (root() != this) { - return root().assignLevels(); - } else { - // This operation is relatively expensive, so we cache the result. - // This will need a mechanism to force recomputation if - // this class ever supports mutations (e.g. in a Java target). - if (levelsAssignedAlready > 0) return true; - - int count = 0; - while (!reactionsWithLevels.isEmpty()) { - ReactionInstance reaction = reactionsWithLevels.poll(); - count++; - // Check downstream reactions to see whether they can get levels assigned. - for (ReactionInstance downstream : reaction.dependentReactions()) { - // Check whether all upstream of downstream have levels. - long candidateLevel = reaction.level + 1L; - for (ReactionInstance upstream : downstream.dependsOnReactions()) { - if (upstream.level < 0L) { - // downstream reaction is not ready to get a level. - candidateLevel = -1L; - break; - } else if (candidateLevel < upstream.level + 1L) { - candidateLevel = upstream.level + 1L; - } - } - if (candidateLevel > 0 && candidateLevel > downstream.level) { - // Can assign a level to downstream. - downstream.level = candidateLevel; - reactionsWithLevels.add(downstream); - } - } - } - if (count < root().totalNumberOfReactions()) { - reporter.reportError(definition, "Reactions form a causality cycle!"); - levelsAssignedAlready = 0; - return false; - } - levelsAssignedAlready = 1; - return true; + public ReactionInstanceGraph assignLevels() { + if (depth != 0) return root().assignLevels(); + if (cachedReactionLoopGraph == null) { + cachedReactionLoopGraph = new ReactionInstanceGraph(this); } + return cachedReactionLoopGraph; } /** @@ -215,6 +179,15 @@ public ReactorInstance getChildReactorInstance(Instantiation definition) { } return null; } + + /** + * Clear any cached data. + * This is useful if a mutation has been realized. + */ + public void clearCaches() { + cachedReactionLoopGraph = null; + totalNumberOfReactionsCache = -1; + } /** * Return an integer is equal to one plus the total number of reactor instances @@ -591,16 +564,6 @@ public Set transitiveClosure(PortInstance source) { ////////////////////////////////////////////////////// //// Protected fields. - /** - * For a root reactor instance only, this will be a queue of reactions - * that either have been assigned an initial level or are ready to be - * assigned levels. During construction of a top-level ReactorInstance, - * this queue will be populated with reactions anywhere in the hierarchy - * that have been assigned level 0 during construction because they have - * no dependencies on other reactions. - */ - protected Deque reactionsWithLevels = new ArrayDeque(); - /** The generator that created this reactor instance. */ protected ErrorReporter reporter; // FIXME: This accumulates a lot of redundant references @@ -672,28 +635,6 @@ protected TriggerInstance getOrCreateShutdown(TriggerRef tri return shutdownTrigger; } - /** - * Collect all reactions that have not been assigned a level and - * return the list. - * @param reactor The reactor for which to check reactions. - * @param result The list to add reactions to. - * @return The list of reactions without levels. - */ - protected List reactionsWithoutLevels( - ReactorInstance reactor, - List result - ) { - for (ReactionInstance reaction : reactor.reactions) { - if (reaction.level < 0L) { - result.add(reaction); - } - } - for (ReactorInstance child : reactor.children) { - reactionsWithoutLevels(child, result); - } - return result; - } - /** * Add to the specified destinations set all ports that receive data from the * specified source. This includes inputs and outputs at the same level @@ -1029,12 +970,9 @@ private void setInitialWidth() { //// Private fields. /** - * Indicator of whether levels have already been assigned. - * This has value 0 if no attempt has been made, 1 if levels have been - * succesfully assigned, and -1 if a causality loop has prevented levels - * from being assigned. + * Cached reaction graph containing reactions that form a causality loop. */ - private int levelsAssignedAlready = 0; + private ReactionInstanceGraph cachedReactionLoopGraph = null; /** * One plus the number of contained reactor instances From 16e272a24ed2ae6102b6972b174bf8395c95c4de Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 18 Dec 2021 16:38:20 -0800 Subject: [PATCH 081/221] Fixed calculation of levels --- .../lflang/generator/ReactionInstance.java | 19 ++++++++++++++++++- .../generator/ReactionInstanceGraph.java | 4 ++-- .../org/lflang/generator/c/CGenerator.xtend | 12 ++++++++++-- 3 files changed, 30 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 2d054ed677..7ca22c8c7d 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -450,9 +450,26 @@ protected List getRuntimeInstances() { //// Inner classes /** Inner class representing a runtime instance. */ - public static class Runtime { + public class Runtime { public int level = 0; public TimeValue deadline = TimeValue.MAX_VALUE; public Runtime dominatingReaction = null; + + public ReactionInstance getReaction() { + return ReactionInstance.this; + } + @Override + public String toString() { + String result = ReactionInstance.this.toString() + + "(level: " + level; + if (deadline != null && deadline != TimeValue.MAX_VALUE) { + result += ", deadline: " + deadline.toString(); + } + if (dominatingReaction != null) { + result += ", dominating: " + dominatingReaction.getReaction(); + } + result += ")"; + return result; + } } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 9cceda5d28..f8bf81ecbc 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -111,11 +111,11 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti for (int srcIndex : sendRange.instances(reaction.parent)) { for (Range dstRange : sendRange.destinations) { for (int dstIndex : dstRange.instances(dstRange.instance.parent)) { - for (ReactionInstance dstReaction : dstRange.instance.dependsOnReactions) { + for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { List dstRuntimes = dstReaction.getRuntimeInstances(); Runtime srcRuntime = srcRuntimes.get(srcIndex); Runtime dstRuntime = dstRuntimes.get(dstIndex); - addEdge(srcRuntime, dstRuntime); + addEdge(dstRuntime, srcRuntime); // Propagate the deadlines, if any. if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 398ddf6c4a..9733bd9959 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3978,15 +3978,23 @@ class CGenerator extends GeneratorBase { startScopedBlock(temp, reactor); for (r : reactor.reactions) { + val levels = r.getLevels(); + if (levels.size != 1) { + throw new RuntimeException("FIXME: Temporarily can't handle reactions with multiple levels."); + } + var level = -1; + for (l : levels) { + level = l; + } if (currentFederate.contains(r.definition)) { foundOne = true; val reactionStructName = '''«CUtil.reactorRef(reactor)»->_lf__reaction_«r.index»''' // xtend doesn't support bitwise operators... - val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, r.level) + val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, level) val reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL" pr(temp, ''' «reactionStructName».chain_id = «r.chainID.toString»; - // index is the OR of level «r.level» and + // index is the OR of level «level» and // deadline «r.deadline.toNanoSeconds» shifted left 16 bits. «reactionStructName».index = «reactionIndex»; ''') From 9db58d4008d5c3121089a55af9a23adb989ae36e Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 18 Dec 2021 16:55:18 -0800 Subject: [PATCH 082/221] Temporary conservative solution for dominating reaction optimization --- org.lflang/src/org/lflang/generator/ReactionInstance.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 7ca22c8c7d..13435987b5 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -328,7 +328,8 @@ public Set dependsOnReactions() { * null otherwise. */ public ReactionInstance findSingleDominatingReaction() { - if (dependsOnReactions().size() == 1) { + // FIXME: Temporary solution. + if (getRuntimeInstances().size() == 1 && dependsOnReactions().size() == 1) { Iterator upstream = dependsOnReactionsCache.iterator(); return upstream.next(); } From 18f9da9dfa81f799d7d7f1168fff008fdff391c9 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 19 Dec 2021 16:58:44 -0800 Subject: [PATCH 083/221] Some progress towards nested banks. --- .../LinguaFrancaDependencyAnalysisTest.xtend | 3 +- .../org/lflang/tests/compiler/RangeTests.java | 18 + .../src/org/lflang/generator/Range.java | 50 ++- .../org/lflang/generator/ReactorInstance.java | 17 + .../org/lflang/generator/c/CGenerator.xtend | 309 +++++++++--------- .../src/org/lflang/generator/c/CUtil.java | 80 ++++- 6 files changed, 297 insertions(+), 180 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend index 1706b2864f..9d5535dfaf 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend @@ -105,7 +105,8 @@ class LinguaFrancaDependencyAnalysisTest { try { val instance = new ReactorInstance(mainDef.reactorClass.toDefinition, new DefaultErrorReporter()); - new ReactionInstanceGraph(instance) + // FIXME: Why is the following not visible?????????????? + // new ReactionInstanceGraph(instance) Assertions.fail("No cycle detected") } catch(InvalidSourceException e) { Assertions.assertTrue(e.message != null && e.message.contains("Reactions form a cycle!"), diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index 0dfa922af8..dbf7999d00 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -1,5 +1,6 @@ package org.lflang.tests.compiler; +import java.util.List; import java.util.Set; import org.eclipse.xtext.testing.InjectWith; @@ -50,6 +51,8 @@ public void createRange() throws Exception { Assertions.assertEquals(8, range.maxWidth); + Assertions.assertEquals(".A.B.P(3,4)", range.toString()); + // The results expected below are derived from the class comment for Range, // which includes this example. Set instances = range.instances(pi); @@ -61,16 +64,31 @@ public void createRange() throws Exception { instances = range.instances(ai); Assertions.assertEquals(Set.of(0, 1), instances); + // Test startIndices. + Assertions.assertEquals(List.of(1, 1, 0), range.startIndices()); + + // Make first interleaved version. range = range.toggleInterleaved(bi); instances = range.instances(pi); Assertions.assertEquals(Set.of(3, 4, 6, 5), instances); + // Test startIndices. + Assertions.assertEquals(List.of(1, 1, 0), range.startIndices()); + + // Make second interleaved version. range = range.toggleInterleaved(ai); instances = range.instances(pi); Assertions.assertEquals(Set.of(6, 1, 5, 3), instances); + // Test startIndices. + Assertions.assertEquals(List.of(0, 1, 1), range.startIndices()); + + // Make third interleaved version. range = range.toggleInterleaved(bi); instances = range.instances(pi); Assertions.assertEquals(Set.of(5, 2, 6, 3), instances); + + // Test startIndices. + Assertions.assertEquals(List.of(1, 0, 1), range.startIndices()); } } diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index add7b3fe26..031e157d0d 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -35,7 +35,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** * Class representing a range of runtime instance objects - * (port channels, reactions, etc.). This class and its derived classes + * (port channels, reactors, reactions, etc.). This class and its derived classes * have the most detailed information about the structure of a Lingua Franca * program. There are three levels of detail: * @@ -251,7 +251,7 @@ public int compareTo(Range o) { return 1; } } - + /** * Return a set of identifiers for runtime instances of the specified instance * that lie within this range. If the specified instance is not this Range's @@ -367,6 +367,40 @@ public List> iterationOrder() { } return result; } + + /** + * Return the nearest containing ReactorInstance for this instance. + * If this instance is a ReactorInstance, then return it. + * Otherwise, return its parent. + */ + public ReactorInstance parentReactor() { + if (instance instanceof ReactorInstance) { + return (ReactorInstance)instance; + } else { + return instance.getParent(); + } + + } + /** + * Return a list of start indices for this instance and all its parents + * except the top-level reactor in hierarchy order, with this instance's + * start position listed first. For any instance that is neither a + * multiport nor a bank, the index will be 0. + */ + public List startIndices() { + List result = new ArrayList(instance.depth); + // Populate the result with default zeros. + for (int i = 0; i < instance.depth; i++) { + result.add(0); + } + int factor = 1; + for (NamedInstance i : iterationOrder()) { + int iStart = (start / factor) % i.width; + factor *= i.width; + result.set(instance.depth - i.depth, iStart); + } + return result; + } /** * Return a new range that represents the leftover elements @@ -404,6 +438,12 @@ public Range toggleInterleaved(ReactorInstance reactor) { public String toString() { return instance.getFullName() + "(" + start + "," + width + ")"; } + + ////////////////////////////////////////////////////////// + //// Protected variables + + /** Record of which levels are interleaved. */ + Set _interleaved = new HashSet(); ////////////////////////////////////////////////////////// //// Public inner classes @@ -422,10 +462,4 @@ public Port(PortInstance instance, int start, int width, Connection connection) super(instance, start, width, connection); } } - - ////////////////////////////////////////////////////////// - //// Protected variables - - /** Record of which levels are interleaved. */ - Set _interleaved = new HashSet(); } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 0b6e6596e9..6a8e4cbb42 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -289,6 +289,23 @@ public int getTotalNumReactorInstances() { return width * numReactorInstances; } + /** + * If this reactor is a bank or any of its parents is a bank, + * return the total number of runtime instances, which is the product + * of the widths of all the parents. + * Return -1 if the width cannot be determined. + */ + public int getTotalWidth() { + if (width <= 0) return -1; + if (depth > 0) { + int parentWidth = parent.getTotalWidth(); + if (parentWidth <= 0) return -1; + return (parentWidth * width); + } else { + return 1; + } + } + /** * Return the trigger instances (input ports, timers, and actions * that trigger reactions) belonging to this reactor instance. diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 9733bd9959..078a91e8ab 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -752,7 +752,7 @@ class CGenerator extends GeneratorBase { for (action : federate.networkMessageActions) { // Find the corresponding ActionInstance. val actionInstance = main.lookupActionInstance(action) - triggers.add(triggerStructName(actionInstance)) + triggers.add(CUtil.triggerRef(actionInstance, null)) } var actionTableCount = 0 for (trigger : triggers) { @@ -2647,9 +2647,6 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); var foundOne = false; - startScopedBlock(temp); - pr(temp, "int count = 0;"); - startScopedBlock(temp, reactor); val reactionRef = CUtil.reactionRef(reaction) // Next handle triggers of the reaction that come from a multiport output @@ -2657,16 +2654,16 @@ class CGenerator extends GeneratorBase { for (trigger : reaction.triggers) { if (trigger.isStartup) { pr(temp, ''' - _lf_startup_reactions[«startupReactionCount» + count++] = &«reactionRef»; + _lf_startup_reactions[_lf_startup_reactions_count++] = &«reactionRef»; ''') - startupReactionCount += reactor.width; + startupReactionCount += reactor.getTotalWidth(); foundOne = true; } else if (trigger.isShutdown) { pr(temp, ''' - _lf_shutdown_reactions[«shutdownReactionCount» + count++] = &«reactionRef»; + _lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &«reactionRef»; ''') foundOne = true; - shutdownReactionCount += reactor.width; + shutdownReactionCount += reactor.getTotalWidth(); if (targetConfig.tracing !== null) { val description = getShortenedName(reactor) @@ -2678,8 +2675,6 @@ class CGenerator extends GeneratorBase { } } } - endScopedBlock(temp); - endScopedBlock(temp); if (foundOne) pr(initializeTriggerObjects, temp.toString); } } @@ -3275,7 +3270,7 @@ class CGenerator extends GeneratorBase { pr(temp, ''' // Add port «output.getFullName» to array of is_present fields. ''') - startChannelIteration(temp, output); + startChannelIteration(temp, output, null); pr(temp, ''' _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)».is_present; @@ -3465,29 +3460,6 @@ class CGenerator extends GeneratorBase { reactor.name.toLowerCase + "reaction_function_" + reactionIndex } - /** 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. - * @param instance The port or action instance. - * @return The name of the trigger struct. - */ - static def triggerStructName(TriggerInstance instance) { - return CUtil.reactorRef(instance.parent) - + '''->_lf__''' - + instance.name - } - - /** Return a reference to the trigger_t struct for the specified output - * port of a contained reactor that triggers a reaction in the specified reactor. - * @param port The output port of a contained reactor. - * @param reaction The reaction triggered by this port. - * @return The name of the trigger struct, which is in the self struct - * of the container of the reaction. - */ - static def triggerStructName(PortInstance port, ReactorInstance reactor) { - return '''«CUtil.reactorRefContained(port.parent)».«port.name»_trigger''' - } - /** * Generates C code to retrieve port->member * This function is used for clarity and is called whenever struct is allocated on heap memory. @@ -3499,7 +3471,6 @@ class CGenerator extends GeneratorBase { «portName»->«member» ''' - /** * Return the operator used to retrieve struct members */ @@ -3600,6 +3571,11 @@ class CGenerator extends GeneratorBase { generateReactorInstanceExtension(main, reactionsInFederate); generateParameterInitialization(main); + pr(initializeTriggerObjects, ''' + int _lf_startup_reactions_count = 0; + int _lf_shutdown_reactions_count = 0; + '''); + for (child: main.children) { if (currentFederate.contains(child)) { generateReactorInstance(child); @@ -3635,7 +3611,7 @@ class CGenerator extends GeneratorBase { // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. - startScopedBlock(initializeTriggerObjects, instance, false); + startScopedBlock(initializeTriggerObjects, instance, false, null); // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). @@ -4743,10 +4719,11 @@ class CGenerator extends GeneratorBase { * This is required to be followed by {@link endChannelIteration(StringBuilder, PortInstance}. * @param builder Where to write the code. * @param port The port. + * @param suffix A suffix to use for variable names or null for default. */ - protected def void startChannelIteration(StringBuilder builder, PortInstance port) { + protected def void startChannelIteration(StringBuilder builder, PortInstance port, String suffix) { if (port.isMultiport) { - val channel = CUtil.channelIndex(port); + val channel = CUtil.channelIndex(port, suffix); pr(builder, ''' // Port «port.fullName» is a multiport. Iterate over its channels. for (int «channel» = 0; «channel» < «port.width»; «channel»++) { @@ -4778,7 +4755,7 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. */ protected def void startScopedBlock(StringBuilder builder) { - startScopedBlock(builder, null, false); + startScopedBlock(builder, null, false, null); } /** @@ -4797,7 +4774,7 @@ class CGenerator extends GeneratorBase { * @param reactor The reactor instance. */ protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { - startScopedBlock(builder, reactor, true) + startScopedBlock(builder, reactor, true, null) } /** @@ -4816,10 +4793,13 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. * @param reactor The reactor instance or null for a simple scoped block. * @param declare True to declare a pointer to the self struct + * @param suffix A suffix to use for variable names or null for default. */ - protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor, boolean declare) { + protected def void startScopedBlock( + StringBuilder builder, ReactorInstance reactor, boolean declare, String suffix + ) { if (reactor !== null && reactor.isBank) { - val index = CUtil.bankIndex(reactor); + val index = CUtil.bankIndex(reactor, suffix); pr(builder, ''' // Reactor is a bank. Iterate over bank members. for (int «index» = 0; «index» < «reactor.width»; «index»++) { @@ -4828,7 +4808,7 @@ class CGenerator extends GeneratorBase { pr(builder, "{"); } indent(builder); - if (declare && reactor !== null) defineSelfStruct(builder, reactor); + if (declare && reactor !== null) defineSelfStruct(builder, reactor, suffix); } /** @@ -4862,7 +4842,7 @@ class CGenerator extends GeneratorBase { pr(builder, '''int «count» = 0;'''); } startScopedBlock(builder, port.parent); - startChannelIteration(builder, port); + startChannelIteration(builder, port, null); } /** @@ -4905,9 +4885,10 @@ class CGenerator extends GeneratorBase { * * @param builder The string builder into which to write. * @param range The send range. + * @param suffix A suffix to use for variable names or null for default. */ protected def void startScopedRangeBlock( - StringBuilder builder, Range range + StringBuilder builder, Range range, String suffix ) { val port = range.instance; @@ -4916,8 +4897,6 @@ class CGenerator extends GeneratorBase { // and width «range.width». ''') - // The first creates a scope in which we can define a pointer to the self - // struct without fear of redefining. startScopedBlock(builder); // Include range_count variable so generated code can safely use it. @@ -4931,10 +4910,10 @@ class CGenerator extends GeneratorBase { for (var i = iterationOrder.size() - 1; i >= 0; i--) { val instance = iterationOrder.get(i); if (instance === port && port.isMultiport) { - startChannelIteration(builder, port); + startChannelIteration(builder, port, suffix); } else if (instance instanceof ReactorInstance) { // Instance is a parent reactor. - startScopedBlock(builder, instance); + startScopedBlock(builder, instance, true, suffix); } } // Allow code to execute only if we are in range. @@ -4950,9 +4929,10 @@ class CGenerator extends GeneratorBase { * End a scoped block for the specified range. * @param builder The string builder into which to write. * @param range The send range. + * @param suffix A suffix to use for variable names or null for default. */ protected def void endScopedRangeBlock( - StringBuilder builder, Range range + StringBuilder builder, Range range, String suffix ) { val port = range.instance; @@ -4988,47 +4968,38 @@ class CGenerator extends GeneratorBase { } endScopedBlock(builder); } - + /** - * Start a scoped block for the specified pair of ranges. This is like - * {@link startScopedRangeBlock(StringBuilder, Range), - * except that it also defines bank and channel indices for the second - * range and iterates over the minimum width of the two ranges. + * Start a scoped block for the specified range in preparation for + * putting a scoped block inside the iteration produced by + * {@link #startScopedRangeBlock(StringBuilder, Range)}. + * This is meant to be used when iterating over two ranges with the same width. + * * This must be followed by a call to - * {@link endScopedRangeBlock(StringBuilder, Range, Range)}. + * {@link #endScopedRangeBlockOutside(StringBuilder, Range, String)}. * * To allow for the possibility that the srcRange and dstRange * refer to ports with the same parent, the variables used to - * refer to bank indices have to be different. To get the pointer - * to the self struct for the srcRange port, use - * {@link CUtil.portRef(PortInstance)}, and - * to get a pointer to the self struct for the destination, - * use {@link CUtil.portRefDestination(PortInstance)}. + * refer to bank indices have to be different. This function + * uses the specified suffix on variable names. * * @param builder The string builder into which to write. * @param srcRange The source range. - * @param dstRange The destination range. + * @param suffix A suffix to use on variable names. */ - protected def void startScopedRangeBlock( - StringBuilder builder, Range srcRange, Range dstRange + protected def void startScopedRangeBlockOutside( + StringBuilder builder, Range srcRange, String suffix ) { val src = srcRange.instance; - val width = (srcRange.width <= dstRange.width)? srcRange.width : dstRange.width; - val sfx = "_src"; pr(builder, ''' - // Iterate over range1 of «src.fullName» with starting offset «srcRange.start», - // and range2 of «dstRange.instance.fullName» with starting offset «dstRange.start», - // with total width «width». + // Define variables to iterate over range1 of «src.fullName» + // with starting offset «srcRange.start» and width «srcRange.width». ''') - // Define additional variables for range2. + // Define additional variables for range. startScopedBlock(builder); - - // Define the self struct for the top-level, in case there are top-level reactions - // that send to inputs of contained reactors. - defineSelfStruct(builder, src.root, sfx); - + // Declare the variables needed for the source range. // These need to be initialized to starting indices. val srcIteration = srcRange.iterationOrder(); @@ -5036,26 +5007,66 @@ class CGenerator extends GeneratorBase { for (i : srcIteration) { val init = (srcRange.start / factor) % i.width; if (i instanceof PortInstance) { - if (i.isMultiport && i != dstRange.instance) { + if (i.isMultiport) { pr(builder, ''' - int «CUtil.channelIndex(i)» = «init»; + int «CUtil.channelIndex(i, suffix)» = «init»; ''') } } else if (i instanceof ReactorInstance) { if (i.isBank) { pr(builder, ''' - int «CUtil.bankIndex(i, sfx)» = «init»; + int «CUtil.bankIndex(i, suffix)» = «init»; ''') } } factor *= i.width; } + } + + /** + * End the block started with + * {@link #startScopedRangeBlockOutside(StringBuilder, Range, String)}. + * + * @param builder The string builder into which to write. + * @param srcRange The source range. + * @param suffix A suffix to use on variable names. + */ + protected def void endScopedRangeBlockOutside( + StringBuilder builder, Range srcRange, String suffix + ) { + endScopedBlock(builder); + } + + /** + * Start a scoped block for the specified ranges to be put inside the + * iteration generated by + * {@link #startScopedRangeBlock(StringBuilder, Range)}, + * when iterating over two ranges with the same width. + * + * This must be followed by a call to + * {@link #endScopedRangeBlockInside(StringBuilder, Range, String)}. + * + * To allow for the possibility that the srcRange and dstRange + * refer to ports with the same parent, the variables used to + * refer to bank indices have to be different. This function + * uses the specified suffix on variable names. + * + * @param builder The string builder into which to write. + * @param range The source range. + * @param suffix A suffix to use on variable names or null for the default. + */ + protected def void startScopedRangeBlockInside( + StringBuilder builder, Range range, String suffix + ) { + val src = range.instance; - startScopedRangeBlock(builder, dstRange); + // Define the self struct for the top-level, in case there are top-level reactions + // that send to inputs of contained reactors. + defineSelfStruct(builder, src.root, suffix); - for (i : srcIteration) { + for (i : range.iterationOrder()) { if (i instanceof ReactorInstance) { - defineSelfStruct(builder, i, sfx); + defineSelfStruct(builder, i, suffix); } } } @@ -5063,39 +5074,23 @@ class CGenerator extends GeneratorBase { /** * End a scoped block for the specified send range. * @param builder The string builder into which to write. - * @param srcRange The source range. - * @param dstRange The destination range. + * @param range The source range. + * @param suffix A suffix to use on variable names or null for the default. */ - protected def void endScopedRangeBlock( - StringBuilder builder, Range srcRange, Range dstRange + protected def void endScopedRangeBlockInside( + StringBuilder builder, Range range, String suffix ) { - val src = srcRange.instance; - val dst = dstRange.instance; - val sfx = "_src"; - - val srcIteration = srcRange.iterationOrder(); + val srcIteration = range.iterationOrder(); for (i : srcIteration) { if (i instanceof PortInstance) { // No need to do anything if it's not a multiport. if (i.isMultiport) { - if (dst == src) { - // If the source and destination ports are the same, then we do not - // need to increment or reset the channel index. But we do still need - // to check whether to increment the next bank variable. - // If an output port is triggering a reaction in its parent's parent, - // then the destination and source may be the same port. - pr(builder, ''' - if («CUtil.channelIndex(i)» >= «i.width» - 1) { // At end. - ''') - indent(builder); - } else { - pr(builder, ''' - «CUtil.channelIndex(i)»++; - if («CUtil.channelIndex(i)» >= «i.width») { - «CUtil.channelIndex(i)» = 0; - ''') - indent(builder); - } + pr(builder, ''' + «CUtil.channelIndex(i, suffix)»++; + if («CUtil.channelIndex(i, suffix)» >= «i.width») { + «CUtil.channelIndex(i, suffix)» = 0; + ''') + indent(builder); } } else if (i instanceof ReactorInstance) { // Need to increment these indices even if the source and @@ -5104,9 +5099,9 @@ class CGenerator extends GeneratorBase { // No need to do anything if it's not a bank. if (i.isBank) { pr(builder, ''' - «CUtil.bankIndex(i, sfx)»++; - if («CUtil.bankIndex(i, sfx)» >= «i.width») { - «CUtil.bankIndex(i, sfx)» = 0; + «CUtil.bankIndex(i, suffix)»++; + if («CUtil.bankIndex(i, suffix)» >= «i.width») { + «CUtil.bankIndex(i, suffix)» = 0; ''') indent(builder); } @@ -5129,8 +5124,6 @@ class CGenerator extends GeneratorBase { } } } - endScopedRangeBlock(builder, dstRange); - endScopedBlock(builder); } /** @@ -5160,7 +5153,8 @@ class CGenerator extends GeneratorBase { * * @param builder The string builder into which to write. * @param reactor The reactor instance for which to provide a self struct. - * @param suffix An optional suffix to append to the variable name. + * @param suffix An optional suffix to append to the variable name and + * to index names for banks. */ private def void defineSelfStruct( StringBuilder builder, ReactorInstance reactor, String suffix @@ -5196,7 +5190,9 @@ class CGenerator extends GeneratorBase { pr(''' // Connect «src.getFullName» to port «dst.getFullName» ''') - startScopedRangeBlock(code, srcRange, dstRange); + startScopedRangeBlockOutside(code, srcRange, "_src"); + startScopedRangeBlock(code, dstRange, null); + startScopedRangeBlockInside(code, srcRange, "_src"); // Above uses suffix "_src" for the self struct for the source range. val sfx = "_src"; @@ -5216,7 +5212,9 @@ class CGenerator extends GeneratorBase { «CUtil.portRef(dst)» = («destStructType»*)&«CUtil.portRef(src, sfx)»; ''') } - endScopedRangeBlock(code, srcRange, dstRange); + endScopedRangeBlockInside(code, srcRange, "_src"); + endScopedRangeBlock(code, dstRange, null); + endScopedRangeBlockOutside(code, srcRange, "_src"); } } } @@ -5857,7 +5855,7 @@ class CGenerator extends GeneratorBase { // If the rootType is 'void', we need to avoid generating the code // 'sizeof(void)', which some compilers reject. val size = (rootType == 'void') ? '0' : '''sizeof(«rootType»)''' - startChannelIteration(code, output); + startChannelIteration(code, output, null); pr(''' «CUtil.portRef(output)».token = _lf_create_token(«size»); ''') @@ -5886,15 +5884,15 @@ class CGenerator extends GeneratorBase { // instance because it may have a reaction to an output of this instance. for (output : reactor.outputs) { for (sendingRange : output.eventualDestinations) { - pr("// For reference counting, set num_destinations for port " + output.name); + pr("// For reference counting, set num_destinations for port " + output.fullName + "."); - startScopedRangeBlock(code, sendingRange); + startScopedRangeBlock(code, sendingRange, null); pr(''' «CUtil.portRef(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') - endScopedRangeBlock(code, sendingRange); + endScopedRangeBlock(code, sendingRange, null); } } } @@ -5932,7 +5930,7 @@ class CGenerator extends GeneratorBase { // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { - startScopedRangeBlock(code, sendingRange); + startScopedRangeBlock(code, sendingRange, null); // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { @@ -5944,7 +5942,7 @@ class CGenerator extends GeneratorBase { «CUtil.portRefNested(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } - endScopedRangeBlock(code, sendingRange); + endScopedRangeBlock(code, sendingRange, null); } } } @@ -6074,8 +6072,8 @@ class CGenerator extends GeneratorBase { val portStructType = variableStructType(trigger) pr(''' - «CUtil.reactorRefContained(trigger.parent)».«trigger.name»_width = «width»; - «CUtil.reactorRefContained(trigger.parent)».«trigger.name» = («portStructType»**)malloc( + «CUtil.reactorRefNested(trigger.parent)».«trigger.name»_width = «width»; + «CUtil.reactorRefNested(trigger.parent)».«trigger.name» = («portStructType»**)malloc( sizeof(«portStructType»*) * «width»); ''') @@ -6209,32 +6207,25 @@ class CGenerator extends GeneratorBase { // its width will be 1. // We generate the code to fill the triggers array first in a temporary code buffer, // so that we can simultaneously calculate the size of the total array. - for (SendRange range : port.eventualDestinations()) { - - startScopedRangeBlock(code, range); - - // Keep track of which reactors have their self struct declared in scope. - val declared = new HashSet(); - declared.add(port.parent); - + val sfx = "_dst"; + for (SendRange srcRange : port.eventualDestinations()) { + // Make a pass to count destination ranges, putting code in temp. val temp = new StringBuilder(); + startScopedRangeBlock(code, srcRange, null); pr("int destination_index = 0;"); - var destRangeCount = 0; - for (destinationRange : range.destinations) { + var dstRangeCount = 0; // Count the number of entries needed in the triggers array. + for (dstRange : srcRange.destinations) { foundDestinations = true; - val destination = destinationRange.instance; + val dst = dstRange.instance; - if (!declared.contains(destination.parent)) { - // Need to declare the self struct of the destination's parent. - declared.add(destination.parent); - defineSelfStruct(temp, destination.parent); - } - - if (destination.isOutput) { + + startScopedRangeBlock(temp, dstRange, sfx); + + if (dst.isOutput) { // Include this destination port only if it has at least one // reaction in the federation. var belongs = false; - for (destinationReaction : destination.dependentReactions) { + for (destinationReaction : dst.dependentReactions) { if (currentFederate.contains(destinationReaction.parent)) { belongs = true } @@ -6243,23 +6234,19 @@ class CGenerator extends GeneratorBase { pr(temp, ''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. - trigger_array[destination_index++] = &«triggerStructName( - destination, - destination.parent.parent - )»; + trigger_array[destination_index++] = &«CUtil.triggerRefNested(dst, sfx)»; ''') - // One array entry for each destination range is sufficient. - destRangeCount++; + dstRangeCount += dstRange.width; } } else { // Destination is an input port. pr(temp, ''' - // Point to destination port «destination.getFullName»'s trigger struct. - trigger_array[destination_index++] = &«triggerStructName(destination)»; + // Point to destination port «dst.getFullName»'s trigger struct. + trigger_array[destination_index++] = &«CUtil.triggerRef(dst, sfx)»; ''') - // One array entry for each destination range is sufficient. - destRangeCount++; + dstRangeCount += dstRange.width; } + endScopedRangeBlock(temp, dstRange, sfx); } var index = "0"; if (port.isMultiport) { @@ -6270,25 +6257,21 @@ class CGenerator extends GeneratorBase { index = "range_count"; } - // Record the total size of the array. - pr(''' - // Reaction «reaction.index» of «name» triggers «destRangeCount» downstream reactions - // through port «port.getFullName». - «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «index»] = «destRangeCount»; - ''') - // Malloc the memory for the arrays. pr(''' + // Reaction «reaction.index» of «name» triggers «dstRangeCount» downstream reactions + // through port «port.getFullName». + «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «index»] = «dstRangeCount»; // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» - trigger_t** trigger_array = (trigger_t**)malloc(«destRangeCount» * sizeof(trigger_t*)); + trigger_t** trigger_array = (trigger_t**)malloc(«dstRangeCount» * sizeof(trigger_t*)); «CUtil.reactionRef(reaction)».triggers[«channelCount» + «index»] = trigger_array; // Fill the trigger array. «temp.toString()» ''') - channelCount += range.width; + channelCount += srcRange.width; - endScopedRangeBlock(code, range); + endScopedRangeBlock(code, srcRange, null); } } if (!foundDestinations) { diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index c06c079a8c..fa1ac06a60 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -41,6 +41,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.TriggerInstance; import org.lflang.generator.ValueGenerator; import org.lflang.lf.Parameter; import org.lflang.lf.Port; @@ -110,7 +111,20 @@ static public String bankIndex(ReactorInstance instance, String suffix) { * @param instance A reactor instance. */ static public String channelIndex(PortInstance port) { - return port.uniqueID() + "_c"; + return channelIndex(port, null); + } + + /** + * Return a name of a variable to refer to the channel index of a port + * in a bank. This is has the form uniqueIDsuffix where uniqueID + * is an identifier for the instance that is guaranteed to be different + * from the ID of any other instance in the program. + * @param suffix The suffix on the name, or null to use the default "_c". + */ + static public String channelIndex(PortInstance port, String suffix) { + if (!port.isMultiport()) return "0"; + if (suffix == null) suffix = "_c"; + return port.uniqueID() + suffix; } /** @@ -140,7 +154,7 @@ static public String channelIndex(PortInstance port) { static public String indexExpression(ReactorInstance instance) { return indexExpression(instance, null); } - + /** * Return an expression that, when evaluated in a context with * bank index variables defined for the specified reactor and @@ -186,7 +200,7 @@ static public String indexExpression(ReactorInstance instance, String suffix) { ); } } - + /** * Return a reference to the specified port on the self struct of the specified * container reactor. The port is required to have the reactor as either its @@ -225,7 +239,7 @@ static public String portRef( channel = "[" + channelIndex(port) + "]"; } if (isNested) { - return reactorRefContained(port.getParent(), suffix) + "." + port.getName() + channel; + return reactorRefNested(port.getParent(), suffix) + "." + port.getName() + channel; } else { String sourceStruct = CUtil.reactorRef(port.getParent(), suffix); return sourceStruct + "->_lf_" + port.getName() + channel; @@ -348,7 +362,17 @@ static public String portRefNestedName(PortInstance port, String suffix) { * @param reaction The reaction. */ static public String reactionRef(ReactionInstance reaction) { - return reactorRef(reaction.getParent()) + "->_lf__reaction_" + reaction.index; + return reactionRef(reaction, null); + } + + /** + * Return a reference to the reaction entry on the self struct + * of the parent of the specified reaction. + * @param reaction The reaction. + * @param suffix A suffix to use for the parent reactor or null for the default. + */ + static public String reactionRef(ReactionInstance reaction, String suffix) { + return reactorRef(reaction.getParent(), suffix) + "->_lf__reaction_" + reaction.index; } /** @@ -385,8 +409,8 @@ static public String reactorRef(ReactorInstance instance, String suffix) { * * @param reactor The contained reactor. */ - static public String reactorRefContained(ReactorInstance reactor) { - return reactorRefContained(reactor, null); + static public String reactorRefNested(ReactorInstance reactor) { + return reactorRefNested(reactor, null); } /** @@ -402,7 +426,7 @@ static public String reactorRefContained(ReactorInstance reactor) { * @param reactor The contained reactor. * @param suffix An optional suffix to append to the struct variable name. */ - static public String reactorRefContained(ReactorInstance reactor, String suffix) { + static public String reactorRefNested(ReactorInstance reactor, String suffix) { String result = reactorRef(reactor.getParent(), suffix) + "->_lf_" + reactor.getName(); if (reactor.isBank()) { result += "[" + bankIndex(reactor) + "]"; @@ -430,6 +454,46 @@ static public String selfType(ReactorInstance instance) { return selfType(instance.getDefinition().getReactorClass()); } + /** 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. + * @param instance The port or action instance. + * @return The name of the trigger struct. + */ + static public String triggerRef(TriggerInstance instance) { + return triggerRef(instance, null); + } + + /** 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. + * @param instance The port or action instance. + * @param suffix The suffix to use for the reactor reference or null for default. + * @return The name of the trigger struct. + */ + static public String triggerRef(TriggerInstance instance, String suffix) { + return reactorRef(instance.getParent(), suffix) + + "->_lf__" + + instance.getName(); + } + + /** Return a reference to the trigger_t struct for the specified + * port of a contained reactor. + * @param port The output port of a contained reactor. + */ + static public String triggerRefNested(PortInstance port) { + return triggerRefNested(port, null); + } + + /** Return a reference to the trigger_t struct for the specified + * port of a contained reactor. + * @param port The output port of a contained reactor. + * @param suffix The suffix to use for the reactor reference or null for default. + */ + static public String triggerRefNested(PortInstance port, String suffix) { + return reactorRefNested(port.getParent(), suffix) + "." + port.getName() + "_trigger"; + } + ////////////////////////////////////////////////////// //// FIXME: Not clear what the strategy is with the following inner interface. // The {@code ReportCommandErrors} interface allows the From d1fc69d6317d0ea25b449e3b5a742a03d58631c2 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 19 Dec 2021 18:20:16 -0800 Subject: [PATCH 084/221] Fixed initialization of actions --- .../org/lflang/generator/c/CGenerator.xtend | 71 ++++++++----------- .../src/org/lflang/generator/c/CUtil.java | 1 + 2 files changed, 32 insertions(+), 40 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 078a91e8ab..d0716df8ee 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3304,15 +3304,13 @@ class CGenerator extends GeneratorBase { * @param actions The actions. */ private def generateActionInitializations(Iterable actions) { - var foundOne = null as ActionInstance; - val temp = new StringBuilder(); for (action : actions) { if (!action.isShutdown) { - foundOne = action; val triggerStructName = CUtil.reactorRef(action.parent) + "->_lf__" + action.name; var minDelay = action.minDelay var minSpacing = action.minSpacing - pr(temp, ''' + pr(initializeTriggerObjects, ''' + // Initializing action «action.fullName» «triggerStructName».offset = «CUtil.VG.getTargetTime(minDelay)»; «IF minSpacing !== null» «triggerStructName».period = «CUtil.VG.getTargetTime(minSpacing)»; @@ -3323,11 +3321,6 @@ class CGenerator extends GeneratorBase { } triggerCount += action.parent.width; } - if (foundOne !== null) { - startScopedBlock(initializeTriggerObjects, foundOne.parent); - pr(initializeTriggerObjects, temp.toString); - endScopedBlock(initializeTriggerObjects); - } } /** @@ -3358,6 +3351,7 @@ class CGenerator extends GeneratorBase { triggerCount += timer.parent.width; } if (foundOne !== null) { + pr(initializeTriggerObjects, '''// Initializing timer «foundOne.fullName».'''); startScopedBlock(initializeTriggerObjects); pr(initializeTriggerObjects, "int count = 0;"); startScopedBlock(initializeTriggerObjects, foundOne.parent); @@ -6209,17 +6203,26 @@ class CGenerator extends GeneratorBase { // so that we can simultaneously calculate the size of the total array. val sfx = "_dst"; for (SendRange srcRange : port.eventualDestinations()) { - // Make a pass to count destination ranges, putting code in temp. - val temp = new StringBuilder(); + for (dstRange : srcRange.destinations) { + startScopedRangeBlockOutside(code, dstRange, sfx); + } startScopedRangeBlock(code, srcRange, null); - pr("int destination_index = 0;"); - var dstRangeCount = 0; // Count the number of entries needed in the triggers array. + pr(''' + // Reaction «reaction.index» of «name» triggers «srcRange.destinations.size» downstream reactions + // through port «port.getFullName». + «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «CUtil.channelIndex(port)»] = «srcRange.destinations.size»; + // For reaction «reaction.index» of «name», allocate an + // array of trigger pointers for downstream reactions through port «port.getFullName» + trigger_t** trigger_array = (trigger_t**)malloc(«srcRange.destinations.size» * sizeof(trigger_t*)); + «CUtil.reactionRef(reaction)».triggers[«channelCount» + «CUtil.channelIndex(port)»] = trigger_array; + // Fill the trigger array. + int destination_index = 0; // In case of multicast. + ''') for (dstRange : srcRange.destinations) { foundDestinations = true; val dst = dstRange.instance; - - startScopedRangeBlock(temp, dstRange, sfx); + startScopedRangeBlockInside(code, dstRange, sfx); if (dst.isOutput) { // Include this destination port only if it has at least one @@ -6231,47 +6234,35 @@ class CGenerator extends GeneratorBase { } } if (belongs) { - pr(temp, ''' + pr(''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. trigger_array[destination_index++] = &«CUtil.triggerRefNested(dst, sfx)»; ''') - dstRangeCount += dstRange.width; + } else { + // Put in a NULL pointer. + pr(''' + // Port «port.getFullName» has reactions in its parent's parent. + // But those are not in the federation. + trigger_array[destination_index++] = NULL; + ''') } } else { // Destination is an input port. - pr(temp, ''' + pr(''' // Point to destination port «dst.getFullName»'s trigger struct. trigger_array[destination_index++] = &«CUtil.triggerRef(dst, sfx)»; ''') - dstRangeCount += dstRange.width; } - endScopedRangeBlock(temp, dstRange, sfx); - } - var index = "0"; - if (port.isMultiport) { - index = CUtil.channelIndex(port); - } - if (reaction.parent != port.parent && port.parent.isBank) { - // The port may be an input of a contained reactor bank. - index = "range_count"; + endScopedRangeBlockInside(code, dstRange, sfx); } - // Malloc the memory for the arrays. - pr(''' - // Reaction «reaction.index» of «name» triggers «dstRangeCount» downstream reactions - // through port «port.getFullName». - «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «index»] = «dstRangeCount»; - // For reaction «reaction.index» of «name», allocate an - // array of trigger pointers for downstream reactions through port «port.getFullName» - trigger_t** trigger_array = (trigger_t**)malloc(«dstRangeCount» * sizeof(trigger_t*)); - «CUtil.reactionRef(reaction)».triggers[«channelCount» + «index»] = trigger_array; - // Fill the trigger array. - «temp.toString()» - ''') channelCount += srcRange.width; endScopedRangeBlock(code, srcRange, null); + for (dstRange : srcRange.destinations) { + endScopedRangeBlockOutside(code, dstRange, sfx); + } } } if (!foundDestinations) { diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index fa1ac06a60..df401020cb 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -108,6 +108,7 @@ static public String bankIndex(ReactorInstance instance, String suffix) { * in a bank. This is has the form uniqueID_c where uniqueID * is an identifier for the instance that is guaranteed to be different * from the ID of any other instance in the program. + * If the port is not a multiport, then return the string "0". * @param instance A reactor instance. */ static public String channelIndex(PortInstance port) { From 346d1be43b0e06eed233d947a8f878deec4a78bd Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 20 Dec 2021 12:06:03 -0800 Subject: [PATCH 085/221] Start on unit tests for PortInstance. --- .../styles/LinguaFrancaShapeExtensions.xtend | 2 +- .../tests/compiler/PortInstanceTests.java | 196 ++++++++++++++++++ .../org/lflang/tests/compiler/RangeTests.java | 10 +- .../org/lflang/generator/GeneratorBase.xtend | 2 +- .../org/lflang/generator/PortInstance.java | 55 ++--- .../lflang/generator/ReactionInstance.java | 4 +- .../org/lflang/generator/ReactorInstance.java | 21 +- .../src/org/lflang/generator/SendRange.java | 21 +- 8 files changed, 259 insertions(+), 52 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend index 8330852301..cddd974122 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend @@ -296,7 +296,7 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { // will only be done once. Note that if this fails due to a causality loop, // then some reactions will have level -1. val levels = reaction.getLevels().join(", "); - contentContainer.addText("level(s): " + levels) => [ + contentContainer.addText("level: " + levels) => [ fontBold = false noSelectionStyle suppressSelectability diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java new file mode 100644 index 0000000000..2c465e7022 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java @@ -0,0 +1,196 @@ +package org.lflang.tests.compiler; + +import java.util.List; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.lflang.DefaultErrorReporter; +import org.lflang.ErrorReporter; +import org.lflang.generator.PortInstance; +import org.lflang.generator.Range; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.SendRange; +import org.lflang.lf.LfFactory; +import org.lflang.lf.Port; +import org.lflang.lf.Reaction; +import org.lflang.lf.Reactor; + +public class PortInstanceTests { + + private ErrorReporter reporter = new DefaultErrorReporter(); + private static LfFactory factory = LfFactory.eINSTANCE; + + @Test + public void createRange() throws Exception { + Reactor main = factory.createReactor(); + ReactorInstance maini = new ReactorInstance(main, reporter); + + ReactorInstance a = newReactor("A", maini); + ReactorInstance b = newReactor("B", maini); + ReactorInstance c = newReactor("C", maini); + + PortInstance p = newOutputPort("p", a); + PortInstance q = newInputPort("q", b); + PortInstance r = newInputPort("r", c); + + Assertions.assertEquals(".A.p", p.getFullName()); + + connect(p, q); + connect(p, r); + + List sr = p.eventualDestinations(); + // Destinations should be empty because there are no reactions. + Assertions.assertEquals("[]", sr.toString()); + + // Clear caches to make a mutation. + maini.clearCaches(); + newReaction(q); + // Re-retrieve destinations. + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1)]]", sr.toString()); + + maini.clearCaches(); + newReaction(r); + // Re-retrieve destinations. + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1), .C.r(0,1)]]", sr.toString()); + + // Now test multiports. + p.setWidth(3); + r.setWidth(2); + // Have to redo the connections. + clearConnections(maini); + maini.clearCaches(); + connect(p, 0, 1, q, 0, 1); + connect(p, 1, 2, r, 0, 2); + + // Re-retrieve destinations. + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1)], .A.p(1,2)->[.C.r(0,2)]]", sr.toString()); + + // More complicated multiport connection. + clearConnections(maini); + maini.clearCaches(); + + ReactorInstance d = newReactor("D", maini); + PortInstance v = newOutputPort("v", d); + v.setWidth(2); + q.setWidth(3); + connect(v, 0, 2, q, 0, 2); + connect(p, 0, 1, q, 2, 1); + connect(p, 1, 2, r, 0, 2); + + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1)], .A.p(1,2)->[.C.r(0,2)]]", sr.toString()); + + // Additional multicast connection. + maini.clearCaches(); + ReactorInstance e = newReactor("E", maini); + PortInstance s = newPort("s", e); + s.setWidth(3); + newReaction(s); + connect(p, s); + + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.E.s(1,2), .C.r(0,2)]]", sr.toString()); + + // Add hierarchical reactors that further split the ranges. + maini.clearCaches(); + ReactorInstance f = newReactor("F", e); + PortInstance t = newPort("t", f); + newReaction(t); + ReactorInstance g = newReactor("G", e); + PortInstance u = newPort("u", g); + u.setWidth(2); + newReaction(u); + connect(s, 0, 1, t, 0, 1); + connect(s, 1, 2, u, 0, 2); + + sr = p.eventualDestinations(); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.E.s(1,2), .C.r(0,2)]]", sr.toString()); + } + + /** + * Clear connections. This recursively clears them for all contained reactors. + */ + protected void clearConnections(ReactorInstance r) { + for (PortInstance p: r.inputs) { + p.getDependentPorts().clear(); + } + for (PortInstance p: r.outputs) { + p.getDependentPorts().clear(); + } + for (ReactorInstance child: r.children) { + clearConnections(child); + } + } + + /** + * Simple connection of two ports. This should be used only + * for connections that would be allowed in the syntax (i.e., no + * cross-hierarchy connections), but this is not checked. + * @param src The sending port. + * @param dst The receiving port. + */ + protected void connect(PortInstance src, PortInstance dst) { + Range srcRange = new Range.Port(src); + Range dstRange = new Range.Port(dst); + ReactorInstance.connectPortInstances(srcRange, dstRange); + } + + /** + * Connection between multiports. This should be used only + * for connections that would be allowed in the syntax (i.e., no + * cross-hierarchy connections), but this is not checked. + * @param src The sending port. + * @param dst The receiving port. + */ + protected void connect( + PortInstance src, int srcStart, int srcWidth, + PortInstance dst, int dstStart, int dstWidth + ) { + Range srcRange = new Range.Port(src, srcStart, srcWidth, null); + Range dstRange = new Range.Port(dst, dstStart, dstWidth, null); + ReactorInstance.connectPortInstances(srcRange, dstRange); + } + + protected PortInstance newPort(String name, ReactorInstance container) { + Port p = factory.createPort(); + p.setName(name); + return new PortInstance(p, container, reporter); + } + + protected PortInstance newInputPort(String name, ReactorInstance container) { + PortInstance pi = newPort(name, container); + container.inputs.add(pi); + return pi; + } + + protected PortInstance newOutputPort(String name, ReactorInstance container) { + PortInstance pi = newPort(name, container); + container.outputs.add(pi); + return pi; + } + + /** + * Return a new reaction triggered by the specified port. + * @param trigger The triggering port. + */ + protected ReactionInstance newReaction(PortInstance trigger) { + Reaction r = factory.createReaction(); + ReactionInstance result = new ReactionInstance( + r, trigger.getParent(), false, trigger.getDependentReactions().size()); + trigger.getDependentReactions().add(result); + trigger.getParent().reactions.add(result); + return result; + } + + protected ReactorInstance newReactor(String name, ReactorInstance container) { + Reactor r = factory.createReactor(); + r.setName(name); + ReactorInstance ri = new ReactorInstance(r, container, reporter); + container.children.add(ri); + return ri; + } +} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index dbf7999d00..c1e3294595 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -3,11 +3,8 @@ import java.util.List; import java.util.Set; -import org.eclipse.xtext.testing.InjectWith; -import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; import org.lflang.DefaultErrorReporter; import org.lflang.ErrorReporter; import org.lflang.generator.PortInstance; @@ -16,10 +13,7 @@ import org.lflang.lf.LfFactory; import org.lflang.lf.Port; import org.lflang.lf.Reactor; -import org.lflang.tests.LFInjectorProvider; -@ExtendWith(InjectionExtension.class) -@InjectWith(LFInjectorProvider.class) public class RangeTests { private ErrorReporter reporter = new DefaultErrorReporter(); @@ -32,12 +26,12 @@ public void createRange() throws Exception { Reactor a = factory.createReactor(); a.setName("A"); - ReactorInstance ai = new ReactorInstance(a, reporter, maini); + ReactorInstance ai = new ReactorInstance(a, maini, reporter); ai.setWidth(2); Reactor b = factory.createReactor(); b.setName("B"); - ReactorInstance bi = new ReactorInstance(b, reporter, ai); + ReactorInstance bi = new ReactorInstance(b, ai, reporter); bi.setWidth(2); Port p = factory.createPort(); diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 4b538f700c..8bf53e7de0 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -336,7 +336,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { // Reroute connections that have delays associated with them via generated delay reactors. transformDelays() - // Invoke this function a second time because transformations may have introduced new reactors! + // Invoke these functions a second time because transformations may have introduced new reactors! setReactorsAndInstantiationGraph() // First, produce any preamble code that the code generator needs diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 357bc88438..f84a9e8c4b 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -294,49 +294,38 @@ private static List eventualDestinations(Range srcRange } // Start with ports that are downstream of the range. - int srcWidthCovered = 0; - int depWidthCovered = 0; + Range wSrcRange = srcRange; // Working source range. Iterator> dependentPorts = srcPort.dependentPorts.iterator(); if (dependentPorts.hasNext()) { - Range dep = dependentPorts.next(); - while(srcWidthCovered < srcRange.width) { - if (srcRange.start >= depWidthCovered + dep.width) { - // Destination is fully before this range. - depWidthCovered += dep.width; - if (!dependentPorts.hasNext()) break; // This should be an error. - dep = dependentPorts.next(); - continue; + Range wDstRange = dependentPorts.next(); + while(true) { + if (wSrcRange == null) { + // Source range has been fully covered, but there are more + // destinations. For multicast, start over with the source. + wSrcRange = srcRange; } - if (depWidthCovered >= srcRange.start + srcRange.width) { - // Source range is covered. We are finished. - break; + if (wDstRange == null) { + // Destination range is covered. + if (!dependentPorts.hasNext()) break; // All done. + wDstRange = dependentPorts.next(); } - // Dependent port overlaps the range of interest. - // Get a new range that possibly subsets the target range. - // Argument is guaranteed by above checks to be less than - // dep.width, so the result will not be null. - Range subDep = dep.tail(depWidthCovered - srcRange.start); - // The following argument is guaranteed to be greater than - // depWidthCovered - srcRange.getStartOffset(). - subDep = subDep.head(srcRange.width); + // Cover the minimum of the two widths. + int width = Math.min(wSrcRange.width, wDstRange.width); + + // Destinations may be a subset. + Range subDst = wDstRange.head(width); // At this point, dep is the subrange of the dependent port of interest. // Recursively get the send ranges of that destination port. - List dstSendRanges = eventualDestinations(subDep); + List dstSendRanges = eventualDestinations(subDst); // For each returned SendRange, convert it to a SendRange // for the srcRange port rather than the dep port. for (SendRange dstSend : dstSendRanges) { - queue.add(dstSend.newSendRange(srcRange)); - } - depWidthCovered += subDep.width; - srcWidthCovered += subDep.width; - if (dep.start + dep.width <= subDep.start + subDep.width) { - // dep range is exhausted. Get another one. - if (!dependentPorts.hasNext()) break; // This should be an error. - dep = dependentPorts.next(); - depWidthCovered = 0; + queue.add(dstSend.newSendRange(wSrcRange.head(width))); } + wSrcRange = wSrcRange.tail(width); + wDstRange = wDstRange.tail(width); } } @@ -354,7 +343,9 @@ private static List eventualDestinations(Range srcRange if (candidate.width <= next.width) { // Can use all of the channels of candidate. // Import the destinations of next and split it. - candidate.destinations.addAll(next.destinations); + for (Range destination : next.destinations) { + candidate.destinations.add(destination.head(candidate.width)); + } if (candidate.width < next.width) { // The next range has more channels connected to this sender. next = (SendRange)next.tail(candidate.width); diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 13435987b5..7d4c3ccd86 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -55,14 +55,14 @@ public class ReactionInstance extends NamedInstance { /** * Create a new reaction instance from the specified definition * within the specified parent. This constructor should be called - * only by the ReactionInstance class. + * only by the ReactorInstance class, but it is public to enable unit tests. * @param definition A reaction definition. * @param parent The parent reactor instance, which cannot be null. * @param isUnordered Indicator that this reaction is unordered w.r.t. other reactions. * @param index The index of the reaction within the reactor (0 for the * first reaction, 1 for the second, etc.). */ - protected ReactionInstance( + public ReactionInstance( Reaction definition, ReactorInstance parent, boolean isUnordered, diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 6a8e4cbb42..cb3386e304 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -99,10 +99,10 @@ public ReactorInstance(Reactor reactor, ErrorReporter reporter, Set un * This constructor is here to allow for unit tests. * It should not be used for any other purpose. * @param reactor The top-level reactor. - * @param reporter The error reporter. * @param parent The parent reactor instance. + * @param reporter The error reporter. */ - public ReactorInstance(Reactor reactor, ErrorReporter reporter, ReactorInstance parent) { + public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter reporter) { this(ASTUtils.createInstantiation(reactor), parent, reporter, -1, null); } @@ -181,12 +181,21 @@ public ReactorInstance getChildReactorInstance(Instantiation definition) { } /** - * Clear any cached data. + * Clear any cached data in this reactor and its children. * This is useful if a mutation has been realized. */ public void clearCaches() { cachedReactionLoopGraph = null; totalNumberOfReactionsCache = -1; + for (ReactorInstance child : children) { + child.clearCaches(); + } + for (PortInstance port : inputs) { + port.clearCaches(); + } + for (PortInstance port : outputs) { + port.clearCaches(); + } } /** @@ -818,10 +827,14 @@ private ReactorInstance( * side. That is, it sets the interleaved state of the destination * to the exclusive OR of the interleaved state of the two ranges, * and sets the interleaved state of the source to false. + * + * NOTE: This method is public to enable its use in unit tests. + * Otherwise, it should be private. This is why it is defined here. + * * @param src The source range. * @param dst The destination range. */ - private void connectPortInstances( + public static void connectPortInstances( Range src, Range dst ) { diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index 625395f86a..060724f970 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -27,6 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -136,6 +137,20 @@ public SendRange tail(int offset) { return result; } + @Override + public String toString() { + StringBuilder result = new StringBuilder(); + result.append(super.toString()); + result.append("->["); + List dsts = new LinkedList(); + for (Range dst : destinations) { + dsts.add(dst.toString()); + } + result.append(String.join(", ", dsts)); + result.append("]"); + return result.toString(); + } + ////////////////////////////////////////////////////////// //// Protected methods @@ -143,10 +158,8 @@ public SendRange tail(int offset) { * Return a new SendRange that is like this one, but * converted to the specified upstream range. The returned * SendRange inherits the destinations of this range. - * - * Normally, the width of the specified range is - * the same as that of this range, but if it is not, - * then the minimum of the two widths is returned. + * The width of the resulting range is + * the minimum of the two widths. * * @param srcRange A new source range. */ From 62e5a7998a4967f509bdd606bc40f4e931bd9c16 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 20 Dec 2021 16:54:44 -0800 Subject: [PATCH 086/221] Made sorting of ranges deterministic, enabling regression tests, and fixed bugs with nested multiports. --- .../tests/compiler/PortInstanceTests.java | 4 +-- .../org/lflang/generator/PortInstance.java | 16 ++++++++---- .../src/org/lflang/generator/Range.java | 1 + .../src/org/lflang/generator/SendRange.java | 26 +++++++++++++++++-- .../org/lflang/generator/c/CGenerator.xtend | 3 +++ 5 files changed, 41 insertions(+), 9 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java index 2c465e7022..d55a639aab 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java @@ -93,7 +93,7 @@ public void createRange() throws Exception { connect(p, s); sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.E.s(1,2), .C.r(0,2)]]", sr.toString()); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.C.r(0,2), .E.s(1,2)]]", sr.toString()); // Add hierarchical reactors that further split the ranges. maini.clearCaches(); @@ -108,7 +108,7 @@ public void createRange() throws Exception { connect(s, 1, 2, u, 0, 2); sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.s(0,1)], .A.p(1,2)->[.E.s(1,2), .C.r(0,2)]]", sr.toString()); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.F.t(0,1), .E.s(0,1)], .A.p(1,2)->[.C.r(0,2), .E.s(1,2), .E.G.u(0,2)]]", sr.toString()); } /** diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index f84a9e8c4b..d5c3da10fe 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -318,11 +318,17 @@ private static List eventualDestinations(Range srcRange // At this point, dep is the subrange of the dependent port of interest. // Recursively get the send ranges of that destination port. List dstSendRanges = eventualDestinations(subDst); + // Widths of these ranges add up to the width of subDst. // For each returned SendRange, convert it to a SendRange // for the srcRange port rather than the dep port. + int sendOffset = 0; for (SendRange dstSend : dstSendRanges) { - queue.add(dstSend.newSendRange(wSrcRange.head(width))); + queue.add(dstSend.newSendRange(wSrcRange.tail(sendOffset).head(dstSend.width))); + sendOffset += dstSend.width; + if (sendOffset >= subDst.width) { + sendOffset = 0; + } } wSrcRange = wSrcRange.tail(width); wDstRange = wDstRange.tail(width); @@ -348,9 +354,9 @@ private static List eventualDestinations(Range srcRange } if (candidate.width < next.width) { // The next range has more channels connected to this sender. - next = (SendRange)next.tail(candidate.width); - // Truncate the destinations just imported. - candidate = candidate.head(candidate.width); + // Put it back on the queue an poll for a new next. + queue.add(next.tail(candidate.width)); + next = queue.poll(); } else { // We are done with next and can discard it. next = queue.poll(); @@ -364,7 +370,7 @@ private static List eventualDestinations(Range srcRange } else { // Because the result list is sorted, next starts at // a higher channel than candidate. - if (candidate.start + candidate.start <= next.start) { + if (candidate.start + candidate.width <= next.start) { // Can use candidate as is and make next the new candidate. result.add(candidate); candidate = next; diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index 031e157d0d..c4abdf9483 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -234,6 +234,7 @@ public Range( * Compare ranges by first comparing their start offset, and then, * if these are equal, comparing their widths. This comparison is * meaningful only for ranges that have the same instances. + * Note that this can return 0 even if equals() does not return true. */ @Override public int compareTo(Range o) { diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index 060724f970..371bc594a6 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -70,6 +70,27 @@ public SendRange( ////////////////////////////////////////////////////////// //// Public methods + /** + * Override the base class to add additional comparisons so that + * ordering is never ambiguous. This means that sorting will be deterministic. + * Note that this can return 0 even if equals() does not return true. + */ + @Override + public int compareTo(Range o) { + int result = super.compareTo(o); + if (result == 0) { + // Longer destination lists come first. + if (destinations.size() > ((SendRange)o).destinations.size()) { + return -1; + } else if (destinations.size() == ((SendRange)o).destinations.size()) { + return instance.getFullName().compareTo(o.instance.getFullName()); + } else { + return 1; + } + } + return result; + } + /** * Return the total number of destination reactors. Specifically, this * is the number of distinct reactors that have one or more ports @@ -105,7 +126,7 @@ public int getNumberOfDestinationReactors() { @Override public SendRange head(int newWidth) { // NOTE: Cannot use the superclass because it returns a Range, not a SendRange. - if (newWidth >= width) return this; + // Also, cannot return this without applying head() to the destinations. if (newWidth <= 0) return null; SendRange result = new SendRange(instance, start, newWidth); @@ -127,7 +148,8 @@ public SendRange head(int newWidth) { */ @Override public SendRange tail(int offset) { - if (offset == 0) return this; + // NOTE: Cannot use the superclass because it returns a Range, not a SendRange. + // Also, cannot return this without applying tail() to the destinations. if (offset >= width) return null; SendRange result = new SendRange(instance, start + offset, width - offset); diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d0716df8ee..fef7599361 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5054,6 +5054,8 @@ class CGenerator extends GeneratorBase { ) { val src = range.instance; + startScopedBlock(builder); + // Define the self struct for the top-level, in case there are top-level reactions // that send to inputs of contained reactors. defineSelfStruct(builder, src.root, suffix); @@ -5118,6 +5120,7 @@ class CGenerator extends GeneratorBase { } } } + endScopedBlock(builder); } /** From 71526c1356afe2e29faa2c4ad5d90912009a1d6c Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 21 Dec 2021 18:01:38 -0800 Subject: [PATCH 087/221] Temporarily disable single dominating reaction plus more. --- .../tests/compiler/PortInstanceTests.java | 25 +++ .../org/lflang/tests/compiler/RangeTests.java | 42 ++-- .../org/lflang/generator/MixedRadixInt.java | 20 ++ .../src/org/lflang/generator/Range.java | 195 +++++++++++------- .../lflang/generator/ReactionInstance.java | 16 +- .../generator/ReactionInstanceGraph.java | 7 +- .../src/org/lflang/generator/SendRange.java | 43 +++- .../org/lflang/generator/c/CGenerator.xtend | 50 +++-- 8 files changed, 253 insertions(+), 145 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java index d55a639aab..cf07414e7c 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java @@ -111,6 +111,31 @@ public void createRange() throws Exception { Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.F.t(0,1), .E.s(0,1)], .A.p(1,2)->[.C.r(0,2), .E.s(1,2), .E.G.u(0,2)]]", sr.toString()); } + @Test + public void multiportDestination() throws Exception { + Reactor main = factory.createReactor(); + ReactorInstance maini = new ReactorInstance(main, reporter); + + ReactorInstance a = newReactor("A", maini); + ReactorInstance b = newReactor("B", maini); + b.setWidth(4); + + PortInstance p = newOutputPort("p", a); + PortInstance q = newInputPort("q", b); + + connect(p, 0, 1, q, 0, 4); + + List sr = p.eventualDestinations(); + // Destination has no reactions, so empty list is right. + Assertions.assertEquals("[]", sr.toString()); + + maini.clearCaches(); + newReaction(q); + sr = p.eventualDestinations(); + // FIXME: how to make order-independent? + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1), .B.q(3,1), .B.q(2,1), .B.q(1,1)]]", sr.toString()); +} + /** * Clear connections. This recursively clears them for all contained reactors. */ diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index c1e3294595..17d56c35fd 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -10,6 +10,7 @@ import org.lflang.generator.PortInstance; import org.lflang.generator.Range; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.SendRange; import org.lflang.lf.LfFactory; import org.lflang.lf.Port; import org.lflang.lf.Reactor; @@ -49,38 +50,53 @@ public void createRange() throws Exception { // The results expected below are derived from the class comment for Range, // which includes this example. - Set instances = range.instances(pi); - Assertions.assertEquals(Set.of(3, 4, 5, 6), instances); + List instances = range.instances(); + Assertions.assertEquals(List.of(3, 4, 5, 6), instances); - instances = range.instances(bi); - Assertions.assertEquals(Set.of(1, 2, 3), instances); - - instances = range.instances(ai); - Assertions.assertEquals(Set.of(0, 1), instances); + Set parents = range.parentInstances(1); + Assertions.assertEquals(Set.of(1, 2, 3), parents); + + parents = range.parentInstances(2); + Assertions.assertEquals(Set.of(0, 1), parents); // Test startIndices. Assertions.assertEquals(List.of(1, 1, 0), range.startIndices()); + // Create a SendRange sending from and to this range. + SendRange sendRange = new SendRange(pi, 3, 4); + sendRange.destinations.add(range); + + // Test getNumberOfDestinationReactors. + Assertions.assertEquals(3, sendRange.getNumberOfDestinationReactors()); + // Make first interleaved version. range = range.toggleInterleaved(bi); - instances = range.instances(pi); - Assertions.assertEquals(Set.of(3, 4, 6, 5), instances); + instances = range.instances(); + Assertions.assertEquals(List.of(3, 4, 6, 5), instances); // Test startIndices. Assertions.assertEquals(List.of(1, 1, 0), range.startIndices()); // Make second interleaved version. range = range.toggleInterleaved(ai); - instances = range.instances(pi); - Assertions.assertEquals(Set.of(6, 1, 5, 3), instances); + instances = range.instances(); + Assertions.assertEquals(List.of(6, 1, 5, 3), instances); // Test startIndices. Assertions.assertEquals(List.of(0, 1, 1), range.startIndices()); + + // Test instances of the parent. + Assertions.assertEquals(Set.of(3, 0, 2, 1), range.parentInstances(1)); + + // Add this range to the sendRange destinations and verify + // that the number of destination reactors becomes 4. + sendRange.addDestination(range); + Assertions.assertEquals(4, sendRange.getNumberOfDestinationReactors()); // Make third interleaved version. range = range.toggleInterleaved(bi); - instances = range.instances(pi); - Assertions.assertEquals(Set.of(5, 2, 6, 3), instances); + instances = range.instances(); + Assertions.assertEquals(List.of(5, 2, 6, 3), instances); // Test startIndices. Assertions.assertEquals(List.of(1, 0, 1), range.startIndices()); diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index c44071f61f..72734c30ac 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -74,6 +74,26 @@ public MixedRadixInt(List digits, List radixes) { ////////////////////////////////////////////////////////// //// Public methods + /** + * Drop the first n digits and return the resulting mixed-radix number. + * @param n The number of digits to drop. + * @throws IllegalArgumentException If n is equal to or larger than the + * number of digits. + */ + public MixedRadixInt drop(int n) { + if (n == 0) return this; + if (n >= digits.size() || n < 0) { + throw new IllegalArgumentException("Cannot drop more digits than there are!"); + } + List d = new ArrayList(digits.size() - n); + List r = new ArrayList(digits.size() - n); + for (int i = n; i < digits.size(); i++) { + d.add(digits.get(i)); + r.add(radixes.get(i)); + } + return new MixedRadixInt(d, r); + } + /** * Get the value as an integer. */ diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index c4abdf9483..4d431d0c89 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -252,83 +252,7 @@ public int compareTo(Range o) { return 1; } } - - /** - * Return a set of identifiers for runtime instances of the specified instance - * that lie within this range. If the specified instance is not this Range's - * instance nor any of its parents, then the returned result be - * an empty list. Otherwise, it will a list of **natural identifiers**, - * as defined below, for the instances within the range. - * - * Each **natural identifier** is the integer value of a mixed-radix number - * defined as follows: - * - * * The low-order digit is the is the index of the runtime instance of - * the specified NamedInstance within its container. If the NamedInstance - * is a PortInstance, this will be the multiport channel or 0 if it is not a - * multiport. If the NamedInstance is a ReactorInstance, then it will be the bank - * index or 0 if the reactor is not a bank. The radix for this digit will be - * the multiport width or bank width or 1 if the NamedInstance is neither a - * multiport nor a bank. - * - * * The next digit will be the bank index of the container of the specified - * NamedInstance or 0 if it is not a bank. - * - * * The remaining digits will be bank indices of containers up to but not - * including the top-level reactor (there is no point in including the top-level - * reactor because it is never a bank. - * - * Each index that is returned can be used as an index into an array of - * runtime instances that is assumed to be in a **natural order**. - */ - public Set instances(NamedInstance i) { - Set result = new LinkedHashSet(width); - - List radixes = new ArrayList(i.depth); - List digits = new ArrayList(i.depth); - List permutation = new ArrayList(i.depth); - - // Pre-fill the permutation array with the natural order. - for (int j = 0; j < i.depth; j++) permutation.add(j); - - // Construct the radix and permutation arrays. - // Meanwhile, check whether the specified instance is indeed in the list. - boolean foundMatch = false; - int stride = 1; - int count = 0; - for (NamedInstance io : iterationOrder()) { - if (io.depth <= i.depth) { - // Instance is shallower than or equal to the desired one. - radixes.add(io.width); - permutation.set(i.depth - io.depth, count); - digits.add(0); - if (i == io) foundMatch = true; - count++; - } else { - // Instance is deeper than the desired one. - stride *= io.width; - } - } - if (!foundMatch) return result; - - // Iterate over the range in its own order. - count = 0; - MixedRadixInt indices = new MixedRadixInt(digits, radixes); - while (count < start + width) { - if (count >= start) { - // Found an instance to include in the list. - // Permute it to get natural order. - MixedRadixInt natural = indices.permute(permutation); - result.add(natural.get()); - } - count++; - if (count % stride == 0) { - indices.increment(); - } - } - return result; - } - + /** * Return a new Range that is identical to this range but * with width reduced to the specified width. @@ -343,6 +267,25 @@ public Range head(int newWidth) { return new Range(instance, start, newWidth, connection); } + /** + * Return the list of **natural identifiers** for the runtime instances + * in this range. Each returned identifier is an integer representation + * of the mixed-radix number [d0, ... , dn] with radices [w0, ... , wn], + * where d0 is the bank or channel index of this Range's instance, which + * has width w0, and dn is the bank index of its topmost parent below the + * top-level (main) reactor, which has width wn. The depth of this Range's + * instance, therefore, is n - 1. The order of the returned list is the order + * in which the runtime instances should be iterated. + */ + public List instances() { + List result = new ArrayList(width); + List mr = instancesMR(); + for (MixedRadixInt i : mr) { + result.add(i.get()); + } + return result; + } + /** * Return a list containing the instance for this range and all of its * parents, not including the top level reactor, in the order in which @@ -369,6 +312,52 @@ public List> iterationOrder() { return result; } + /** + * Return a set of identifiers for runtime instances of a parent of this + * Range's instance n levels above this Range's instance. If n == 1, for + * example, then this return the identifiers for the parent ReactorInstance. + * + * This returns a list of **natural identifiers**, + * as defined below, for the instances within the range. + * + * The resulting list can be used to count the number of distinct + * runtime instances of this Range's instance (using n == 0) or any of its parents that + * lie within the range and to provide an index into an array of runtime + * instances. + * + * Each **natural identifier** is the integer value of a mixed-radix number + * defined as follows: + * + * * The low-order digit is the index of the runtime instance of i + * within its container. If the NamedInstance + * is a PortInstance, this will be the multiport channel or 0 if it is not a + * multiport. If the NamedInstance is a ReactorInstance, then it will be the bank + * index or 0 if the reactor is not a bank. The radix for this digit will be + * the multiport width or bank width or 1 if the NamedInstance is neither a + * multiport nor a bank. + * + * * The next digit will be the bank index of the container of the specified + * NamedInstance or 0 if it is not a bank. + * + * * The remaining digits will be bank indices of containers up to but not + * including the top-level reactor (there is no point in including the top-level + * reactor because it is never a bank. + * + * Each index that is returned can be used as an index into an array of + * runtime instances that is assumed to be in a **natural order**. + * + * @param n The number of levels up of the parent. This is required to be + * less than the depth of this Range's instance or an exception will be thrown. + */ + public Set parentInstances(int n) { + Set result = new LinkedHashSet(width); + List mr = instancesMR(); + for (MixedRadixInt m : mr) { + result.add(m.drop(n).get()); + } + return result; + } + /** * Return the nearest containing ReactorInstance for this instance. * If this instance is a ReactorInstance, then return it. @@ -406,13 +395,13 @@ public List startIndices() { /** * Return a new range that represents the leftover elements * starting at the specified offset relative to start. - * If start + offset is greater than or equal to the maxWidth, then this returns null. + * If start + offset is greater than or equal to the width, then this returns null. * If this offset is 0 then this returns this range unmodified. * @param offset The number of elements to consume. */ public Range tail(int offset) { if (offset == 0) return this; - if (start + offset >= maxWidth) return null; + if (offset >= width) return null; return new Range(instance, start + offset, width - offset, connection); } @@ -445,6 +434,54 @@ public String toString() { /** Record of which levels are interleaved. */ Set _interleaved = new HashSet(); + + ////////////////////////////////////////////////////////// + //// Protected methods + + /** + * Return the list of MixedRadixInt identifiers for the runtime instances + * in this range. Each returned identifier is a mixed-radix number + * [d0, ... , dn] with radices [w0, ... , wn], where d0 is the bank or + * channel index of this Range's instance, which has width w0, and + * dn is the bank index of its topmost parent below the top-level (main) + * reactor, which has width wn. The depth of this Range's instance, + * therefore, is n - 1. The order of the returned list is the order + * in which the runtime instances should be iterated. + */ + protected List instancesMR() { + List result = new ArrayList(width); + + // First, build mixed-radix number with value 0. + List radixes = new ArrayList(instance.depth); + List digits = new ArrayList(instance.depth); + List permutation = new ArrayList(instance.depth); + + // Pre-fill the permutation array with the natural order. + for (int j = 0; j < instance.depth; j++) permutation.add(j); + + // Construct the radix and permutation arrays. + int count = 0; + for (NamedInstance io : iterationOrder()) { + radixes.add(io.width); + permutation.set(instance.depth - io.depth, count); + digits.add(0); + count++; + } + + // Iterate over the range in its own order. + count = 0; + MixedRadixInt indices = new MixedRadixInt(digits, radixes); + while (count < start + width) { + if (count >= start) { + // Found an instance to include in the list. + // Permute it to get natural order. + result.add(indices.permute(permutation)); + } + indices.increment(); + count++; + } + return result; + } ////////////////////////////////////////////////////////// //// Public inner classes diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 7d4c3ccd86..0fbabc4d3a 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -323,19 +323,6 @@ public Set dependsOnReactions() { return dependsOnReactionsCache; } - /** - * Return the single dominating reaction if this reaction has one, or - * null otherwise. - */ - public ReactionInstance findSingleDominatingReaction() { - // FIXME: Temporary solution. - if (getRuntimeInstances().size() == 1 && dependsOnReactions().size() == 1) { - Iterator upstream = dependsOnReactionsCache.iterator(); - return upstream.next(); - } - return null; - } - /** * Return a set of levels that instances of this reaction have. * A ReactionInstance may have more than one level if it lies within @@ -443,7 +430,8 @@ protected List getRuntimeInstances() { * of the containing reactors are w0, w1, and w2, and * we are interested in the instance at bank indexes * b0, b1, and b2. That instance is in this array at - * location FIXME + * location given by the **natural ordering**, which + * is the mixed radix number b2%w2; b1%w1. */ private List runtimeInstances; diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index f8bf81ecbc..4ce48ecf15 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -99,7 +99,7 @@ public void rebuild() { /** * Add to the graph edges between the given reaction and all the reactions * that depend on the specified port. - * @param port The port that the given reaction as as an effect. + * @param port The port that the given reaction has as an effect. * @param reaction The reaction to relate downstream reactions to. */ protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { @@ -108,9 +108,10 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti // banks and multiports, each of these for loops iterates exactly once. List srcRuntimes = reaction.getRuntimeInstances(); for (SendRange sendRange : port.eventualDestinations()) { - for (int srcIndex : sendRange.instances(reaction.parent)) { + int depth = (port.isInput())? 2 : 1; + for (int srcIndex : sendRange.parentInstances(depth)) { for (Range dstRange : sendRange.destinations) { - for (int dstIndex : dstRange.instances(dstRange.instance.parent)) { + for (int dstIndex : dstRange.parentInstances(1)) { for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { List dstRuntimes = dstReaction.getRuntimeInstances(); Runtime srcRuntime = srcRuntimes.get(srcIndex); diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index 371bc594a6..35c1ecdaaf 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -26,9 +26,10 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; -import java.util.HashSet; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -70,6 +71,22 @@ public SendRange( ////////////////////////////////////////////////////////// //// Public methods + /** + * Add a destination to the list of destinations of this range. + * If the width of the destination is not a multiple of the width + * of this range, throw an exception. + * @throws IllegalArgumentException If the width doesn't match. + */ + public void addDestination(Range dst) { + if (dst.width % width != 0) { + throw new IllegalArgumentException( + "Destination range width is not a multiple of sender's width"); + } + destinations.add(dst); + // Void any precomputed number of destinations. + _numberOfDestinationReactors = -1; + } + /** * Override the base class to add additional comparisons so that * ordering is never ambiguous. This means that sorting will be deterministic. @@ -93,22 +110,28 @@ public int compareTo(Range o) { /** * Return the total number of destination reactors. Specifically, this - * is the number of distinct reactors that have one or more ports - * with dependent reactions in the reactor. + * is the number of distinct reactors that react to messages from this + * send range. */ public int getNumberOfDestinationReactors() { if (_numberOfDestinationReactors < 0) { // Has not been calculated before. Calculate now. - Set result = new HashSet(); + _numberOfDestinationReactors = 0; + Map> result = new HashMap>(); for (Range destination : this.destinations) { - for (NamedInstance instance : destination.iterationOrder()) { - if (instance instanceof PortInstance - && !((PortInstance)instance).dependentReactions.isEmpty()) { - result.add(instance.getParent()); - } + // The following set contains unique identifiers the parent reactors + // of destination ports. + Set parentIDs = destination.parentInstances(1); + Set previousParentIDs = result.get(destination.instance.parent); + if (previousParentIDs == null) { + result.put(destination.instance.parent, parentIDs); + } else { + previousParentIDs.addAll(parentIDs); } } - _numberOfDestinationReactors = result.size(); + for (ReactorInstance parent : result.keySet()) { + _numberOfDestinationReactors += result.get(parent).size(); + } } return _numberOfDestinationReactors; } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index fef7599361..c653994c62 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4934,17 +4934,13 @@ class CGenerator extends GeneratorBase { unindent(builder); pr(builder, "}"); // End of predicate on range_count. } - if (range.width > 1) { - pr(builder, ''' - range_count++; - ''') - } + pr(builder, ''' + range_count++; + ''') - val iterationOrder = range.iterationOrder(); - // We need to iterate backwards over the iteration order. - for (var i = iterationOrder.size() - 1; i >= 0; i--) { - val instance = iterationOrder.get(i); + // We need to iterate forwards over the iteration order. + for (instance : range.iterationOrder()) { if (instance === port && port.isMultiport) { pr(builder, ''' if (range_count >= «range.start» + «range.width») break; @@ -6005,16 +6001,20 @@ class CGenerator extends GeneratorBase { ) { val reactor = reaction.parent; + // FIXME // This is called within a scoped block for the parent. + // Use the index variables for the parent to index into the Runtimes + // field of the reaction. Do a run-length encoding of the result. + // Record the number of reactions that this reaction depends on. // This is used for optimization. When that number is 1, the reaction can // be executed immediately when its triggering reaction has completed. - var dominatingReaction = reaction.findSingleDominatingReaction(); + var dominatingReaction = null as ReactionInstance; // reaction.findSingleDominatingReaction(); // The dominating reaction may not be included in this federate, in which case, we need to keep searching. while (dominatingReaction !== null && (!currentFederate.contains(dominatingReaction.definition)) ) { - dominatingReaction = dominatingReaction.findSingleDominatingReaction(); + dominatingReaction = null as ReactionInstance; // dominatingReaction.findSingleDominatingReaction(); } val reactionRef = CUtil.reactionRef(reaction); if (dominatingReaction !== null @@ -6206,10 +6206,8 @@ class CGenerator extends GeneratorBase { // so that we can simultaneously calculate the size of the total array. val sfx = "_dst"; for (SendRange srcRange : port.eventualDestinations()) { - for (dstRange : srcRange.destinations) { - startScopedRangeBlockOutside(code, dstRange, sfx); - } startScopedRangeBlock(code, srcRange, null); + val triggerArray = '''«CUtil.reactionRef(reaction)».triggers[«channelCount» + «CUtil.channelIndex(port)»]''' pr(''' // Reaction «reaction.index» of «name» triggers «srcRange.destinations.size» downstream reactions // through port «port.getFullName». @@ -6217,14 +6215,18 @@ class CGenerator extends GeneratorBase { // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» trigger_t** trigger_array = (trigger_t**)malloc(«srcRange.destinations.size» * sizeof(trigger_t*)); - «CUtil.reactionRef(reaction)».triggers[«channelCount» + «CUtil.channelIndex(port)»] = trigger_array; - // Fill the trigger array. - int destination_index = 0; // In case of multicast. + «triggerArray» = trigger_array; ''') + endScopedRangeBlock(code, srcRange, null); + channelCount += srcRange.width; + + var multicastCount = 0; for (dstRange : srcRange.destinations) { foundDestinations = true; val dst = dstRange.instance; + startScopedRangeBlockOutside(code, dstRange, sfx); + startScopedRangeBlock(code, srcRange, null); startScopedRangeBlockInside(code, dstRange, sfx); if (dst.isOutput) { @@ -6240,31 +6242,27 @@ class CGenerator extends GeneratorBase { pr(''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. - trigger_array[destination_index++] = &«CUtil.triggerRefNested(dst, sfx)»; + «triggerArray»[«multicastCount»] = &«CUtil.triggerRefNested(dst, sfx)»; ''') } else { // Put in a NULL pointer. pr(''' // Port «port.getFullName» has reactions in its parent's parent. // But those are not in the federation. - trigger_array[destination_index++] = NULL; + «triggerArray»[«multicastCount»] = NULL; ''') } } else { // Destination is an input port. pr(''' // Point to destination port «dst.getFullName»'s trigger struct. - trigger_array[destination_index++] = &«CUtil.triggerRef(dst, sfx)»; + «triggerArray»[«multicastCount»] = &«CUtil.triggerRef(dst, sfx)»; ''') } endScopedRangeBlockInside(code, dstRange, sfx); - } - - channelCount += srcRange.width; - - endScopedRangeBlock(code, srcRange, null); - for (dstRange : srcRange.destinations) { + endScopedRangeBlock(code, srcRange, null); endScopedRangeBlockOutside(code, dstRange, sfx); + multicastCount++; } } } From b77d075a74581469756a7520abbb2e56d9fd94f8 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 22 Dec 2021 09:28:39 -0800 Subject: [PATCH 088/221] Interrim checkin --- .../lflang/generator/ReactionInstance.java | 60 ++++---- .../generator/ReactionInstanceGraph.java | 47 +++--- .../org/lflang/generator/ReactorInstance.java | 49 ++++-- .../org/lflang/generator/c/CGenerator.xtend | 144 ++++++++++++------ .../src/org/lflang/generator/c/CUtil.java | 13 ++ 5 files changed, 198 insertions(+), 115 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 0fbabc4d3a..852bcbfaad 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -27,7 +27,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -348,30 +347,6 @@ public String getName() { return "reaction_" + this.index; } - /** - * Purge 'portInstance' from this reaction, removing it from the list - * of triggers, sources, effects, and reads. - */ - public void removePortInstance(PortInstance portInstance) { - this.triggers.remove(portInstance); - this.sources.remove(portInstance); - this.effects.remove(portInstance); - this.reads.remove(portInstance); - clearCaches(); - portInstance.clearCaches(); - } - - /** - * Return a descriptive string. - */ - @Override - public String toString() { - return getName() + " of " + parent.getFullName(); - } - - ////////////////////////////////////////////////////// - //// Protected methods. - /** * Return an array of runtime instances of this reaction in a * **natural order**, defined as follows. The position within the @@ -392,17 +367,13 @@ public String toString() { * to determine and record levels and deadline for runtime instances * of reactors. */ - protected List getRuntimeInstances() { + public List getRuntimeInstances() { if (runtimeInstances != null) return runtimeInstances; - int size = 1; - ReactorInstance p = parent; - while (p != null) { - size *= p.width; - p = p.parent; - } + int size = parent.getTotalWidth(); runtimeInstances = new ArrayList(size); for (int i = 0; i < size; i++) { Runtime r = new Runtime(); + r.id = i; if (declaredDeadline != null) { r.deadline = declaredDeadline.maxDelay; } @@ -411,6 +382,27 @@ protected List getRuntimeInstances() { return runtimeInstances; } + /** + * Purge 'portInstance' from this reaction, removing it from the list + * of triggers, sources, effects, and reads. + */ + public void removePortInstance(PortInstance portInstance) { + this.triggers.remove(portInstance); + this.sources.remove(portInstance); + this.effects.remove(portInstance); + this.reads.remove(portInstance); + clearCaches(); + portInstance.clearCaches(); + } + + /** + * Return a descriptive string. + */ + @Override + public String toString() { + return getName() + " of " + parent.getFullName(); + } + ////////////////////////////////////////////////////// //// Private variables. @@ -440,9 +432,11 @@ protected List getRuntimeInstances() { /** Inner class representing a runtime instance. */ public class Runtime { - public int level = 0; public TimeValue deadline = TimeValue.MAX_VALUE; public Runtime dominatingReaction = null; + /** ID ranging from 0 to parent.getTotalWidth() - 1. */ + public int id = 0; + public int level = 0; public ReactionInstance getReaction() { return ReactionInstance.this; diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 4ce48ecf15..3771f3e7f7 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -27,6 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; +import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -109,28 +110,30 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti List srcRuntimes = reaction.getRuntimeInstances(); for (SendRange sendRange : port.eventualDestinations()) { int depth = (port.isInput())? 2 : 1; - for (int srcIndex : sendRange.parentInstances(depth)) { - for (Range dstRange : sendRange.destinations) { - for (int dstIndex : dstRange.parentInstances(1)) { - for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { - List dstRuntimes = dstReaction.getRuntimeInstances(); - Runtime srcRuntime = srcRuntimes.get(srcIndex); - Runtime dstRuntime = dstRuntimes.get(dstIndex); - addEdge(dstRuntime, srcRuntime); - - // Propagate the deadlines, if any. - if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { - srcRuntime.deadline = dstRuntime.deadline; - } - - // If this seems to be a single dominating reaction, set it. - // If another upstream reaction shows up, then this will be - // reset to null. - if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { - dstRuntime.dominatingReaction = srcRuntime; - } else { - dstRuntime.dominatingReaction = null; - } + for (Range dstRange : sendRange.destinations) { + Iterator sendParentIDs = sendRange.parentInstances(depth).iterator(); + for (int dstIndex : dstRange.parentInstances(1)) { + int srcIndex = sendParentIDs.next(); + // Destination may be wider than the source (multicast). + if (!sendParentIDs.hasNext()) sendParentIDs = sendRange.parentInstances(depth).iterator(); + for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { + List dstRuntimes = dstReaction.getRuntimeInstances(); + Runtime srcRuntime = srcRuntimes.get(srcIndex); + Runtime dstRuntime = dstRuntimes.get(dstIndex); + addEdge(dstRuntime, srcRuntime); + + // Propagate the deadlines, if any. + if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { + srcRuntime.deadline = dstRuntime.deadline; + } + + // If this seems to be a single dominating reaction, set it. + // If another upstream reaction shows up, then this will be + // reset to null. + if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { + dstRuntime.dominatingReaction = srcRuntime; + } else { + dstRuntime.dominatingReaction = null; } } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index cb3386e304..29c7fada6a 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -119,6 +119,13 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re * bank members (which have bankIndex >= 0). */ public final List children = new ArrayList(); + + /** + * The ID of this reactor instance. This is 0 for a top-level (main) reactor + * and increases for each created reactor in the order created until it + * reaches main's {@link #totalNumberOfChildren()}. + */ + public final int id; /** The input port instances belonging to this reactor instance. */ public final List inputs = new ArrayList(); @@ -186,7 +193,7 @@ public ReactorInstance getChildReactorInstance(Instantiation definition) { */ public void clearCaches() { cachedReactionLoopGraph = null; - totalNumberOfReactionsCache = -1; + totalNumChildrenCache = -1; for (ReactorInstance child : children) { child.clearCaches(); } @@ -210,6 +217,7 @@ public void clearCaches() { * This assumes that index 0 refers to the parent, hence the "one plus." */ public int getIndexOffset() { + // FIXME FIXME get rid of this. return indexOffset; } @@ -243,6 +251,7 @@ public String getName() { * taking into account their bank widths if necessary. */ public int getNumReactorInstances() { + // FIXME FIXME get rid of this. return numReactorInstances; } @@ -294,6 +303,7 @@ public TriggerInstance getShutdownTrigger() { * as returned by width(). */ public int getTotalNumReactorInstances() { + // FIXME FIXME: get rid of this. if (width < 0 || numReactorInstances < 0) return -1; return width * numReactorInstances; } @@ -558,16 +568,17 @@ public String toString() { } /** - * Return the total number of reactions in this reactor - * and all its contained reactors. + * Return the total number of children in this reactor + * and all its contained reactors. Each bank counts as one child. */ - public int totalNumberOfReactions() { - if (totalNumberOfReactionsCache >= 0) return totalNumberOfReactionsCache; - totalNumberOfReactionsCache = reactions.size(); - for (ReactorInstance containedReactor : children) { - totalNumberOfReactionsCache += containedReactor.totalNumberOfReactions(); + public int totalNumberOfChildren() { + if (totalNumChildrenCache < 0) { + totalNumChildrenCache = children.size(); + for (ReactorInstance containedReactor : children) { + totalNumChildrenCache += containedReactor.totalNumberOfChildren(); + } } - return totalNumberOfReactionsCache; + return totalNumChildrenCache; } /** @@ -723,6 +734,7 @@ private ReactorInstance( super(definition, parent); this.reporter = reporter; this.reactorDefinition = ASTUtils.toDefinition(definition.getReactorClass()); + this.id = root().childCount++; if (unorderedReactions != null) { this.unorderedReactions = unorderedReactions; @@ -1004,6 +1016,19 @@ private void setInitialWidth() { */ private ReactionInstanceGraph cachedReactionLoopGraph = null; + /** + * Count of children created for assigning IDs. This should only be + * used by a top-level reactor. + */ + private int childCount = 0; + + /** + * Cache of the deep number of children. + */ + private int totalNumChildrenCache = -1; + + // FIXME FIXME: Get rid of the following. + /** * One plus the number of contained reactor instances * (if a bank, in a bank element). @@ -1017,10 +1042,4 @@ private void setInitialWidth() { * this in the parent. */ private int indexOffset = 0; - - /** - * Cached version of the total number of reactions within - * this reactor and its contained reactors. - */ - private int totalNumberOfReactionsCache = -1; } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index c653994c62..876a528138 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3539,11 +3539,14 @@ class CGenerator extends GeneratorBase { t | return currentFederate.contains(t.definition); ]; - // Create an array to store all self structs. + // Create an array of arrays to store all self structs. // This is needed because connections cannot be established until // all reactor instances have self structs because ports that // receive data reference the self structs of the originating // reactors, which are arbitarily far away in the program graph. + generateSelfStructs(main); + + // FIXME FIXME get rid of self_structs everywhere. // The type of each struct type is different, so the type of this // array is vague. pr(initializeTriggerObjects, ''' @@ -3557,6 +3560,7 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, ''' «CUtil.selfType(main)»* «CUtil.reactorRef(main)» = new_«main.name»(); self_structs[0] = «CUtil.reactorRef(main)»; + «CUtil.reactorRef(main, "fixme")»[0] = «CUtil.reactorRef(main)»; ''') // Generate code for top-level parameters, actions, timers, and reactions that @@ -3609,10 +3613,9 @@ class CGenerator extends GeneratorBase { // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). - // Record the self struct on the big array of self structs. - // FIXME: only works for banks. pr(initializeTriggerObjects, ''' - self_structs[«CUtil.indexExpression(instance)»] = new_«reactorClass.name»(); + «CUtil.reactorRef(instance, "fixme")»[«CUtil.bankIndex(instance)»] = new_«reactorClass.name»(); + self_structs[«CUtil.indexExpression(instance)»] = «CUtil.reactorRef(instance, "fixme")»[«CUtil.bankIndex(instance)»]; ''') // Create a variable that will point to this new self struct in @@ -5801,6 +5804,8 @@ class CGenerator extends GeneratorBase { deferredOutputNumDestinations(reactor); // NOTE: Does nothing for top level. deferredFillTriggerTable(reactions); + deferredOptimizeForSingleDominatingReaction(reactor); + pr('''// **** End of deferredInitialize for «reactor.getFullName()»''') } @@ -5988,55 +5993,80 @@ class CGenerator extends GeneratorBase { } /** + * For each reaction of the specified reactor, * Set the last_enabling_reaction field of the reaction struct to point * to the single dominating upstream reaction, if there is one, or to be * NULL if not. * - * @param reaction The reaction. - * @param reactionNumber The reaction number within the parent. + * @param reactor The reactor. */ - def deferredOptimizeForSingleDominatingReaction ( - ReactionInstance reaction, - int reactionNumber + private def deferredOptimizeForSingleDominatingReaction (ReactorInstance r) { + for (reaction : r.reactions) { + var start = 0; + var end = 0; + var domStart = 0; + var domEnd = 0; + var dominating = null as ReactionInstance.Runtime; + for (runtime : reaction.getRuntimeInstances()) { + if (end > start // There is something to output. + && ( + // Not equal and not a successor + (runtime.dominatingReaction != dominating + && ( + runtime.dominatingReaction === null + || runtime.dominatingReaction.id != domEnd + 1 + ) + ) + ) + ) { + // Change in value. + printOptimizeForSingleDominatingReaction(reaction, start, end, dominating, domStart); + start = end; + domStart = domEnd; + } + dominating = runtime.dominatingReaction; + end++; + domEnd++; + } + if (end > start) { + printOptimizeForSingleDominatingReaction(reaction, start, end, dominating, domStart); + } + } + } + + /** + * Print statement that sets the last_enabling_reaction field of a reaction. + */ + private def printOptimizeForSingleDominatingReaction( + ReactionInstance reaction, int start, int end, + ReactionInstance.Runtime dominating, int domStart ) { - val reactor = reaction.parent; - - // FIXME // This is called within a scoped block for the parent. - // Use the index variables for the parent to index into the Runtimes - // field of the reaction. Do a run-length encoding of the result. + var dominatingRef = "NULL"; - // Record the number of reactions that this reaction depends on. - // This is used for optimization. When that number is 1, the reaction can - // be executed immediately when its triggering reaction has completed. - var dominatingReaction = null as ReactionInstance; // reaction.findSingleDominatingReaction(); - - // The dominating reaction may not be included in this federate, in which case, we need to keep searching. - while (dominatingReaction !== null - && (!currentFederate.contains(dominatingReaction.definition)) - ) { - dominatingReaction = null as ReactionInstance; // dominatingReaction.findSingleDominatingReaction(); - } - val reactionRef = CUtil.reactionRef(reaction); - if (dominatingReaction !== null - && currentFederate.contains(dominatingReaction.definition) - && currentFederate.contains(dominatingReaction.parent) - ) { - val temp = new StringBuilder(); - - val dominatingReactionRef = CUtil.reactionRef(dominatingReaction) - if (dominatingReaction.parent != reaction.parent) { - defineSelfStruct(temp, dominatingReaction.parent) + if (end > start + 1) { + val reactionRef = CUtil.reactionRef(reaction, "fixme", "i"); + if (dominating !== null) { + dominatingRef = "&(" + CUtil.reactionRef(dominating.reaction, "fixme", "j++") + ")"; + pr(''' + int j = «domStart»; + ''') } - pr(temp, ''' - // Reaction «reactionNumber» of «reactor.getFullName» depends on one maximal upstream reaction. - «reactionRef».last_enabling_reaction = &(«dominatingReactionRef»); + startScopedBlock(code); + pr(''' + // Reaction «reaction.index» of «reaction.getFullName» dominating upstream reaction. + for (int i = «start»; i < «end»; i++) { + «reactionRef».last_enabling_reaction = «dominatingRef»; + } ''') - - encloseInBankIteration(code, dominatingReaction, reaction, temp); - } else { + endScopedBlock(code); + } else if (end == start + 1) { + val reactionRef = CUtil.reactionRef(reaction, "fixme", "" + start); + if (dominating !== null) { + dominatingRef = "&(" + CUtil.reactionRef(dominating.reaction, "fixme", "" + domStart) + ")"; + } pr(''' - // Reaction «reactionNumber» of «reactor.getFullName» does not depend on one maximal upstream reaction. - «reactionRef».last_enabling_reaction = NULL; + // Reaction «reaction.index» of «reaction.getFullName» dominating upstream reaction. + «reactionRef».last_enabling_reaction = «dominatingRef»; ''') } } @@ -6170,8 +6200,6 @@ class CGenerator extends GeneratorBase { ''') } - deferredOptimizeForSingleDominatingReaction(reaction, reaction.index); - pr(''' «init.toString» // ** End initialization for reaction «reaction.index» of «name» @@ -6280,6 +6308,32 @@ class CGenerator extends GeneratorBase { } } } + + /** + * Generate an array of arrays for storing the self structs. + * @param r The reactor instance. + */ + private def void generateSelfStructs(ReactorInstance r) { + if (r.depth == 0) { + // Top level, so generate main arrays. + // NOTE; these arrays are put on the stack and discarded after initialization. + val num = r.totalNumberOfChildren + 1; + pr(initializeTriggerObjects, ''' + void* selfs[«num»]; + size_t selfs_sizes[«num»]; + ''') + } + // FIXME FIXME get rid of "fixme" everywhere. + pr(initializeTriggerObjects, ''' + «CUtil.selfType(r)»* «CUtil.reactorRef(r, "fixme")»[«r.totalWidth»]; + selfs[«r.id»] = «CUtil.reactorRef(r, "fixme")»; + selfs_sizes[«r.id»] = «r.totalWidth»; + ''') + for (child : r.children) { + generateSelfStructs(child); + } + } + ////////////////////////////////////////////////////////////// // Inner class diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index df401020cb..a2a3ee5975 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -376,6 +376,19 @@ static public String reactionRef(ReactionInstance reaction, String suffix) { return reactorRef(reaction.getParent(), suffix) + "->_lf__reaction_" + reaction.index; } + /** + * Return a reference to the reaction entry on the self struct + * of the parent of the specified reaction. + * @param reaction The reaction. + * @param suffix A suffix to use for the parent reactor or null for the default. + * @param index An index into the array of self structs for the parent. + */ + static public String reactionRef(ReactionInstance reaction, String suffix, String index) { + return reactorRef(reaction.getParent(), suffix) + + "[" + index + "]" + + "->_lf__reaction_" + reaction.index; + } + /** * Return a name for a pointer to the "self" struct of the specified * reactor instance. From 1faca77de85d188859ff53cd862d84b25b2807a3 Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 24 Dec 2021 11:00:51 -0800 Subject: [PATCH 089/221] Interrim checkin --- .../org/lflang/tests/compiler/RangeTests.java | 16 +- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/MixedRadixInt.java | 14 + .../src/org/lflang/generator/Range.java | 45 +- .../lflang/generator/ReactionInstance.java | 6 +- .../generator/ReactionInstanceGraph.java | 4 +- .../org/lflang/generator/c/CGenerator.xtend | 891 +++++++----------- .../src/org/lflang/generator/c/CUtil.java | 359 ++++--- 8 files changed, 587 insertions(+), 750 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index 17d56c35fd..ed3db2bba9 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -59,8 +59,8 @@ public void createRange() throws Exception { parents = range.parentInstances(2); Assertions.assertEquals(Set.of(0, 1), parents); - // Test startIndices. - Assertions.assertEquals(List.of(1, 1, 0), range.startIndices()); + // Test startMRNatural().getDigits. + Assertions.assertEquals(List.of(1, 1, 0), range.startMRNatural().getDigits()); // Create a SendRange sending from and to this range. SendRange sendRange = new SendRange(pi, 3, 4); @@ -74,16 +74,16 @@ public void createRange() throws Exception { instances = range.instances(); Assertions.assertEquals(List.of(3, 4, 6, 5), instances); - // Test startIndices. - Assertions.assertEquals(List.of(1, 1, 0), range.startIndices()); + // Test startMRNatural().getDigits. + Assertions.assertEquals(List.of(1, 1, 0), range.startMRNatural().getDigits()); // Make second interleaved version. range = range.toggleInterleaved(ai); instances = range.instances(); Assertions.assertEquals(List.of(6, 1, 5, 3), instances); - // Test startIndices. - Assertions.assertEquals(List.of(0, 1, 1), range.startIndices()); + // Test startMRNatural().getDigits. + Assertions.assertEquals(List.of(0, 1, 1), range.startMRNatural().getDigits()); // Test instances of the parent. Assertions.assertEquals(Set.of(3, 0, 2, 1), range.parentInstances(1)); @@ -98,7 +98,7 @@ public void createRange() throws Exception { instances = range.instances(); Assertions.assertEquals(List.of(5, 2, 6, 3), instances); - // Test startIndices. - Assertions.assertEquals(List.of(1, 0, 1), range.startIndices()); + // Test startMRNatural().getDigits. + Assertions.assertEquals(List.of(1, 0, 1), range.startMRNatural().getDigits()); } } diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index e82c413d2f..06b71acc56 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit e82c413d2f005901c8efe33df3b45762bb79451c +Subproject commit 06b71acc56fb09380c2986dd630ac36bdea94bf1 diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index 72734c30ac..a01f7c9b8b 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -110,6 +110,20 @@ public int get() { return result; } + /** + * Return the digits. + */ + public List getDigits() { + return digits; + } + + /** + * Return the radixes. + */ + public List getRadixes() { + return radixes; + } + /** * Increment the number by one. */ diff --git a/org.lflang/src/org/lflang/generator/Range.java b/org.lflang/src/org/lflang/generator/Range.java index 4d431d0c89..dcaeb4becb 100644 --- a/org.lflang/src/org/lflang/generator/Range.java +++ b/org.lflang/src/org/lflang/generator/Range.java @@ -369,27 +369,54 @@ public ReactorInstance parentReactor() { } else { return instance.getParent(); } - } + /** - * Return a list of start indices for this instance and all its parents - * except the top-level reactor in hierarchy order, with this instance's - * start position listed first. For any instance that is neither a - * multiport nor a bank, the index will be 0. + * Return that permutation that will convert the mixed-radix number + * with digits given by {@link #startIndices()} into a mixed-radix + * number in natural order, i.e. with digits given by + * {@link #startIndicesNatural()}. */ - public List startIndices() { + public List permutation() { List result = new ArrayList(instance.depth); // Populate the result with default zeros. for (int i = 0; i < instance.depth; i++) { result.add(0); } + int count = 0; + for (NamedInstance i : iterationOrder()) { + result.set(instance.depth - i.depth, count++); + } + return result; + } + + /** + * Return the start as a mixed-radix number where the digits and + * radixes are in the order given by {@link #instances()}. + * For any instance that is neither a + * multiport nor a bank, the digit will be 0. + */ + public MixedRadixInt startMR() { + List digits = new ArrayList(instance.depth); + List radixes = new ArrayList(instance.depth); int factor = 1; for (NamedInstance i : iterationOrder()) { int iStart = (start / factor) % i.width; factor *= i.width; - result.set(instance.depth - i.depth, iStart); + digits.add(iStart); + radixes.add(i.width); } - return result; + return new MixedRadixInt(digits, radixes); + } + + /** + * Return the start as a mixed-radix number where the digits and + * radixes are in hierarchy order (natural order), with this instance's + * start position listed first. For any instance that is neither a + * multiport nor a bank, the digit will be 0. + */ + public MixedRadixInt startMRNatural() { + return startMR().permute(permutation()); } /** @@ -441,7 +468,7 @@ public String toString() { /** * Return the list of MixedRadixInt identifiers for the runtime instances * in this range. Each returned identifier is a mixed-radix number - * [d0, ... , dn] with radices [w0, ... , wn], where d0 is the bank or + * [d0, ... , dn] with radixes [w0, ... , wn], where d0 is the bank or * channel index of this Range's instance, which has width w0, and * dn is the bank index of its topmost parent below the top-level (main) * reactor, which has width wn. The depth of this Range's instance, diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 852bcbfaad..cd864739ae 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -433,7 +433,7 @@ public String toString() { /** Inner class representing a runtime instance. */ public class Runtime { public TimeValue deadline = TimeValue.MAX_VALUE; - public Runtime dominatingReaction = null; + public Runtime dominating = null; /** ID ranging from 0 to parent.getTotalWidth() - 1. */ public int id = 0; public int level = 0; @@ -448,8 +448,8 @@ public String toString() { if (deadline != null && deadline != TimeValue.MAX_VALUE) { result += ", deadline: " + deadline.toString(); } - if (dominatingReaction != null) { - result += ", dominating: " + dominatingReaction.getReaction(); + if (dominating != null) { + result += ", dominating: " + dominating.getReaction(); } result += ")"; return result; diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 3771f3e7f7..8b22bb762e 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -131,9 +131,9 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti // If another upstream reaction shows up, then this will be // reset to null. if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { - dstRuntime.dominatingReaction = srcRuntime; + dstRuntime.dominating = srcRuntime; } else { - dstRuntime.dominatingReaction = null; + dstRuntime.dominating = null; } } } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 876a528138..9768648cce 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -65,7 +65,6 @@ import org.lflang.federated.serialization.SupportedSerializers import org.lflang.generator.ActionInstance import org.lflang.generator.GeneratorBase import org.lflang.generator.JavaGeneratorUtils -import org.lflang.generator.NamedInstance import org.lflang.generator.ParameterInstance import org.lflang.generator.PortInstance import org.lflang.generator.Range @@ -1016,6 +1015,8 @@ class CGenerator extends GeneratorBase { ]; deferredInitialize(main, reactionsInFederate) + + deferredInitializeNonNested(main, reactionsInFederate) // Next, for every input port, populate its "self" struct // fields with pointers to the output port that sends it data. @@ -1027,7 +1028,7 @@ class CGenerator extends GeneratorBase { // between inputs and outputs. pr(startTimeStep.toString) - setReactionPriorities(main) + setReactionPriorities(main, code) initializeFederate(federate) unindent() @@ -3208,7 +3209,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(temp, instance); startScopedBankChannelIteration(temp, port, "count"); - val portRef = CUtil.portRef(port, true, true, null); + val portRef = CUtil.portRef(port, true, true, null, null); pr(temp, ''' _lf_tokens_with_ref_count[«startTimeStepTokens» + count].token = &«portRef»->token; @@ -3270,7 +3271,7 @@ class CGenerator extends GeneratorBase { pr(temp, ''' // Add port «output.getFullName» to array of is_present fields. ''') - startChannelIteration(temp, output, null); + startChannelIteration(temp, output); pr(temp, ''' _lf_is_present_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)».is_present; @@ -3546,21 +3547,12 @@ class CGenerator extends GeneratorBase { // reactors, which are arbitarily far away in the program graph. generateSelfStructs(main); - // FIXME FIXME get rid of self_structs everywhere. - // The type of each struct type is different, so the type of this - // array is vague. - pr(initializeTriggerObjects, ''' - void* self_structs[«main.getTotalNumReactorInstances()»]; - ''') - pr(initializeTriggerObjects, '// ***** Start initializing ' + main.name) // Generate the self struct declaration for the top level. pr(initializeTriggerObjects, ''' - «CUtil.selfType(main)»* «CUtil.reactorRef(main)» = new_«main.name»(); - self_structs[0] = «CUtil.reactorRef(main)»; - «CUtil.reactorRef(main, "fixme")»[0] = «CUtil.reactorRef(main)»; + «CUtil.reactorRef(main)» = new_«main.name»(); ''') // Generate code for top-level parameters, actions, timers, and reactions that @@ -3609,18 +3601,15 @@ class CGenerator extends GeneratorBase { // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. - startScopedBlock(initializeTriggerObjects, instance, false, null); + // Need to do this for each of the builders into which the code writes. + startScopedBlock(startTimeStep, instance); + startScopedBlock(initializeTriggerObjects, instance); // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). pr(initializeTriggerObjects, ''' - «CUtil.reactorRef(instance, "fixme")»[«CUtil.bankIndex(instance)»] = new_«reactorClass.name»(); - self_structs[«CUtil.indexExpression(instance)»] = «CUtil.reactorRef(instance, "fixme")»[«CUtil.bankIndex(instance)»]; + «CUtil.reactorRefName(instance)»[«CUtil.runtimeIndex(instance)»] = new_«reactorClass.name»(); ''') - - // Create a variable that will point to this new self struct in - // this scope and nested scopes. - defineSelfStruct(initializeTriggerObjects, instance); // Generate code to initialize the "self" struct in the // _lf_initialize_trigger_objects function. @@ -3688,8 +3677,9 @@ class CGenerator extends GeneratorBase { // Note that this function is also run once at the end // so that it can deallocate any memory. generateStartTimeStep(instance) - + endScopedBlock(initializeTriggerObjects); + endScopedBlock(startTimeStep); pr(initializeTriggerObjects, "//***** End initializing " + fullName) } @@ -3941,15 +3931,16 @@ class CGenerator extends GeneratorBase { /** * Set the reaction priorities based on dependency analysis. * @param reactor The reactor on which to do this. - * @param federate A federate to conditionally generate code for - * contained reactors or null if there are no federates. + * @param builder Where to write the code. */ - def void setReactionPriorities(ReactorInstance reactor) { - val temp = new StringBuilder(); + private def void setReactionPriorities(ReactorInstance reactor, StringBuilder builder) { var foundOne = false; - startScopedBlock(temp, reactor); - + if (reactor.isBank) { + startScopedBlock(builder, reactor); + } + val temp = new StringBuilder(); + pr(temp, "// Set reaction priorities for " + reactor.toString()); for (r : reactor.reactions) { val levels = r.getLevels(); if (levels.size != 1) { @@ -3973,15 +3964,15 @@ class CGenerator extends GeneratorBase { ''') } } - endScopedBlock(temp); - - if (foundOne) pr(temp.toString()); - + if (foundOne) pr(builder, temp.toString()); for (child : reactor.children) { if (currentFederate.contains(child)) { - setReactionPriorities(child) + setReactionPriorities(child, builder); } } + if (reactor.isBank) { + endScopedBlock(builder); + } } override getTargetTypes() { @@ -4616,98 +4607,6 @@ class CGenerator extends GeneratorBase { // ////////////////////////////////////////// // // Private methods. - /** - * Enclose the specified code in a potentially nested iteration over - * bank indices for the specified connection from the given source to the - * given destination. The code may include references to bank indices of - * parents of either the source or destination. The names of these reference - * variables are the string returned by {@link CUtil.bankIndex(ReactorInstance)}. - * These variables are also defined if the parent of either the source or - * destination (or both) is not a parent of the context argument (even - * indirectly). - * - * This returns the total number of iterations, which is never less than 1. - */ - private def int encloseInBankIteration( - StringBuilder builder, - NamedInstance source, - NamedInstance destination, - StringBuilder toEnclose - ) { - val sourceParentBanksReversed = new LinkedList(); - var result = 1; - - // Start a scoped block so we can define bank index variables without - // resulting in them being multiply defined. - startScopedBlock(builder); - - if (source.parent != destination.parent) { - defineSelfStruct(builder, destination.parent); - - // Initialize parent bank indices and construct a reversed list. - val sourceParents = source.parents; - for (s : sourceParents) { - if (s.isBank) { - pr(builder, ''' - int «CUtil.bankIndex(s)» = 0; - ''') - sourceParentBanksReversed.push(s); - } - } - } - - // Generate iterations over banks of the destination containers. - var indents = 0; - for (destinationContainer : destination.parents) { - if (destinationContainer.isBank) { - // Inserting a new for loop, so the total number of iterations - // becomes the previous total multiplied by the number here. - result *= destinationContainer.width; - - val index = CUtil.bankIndex(destinationContainer); - pr(builder, ''' - // Iterate over destination bank members. - for (int «index» = 0; «index» < «destinationContainer.width»; «index»++) { - ''') - indent(builder); - indents++; - } - } - - pr(builder, toEnclose); - - // Increment the source bank variables as needed. - if (!sourceParentBanksReversed.isEmpty) { - var s = sourceParentBanksReversed.pop(); - var i = CUtil.bankIndex(s); - pr(builder, ''' - «i»++; - if («i» >= «s.width») { - «i» = 0; - } - ''') - while (!sourceParentBanksReversed.isEmpty) { - s = sourceParentBanksReversed.pop(); - i = CUtil.bankIndex(s); - indent(builder); - indents++; - pr(builder, ''' - if («i» >= «s.width») { - «i» = 0; - } - ''') - } - } - while(indents > 0) { - indents--; - unindent(builder); - pr(builder, "}"); - } - - endScopedBlock(builder); - return result; - } - /** * If the specified port is a multiport, then start a specified iteration * over the channels of the multiport using as the channel index the @@ -4716,11 +4615,10 @@ class CGenerator extends GeneratorBase { * This is required to be followed by {@link endChannelIteration(StringBuilder, PortInstance}. * @param builder Where to write the code. * @param port The port. - * @param suffix A suffix to use for variable names or null for default. */ - protected def void startChannelIteration(StringBuilder builder, PortInstance port, String suffix) { + private def void startChannelIteration(StringBuilder builder, PortInstance port) { if (port.isMultiport) { - val channel = CUtil.channelIndex(port, suffix); + val channel = CUtil.channelIndexName(port); pr(builder, ''' // Port «port.fullName» is a multiport. Iterate over its channels. for (int «channel» = 0; «channel» < «port.width»; «channel»++) { @@ -4738,7 +4636,7 @@ class CGenerator extends GeneratorBase { * @param builder Where to write the code. * @param port The port. */ - protected def void endChannelIteration(StringBuilder builder, PortInstance port) { + private def void endChannelIteration(StringBuilder builder, PortInstance port) { if (port.isMultiport) { unindent(builder); pr(builder, "}"); @@ -4751,8 +4649,9 @@ class CGenerator extends GeneratorBase { * This must be followed by an {@link endScopedBlock(StringBuilder)}. * @param builder The string builder into which to write. */ - protected def void startScopedBlock(StringBuilder builder) { - startScopedBlock(builder, null, false, null); + private def void startScopedBlock(StringBuilder builder) { + pr(builder, "{"); + indent(builder); } /** @@ -4765,38 +4664,20 @@ class CGenerator extends GeneratorBase { * This also adds a declaration of a pointer to the self * struct of the reactor or bank member. * + * This block is intended to be nested, where each block is + * put within a similar block for the reactor's parent. + * This ensures that all (possibly nested) bank index variables + * are defined within the block. + * * This must be followed by an {@link endScopedBlock(StringBuilder)}. * * @param builder The string builder into which to write. * @param reactor The reactor instance. */ protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { - startScopedBlock(builder, reactor, true, null) - } - - /** - * Start a scoped block for the specified reactor. - * If the reactor is a bank, then this starts a for loop - * that iterates over the bank members using a standard index - * variable whose name is that returned by {@link CUtil.bankIndex(ReactorInstance)}. - * If the reactor is null or is not a bank, then this simply - * starts a scoped block by printing an opening curly brace. - * If the declare argument is true, then this also - * adds a declaration of a pointer to the self - * struct of the reactor or bank member. - * - * This must be followed by an {@link endScopedBlock(StringBuilder)}. - * - * @param builder The string builder into which to write. - * @param reactor The reactor instance or null for a simple scoped block. - * @param declare True to declare a pointer to the self struct - * @param suffix A suffix to use for variable names or null for default. - */ - protected def void startScopedBlock( - StringBuilder builder, ReactorInstance reactor, boolean declare, String suffix - ) { - if (reactor !== null && reactor.isBank) { - val index = CUtil.bankIndex(reactor, suffix); + // NOTE: This is protected because it is used by the PythonGenerator. + if (reactor !== null && reactor.width > 1) { + val index = CUtil.bankIndexName(reactor); pr(builder, ''' // Reactor is a bank. Iterate over bank members. for (int «index» = 0; «index» < «reactor.width»; «index»++) { @@ -4805,7 +4686,6 @@ class CGenerator extends GeneratorBase { pr(builder, "{"); } indent(builder); - if (declare && reactor !== null) defineSelfStruct(builder, reactor, suffix); } /** @@ -4813,6 +4693,7 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. */ protected def void endScopedBlock(StringBuilder builder) { + // NOTE: This is protected because it is used by the PythonGenerator. unindent(builder); pr(builder, "}"); } @@ -4824,6 +4705,9 @@ class CGenerator extends GeneratorBase { * If this port is a multiport, then the channel index * variable name is that returned by {@link CUtil.channelIndex(PortInstance)}. * + * This block is intended to be nested, where each block is + * put within a similar block for the reactor's parent. + * * This is required to be followed by a call to * {@link endScopedBankChannelIteration(StringBuilder, PortInstance, String)}. * @param builder Where to write the code. @@ -4831,7 +4715,7 @@ class CGenerator extends GeneratorBase { * @param count The variable name to use for the counter, or * null to not provide a counter. */ - protected def void startScopedBankChannelIteration( + private def void startScopedBankChannelIteration( StringBuilder builder, PortInstance port, String count ) { if (count !== null) { @@ -4839,7 +4723,7 @@ class CGenerator extends GeneratorBase { pr(builder, '''int «count» = 0;'''); } startScopedBlock(builder, port.parent); - startChannelIteration(builder, port, null); + startChannelIteration(builder, port); } /** @@ -4851,7 +4735,7 @@ class CGenerator extends GeneratorBase { * @param count The variable name to use for the counter, or * null to not provide a counter. */ - protected def void endScopedBankChannelIteration( + private def void endScopedBankChannelIteration( StringBuilder builder, PortInstance port, String count ) { if (count !== null) { @@ -4865,60 +4749,79 @@ class CGenerator extends GeneratorBase { } /** - * Start a scoped block for the specified range. - * - * If the port of the range is contained by any banks, then this - * generates iterations over the range of bank members. - * If the port is also a multiport, then the iteration is over bank - * members and channels, where the order depends on whether the - * range is interleaved. - * The order of the iterations is specified by the range. - * - * In all cases, this stops the iteration when the - * total width of the range has been covered. + * Start a scoped block that iterates over the specified range. * * This must be followed by a call to * {@link endScopedRangeBlock(StringBuilder, Range)}. + * + * This block should NOT be nested, where each block is + * put within a similar block for the reactor's parent. + * Within the created block, every use of + * {@link CUtil.reactorRef(ReactorInstance, String)} + * must provide the second argument, a runtime index variable name, + * that must match the runtimeIndex parameter given here. * * @param builder The string builder into which to write. * @param range The send range. - * @param suffix A suffix to use for variable names or null for default. + * @param runtimeIndex The name of variable whose value specifies + * which runtime instance of the ReactorInstance of the range + * is being referred to. */ - protected def void startScopedRangeBlock( - StringBuilder builder, Range range, String suffix + private def void startScopedRangeBlock( + StringBuilder builder, Range range, String runtimeIndex ) { - val port = range.instance; pr(builder, ''' - // Iterate over range of «port.fullName» with starting offset «range.start», - // and width «range.width». + // Iterate over range «range.toString()». ''') startScopedBlock(builder); - // Include range_count variable so generated code can safely use it. - pr(builder, ''' - int range_count = 0; - '''); - - val iterationOrder = range.iterationOrder(); - - // We need to iterate backwards over the iteration order. - for (var i = iterationOrder.size() - 1; i >= 0; i--) { - val instance = iterationOrder.get(i); - if (instance === port && port.isMultiport) { - startChannelIteration(builder, port, suffix); - } else if (instance instanceof ReactorInstance) { - // Instance is a parent reactor. - startScopedBlock(builder, instance, true, suffix); - } - } - // Allow code to execute only if we are in range. - if (range.start > 0) { + val ci = CUtil.channelIndexName(range.instance); + val rangeMR = range.startMR(); + + if (range.width > 1) { pr(builder, ''' - if (range_count >= «range.start») { - ''') + int range_start[] = { «rangeMR.getDigits().join(", ")» }; + int range_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + int permutation[] = { «range.permutation().join(", ")» }; + mixed_radix_int_t range_mr = { + «rangeMR.getDigits().size()», + range_start, + range_radixes + }; + for (int range_count = «range.start»; range_count < «range.start» + «range.width»; range_count++) { + '''); indent(builder); + // FIXME: Following could be much simpler when there is no interleaving. + pr(builder, ''' + int «ci» = range_mr.digits[permutation[0]]; // Channel index. + int range_natural_start[] = { «rangeMR.getDigits().join(", ")» }; + int range_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + mixed_radix_int_t range_natural = { + «rangeMR.getDigits().size()», + range_natural_start, + range_natural_radixes + }; + int banks_natural_start[] = { «rangeMR.getDigits().join(", ")» }; + int banks_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + mixed_radix_int_t banks_natural = { + «rangeMR.getDigits().size()» - 1, + banks_natural_start, + banks_natural_radixes + }; + mixed_radix_permute(&range_natural, &range_mr, permutation); + mixed_radix_drop(&banks_natural, &range_natural, 1); + int «runtimeIndex» = mixed_radix_to_int(&banks_natural); + ''') + } else { + val mr = range.startMRNatural(); + val ciValue = mr.getDigits().get(0); + val biValue = mr.drop(1).get(); + pr(builder, ''' + int «ci» = «ciValue»; // Channel index. + int «runtimeIndex» = «biValue»; // Bank index. + ''') } } @@ -4926,243 +4829,135 @@ class CGenerator extends GeneratorBase { * End a scoped block for the specified range. * @param builder The string builder into which to write. * @param range The send range. - * @param suffix A suffix to use for variable names or null for default. */ - protected def void endScopedRangeBlock( - StringBuilder builder, Range range, String suffix - ) { - val port = range.instance; - - if (range.start > 0) { + private def void endScopedRangeBlock(StringBuilder builder, Range range) { + if (range.width > 1) { + pr(builder, "mixed_radix_incr(&range_mr);"); unindent(builder); - pr(builder, "}"); // End of predicate on range_count. - } - pr(builder, ''' - range_count++; - ''') - - - // We need to iterate forwards over the iteration order. - for (instance : range.iterationOrder()) { - if (instance === port && port.isMultiport) { - pr(builder, ''' - if (range_count >= «range.start» + «range.width») break; - ''') - endChannelIteration(builder, port); - } else if (instance instanceof ReactorInstance) { - if (instance.isBank) { - pr(builder, ''' - if (range_count >= «range.start» + «range.width») break; - ''') - } - // Instance is a parent reactor. - endScopedBlock(builder); - } + pr(builder, "}"); } endScopedBlock(builder); } /** - * Start a scoped block for the specified range in preparation for - * putting a scoped block inside the iteration produced by - * {@link #startScopedRangeBlock(StringBuilder, Range)}. - * This is meant to be used when iterating over two ranges with the same width. + * Start a scoped block that iterates over the specified pair of ranges. + * The destination range can be wider than the source range, in which case the + * source range is reused until the destination range is filled. + * The bankIndex and channelIndex arguments specify the variable names + * to use for the source channel and bank. The destination channel and + * bank variables are he default ones. * - * This must be followed by a call to - * {@link #endScopedRangeBlockOutside(StringBuilder, Range, String)}. + * This block should NOT be nested, where each block is + * put within a similar block for the reactor's parent. + * Within the created block, every use of + * {@link CUtil.reactorRef(ReactorInstance, String)} + * must provide the second argument, a runtime index variable name, + * that must match the srcRuntimeIndex or dstRuntimeIndex parameter given here. * - * To allow for the possibility that the srcRange and dstRange - * refer to ports with the same parent, the variables used to - * refer to bank indices have to be different. This function - * uses the specified suffix on variable names. + * This must be followed by a call to + * {@link #endScopedRangeBlock(StringBuilder, Range)}. * * @param builder The string builder into which to write. - * @param srcRange The source range. - * @param suffix A suffix to use on variable names. + * @param srcRange The send range. + * @param dstRange The destination range. + * @param srcRuntimeIndex Index variable name to use to address bank members for the source. + * @param srcChannelIndex Index variable name to use to address channels for the source. + * @param dstRuntimeIndex Index variable name to use to address bank members for the destination. */ - protected def void startScopedRangeBlockOutside( - StringBuilder builder, Range srcRange, String suffix + private def void startScopedRangeBlock( + StringBuilder builder, + SendRange srcRange, + Range dstRange, + String srcRuntimeIndex, + String srcChannelIndex, + String dstRuntimeIndex ) { - val src = srcRange.instance; - + pr(builder, ''' - // Define variables to iterate over range1 of «src.fullName» - // with starting offset «srcRange.start» and width «srcRange.width». + // Iterate over ranges «srcRange.toString» and «dstRange.toString». ''') - - // Define additional variables for range. + startScopedBlock(builder); - // Declare the variables needed for the source range. - // These need to be initialized to starting indices. - val srcIteration = srcRange.iterationOrder(); - var factor = 1; - for (i : srcIteration) { - val init = (srcRange.start / factor) % i.width; - if (i instanceof PortInstance) { - if (i.isMultiport) { - pr(builder, ''' - int «CUtil.channelIndex(i, suffix)» = «init»; - ''') - } - } else if (i instanceof ReactorInstance) { - if (i.isBank) { - pr(builder, ''' - int «CUtil.bankIndex(i, suffix)» = «init»; - ''') - } - } - factor *= i.width; + val rangeMR = srcRange.startMR(); + + if (srcRange.width > 1) { + pr(builder, ''' + int src_start[] = { «rangeMR.getDigits().join(", ")» }; + int src_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + int src_permutation[] = { «srcRange.permutation().join(", ")» }; + mixed_radix_int_t src_range_mr = { + «rangeMR.getDigits().size()», + src_start, + src_radixes + }; + '''); + } else { + val mr = srcRange.startMRNatural(); + val ciValue = mr.getDigits().get(0); + val biValue = mr.drop(1).get(); + pr(builder, ''' + int «srcChannelIndex» = «ciValue»; // Channel index. + int «srcRuntimeIndex» = «biValue»; // Bank index. + ''') + } + + startScopedRangeBlock(builder, dstRange, dstRuntimeIndex); + + if (srcRange.width > 1) { + pr(builder, ''' + int «srcChannelIndex» = src_range_mr.digits[permutation[0]]; // Channel index. + int src_range_natural_start[] = { «rangeMR.getDigits().join(", ")» }; + int src_range_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + mixed_radix_int_t src_range_natural = { + «rangeMR.getDigits().size()», + src_range_natural_start, + src_range_natural_radixes + }; + int src_banks_natural_start[] = { «rangeMR.getDigits().join(", ")» }; + int src_banks_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + mixed_radix_int_t src_banks_natural = { + «rangeMR.getDigits().size()» - 1, + src_banks_natural_start, + src_banks_natural_radixes + }; + mixed_radix_permute(&src_range_natural, &src_range_mr, src_permutation); + mixed_radix_drop(&src_banks_natural, &src_range_natural, 1); + int «srcRuntimeIndex» = mixed_radix_to_int(&src_banks_natural); + ''') } } /** - * End the block started with - * {@link #startScopedRangeBlockOutside(StringBuilder, Range, String)}. - * - * @param builder The string builder into which to write. - * @param srcRange The source range. - * @param suffix A suffix to use on variable names. - */ - protected def void endScopedRangeBlockOutside( - StringBuilder builder, Range srcRange, String suffix - ) { - endScopedBlock(builder); - } - - /** - * Start a scoped block for the specified ranges to be put inside the - * iteration generated by - * {@link #startScopedRangeBlock(StringBuilder, Range)}, - * when iterating over two ranges with the same width. - * - * This must be followed by a call to - * {@link #endScopedRangeBlockInside(StringBuilder, Range, String)}. - * - * To allow for the possibility that the srcRange and dstRange - * refer to ports with the same parent, the variables used to - * refer to bank indices have to be different. This function - * uses the specified suffix on variable names. + * End a scoped block that iterates over the specified pair of ranges. * * @param builder The string builder into which to write. - * @param range The source range. - * @param suffix A suffix to use on variable names or null for the default. - */ - protected def void startScopedRangeBlockInside( - StringBuilder builder, Range range, String suffix - ) { - val src = range.instance; - - startScopedBlock(builder); - - // Define the self struct for the top-level, in case there are top-level reactions - // that send to inputs of contained reactors. - defineSelfStruct(builder, src.root, suffix); - - for (i : range.iterationOrder()) { - if (i instanceof ReactorInstance) { - defineSelfStruct(builder, i, suffix); - } - } - } - - /** - * End a scoped block for the specified send range. - * @param builder The string builder into which to write. - * @param range The source range. - * @param suffix A suffix to use on variable names or null for the default. + * @param srcRange The send range. + * @param dstRange The destination range. + * @param srcRuntimeIndex Index variable name to use to address bank members for the source. + * @param srcChannelIndex Index variable name to use to address channels for the source. + * @param dstRuntimeIndex Index variable name to use to address bank members for the destination. */ - protected def void endScopedRangeBlockInside( - StringBuilder builder, Range range, String suffix + private def void endScopedRangeBlock( + StringBuilder builder, + SendRange srcRange, + Range dstRange, + String srcRuntimeIndex, + String srcChannelIndex, + String dstRuntimeIndex ) { - val srcIteration = range.iterationOrder(); - for (i : srcIteration) { - if (i instanceof PortInstance) { - // No need to do anything if it's not a multiport. - if (i.isMultiport) { - pr(builder, ''' - «CUtil.channelIndex(i, suffix)»++; - if («CUtil.channelIndex(i, suffix)» >= «i.width») { - «CUtil.channelIndex(i, suffix)» = 0; - ''') - indent(builder); - } - } else if (i instanceof ReactorInstance) { - // Need to increment these indices even if the source and - // destination are the same port because these variables have a - // different suffix from those being used to index the destination. - // No need to do anything if it's not a bank. - if (i.isBank) { - pr(builder, ''' - «CUtil.bankIndex(i, suffix)»++; - if («CUtil.bankIndex(i, suffix)» >= «i.width») { - «CUtil.bankIndex(i, suffix)» = 0; - ''') - indent(builder); - } - } - } - // Now need to close all these if blocks. - for (i : srcIteration) { - if (i instanceof PortInstance) { - // No need to do anything if it's not a multiport or if - // the source and destination ports are the same. - if (i.isMultiport) { - unindent(builder); - pr(builder, "}"); - } - } else if (i instanceof ReactorInstance) { - // No need to do anything if it's not a bank. - if (i.isBank) { - unindent(builder); - pr(builder, "}"); + if (srcRange.width > 1) { + pr(builder, ''' + mixed_radix_incr(&src_range_mr); + if (mixed_radix_to_int(&src_range_mr) >= «srcRange.start» + «srcRange.width») { + // Start over with the source. + src_range_mr.digits = src_start; } - } + '''); } + endScopedRangeBlock(builder, dstRange); endScopedBlock(builder); - } - - /** - * For the specified reactor, print code to the specified builder - * that defines a pointer to the self struct of the specified - * reactor. - * - * If the specified reactor is a bank, then the pointer - * is to the array of pointers to the self structs for that bank. - * - * @param builder The string builder into which to write. - * @param reactor The reactor instance for which to provide a self struct. - */ - private def void defineSelfStruct( - StringBuilder builder, ReactorInstance reactor - ) { - defineSelfStruct(builder, reactor, null); - } - - /** - * For the specified reactor, print code to the specified builder - * that defines a pointer to the self struct of the specified - * reactor. - * - * If the specified reactor is a bank, then the pointer - * is to the array of pointers to the self structs for that bank. - * - * @param builder The string builder into which to write. - * @param reactor The reactor instance for which to provide a self struct. - * @param suffix An optional suffix to append to the variable name and - * to index names for banks. - */ - private def void defineSelfStruct( - StringBuilder builder, ReactorInstance reactor, String suffix - ) { - if (reactor === null) return; - var nameOfSelfStruct = reactor.uniqueID() + "_self"; - if (suffix !== null) nameOfSelfStruct += suffix; - var structType = CUtil.selfType(reactor) - pr(builder, ''' - «structType»* «nameOfSelfStruct» = («structType»*)self_structs[«CUtil.indexExpression(reactor, suffix)»]; - ''') - } + } /** * Generate assignments of pointers in the "self" struct of a destination @@ -5186,31 +4981,29 @@ class CGenerator extends GeneratorBase { pr(''' // Connect «src.getFullName» to port «dst.getFullName» ''') - startScopedRangeBlockOutside(code, srcRange, "_src"); - startScopedRangeBlock(code, dstRange, null); - startScopedRangeBlockInside(code, srcRange, "_src"); - // Above uses suffix "_src" for the self struct for the source range. - val sfx = "_src"; + val srcChannel = "src_channel"; + val srcBank = "src_bank"; + val dstBank = "dst_bank"; + + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); if (src.isInput) { // Source port is written to by reaction in port's parent's parent // and ultimate destination is further downstream. pr(''' - «CUtil.portRef(dst)» = («destStructType»*)«mod»«CUtil.portRefNested(src, sfx)»; + «CUtil.portRef(dst, dstBank, null)» = («destStructType»*)«mod»«CUtil.portRefNested(src, srcBank, srcChannel)»; ''') } else if (src == dst) { // An output port of a contained reactor is triggering a reaction. pr(''' - «CUtil.portRefNested(dst)» = («destStructType»*)&«CUtil.portRef(src, sfx)»; + «CUtil.portRefNested(dst, dstBank, null)» = («destStructType»*)&«CUtil.portRef(src, srcBank, srcChannel)»; ''') } else { pr(''' - «CUtil.portRef(dst)» = («destStructType»*)&«CUtil.portRef(src, sfx)»; + «CUtil.portRef(dst, dstBank, null)» = («destStructType»*)&«CUtil.portRef(src, srcBank, srcChannel)»; ''') } - endScopedRangeBlockInside(code, srcRange, "_src"); - endScopedRangeBlock(code, dstRange, null); - endScopedRangeBlockOutside(code, srcRange, "_src"); + endScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); } } } @@ -5752,13 +5545,9 @@ class CGenerator extends GeneratorBase { } /** - * Data structure that for each instantiation of a contained - * reactor. This provides a set of input and output ports that trigger - * reactions of the container, are read by a reaction of the - * container, or that receive data from a reaction of the container. - * For each port, this provides a list of reaction indices that - * are triggered by the port, or an empty list if there are no - * reactions triggered by the port. + * Perform initialization functions that must be performed after + * all reactor runtime instances have been created. + * This function creates nested loops over nested banks. * @param reactor The container. * @param federate The federate (used to determine whether a * reaction belongs to the federate). @@ -5770,7 +5559,7 @@ class CGenerator extends GeneratorBase { return; } - pr('''// **** Start deferredInitialize for «reactor.getFullName()»''') + pr('''// **** Start deferred initialize for «reactor.getFullName()»''') // First batch of initializations is within a for loop iterating // over bank members for the reactor's parent. @@ -5783,21 +5572,45 @@ class CGenerator extends GeneratorBase { // to so we have to assume it can write to any. deferredAllocationForEffectsOnInputs(reactor); - // Initialize the num_destinations fields of port structs on the self struct. - deferredInputNumDestinations(reactions); - deferredReactionMemory(reactions); // For outputs that are not primitive types (of form type* or type[]), // create a default token on the self struct. deferredCreateDefaultTokens(reactor); - + for (child: reactor.children) { deferredInitialize(child, child.reactions); } - + endScopedBlock(code) + pr('''// **** End of deferred initialize for «reactor.getFullName()»''') + } + + /** + * Perform initialization functions that must be performed after + * all reactor runtime instances have been created. + * This function does not create nested loops over nested banks, + * so each function it calls must handle its own iteration + * over all runtime instance. + * @param reactor The container. + * @param federate The federate (used to determine whether a + * reaction belongs to the federate). + */ + private def void deferredInitializeNonNested( + ReactorInstance reactor, Iterable reactions + ) { + if (!currentFederate.contains(reactor)) { + return; + } + + pr('''// **** Start non-nested deferred initialize for «reactor.getFullName()»''') + + // Initialize the num_destinations fields of port structs on the self struct. + // This needs to be outside the above scoped block because it performs + // its own iteration over ranges. + deferredInputNumDestinations(reactions); + // Second batch of initializes cannot be within a for loop // iterating over bank members because they iterate over send // ranges which may span bank members. @@ -5806,9 +5619,13 @@ class CGenerator extends GeneratorBase { deferredOptimizeForSingleDominatingReaction(reactor); - pr('''// **** End of deferredInitialize for «reactor.getFullName()»''') + for (child: reactor.children) { + deferredInitializeNonNested(child, child.reactions); + } + + pr('''// **** End of non-nested deferred initialize for «reactor.getFullName()»''') } - + /** * Generate assignments of pointers in the "self" struct of a destination * port's reactor to the appropriate entries in the "self" struct of the @@ -5853,7 +5670,7 @@ class CGenerator extends GeneratorBase { // If the rootType is 'void', we need to avoid generating the code // 'sizeof(void)', which some compilers reject. val size = (rootType == 'void') ? '0' : '''sizeof(«rootType»)''' - startChannelIteration(code, output, null); + startChannelIteration(code, output); pr(''' «CUtil.portRef(output)».token = _lf_create_token(«size»); ''') @@ -5884,13 +5701,13 @@ class CGenerator extends GeneratorBase { for (sendingRange : output.eventualDestinations) { pr("// For reference counting, set num_destinations for port " + output.fullName + "."); - startScopedRangeBlock(code, sendingRange, null); + startScopedRangeBlock(code, sendingRange, "bank_index"); pr(''' - «CUtil.portRef(output)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRef(output, "bank_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') - endScopedRangeBlock(code, sendingRange, null); + endScopedRangeBlock(code, sendingRange); } } } @@ -5928,19 +5745,19 @@ class CGenerator extends GeneratorBase { // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { - startScopedRangeBlock(code, sendingRange, null); + startScopedRangeBlock(code, sendingRange, "bank_index"); // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { pr(''' - «CUtil.portRefNested(port)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "bank_index", null)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' - «CUtil.portRefNested(port)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "bank_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } - endScopedRangeBlock(code, sendingRange, null); + endScopedRangeBlock(code, sendingRange); } } } @@ -6002,34 +5819,58 @@ class CGenerator extends GeneratorBase { */ private def deferredOptimizeForSingleDominatingReaction (ReactorInstance r) { for (reaction : r.reactions) { + // The following code attempts to gather into a loop assignments of successive + // bank members relations between reactions to avoid large chunks of inline code + // when a large bank sends to a large bank or when a large bank receives from + // one reaction that is either multicasting or sending through a multiport. var start = 0; var end = 0; var domStart = 0; - var domEnd = 0; - var dominating = null as ReactionInstance.Runtime; + var same = false; // Set to true when finding a string of identical dominating reactions. + var previousRuntime = null as ReactionInstance.Runtime; + var first = true; //First time through the loop. for (runtime : reaction.getRuntimeInstances()) { - if (end > start // There is something to output. - && ( - // Not equal and not a successor - (runtime.dominatingReaction != dominating - && ( - runtime.dominatingReaction === null - || runtime.dominatingReaction.id != domEnd + 1 - ) - ) - ) - ) { - // Change in value. - printOptimizeForSingleDominatingReaction(reaction, start, end, dominating, domStart); - start = end; - domStart = domEnd; + if (!first) { // Not the first time through the loop. + if (same) { // Previously seen at least two identical dominating. + if (runtime.dominating != previousRuntime.dominating) { + // End of streak of same dominating reaction runtime instance. + printOptimizeForSingleDominatingReaction( + previousRuntime, start, end, domStart, same + ); + same = false; + start = runtime.id; + domStart = runtime.dominating.id; + } + } else if (runtime.dominating == previousRuntime.dominating) { + // Start of a streak of identical dominating reaction runtime instances. + same = true; + } else if (runtime.dominating.reaction == previousRuntime.dominating.reaction) { + // Same dominating reaction even if not the same dominating runtime. + if (runtime.dominating.id != previousRuntime.dominating.id + 1) { + // End of a streak of contiguous runtimes. + printOptimizeForSingleDominatingReaction( + previousRuntime, start, end, domStart, same + ); + same = false; + start = runtime.id; + domStart = runtime.dominating.id; + } + } else { + // Different dominating reaction. + printOptimizeForSingleDominatingReaction( + previousRuntime, start, end, domStart, same + ); + same = false; + start = runtime.id; + domStart = runtime.dominating.id; + } } - dominating = runtime.dominatingReaction; + first = false; + previousRuntime = runtime; end++; - domEnd++; } if (end > start) { - printOptimizeForSingleDominatingReaction(reaction, start, end, dominating, domStart); + printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); } } } @@ -6038,34 +5879,37 @@ class CGenerator extends GeneratorBase { * Print statement that sets the last_enabling_reaction field of a reaction. */ private def printOptimizeForSingleDominatingReaction( - ReactionInstance reaction, int start, int end, - ReactionInstance.Runtime dominating, int domStart + ReactionInstance.Runtime runtime, int start, int end, int domStart, boolean same ) { var dominatingRef = "NULL"; if (end > start + 1) { - val reactionRef = CUtil.reactionRef(reaction, "fixme", "i"); - if (dominating !== null) { - dominatingRef = "&(" + CUtil.reactionRef(dominating.reaction, "fixme", "j++") + ")"; - pr(''' - int j = «domStart»; - ''') - } startScopedBlock(code); + val reactionRef = CUtil.reactionRef(runtime.reaction, "i"); + if (runtime.dominating !== null) { + if (same) { + dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; + } else { + dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "j++") + ")"; + pr(''' + int j = «domStart»; + ''') + } + } pr(''' - // Reaction «reaction.index» of «reaction.getFullName» dominating upstream reaction. + // «runtime.reaction.getFullName» dominating upstream reaction. for (int i = «start»; i < «end»; i++) { «reactionRef».last_enabling_reaction = «dominatingRef»; } ''') endScopedBlock(code); } else if (end == start + 1) { - val reactionRef = CUtil.reactionRef(reaction, "fixme", "" + start); - if (dominating !== null) { - dominatingRef = "&(" + CUtil.reactionRef(dominating.reaction, "fixme", "" + domStart) + ")"; + val reactionRef = CUtil.reactionRef(runtime.reaction, "" + start); + if (runtime.dominating !== null) { + dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; } pr(''' - // Reaction «reaction.index» of «reaction.getFullName» dominating upstream reaction. + // «runtime.reaction.getFullName» dominating upstream reaction. «reactionRef».last_enabling_reaction = «dominatingRef»; ''') } @@ -6187,7 +6031,7 @@ class CGenerator extends GeneratorBase { } pr(''' // Total number of outputs (single ports and multiport channels) - // produced by reaction «reaction.index» of «name». + // produced by «reaction.toString». «CUtil.reactionRef(reaction)».num_outputs = «outputCount»; ''') if (outputCount > 0) { @@ -6215,47 +6059,51 @@ class CGenerator extends GeneratorBase { private def void deferredFillTriggerTable(Iterable reactions) { for (reaction: reactions) { val name = reaction.parent.getFullName; - // Count the total number of channels in the effects of this reaction - // so that we can allocate memory for triggers[] and triggered_sizes[] - // on the reaction_t struct. - var channelCount = 0; - for (port : reaction.effects.filter(PortInstance)) { - var foundDestinations = false; - // Skip ports whose parent is not in the federation. - // This can happen with reactions in the top-level that have - // as an effect a port in a bank. - if (currentFederate.contains(port.parent)) { + // If the port is a multiport, then its channels may have different sets + // of destinations. For ordinary ports, there will be only one range and + // its width will be 1. + // We generate the code to fill the triggers array first in a temporary code buffer, + // so that we can simultaneously calculate the size of the total array. + val bi = "bank_index"; + for (SendRange srcRange : port.eventualDestinations()) { + startScopedRangeBlock(code, srcRange, bi); - // If the port is a multiport, then its channels may have different sets - // of destinations. For ordinary ports, there will be only one range and - // its width will be 1. - // We generate the code to fill the triggers array first in a temporary code buffer, - // so that we can simultaneously calculate the size of the total array. - val sfx = "_dst"; - for (SendRange srcRange : port.eventualDestinations()) { - startScopedRangeBlock(code, srcRange, null); - val triggerArray = '''«CUtil.reactionRef(reaction)».triggers[«channelCount» + «CUtil.channelIndex(port)»]''' + var triggerArray = '''«CUtil.reactionRef(reaction, bi)».triggers[«CUtil.channelIndex(port)»]''' + // Skip ports whose parent is not in the federation. + // This can happen with reactions in the top-level that have + // as an effect a port in a bank. + if (currentFederate.contains(port.parent)) { pr(''' // Reaction «reaction.index» of «name» triggers «srcRange.destinations.size» downstream reactions // through port «port.getFullName». - «CUtil.reactionRef(reaction)».triggered_sizes[«channelCount» + «CUtil.channelIndex(port)»] = «srcRange.destinations.size»; + «CUtil.reactionRef(reaction, bi)».triggered_sizes[«CUtil.channelIndex(port)»] = «srcRange.destinations.size»; // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» trigger_t** trigger_array = (trigger_t**)malloc(«srcRange.destinations.size» * sizeof(trigger_t*)); «triggerArray» = trigger_array; ''') - endScopedRangeBlock(code, srcRange, null); - channelCount += srcRange.width; + } else { + // Port is not in the federate or has no destinations. + // Set the triggered_width fields to 0. + pr(''' + «CUtil.reactionRef(reaction, bi)».triggered_sizes[«CUtil.channelIndex(port)»] = 0; + ''') + } + endScopedRangeBlock(code, srcRange); + if (currentFederate.contains(port.parent)) { var multicastCount = 0; for (dstRange : srcRange.destinations) { - foundDestinations = true; val dst = dstRange.instance; - startScopedRangeBlockOutside(code, dstRange, sfx); - startScopedRangeBlock(code, srcRange, null); - startScopedRangeBlockInside(code, dstRange, sfx); + val srcChannel = "src_channel"; + val srcBank = "src_bank"; + val dstBank = "dst_bank"; + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); + + // Need to reset triggerArray because of new channel and bank names. + triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[«srcChannel»]''' if (dst.isOutput) { // Include this destination port only if it has at least one @@ -6270,7 +6118,7 @@ class CGenerator extends GeneratorBase { pr(''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. - «triggerArray»[«multicastCount»] = &«CUtil.triggerRefNested(dst, sfx)»; + «triggerArray»[«multicastCount»] = &«CUtil.triggerRefNested(dst, dstBank)»; ''') } else { // Put in a NULL pointer. @@ -6284,56 +6132,31 @@ class CGenerator extends GeneratorBase { // Destination is an input port. pr(''' // Point to destination port «dst.getFullName»'s trigger struct. - «triggerArray»[«multicastCount»] = &«CUtil.triggerRef(dst, sfx)»; + «triggerArray»[«multicastCount»] = &«CUtil.triggerRef(dst, dstBank)»; ''') } - endScopedRangeBlockInside(code, dstRange, sfx); - endScopedRangeBlock(code, srcRange, null); - endScopedRangeBlockOutside(code, dstRange, sfx); + endScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); multicastCount++; } } } - if (!foundDestinations) { - // Port is not in the federate or has no destinations. - channelCount += port.width; - - // Also, set the triggered_width fields to 0. - pr(''' - for (int i = 0; i < «port.width»; i++) { - «CUtil.reactionRef(reaction)».triggered_sizes[destination_index++] = 0; - } - ''') - } } } } /** - * Generate an array of arrays for storing the self structs. + * Generate an array of self structs for the reactor + * and one for each of its children. * @param r The reactor instance. */ private def void generateSelfStructs(ReactorInstance r) { - if (r.depth == 0) { - // Top level, so generate main arrays. - // NOTE; these arrays are put on the stack and discarded after initialization. - val num = r.totalNumberOfChildren + 1; - pr(initializeTriggerObjects, ''' - void* selfs[«num»]; - size_t selfs_sizes[«num»]; - ''') - } - // FIXME FIXME get rid of "fixme" everywhere. pr(initializeTriggerObjects, ''' - «CUtil.selfType(r)»* «CUtil.reactorRef(r, "fixme")»[«r.totalWidth»]; - selfs[«r.id»] = «CUtil.reactorRef(r, "fixme")»; - selfs_sizes[«r.id»] = «r.totalWidth»; + «CUtil.selfType(r)»* «CUtil.reactorRefName(r)»[«r.totalWidth»]; ''') for (child : r.children) { generateSelfStructs(child); } } - ////////////////////////////////////////////////////////////// // Inner class diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index a2a3ee5975..da64da1bb3 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -76,7 +76,7 @@ public class CUtil { //// Public methods. /** - * Return a name of a variable to refer to the bank index of a reactor + * Return a default name of a variable to refer to the bank index of a reactor * in a bank. This is has the form uniqueID_i where uniqueID * is an identifier for the instance that is guaranteed to be different * from the ID of any other instance in the program. @@ -84,27 +84,23 @@ public class CUtil { * @param instance A reactor instance. */ static public String bankIndex(ReactorInstance instance) { - return bankIndex(instance, null); + if (!instance.isBank()) return "0"; + return bankIndexName(instance); } /** - * Return a name of a variable to refer to the bank index of a reactor - * in a bank. This is has the form uniqueID_suffix where uniqueID + * Return a default name of a variable to refer to the bank index of a reactor + * in a bank. This is has the form uniqueID_i where uniqueID * is an identifier for the instance that is guaranteed to be different - * from the ID of any other instance in the program and suffix is the - * specified suffix. If the suffix is null, then "_i" is used. - * If the instance is not a bank, return "0". + * from the ID of any other instance in the program. * @param instance A reactor instance. - * @param suffix The suffix or null to use a default suffix. */ - static public String bankIndex(ReactorInstance instance, String suffix) { - if (!instance.isBank()) return "0"; - if (suffix == null) suffix = "_i"; - return instance.uniqueID() + suffix; + static public String bankIndexName(ReactorInstance instance) { + return instance.uniqueID() + "_i"; } /** - * Return a name of a variable to refer to the channel index of a port + * Return a default name of a variable to refer to the channel index of a port * in a bank. This is has the form uniqueID_c where uniqueID * is an identifier for the instance that is guaranteed to be different * from the ID of any other instance in the program. @@ -112,104 +108,28 @@ static public String bankIndex(ReactorInstance instance, String suffix) { * @param instance A reactor instance. */ static public String channelIndex(PortInstance port) { - return channelIndex(port, null); + if (!port.isMultiport()) return "0"; + return channelIndexName(port); } /** - * Return a name of a variable to refer to the channel index of a port - * in a bank. This is has the form uniqueIDsuffix where uniqueID + * Return a default name of a variable to refer to the channel index of a port + * in a bank. This is has the form uniqueID_c where uniqueID * is an identifier for the instance that is guaranteed to be different * from the ID of any other instance in the program. - * @param suffix The suffix on the name, or null to use the default "_c". + * @param instance A reactor instance. */ - static public String channelIndex(PortInstance port, String suffix) { - if (!port.isMultiport()) return "0"; - if (suffix == null) suffix = "_c"; - return port.uniqueID() + suffix; + static public String channelIndexName(PortInstance port) { + return port.uniqueID() + "_c"; } /** - * Return an expression that, when evaluated in a context with - * bank index variables defined for the specified reactor and - * any container(s) that are also banks, returns a unique index - * for a runtime reactor instance. This can be used to maintain an - * array of runtime instance objects, each of which will have a - * unique index. - * - * This is rather complicated because - * this reactor instance and any of its parents may actually - * represent a bank of runtime reactor instances rather a single - * runtime instance. This method returns an expression that should - * be evaluatable in any target language that uses + for addition - * and * for multiplication and has defined variables in the context - * in which this will be evaluated that specify which bank member is - * desired for this reactor instance and any of its parents that is - * a bank. The names of these variables need to be values returned - * by bankIndex(). - * - * If this is a top-level reactor, this returns "0". - * - * @see bankIndex(ReactorInstance) - * @param prefix The prefix used for index variables for bank members. - */ - static public String indexExpression(ReactorInstance instance) { - return indexExpression(instance, null); - } - - /** - * Return an expression that, when evaluated in a context with - * bank index variables defined for the specified reactor and - * any container(s) that are also banks, returns a unique index - * for a runtime reactor instance. This can be used to maintain an - * array of runtime instance objects, each of which will have a - * unique index. - * - * This is rather complicated because - * this reactor instance and any of its parents may actually - * represent a bank of runtime reactor instances rather a single - * runtime instance. This method returns an expression that should - * be evaluatable in any target language that uses + for addition - * and * for multiplication and has defined variables in the context - * in which this will be evaluated that specify which bank member is - * desired for this reactor instance and any of its parents that is - * a bank. The names of these variables need to be values returned - * by {@link bankIndex(ReactorInstance, String)}), where the second - * argument is the specified suffix. - * - * If this is a top-level reactor, this returns "0". - * - * @param prefix The prefix used for index variables for bank members. - * @param suffix The suffix to use for bank indices, or null to use the default. - */ - static public String indexExpression(ReactorInstance instance, String suffix) { - if (instance.getDepth() == 0) return("0"); - if (instance.isBank()) { - return( - // Position of the bank member relative to the bank. - bankIndex(instance, suffix) + " * " + instance.getNumReactorInstances() - // Position of the bank within its parent. - + " + " + instance.getIndexOffset() - // Position of the parent. - + " + " + indexExpression(instance.getParent(), suffix) - ); - } else { - return( - // Position within the parent. - instance.getIndexOffset() - // Position of the parent. - + " + " + indexExpression(instance.getParent(), suffix) - ); - } - } - - /** - * Return a reference to the specified port on the self struct of the specified - * container reactor. The port is required to have the reactor as either its - * parent or its parent parent or an exception will be thrown. + * Return a reference to the specified port. * * The returned string will have one of the following forms: * * * selfStruct->_lf_portName + * * selfStruct->_lf_portName * * selfStruct->_lf_portName[i] * * selfStruct->_lf_parent.portName * * selfStruct->_lf_parent.portName[i] @@ -229,132 +149,136 @@ static public String indexExpression(ReactorInstance instance, String suffix) { * @param port The port. * @param isNested True to return a reference relative to the parent's parent. * @param includeChannelIndex True to include the channel index at the end. - * @param suffix An optional suffix to append to the struct variable name. + * @param bankIndex A variable name to use to index the bank or null to use the default. + * @param channelIndex A variable name to use to index the channel or null to use the default. */ static public String portRef( - PortInstance port, boolean isNested, boolean includeChannelIndex, String suffix + PortInstance port, + boolean isNested, + boolean includeChannelIndex, + String bankIndex, + String channelIndex ) { String channel = ""; - if (suffix == null) suffix = ""; + if (channelIndex == null) channelIndex = channelIndex(port); if (port.isMultiport() && includeChannelIndex) { - channel = "[" + channelIndex(port) + "]"; + channel = "[" + channelIndex + "]"; } if (isNested) { - return reactorRefNested(port.getParent(), suffix) + "." + port.getName() + channel; + return reactorRefNested(port.getParent(), bankIndex) + "." + port.getName() + channel; } else { - String sourceStruct = CUtil.reactorRef(port.getParent(), suffix); + String sourceStruct = CUtil.reactorRef(port.getParent(), bankIndex); return sourceStruct + "->_lf_" + port.getName() + channel; } } /** - * Special case of {@link portRef(PortInstance, boolean, boolean)} - * that provides a reference to the port on the self struct of the + * Return a reference to the port on the self struct of the * port's parent. This is used when an input port triggers a reaction * in the port's parent or when an output port is written to by * a reaction in the port's parent. - * This is equivalent to calling `portRef(port, false, true, null)`. + * This is equivalent to calling `portRef(port, false, true, null, null)`. * @param port An instance of the port to be referenced. */ static public String portRef(PortInstance port) { - return portRef(port, false, true, null); + return portRef(port, false, true, null, null); } /** - * Special case of {@link portRef(PortInstance, boolean, boolean)} - * that provides a reference to the port on the self struct of the - * port's parent. This is used when an input port triggers a reaction + * Return a reference to the port on the self struct of the + * port's parent using the specified index variables. + * This is used when an input port triggers a reaction * in the port's parent or when an output port is written to by * a reaction in the port's parent. - * This is equivalent to calling `portRef(port, false, true, suffix)`. + * This is equivalent to calling `portRef(port, false, true, bankIndex, channelIndex)`. * @param port An instance of the port to be referenced. - * @param suffix An optional suffix to append to the struct variable name. + * @param bankIndex A variable name to use to index the bank or null to use the default. + * @param channelIndex A variable name to use to index the channel or null to use the default. */ - static public String portRef(PortInstance port, String suffix) { - return portRef(port, false, true, suffix); + static public String portRef(PortInstance port, String bankIndex, String channelIndex) { + return portRef(port, false, true, bankIndex, channelIndex); } /** - * Return the portRef without the channel indexing. + * Return a reference to a port without any channel indexing. * This is useful for deriving a reference to the _width variable. * @param port An instance of the port to be referenced. */ static public String portRefName(PortInstance port) { - return portRef(port, false, false, null); + return portRef(port, false, false, null, null); } /** * Return the portRef without the channel indexing. * This is useful for deriving a reference to the _width variable. * @param port An instance of the port to be referenced. - * @param suffix An optional suffix to append to the struct variable name. + * @param bankIndex A variable name to use to index the bank or null to use the default. + * @param channelIndex A variable name to use to index the channel or null to use the default. */ - static public String portRefName(PortInstance port, String suffix) { - return portRef(port, false, false, suffix); + static public String portRefName(PortInstance port, String bankIndex, String channelIndex) { + return portRef(port, false, false, bankIndex, channelIndex); } /** - * Special case of {@link portRef(PortInstance, boolean, boolean)} - * that provides a reference to the port on the self struct of the + * Return a port reference to a port on the self struct of the * parent of the port's parent. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the * port's parent. - * This is equivalent to calling `portRef(port, true, true)`. + * This is equivalent to calling `portRef(port, true, true, null, null)`. * * @param port The port. */ static public String portRefNested(PortInstance port) { - return portRef(port, true, true, null); + return portRef(port, true, true, null, null); } /** - * Special case of {@link portRef(PortInstance, boolean, boolean)} - * that provides a reference to the port on the self struct of the + * Return a reference to the port on the self struct of the * parent of the port's parent. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the * port's parent. - * This is equivalent to calling `portRef(port, true, true)`. + * This is equivalent to calling `portRef(port, true, true, bankIndex, channelIndex)`. * * @param port The port. - * @param suffix An optional suffix to append to the struct variable name. + * @param bankIndex A variable name to use to index the bank or null to use the default. + * @param channelIndex A variable name to use to index the channel or null to use the default. */ - static public String portRefNested(PortInstance port, String suffix) { - return portRef(port, true, true, suffix); + static public String portRefNested(PortInstance port, String bankIndex, String channelIndex) { + return portRef(port, true, true, bankIndex, channelIndex); } /** - * Special case of {@link portRef(PortInstance, boolean, boolean)} - * that provides a reference to the port on the self struct of the + * Return a reference to the port on the self struct of the * parent of the port's parent, but without the channel indexing, * even if it is a multiport. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the * port's parent. - * This is equivalent to calling `portRef(port, true, false)`. + * This is equivalent to calling `portRef(port, true, false, null, null)`. * * @param port The port. */ static public String portRefNestedName(PortInstance port) { - return portRef(port, true, false, null); + return portRef(port, true, false, null, null); } /** - * Special case of {@link portRef(PortInstance, boolean, boolean)} - * that provides a reference to the port on the self struct of the + * Return a reference to the port on the self struct of the * parent of the port's parent, but without the channel indexing, * even if it is a multiport. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the * port's parent. - * This is equivalent to calling `portRef(port, true, false)`. + * This is equivalent to calling `portRef(port, true, false, bankIndex, channelIndex)`. * * @param port The port. - * @param suffix An optional suffix to append to the struct variable name. + * @param bankIndex A variable name to use to index the bank or null to use the default. + * @param channelIndex A variable name to use to index the channel or null to use the default. */ - static public String portRefNestedName(PortInstance port, String suffix) { - return portRef(port, true, false, suffix); + static public String portRefNestedName(PortInstance port, String bankIndex, String channelIndex) { + return portRef(port, true, false, bankIndex, channelIndex); } /** @@ -370,47 +294,52 @@ static public String reactionRef(ReactionInstance reaction) { * Return a reference to the reaction entry on the self struct * of the parent of the specified reaction. * @param reaction The reaction. - * @param suffix A suffix to use for the parent reactor or null for the default. + * @param bankIndex An index into the array of self structs for the parent. */ - static public String reactionRef(ReactionInstance reaction, String suffix) { - return reactorRef(reaction.getParent(), suffix) + "->_lf__reaction_" + reaction.index; - } - - /** - * Return a reference to the reaction entry on the self struct - * of the parent of the specified reaction. - * @param reaction The reaction. - * @param suffix A suffix to use for the parent reactor or null for the default. - * @param index An index into the array of self structs for the parent. - */ - static public String reactionRef(ReactionInstance reaction, String suffix, String index) { - return reactorRef(reaction.getParent(), suffix) - + "[" + index + "]" + static public String reactionRef(ReactionInstance reaction, String bankIndex) { + return reactorRef(reaction.getParent(), bankIndex) + "->_lf__reaction_" + reaction.index; } /** - * Return a name for a pointer to the "self" struct of the specified - * reactor instance. + * Return a reference to the "self" struct of the specified + * reactor instance. The returned string has the form + * self[j], where self is the name of the array of self structs + * for this reactor instance and j is the expression returned + * by {@link #runtimeIndex(ReactorInstance)} or 0 if there are no banks. * @param instance The reactor instance. - * @return A name to use for a pointer to the self struct. */ static public String reactorRef(ReactorInstance instance) { return reactorRef(instance, null); } /** - * Return a name for a pointer to the "self" struct of the specified - * reactor instance. + * Return the name of the array of "self" structs of the specified + * reactor instance. This is similar to {@link #reactorRef(ReactorInstance)} + * except that it does not index into the array. * @param instance The reactor instance. - * @return A name to use for a pointer to the self struct. - * @param suffix An optional suffix to append to the struct variable name. */ - static public String reactorRef(ReactorInstance instance, String suffix) { - if (suffix == null) suffix = ""; - return instance.uniqueID() + "_self" + suffix; + static public String reactorRefName(ReactorInstance instance) { + return instance.uniqueID() + "_self"; } - + + /** + * Return a reference to the "self" struct of the specified + * reactor instance. The returned string has the form + * self[runtimeIndex], where self is the name of the array of self structs + * for this reactor instance. If runtimeIndex is null, then it is replaced by + * the expression returned + * by {@link runtimeIndex(ReactorInstance)} or 0 if there are no banks. + * @param instance The reactor instance. + * @param runtimeIndex An optional expression to use to address bank members. + * If this is null, the expression used will be that returned by + * {@link #runtimeIndex(ReactorInstance)}. + */ + static public String reactorRef(ReactorInstance instance, String runtimeIndex) { + if (runtimeIndex == null) runtimeIndex = runtimeIndex(instance); + return reactorRefName(instance) + "[" + runtimeIndex + "]"; + } + /** * For situations where a reaction reacts to or reads from an output * of a contained reactor or sends to an input of a contained reactor, @@ -438,15 +367,57 @@ static public String reactorRefNested(ReactorInstance reactor) { * contained reactor. Use {@link reactorRef(ReactorInstance)} for that. * * @param reactor The contained reactor. - * @param suffix An optional suffix to append to the struct variable name. + * @param index An optional index epression to use to address bank members + * when the contained reactor is a bank. Note that the default runtime + * index expression will be used to obtain the self struct of the container. */ - static public String reactorRefNested(ReactorInstance reactor, String suffix) { - String result = reactorRef(reactor.getParent(), suffix) + "->_lf_" + reactor.getName(); + static public String reactorRefNested(ReactorInstance reactor, String index) { + String result = reactorRef(reactor.getParent()) + "->_lf_" + reactor.getName(); if (reactor.isBank()) { - result += "[" + bankIndex(reactor) + "]"; + if (index == null) index = runtimeIndex(reactor); + result += "[" + index + "]"; } return result; } + + /** + * Return an expression that, when evaluated, gives the index of + * a runtime instance of the specified ReactorInstance. If the reactor + * is not within any banks, then this will return "0". Otherwise, it + * will return an expression that evaluates a mixed-radix number + * d0%w0, d1%w1, ... , dn%wn, where n is the depth minus one of the + * reactor. The radixes, w0 to wn, are the widths of this reactor, + * its parent reactor, on up to the top-level reactor. Since the top-level + * reactor is never a bank, dn = 0 and wn = 1. The digits, di, are + * either 0 (of the parent is not a bank) or the variable name returned + * by {@link #bankIndexName(ReactorInstance)} if the parent is a bank. + * The returned expression, when evaluated, will yield the following value: + * + * d0 + w0 * (d1 + w1 * ( ... (dn-1 + wn-1 * dn) ... ) + * + * @param reactor The reactor. + */ + static public String runtimeIndex(ReactorInstance reactor) { + StringBuilder result = new StringBuilder(); + int width = 0; + int parens = 0; + while (reactor != null) { + if (reactor.isBank()) { + if (width > 0) { + result.append(" + " + width + " * ("); + parens++; + } + result.append(bankIndexName(reactor)); + width = reactor.getWidth(); + } + reactor = reactor.getParent(); + } + while (parens-- > 0) { + result.append(")"); + } + if (result.length() == 0) return "0"; + return result.toString(); + } /** * Return a unique type for the "self" struct of the specified @@ -468,44 +439,46 @@ static public String selfType(ReactorInstance instance) { return selfType(instance.getDefinition().getReactorClass()); } - /** 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. - * @param instance The port or action instance. - * @return The name of the trigger struct. + /** + * 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. + * @param instance The port or action instance. */ static public String triggerRef(TriggerInstance instance) { return triggerRef(instance, null); } - /** 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. - * @param instance The port or action instance. - * @param suffix The suffix to use for the reactor reference or null for default. - * @return The name of the trigger struct. - */ - static public String triggerRef(TriggerInstance instance, String suffix) { - return reactorRef(instance.getParent(), suffix) + /** + * 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. + * @param instance The port or action instance. + * @param bankIndex An optional index variable name to use to address bank members. + */ + static public String triggerRef(TriggerInstance instance, String bankIndex) { + return reactorRef(instance.getParent(), bankIndex) + "->_lf__" + instance.getName(); } - /** Return a reference to the trigger_t struct for the specified - * port of a contained reactor. - * @param port The output port of a contained reactor. + /** + * Return a reference to the trigger_t struct for the specified + * port of a contained reactor. + * @param port The output port of a contained reactor. */ static public String triggerRefNested(PortInstance port) { return triggerRefNested(port, null); } - /** Return a reference to the trigger_t struct for the specified - * port of a contained reactor. - * @param port The output port of a contained reactor. - * @param suffix The suffix to use for the reactor reference or null for default. + /** + * Return a reference to the trigger_t struct for the specified + * port of a contained reactor. + * @param port The output port of a contained reactor. + * @param bankIndex An optional index variable name to use to address bank members. */ - static public String triggerRefNested(PortInstance port, String suffix) { - return reactorRefNested(port.getParent(), suffix) + "." + port.getName() + "_trigger"; + static public String triggerRefNested(PortInstance port, String bankIndex) { + return reactorRefNested(port.getParent(), bankIndex) + "." + port.getName() + "_trigger"; } ////////////////////////////////////////////////////// From 5aa55c002733886707592c6b61a63ca5681d6434 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 25 Dec 2021 14:41:45 -0800 Subject: [PATCH 090/221] Renamed bank_index to runtime_index for clarity --- .../src/org/lflang/generator/c/CGenerator.xtend | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 9768648cce..46e330d046 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5701,10 +5701,10 @@ class CGenerator extends GeneratorBase { for (sendingRange : output.eventualDestinations) { pr("// For reference counting, set num_destinations for port " + output.fullName + "."); - startScopedRangeBlock(code, sendingRange, "bank_index"); + startScopedRangeBlock(code, sendingRange, "runtime_index"); pr(''' - «CUtil.portRef(output, "bank_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRef(output, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') endScopedRangeBlock(code, sendingRange); @@ -5745,16 +5745,16 @@ class CGenerator extends GeneratorBase { // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { - startScopedRangeBlock(code, sendingRange, "bank_index"); + startScopedRangeBlock(code, sendingRange, "runtime_index"); // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { pr(''' - «CUtil.portRefNested(port, "bank_index", null)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "runtime_index", null)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' - «CUtil.portRefNested(port, "bank_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } endScopedRangeBlock(code, sendingRange); @@ -6065,7 +6065,7 @@ class CGenerator extends GeneratorBase { // its width will be 1. // We generate the code to fill the triggers array first in a temporary code buffer, // so that we can simultaneously calculate the size of the total array. - val bi = "bank_index"; + val bi = "runtime_index"; for (SendRange srcRange : port.eventualDestinations()) { startScopedRangeBlock(code, srcRange, bi); From c29a53dcdee101babc0091605d4e8f6bd7441f29 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 25 Dec 2021 14:42:09 -0800 Subject: [PATCH 091/221] Comments only --- org.lflang/src/org/lflang/generator/PortInstance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index d5c3da10fe..7269e59f64 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -300,7 +300,7 @@ private static List eventualDestinations(Range srcRange Range wDstRange = dependentPorts.next(); while(true) { if (wSrcRange == null) { - // Source range has been fully covered, but there are more + // Source range has been fully covered, but there may be more // destinations. For multicast, start over with the source. wSrcRange = srcRange; } From 9ed0e3d6be6b1bb2246795c42af1e8105d19c8e6 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 25 Dec 2021 14:42:49 -0800 Subject: [PATCH 092/221] Limit ranges to widths that are visible at a given level of the hierarchy --- .../org/lflang/generator/ReactorInstance.java | 44 +++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 29c7fada6a..419c4bc3cd 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -29,6 +29,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -867,8 +868,8 @@ private void establishPortConnections() { for (Connection connection : ASTUtils.allConnections(reactorDefinition)) { List> leftPorts = listPortInstances(connection.getLeftPorts(), connection); Iterator> srcRanges = leftPorts.iterator(); - Iterator> dstRanges - = listPortInstances(connection.getRightPorts(), connection).iterator(); + List> rightPorts = listPortInstances(connection.getRightPorts(), connection); + Iterator> dstRanges = rightPorts.iterator(); // Check for empty lists. if (!srcRanges.hasNext()) { @@ -961,6 +962,8 @@ private List> listPortInstances( List references, Connection connection ) { List> result = new ArrayList>(); + List> tails = new LinkedList>(); + int count = 0; for (VarRef portRef : references) { // Simple error checking first. if (!(portRef.getVariable() instanceof Port)) { @@ -983,14 +986,49 @@ private List> listPortInstances( if (portRef.isInterleaved()) { // Toggle interleaving at the depth of the reactor // that is the parent of the port. - // FIXME: Here, we are assuming that the interleaved() + // NOTE: Here, we are assuming that the interleaved() // keyword is only allowed on the multiports contained by // contained reactors. range = range.toggleInterleaved(portInstance.parent); } + // If this portRef is not the last one in the references list + // then we have to check whether the range can be incremented at + // the lowest two levels (port and container). If not, + // split the range and add the tail to list to iterate over again. + // The reason for this is that the connection has only local visibility, + // but the range width may be reflective of bank structure higher + // in the hierarchy. + if (count < references.size() - 1) { + int widthBound = portInstance.width * portInstance.parent.width; + if (widthBound < range.width) { + // Need to split the range. + tails.add(range.tail(widthBound)); + range = range.head(widthBound); + } + } result.add(range); } } + // Iterate over the tails. + while(tails.size() > 0) { + List> moreTails = new LinkedList>(); + count = 0; + for (Range tail : tails) { + if (count < tails.size() - 1) { + int widthBound = tail.instance.width; + if (tail._interleaved.contains(tail.instance.parent)) { + widthBound = tail.instance.parent.width; + } + if (widthBound < tail.width) { + // Need to split the range again + moreTails.add(tail.tail(widthBound)); + tail = tail.head(widthBound); + } + } + result.add(tail); + } + tails = moreTails; + } return result; } From f6c80b8dd983ac7af5eafa67606e4169e8ea4cfa Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 26 Dec 2021 10:05:25 -0800 Subject: [PATCH 093/221] More graph corner cases. --- .../generator/ReactionInstanceGraph.java | 46 ++++++++++--------- .../org/lflang/generator/c/CGenerator.xtend | 23 +++------- 2 files changed, 31 insertions(+), 38 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 8b22bb762e..e503b33e5f 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -111,29 +111,33 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti for (SendRange sendRange : port.eventualDestinations()) { int depth = (port.isInput())? 2 : 1; for (Range dstRange : sendRange.destinations) { + // If the destination instance is the same as the source instance, + // skip this. Such ranges show up whenever retrieving destinations + // for a port that has reactions. + if (dstRange.instance == sendRange.instance) continue; Iterator sendParentIDs = sendRange.parentInstances(depth).iterator(); - for (int dstIndex : dstRange.parentInstances(1)) { + while (sendParentIDs.hasNext()) { int srcIndex = sendParentIDs.next(); - // Destination may be wider than the source (multicast). - if (!sendParentIDs.hasNext()) sendParentIDs = sendRange.parentInstances(depth).iterator(); - for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { - List dstRuntimes = dstReaction.getRuntimeInstances(); - Runtime srcRuntime = srcRuntimes.get(srcIndex); - Runtime dstRuntime = dstRuntimes.get(dstIndex); - addEdge(dstRuntime, srcRuntime); - - // Propagate the deadlines, if any. - if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { - srcRuntime.deadline = dstRuntime.deadline; - } - - // If this seems to be a single dominating reaction, set it. - // If another upstream reaction shows up, then this will be - // reset to null. - if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { - dstRuntime.dominating = srcRuntime; - } else { - dstRuntime.dominating = null; + for (int dstIndex : dstRange.parentInstances(1)) { + for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { + List dstRuntimes = dstReaction.getRuntimeInstances(); + Runtime srcRuntime = srcRuntimes.get(srcIndex); + Runtime dstRuntime = dstRuntimes.get(dstIndex); + addEdge(dstRuntime, srcRuntime); + + // Propagate the deadlines, if any. + if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { + srcRuntime.deadline = dstRuntime.deadline; + } + + // If this seems to be a single dominating reaction, set it. + // If another upstream reaction shows up, then this will be + // reset to null. + if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { + dstRuntime.dominating = srcRuntime; + } else { + dstRuntime.dominating = null; + } } } } diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 46e330d046..ad5e50cd57 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3334,32 +3334,20 @@ class CGenerator extends GeneratorBase { * @param timers The timers. */ private def generateTimerInitializations(Iterable timers) { - var foundOne = null as TimerInstance; - val temp = new StringBuilder(); for (timer : timers) { if (!timer.isStartup) { - foundOne = timer; val triggerStructName = CUtil.reactorRef(timer.parent) + "->_lf__" + timer.name; val offset = CUtil.VG.getTargetTime(timer.offset) val period = CUtil.VG.getTargetTime(timer.period) - pr(temp, ''' + pr(initializeTriggerObjects, ''' + // Initializing timer «timer.fullName». «triggerStructName».offset = «offset»; «triggerStructName».period = «period»; - _lf_timer_triggers[«timerCount» + count] = &«triggerStructName»; + _lf_timer_triggers[_lf_timer_triggers_count++] = &«triggerStructName»; ''') - timerCount += timer.parent.width; + timerCount += timer.parent.totalWidth; } - triggerCount += timer.parent.width; - } - if (foundOne !== null) { - pr(initializeTriggerObjects, '''// Initializing timer «foundOne.fullName».'''); - startScopedBlock(initializeTriggerObjects); - pr(initializeTriggerObjects, "int count = 0;"); - startScopedBlock(initializeTriggerObjects, foundOne.parent); - pr(initializeTriggerObjects, temp.toString); - pr(initializeTriggerObjects, "count++;"); - endScopedBlock(initializeTriggerObjects); - endScopedBlock(initializeTriggerObjects); + triggerCount += timer.parent.totalWidth; } } @@ -3564,6 +3552,7 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, ''' int _lf_startup_reactions_count = 0; int _lf_shutdown_reactions_count = 0; + int _lf_timer_triggers_count = 0; '''); for (child: main.children) { From 25acebe8d89cd9d3810ed590087b014c15e94685 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 26 Dec 2021 16:02:51 -0800 Subject: [PATCH 094/221] Make parameter initializer intermediate variable static so array initializers do not get put on the stack. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index ad5e50cd57..95ec9cbddc 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3804,11 +3804,14 @@ class CGenerator extends GeneratorBase { // NOTE: we now use the resolved literal value. For better efficiency, we could // store constants in a global array and refer to its elements to avoid duplicate // memory allocations. - // Use an intermediate temporary variable so that parameter dependencies + // NOTE: Use an intermediate temporary variable so that parameter dependencies // are resolved correctly. + // NOTE: Use a static variable because if the initializer is an array, then + // at least some compliers (gcc) will put it on the stack and when it gets + // dereferenced, it will likely be corrupted. val temporaryVariableName = parameter.uniqueID pr(initializeTriggerObjects, ''' - «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; + static «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; «selfRef»->«parameter.name» = «temporaryVariableName»; ''') } From e74a0ab902a5d52436f4d0c0d2e90f1de802d4f0 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 26 Dec 2021 17:01:03 -0800 Subject: [PATCH 095/221] Use static initializer to prevent initial array value for a parameter from going on the stack --- .../org/lflang/generator/c/CGenerator.xtend | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 95ec9cbddc..02ad660a13 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3804,16 +3804,23 @@ class CGenerator extends GeneratorBase { // NOTE: we now use the resolved literal value. For better efficiency, we could // store constants in a global array and refer to its elements to avoid duplicate // memory allocations. - // NOTE: Use an intermediate temporary variable so that parameter dependencies - // are resolved correctly. - // NOTE: Use a static variable because if the initializer is an array, then - // at least some compliers (gcc) will put it on the stack and when it gets - // dereferenced, it will likely be corrupted. - val temporaryVariableName = parameter.uniqueID - pr(initializeTriggerObjects, ''' - static «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «parameter.getInitializer»; - «selfRef»->«parameter.name» = «temporaryVariableName»; - ''') + // NOTE: If the parameter is initialized with a static initializer for an array + // or struct (the initialization expression is surrounded by { ... }), then we + // have to declare a static variable to ensure that the memory is put in data space + // and not on the stack. + // FIXME: Is there a better way to determine this than the string comparison? + val initializer = getInitializer(parameter); + if (initializer.startsWith("{")) { + val temporaryVariableName = parameter.uniqueID + pr(initializeTriggerObjects, ''' + static «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «initializer»; + «selfRef»->«parameter.name» = «temporaryVariableName»; + ''') + } else { + pr(initializeTriggerObjects, ''' + «selfRef»->«parameter.name» = «initializer»; + ''') + } } } From 869241feab2479b057f0a6c8aa5e3fd2a87b64b9 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 27 Dec 2021 10:26:43 -0800 Subject: [PATCH 096/221] Convert types of form int[] to int* because C can't have int[] --- org.lflang/src/org/lflang/generator/c/CTypes.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java index c2c7320050..df8a33c42c 100644 --- a/org.lflang/src/org/lflang/generator/c/CTypes.java +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -82,10 +82,7 @@ public String getTargetType(InferredType type) { * is an array type. */ public String getVariableDeclaration(InferredType type, String variableName) { - // FIXME: Same as for getTargetType. - // Array type has to be handled specially because C doesn't accept - // type[] as a type designator. - String t = TargetTypes.super.getTargetType(type); + String t = getTargetType(type); Matcher matcher = arrayPattern.matcher(t); String declaration = String.format("%s %s", t, variableName); if (matcher.find()) { From 31b2268f4c4f4c66925d42e7bc6a4972382d4570 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 27 Dec 2021 10:26:58 -0800 Subject: [PATCH 097/221] Pointer connector --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 2dd0eb1512..09f6f902b0 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3200,7 +3200,7 @@ class CGenerator extends GeneratorBase { _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token = &«portRef»[i]->token; _lf_tokens_with_ref_count[«startTimeStepTokens» + i].status - = (port_status_t*)&«portRef»[i].is_present; + = (port_status_t*)&«portRef»[i]->is_present; _lf_tokens_with_ref_count[«startTimeStepTokens» + i].reset_is_present = false; } ''') @@ -3210,7 +3210,7 @@ class CGenerator extends GeneratorBase { _lf_tokens_with_ref_count[«startTimeStepTokens»].token = &«portRef»->token; _lf_tokens_with_ref_count[«startTimeStepTokens»].status - = (port_status_t*)&«portRef».is_present; + = (port_status_t*)&«portRef»->is_present; _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = false; ''') startTimeStepTokens++ From d4ce815c07cf038939a96e7e44ee4c5b321f7741 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 27 Dec 2021 14:04:48 -0800 Subject: [PATCH 098/221] Return ZERO when popping up to the top level --- org.lflang/src/org/lflang/generator/MixedRadixInt.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index a01f7c9b8b..9334469734 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -70,20 +70,21 @@ public MixedRadixInt(List digits, List radixes) { this.digits.add(0); } } + + public final static MixedRadixInt ZERO = new MixedRadixInt(List.of(0), List.of(1)); ////////////////////////////////////////////////////////// //// Public methods /** * Drop the first n digits and return the resulting mixed-radix number. + * If n is larger than or equal to the total number of digits, return ZERO. * @param n The number of digits to drop. - * @throws IllegalArgumentException If n is equal to or larger than the - * number of digits. */ public MixedRadixInt drop(int n) { if (n == 0) return this; if (n >= digits.size() || n < 0) { - throw new IllegalArgumentException("Cannot drop more digits than there are!"); + return ZERO; } List d = new ArrayList(digits.size() - n); List r = new ArrayList(digits.size() - n); From 04ebf9d40a9c8cb83ac7cf06378a314c9967c596 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 27 Dec 2021 14:05:23 -0800 Subject: [PATCH 099/221] Rename methods and fix handling of outputs that trigger reactions --- .../org/lflang/generator/c/CGenerator.xtend | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 09f6f902b0..11760bb185 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -650,7 +650,7 @@ class CGenerator extends GeneratorBase { copyTargetHeaderFile() // Generate code for each reactor. - generateReactorDefinitionsForFederate(); + generateReactorDefinitions(); // Derive target filename from the .lf filename. val cFilename = CCompiler.getTargetFileName(topLevelName, this.CCppMode); @@ -1128,14 +1128,14 @@ class CGenerator extends GeneratorBase { * of cmake-include files. * - If there are any preambles, add them to the preambles of the reactor. */ - private def void generateReactorDefinitionsForFederate() { + private def void generateReactorDefinitions() { val generatedReactorDecls = newLinkedHashSet if (this.main !== null) { - generateReactorChildrenForReactorInFederate(this.main, generatedReactorDecls); + generateReactorChildren(this.main, generatedReactorDecls); } if (this.mainDef !== null) { - generateReactorFederated(this.mainDef.reactorClass) + generateReactorClass(this.mainDef.reactorClass) } // Generate code for each reactor that was not instantiated in main or its children. @@ -1147,7 +1147,7 @@ class CGenerator extends GeneratorBase { // generate code for it anyway (at a minimum, this means that the compiler is invoked // so that reaction bodies are checked). if (mainDef === null && declarations.isEmpty()) { - generateReactorFederated(r) + generateReactorClass(r) } } } @@ -1164,7 +1164,7 @@ class CGenerator extends GeneratorBase { * * @param reactor Used to extract children from */ - private def void generateReactorChildrenForReactorInFederate( + private def void generateReactorChildren( ReactorInstance reactor, LinkedHashSet generatedReactorDecls ) { @@ -1175,9 +1175,9 @@ class CGenerator extends GeneratorBase { for (d : declarations) { if (!generatedReactorDecls.contains(d)) { generatedReactorDecls.add(d); - generateReactorChildrenForReactorInFederate(r, generatedReactorDecls); + generateReactorChildren(r, generatedReactorDecls); inspectReactorEResource(d); - generateReactorFederated(d); + generateReactorClass(d); } } } @@ -1770,7 +1770,7 @@ class CGenerator extends GeneratorBase { * data to contained reactors that are not in the federate. * @param reactor The parsed reactor data structure. */ - private def generateReactorFederated(ReactorDecl reactor) { + private def generateReactorClass(ReactorDecl reactor) { // FIXME: Currently we're not reusing definitions for declarations that point to the same definition. val defn = reactor.toDefinition @@ -5100,12 +5100,13 @@ class CGenerator extends GeneratorBase { pr(''' «CUtil.portRef(dst, dstBank, null)» = («destStructType»*)«mod»«CUtil.portRefNested(src, srcBank, srcChannel)»; ''') - } else if (src == dst) { + } else if (dst.isOutput) { // An output port of a contained reactor is triggering a reaction. pr(''' «CUtil.portRefNested(dst, dstBank, null)» = («destStructType»*)&«CUtil.portRef(src, srcBank, srcChannel)»; ''') } else { + // An output port is triggering pr(''' «CUtil.portRef(dst, dstBank, null)» = («destStructType»*)&«CUtil.portRef(src, srcBank, srcChannel)»; ''') From 329584ec9c244de5b9f7b1f87d51686b4b18cb6c Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 27 Dec 2021 15:57:49 -0800 Subject: [PATCH 100/221] Do not optimize single downstream reaction invocation unless the reaction index is 0 or the reaction is unordered --- .../src/org/lflang/generator/ReactionInstanceGraph.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 49445dcfa4..529b9be0b5 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -133,7 +133,9 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti // If this seems to be a single dominating reaction, set it. // If another upstream reaction shows up, then this will be // reset to null. - if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1) { + if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 + && (dstRuntime.getReaction().isUnordered + || dstRuntime.getReaction().index == 0)) { dstRuntime.dominating = srcRuntime; } else { dstRuntime.dominating = null; From 48aafebe44a6f0946dace6a5f0cad402e45856c2 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 27 Dec 2021 16:11:21 -0800 Subject: [PATCH 101/221] Fixed setting of output_produced with multiple destinations --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 11760bb185..7dbb4ef9ef 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6090,6 +6090,8 @@ class CGenerator extends GeneratorBase { var outputCount = 0; val init = new StringBuilder() + startScopedBlock(init); + pr(init, "int count = 0;") for (effect : reaction.effects.filter(PortInstance)) { // Create the entry in the output_produced array for this port. // If the port is a multiport, then we need to create an entry for each @@ -6098,8 +6100,6 @@ class CGenerator extends GeneratorBase { // If the port is an input of a contained reactor, then, if that // contained reactor is a bank, we will have to iterate over bank // members. - startScopedBlock(init); - pr(init, "int count = 0;") var bankWidth = 1; var portRef = ""; if (effect.isInput) { @@ -6135,8 +6135,8 @@ class CGenerator extends GeneratorBase { outputCount += bankWidth; } endScopedBlock(init); - endScopedBlock(init); } + endScopedBlock(init); pr(''' // Total number of outputs (single ports and multiport channels) // produced by «reaction.toString». From 32305f8fdb17a6bee11f7aeb46e1556801879e11 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 27 Dec 2021 17:38:57 -0800 Subject: [PATCH 102/221] Get LFC to compile. --- .../org/lflang/generator/GeneratorBase.xtend | 16 -- .../org/lflang/generator/ValueGenerator.java | 175 ++++++++++++++++++ .../org/lflang/generator/ts/TSGenerator.kt | 31 +--- 3 files changed, 178 insertions(+), 44 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/ValueGenerator.java diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 82c9708a73..ff0b828d01 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -1238,22 +1238,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { */ abstract def Target getTarget() - protected def getTargetType(Parameter p) { - return getTargetTypes().getTargetType(p.inferredType) - } - - protected def getTargetType(StateVar s) { - return getTargetTypes().getTargetType(s.inferredType) - } - - protected def getTargetType(Action a) { - return getTargetTypes().getTargetType(a.inferredType) - } - - protected def getTargetType(Port p) { - return getTargetTypes().getTargetType(p.inferredType) - } - /** * Get textual representation of a time in the target language. * diff --git a/org.lflang/src/org/lflang/generator/ValueGenerator.java b/org.lflang/src/org/lflang/generator/ValueGenerator.java new file mode 100644 index 0000000000..190777f764 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/ValueGenerator.java @@ -0,0 +1,175 @@ +package org.lflang.generator; + +import java.util.List; +import java.util.ArrayList; +import java.util.stream.Collectors; + +import org.lflang.ASTUtils; +import org.lflang.JavaAstUtils; +import org.lflang.TimeValue; +import org.lflang.lf.Assignment; +import org.lflang.lf.Delay; +import org.lflang.lf.Instantiation; +import org.lflang.lf.Parameter; +import org.lflang.lf.StateVar; +import org.lflang.lf.Time; +import org.lflang.TimeUnit; +import org.lflang.lf.Value; + +/** + * Encapsulates logic for representing {@code Value}s in a + * target language. + */ +public final class ValueGenerator { + + /** + * A {@code TimeInTargetLanguage} is a + * target-language-specific time representation + * strategy. + */ + public interface TimeInTargetLanguage { + String apply(TimeValue t); + } + + /** + * A {@code GetTargetReference} instance is a + * target-language-specific function. It provides the + * target language code that refers to the given + * parameter {@code param}. + */ + public interface GetTargetReference { + String apply(Parameter param); + } + + private final TimeInTargetLanguage timeInTargetLanguage; + private final GetTargetReference getTargetReference; + + /** + * Instantiates a target-language-specific + * ValueGenerator parameterized by {@code f}. + * @param f a time representation strategy + */ + public ValueGenerator(TimeInTargetLanguage f, GetTargetReference g) { + this.timeInTargetLanguage = f; + this.getTargetReference = g; + } + + /** + * Create a list of state initializers in target code. + * + * @param state The state variable to create initializers for + * @return A list of initializers in target code + */ + public List getInitializerList(StateVar state) { + List list = new ArrayList<>(); + // FIXME: Previously, we returned null if it was not initialized, which would have caused an + // NPE in TSStateGenerator. Is this the desired behavior? + if (!ASTUtils.isInitialized(state)) return list; + for (Value v : state.getInit()) { + if (v.getParameter() != null) { + list.add(getTargetReference.apply(v.getParameter())); + } else { + list.add(getTargetValue(v, JavaAstUtils.isOfTimeType(state))); + } + } + return list; + } + + /** + * Create a list of default parameter initializers in target code. + * + * @param param The parameter to create initializers for + * @return A list of initializers in target code + */ + public List getInitializerList(Parameter param) { + List list = new ArrayList<>(); + if (param == null) return list; + for (Value v : param.getInit()) + list.add(getTargetValue(v, JavaAstUtils.isOfTimeType(param))); + return list; + } + + /** + * Create a list of parameter initializers in target code in the context + * of an reactor instantiation. + * + * This respects the parameter assignments given in the reactor + * instantiation and falls back to the reactors default initializers + * if no value is assigned to it. + * + * @param param The parameter to create initializers for + * @return A list of initializers in target code + */ + public List getInitializerList(Parameter param, Instantiation i) { + List assignments = i.getParameters().stream() + .filter(it -> it.getLhs() == param) + .collect(Collectors.toList()); + if (assignments.isEmpty()) // Case 0: The parameter was not overwritten in the instantiation + return getInitializerList(param); + // Case 1: The parameter was overwritten in the instantiation + List list = new ArrayList<>(); + if (assignments.get(0) == null) return list; + for (Value init : assignments.get(0).getRhs()) + list.add(getTargetValue(init, JavaAstUtils.isOfTimeType(param))); + return list; + } + + /** + * Return the time specified by {@code t}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(TimeValue t) { + return timeInTargetLanguage.apply(t); + } + + /** + * Return the time specified by {@code t}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(Time t) { + return timeInTargetLanguage.apply(new TimeValue(t.getInterval(), TimeUnit.fromName(t.getUnit()))); + } + + /** + * Return the time specified by {@code d}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(Delay d) { + return d.getParameter() != null ? ASTUtils.toText(d) : timeInTargetLanguage.apply( + JavaAstUtils.toTimeValue(d.getTime()) // The time is given as a parameter reference. + ); + } + + /** + * Return the time specified by {@code v}, expressed as + * code that is valid for some target languages. + */ + public String getTargetTime(Value v) { + return getTargetValue(v, true); + } + + /** + * Get textual representation of a value in the target language. + * + * If the value evaluates to 0, it is interpreted as a normal value. + * + * @param v A time AST node + * @return A time string in the target language + */ + public String getTargetValue(Value v) { + return ASTUtils.toText(v); + } + + /** + * Get textual representation of a value in the target language. + * + * @param v A time AST node + * @param isTime Whether {@code v} is expected to be a time + * @return A time string in the target language + */ + public String getTargetValue(Value v, boolean isTime) { + if (v.getTime() != null) return getTargetTime(v.getTime()); + if (isTime && ASTUtils.isZero(v)) return timeInTargetLanguage.apply(TimeValue.ZERO); + return ASTUtils.toText(v); + } +} diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index b29c495ed6..73752190b4 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -78,7 +78,7 @@ class TSGenerator( private val VG = ValueGenerator(::timeInTargetLanguage) { param -> "this.${param.name}.get()" } private fun timeInTargetLanguage(value: TimeValue): String { - return if (value.unit != TimeUnit.NONE) { + return if (value.unit != null) { "TimeValue.${value.unit}(${value.time})" } else { // The value must be zero. @@ -355,6 +355,8 @@ class TSGenerator( // } } + override fun getTargetTypes(): TargetTypes = TSTypes + /** * Return a TS type for the specified action. * If the type has not been specified, return @@ -370,33 +372,6 @@ class TSGenerator( } } - /** Given a representation of time that may possibly include units, - * return a string that TypeScript can recognize as a value. - * @param value Literal that represents a time value. - * @return A string, as "[ timeLiteral, TimeUnit.unit]" . - */ - override fun timeInTargetLanguage(value: TimeValue): String { - return if (value.unit != null) { - "TimeValue.${value.unit.canonicalName}(${value.magnitude})" - } else { - // The value must be zero. - "TimeValue.zero()" - } - } - - override fun getTargetType(s: StateVar): String { - val type = super.getTargetType(s) - return if (!isInitialized(s)) { - "$type | undefined" - } else { - type - } - } - - override fun getTargetReference(param: Parameter): String { - return "this.${param.name}.get()" - } - /** * Generate code for the body of a reaction that handles the * action that is triggered by receiving a message from a remote From 988919c2019d193e19f74ec4a9af4b070c2594d8 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 27 Dec 2021 18:30:07 -0800 Subject: [PATCH 103/221] Bring back special handling for arrays. --- org.lflang/src/org/lflang/generator/c/CTypes.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java index df8a33c42c..b1d5c7de9d 100644 --- a/org.lflang/src/org/lflang/generator/c/CTypes.java +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -82,7 +82,7 @@ public String getTargetType(InferredType type) { * is an array type. */ public String getVariableDeclaration(InferredType type, String variableName) { - String t = getTargetType(type); + String t = TargetTypes.super.getTargetType(type); Matcher matcher = arrayPattern.matcher(t); String declaration = String.format("%s %s", t, variableName); if (matcher.find()) { From 1c98c890c695dbdef4ad5efd0577ebd5fbb499ec Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 28 Dec 2021 08:21:40 -0800 Subject: [PATCH 104/221] Further refinement of C types special cases --- .../org/lflang/generator/c/CGenerator.xtend | 9 ++-- .../src/org/lflang/generator/c/CTypes.java | 52 ++++++++++++++----- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 7dbb4ef9ef..03d1d4bcf5 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1978,7 +1978,7 @@ class CGenerator extends GeneratorBase { } // Do not convert to lf_token_t* using lfTypeToTokenType because there // will be a separate field pointing to the token. - return types.getVariableDeclaration(port.inferredType, "value") + ";" + return types.getVariableDeclaration(port.inferredType, "value", false) + ";" } /** @@ -3862,7 +3862,7 @@ class CGenerator extends GeneratorBase { } else { pr(initializeTriggerObjects, ''' { // For scoping - static «types.getVariableDeclaration(stateVar.inferredType, "_initial")» = «initializer»; + static «types.getVariableDeclaration(stateVar.inferredType, "_initial", true)» = «initializer»; «selfRef»->«stateVar.name» = _initial; } // End scoping. ''' @@ -3908,7 +3908,7 @@ class CGenerator extends GeneratorBase { if (initializer.startsWith("{")) { val temporaryVariableName = parameter.uniqueID pr(initializeTriggerObjects, ''' - static «types.getVariableDeclaration(parameter.type, temporaryVariableName)» = «initializer»; + static «types.getVariableDeclaration(parameter.type, temporaryVariableName, true)» = «initializer»; «selfRef»->«parameter.name» = «temporaryVariableName»; ''') } else { @@ -5481,7 +5481,8 @@ class CGenerator extends GeneratorBase { protected def isTokenType(InferredType type) { if (type.isUndefined) return false - val targetType = types.getVariableDeclaration(type, "") // This is a hacky way to do this. It is now considered to be a bug (#657) + // This is a hacky way to do this. It is now considered to be a bug (#657) + val targetType = types.getVariableDeclaration(type, "", false) return type.isVariableSizeList || targetType.contains("*") } diff --git a/org.lflang/src/org/lflang/generator/c/CTypes.java b/org.lflang/src/org/lflang/generator/c/CTypes.java index b1d5c7de9d..5da55e1401 100644 --- a/org.lflang/src/org/lflang/generator/c/CTypes.java +++ b/org.lflang/src/org/lflang/generator/c/CTypes.java @@ -59,12 +59,18 @@ public String getTargetUndefinedType() { return String.format("/* %s */", errorReporter.reportError("undefined type")); } + /** + * Given a type, return a C representation of the type. Note that + * C types are very idiosyncratic. For example, {@code int[]} is not always accepted + * as a type, and {@code int*} must be used instead, except when initializing + * a variable using a static initializer, as in {@code int[] foo = {1, 2, 3};}. + * When initializing a variable using a static initializer, use + * {@link #getVariableDeclaration(InferredType, String)} instead. + * @param type The type. + */ @Override public String getTargetType(InferredType type) { var result = TargetTypes.super.getTargetType(type); - // FIXME: This is brittle. Rather than patching up the results of almost-right calls - // to other methods, we should have a more general implementation of the method that - // takes more parameters. Matcher matcher = arrayPattern.matcher(result); if (matcher.find()) { return matcher.group(1) + '*'; @@ -73,23 +79,43 @@ public String getTargetType(InferredType type) { } /** - * Provides the same functionality as getTargetType, except with - * the array syntax {@code []} preferred over the pointer syntax - * {@code *} (if applicable), and with the variable name - * included. - * The result is of the form {@code type variable_name[]} or + * Return a variable declaration of the form "{@code type name}". + * The type is as returned by {@link #getTargetType(InferredType)}, except with + * the array syntax {@code [size]} preferred over the pointer syntax + * {@code *} (if applicable). This also includes the variable name + * because C requires the array type specification to be placed after + * the variable name rather than with the type. + * The result is of the form {@code type variable_name[size]} or * {@code type variable_name} depending on whether the given type - * is an array type. + * is an array type, unless the array type has no size (it is given + * as {@code []}. In that case, the returned form depends on the + * third argument, initializer. If true, the then the returned + * declaration will have the form {@code type variable_name[]}, + * and otherwise it will have the form {@code type* variable_name}. + * @param type The type. + * @param variableName The name of the variable. + * @param initializer True to return a form usable in a static initializer. */ - public String getVariableDeclaration(InferredType type, String variableName) { + public String getVariableDeclaration( + InferredType type, + String variableName, + boolean initializer + ) { String t = TargetTypes.super.getTargetType(type); Matcher matcher = arrayPattern.matcher(t); String declaration = String.format("%s %s", t, variableName); if (matcher.find()) { - // If the state type ends in [], then we have to move the [] + // For array types, we have to move the [] // because C is very picky about where this goes. It has to go - // after the variable name. - declaration = String.format("%s %s[%s]", matcher.group(1), variableName, matcher.group(2)); + // after the variable name. Also, in an initializer, it has to have + // form [], and in a struct definition, it has to use *. + if (matcher.group(2).equals("") && !initializer) { + declaration = String.format("%s* %s", + matcher.group(1), variableName); + } else { + declaration = String.format("%s %s[%s]", + matcher.group(1), variableName, matcher.group(2)); + } } return declaration; } From 906d6c2b3b7af1224834b0cef092d8cc946dbcfe Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 28 Dec 2021 10:22:58 -0800 Subject: [PATCH 105/221] Handle reactions driven by an output port of a contained reactor --- .../src/org/lflang/generator/ReactionInstanceGraph.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 529b9be0b5..5398a3f725 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -118,7 +118,10 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti Iterator sendParentIDs = sendRange.parentInstances(depth).iterator(); while (sendParentIDs.hasNext()) { int srcIndex = sendParentIDs.next(); - for (int dstIndex : dstRange.parentInstances(1)) { + Set parentInstances = (dstRange.instance.isOutput()) ? + dstRange.parentInstances(2) + : dstRange.parentInstances(1); + for (int dstIndex : parentInstances) { for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { List dstRuntimes = dstReaction.getRuntimeInstances(); Runtime srcRuntime = srcRuntimes.get(srcIndex); From 8ef1e33e644d2ffefc762ad039c6b23b310709c6 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 28 Dec 2021 10:23:31 -0800 Subject: [PATCH 106/221] Store both declaration and definition of reactor. --- org.lflang/src/org/lflang/generator/ReactorInstance.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index b26d84d6c8..2c6d76b128 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -48,6 +48,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Port; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; +import org.lflang.lf.ReactorDecl; import org.lflang.lf.Timer; import org.lflang.lf.TriggerRef; import org.lflang.lf.Value; @@ -146,7 +147,10 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re /** The timer instances belonging to this reactor instance. */ public final List timers = new ArrayList<>(); - /** The reactor definition in the AST. */ + /** The reactor declaration in the AST. This is either an import or Reactor declaration. */ + public final ReactorDecl reactorDeclaration; + + /** The reactor after imports are resolve. */ public final Reactor reactorDefinition; /** Indicator that this reactor has itself as a parent, an error condition. */ @@ -767,7 +771,8 @@ private ReactorInstance( Set unorderedReactions) { super(definition, parent); this.reporter = reporter; - this.reactorDefinition = ASTUtils.toDefinition(definition.getReactorClass()); + this.reactorDeclaration = definition.getReactorClass(); + this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); this.id = root().childCount++; if (unorderedReactions != null) { From ba78867244fafd7d9cc5018e6588d4cec3162a11 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 28 Dec 2021 10:24:07 -0800 Subject: [PATCH 107/221] Use declaration rather than definition to solve typing problems --- .../org/lflang/generator/c/CGenerator.xtend | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 03d1d4bcf5..08f4accd88 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1138,16 +1138,18 @@ class CGenerator extends GeneratorBase { generateReactorClass(this.mainDef.reactorClass) } - // Generate code for each reactor that was not instantiated in main or its children. - for (r : reactors) { - // Get the declarations for reactors that are instantiated somewhere. - // A declaration is either a reactor definition or an import statement. - val declarations = this.instantiationGraph.getDeclarations(r); - // If the reactor has no instantiations and there is no main reactor, then - // generate code for it anyway (at a minimum, this means that the compiler is invoked - // so that reaction bodies are checked). - if (mainDef === null && declarations.isEmpty()) { - generateReactorClass(r) + if (mainDef === null) { + // Generate code for each reactor that was not instantiated in main or its children. + for (r : reactors) { + // Get the declarations for reactors that are instantiated somewhere. + // A declaration is either a reactor definition or an import statement. + val declarations = this.instantiationGraph.getDeclarations(r); + // If the reactor has no instantiations and there is no main reactor, then + // generate code for it anyway (at a minimum, this means that the compiler is invoked + // so that reaction bodies are checked). + if (declarations.isEmpty()) { + generateReactorClass(r) + } } } } @@ -1170,15 +1172,12 @@ class CGenerator extends GeneratorBase { ) { for (r : reactor.children) { if (currentFederate.contains(r)) { - val declarations = this.instantiationGraph.getDeclarations(r.reactorDefinition); - if (!declarations.isNullOrEmpty) { - for (d : declarations) { - if (!generatedReactorDecls.contains(d)) { - generatedReactorDecls.add(d); - generateReactorChildren(r, generatedReactorDecls); - inspectReactorEResource(d); - generateReactorClass(d); - } + if (r.reactorDeclaration !== null) { + if (!generatedReactorDecls.contains(r.reactorDeclaration)) { + generatedReactorDecls.add(r.reactorDeclaration); + generateReactorChildren(r, generatedReactorDecls); + inspectReactorEResource(r.reactorDeclaration); + generateReactorClass(r.reactorDeclaration); } } } @@ -3524,7 +3523,7 @@ class CGenerator extends GeneratorBase { * @return The name of the self struct. */ static def variableStructType(TriggerInstance portOrAction) { - '''«portOrAction.parent.reactorDefinition.name.toLowerCase»_«portOrAction.name»_t''' + '''«portOrAction.parent.reactorDeclaration.name.toLowerCase»_«portOrAction.name»_t''' } /** From 832f28aeb0d5efaabb0c49eaecea58427c2fc474 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 28 Dec 2021 10:25:43 -0800 Subject: [PATCH 108/221] In the diagrams, use the renamed name of an imported reactor rather than the original name. --- .../lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend | 7 +++---- .../org/lflang/diagram/synthesis/util/ReactorIcons.xtend | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 2f101d6bdd..2eaf04cc85 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -92,7 +92,6 @@ import org.lflang.generator.ReactorInstance import org.lflang.generator.TimerInstance import org.lflang.generator.TriggerInstance import org.lflang.generator.TriggerInstance.BuiltinTriggerVariable -import org.lflang.lf.Connection import org.lflang.lf.Model import static extension org.eclipse.emf.ecore.util.EcoreUtil.* @@ -854,12 +853,12 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } } if (reactorInstance.mainOrFederated) { - b.append(FileConfig.nameWithoutExtension(reactorInstance.reactorDefinition.eResource)) - } else if (reactorInstance.reactorDefinition === null) { + b.append(FileConfig.nameWithoutExtension(reactorInstance.reactorDeclaration.eResource)) + } else if (reactorInstance.reactorDeclaration === null) { // There is an error in the graph. b.append("") } else { - b.append(reactorInstance.reactorDefinition.name) + b.append(reactorInstance.reactorDeclaration.name) } if (REACTOR_PARAMETER_MODE.objectValue === ReactorParameterDisplayModes.TITLE) { if (reactorInstance.parameters.empty) { diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.xtend index 7346601aab..31031ab45f 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/ReactorIcons.xtend @@ -40,7 +40,7 @@ import org.eclipse.swt.graphics.ImageData import org.eclipse.swt.graphics.ImageLoader import org.lflang.ASTUtils import org.lflang.diagram.synthesis.AbstractSynthesisExtensions -import org.lflang.lf.Reactor +import org.lflang.lf.ReactorDecl /** * Utility class to handle icons for reactors in Lingua Franca diagrams. @@ -56,7 +56,7 @@ class ReactorIcons extends AbstractSynthesisExtensions { static val LOADER = new ImageLoader(); static val CACHE = >newHashMap // memory-sensitive cache - def void handleIcon(KContainerRendering rendering, Reactor reactor, boolean collapsed) { + def void handleIcon(KContainerRendering rendering, ReactorDecl reactor, boolean collapsed) { if (!collapsed) { return } From 4409cf2062f3ee3d34ec28f1187c95c759bec9b9 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 29 Dec 2021 10:43:59 -0800 Subject: [PATCH 109/221] Allow for reactions within banks that send to multiple ports, themselves possibly within banks --- .../org/lflang/generator/c/CGenerator.xtend | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 08f4accd88..44fd3acfd9 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6167,6 +6167,16 @@ class CGenerator extends GeneratorBase { private def void deferredFillTriggerTable(Iterable reactions) { for (reaction: reactions) { val name = reaction.parent.getFullName; + + // The reaction may have multiple runtime instances, one in each bank member of the parent. + // Need a separate index for the triggers array for each bank member. + val triggersIndexInitializer = new LinkedList(); + for (var i = 0; i < reaction.parent.totalWidth; i++) triggersIndexInitializer.add(0); + startScopedBlock(code); + pr(''' + int triggers_index[] = { «triggersIndexInitializer.join(", ")» }; + ''') + for (port : reaction.effects.filter(PortInstance)) { // If the port is a multiport, then its channels may have different sets // of destinations. For ordinary ports, there will be only one range and @@ -6177,7 +6187,7 @@ class CGenerator extends GeneratorBase { for (SendRange srcRange : port.eventualDestinations()) { startScopedRangeBlock(code, srcRange, bi); - var triggerArray = '''«CUtil.reactionRef(reaction, bi)».triggers[«CUtil.channelIndex(port)»]''' + var triggerArray = '''«CUtil.reactionRef(reaction, bi)».triggers[triggers_index[«bi»]++]''' // Skip ports whose parent is not in the federation. // This can happen with reactions in the top-level that have // as an effect a port in a bank. @@ -6185,7 +6195,7 @@ class CGenerator extends GeneratorBase { pr(''' // Reaction «reaction.index» of «name» triggers «srcRange.destinations.size» downstream reactions // through port «port.getFullName». - «CUtil.reactionRef(reaction, bi)».triggered_sizes[«CUtil.channelIndex(port)»] = «srcRange.destinations.size»; + «CUtil.reactionRef(reaction, bi)».triggered_sizes[triggers_index[«bi»]] = «srcRange.destinations.size»; // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» trigger_t** trigger_array = (trigger_t**)malloc(«srcRange.destinations.size» * sizeof(trigger_t*)); @@ -6199,7 +6209,16 @@ class CGenerator extends GeneratorBase { ''') } endScopedRangeBlock(code, srcRange); + } + } + endScopedBlock(code); + startScopedBlock(code); + pr(''' + int triggers_index[] = { «triggersIndexInitializer.join(", ")» }; + ''') + for (port : reaction.effects.filter(PortInstance)) { + for (SendRange srcRange : port.eventualDestinations()) { if (currentFederate.contains(port.parent)) { var multicastCount = 0; for (dstRange : srcRange.destinations) { @@ -6211,7 +6230,7 @@ class CGenerator extends GeneratorBase { startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); // Need to reset triggerArray because of new channel and bank names. - triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[«srcChannel»]''' + val triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[src_bank]++]''' if (dst.isOutput) { // Include this destination port only if it has at least one @@ -6249,6 +6268,7 @@ class CGenerator extends GeneratorBase { } } } + endScopedBlock(code); } } From c695f87851a5f8ac4d7f71be86c3f5d2eb33f95b Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 29 Dec 2021 16:21:44 -0800 Subject: [PATCH 110/221] More tests passing with multicast over banks and multiports --- .../org/lflang/generator/c/CGenerator.xtend | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 44fd3acfd9..3b6cf3979d 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4927,6 +4927,7 @@ class CGenerator extends GeneratorBase { pr(builder, ''' int «ci» = «ciValue»; // Channel index. int «runtimeIndex» = «biValue»; // Bank index. + int range_count = 0; ''') } } @@ -6213,24 +6214,31 @@ class CGenerator extends GeneratorBase { } endScopedBlock(code); - startScopedBlock(code); - pr(''' - int triggers_index[] = { «triggersIndexInitializer.join(", ")» }; - ''') + // To get triggers_index right, have to duplicate the logic in deferredReactionOutputs(). + var outputCount = 0; + for (port : reaction.effects.filter(PortInstance)) { for (SendRange srcRange : port.eventualDestinations()) { if (currentFederate.contains(port.parent)) { var multicastCount = 0; + val srcChannel = "src_channel"; + val srcBank = "src_bank"; + val dstBank = "dst_bank"; for (dstRange : srcRange.destinations) { val dst = dstRange.instance; - val srcChannel = "src_channel"; - val srcBank = "src_bank"; - val dstBank = "dst_bank"; + // Each multicast target needs to start with the same indexes into triggers array. + triggersIndexInitializer.clear(); + for (var i = 0; i < reaction.parent.totalWidth; i++) triggersIndexInitializer.add(outputCount); + startScopedBlock(code); + pr(''' + int triggers_index[] = { «triggersIndexInitializer.join(", ")» }; + ''') + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); // Need to reset triggerArray because of new channel and bank names. - val triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[src_bank]++]''' + val triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»]++]''' if (dst.isOutput) { // Include this destination port only if it has at least one @@ -6264,11 +6272,16 @@ class CGenerator extends GeneratorBase { } endScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); multicastCount++; + endScopedBlock(code); } } } + var bankWidth = 1; + if (port.isInput) { + bankWidth = port.parent.width; + } + outputCount += port.width * bankWidth; } - endScopedBlock(code); } } From e80c2c02379f4a5c71778c5dbc5ccb5bef656bd6 Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 30 Dec 2021 16:16:07 -0800 Subject: [PATCH 111/221] Typo --- org.lflang/src/org/lflang/ASTUtils.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/ASTUtils.xtend b/org.lflang/src/org/lflang/ASTUtils.xtend index 6a186d7048..cc78dfae17 100644 --- a/org.lflang/src/org/lflang/ASTUtils.xtend +++ b/org.lflang/src/org/lflang/ASTUtils.xtend @@ -1214,7 +1214,7 @@ class ASTUtils { /** * Given the width specification of port or instantiation - * and an (optional) list of nested intantiations, return + * and an (optional) list of nested instantiations, return * the width if it can be determined and -1 if not. * It will not be able to be determined if either the * width is variable (in which case you should use From 14605248a72314f25db9c0e1050a2413f0367450 Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 30 Dec 2021 16:17:11 -0800 Subject: [PATCH 112/221] Tolerate variable width banks that happen to have width one. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 4 ++-- org.lflang/src/org/lflang/generator/c/CUtil.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 3b6cf3979d..22c5d45b66 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4782,7 +4782,7 @@ class CGenerator extends GeneratorBase { */ protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { // NOTE: This is protected because it is used by the PythonGenerator. - if (reactor !== null && reactor.width > 1) { + if (reactor !== null && reactor.isBank) { val index = CUtil.bankIndexName(reactor); pr(builder, ''' // Reactor is a bank. Iterate over bank members. @@ -5013,7 +5013,7 @@ class CGenerator extends GeneratorBase { if (srcRange.width > 1) { pr(builder, ''' - int «srcChannelIndex» = src_range_mr.digits[permutation[0]]; // Channel index. + int «srcChannelIndex» = src_range_mr.digits[src_permutation[0]]; // Channel index. int src_range_natural_start[] = { «rangeMR.getDigits().join(", ")» }; int src_range_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; mixed_radix_int_t src_range_natural = { diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index c5f62e2cad..fe09603bb4 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -386,7 +386,7 @@ static public String runtimeIndex(ReactorInstance reactor) { int width = 0; int parens = 0; while (reactor != null) { - if (reactor.isBank()) { + if (reactor.isBank() && reactor.getWidth() > 1) { if (width > 0) { result.append(" + " + width + " * ("); parens++; From a9cc612def140424770ecdf027d2d97005de7f14 Mon Sep 17 00:00:00 2001 From: eal Date: Fri, 31 Dec 2021 08:14:48 -0800 Subject: [PATCH 113/221] Removed transitiveClosure methods (not used) --- .../org/lflang/generator/ReactorInstance.java | 57 ------------------- 1 file changed, 57 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 2c6d76b128..232db573d8 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -589,23 +589,6 @@ public int totalNumberOfChildren() { return totalNumChildrenCache; } - /** - * Return the set of all ports that receive data from the - * specified source. This includes inputs and outputs at the same level - * of hierarchy and input ports deeper in the hierarchy. - * It also includes inputs or outputs up the hierarchy (i.e., ones - * that are reached via any output port). - * If the argument is an input port, then it is included in the result. - * No port will appear more than once in the result. - * - * @param source An output or input port. - */ - public Set transitiveClosure(PortInstance source) { - var result = new LinkedHashSet(); - transitiveClosure(source, result); - return result; - } - /** * Assuming that the given value denotes a valid time, return a time value. * @@ -710,46 +693,6 @@ protected TriggerInstance getOrCreateShutdown(TriggerRef tri return shutdownTrigger; } - /** - * Add to the specified destinations set all ports that receive data from the - * specified source. This includes inputs and outputs at the same level - * of hierarchy and input ports deeper in the hierarchy. - * It also includes inputs or outputs up the hierarchy (i.e., ones - * that are reached via any output port). - * @param source A port belonging to this reaction instance or one - * of its children. - * @param destinations The set of destinations to populate. - */ - protected void transitiveClosure( - PortInstance source, - LinkedHashSet destinations - ) { - // Check that the specified port belongs to this reactor or one of its children. - // The following assumes that the main reactor has no ports, or else - // a NPE will occur. - if (source.parent != this && source.parent.parent != this) { - throw new InvalidSourceException( - "Internal error: port " + source + " does not belong to " + - this + " nor any of its children." - ); - } - // If the port is an input port, then include it in the result. - if (source.isInput()) { - destinations.add(source); - } - for (RuntimeRange dst : source.dependentPorts) { - PortInstance destination = dst.instance; - destinations.add(destination); - if (destination.isInput()) { - // Destination may have further destinations lower in the hierarchy. - destination.parent.transitiveClosure(destination, destinations); - } else if (destination.parent.parent != null) { - // Destination may have further destinations higher in the hierarchy. - destination.parent.parent.transitiveClosure(destination, destinations); - } - } - } - //////////////////////////////////////// //// Private constructors From 21bd78b72714091c8d56ba0e602e07e9fbdf54bc Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 1 Jan 2022 16:16:55 -0800 Subject: [PATCH 114/221] Drastically simplified mixed-radix infrastructure and usage. --- .../tests/compiler/MixedRadixIntTest.java | 54 +++-- .../org/lflang/tests/compiler/RangeTests.java | 17 +- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/MixedRadixInt.java | 221 ++++++++++-------- .../org/lflang/generator/RuntimeRange.java | 152 +++++------- .../src/org/lflang/generator/SendRange.java | 4 +- .../org/lflang/generator/c/CGenerator.xtend | 66 ++---- 7 files changed, 257 insertions(+), 259 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java index 510eed6c75..bf6731acbd 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java @@ -16,19 +16,19 @@ public class MixedRadixIntTest { @Test public void create() throws Exception { - MixedRadixInt num = new MixedRadixInt(digits, radixes); + MixedRadixInt num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals("1%2, 2%3, 3%4, 4%5", num.toString()); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); List altDigits = new ArrayList(List.of(1, 2, 1)); - MixedRadixInt altNum = new MixedRadixInt(altDigits, radixes); + MixedRadixInt altNum = new MixedRadixInt(altDigits, radixes, null); Assertions.assertEquals(11, altNum.get()); } @Test public void createWithInfinity() throws Exception { List radixes = new ArrayList(List.of(2, 3, 4)); - MixedRadixInt num = new MixedRadixInt(digits, radixes); + MixedRadixInt num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); } @@ -36,7 +36,7 @@ public void createWithInfinity() throws Exception { public void createWithError() throws Exception { List radixes = new ArrayList(List.of(2, 3)); try { - new MixedRadixInt(digits, radixes); + new MixedRadixInt(digits, radixes, null); } catch (IllegalArgumentException ex) { Assertions.assertTrue(ex.getMessage().startsWith("Invalid")); return; @@ -46,37 +46,63 @@ public void createWithError() throws Exception { @Test public void createWithNullAndSet() throws Exception { - MixedRadixInt num = new MixedRadixInt(null, radixes); + MixedRadixInt num = new MixedRadixInt(null, radixes, null); Assertions.assertEquals(0, num.get()); Assertions.assertEquals("0%2", num.toString()); num.set(1 + 2*2 + 3*6 + 4*24); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); + int mag = num.magnitude(); + Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, mag); + num.increment(); + Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24 + 1, num.get()); + Assertions.assertEquals(mag + 1, num.magnitude()); + + num = new MixedRadixInt(null, radixes, null); + num.setMagnitude(mag); + Assertions.assertEquals(mag, num.magnitude()); } @Test public void testPermutation() throws Exception { List radixes = new ArrayList(List.of(2, 5)); List digits = new ArrayList(List.of(1, 2)); - MixedRadixInt num = new MixedRadixInt(digits, radixes); - Assertions.assertEquals(5, num.get()); - List permutation = new ArrayList(List.of(1, 0)); - MixedRadixInt pnum = num.permute(permutation); - Assertions.assertEquals(7, pnum.get()); + MixedRadixInt num = new MixedRadixInt(digits, radixes, permutation); + Assertions.assertEquals(5, num.get()); + Assertions.assertEquals(2, num.get(1)); + Assertions.assertEquals(7, num.magnitude()); + num.increment(); + Assertions.assertEquals(7, num.get()); + Assertions.assertEquals(8, num.magnitude()); + num = new MixedRadixInt(null, radixes, permutation); + num.setMagnitude(8); + Assertions.assertEquals(8, num.magnitude()); + Assertions.assertEquals(7, num.get()); + + // Test radix infinity digit. digits = new ArrayList(List.of(1, 2, 1)); - num = new MixedRadixInt(digits, radixes); + num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals(15, num.get()); + Assertions.assertEquals(7, num.get(1)); + Assertions.assertEquals(15, num.magnitude()); + + num = new MixedRadixInt(digits, radixes, permutation); + num.increment(); + Assertions.assertEquals(17, num.get()); + Assertions.assertEquals(18, num.magnitude()); - pnum = num.permute(permutation); - Assertions.assertEquals(17, pnum.get()); + num = new MixedRadixInt(null, radixes, permutation); + num.setMagnitude(18); + Assertions.assertEquals(18, num.magnitude()); + Assertions.assertEquals(17, num.get()); } @Test public void testIncrement() throws Exception { List radixes = new ArrayList(List.of(2, 3)); List digits = new ArrayList(List.of(0, 2)); - MixedRadixInt num = new MixedRadixInt(digits, radixes); + MixedRadixInt num = new MixedRadixInt(digits, radixes, null); num.increment(); Assertions.assertEquals(5, num.get()); Assertions.assertEquals(2, digits.size()); diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index ce88a3cb3c..73e0a4bcc1 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -52,15 +52,14 @@ public void createRange() throws Exception { // which includes this example. List instances = range.instances(); Assertions.assertEquals(List.of(3, 4, 5, 6), instances); - Set parents = range.parentInstances(1); Assertions.assertEquals(Set.of(1, 2, 3), parents); parents = range.parentInstances(2); Assertions.assertEquals(Set.of(0, 1), parents); - // Test startMRNatural().getDigits. - Assertions.assertEquals(List.of(1, 1, 0), range.startMRNatural().getDigits()); + // Test startMR().getDigits. + Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); // Create a SendRange sending from and to this range. SendRange sendRange = new SendRange(pi, 3, 4); @@ -74,16 +73,16 @@ public void createRange() throws Exception { instances = range.instances(); Assertions.assertEquals(List.of(3, 4, 6, 5), instances); - // Test startMRNatural().getDigits. - Assertions.assertEquals(List.of(1, 1, 0), range.startMRNatural().getDigits()); + // Test startMR().getDigits. + Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); // Make second interleaved version. range = range.toggleInterleaved(ai); instances = range.instances(); Assertions.assertEquals(List.of(6, 1, 5, 3), instances); - // Test startMRNatural().getDigits. - Assertions.assertEquals(List.of(0, 1, 1), range.startMRNatural().getDigits()); + // Test startMR().getDigits. + Assertions.assertEquals(List.of(0, 1, 1), range.startMR().getDigits()); // Test instances of the parent. Assertions.assertEquals(Set.of(3, 0, 2, 1), range.parentInstances(1)); @@ -98,7 +97,7 @@ public void createRange() throws Exception { instances = range.instances(); Assertions.assertEquals(List.of(5, 2, 6, 3), instances); - // Test startMRNatural().getDigits. - Assertions.assertEquals(List.of(1, 0, 1), range.startMRNatural().getDigits()); + // Test startMR().getDigits. + Assertions.assertEquals(List.of(1, 0, 1), range.startMR().getDigits()); } } diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 9b0cb33c1f..4d1e64d156 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 9b0cb33c1f29d2c34e0e5c1188d3c7953971fa6f +Subproject commit 4d1e64d156af2f6ea73059d4f56e2e2be1ccfcd7 diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index 9334469734..5fa5efd40a 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -27,18 +27,30 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; +import java.util.Set; /** - * Representation of a mixed radix integer. + * Representation of a permuted mixed radix (PMR) integer. * A mixed radix number is a number representation where each digit can have * a distinct radix. The radixes are given by a list of numbers, r0, r1, ... , rn, * where r0 is the radix of the lowest-order digit and rn is the radix of the * highest order digit that has a specified radix. There is an additional implict * radix after rn of infinity, which means that the mixed radix number may have a * total n + 2 digits, where the last digit is unbounded. + * + * A PMR is a mixed radix number that, when incremented, + * increments the digits in the order given by the permutation matrix. + * For an ordinary mixed radix number, the permutation matrix is + * [0, 1, ..., n-1]. The permutation matrix may be any permutation of + * these digits, [d0, d1, ..., dn-1], in which case, when incremented, + * the d0 digit will be incremented first. If it overflows, it will be + * set to 0 and the d1 digit will be incremented. If it overflows, the + * next digit is incremented. If the last digit overflows, then the + * number wraps around so that all digits become zero. * * The {@link #toString()} method gives a string representation of the number * where each digit is represented by the string "d%r", where d is the digit @@ -55,11 +67,16 @@ public class MixedRadixInt { * If there is one more digit than radixes, then the last digit is * assumed to have infinite radix. If there are more digits than that, * throw an exception. - * @param digits The digits. + * @param digits The digits, or null to get a zero-valued number. * @param radixes The radixes. + * @param permutation The permutation matrix, or null for the default permutation. */ - public MixedRadixInt(List digits, List radixes) { - if (radixes == null || (digits != null && digits.size() > radixes.size() + 1)) { + public MixedRadixInt( + List digits, List radixes, List permutation + ) { + if (radixes == null + || (digits != null && digits.size() > radixes.size() + 1) + || (permutation != null && permutation.size() != radixes.size())) { throw new IllegalArgumentException("Invalid constructor arguments."); } this.radixes = radixes; @@ -69,48 +86,55 @@ public MixedRadixInt(List digits, List radixes) { this.digits = new ArrayList(1); this.digits.add(0); } + if (permutation != null) { + // Check the permutation matrix. + Set indices = new HashSet(); + for (int p : permutation) { + if (p < 0 || p >= radixes.size() || indices.contains(p)) { + throw new IllegalArgumentException( + "Permutation list is required to be a permutation of [0, 1, ... , n-1]."); + } + indices.add(p); + } + this.permutation = permutation; + } } - public final static MixedRadixInt ZERO = new MixedRadixInt(List.of(0), List.of(1)); + /** + * A zero-valued mixed radix number with just one digit will radix 1. + */ + public final static MixedRadixInt ZERO = new MixedRadixInt(null, List.of(1), null); ////////////////////////////////////////////////////////// //// Public methods /** - * Drop the first n digits and return the resulting mixed-radix number. - * If n is larger than or equal to the total number of digits, return ZERO. - * @param n The number of digits to drop. + * Get the value as an integer. */ - public MixedRadixInt drop(int n) { - if (n == 0) return this; - if (n >= digits.size() || n < 0) { - return ZERO; - } - List d = new ArrayList(digits.size() - n); - List r = new ArrayList(digits.size() - n); - for (int i = n; i < digits.size(); i++) { - d.add(digits.get(i)); - r.add(radixes.get(i)); - } - return new MixedRadixInt(d, r); + public int get() { + return get(0); } /** - * Get the value as an integer. + * Get the value as an integer after dropping the first n digits. + * @param n The number of digits to drop. */ - public int get() { + public int get(int n) { int result = 0; int scale = 1; - Iterator radixIterator = radixes.iterator(); - for (int digit : digits) { - result += digit * scale; - if (radixIterator.hasNext()) { - scale *= radixIterator.next(); - } + if (n < 0) n = 0; + for (int i = n; i < radixes.size(); i++) { + if (i >= digits.size()) return result; + result += digits.get(i) * scale; + scale *= radixes.get(i); + } + if (digits.size() > radixes.size()) { + // Handle radix infinity digit. + result += digits.get(radixes.size()) * scale; } return result; } - + /** * Return the digits. */ @@ -118,6 +142,20 @@ public List getDigits() { return digits; } + /** + * Return the permutation list. + */ + public List getPermutation() { + if (permutation == null) { + // Construct a default permutation. + permutation = new ArrayList(radixes.size()); + for (int i = 0; i < radixes.size(); i++) { + permutation.add(i); + } + } + return permutation; + } + /** * Return the radixes. */ @@ -126,49 +164,55 @@ public List getRadixes() { } /** - * Increment the number by one. + * Increment the number by one, using the permutation vector to + * determine the order in which the digits are incremented. + * If an overflow occurs, then a radix-infinity digit will be added + * to the digits array if there isn't one there already. */ public void increment() { - increment(0); + int i = 0; + while (i < radixes.size()) { + int digit_to_increment = getPermutation().get(i); + while (digit_to_increment >= digits.size()) { + digits.add(0); + } + digits.set(digit_to_increment, digits.get(digit_to_increment) + 1); + if (digits.get(digit_to_increment) >= radixes.get(digit_to_increment)) { + digits.set(digit_to_increment, 0); + i++; + } else { + return; // All done. + } + } + // If we get here, the number has overflowed. + // Append a radix infinity digit if needed. + while (digits.size() < radixes.size() + 1) { + digits.add(0); + } + digits.set(radixes.size(), digits.get(radixes.size()) + 1); } /** - * Permute the digits of the number and return a new number. - * The argument is required to be a list of indices from 0 to L-1, - * where L is less than or equal to the length of the radixes list - * specified in the constructor. Each index in this list specifies - * the index from which the corresponding digit should be taken. - * - * For example, if this number is "1%2, 2%5", which has value 5 = 1 + 2*2, - * then permute([1, 0]) will return the number "2%5, 1%2", which - * has value 7 = 2 + 1*5. - * - * If this number has a final radix-infinity term, then that term - * will be included in the result as a radix-infinity term. - * - * @param permutation The permutation array. - * @throws IllegalArgumentException If the argument size does not equal - * the radixes size. + * Return the magnitude of this PMR, which is defined to be the number + * of times that increment() would need to invoked starting with zero + * before the value returned by {@link #get()} would be reached. */ - public MixedRadixInt permute(List permutation) { - if (permutation.size() > radixes.size()) { - throw new IllegalArgumentException( - "Permutation list cannot be larger than the radixes, " - + radixes.size()); - } - List newRadixes = new ArrayList(radixes.size()); - List newDigits = new ArrayList(digits.size()); - for (int p : permutation) { - newRadixes.add(radixes.get(p)); - newDigits.add(digits.get(p)); + public int magnitude() { + int factor = 1; + int result = 0; + List p = getPermutation(); + for (int i = 0; i < radixes.size(); i++) { + if (digits.size() <= i) return result; + result += factor * digits.get(p.get(i)); + factor *= radixes.get(p.get(i)); } - // Handle possible radix-infinity digit. if (digits.size() > radixes.size()) { - newDigits.add(digits.get(digits.size() - 1)); + // Add in any infinite radix digit. + result += factor * digits.get(radixes.size()); } - return new MixedRadixInt(newDigits, newRadixes); + return result; } - + /** * Set the value of this number to equal that of the specified integer. * @param v The ordinary integer value of this number. @@ -187,6 +231,27 @@ public void set(int v) { } } + /** + * Set the magnitude of this number to equal that of the specified integer, + * which is the number of times that increment must be invoked from zero + * for the value returned by {@link #get()} to equal v. + * @param v The new magnitude of this number. + */ + public void setMagnitude(int v) { + int temp = v; + for (int i = 0; i < radixes.size(); i++) { + int p = getPermutation().get(i); + while (digits.size() < p + 1) digits.add(0); + digits.set(p, temp % radixes.get(p)); + temp = temp / radixes.get(p); + } + if (temp > 0) { + // Add a radix infinity digit. + while (digits.size() < radixes.size() + 1) digits.add(0); + digits.set(radixes.size(), temp); + } + } + /** * Give a string representation of the number, where each digit is * represented as n%r, where r is the radix. @@ -205,40 +270,10 @@ public String toString() { return String.join(", ", pieces); } - ////////////////////////////////////////////////////////// - //// Private methods - - /** - * Increment the specified digit by one. If an overflow occurs, - * then a radix-infinity digit will be added to the digits array - * if there isn't one there already. - * @param d The digit to increment, which assumed to be less than - * the length of digits. - */ - public void increment(int d) { - int value = digits.get(d) + 1; - if (d >= radixes.size()) { - // We are at a radix infinity digit. - digits.set(d, value); - return; - } - if (value < radixes.get(d)) { - digits.set(d, value); - return; - } else { - digits.set(d, 0); - if (d < digits.size() - 1) { - increment(d + 1); - } else { - digits.add(1); - } - return; - } - } - ////////////////////////////////////////////////////////// //// Private variables private List radixes; private List digits; + private List permutation; } diff --git a/org.lflang/src/org/lflang/generator/RuntimeRange.java b/org.lflang/src/org/lflang/generator/RuntimeRange.java index d1451778b4..5a320d07fb 100644 --- a/org.lflang/src/org/lflang/generator/RuntimeRange.java +++ b/org.lflang/src/org/lflang/generator/RuntimeRange.java @@ -65,9 +65,10 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * * This class and its subclasses give a more compact representation of the * RIG in most common cases where collections of runtime instances all have - * the same dependencies. + * the same dependencies or dependencies form a range that can be represented + * compactly. * - * A Range represents an adjacent set of RIG objects (port channels, reactions + * A RuntimeRange represents an adjacent set of RIG objects (port channels, reactions * reactors). For example, it can represent port channels 2 through 5 in a multiport * of width 10. The width in this case is 4. If such a port is * contained by one or more banks of reactors, then channels 2 through 5 @@ -89,7 +90,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * * The simplest Ranges are those where the corresponding CIG node represents * only one runtime instance (its instance is not (deeply) within a bank - * and is not a multiport). In this case, the Range and all the objects + * and is not a multiport). In this case, the RuntimeRange and all the objects * returned by iterationOrder will have width 1. * * In a more complex instance, consider a bank A of width 2 that contains a @@ -146,14 +147,26 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * A0.B1.P1 * A1.B1.P1 * - * A Range is a contiguous subset of one of the above lists, given by + * A RuntimeRange is a contiguous subset of one of the above lists, given by * a start offset and a width that is less than or equal to maxWidth. * - * For a Range with width greater than 1, + * Each element of the above lists can be represented as a permuted mixed-radix (PMR) number, + * where the low-order digit has radix equal to the width of P, the second digit + * has radix equal to the width of B, and the final digit has radix equal to the + * width of A. Each PMR has a permutation vector that defines how to increment + * PMR number. This permutation vector is derived from the iteration order as + * follows. When there is no interleaving, the iteration order is [P, B, A], + * and the permutation vector is [0, 1, 2]. When there is interleaving, the permutation + * vector simply specifies the iteration order. For example, if the iteration order + * is [A, P, B], then the permutation vector is [2, 0, 1], indicating that digit 2 + * of the PMR (corresponding to A) should be incremented first, then digit 0 (for P), + * then digit 1 (for B). + * + * For a RuntimeRange with width greater than 1, * the head() and tail() functions split the range. * * This class and subclasses are designed to be immutable. - * Modifications always return a new Range. + * Modifications always return a new RuntimeRange. * * @author{Edward A. Lee } */ @@ -254,7 +267,7 @@ public int compareTo(RuntimeRange o) { } /** - * Return a new Range that is identical to this range but + * Return a new RuntimeRange that is identical to this range but * with width reduced to the specified width. * If the new width is greater than or equal to the width * of this range, then return this range. @@ -271,17 +284,19 @@ public RuntimeRange head(int newWidth) { * Return the list of **natural identifiers** for the runtime instances * in this range. Each returned identifier is an integer representation * of the mixed-radix number [d0, ... , dn] with radices [w0, ... , wn], - * where d0 is the bank or channel index of this Range's instance, which + * where d0 is the bank or channel index of this RuntimeRange's instance, which * has width w0, and dn is the bank index of its topmost parent below the - * top-level (main) reactor, which has width wn. The depth of this Range's + * top-level (main) reactor, which has width wn. The depth of this RuntimeRange's * instance, therefore, is n - 1. The order of the returned list is the order * in which the runtime instances should be iterated. */ public List instances() { List result = new ArrayList(width); - List mr = instancesMR(); - for (MixedRadixInt i : mr) { - result.add(i.get()); + MixedRadixInt mr = startMR(); + int count = 0; + while (count++ < width) { + result.add(mr.get()); + mr.increment(); } return result; } @@ -314,14 +329,14 @@ public List> iterationOrder() { /** * Return a set of identifiers for runtime instances of a parent of this - * Range's instance n levels above this Range's instance. If n == 1, for + * RuntimeRange's instance n levels above this RuntimeRange's instance. If n == 1, for * example, then this return the identifiers for the parent ReactorInstance. * * This returns a list of **natural identifiers**, * as defined below, for the instances within the range. * * The resulting list can be used to count the number of distinct - * runtime instances of this Range's instance (using n == 0) or any of its parents that + * runtime instances of this RuntimeRange's instance (using n == 0) or any of its parents that * lie within the range and to provide an index into an array of runtime * instances. * @@ -347,13 +362,15 @@ public List> iterationOrder() { * runtime instances that is assumed to be in a **natural order**. * * @param n The number of levels up of the parent. This is required to be - * less than the depth of this Range's instance or an exception will be thrown. + * less than the depth of this RuntimeRange's instance or an exception will be thrown. */ public Set parentInstances(int n) { Set result = new LinkedHashSet(width); - List mr = instancesMR(); - for (MixedRadixInt m : mr) { - result.add(m.drop(n).get()); + MixedRadixInt mr = startMR(); + int count = 0; + while (count++ < width) { + result.add(mr.get(n)); + mr.increment(); } return result; } @@ -372,10 +389,9 @@ public ReactorInstance parentReactor() { } /** - * Return that permutation that will convert the mixed-radix number - * with digits given by {@link #startIndices()} into a mixed-radix - * number in natural order, i.e. with digits given by - * {@link #startIndicesNatural()}. + * Return the permutation vector that indicates the order in which the digits + * of the permuted mixed-radix representations of indices in this range should + * be incremented. */ public List permutation() { List result = new ArrayList(instance.depth); @@ -385,38 +401,36 @@ public List permutation() { } int count = 0; for (NamedInstance i : iterationOrder()) { - result.set(instance.depth - i.depth, count++); + result.set(count++, instance.depth - i.depth); } return result; } /** - * Return the start as a mixed-radix number where the digits and - * radixes are in the order given by {@link #instances()}. - * For any instance that is neither a - * multiport nor a bank, the digit will be 0. + * Return the radixes vector containing the width of this instance followed + * by the widths of its containers, not including the top level, which always + * has radix 1 and value 0. */ - public MixedRadixInt startMR() { - List digits = new ArrayList(instance.depth); - List radixes = new ArrayList(instance.depth); - int factor = 1; - for (NamedInstance i : iterationOrder()) { - int iStart = (start / factor) % i.width; - factor *= i.width; - digits.add(iStart); - radixes.add(i.width); + public List radixes() { + List result = new ArrayList(instance.depth); + result.add(instance.width); + ReactorInstance p = instance.getParent(); + while (p != null && p.getDepth() > 0) { + result.add(p.getWidth()); + p = p.getParent(); } - return new MixedRadixInt(digits, radixes); + return result; } /** - * Return the start as a mixed-radix number where the digits and - * radixes are in hierarchy order (natural order), with this instance's - * start position listed first. For any instance that is neither a - * multiport nor a bank, the digit will be 0. + * Return the start as a new permuted mixed-radix number. + * For any instance that is neither a multiport nor a bank, the + * corresponding digit will be 0. */ - public MixedRadixInt startMRNatural() { - return startMR().permute(permutation()); + public MixedRadixInt startMR() { + MixedRadixInt result = new MixedRadixInt(null, radixes(), permutation()); + result.setMagnitude(start); + return result; } /** @@ -436,7 +450,7 @@ public RuntimeRange tail(int offset) { * Toggle the interleaved status of the specified reactor, which is assumed * to be a parent of the instance of this range. * If it was previously interleaved, make it not interleaved - * and vice versa. This returns a new Range. + * and vice versa. This returns a new RuntimeRange. * @param reactor The parent reactor at which to toggle interleaving. */ public RuntimeRange toggleInterleaved(ReactorInstance reactor) { @@ -462,59 +476,11 @@ public String toString() { /** Record of which levels are interleaved. */ Set _interleaved = new HashSet(); - ////////////////////////////////////////////////////////// - //// Protected methods - - /** - * Return the list of MixedRadixInt identifiers for the runtime instances - * in this range. Each returned identifier is a mixed-radix number - * [d0, ... , dn] with radixes [w0, ... , wn], where d0 is the bank or - * channel index of this Range's instance, which has width w0, and - * dn is the bank index of its topmost parent below the top-level (main) - * reactor, which has width wn. The depth of this Range's instance, - * therefore, is n - 1. The order of the returned list is the order - * in which the runtime instances should be iterated. - */ - protected List instancesMR() { - List result = new ArrayList(width); - - // First, build mixed-radix number with value 0. - List radixes = new ArrayList(instance.depth); - List digits = new ArrayList(instance.depth); - List permutation = new ArrayList(instance.depth); - - // Pre-fill the permutation array with the natural order. - for (int j = 0; j < instance.depth; j++) permutation.add(j); - - // Construct the radix and permutation arrays. - int count = 0; - for (NamedInstance io : iterationOrder()) { - radixes.add(io.width); - permutation.set(instance.depth - io.depth, count); - digits.add(0); - count++; - } - - // Iterate over the range in its own order. - count = 0; - MixedRadixInt indices = new MixedRadixInt(digits, radixes); - while (count < start + width) { - if (count >= start) { - // Found an instance to include in the list. - // Permute it to get natural order. - result.add(indices.permute(permutation)); - } - indices.increment(); - count++; - } - return result; - } - ////////////////////////////////////////////////////////// //// Public inner classes /** - * Special case of Range for PortInstance. + * Special case of RuntimeRange for PortInstance. */ public static class Port extends RuntimeRange { public Port(PortInstance instance) { diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index 5e4d6af0c6..81c88cc38b 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -110,8 +110,8 @@ public int compareTo(RuntimeRange o) { /** * Return the total number of destination reactors. Specifically, this - * is the number of distinct reactors that react to messages from this - * send range. + * is the number of distinct runtime reactor instances that react to + * messages from this send range. */ public int getNumberOfDestinationReactors() { if (_numberOfDestinationReactors < 0) { diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 22c5d45b66..90a2033a9a 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4894,39 +4894,23 @@ class CGenerator extends GeneratorBase { mixed_radix_int_t range_mr = { «rangeMR.getDigits().size()», range_start, - range_radixes + range_radixes, + permutation }; for (int range_count = «range.start»; range_count < «range.start» + «range.width»; range_count++) { '''); indent(builder); - // FIXME: Following could be much simpler when there is no interleaving. pr(builder, ''' - int «ci» = range_mr.digits[permutation[0]]; // Channel index. - int range_natural_start[] = { «rangeMR.getDigits().join(", ")» }; - int range_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; - mixed_radix_int_t range_natural = { - «rangeMR.getDigits().size()», - range_natural_start, - range_natural_radixes - }; - int banks_natural_start[] = { «rangeMR.getDigits().join(", ")» }; - int banks_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; - mixed_radix_int_t banks_natural = { - «rangeMR.getDigits().size()» - 1, - banks_natural_start, - banks_natural_radixes - }; - mixed_radix_permute(&range_natural, &range_mr, permutation); - mixed_radix_drop(&banks_natural, &range_natural, 1); - int «runtimeIndex» = mixed_radix_to_int(&banks_natural); + int «ci» = range_mr.digits[0]; // Channel index. + int «runtimeIndex» = mixed_radix_parent(&range_mr, 1); ''') } else { - val mr = range.startMRNatural(); + val mr = range.startMR(); val ciValue = mr.getDigits().get(0); - val biValue = mr.drop(1).get(); + val biValue = mr.get(1); pr(builder, ''' int «ci» = «ciValue»; // Channel index. - int «runtimeIndex» = «biValue»; // Bank index. + int «runtimeIndex» = «biValue»; // Bank(s) identifier. int range_count = 0; ''') } @@ -4991,21 +4975,23 @@ class CGenerator extends GeneratorBase { if (srcRange.width > 1) { pr(builder, ''' int src_start[] = { «rangeMR.getDigits().join(", ")» }; + int src_value[] = { «rangeMR.getDigits().join(", ")» }; // Will be incremented. int src_radixes[] = { «rangeMR.getRadixes().join(", ")» }; int src_permutation[] = { «srcRange.permutation().join(", ")» }; mixed_radix_int_t src_range_mr = { «rangeMR.getDigits().size()», - src_start, - src_radixes + src_value, + src_radixes, + src_permutation }; '''); } else { - val mr = srcRange.startMRNatural(); + val mr = srcRange.startMR(); val ciValue = mr.getDigits().get(0); - val biValue = mr.drop(1).get(); + val biValue = mr.get(1); pr(builder, ''' int «srcChannelIndex» = «ciValue»; // Channel index. - int «srcRuntimeIndex» = «biValue»; // Bank index. + int «srcRuntimeIndex» = «biValue»; // Bank(s) identifier. ''') } @@ -5013,24 +4999,8 @@ class CGenerator extends GeneratorBase { if (srcRange.width > 1) { pr(builder, ''' - int «srcChannelIndex» = src_range_mr.digits[src_permutation[0]]; // Channel index. - int src_range_natural_start[] = { «rangeMR.getDigits().join(", ")» }; - int src_range_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; - mixed_radix_int_t src_range_natural = { - «rangeMR.getDigits().size()», - src_range_natural_start, - src_range_natural_radixes - }; - int src_banks_natural_start[] = { «rangeMR.getDigits().join(", ")» }; - int src_banks_natural_radixes[] = { «rangeMR.getRadixes().join(", ")» }; - mixed_radix_int_t src_banks_natural = { - «rangeMR.getDigits().size()» - 1, - src_banks_natural_start, - src_banks_natural_radixes - }; - mixed_radix_permute(&src_range_natural, &src_range_mr, src_permutation); - mixed_radix_drop(&src_banks_natural, &src_range_natural, 1); - int «srcRuntimeIndex» = mixed_radix_to_int(&src_banks_natural); + int «srcChannelIndex» = src_range_mr.digits[0]; // Channel index. + int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 1); ''') } } @@ -5058,7 +5028,9 @@ class CGenerator extends GeneratorBase { mixed_radix_incr(&src_range_mr); if (mixed_radix_to_int(&src_range_mr) >= «srcRange.start» + «srcRange.width») { // Start over with the source. - src_range_mr.digits = src_start; + for (int i = 0; i < src_range_mr.size; i++) { + src_range_mr.digits[i] = src_start[i]; + } } '''); } From 74245834b6230983d6f67a8815770fd76d987c17 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 3 Jan 2022 17:56:53 -0800 Subject: [PATCH 115/221] Temporarily commented out cycle detection --- .../synthesis/LinguaFrancaSynthesis.xtend | 106 +++++++++--------- 1 file changed, 56 insertions(+), 50 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 2eaf04cc85..e680421907 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -478,6 +478,7 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { return node.addErrorComment(TEXT_ERROR_CONTAINS_RECURSION) } else { // only detect dependency cycles if not recursive try { + /* FIXME val hasCycle = reactorInstance.detectAndHighlightCycles(allReactorNodes, [ if (it instanceof KNode) { val renderings = it.data.filter(typeof(KRendering)).toList @@ -496,6 +497,9 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { //it.reverseTrianglePort() } ]) + * + */ + val hasCycle = false; if (hasCycle) { val err = node.addErrorComment(TEXT_ERROR_CONTAINS_CYCLE) @@ -738,58 +742,60 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } for (leftPort : sourcePorts) { - for (rightRange : leftPort.dependentPorts) { - val source = if (leftPort.parent == reactorInstance) { - parentInputPorts.get(leftPort) - } else { - outputPorts.get(leftPort.parent, leftPort) - } - val rightPort = rightRange.instance; - val target = if (rightPort.parent == reactorInstance) { - parentOutputPorts.get(rightPort) - } else { - inputPorts.get(rightPort.parent, rightPort) - } - // There should be a connection, but skip if not. - val connection = rightRange.connection; - if (connection !== null) { - val edge = createIODependencyEdge(connection, leftPort.isMultiport() || rightPort.isMultiport()) - if (connection.delay !== null) { - edge.addCenterEdgeLabel(connection.delay.toText) => [ - associateWith(connection.delay) - if (connection.physical) { - applyOnEdgePysicalDelayStyle( - reactorInstance.mainOrFederated ? Colors.WHITE : Colors.GRAY_95) - } else { - applyOnEdgeDelayStyle() - } - ] - } else if (connection.physical) { - edge.addCenterEdgeLabel("---").applyOnEdgePysicalStyle( - reactorInstance.mainOrFederated ? Colors.WHITE : Colors.GRAY_95) - } - if (source !== null && target !== null) { - // check for inside loop (direct in -> out connection with delay) - if (parentInputPorts.values.contains(source) && parentOutputPorts.values.contains(target)) { - // edge.setLayoutOption(CoreOptions.INSIDE_SELF_LOOPS_YO, true) // Does not work as expected - // Introduce dummy node to enable direct connection (that is also hidden when collapsed) - var dummy = createNode() - if (directConnectionDummyNodes.containsKey(target)) { - dummy = directConnectionDummyNodes.get(target) + val source = if (leftPort.parent == reactorInstance) { + parentInputPorts.get(leftPort) + } else { + outputPorts.get(leftPort.parent, leftPort) + } + for (rightSendRange : leftPort.dependentPortsFIXME) { + for (rightRange : rightSendRange.destinations) { + val rightPort = rightRange.instance; + val target = if (rightPort.parent == reactorInstance) { + parentOutputPorts.get(rightPort) + } else { + inputPorts.get(rightPort.parent, rightPort) + } + // There should be a connection, but skip if not. + val connection = rightRange.connection; + if (connection !== null) { + val edge = createIODependencyEdge(connection, leftPort.isMultiport() || rightPort.isMultiport()) + if (connection.delay !== null) { + edge.addCenterEdgeLabel(connection.delay.toText) => [ + associateWith(connection.delay) + if (connection.physical) { + applyOnEdgePysicalDelayStyle( + reactorInstance.mainOrFederated ? Colors.WHITE : Colors.GRAY_95) + } else { + applyOnEdgeDelayStyle() + } + ] + } else if (connection.physical) { + edge.addCenterEdgeLabel("---").applyOnEdgePysicalStyle( + reactorInstance.mainOrFederated ? Colors.WHITE : Colors.GRAY_95) + } + if (source !== null && target !== null) { + // check for inside loop (direct in -> out connection with delay) + if (parentInputPorts.values.contains(source) && parentOutputPorts.values.contains(target)) { + // edge.setLayoutOption(CoreOptions.INSIDE_SELF_LOOPS_YO, true) // Does not work as expected + // Introduce dummy node to enable direct connection (that is also hidden when collapsed) + var dummy = createNode() + if (directConnectionDummyNodes.containsKey(target)) { + dummy = directConnectionDummyNodes.get(target) + } else { + nodes += dummy + directConnectionDummyNodes.put(target, dummy) + + dummy.addInvisibleContainerRendering() + dummy.setNodeSize(0, 0) + + val extraEdge = createIODependencyEdge(null, + leftPort.isMultiport() || rightPort.isMultiport()) + extraEdge.connect(dummy, target) + } + edge.connect(source, dummy) } else { - nodes += dummy - directConnectionDummyNodes.put(target, dummy) - - dummy.addInvisibleContainerRendering() - dummy.setNodeSize(0, 0) - - val extraEdge = createIODependencyEdge(null, - leftPort.isMultiport() || rightPort.isMultiport()) - extraEdge.connect(dummy, target) + edge.connect(source, target) } - edge.connect(source, dummy) - } else { - edge.connect(source, target) } } } From 4d96d38dbb0a45ead6468a11ca9c6da2d55d1579 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 3 Jan 2022 17:57:30 -0800 Subject: [PATCH 116/221] Wrap rather than add an infinite radix digit to match C runtime --- .../tests/compiler/MixedRadixIntTest.java | 18 +++++----- .../org/lflang/generator/MixedRadixInt.java | 34 +++++-------------- 2 files changed, 17 insertions(+), 35 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java index bf6731acbd..424bb5fe54 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java @@ -27,7 +27,7 @@ public void create() throws Exception { @Test public void createWithInfinity() throws Exception { - List radixes = new ArrayList(List.of(2, 3, 4)); + List radixes = new ArrayList(List.of(2, 3, 4, 10000)); MixedRadixInt num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); } @@ -53,9 +53,9 @@ public void createWithNullAndSet() throws Exception { Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); int mag = num.magnitude(); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, mag); - num.increment(); - Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24 + 1, num.get()); - Assertions.assertEquals(mag + 1, num.magnitude()); + num.increment(); // wrap to zero. + Assertions.assertEquals(0, num.get()); + Assertions.assertEquals(0, num.magnitude()); num = new MixedRadixInt(null, radixes, null); num.setMagnitude(mag); @@ -80,13 +80,15 @@ public void testPermutation() throws Exception { Assertions.assertEquals(8, num.magnitude()); Assertions.assertEquals(7, num.get()); - // Test radix infinity digit. + // Test radix infinity digit (effectively). digits = new ArrayList(List.of(1, 2, 1)); + radixes = new ArrayList(List.of(2, 5, 1000000)); num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals(15, num.get()); Assertions.assertEquals(7, num.get(1)); Assertions.assertEquals(15, num.magnitude()); + permutation = new ArrayList(List.of(1, 0, 2)); num = new MixedRadixInt(digits, radixes, permutation); num.increment(); Assertions.assertEquals(17, num.get()); @@ -105,9 +107,7 @@ public void testIncrement() throws Exception { MixedRadixInt num = new MixedRadixInt(digits, radixes, null); num.increment(); Assertions.assertEquals(5, num.get()); - Assertions.assertEquals(2, digits.size()); - num.increment(); - Assertions.assertEquals(6, num.get()); - Assertions.assertEquals(3, digits.size()); + num.increment(); // Wrap around to zero. + Assertions.assertEquals(0, num.get()); } } diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index 5fa5efd40a..52a0c25055 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -38,9 +38,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * A mixed radix number is a number representation where each digit can have * a distinct radix. The radixes are given by a list of numbers, r0, r1, ... , rn, * where r0 is the radix of the lowest-order digit and rn is the radix of the - * highest order digit that has a specified radix. There is an additional implict - * radix after rn of infinity, which means that the mixed radix number may have a - * total n + 2 digits, where the last digit is unbounded. + * highest order digit that has a specified radix. * * A PMR is a mixed radix number that, when incremented, * increments the digits in the order given by the permutation matrix. @@ -52,6 +50,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * next digit is incremented. If the last digit overflows, then the * number wraps around so that all digits become zero. * + * This implementation realizes a finite set of numbers, where incrementing + * past the end of the range wraps around to the beginning. As a consequence, + * the increment() function from any starting point is guaranteed to eventually + * cover all possible values. + * * The {@link #toString()} method gives a string representation of the number * where each digit is represented by the string "d%r", where d is the digit * and r is the radix. For example, the number "1%2, 2%3, 1%4" has value 11, @@ -64,9 +67,7 @@ public class MixedRadixInt { /** * Create a mixed radix number with the specified digits and radixes, * which are given low-order digits first. - * If there is one more digit than radixes, then the last digit is - * assumed to have infinite radix. If there are more digits than that, - * throw an exception. + * If there is one more digit than radixes, throw an exception. * @param digits The digits, or null to get a zero-valued number. * @param radixes The radixes. * @param permutation The permutation matrix, or null for the default permutation. @@ -75,7 +76,7 @@ public MixedRadixInt( List digits, List radixes, List permutation ) { if (radixes == null - || (digits != null && digits.size() > radixes.size() + 1) + || (digits != null && digits.size() > radixes.size()) || (permutation != null && permutation.size() != radixes.size())) { throw new IllegalArgumentException("Invalid constructor arguments."); } @@ -128,10 +129,6 @@ public int get(int n) { result += digits.get(i) * scale; scale *= radixes.get(i); } - if (digits.size() > radixes.size()) { - // Handle radix infinity digit. - result += digits.get(radixes.size()) * scale; - } return result; } @@ -184,12 +181,6 @@ public void increment() { return; // All done. } } - // If we get here, the number has overflowed. - // Append a radix infinity digit if needed. - while (digits.size() < radixes.size() + 1) { - digits.add(0); - } - digits.set(radixes.size(), digits.get(radixes.size()) + 1); } /** @@ -206,10 +197,6 @@ public int magnitude() { result += factor * digits.get(p.get(i)); factor *= radixes.get(p.get(i)); } - if (digits.size() > radixes.size()) { - // Add in any infinite radix digit. - result += factor * digits.get(radixes.size()); - } return result; } @@ -245,11 +232,6 @@ public void setMagnitude(int v) { digits.set(p, temp % radixes.get(p)); temp = temp / radixes.get(p); } - if (temp > 0) { - // Add a radix infinity digit. - while (digits.size() < radixes.size() + 1) digits.add(0); - digits.set(radixes.size(), temp); - } } /** From e1b9dc91f7a2867b8c088a6a563d44301d8d00f8 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 3 Jan 2022 18:05:29 -0800 Subject: [PATCH 117/221] Restored cycle detection. --- .../lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index e680421907..0ea5e912bb 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -478,7 +478,6 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { return node.addErrorComment(TEXT_ERROR_CONTAINS_RECURSION) } else { // only detect dependency cycles if not recursive try { - /* FIXME val hasCycle = reactorInstance.detectAndHighlightCycles(allReactorNodes, [ if (it instanceof KNode) { val renderings = it.data.filter(typeof(KRendering)).toList @@ -497,9 +496,6 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { //it.reverseTrianglePort() } ]) - * - */ - val hasCycle = false; if (hasCycle) { val err = node.addErrorComment(TEXT_ERROR_CONTAINS_CYCLE) @@ -747,7 +743,7 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } else { outputPorts.get(leftPort.parent, leftPort) } - for (rightSendRange : leftPort.dependentPortsFIXME) { + for (rightSendRange : leftPort.dependentPorts) { for (rightRange : rightSendRange.destinations) { val rightPort = rightRange.instance; val target = if (rightPort.parent == reactorInstance) { From d038435a1f60f33f184b1427d5cce7b6e9714b7a Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 3 Jan 2022 18:06:35 -0800 Subject: [PATCH 118/221] Added support for detecting overlapping ranges --- .../org/lflang/generator/RuntimeRange.java | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/RuntimeRange.java b/org.lflang/src/org/lflang/generator/RuntimeRange.java index 5a320d07fb..c57103a85d 100644 --- a/org.lflang/src/org/lflang/generator/RuntimeRange.java +++ b/org.lflang/src/org/lflang/generator/RuntimeRange.java @@ -327,6 +327,26 @@ public List> iterationOrder() { return result; } + /** + * Return a range that is the subset of this range that overlaps with the + * specified range or null if there is no overlap. + */ + public RuntimeRange overlap(RuntimeRange range) { + if (!overlaps(range)) return null; + int newStart = Math.max(start, range.start); + int newEnd = Math.min(start + width, range.start + range.width); + return tail(newStart - start).head(newEnd - newStart); + } + + /** + * Return true if the specified range has the same instance as this range + * and the ranges overlap. + */ + public boolean overlaps(RuntimeRange range) { + if (!instance.equals(range.instance)) return false; + return (start < range.start + range.width && start + width > range.start); + } + /** * Return a set of identifiers for runtime instances of a parent of this * RuntimeRange's instance n levels above this RuntimeRange's instance. If n == 1, for From 01a5cc69e1dae8ee90839c652cf51034c54fd2dc Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 3 Jan 2022 18:07:41 -0800 Subject: [PATCH 119/221] Major refactoring so that eventualDestinations() returns a list of SendRange rather than just RuntimeRange so that we can preserve interleaved status on the sending side. --- .../tests/compiler/PortInstanceTests.java | 10 +- .../org/lflang/generator/PortInstance.java | 107 +++++----------- .../generator/ReactionInstanceGraph.java | 84 ++++++------ .../org/lflang/generator/ReactorInstance.java | 33 +++-- .../src/org/lflang/generator/SendRange.java | 121 ++++++++++++++---- .../org/lflang/generator/c/CGenerator.xtend | 9 +- .../src/org/lflang/graph/TopologyGraph.java | 12 +- 7 files changed, 215 insertions(+), 161 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java index 7b24a6a519..f5f2ba387b 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/PortInstanceTests.java @@ -108,7 +108,8 @@ public void createRange() throws Exception { connect(s, 1, 2, u, 0, 2); sr = p.eventualDestinations(); - Assertions.assertEquals("[.A.p(0,1)->[.B.q(2,1), .E.F.t(0,1), .E.s(0,1)], .A.p(1,2)->[.C.r(0,2), .E.s(1,2), .E.G.u(0,2)]]", sr.toString()); + // FIXME: Multicast destinations should be able to be reported in arbitrary order. + Assertions.assertEquals("[.A.p(0,1)->[.E.F.t(0,1), .E.s(0,1), .B.q(2,1)], .A.p(1,2)->[.E.G.u(0,2), .E.s(1,2), .C.r(0,2)]]", sr.toString()); } @Test @@ -132,8 +133,7 @@ public void multiportDestination() throws Exception { maini.clearCaches(); newReaction(q); sr = p.eventualDestinations(); - // FIXME: how to make order-independent? - Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,1), .B.q(3,1), .B.q(2,1), .B.q(1,1)]]", sr.toString()); + Assertions.assertEquals("[.A.p(0,1)->[.B.q(0,4)]]", sr.toString()); } /** @@ -161,7 +161,7 @@ protected void clearConnections(ReactorInstance r) { protected void connect(PortInstance src, PortInstance dst) { RuntimeRange srcRange = new RuntimeRange.Port(src); RuntimeRange dstRange = new RuntimeRange.Port(dst); - ReactorInstance.connectPortInstances(srcRange, dstRange); + ReactorInstance.connectPortInstances(srcRange, dstRange, null); } /** @@ -177,7 +177,7 @@ protected void connect( ) { RuntimeRange srcRange = new RuntimeRange.Port(src, srcStart, srcWidth, null); RuntimeRange dstRange = new RuntimeRange.Port(dst, dstStart, dstWidth, null); - ReactorInstance.connectPortInstances(srcRange, dstRange); + ReactorInstance.connectPortInstances(srcRange, dstRange, null); } protected PortInstance newPort(String name, ReactorInstance container) { diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index d0f203b0b6..bbadf9612a 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -161,10 +161,12 @@ public List> eventualSources() { } /** - * Return the list of downstream ports that are connected to this port - * or an empty list if there are none. + * Return the list of ranges of this port together with the + * downstream ports that are connected to this port. + * The total with of the ranges in the returned list is a + * multiple N >= 0 of the total width of this port. */ - public List> getDependentPorts() { + public List getDependentPorts() { return dependentPorts; } @@ -199,22 +201,6 @@ public boolean isOutput() { return (definition instanceof Output); } - /** - * Return the number of destination reactors for this port instance. - * This can be used to initialize reference counting, but not for - * multiport. For multiports, the number of destinations can vary - * by channel, and hence must be obtained from the ranges reported - * by eventualDestinations(); - */ - public int numDestinationReactors() { - List sourceChannelRanges = eventualDestinations(); - int result = 0; - for (SendRange ranges : sourceChannelRanges) { - result += ranges.getNumberOfDestinationReactors(); - } - return result; - } - @Override public String toString() { return "PortInstance " + getFullName(); @@ -224,18 +210,17 @@ public String toString() { //// Protected fields. /** - * Downstream ports that are connected directly to this port. - * These are listed in the order they appear in connections. - * If this port is input port, then the connections are those - * in the parent reactor of this port (inside connections). - * If the port is an output port, then the connections are those - * in the parent's parent (outside connections). - * The sum of the widths of the dependent ports is required to - * be an integer multiple N of the width of this port (this is checked + * Ranges of this port together with downstream ports that + * are connected directly to this port. When there are multiple destinations, + * the destinations are listed in the order they appear in connections + * in the parent reactor instance of this port (inside connections), + * followed by the order in which they appear in the parent's parent (outside + * connections). The total of the widths of these SendRanges is an integer + * multiple N >= 0 of the width of this port (this is checked * by the validator). Each channel of this port will be broadcast - * to N recipients. + * to N recipients (or, if there are no connections to zero recipients). */ - List> dependentPorts = new ArrayList>(); + List dependentPorts = new ArrayList(); /** * Upstream ports that are connected directly to this port, if there are any. @@ -255,12 +240,9 @@ public String toString() { * Given a RuntimeRange, return a list of SendRange that describes * the eventual destinations of the given range. * The sum of the total widths of the send ranges on the returned list - * will equal the total width of the specified range. - * The returned list will be non-overlapping ranges in - * the order in which they should be traversed (channels, then - * banks if the specified range is not interleaved, or banks - * then channels otherwise). Each returned SendRange has a list - * of destinations RuntimeRange, each of which represents a port that + * will be an integer multiple N of the total width of the specified range. + * Each returned SendRange has a list + * of destination RuntimeRanges, each of which represents a port that * has dependent reactions. Intermediate ports with no dependent * reactions are not listed. * @param srcRange The source range. @@ -271,8 +253,8 @@ private static List eventualDestinations(RuntimeRange s // because of multicast, where there is more than one connection statement // for a source of data. The strategy we follow here is to first get all // the ports that this port eventually sends to. Then, if needed, split - // the resulting ranges so that each source range width matches all the - // destination range widths. We make two passes. First, we build + // the resulting ranges so that the resulting list covers exactly + // srcRange, possibly in pieces. We make two passes. First, we build // a queue of ranges that may overlap, then we split those ranges // and consolidate their destinations. @@ -290,48 +272,25 @@ private static List eventualDestinations(RuntimeRange s srcRange.width ); candidate.destinations.add(srcRange); - result.add(candidate); + queue.add(candidate); } - // Start with ports that are downstream of the range. - RuntimeRange wSrcRange = srcRange; // Working source range. - Iterator> dependentPorts = srcPort.dependentPorts.iterator(); - if (dependentPorts.hasNext()) { - RuntimeRange wDstRange = dependentPorts.next(); - while(true) { - if (wSrcRange == null) { - // Source range has been fully covered, but there may be more - // destinations. For multicast, start over with the source. - wSrcRange = srcRange; - } - if (wDstRange == null) { - // Destination range is covered. - if (!dependentPorts.hasNext()) break; // All done. - wDstRange = dependentPorts.next(); - } - // Cover the minimum of the two widths. - int width = Math.min(wSrcRange.width, wDstRange.width); - - // Destinations may be a subset. - RuntimeRange subDst = wDstRange.head(width); - - // At this point, dep is the subrange of the dependent port of interest. + // Need to find send ranges that overlap with this srcRange. + Iterator sendRanges = srcPort.dependentPorts.iterator(); + while (sendRanges.hasNext()) { + SendRange wSendRange = sendRanges.next().overlap(srcRange); + if (wSendRange == null) { + // This send range does not overlap with the desired range. Try the next one. + continue; + } + for (RuntimeRange dstRange : wSendRange.destinations) { // Recursively get the send ranges of that destination port. - List dstSendRanges = eventualDestinations(subDst); - // Widths of these ranges add up to the width of subDst. - - // For each returned SendRange, convert it to a SendRange - // for the srcRange port rather than the dep port. - int sendOffset = 0; + List dstSendRanges = eventualDestinations(dstRange); + int sendRangeStart = 0; for (SendRange dstSend : dstSendRanges) { - queue.add(dstSend.newSendRange(wSrcRange.tail(sendOffset).head(dstSend.width))); - sendOffset += dstSend.width; - if (sendOffset >= subDst.width) { - sendOffset = 0; - } + queue.add(dstSend.newSendRange(wSendRange, sendRangeStart)); + sendRangeStart += dstSend.width; } - wSrcRange = wSrcRange.tail(width); - wDstRange = wDstRange.tail(width); } } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 5398a3f725..ed6d0bc6f6 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -27,7 +27,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY package org.lflang.generator; import java.util.ArrayList; -import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; @@ -104,49 +103,58 @@ public void rebuild() { * @param reaction The reaction to relate downstream reactions to. */ protected void addDownstreamReactions(PortInstance port, ReactionInstance reaction) { - // This is a scary piece of code because of the richness of possible - // interconnections using banks and multiports. For any program without - // banks and multiports, each of these for loops iterates exactly once. + // Use mixed-radix numbers to increment over the ranges. List srcRuntimes = reaction.getRuntimeInstances(); - for (SendRange sendRange : port.eventualDestinations()) { - int depth = (port.isInput())? 2 : 1; + List eventualDestinations = port.eventualDestinations(); + + int srcDepth = (port.isInput())? 2 : 1; + + for (SendRange sendRange : eventualDestinations) { for (RuntimeRange dstRange : sendRange.destinations) { - // If the destination instance is the same as the source instance, - // skip this. Such ranges show up whenever retrieving destinations - // for a port that has reactions. if (dstRange.instance == sendRange.instance) continue; - Iterator sendParentIDs = sendRange.parentInstances(depth).iterator(); - while (sendParentIDs.hasNext()) { - int srcIndex = sendParentIDs.next(); - Set parentInstances = (dstRange.instance.isOutput()) ? - dstRange.parentInstances(2) - : dstRange.parentInstances(1); - for (int dstIndex : parentInstances) { - for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { - List dstRuntimes = dstReaction.getRuntimeInstances(); - Runtime srcRuntime = srcRuntimes.get(srcIndex); - Runtime dstRuntime = dstRuntimes.get(dstIndex); - addEdge(dstRuntime, srcRuntime); - - // Propagate the deadlines, if any. - if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { - srcRuntime.deadline = dstRuntime.deadline; - } - - // If this seems to be a single dominating reaction, set it. - // If another upstream reaction shows up, then this will be - // reset to null. - if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 - && (dstRuntime.getReaction().isUnordered - || dstRuntime.getReaction().index == 0)) { - dstRuntime.dominating = srcRuntime; - } else { - dstRuntime.dominating = null; - } + + int dstDepth = (dstRange.instance.isOutput())? 2 : 1; + MixedRadixInt dstRangePosition = dstRange.startMR(); + int dstRangeCount = 0; + + MixedRadixInt sendRangePosition = sendRange.startMR(); + int sendRangeCount = 0; + + while (dstRangeCount++ < dstRange.width) { + int srcIndex = sendRangePosition.get(srcDepth); + int dstIndex = dstRangePosition.get(dstDepth); + for (ReactionInstance dstReaction : dstRange.instance.dependentReactions) { + List dstRuntimes = dstReaction.getRuntimeInstances(); + Runtime srcRuntime = srcRuntimes.get(srcIndex); + Runtime dstRuntime = dstRuntimes.get(dstIndex); + addEdge(dstRuntime, srcRuntime); + + // Propagate the deadlines, if any. + if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { + srcRuntime.deadline = dstRuntime.deadline; + } + + // If this seems to be a single dominating reaction, set it. + // If another upstream reaction shows up, then this will be + // reset to null. + if (this.getUpstreamAdjacentNodes(dstRuntime).size() == 1 + && (dstRuntime.getReaction().isUnordered + || dstRuntime.getReaction().index == 0)) { + dstRuntime.dominating = srcRuntime; + } else { + dstRuntime.dominating = null; } } + dstRangePosition.increment(); + sendRangePosition.increment(); + sendRangeCount++; + if (sendRangeCount >= sendRange.width) { + // Reset to multicast. + sendRangeCount = 0; + sendRangePosition = sendRange.startMR(); + } } - } + } } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 232db573d8..28e3e465f6 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -439,6 +439,20 @@ public boolean isMainOrFederated() { && (reactorDefinition.isMain() || reactorDefinition.isFederated()); } + /** + * Return true if the specified reactor instance is either equal to this + * reactor instance or a parent of it. + * @param r The reactor instance. + */ + public boolean isParent(ReactorInstance r) { + ReactorInstance p = this; + while (p != null) { + if (p == r) return true; + p = p.getParent(); + } + return false; + } + /////////////////////////////////////////////////// //// Methods for finding instances in this reactor given an AST node. @@ -817,22 +831,21 @@ private ReactorInstance( /** * Connect the given left port range to the given right port range. - * This method consolidates the interleaved state on the destination - * side. That is, it sets the interleaved state of the destination - * to the exclusive OR of the interleaved state of the two ranges, - * and sets the interleaved state of the source to false. * * NOTE: This method is public to enable its use in unit tests. - * Otherwise, it should be private. This is why it is defined here. + * Otherwise, it should be private. This is why it is defined here, + * in the section labeled "Private methods." * * @param src The source range. * @param dst The destination range. */ public static void connectPortInstances( RuntimeRange src, - RuntimeRange dst + RuntimeRange dst, + Connection connection ) { - src.instance.dependentPorts.add(dst); + SendRange range = new SendRange(src, dst, connection); + src.instance.dependentPorts.add(range); dst.instance.dependsOnPorts.add(src); } @@ -868,7 +881,7 @@ private void establishPortConnections() { while(true) { if (dst.width == src.width) { - connectPortInstances(src, dst); + connectPortInstances(src, dst, connection); if (!dstRanges.hasNext()) { if (srcRanges.hasNext()) { // Should not happen (checked by the validator). @@ -893,7 +906,7 @@ private void establishPortConnections() { src = srcRanges.next(); } else if (dst.width < src.width) { // Split the left (src) range in two. - connectPortInstances(src.head(dst.width), dst); + connectPortInstances(src.head(dst.width), dst, connection); src = src.tail(dst.width); if (!dstRanges.hasNext()) { // Should not happen (checked by the validator). @@ -904,7 +917,7 @@ private void establishPortConnections() { dst = dstRanges.next(); } else if (src.width < dst.width) { // Split the right (dst) range in two. - connectPortInstances(src, dst.head(src.width)); + connectPortInstances(src, dst.head(src.width), connection); dst = dst.tail(src.width); if (!srcRanges.hasNext()) { if (connection.isIterated()) { diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index 81c88cc38b..df32f9dcfb 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -32,14 +32,14 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.Map; import java.util.Set; +import org.lflang.lf.Connection; + /** * Class representing a range of a port that sources data * together with a list of destination ranges of other ports that should all * receive the same data sent in this range. - * All ranges in the destinations list have the same width as this range, - * but not necessarily the same start offsets. - * This class also includes a field representing the number of destination - * reactors. + * All ranges in the destinations list have widths that are an integer + * multiple N of this range but not necessarily the same start offsets. * * This class and subclasses are designed to be immutable. * Modifications always return a new RuntimeRange. @@ -62,11 +62,32 @@ public SendRange( super(instance, start, width, null); } + /** + * Create a new send range representing sending from the specified + * src to the specified dst. This preserves the interleaved status + * of both the src and dst. + * @param src The source range. + * @param dst The destination range. + * @param connection The connection. + */ + public SendRange( + RuntimeRange src, + RuntimeRange dst, + Connection connection + ) { + super(src.instance, src.start, src.width, connection); + destinations.add(dst); + for (ReactorInstance i : src._interleaved) { + toggleInterleaved(i); + } + } + ////////////////////////////////////////////////////////// //// Public variables /** The list of destination ranges to which this broadcasts. */ - public final List> destinations = new ArrayList>(); + public final List> destinations + = new ArrayList>(); ////////////////////////////////////////////////////////// //// Public methods @@ -109,9 +130,9 @@ public int compareTo(RuntimeRange o) { } /** - * Return the total number of destination reactors. Specifically, this - * is the number of distinct runtime reactor instances that react to - * messages from this send range. + * Return the total number of destination reactors for this range. + * Specifically, this is the number of distinct runtime reactor instances + * that react to messages from this send range. */ public int getNumberOfDestinationReactors() { if (_numberOfDestinationReactors < 0) { @@ -160,6 +181,36 @@ public SendRange head(int newWidth) { return result; } + /** + * Return a range that is the subset of this range that overlaps with the + * specified range or null if there is no overlap. + */ + @Override + public SendRange overlap(RuntimeRange range) { + if (!overlaps(range)) return null; + if (range.start == start && range.width == width) return this; + int newStart = Math.max(start, range.start); + int newEnd = Math.min(start + width, range.start + range.width); + int newWidth = newEnd - newStart; + SendRange result = new SendRange(instance, newStart, newWidth); + for (RuntimeRange destination : destinations) { + // The destination width is a multiple of this range's width. + // If the multiple is greater than 1, then the destination needs to + // split into multiple destinations. + while (destination != null) { + int dstStart = destination.start + (newStart - start); + result.addDestination(new RuntimeRange.Port( + destination.instance, + dstStart, + newWidth, + destination.connection + )); + destination = destination.tail(width); + } + } + return result; + } + /** * Return a new SendRange that represents the leftover elements * starting at the specified offset. If the offset is greater @@ -200,27 +251,47 @@ public String toString() { //// Protected methods /** - * Return a new SendRange that is like this one, but - * converted to the specified upstream range. The returned - * SendRange inherits the destinations of this range. - * The width of the resulting range is - * the minimum of the two widths. + * Assuming that this SendRange is completely contained by one + * of the destinations of the specified srcRange, return a new SendRange + * where the send range is the subrange of the specified srcRange that + * overlaps with this SendRange and the destinations include all the + * destinations of this SendRange. If the assumption is not satisfied, + * throw an IllegalArgumentException. + * + * If any parent of this range is marked interleaved and is also a parent of the + * specified srcRange, then that parent will be marked interleaved + * in the result. * * @param srcRange A new source range. + * @param srcRangeOffset An offset into the source range. */ - protected SendRange newSendRange(RuntimeRange srcRange) { - SendRange reference = this; - if (srcRange.width > width) { - srcRange = srcRange.head(width); - } else if (srcRange.width < width) { - reference = head(srcRange.width); - } - SendRange result = new SendRange(srcRange.instance, srcRange.start, srcRange.width); - - for (RuntimeRange dst : reference.destinations) { - result.destinations.add(dst.head(srcRange.width)); + protected SendRange newSendRange(SendRange srcRange, int srcRangeOffset) { + // Every destination of srcRange receives all channels of srcRange (multicast). + // Find which multicast destination overlaps with this srcRange. + for (RuntimeRange srcDestination : srcRange.destinations) { + RuntimeRange overlap = srcDestination.overlap(this); + if (overlap == null) continue; // Not this one. + + if (overlap.width == width) { + // Found an overlap that is completely contained. + // If this width is greater than the srcRange width, + // then assume srcRange is multicasting via this. + int newWidth = Math.min(width, srcRange.width); + SendRange result = new SendRange(srcRange.instance, srcRange.start + srcRangeOffset, newWidth); + for (RuntimeRange dst : destinations) { + result.addDestination(dst); + } + for (ReactorInstance i : _interleaved) { + if (result.instance.getParent().isParent(i)) { + result.toggleInterleaved(i); + } + } + return result; + } } - return result; + throw new IllegalArgumentException( + "Expected this SendRange " + this.toString() + + " to be completely contained by a destination of " + srcRange.toString()); } ////////////////////////////////////////////////////////// diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 90a2033a9a..e6a2821dd3 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4902,7 +4902,7 @@ class CGenerator extends GeneratorBase { indent(builder); pr(builder, ''' int «ci» = range_mr.digits[0]; // Channel index. - int «runtimeIndex» = mixed_radix_parent(&range_mr, 1); + int «runtimeIndex» = mixed_radix_parent(&range_mr, 1); ''') } else { val mr = range.startMR(); @@ -5785,7 +5785,7 @@ class CGenerator extends GeneratorBase { startScopedRangeBlock(code, sendingRange, "runtime_index"); pr(''' - «CUtil.portRef(output, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRef(output, "runtime_index", null)».num_destinations += «sendingRange.getNumberOfDestinationReactors()»; ''') endScopedRangeBlock(code, sendingRange); @@ -5831,11 +5831,11 @@ class CGenerator extends GeneratorBase { // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { pr(''' - «CUtil.portRefNested(port, "runtime_index", null)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "runtime_index", null)»->num_destinations += «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' - «CUtil.portRefNested(port, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "runtime_index", null)».num_destinations += «sendingRange.getNumberOfDestinationReactors»; ''') } endScopedRangeBlock(code, sendingRange); @@ -6141,7 +6141,6 @@ class CGenerator extends GeneratorBase { for (reaction: reactions) { val name = reaction.parent.getFullName; - // The reaction may have multiple runtime instances, one in each bank member of the parent. // Need a separate index for the triggers array for each bank member. val triggersIndexInitializer = new LinkedList(); for (var i = 0; i < reaction.parent.totalWidth; i++) triggersIndexInitializer.add(0); diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index a8dc2c8deb..ee2aa4dbe6 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -26,12 +26,14 @@ package org.lflang.graph; -import java.util.Collection; import java.util.Arrays; +import java.util.Collection; + import org.lflang.generator.NamedInstance; import org.lflang.generator.PortInstance; import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; +import org.lflang.generator.SendRange; import org.lflang.generator.TriggerInstance; import org.lflang.lf.Connection; import org.lflang.lf.Variable; @@ -111,9 +113,11 @@ private void addEffects(ReactionInstance reaction) { if (effect instanceof PortInstance) { addEdge(effect, reaction); PortInstance orig = (PortInstance) effect; - orig.getDependentPorts().forEach(dest -> { - recordDependency(reaction, orig, dest.instance, dest.connection); - }); + for (SendRange sendRange : orig.getDependentPorts()) { + sendRange.destinations.forEach(dest -> { + recordDependency(reaction, orig, dest.instance, dest.connection); + }); + } } } } From 4dc3a4cd046380c37403b3b09f43aa1ab815a1ea Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 4 Jan 2022 11:27:53 -0800 Subject: [PATCH 120/221] Inherit interleaving status more systematically. Also moved Connection to SendRange. --- .../synthesis/LinguaFrancaSynthesis.xtend | 6 +- .../org/lflang/tests/compiler/RangeTests.java | 2 +- .../org/lflang/generator/GeneratorBase.xtend | 152 ++++++++++-------- .../org/lflang/generator/PortInstance.java | 4 +- .../org/lflang/generator/ReactorInstance.java | 14 +- .../org/lflang/generator/RuntimeRange.java | 35 ++-- .../src/org/lflang/generator/SendRange.java | 55 ++++--- .../src/org/lflang/graph/TopologyGraph.java | 6 +- 8 files changed, 152 insertions(+), 122 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 0ea5e912bb..7bcfc2eac1 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -743,8 +743,8 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } else { outputPorts.get(leftPort.parent, leftPort) } - for (rightSendRange : leftPort.dependentPorts) { - for (rightRange : rightSendRange.destinations) { + for (sendRange : leftPort.dependentPorts) { + for (rightRange : sendRange.destinations) { val rightPort = rightRange.instance; val target = if (rightPort.parent == reactorInstance) { parentOutputPorts.get(rightPort) @@ -752,7 +752,7 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { inputPorts.get(rightPort.parent, rightPort) } // There should be a connection, but skip if not. - val connection = rightRange.connection; + val connection = sendRange.connection; if (connection !== null) { val edge = createIODependencyEdge(connection, leftPort.isMultiport() || rightPort.isMultiport()) if (connection.delay !== null) { diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java index 73e0a4bcc1..5cd489d0f7 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RangeTests.java @@ -62,7 +62,7 @@ public void createRange() throws Exception { Assertions.assertEquals(List.of(1, 1, 0), range.startMR().getDigits()); // Create a SendRange sending from and to this range. - SendRange sendRange = new SendRange(pi, 3, 4); + SendRange sendRange = new SendRange(pi, 3, 4, null, null); sendRange.destinations.add(range); // Test getNumberOfDestinationReactors. diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index ff0b828d01..b662ca24d0 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -61,16 +61,13 @@ import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory import org.lflang.lf.Model import org.lflang.lf.Parameter -import org.lflang.lf.Port import org.lflang.lf.Reaction import org.lflang.lf.Reactor -import org.lflang.lf.StateVar import org.lflang.lf.Time import org.lflang.lf.Value import org.lflang.lf.VarRef import static extension org.lflang.ASTUtils.* -import static extension org.lflang.JavaAstUtils.* /** * Generator base class for specifying core functionality @@ -1113,92 +1110,107 @@ abstract class GeneratorBase extends JavaGeneratorBase { val mainInstance = new ReactorInstance(mainReactor, errorReporter, 1) for (federateReactor : mainInstance.children) { - // FIXME: This used to Skip banks and just process the individual instances. val federateInstances = federatesByInstantiation.get(federateReactor.definition); for (federateInstance : federateInstances) { - for (input : federateReactor.inputs) { - replaceConnectionFromSource(input, federateInstance, federateReactor, mainInstance) + for (output : federateReactor.outputs) { + replaceConnectionFromFederate(output, federateReactor, mainInstance) } } } } /** - * Replace the connections to the specified input port for the specified federate reactor. - * @param input The input port instance. - * @param destinationFederate The federate for which this port is an input. + * Replace the connections from the specified output port for the specified federate reactor. + * @param output The output port instance. + * @param srcFederate The federate for which this port is an output. * @param federateReactor The reactor instance for that federate. * @param mainInstance The main reactor instance. */ - def void replaceConnectionFromSource( - PortInstance input, FederateInstance destinationFederate, ReactorInstance federateReactor, ReactorInstance mainInstance + private def void replaceConnectionFromFederate( + PortInstance output, + ReactorInstance federateReactor, + ReactorInstance mainInstance ) { - var channel = 0; // Next input channel to be replaced. - // If the port is not an input, ignore it. - if (input.isInput) { - for (source : input.dependsOnPorts) { - // FIXME: How to determine the bank index of the source? - // Need ReactorInstance support for ranges. - // val sourceBankIndex = (source.getPort().parent.bankIndex >= 0) ? source.getPort().parent.bankIndex : 0 - val sourceBankIndex = 0 - val sourceFederate = federatesByInstantiation.get(source.instance.parent.definition).get(sourceBankIndex); - - // Set up dependency information. - var connection = source.connection; - if (connection === null) { - // This should not happen. - errorReporter.reportError(input.definition, "Unexpected error. Cannot find input connection for port") - } else { - if (sourceFederate !== destinationFederate - && !connection.physical - && targetConfig.coordination !== CoordinationType.DECENTRALIZED) { - // Map the delays on connections between federates. - // First see if the cache has been created. - var dependsOnDelays = destinationFederate.dependsOn.get(sourceFederate) - if (dependsOnDelays === null) { - // If not, create it. - dependsOnDelays = new LinkedHashSet() - destinationFederate.dependsOn.put(sourceFederate, dependsOnDelays) - } - // Put the delay on the cache. - if (connection.delay !== null) { - dependsOnDelays.add(connection.delay) - } else { - // To indicate that at least one connection has no delay, add a null entry. - dependsOnDelays.add(null) - } - // Map the connections between federates. - var sendsToDelays = sourceFederate.sendsTo.get(destinationFederate) - if (sendsToDelays === null) { - sendsToDelays = new LinkedHashSet() - sourceFederate.sendsTo.put(destinationFederate, sendsToDelays) - } - if (connection.delay !== null) { - sendsToDelays.add(connection.delay) - } else { - // To indicate that at least one connection has no delay, add a null entry. - sendsToDelays.add(null) + for (srcRange : output.dependentPorts) { + for (RuntimeRange dstRange : srcRange.destinations) { + + var srcID = srcRange.startMR(); + val dstID = dstRange.startMR(); + var dstCount = 0; + var srcCount = 0; + + while (dstCount++ < dstRange.width) { + val srcChannel = srcID.digits.get(0); + val srcBank = srcID.get(1); + val dstChannel = dstID.digits.get(0); + val dstBank = dstID.get(1); + + val srcFederate = federatesByInstantiation.get( + srcRange.instance.parent.definition + ).get(srcBank); + val dstFederate = federatesByInstantiation.get( + dstRange.instance.parent.definition + ).get(dstBank); + + val connection = srcRange.connection; + + if (connection === null) { + // This should not happen. + errorReporter.reportError(output.definition, + "Unexpected error. Cannot find output connection for port") + } else { + if (srcFederate !== dstFederate + && !connection.physical + && targetConfig.coordination !== CoordinationType.DECENTRALIZED) { + // Map the delays on connections between federates. + // First see if the cache has been created. + var dependsOnDelays = dstFederate.dependsOn.get(srcFederate) + if (dependsOnDelays === null) { + // If not, create it. + dependsOnDelays = new LinkedHashSet() + dstFederate.dependsOn.put(srcFederate, dependsOnDelays) + } + // Put the delay on the cache. + if (connection.delay !== null) { + dependsOnDelays.add(connection.delay) + } else { + // To indicate that at least one connection has no delay, add a null entry. + dependsOnDelays.add(null) + } + // Map the connections between federates. + var sendsToDelays = srcFederate.sendsTo.get(dstFederate) + if (sendsToDelays === null) { + sendsToDelays = new LinkedHashSet() + srcFederate.sendsTo.put(dstFederate, sendsToDelays) + } + if (connection.delay !== null) { + sendsToDelays.add(connection.delay) + } else { + // To indicate that at least one connection has no delay, add a null entry. + sendsToDelays.add(null) + } } - } - - // Make one communication for each channel. - // FIXME: There is an opportunity for optimization here by aggregating channels. - for (var i = 0; i < source.width; i++) { + FedASTUtils.makeCommunication( - source.instance, - input, + srcRange.instance, + dstRange.instance, connection, - sourceFederate, - 0, // FIXME: source.getPort().parent.bankIndex, - source.start + i, - destinationFederate, - 0, // FIXME: input.parent.bankIndex, - channel + i, + srcFederate, + srcBank, + srcChannel, + dstFederate, + dstBank, + dstChannel, this, targetConfig.coordination ); + } + dstID.increment(); + srcID.increment(); + srcCount++; + if (srcCount == srcRange.width) { + srcID = srcRange.startMR(); // Multicast. Start over. } - channel += source.width; } } } diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index bbadf9612a..3262851227 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -269,7 +269,9 @@ private static List eventualDestinations(RuntimeRange s SendRange candidate = new SendRange( srcRange.instance, srcRange.start, - srcRange.width + srcRange.width, + null, // No interleaving for this range. + null // No connection for this range. ); candidate.destinations.add(srcRange); queue.add(candidate); diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 28e3e465f6..4063b78543 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -838,13 +838,14 @@ private ReactorInstance( * * @param src The source range. * @param dst The destination range. + * @param connection The connection establishing this relationship. */ public static void connectPortInstances( RuntimeRange src, RuntimeRange dst, Connection connection ) { - SendRange range = new SendRange(src, dst, connection); + SendRange range = new SendRange(src, dst, src._interleaved, connection); src.instance.dependentPorts.add(range); dst.instance.dependsOnPorts.add(src); } @@ -974,17 +975,18 @@ private List> listPortInstances( // The reactor can be null only if there is an error in the code. // Skip this portRef so that diagram synthesis can complete. if (reactor != null) { - PortInstance portInstance = reactor.lookupPortInstance((Port) portRef.getVariable()); + PortInstance portInstance = reactor.lookupPortInstance( + (Port) portRef.getVariable()); - RuntimeRange range = new RuntimeRange.Port(portInstance, connection); + Set interleaved = new LinkedHashSet(); if (portRef.isInterleaved()) { - // Toggle interleaving at the depth of the reactor - // that is the parent of the port. // NOTE: Here, we are assuming that the interleaved() // keyword is only allowed on the multiports contained by // contained reactors. - range = range.toggleInterleaved(portInstance.parent); + interleaved.add(portInstance.parent); } + RuntimeRange range = new RuntimeRange.Port( + portInstance, interleaved); // If this portRef is not the last one in the references list // then we have to check whether the range can be incremented at // the lowest two levels (port and container). If not, diff --git a/org.lflang/src/org/lflang/generator/RuntimeRange.java b/org.lflang/src/org/lflang/generator/RuntimeRange.java index c57103a85d..fcbe4a4df2 100644 --- a/org.lflang/src/org/lflang/generator/RuntimeRange.java +++ b/org.lflang/src/org/lflang/generator/RuntimeRange.java @@ -31,8 +31,6 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.List; import java.util.Set; -import org.lflang.lf.Connection; - /** * Class representing a range of runtime instance objects * (port channels, reactors, reactions, etc.). This class and its derived classes @@ -178,13 +176,13 @@ public class RuntimeRange> implements Comparable interleaved ) { - this(instance, 0, 0, connection); + this(instance, 0, 0, interleaved); } /** @@ -195,17 +193,19 @@ public RuntimeRange( * @param instance The instance over which this is a range (port, reaction, etc.) * @param start The starting index for the range. * @param width The width of the range or 0 to specify the maximum possible width. - * @param connection The connection establishing this range or null if not unique. + * @param interleaved A list of parents that are interleaved or null if none. */ public RuntimeRange( T instance, int start, int width, - Connection connection + Set interleaved ) { this.instance = instance; this.start = start; - this.connection = connection; + if (interleaved != null) { + this._interleaved.addAll(interleaved); + } int maxWidth = instance.width; // Initial value. NamedInstance parent = instance.parent; @@ -225,9 +225,6 @@ public RuntimeRange( ////////////////////////////////////////////////////////// //// Public variables - /** The connection establishing this range or null if not unique. */ - public final Connection connection; - /** The instance that this is a range of. */ public final T instance; @@ -277,7 +274,7 @@ public int compareTo(RuntimeRange o) { public RuntimeRange head(int newWidth) { if (newWidth >= width) return this; if (newWidth <= 0) return null; - return new RuntimeRange(instance, start, newWidth, connection); + return new RuntimeRange(instance, start, newWidth, _interleaved); } /** @@ -463,7 +460,7 @@ public MixedRadixInt startMR() { public RuntimeRange tail(int offset) { if (offset == 0) return this; if (offset >= width) return null; - return new RuntimeRange(instance, start + offset, width - offset, connection); + return new RuntimeRange(instance, start + offset, width - offset, _interleaved); } /** @@ -480,9 +477,7 @@ public RuntimeRange toggleInterleaved(ReactorInstance reactor) { } else { newInterleaved.add(reactor); } - RuntimeRange result = new RuntimeRange(instance, start, width, connection); - result._interleaved = newInterleaved; - return result; + return new RuntimeRange(instance, start, width, newInterleaved); } @Override @@ -506,11 +501,11 @@ public static class Port extends RuntimeRange { public Port(PortInstance instance) { super(instance, null); } - public Port(PortInstance instance, Connection connection) { - super(instance, connection); + public Port(PortInstance instance, Set interleaved) { + super(instance, interleaved); } - public Port(PortInstance instance, int start, int width, Connection connection) { - super(instance, start, width, connection); + public Port(PortInstance instance, int start, int width, Set interleaved) { + super(instance, start, width, interleaved); } } } \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/SendRange.java b/org.lflang/src/org/lflang/generator/SendRange.java index df32f9dcfb..62cc418079 100644 --- a/org.lflang/src/org/lflang/generator/SendRange.java +++ b/org.lflang/src/org/lflang/generator/SendRange.java @@ -27,6 +27,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -53,13 +54,18 @@ public class SendRange extends RuntimeRange.Port { * @param instance The instance over which this is a range of. * @param start The starting index. * @param width The width. + * @param interleaved A list of parents that are interleaved or null if none. + * @param connection The connection that establishes this send or null if not unique or none. */ public SendRange( PortInstance instance, int start, - int width + int width, + Set interleaved, + Connection connection ) { - super(instance, start, width, null); + super(instance, start, width, interleaved); + this.connection = connection; } /** @@ -68,23 +74,27 @@ public SendRange( * of both the src and dst. * @param src The source range. * @param dst The destination range. - * @param connection The connection. + * @param interleaved A list of parents that are interleaved or null if none. + * @param connection The connection that establishes this send or null if not unique or none. */ public SendRange( RuntimeRange src, RuntimeRange dst, + Set interleaved, Connection connection ) { - super(src.instance, src.start, src.width, connection); + super(src.instance, src.start, src.width, interleaved); destinations.add(dst); - for (ReactorInstance i : src._interleaved) { - toggleInterleaved(i); - } + _interleaved.addAll(src._interleaved); + this.connection = connection; } ////////////////////////////////////////////////////////// //// Public variables + /** The connection that establishes this relationship or null if not unique or none. */ + public final Connection connection; + /** The list of destination ranges to which this broadcasts. */ public final List> destinations = new ArrayList>(); @@ -173,7 +183,7 @@ public SendRange head(int newWidth) { // Also, cannot return this without applying head() to the destinations. if (newWidth <= 0) return null; - SendRange result = new SendRange(instance, start, newWidth); + SendRange result = new SendRange(instance, start, newWidth, _interleaved, connection); for (RuntimeRange destination : destinations) { result.destinations.add(destination.head(newWidth)); @@ -192,19 +202,21 @@ public SendRange overlap(RuntimeRange range) { int newStart = Math.max(start, range.start); int newEnd = Math.min(start + width, range.start + range.width); int newWidth = newEnd - newStart; - SendRange result = new SendRange(instance, newStart, newWidth); + SendRange result = new SendRange(instance, newStart, newWidth, _interleaved, connection); + result._interleaved.addAll(_interleaved); for (RuntimeRange destination : destinations) { // The destination width is a multiple of this range's width. // If the multiple is greater than 1, then the destination needs to // split into multiple destinations. while (destination != null) { int dstStart = destination.start + (newStart - start); - result.addDestination(new RuntimeRange.Port( + RuntimeRange.Port dst = new RuntimeRange.Port( destination.instance, dstStart, newWidth, - destination.connection - )); + destination._interleaved + ); + result.addDestination(dst); destination = destination.tail(width); } } @@ -225,7 +237,8 @@ public SendRange tail(int offset) { // NOTE: Cannot use the superclass because it returns a RuntimeRange, not a SendRange. // Also, cannot return this without applying tail() to the destinations. if (offset >= width) return null; - SendRange result = new SendRange(instance, start + offset, width - offset); + SendRange result = new SendRange( + instance, start + offset, width - offset, _interleaved, connection); for (RuntimeRange destination : destinations) { result.destinations.add(destination.tail(offset)); @@ -277,15 +290,19 @@ protected SendRange newSendRange(SendRange srcRange, int srcRangeOffset) { // If this width is greater than the srcRange width, // then assume srcRange is multicasting via this. int newWidth = Math.min(width, srcRange.width); - SendRange result = new SendRange(srcRange.instance, srcRange.start + srcRangeOffset, newWidth); + // The interleaving of the result is the union of the two interleavings. + Set interleaving = new LinkedHashSet(); + interleaving.addAll(_interleaved); + interleaving.addAll(srcRange._interleaved); + SendRange result = new SendRange( + srcRange.instance, + srcRange.start + srcRangeOffset, + newWidth, + interleaving, + connection); for (RuntimeRange dst : destinations) { result.addDestination(dst); } - for (ReactorInstance i : _interleaved) { - if (result.instance.getParent().isParent(i)) { - result.toggleInterleaved(i); - } - } return result; } } diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index ee2aa4dbe6..f0be88c566 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -115,7 +115,7 @@ private void addEffects(ReactionInstance reaction) { PortInstance orig = (PortInstance) effect; for (SendRange sendRange : orig.getDependentPorts()) { sendRange.destinations.forEach(dest -> { - recordDependency(reaction, orig, dest.instance, dest.connection); + recordDependency(reaction, orig, dest.instance, sendRange.connection); }); } } @@ -136,7 +136,9 @@ private void addSources(ReactionInstance reaction) { addEdge(reaction, source); PortInstance dest = (PortInstance) source; dest.getDependsOnPorts().forEach(orig -> { - recordDependency(reaction, orig.instance, dest, orig.connection); + // FIXME: Don't have connection information here, hence the null argument. + // This will like result in invalid cycle detection. + recordDependency(reaction, orig.instance, dest, null); }); } } From 5fd419542ec382d21d86d9592862ac8d001834a3 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 5 Jan 2022 13:58:18 -0800 Subject: [PATCH 121/221] Updated level assignment to support multiple levels --- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/c/CGenerator.xtend | 162 +++++++++++------- 2 files changed, 105 insertions(+), 59 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 4d1e64d156..b11ffdf9cd 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 4d1e64d156af2f6ea73059d4f56e2e2be1ccfcd7 +Subproject commit b11ffdf9cdf009ca70dddbdb3288be5bb7ba6eef diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index e6a2821dd3..d60e819d6a 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4026,46 +4026,84 @@ class CGenerator extends GeneratorBase { * @param reactor The reactor on which to do this. * @param builder Where to write the code. */ - private def void setReactionPriorities(ReactorInstance reactor, StringBuilder builder) { + private def boolean setReactionPriorities(ReactorInstance reactor, StringBuilder builder) { var foundOne = false; - if (reactor.isBank) { - startScopedBlock(builder, reactor); + // Force calculation of levels if it has not been done. + reactor.assignLevels(); + + // If any reaction has multiple levels, then we need to create + // an array with the levels here, before entering the iteration over banks. + val prolog = new StringBuilder(); + val epilog = new StringBuilder(); + for (r : reactor.reactions) { + if (currentFederate.contains(r.definition)) { + val levels = r.getLevels(); + if (levels.size != 1) { + if (prolog.length() == 0) { + startScopedBlock(prolog); + endScopedBlock(epilog); + } + pr(prolog, ''' + int «r.uniqueID»_levels[] = { «levels.join(", ")» }; + ''') + } + } } + val temp = new StringBuilder(); pr(temp, "// Set reaction priorities for " + reactor.toString()); + + startScopedBlock(temp, reactor); + for (r : reactor.reactions) { - val levels = r.getLevels(); - if (levels.size != 1) { - throw new RuntimeException("FIXME: Temporarily can't handle reactions with multiple levels."); - } - var level = -1; - for (l : levels) { - level = l; - } if (currentFederate.contains(r.definition)) { foundOne = true; - val reactionStructName = '''«CUtil.reactorRef(reactor)»->_lf__reaction_«r.index»''' - // xtend doesn't support bitwise operators... - val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, level) - val reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL" - pr(temp, ''' - «reactionStructName».chain_id = «r.chainID.toString»; - // index is the OR of level «level» and - // deadline «r.deadline.toNanoSeconds» shifted left 16 bits. - «reactionStructName».index = «reactionIndex»; - ''') + + // The most common case is that all runtime instances of the + // reaction have the same level, so deal with that case + // specially. + val levels = r.getLevels(); + if (levels.size == 1) { + var level = -1; + for (l : levels) { + level = l; + } + // xtend doesn't support bitwise operators... + val indexValue = XtendUtil.longOr(r.deadline.toNanoSeconds << 16, level) + val reactionIndex = "0x" + Long.toString(indexValue, 16) + "LL" + + pr(temp, ''' + «CUtil.reactionRef(r)».chain_id = «r.chainID.toString»; + // index is the OR of level «level» and + // deadline «r.deadline.toNanoSeconds» shifted left 16 bits. + «CUtil.reactionRef(r)».index = «reactionIndex»; + ''') + } else { + val reactionDeadline = "0x" + Long.toString(r.deadline.toNanoSeconds, 16) + "LL" + + pr(temp, ''' + «CUtil.reactionRef(r)».chain_id = «r.chainID.toString»; + // index is the OR of levels[«CUtil.runtimeIndex(r.parent)»] and + // deadline «r.deadline.toNanoSeconds» shifted left 16 bits. + «CUtil.reactionRef(r)».index = («reactionDeadline» << 16) | «r.uniqueID»_levels[«CUtil.runtimeIndex(r.parent)»]; + ''') + } } } - if (foundOne) pr(builder, temp.toString()); for (child : reactor.children) { if (currentFederate.contains(child)) { - setReactionPriorities(child, builder); + foundOne = setReactionPriorities(child, temp) || foundOne; } } - if (reactor.isBank) { - endScopedBlock(builder); + endScopedBlock(temp); + + if (foundOne) { + pr(builder, prolog.toString()); + pr(builder, temp.toString()); + pr(builder, epilog.toString()); } + return foundOne; } override getTargetTypes() { @@ -4945,6 +4983,14 @@ class CGenerator extends GeneratorBase { * must provide the second argument, a runtime index variable name, * that must match the srcRuntimeIndex or dstRuntimeIndex parameter given here. * + * If the nested argument is true, then treat the corner case where + * if srcRange and dstRange refer to the same instance, + * and if that instance is an input, then the situation we have that + * of a reaction driving an input port of a contained reactor. + * In this case, the source bank should be the bank of the parent's parent, + * and the channel index should be the product of the two low-order digits + * of the mixed-radix number. + * * This must be followed by a call to * {@link #endScopedRangeBlock(StringBuilder, RuntimeRange)}. * @@ -4961,7 +5007,8 @@ class CGenerator extends GeneratorBase { RuntimeRange dstRange, String srcRuntimeIndex, String srcChannelIndex, - String dstRuntimeIndex + String dstRuntimeIndex, + boolean nested ) { pr(builder, ''' @@ -4996,12 +5043,26 @@ class CGenerator extends GeneratorBase { } startScopedRangeBlock(builder, dstRange, dstRuntimeIndex); - + if (srcRange.width > 1) { - pr(builder, ''' - int «srcChannelIndex» = src_range_mr.digits[0]; // Channel index. - int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 1); - ''') + // If srcRange and dstRange refer to the same instance, + // and if that instance is an input, then the situation we have that + // of a reaction driving an input port of a contained reactor. + // In this case, the source bank should be the bank of the parent's parent, + // and the channel index should be the product of the two low-order digits + // of the mixed-radix number. + if (nested && srcRange.instance == dstRange.instance && srcRange.instance.isInput()) { + pr(builder, ''' + int «srcChannelIndex» = src_range_mr.digits[0] + + src_range_mr.digits[1] * src_range_mr.radixes[0]; // Channel index. + int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 2); + ''') + } else { + pr(builder, ''' + int «srcChannelIndex» = src_range_mr.digits[0]; // Channel index. + int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 1); + ''') + } } } @@ -5058,13 +5119,13 @@ class CGenerator extends GeneratorBase { val mod = (dst.isMultiport)? "" : "&"; pr(''' - // Connect «src.getFullName» to port «dst.getFullName» + // Connect «srcRange.toString» to port «dstRange.toString» ''') val srcChannel = "src_channel"; val srcBank = "src_bank"; val dstBank = "dst_bank"; - startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, false); if (src.isInput) { // Source port is written to by reaction in port's parent's parent @@ -6142,11 +6203,9 @@ class CGenerator extends GeneratorBase { val name = reaction.parent.getFullName; // Need a separate index for the triggers array for each bank member. - val triggersIndexInitializer = new LinkedList(); - for (var i = 0; i < reaction.parent.totalWidth; i++) triggersIndexInitializer.add(0); startScopedBlock(code); pr(''' - int triggers_index[] = { «triggersIndexInitializer.join(", ")» }; + int triggers_index[«reaction.parent.totalWidth»] = { 0 }; // Number of banks. ''') for (port : reaction.effects.filter(PortInstance)) { @@ -6183,12 +6242,11 @@ class CGenerator extends GeneratorBase { endScopedRangeBlock(code, srcRange); } } - endScopedBlock(code); - - // To get triggers_index right, have to duplicate the logic in deferredReactionOutputs(). - var outputCount = 0; - + var cumulativePortWidth = 0; for (port : reaction.effects.filter(PortInstance)) { + pr(''' + for (int i = 0; i < «reaction.parent.totalWidth»; i++) triggers_index[i] = «cumulativePortWidth»; + ''') for (SendRange srcRange : port.eventualDestinations()) { if (currentFederate.contains(port.parent)) { var multicastCount = 0; @@ -6197,19 +6255,11 @@ class CGenerator extends GeneratorBase { val dstBank = "dst_bank"; for (dstRange : srcRange.destinations) { val dst = dstRange.instance; - - // Each multicast target needs to start with the same indexes into triggers array. - triggersIndexInitializer.clear(); - for (var i = 0; i < reaction.parent.totalWidth; i++) triggersIndexInitializer.add(outputCount); - startScopedBlock(code); - pr(''' - int triggers_index[] = { «triggersIndexInitializer.join(", ")» }; - ''') - - startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); + + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, true); // Need to reset triggerArray because of new channel and bank names. - val triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»]++]''' + val triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»] + «srcChannel»]''' if (dst.isOutput) { // Include this destination port only if it has at least one @@ -6243,16 +6293,12 @@ class CGenerator extends GeneratorBase { } endScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); multicastCount++; - endScopedBlock(code); } } } - var bankWidth = 1; - if (port.isInput) { - bankWidth = port.parent.width; - } - outputCount += port.width * bankWidth; + cumulativePortWidth += port.width; } + endScopedBlock(code); } } From baf9fc1348e2842b1c69353367585aafb141feb8 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 5 Jan 2022 14:32:12 -0800 Subject: [PATCH 122/221] Do not generate reactor arrays for reactors not in the federate. --- .../org/lflang/generator/c/CGenerator.xtend | 94 ++++++++++--------- 1 file changed, 49 insertions(+), 45 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index d60e819d6a..36ce2affec 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3676,7 +3676,8 @@ class CGenerator extends GeneratorBase { def void generateReactorInstance(ReactorInstance instance) { // FIXME: Consolidate this with generateFederate. The only difference is that // generateFederate is the version of this method that is run on main, the - // top-level reactor. + // top-level reactor. + var reactorClass = instance.definition.reactorClass var fullName = instance.fullName pr(initializeTriggerObjects, @@ -5961,58 +5962,60 @@ class CGenerator extends GeneratorBase { */ private def deferredOptimizeForSingleDominatingReaction (ReactorInstance r) { for (reaction : r.reactions) { - // The following code attempts to gather into a loop assignments of successive - // bank members relations between reactions to avoid large chunks of inline code - // when a large bank sends to a large bank or when a large bank receives from - // one reaction that is either multicasting or sending through a multiport. - var start = 0; - var end = 0; - var domStart = 0; - var same = false; // Set to true when finding a string of identical dominating reactions. - var previousRuntime = null as ReactionInstance.Runtime; - var first = true; //First time through the loop. - for (runtime : reaction.getRuntimeInstances()) { - if (!first) { // Not the first time through the loop. - if (same) { // Previously seen at least two identical dominating. - if (runtime.dominating != previousRuntime.dominating) { - // End of streak of same dominating reaction runtime instance. - printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same - ); - same = false; - start = runtime.id; - domStart = runtime.dominating.id; - } - } else if (runtime.dominating == previousRuntime.dominating) { - // Start of a streak of identical dominating reaction runtime instances. - same = true; - } else if (runtime.dominating.reaction == previousRuntime.dominating.reaction) { - // Same dominating reaction even if not the same dominating runtime. - if (runtime.dominating.id != previousRuntime.dominating.id + 1) { - // End of a streak of contiguous runtimes. + if (currentFederate.contains(reaction.definition)) { + // The following code attempts to gather into a loop assignments of successive + // bank members relations between reactions to avoid large chunks of inline code + // when a large bank sends to a large bank or when a large bank receives from + // one reaction that is either multicasting or sending through a multiport. + var start = 0; + var end = 0; + var domStart = 0; + var same = false; // Set to true when finding a string of identical dominating reactions. + var previousRuntime = null as ReactionInstance.Runtime; + var first = true; //First time through the loop. + for (runtime : reaction.getRuntimeInstances()) { + if (!first) { // Not the first time through the loop. + if (same) { // Previously seen at least two identical dominating. + if (runtime.dominating != previousRuntime.dominating) { + // End of streak of same dominating reaction runtime instance. + printOptimizeForSingleDominatingReaction( + previousRuntime, start, end, domStart, same + ); + same = false; + start = runtime.id; + domStart = runtime.dominating.id; + } + } else if (runtime.dominating == previousRuntime.dominating) { + // Start of a streak of identical dominating reaction runtime instances. + same = true; + } else if (runtime.dominating.reaction == previousRuntime.dominating.reaction) { + // Same dominating reaction even if not the same dominating runtime. + if (runtime.dominating.id != previousRuntime.dominating.id + 1) { + // End of a streak of contiguous runtimes. + printOptimizeForSingleDominatingReaction( + previousRuntime, start, end, domStart, same + ); + same = false; + start = runtime.id; + domStart = runtime.dominating.id; + } + } else { + // Different dominating reaction. printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same + previousRuntime, start, end, domStart, same ); same = false; start = runtime.id; domStart = runtime.dominating.id; } - } else { - // Different dominating reaction. - printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same - ); - same = false; - start = runtime.id; - domStart = runtime.dominating.id; } + first = false; + previousRuntime = runtime; + end++; + } + if (end > start) { + printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); } - first = false; - previousRuntime = runtime; - end++; - } - if (end > start) { - printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); } } } @@ -6308,6 +6311,7 @@ class CGenerator extends GeneratorBase { * @param r The reactor instance. */ private def void generateSelfStructs(ReactorInstance r) { + if (!currentFederate.contains(r)) return; pr(initializeTriggerObjects, ''' «CUtil.selfType(r)»* «CUtil.reactorRefName(r)»[«r.totalWidth»]; ''') From 6cffccb2c35985537c93f0672f9b5a6aa1508abf Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 5 Jan 2022 15:39:09 -0800 Subject: [PATCH 123/221] Fixed bug with dependence graph construction and guard against generating code referring other federates. --- .../src/org/lflang/generator/ReactionInstanceGraph.java | 5 +++-- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 5 ++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index ed6d0bc6f6..b00fd08107 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -111,7 +111,6 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti for (SendRange sendRange : eventualDestinations) { for (RuntimeRange dstRange : sendRange.destinations) { - if (dstRange.instance == sendRange.instance) continue; int dstDepth = (dstRange.instance.isOutput())? 2 : 1; MixedRadixInt dstRangePosition = dstRange.startMR(); @@ -127,7 +126,9 @@ protected void addDownstreamReactions(PortInstance port, ReactionInstance reacti List dstRuntimes = dstReaction.getRuntimeInstances(); Runtime srcRuntime = srcRuntimes.get(srcIndex); Runtime dstRuntime = dstRuntimes.get(dstIndex); - addEdge(dstRuntime, srcRuntime); + if (dstRuntime != srcRuntime) { + addEdge(dstRuntime, srcRuntime); + } // Propagate the deadlines, if any. if (srcRuntime.deadline.compareTo(dstRuntime.deadline) > 0) { diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 36ce2affec..56b0512932 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6031,7 +6031,10 @@ class CGenerator extends GeneratorBase { if (end > start + 1) { startScopedBlock(code); val reactionRef = CUtil.reactionRef(runtime.reaction, "i"); - if (runtime.dominating !== null) { + if (runtime.dominating !== null + && currentFederate.contains(runtime.dominating.getReaction().definition) + && currentFederate.contains(runtime.dominating.getReaction().getParent()) + ) { if (same) { dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; } else { From 01f5f9c8c94aa34cb36754e3e767627ce41f10eb Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 5 Jan 2022 16:35:28 -0800 Subject: [PATCH 124/221] One more place where guard for current federate wasn't quite right. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 56b0512932..608ad02017 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5962,7 +5962,9 @@ class CGenerator extends GeneratorBase { */ private def deferredOptimizeForSingleDominatingReaction (ReactorInstance r) { for (reaction : r.reactions) { - if (currentFederate.contains(reaction.definition)) { + if (currentFederate.contains(reaction.definition) + && currentFederate.contains(reaction.parent) + ) { // The following code attempts to gather into a loop assignments of successive // bank members relations between reactions to avoid large chunks of inline code // when a large bank sends to a large bank or when a large bank receives from @@ -6053,7 +6055,10 @@ class CGenerator extends GeneratorBase { endScopedBlock(code); } else if (end == start + 1) { val reactionRef = CUtil.reactionRef(runtime.reaction, "" + start); - if (runtime.dominating !== null) { + if (runtime.dominating !== null + && currentFederate.contains(runtime.dominating.getReaction().definition) + && currentFederate.contains(runtime.dominating.getReaction().getParent()) + ) { dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; } pr(''' @@ -6469,7 +6474,6 @@ class CGenerator extends GeneratorBase { } } - // FIXME: Get rid of this, if possible. /** The current federate for which we are generating code. */ var currentFederate = null as FederateInstance; From 55dd448ef10c130a2d0fd6242b771bdad0ffa0bd Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 5 Jan 2022 17:35:31 -0800 Subject: [PATCH 125/221] Comments only --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index b662ca24d0..318389fdaa 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -981,12 +981,8 @@ abstract class GeneratorBase extends JavaGeneratorBase { * variables. * * In addition, analyze the connections between federates. - * Ensure that every cycle has a non-zero delay (microstep - * delays will not be sufficient). Construct the dependency - * graph between federates. And replace connections between - * federates with a pair of reactions, one triggered by - * the sender's output port, and the other triggered by - * an action. + * Replace connections between federates with sending and + * receiving reactions. * * This class is target independent, so the target code * generator still has quite a bit of work to do. From 17b6e937e065720e91093697f7ffdd47af6177fc Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 9 Jan 2022 09:56:02 -0800 Subject: [PATCH 126/221] Comments --- .../src/org/lflang/federated/FederateInstance.xtend | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/federated/FederateInstance.xtend b/org.lflang/src/org/lflang/federated/FederateInstance.xtend index 46b8a8e895..3660ff9445 100644 --- a/org.lflang/src/org/lflang/federated/FederateInstance.xtend +++ b/org.lflang/src/org/lflang/federated/FederateInstance.xtend @@ -227,7 +227,7 @@ class FederateInstance { ///////////////////////////////////////////// //// Public Methods - + /** * Return true if the specified action should be included in the code generated * for the federate. This means that either the action is used as a trigger, @@ -285,7 +285,7 @@ class FederateInstance { if (!reactor.federated || isSingleton) return true // If the port is used as a trigger, a source, or an effect for a top-level reaction - // that belongs to this federate, then generate it. + // that belongs to this federate, then return true. for (react : reactor.allReactions) { if (contains(react)) { // Look in triggers @@ -354,13 +354,16 @@ class FederateInstance { } /** - * Return true if the specified reactor instance or any parent + * Return true if the specified runtime reactor instance or any parent * reactor instance is contained by this federate. * If the specified instance is the top-level reactor, return true - * (this reactor belongs to all federates). + * (the top-level reactor belongs to all federates). * If this federate instance is a singleton, then return true if the * instance is non null. * + * NOTE: If the instance is bank within the top level, then this + * returns true even though only one of the bank members is in the federate. + * * @param instance The reactor instance. * @return True if this federate contains the reactor instance */ From d2880a478d0bb1bd61f1df618997b21db07131ba Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 9 Jan 2022 09:56:53 -0800 Subject: [PATCH 127/221] Simplified access to outputs --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 318389fdaa..f8f10f7c68 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -1105,12 +1105,9 @@ abstract class GeneratorBase extends JavaGeneratorBase { // that those contain. val mainInstance = new ReactorInstance(mainReactor, errorReporter, 1) - for (federateReactor : mainInstance.children) { - val federateInstances = federatesByInstantiation.get(federateReactor.definition); - for (federateInstance : federateInstances) { - for (output : federateReactor.outputs) { - replaceConnectionFromFederate(output, federateReactor, mainInstance) - } + for (child : mainInstance.children) { + for (output : child.outputs) { + replaceConnectionFromFederate(output, child, mainInstance) } } } From 5bb54dec05882681e3742bce5c01279bf264af06 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 9 Jan 2022 09:57:38 -0800 Subject: [PATCH 128/221] Pad digits when asked and provide numDigits function --- .../src/org/lflang/generator/MixedRadixInt.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index 52a0c25055..9fd9fc7470 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -133,9 +133,13 @@ public int get(int n) { } /** - * Return the digits. + * Return the digits. This is assured of returning as many + * digits as there are radixes. */ public List getDigits() { + while (digits.size() < radixes.size()) { + digits.add(0); + } return digits; } @@ -200,6 +204,14 @@ public int magnitude() { return result; } + /** + * Return the number of digits in this mixed radix number. + * This is the size of the radixes list. + */ + public int numDigits() { + return radixes.size(); + } + /** * Set the value of this number to equal that of the specified integer. * @param v The ordinary integer value of this number. @@ -233,7 +245,7 @@ public void setMagnitude(int v) { temp = temp / radixes.get(p); } } - + /** * Give a string representation of the number, where each digit is * represented as n%r, where r is the radix. From 3335727804eb8d41a7b33e3627ca2651afedf0be Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 9 Jan 2022 09:58:13 -0800 Subject: [PATCH 129/221] Added support to get total width within a federate --- .../org/lflang/generator/ReactorInstance.java | 30 ++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 4063b78543..4e7dcb6195 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -323,16 +323,32 @@ public int getTotalNumReactorInstances() { * Return -1 if the width cannot be determined. */ public int getTotalWidth() { + return getTotalWidth(0); + } + + /** + * If this reactor is a bank or any of its parents is a bank, + * return the total number of runtime instances, which is the product + * of the widths of all the parents. + * Return -1 if the width cannot be determined. + * @param atDepth The depth at which to determine the width. + * Use 0 to get the total number of instances. + * Use 1 to get the number of instances within a single top-level + * bank member (this is useful for federates). + */ + public int getTotalWidth(int atDepth) { if (width <= 0) return -1; - if (depth > 0) { - int parentWidth = parent.getTotalWidth(); - if (parentWidth <= 0) return -1; - return (parentWidth * width); - } else { - return 1; + if (depth <= atDepth) return 1; + int result = width; + ReactorInstance p = parent; + while (parent != null && parent.depth > atDepth) { + if (parent.width <= 0) return -1; + result *= parent.width; + p = p.parent; } + return result; } - + /** * Return the trigger instances (input ports, timers, and actions * that trigger reactions) belonging to this reactor instance. From c593bb7bfe27f9142ca0e2a8551ae8ae2cff1301 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 9 Jan 2022 09:58:41 -0800 Subject: [PATCH 130/221] Removed redundant scoped block --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 810c36176a..dddcaf9867 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1651,9 +1651,7 @@ class PythonGenerator extends CGenerator { ) { return } - - startScopedBlock(initializeTriggerObjects, instance); - + // Initialize the name field to the unique name of the instance pr(initializeTriggerObjects, ''' «nameOfSelfStruct»->_lf_name = "«instance.uniqueID»_lf"; @@ -1680,7 +1678,6 @@ class PythonGenerator extends CGenerator { ''') } } - endScopedBlock(initializeTriggerObjects); } From f4d8611db6d663a279f4759250f7426c69073472 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 9 Jan 2022 15:59:20 -0800 Subject: [PATCH 131/221] Fixed NPE in getTotalWidth() --- org.lflang/src/org/lflang/generator/ReactorInstance.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 4e7dcb6195..e2cf8b4d77 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -341,9 +341,9 @@ public int getTotalWidth(int atDepth) { if (depth <= atDepth) return 1; int result = width; ReactorInstance p = parent; - while (parent != null && parent.depth > atDepth) { - if (parent.width <= 0) return -1; - result *= parent.width; + while (p != null && p.depth > atDepth) { + if (p.width <= 0) return -1; + result *= p.width; p = p.parent; } return result; From cb85765b8b30d780ef434d05b91002947756a283 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 10 Jan 2022 10:22:54 -0800 Subject: [PATCH 132/221] Added numRuntimeInstances function --- .../org/lflang/federated/FederateInstance.xtend | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/federated/FederateInstance.xtend b/org.lflang/src/org/lflang/federated/FederateInstance.xtend index 3660ff9445..2fcdb3a78e 100644 --- a/org.lflang/src/org/lflang/federated/FederateInstance.xtend +++ b/org.lflang/src/org/lflang/federated/FederateInstance.xtend @@ -354,7 +354,7 @@ class FederateInstance { } /** - * Return true if the specified runtime reactor instance or any parent + * Return true if the specified reactor instance or any parent * reactor instance is contained by this federate. * If the specified instance is the top-level reactor, return true * (the top-level reactor belongs to all federates). @@ -415,6 +415,20 @@ class FederateInstance { return false; } + + /** + * Return the total number of runtime instances of the specified reactor + * instance in this federate. This is zero if the reactor is not in the + * federate at all, and otherwise is the product of the bank widths of + * all the parent containers of the instance, except that if the depth + * one parent is bank, its width is ignored (only one bank member can be + * in any federate). + */ + def numRuntimeInstances(ReactorInstance reactor) { + if (!contains(reactor)) return 0; + val depth = isSingleton ? 0 : 1; + return reactor.getTotalWidth(depth); + } /** * Build an index of reactions at the top-level (in the From 65f49618bca0f98bcd7cdef63490fe3c68215d9c Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 10 Jan 2022 10:25:05 -0800 Subject: [PATCH 133/221] First pass on getting some federated tests to run --- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/c/CGenerator.xtend | 406 ++++++++++++------ 2 files changed, 269 insertions(+), 139 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index b11ffdf9cd..9390df081e 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit b11ffdf9cdf009ca70dddbdb3288be5bb7ba6eef +Subproject commit 9390df081e0f442b098d0b34469a5dae89fc9308 diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 608ad02017..97f1999649 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -501,6 +501,9 @@ class CGenerator extends GeneratorBase { // Avoid compile errors by removing disconnected network ports. // This must be done after assigning levels. removeRemoteFederateConnectionPorts(main); + // Force reconstruction of dependence information. + // FIXME: Probably only need to do this for federated execution. + this.main.clearCaches(); } } @@ -1006,7 +1009,7 @@ class CGenerator extends GeneratorBase { // Allocate the initial (before mutations) array of pointers to tokens. pr(''' _lf_tokens_with_ref_count_size = «startTimeStepTokens»; - _lf_tokens_with_ref_count = (token_present_t*)malloc(«startTimeStepTokens» * sizeof(token_present_t)); + _lf_tokens_with_ref_count = (token_present_t*)calloc(«startTimeStepTokens», sizeof(token_present_t)); ''') } // Create the table to initialize is_present fields to false between time steps. @@ -1015,8 +1018,8 @@ class CGenerator extends GeneratorBase { pr(''' // Create the array that will contain pointers to is_present fields to reset on each step. _lf_is_present_fields_size = «startTimeStepIsPresentCount»; - _lf_is_present_fields = (bool**)malloc(«startTimeStepIsPresentCount» * sizeof(bool*)); - _lf_is_present_fields_abbreviated = (bool**)malloc(«startTimeStepIsPresentCount» * sizeof(bool*)); + _lf_is_present_fields = (bool**)calloc(«startTimeStepIsPresentCount», sizeof(bool*)); + _lf_is_present_fields_abbreviated = (bool**)calloc(«startTimeStepIsPresentCount», sizeof(bool*)); _lf_is_present_fields_abbreviated_size = 0; ''') } @@ -2743,7 +2746,7 @@ class CGenerator extends GeneratorBase { var foundOne = false; val reactionRef = CUtil.reactionRef(reaction) - + // Next handle triggers of the reaction that come from a multiport output // of a contained reactor. Also, handle startup and shutdown triggers. for (trigger : reaction.triggers) { @@ -2751,14 +2754,14 @@ class CGenerator extends GeneratorBase { pr(temp, ''' _lf_startup_reactions[_lf_startup_reactions_count++] = &«reactionRef»; ''') - startupReactionCount += reactor.getTotalWidth(); + startupReactionCount += currentFederate.numRuntimeInstances(reactor); foundOne = true; } else if (trigger.isShutdown) { pr(temp, ''' _lf_shutdown_reactions[_lf_shutdown_reactions_count++] = &«reactionRef»; ''') foundOne = true; - shutdownReactionCount += reactor.getTotalWidth(); + shutdownReactionCount += currentFederate.numRuntimeInstances(reactor); if (targetConfig.tracing !== null) { val description = getShortenedName(reactor) @@ -3187,7 +3190,7 @@ class CGenerator extends GeneratorBase { var foundOne = false; val temp = new StringBuilder(); - startScopedBlock(temp, child); + startScopedBlock(temp, child, true); for (input : child.inputs) { if (isTokenType((input.definition as Input).inferredType)) { @@ -3196,24 +3199,23 @@ class CGenerator extends GeneratorBase { if (input.isMultiport()) { pr(temp, ''' for (int i = 0; i < «input.width»; i++) { - _lf_tokens_with_ref_count[«startTimeStepTokens» + i].token + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].token = &«portRef»[i]->token; - _lf_tokens_with_ref_count[«startTimeStepTokens» + i].status + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].status = (port_status_t*)&«portRef»[i]->is_present; - _lf_tokens_with_ref_count[«startTimeStepTokens» + i].reset_is_present = false; + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count++].reset_is_present = false; } ''') - startTimeStepTokens += input.width } else { pr(temp, ''' - _lf_tokens_with_ref_count[«startTimeStepTokens»].token + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].token = &«portRef»->token; - _lf_tokens_with_ref_count[«startTimeStepTokens»].status + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].status = (port_status_t*)&«portRef»->is_present; - _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = false; + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count++].reset_is_present = false; ''') - startTimeStepTokens++ } + startTimeStepTokens += currentFederate.numRuntimeInstances(input.parent) * input.width; } } endScopedBlock(temp); @@ -3241,6 +3243,8 @@ class CGenerator extends GeneratorBase { portsSeen.add(port) // This reaction is sending to an input. Must be // the input of a contained reactor in the federate. + // NOTE: If instance == main and the federate is within a bank, + // this assumes that the reaction writes only to the bank member in the federate. if (currentFederate.contains(port.parent)) { foundOne = true; @@ -3253,7 +3257,7 @@ class CGenerator extends GeneratorBase { // iterate over the instance bank members. startScopedBlock(temp); pr(temp, "int count = 0;"); - startScopedBlock(temp, instance); + startScopedBlock(temp, instance, true); startScopedBankChannelIteration(temp, port, null); } else { startScopedBankChannelIteration(temp, port, "count"); @@ -3271,14 +3275,14 @@ class CGenerator extends GeneratorBase { ''') } + startTimeStepIsPresentCount += port.width * currentFederate.numRuntimeInstances(port.parent); + if (port.parent != instance) { - startTimeStepIsPresentCount += port.width * port.parent.width * instance.width; pr(temp, "count++;"); endScopedBlock(temp); endScopedBlock(temp); endScopedBankChannelIteration(temp, port, null); } else { - startTimeStepIsPresentCount += port.width * port.parent.width; endScopedBankChannelIteration(temp, port, "count"); } } @@ -3300,17 +3304,17 @@ class CGenerator extends GeneratorBase { // Potentially have to iterate over bank members of the instance // (parent of the reaction), bank members of the contained reactor (if a bank), // and channels of the multiport (if multiport). - startScopedBlock(temp, instance); + startScopedBlock(temp, instance, true); startScopedBankChannelIteration(temp, port, "count"); val portRef = CUtil.portRef(port, true, true, null, null); pr(temp, ''' - _lf_tokens_with_ref_count[«startTimeStepTokens» + count].token = &«portRef»->token; - _lf_tokens_with_ref_count[«startTimeStepTokens» + count].status = (port_status_t*)&«portRef»->is_present; - _lf_tokens_with_ref_count[«startTimeStepTokens» + count].reset_is_present = false; + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].token = &«portRef»->token; + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].status = (port_status_t*)&«portRef»->is_present; + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count++].reset_is_present = false; ''') - startTimeStepTokens += port.width * port.parent.width; + startTimeStepTokens += port.width * currentFederate.numRuntimeInstances(port.parent); endScopedBankChannelIteration(temp, port, "count"); endScopedBlock(temp); @@ -3326,7 +3330,7 @@ class CGenerator extends GeneratorBase { for (action : instance.actions) { if (currentFederate === null || currentFederate.contains(action.definition)) { foundOne = true; - startScopedBlock(temp, instance); + startScopedBlock(temp, instance, true); pr(temp, ''' // Add action «action.getFullName» to array of is_present fields. @@ -3341,7 +3345,7 @@ class CGenerator extends GeneratorBase { = &«containerSelfStructName»->_lf_«action.name».intended_tag; ''') } - startTimeStepIsPresentCount++ + startTimeStepIsPresentCount += currentFederate.numRuntimeInstances(action.parent); endScopedBlock(temp); } } @@ -3355,7 +3359,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(temp); pr(temp, '''int count = 0;'''); - startScopedBlock(temp, child); + startScopedBlock(temp, child, true); var channelCount = 0; for (output : child.outputs) { @@ -3384,7 +3388,7 @@ class CGenerator extends GeneratorBase { endChannelIteration(temp, output); } } - startTimeStepIsPresentCount += channelCount * child.width; + startTimeStepIsPresentCount += channelCount * currentFederate.numRuntimeInstances(child); endScopedBlock(temp); endScopedBlock(temp); } @@ -3414,7 +3418,7 @@ class CGenerator extends GeneratorBase { «ENDIF» ''') } - triggerCount += action.parent.width; + triggerCount += currentFederate.numRuntimeInstances(action.parent); } } @@ -3439,9 +3443,9 @@ class CGenerator extends GeneratorBase { «triggerStructName».period = «period»; _lf_timer_triggers[_lf_timer_triggers_count++] = &«triggerStructName»; ''') - timerCount += timer.parent.totalWidth; + timerCount += currentFederate.numRuntimeInstances(timer.parent); } - triggerCount += timer.parent.totalWidth; + triggerCount += currentFederate.numRuntimeInstances(timer.parent); } } @@ -3606,7 +3610,8 @@ class CGenerator extends GeneratorBase { } /** - * Generate code to instantiate the main reactor in the specified federate. + * Generate code to instantiate the main reactor and any contained reactors + * that are in the current federate. */ private def void generateMain() { @@ -3647,10 +3652,13 @@ class CGenerator extends GeneratorBase { int _lf_startup_reactions_count = 0; int _lf_shutdown_reactions_count = 0; int _lf_timer_triggers_count = 0; + int _lf_tokens_with_ref_count_count = 0; '''); for (child: main.children) { if (currentFederate.contains(child)) { + // NOTE: child could be a bank, in which case, for federated + // systems, only one of the bank members will be part of the federate. generateReactorInstance(child); } } @@ -3674,8 +3682,8 @@ class CGenerator extends GeneratorBase { * contained reactors or null if there are no federates. */ def void generateReactorInstance(ReactorInstance instance) { - // FIXME: Consolidate this with generateFederate. The only difference is that - // generateFederate is the version of this method that is run on main, the + // FIXME: Consolidate this with generateMain. The only difference is that + // generateMain is the version of this method that is run on main, the // top-level reactor. var reactorClass = instance.definition.reactorClass @@ -3686,8 +3694,8 @@ class CGenerator extends GeneratorBase { // If this reactor is a placeholder for a bank of reactors, then generate // an array of instances of reactors and create an enclosing for loop. // Need to do this for each of the builders into which the code writes. - startScopedBlock(startTimeStep, instance); - startScopedBlock(initializeTriggerObjects, instance); + startScopedBlock(startTimeStep, instance, true); + startScopedBlock(initializeTriggerObjects, instance, true); // Generate the instance self struct containing parameters, state variables, // and outputs (the "self" struct). @@ -3807,13 +3815,13 @@ class CGenerator extends GeneratorBase { // of each action's trigger object to false and free a previously // allocated token if appropriate. This code sets up the table that does that. pr(initializeTriggerObjects, ''' - _lf_tokens_with_ref_count[«startTimeStepTokens»].token + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].token = &«selfStruct»->_lf__«action.name».token; - _lf_tokens_with_ref_count[«startTimeStepTokens»].status + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].status = &«selfStruct»->_lf__«action.name».status; - _lf_tokens_with_ref_count[«startTimeStepTokens»].reset_is_present = true; + _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count++].reset_is_present = true; ''') - startTimeStepTokens++ + startTimeStepTokens += currentFederate.numRuntimeInstances(action.parent); } } } @@ -3933,7 +3941,7 @@ class CGenerator extends GeneratorBase { «portRefName»_width = «output.width»; // Allocate memory for multiport output. «portRefName» = («portStructType»*)calloc(«output.width», sizeof(«portStructType»)); - «portRefName»_pointers = («portStructType»**)malloc(sizeof(«portStructType»*) * «output.width»); + «portRefName»_pointers = («portStructType»**)calloc(«output.width», sizeof(«portStructType»*)); // Assign each output port pointer to be used in reactions to facilitate user access to output ports for(int i=0; i < «output.width»; i++) { «portRefName»_pointers[i] = &(«portRefName»[i]); @@ -3960,7 +3968,7 @@ class CGenerator extends GeneratorBase { pr(initializeTriggerObjects, ''' «portRefName»_width = «input.width»; // Allocate memory for multiport inputs. - «portRefName» = («variableStructType(input)»**)malloc(sizeof(«variableStructType(input)»*) * «input.width»); + «portRefName» = («variableStructType(input)»**)calloc(«input.width», sizeof(«variableStructType(input)»*)); // Set inputs by default to an always absent default input. for (int i = 0; i < «input.width»; i++) { «portRefName»[i] = &«CUtil.reactorRef(reactor)»->_lf_default__«input.name»; @@ -4055,7 +4063,7 @@ class CGenerator extends GeneratorBase { val temp = new StringBuilder(); pr(temp, "// Set reaction priorities for " + reactor.toString()); - startScopedBlock(temp, reactor); + startScopedBlock(temp, reactor, true); for (r : reactor.reactions) { if (currentFederate.contains(r.definition)) { @@ -4818,19 +4826,29 @@ class CGenerator extends GeneratorBase { * * @param builder The string builder into which to write. * @param reactor The reactor instance. + * @param restrict For federated execution only, if this is true, then + * skip iterations where the topmost bank member is not in the federate. */ - protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor) { + protected def void startScopedBlock(StringBuilder builder, ReactorInstance reactor, boolean restrict) { // NOTE: This is protected because it is used by the PythonGenerator. if (reactor !== null && reactor.isBank) { - val index = CUtil.bankIndexName(reactor); - pr(builder, ''' - // Reactor is a bank. Iterate over bank members. - for (int «index» = 0; «index» < «reactor.width»; «index»++) { - ''') + val index = CUtil.bankIndexName(reactor); + if (reactor.depth == 1 && isFederated && restrict) { + // Special case: A bank of federates. Instantiate only the current federate. + startScopedBlock(builder); + pr(builder, ''' + int «index» = «currentFederate.bankIndex»; + ''') + } else { + pr(builder, ''' + // Reactor is a bank. Iterate over bank members. + for (int «index» = 0; «index» < «reactor.width»; «index»++) { + ''') + indent(builder); + } } else { - pr(builder, "{"); + startScopedBlock(builder); } - indent(builder); } /** @@ -4867,7 +4885,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(builder); pr(builder, '''int «count» = 0;'''); } - startScopedBlock(builder, port.parent); + startScopedBlock(builder, port.parent, true); startChannelIteration(builder, port); } @@ -4894,7 +4912,7 @@ class CGenerator extends GeneratorBase { } /** - * Start a scoped block that iterates over the specified range. + * Start a scoped block that iterates over the specified range of port channels. * * This must be followed by a call to * {@link endScopedRangeBlock(StringBuilder, RuntimeRange)}. @@ -4911,20 +4929,25 @@ class CGenerator extends GeneratorBase { * @param runtimeIndex The name of variable whose value specifies * which runtime instance of the ReactorInstance of the range * is being referred to. + * @param nested If true, then the runtimeIndex variable will be set + * to the bank index of the port's parent's parent rather than the + * port's parent. + * @param restrict For federated execution (only), if this argument + * is true, then the iteration will skip over bank members that + * are not in the current federate. */ private def void startScopedRangeBlock( - StringBuilder builder, RuntimeRange range, String runtimeIndex + StringBuilder builder, RuntimeRange range, String runtimeIndex, boolean nested, boolean restrict ) { pr(builder, ''' // Iterate over range «range.toString()». ''') - - startScopedBlock(builder); - + val ci = CUtil.channelIndexName(range.instance); val rangeMR = range.startMR(); + startScopedBlock(builder); if (range.width > 1) { pr(builder, ''' int range_start[] = { «rangeMR.getDigits().join(", ")» }; @@ -4943,10 +4966,36 @@ class CGenerator extends GeneratorBase { int «ci» = range_mr.digits[0]; // Channel index. int «runtimeIndex» = mixed_radix_parent(&range_mr, 1); ''') + if (isFederated) { + if (restrict) { + // In case we have a bank of federates. Need that iteration + // only cover the one federate. The last digit of the mixed-radix + // number is the bank index (or 0 if this is not a bank of federates). + pr(builder, ''' + if (range_mr.digits[range_mr.size - 1] == «currentFederate.bankIndex») { + ''') + indent(builder); + } else { + startScopedBlock(builder); + } + } } else { val mr = range.startMR(); val ciValue = mr.getDigits().get(0); val biValue = mr.get(1); + if (isFederated) { + if (restrict) { + // Special case. Have a bank of federates. Need that iteration + // only cover the one federate. The last digit of the mixed-radix + // number identifies the bank member (or is 0 if not within a bank). + pr(builder, ''' + if («mr.get(mr.numDigits() - 1)» == «currentFederate.bankIndex») { + ''') + indent(builder); + } else { + startScopedBlock(builder); + } + } pr(builder, ''' int «ci» = «ciValue»; // Channel index. int «runtimeIndex» = «biValue»; // Bank(s) identifier. @@ -4961,10 +5010,13 @@ class CGenerator extends GeneratorBase { * @param range The send range. */ private def void endScopedRangeBlock(StringBuilder builder, RuntimeRange range) { + if (isFederated) { + // Terminate the if statement or block (if not restrict). + endScopedBlock(builder); + } if (range.width > 1) { pr(builder, "mixed_radix_incr(&range_mr);"); - unindent(builder); - pr(builder, "}"); + endScopedBlock(builder); // Terminate for loop. } endScopedBlock(builder); } @@ -5009,61 +5061,66 @@ class CGenerator extends GeneratorBase { String srcRuntimeIndex, String srcChannelIndex, String dstRuntimeIndex, - boolean nested + boolean xxxsrcNested, + boolean xxxdstNested ) { + val srcRangeMR = srcRange.startMR(); pr(builder, ''' // Iterate over ranges «srcRange.toString» and «dstRange.toString». ''') - - startScopedBlock(builder); - - val rangeMR = srcRange.startMR(); + + if (isFederated && srcRange.width == 1) { + // Skip this whole block if the src is not in the federate. + pr(builder, ''' + if («srcRangeMR.get(srcRangeMR.numDigits() - 1)» == «currentFederate.bankIndex») { + ''') + indent(builder); + } else { + startScopedBlock(builder); + } if (srcRange.width > 1) { pr(builder, ''' - int src_start[] = { «rangeMR.getDigits().join(", ")» }; - int src_value[] = { «rangeMR.getDigits().join(", ")» }; // Will be incremented. - int src_radixes[] = { «rangeMR.getRadixes().join(", ")» }; + int src_start[] = { «srcRangeMR.getDigits().join(", ")» }; + int src_value[] = { «srcRangeMR.getDigits().join(", ")» }; // Will be incremented. + int src_radixes[] = { «srcRangeMR.getRadixes().join(", ")» }; int src_permutation[] = { «srcRange.permutation().join(", ")» }; mixed_radix_int_t src_range_mr = { - «rangeMR.getDigits().size()», + «srcRangeMR.getDigits().size()», src_value, src_radixes, src_permutation }; '''); } else { - val mr = srcRange.startMR(); - val ciValue = mr.getDigits().get(0); - val biValue = mr.get(1); + val ciValue = srcRangeMR.getDigits().get(0); + val biValue = srcRangeMR.get(1); pr(builder, ''' int «srcChannelIndex» = «ciValue»; // Channel index. int «srcRuntimeIndex» = «biValue»; // Bank(s) identifier. ''') } - - startScopedRangeBlock(builder, dstRange, dstRuntimeIndex); + + startScopedRangeBlock(builder, dstRange, dstRuntimeIndex, false, true); if (srcRange.width > 1) { - // If srcRange and dstRange refer to the same instance, - // and if that instance is an input, then the situation we have that - // of a reaction driving an input port of a contained reactor. - // In this case, the source bank should be the bank of the parent's parent, - // and the channel index should be the product of the two low-order digits - // of the mixed-radix number. - if (nested && srcRange.instance == dstRange.instance && srcRange.instance.isInput()) { - pr(builder, ''' - int «srcChannelIndex» = src_range_mr.digits[0] - + src_range_mr.digits[1] * src_range_mr.radixes[0]; // Channel index. - int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 2); - ''') - } else { - pr(builder, ''' - int «srcChannelIndex» = src_range_mr.digits[0]; // Channel index. - int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 1); - ''') - } + pr(builder, ''' + int «srcChannelIndex» = src_range_mr.digits[0]; // Channel index. + int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 1); + ''') + } + + // The above startScopedRangeBlock() call will skip any iteration where the destination + // is a bank member is not in the federation. Here, we skip any iteration where the + // source is a bank member not in the federation. + if (isFederated && srcRange.width > 1) { + // The last digit of the mixed radix + // number identifies the bank (or is 0 if no bank). + pr(builder, ''' + if (src_range_mr.digits[src_range_mr.size - 1] == «currentFederate.bankIndex») { + ''') + indent(builder); } } @@ -5085,6 +5142,15 @@ class CGenerator extends GeneratorBase { String srcChannelIndex, String dstRuntimeIndex ) { + // Do not use endScopedRangeBlock because we need things nested. + if (isFederated) { + if (srcRange.width > 1) { + // Terminate the if statement. + endScopedBlock(builder); + } + // Terminate the if statement or block (if not restrict). + endScopedBlock(builder); + } if (srcRange.width > 1) { pr(builder, ''' mixed_radix_incr(&src_range_mr); @@ -5096,7 +5162,12 @@ class CGenerator extends GeneratorBase { } '''); } - endScopedRangeBlock(builder, dstRange); + if (dstRange.width > 1) { + pr(builder, "mixed_radix_incr(&range_mr);"); + endScopedBlock(builder); // Terminate for loop. + } + // Terminate unconditional scope block in startScopedRangeBlock calls. + endScopedBlock(builder); endScopedBlock(builder); } @@ -5115,6 +5186,11 @@ class CGenerator extends GeneratorBase { for (dstRange : srcRange.destinations) { val dst = dstRange.instance; val destStructType = variableStructType(dst) + + // NOTE: For federated execution, dst.parent should always be contained + // by the currentFederate because an AST transformation removes connections + // between ports of distinct federates. So the following check is not + // really necessary. if (currentFederate.contains(dst.parent)) { val mod = (dst.isMultiport)? "" : "&"; @@ -5126,7 +5202,9 @@ class CGenerator extends GeneratorBase { val srcBank = "src_bank"; val dstBank = "dst_bank"; - startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, false); + val srcNested = (srcRange.instance.isInput)? true : false; + val dstNested = (dstRange.instance.isOutput)? true : false; + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, srcNested, dstNested); if (src.isInput) { // Source port is written to by reaction in port's parent's parent @@ -5706,7 +5784,7 @@ class CGenerator extends GeneratorBase { // First batch of initializations is within a for loop iterating // over bank members for the reactor's parent. - startScopedBlock(code, reactor); + startScopedBlock(code, reactor, true); // If the child has a multiport that is an effect of some reaction in its container, // then we have to generate code to allocate memory for arrays pointing to @@ -5722,7 +5800,9 @@ class CGenerator extends GeneratorBase { deferredCreateDefaultTokens(reactor); for (child: reactor.children) { - deferredInitialize(child, child.reactions); + if (currentFederate.contains(child)) { + deferredInitialize(child, child.reactions); + } } endScopedBlock(code) @@ -5743,10 +5823,6 @@ class CGenerator extends GeneratorBase { private def void deferredInitializeNonNested( ReactorInstance reactor, Iterable reactions ) { - if (!currentFederate.contains(reactor)) { - return; - } - pr('''// **** Start non-nested deferred initialize for «reactor.getFullName()»''') // Initialize the num_destinations fields of port structs on the self struct. @@ -5763,7 +5839,9 @@ class CGenerator extends GeneratorBase { deferredOptimizeForSingleDominatingReaction(reactor); for (child: reactor.children) { - deferredInitializeNonNested(child, child.reactions); + if (currentFederate.contains(child)) { + deferredInitializeNonNested(child, child.reactions); + } } pr('''// **** End of non-nested deferred initialize for «reactor.getFullName()»''') @@ -5844,10 +5922,10 @@ class CGenerator extends GeneratorBase { for (sendingRange : output.eventualDestinations) { pr("// For reference counting, set num_destinations for port " + output.fullName + "."); - startScopedRangeBlock(code, sendingRange, "runtime_index"); + startScopedRangeBlock(code, sendingRange, "runtime_index", false, true); pr(''' - «CUtil.portRef(output, "runtime_index", null)».num_destinations += «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRef(output, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') endScopedRangeBlock(code, sendingRange); @@ -5888,16 +5966,16 @@ class CGenerator extends GeneratorBase { // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { - startScopedRangeBlock(code, sendingRange, "runtime_index"); + startScopedRangeBlock(code, sendingRange, "runtime_index", false, true); // Syntax is slightly different for a multiport output vs. single port. if (port.isMultiport()) { pr(''' - «CUtil.portRefNested(port, "runtime_index", null)»->num_destinations += «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "runtime_index", null)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } else { pr(''' - «CUtil.portRefNested(port, "runtime_index", null)».num_destinations += «sendingRange.getNumberOfDestinationReactors»; + «CUtil.portRefNested(port, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; ''') } endScopedRangeBlock(code, sendingRange); @@ -5933,14 +6011,14 @@ class CGenerator extends GeneratorBase { val portStructType = variableStructType(effect) - startScopedBlock(code, effect.parent); + startScopedBlock(code, effect.parent, true); val effectRef = CUtil.portRefNestedName(effect); pr(''' «effectRef»_width = «effect.width»; // Allocate memory to store output of reaction feeding a multiport input of a contained reactor. - «effectRef» = («portStructType»**)malloc(sizeof(«portStructType»*) * «effect.width»); + «effectRef» = («portStructType»**)calloc(«effect.width», sizeof(«portStructType»*)); for (int i = 0; i < «effect.width»; i++) { «effectRef»[i] = («portStructType»*)calloc(1, sizeof(«portStructType»)); } @@ -5965,6 +6043,19 @@ class CGenerator extends GeneratorBase { if (currentFederate.contains(reaction.definition) && currentFederate.contains(reaction.parent) ) { + + // For federated systems, the above test may not be enough if there is a bank + // of federates. Calculate the divisor needed to compute the federate bank + // index from the instance index of the reaction. + var divisor = 1; // Value 1 will indicate nothing needs to be done. + if (isFederated) { + var parent = reaction.parent; + while (parent !== null) { + divisor *= parent.width; + parent = parent.parent; + } + } + // The following code attempts to gather into a loop assignments of successive // bank members relations between reactions to avoid large chunks of inline code // when a large bank sends to a large bank or when a large bank receives from @@ -5981,7 +6072,7 @@ class CGenerator extends GeneratorBase { if (runtime.dominating != previousRuntime.dominating) { // End of streak of same dominating reaction runtime instance. printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same + previousRuntime, start, end, domStart, same, divisor ); same = false; start = runtime.id; @@ -5995,7 +6086,7 @@ class CGenerator extends GeneratorBase { if (runtime.dominating.id != previousRuntime.dominating.id + 1) { // End of a streak of contiguous runtimes. printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same + previousRuntime, start, end, domStart, same, divisor ); same = false; start = runtime.id; @@ -6004,7 +6095,7 @@ class CGenerator extends GeneratorBase { } else { // Different dominating reaction. printOptimizeForSingleDominatingReaction( - previousRuntime, start, end, domStart, same + previousRuntime, start, end, domStart, same, divisor ); same = false; start = runtime.id; @@ -6016,7 +6107,7 @@ class CGenerator extends GeneratorBase { end++; } if (end > start) { - printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same); + printOptimizeForSingleDominatingReaction(previousRuntime, start, end, domStart, same, divisor); } } } @@ -6026,29 +6117,49 @@ class CGenerator extends GeneratorBase { * Print statement that sets the last_enabling_reaction field of a reaction. */ private def printOptimizeForSingleDominatingReaction( - ReactionInstance.Runtime runtime, int start, int end, int domStart, boolean same + ReactionInstance.Runtime runtime, int start, int end, int domStart, boolean same, int divisor ) { - var dominatingRef = "NULL"; + var domDivisor = 1; + if (isFederated && runtime.dominating !== null) { + val domReaction = runtime.dominating.getReaction(); + // No need to do anything if the dominating reaction is not in the federate. + // Note that this test is imperfect because the current federate may be a + // bank member. + if (!currentFederate.contains(domReaction.definition) + || !currentFederate.contains(domReaction.getParent())) { + return; + } + // To really know whether the dominating reaction is in the federate, + // we need to calculate a divisor for its runtime index. + var parent = runtime.dominating.getReaction().parent; + while (parent !== null) { + domDivisor *= parent.width; + parent = parent.parent; + } + } + var dominatingRef = "NULL"; + if (end > start + 1) { startScopedBlock(code); val reactionRef = CUtil.reactionRef(runtime.reaction, "i"); - if (runtime.dominating !== null - && currentFederate.contains(runtime.dominating.getReaction().definition) - && currentFederate.contains(runtime.dominating.getReaction().getParent()) - ) { + if (runtime.dominating !== null) { if (same) { dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; } else { dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "j++") + ")"; - pr(''' - int j = «domStart»; - ''') } } pr(''' // «runtime.reaction.getFullName» dominating upstream reaction. + int j = «domStart»; for (int i = «start»; i < «end»; i++) { + «IF divisor > 1» + if (i / «divisor» != «currentFederate.bankIndex») continue; // Reaction is not in the federate. + «ENDIF» + «IF domDivisor > 1» + if (j / «domDivisor» != «currentFederate.bankIndex») continue; // Dominating reaction is not in the federate. + «ENDIF» «reactionRef».last_enabling_reaction = «dominatingRef»; } ''') @@ -6056,15 +6167,16 @@ class CGenerator extends GeneratorBase { } else if (end == start + 1) { val reactionRef = CUtil.reactionRef(runtime.reaction, "" + start); if (runtime.dominating !== null - && currentFederate.contains(runtime.dominating.getReaction().definition) - && currentFederate.contains(runtime.dominating.getReaction().getParent()) + && (domDivisor == 1 || domStart/domDivisor == currentFederate.bankIndex) ) { dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; } - pr(''' - // «runtime.reaction.getFullName» dominating upstream reaction. - «reactionRef».last_enabling_reaction = «dominatingRef»; - ''') + if (divisor == 1 || start/divisor == currentFederate.bankIndex) { + pr(''' + // «runtime.reaction.getFullName» dominating upstream reaction. + «reactionRef».last_enabling_reaction = «dominatingRef»; + ''') + } } } @@ -6090,15 +6202,15 @@ class CGenerator extends GeneratorBase { // Allocate memory to store pointers to the multiport output «trigger.name» // of a contained reactor «trigger.parent.getFullName» ''') - startScopedBlock(code, trigger.parent); + startScopedBlock(code, trigger.parent, true); val width = trigger.width; val portStructType = variableStructType(trigger) pr(''' «CUtil.reactorRefNested(trigger.parent)».«trigger.name»_width = «width»; - «CUtil.reactorRefNested(trigger.parent)».«trigger.name» = («portStructType»**)malloc( - sizeof(«portStructType»*) * «width»); + «CUtil.reactorRefNested(trigger.parent)».«trigger.name» = («portStructType»**)calloc( + «width», sizeof(«portStructType»*)); ''') endScopedBlock(code); @@ -6150,7 +6262,7 @@ class CGenerator extends GeneratorBase { if (effect.isInput) { pr(init, "// Reaction writes to an input of a contained reactor.") bankWidth = effect.parent.width; - startScopedBlock(init, effect.parent); + startScopedBlock(init, effect.parent, true); portRef = CUtil.portRefNestedName(effect); } else { startScopedBlock(init); @@ -6191,9 +6303,9 @@ class CGenerator extends GeneratorBase { pr(''' // Allocate memory for triggers[] and triggered_sizes[] on the reaction_t // struct for this reaction. - «CUtil.reactionRef(reaction)».triggers = (trigger_t***)malloc(«outputCount» * sizeof(trigger_t**)); - «CUtil.reactionRef(reaction)».triggered_sizes = (int*)malloc(«outputCount» * sizeof(int)); - «CUtil.reactionRef(reaction)».output_produced = (bool**)malloc(«outputCount» * sizeof(bool*)); + «CUtil.reactionRef(reaction)».triggers = (trigger_t***)calloc(«outputCount», sizeof(trigger_t**)); + «CUtil.reactionRef(reaction)».triggered_sizes = (int*)calloc(«outputCount», sizeof(int)); + «CUtil.reactionRef(reaction)».output_produced = (bool**)calloc(«outputCount», sizeof(bool*)); ''') } @@ -6227,12 +6339,14 @@ class CGenerator extends GeneratorBase { // so that we can simultaneously calculate the size of the total array. val bi = "runtime_index"; for (SendRange srcRange : port.eventualDestinations()) { - startScopedRangeBlock(code, srcRange, bi); + val srcNested = (port.isInput)? true : false; + startScopedRangeBlock(code, srcRange, bi, srcNested, true); var triggerArray = '''«CUtil.reactionRef(reaction, bi)».triggers[triggers_index[«bi»]++]''' // Skip ports whose parent is not in the federation. // This can happen with reactions in the top-level that have // as an effect a port in a bank. + // FIXME FIXME // reactionRef below is incorrect for reactions writing to contained reactors! if (currentFederate.contains(port.parent)) { pr(''' // Reaction «reaction.index» of «name» triggers «srcRange.destinations.size» downstream reactions @@ -6240,7 +6354,7 @@ class CGenerator extends GeneratorBase { «CUtil.reactionRef(reaction, bi)».triggered_sizes[triggers_index[«bi»]] = «srcRange.destinations.size»; // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» - trigger_t** trigger_array = (trigger_t**)malloc(«srcRange.destinations.size» * sizeof(trigger_t*)); + trigger_t** trigger_array = (trigger_t**)calloc(«srcRange.destinations.size», sizeof(trigger_t*)); «triggerArray» = trigger_array; ''') } else { @@ -6267,11 +6381,21 @@ class CGenerator extends GeneratorBase { for (dstRange : srcRange.destinations) { val dst = dstRange.instance; - startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, true); + val srcNested = (port.isInput)? true : false; + val dstNested = (dst.isOutput)? true : false; + startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, srcNested, dstNested); - // Need to reset triggerArray because of new channel and bank names. - val triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»] + «srcChannel»]''' - + // If the source is nested, need to take into account the parent's bank index + // when indexing into the triggers array. + var triggerArray = ""; + if (srcNested && port.parent.width > 1) { + triggerArray = ''' + «CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»] + «srcChannel» + src_range_mr.digits[1] * src_range_mr.radixes[0]] + ''' + } else { + triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»] + «srcChannel»]''' + } + if (dst.isOutput) { // Include this destination port only if it has at least one // reaction in the federation. @@ -6320,6 +6444,12 @@ class CGenerator extends GeneratorBase { */ private def void generateSelfStructs(ReactorInstance r) { if (!currentFederate.contains(r)) return; + // FIXME: For federated execution, if the reactor is a bank, then + // it may be that only one of the bank members is in the federate, + // but this creates an array big enough to hold all bank members. + // Fixing this will require making the functions in CUtil that + // create references to the runtime instances aware of this exception. + // For now, we just create a larger array than needed. pr(initializeTriggerObjects, ''' «CUtil.selfType(r)»* «CUtil.reactorRefName(r)»[«r.totalWidth»]; ''') From 89b5dd9a9b467f5feb88a4f1db187331bb71f5fb Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 10 Jan 2022 17:52:44 -0800 Subject: [PATCH 134/221] Separate the runtime and bank indices when iterating using mixed-radix numbers. --- .../org/lflang/generator/c/CGenerator.xtend | 192 +++++++++--------- .../src/org/lflang/generator/c/CUtil.java | 169 +++++++++------ 2 files changed, 199 insertions(+), 162 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 97f1999649..c608166455 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3307,7 +3307,7 @@ class CGenerator extends GeneratorBase { startScopedBlock(temp, instance, true); startScopedBankChannelIteration(temp, port, "count"); - val portRef = CUtil.portRef(port, true, true, null, null); + val portRef = CUtil.portRef(port, true, true, null, null, null); pr(temp, ''' _lf_tokens_with_ref_count[_lf_tokens_with_ref_count_count].token = &«portRef»->token; @@ -4915,7 +4915,7 @@ class CGenerator extends GeneratorBase { * Start a scoped block that iterates over the specified range of port channels. * * This must be followed by a call to - * {@link endScopedRangeBlock(StringBuilder, RuntimeRange)}. + * {@link #endScopedRangeBlock(StringBuilder, RuntimeRange)}. * * This block should NOT be nested, where each block is * put within a similar block for the reactor's parent. @@ -4925,10 +4925,14 @@ class CGenerator extends GeneratorBase { * that must match the runtimeIndex parameter given here. * * @param builder The string builder into which to write. - * @param range The send range. - * @param runtimeIndex The name of variable whose value specifies - * which runtime instance of the ReactorInstance of the range - * is being referred to. + * @param range The range of port channels. + * @param runtimeIndex A variable name to use to index the runtime instance of + * either port's parent or the port's parent's parent (if nested is true), or + * null to use the default, "runtime_index". + * @param bankIndex A variable name to use to index the bank of the port's parent or null to use the + * default, the string returned by {@link CUtil.bankIndexName(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to + * use the default, the string returned by {@link CUtil.channelIndexName(PortInstance)}. * @param nested If true, then the runtimeIndex variable will be set * to the bank index of the port's parent's parent rather than the * port's parent. @@ -4937,15 +4941,24 @@ class CGenerator extends GeneratorBase { * are not in the current federate. */ private def void startScopedRangeBlock( - StringBuilder builder, RuntimeRange range, String runtimeIndex, boolean nested, boolean restrict + StringBuilder builder, + RuntimeRange range, + String runtimeIndex, + String bankIndex, + String channelIndex, + boolean nested, + boolean restrict ) { pr(builder, ''' // Iterate over range «range.toString()». ''') - - val ci = CUtil.channelIndexName(range.instance); + val ri = (runtimeIndex === null)? "runtime_index" : runtimeIndex; + val ci = (channelIndex === null)? CUtil.channelIndexName(range.instance) : channelIndex; + val bi = (bankIndex === null)? CUtil.bankIndexName(range.instance.parent) : bankIndex; val rangeMR = range.startMR(); + val sizeMR = rangeMR.getDigits().size(); + val nestedLevel = (nested) ? 2 : 1; startScopedBlock(builder); if (range.width > 1) { @@ -4954,7 +4967,7 @@ class CGenerator extends GeneratorBase { int range_radixes[] = { «rangeMR.getRadixes().join(", ")» }; int permutation[] = { «range.permutation().join(", ")» }; mixed_radix_int_t range_mr = { - «rangeMR.getDigits().size()», + «sizeMR», range_start, range_radixes, permutation @@ -4963,8 +4976,9 @@ class CGenerator extends GeneratorBase { '''); indent(builder); pr(builder, ''' + int «ri» = mixed_radix_parent(&range_mr, «nestedLevel»); // Runtime index. int «ci» = range_mr.digits[0]; // Channel index. - int «runtimeIndex» = mixed_radix_parent(&range_mr, 1); + int «bi» = «IF sizeMR <= 1»0«ELSE»range_mr.digits[1]«ENDIF»; // Bank index. ''') if (isFederated) { if (restrict) { @@ -4980,16 +4994,16 @@ class CGenerator extends GeneratorBase { } } } else { - val mr = range.startMR(); - val ciValue = mr.getDigits().get(0); - val biValue = mr.get(1); + val ciValue = rangeMR.getDigits().get(0); + val riValue = rangeMR.get(nestedLevel); + val biValue = (sizeMR > 1)? rangeMR.getDigits().get(1) : 0; if (isFederated) { if (restrict) { // Special case. Have a bank of federates. Need that iteration // only cover the one federate. The last digit of the mixed-radix // number identifies the bank member (or is 0 if not within a bank). pr(builder, ''' - if («mr.get(mr.numDigits() - 1)» == «currentFederate.bankIndex») { + if («rangeMR.get(sizeMR - 1)» == «currentFederate.bankIndex») { ''') indent(builder); } else { @@ -4997,8 +5011,9 @@ class CGenerator extends GeneratorBase { } } pr(builder, ''' + int «ri» = «riValue»; // Runtime index. int «ci» = «ciValue»; // Channel index. - int «runtimeIndex» = «biValue»; // Bank(s) identifier. + int «bi» = «biValue»; // Bank index. int range_count = 0; ''') } @@ -5020,51 +5035,55 @@ class CGenerator extends GeneratorBase { } endScopedBlock(builder); } + + static val sc = "src_channel"; + static val sb = "src_bank"; + static val sr = "src_runtime"; + static val dc = "dst_channel"; + static val db = "dst_bank"; + static val dr = "dst_runtime"; /** * Start a scoped block that iterates over the specified pair of ranges. * The destination range can be wider than the source range, in which case the * source range is reused until the destination range is filled. - * The bankIndex and channelIndex arguments specify the variable names - * to use for the source channel and bank. The destination channel and - * bank variables are he default ones. + * The following integer variables will be defined within the scoped block: + * + * * src_channel: The channel index for the source. + * * src_bank: The bank index of the source port's parent. + * * src_runtime: The runtime index of the source port's parent or + * the parent's parent (if the source is an input). + * + * * dst_channel: The channel index for the destination. + * * dst_bank: The bank index of the destination port's parent. + * * dst_runtime: The runtime index of the destination port's parent or + * the parent's parent (if destination is an output). * + * For convenience, the above variable names are defined in the private + * class variables sc, sb, sr, and dc, db, dr. + * * This block should NOT be nested, where each block is * put within a similar block for the reactor's parent. * Within the created block, every use of - * {@link CUtil.reactorRef(ReactorInstance, String)} - * must provide the second argument, a runtime index variable name, - * that must match the srcRuntimeIndex or dstRuntimeIndex parameter given here. - * - * If the nested argument is true, then treat the corner case where - * if srcRange and dstRange refer to the same instance, - * and if that instance is an input, then the situation we have that - * of a reaction driving an input port of a contained reactor. - * In this case, the source bank should be the bank of the parent's parent, - * and the channel index should be the product of the two low-order digits - * of the mixed-radix number. + * {@link CUtil.reactorRef(ReactorInstance, String, String)} + * and related functions must provide the above variable names. * * This must be followed by a call to - * {@link #endScopedRangeBlock(StringBuilder, RuntimeRange)}. + * {@link #endScopedRangeBlock(StringBuilder, SendRange, RuntimeRange)}. * * @param builder The string builder into which to write. * @param srcRange The send range. * @param dstRange The destination range. - * @param srcRuntimeIndex Index variable name to use to address bank members for the source. - * @param srcChannelIndex Index variable name to use to address channels for the source. - * @param dstRuntimeIndex Index variable name to use to address bank members for the destination. */ private def void startScopedRangeBlock( StringBuilder builder, SendRange srcRange, - RuntimeRange dstRange, - String srcRuntimeIndex, - String srcChannelIndex, - String dstRuntimeIndex, - boolean xxxsrcNested, - boolean xxxdstNested + RuntimeRange dstRange ) { val srcRangeMR = srcRange.startMR(); + val srcSizeMR = srcRangeMR.radixes.size(); + val srcNestedLevel = (srcRange.instance.isInput) ? 2 : 1; + val dstNested = dstRange.instance.isOutput; pr(builder, ''' // Iterate over ranges «srcRange.toString» and «dstRange.toString». @@ -5087,7 +5106,7 @@ class CGenerator extends GeneratorBase { int src_radixes[] = { «srcRangeMR.getRadixes().join(", ")» }; int src_permutation[] = { «srcRange.permutation().join(", ")» }; mixed_radix_int_t src_range_mr = { - «srcRangeMR.getDigits().size()», + «srcSizeMR», src_value, src_radixes, src_permutation @@ -5095,19 +5114,22 @@ class CGenerator extends GeneratorBase { '''); } else { val ciValue = srcRangeMR.getDigits().get(0); - val biValue = srcRangeMR.get(1); + val biValue = (srcSizeMR > 1)? srcRangeMR.getDigits().get(1) : 0; + val riValue = srcRangeMR.get(srcNestedLevel); pr(builder, ''' - int «srcChannelIndex» = «ciValue»; // Channel index. - int «srcRuntimeIndex» = «biValue»; // Bank(s) identifier. + int «sr» = «riValue»; // Runtime index. + int «sc» = «ciValue»; // Channel index. + int «sb» = «biValue»; // Bank index. ''') } - startScopedRangeBlock(builder, dstRange, dstRuntimeIndex, false, true); - + startScopedRangeBlock(builder, dstRange, dr, db, dc, dstNested, true); + if (srcRange.width > 1) { pr(builder, ''' - int «srcChannelIndex» = src_range_mr.digits[0]; // Channel index. - int «srcRuntimeIndex» = mixed_radix_parent(&src_range_mr, 1); + int «sr» = mixed_radix_parent(&src_range_mr, «srcNestedLevel»); // Runtime index. + int «sc» = src_range_mr.digits[0]; // Channel index. + int «sb» = «IF srcSizeMR <= 1»0«ELSE»src_range_mr.digits[1]«ENDIF»; // Bank index. ''') } @@ -5130,17 +5152,11 @@ class CGenerator extends GeneratorBase { * @param builder The string builder into which to write. * @param srcRange The send range. * @param dstRange The destination range. - * @param srcRuntimeIndex Index variable name to use to address bank members for the source. - * @param srcChannelIndex Index variable name to use to address channels for the source. - * @param dstRuntimeIndex Index variable name to use to address bank members for the destination. */ private def void endScopedRangeBlock( StringBuilder builder, SendRange srcRange, - RuntimeRange dstRange, - String srcRuntimeIndex, - String srcChannelIndex, - String dstRuntimeIndex + RuntimeRange dstRange ) { // Do not use endScopedRangeBlock because we need things nested. if (isFederated) { @@ -5198,32 +5214,26 @@ class CGenerator extends GeneratorBase { pr(''' // Connect «srcRange.toString» to port «dstRange.toString» ''') - val srcChannel = "src_channel"; - val srcBank = "src_bank"; - val dstBank = "dst_bank"; - - val srcNested = (srcRange.instance.isInput)? true : false; - val dstNested = (dstRange.instance.isOutput)? true : false; - startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, srcNested, dstNested); + startScopedRangeBlock(code, srcRange, dstRange); if (src.isInput) { // Source port is written to by reaction in port's parent's parent // and ultimate destination is further downstream. pr(''' - «CUtil.portRef(dst, dstBank, null)» = («destStructType»*)«mod»«CUtil.portRefNested(src, srcBank, srcChannel)»; + «CUtil.portRef(dst, dr, db, dc)» = («destStructType»*)«mod»«CUtil.portRefNested(src, sr, sb, sc)»; ''') } else if (dst.isOutput) { // An output port of a contained reactor is triggering a reaction. pr(''' - «CUtil.portRefNested(dst, dstBank, null)» = («destStructType»*)&«CUtil.portRef(src, srcBank, srcChannel)»; + «CUtil.portRefNested(dst, dr, db, dc)» = («destStructType»*)&«CUtil.portRef(src, sr, sb, sc)»; ''') } else { // An output port is triggering pr(''' - «CUtil.portRef(dst, dstBank, null)» = («destStructType»*)&«CUtil.portRef(src, srcBank, srcChannel)»; + «CUtil.portRef(dst, dr, db, dc)» = («destStructType»*)&«CUtil.portRef(src, sr, sb, sc)»; ''') } - endScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); + endScopedRangeBlock(code, srcRange, dstRange); } } } @@ -5922,10 +5932,10 @@ class CGenerator extends GeneratorBase { for (sendingRange : output.eventualDestinations) { pr("// For reference counting, set num_destinations for port " + output.fullName + "."); - startScopedRangeBlock(code, sendingRange, "runtime_index", false, true); + startScopedRangeBlock(code, sendingRange, sr, sb, sc, sendingRange.instance.isInput, true); pr(''' - «CUtil.portRef(output, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; + «CUtil.portRef(output, sr, sb, sc)».num_destinations = «sendingRange.getNumberOfDestinationReactors()»; ''') endScopedRangeBlock(code, sendingRange); @@ -5966,18 +5976,14 @@ class CGenerator extends GeneratorBase { // The input port may itself have multiple destinations. for (sendingRange : port.eventualDestinations) { - startScopedRangeBlock(code, sendingRange, "runtime_index", false, true); + startScopedRangeBlock(code, sendingRange, sr, sb, sc, sendingRange.instance.isInput, true); // Syntax is slightly different for a multiport output vs. single port. - if (port.isMultiport()) { - pr(''' - «CUtil.portRefNested(port, "runtime_index", null)»->num_destinations = «sendingRange.getNumberOfDestinationReactors»; - ''') - } else { - pr(''' - «CUtil.portRefNested(port, "runtime_index", null)».num_destinations = «sendingRange.getNumberOfDestinationReactors»; - ''') - } + val connector = (port.isMultiport())? "->" : "."; + pr(''' + «CUtil.portRefNested(port, sr, sb, sc)»«connector»num_destinations = «sendingRange.getNumberOfDestinationReactors»; + ''') + endScopedRangeBlock(code, sendingRange); } } @@ -6337,21 +6343,19 @@ class CGenerator extends GeneratorBase { // its width will be 1. // We generate the code to fill the triggers array first in a temporary code buffer, // so that we can simultaneously calculate the size of the total array. - val bi = "runtime_index"; for (SendRange srcRange : port.eventualDestinations()) { val srcNested = (port.isInput)? true : false; - startScopedRangeBlock(code, srcRange, bi, srcNested, true); + startScopedRangeBlock(code, srcRange, sr, sb, sc, srcNested, true); - var triggerArray = '''«CUtil.reactionRef(reaction, bi)».triggers[triggers_index[«bi»]++]''' + var triggerArray = '''«CUtil.reactionRef(reaction, sr)».triggers[triggers_index[«sr»]++]''' // Skip ports whose parent is not in the federation. // This can happen with reactions in the top-level that have // as an effect a port in a bank. - // FIXME FIXME // reactionRef below is incorrect for reactions writing to contained reactors! if (currentFederate.contains(port.parent)) { pr(''' // Reaction «reaction.index» of «name» triggers «srcRange.destinations.size» downstream reactions // through port «port.getFullName». - «CUtil.reactionRef(reaction, bi)».triggered_sizes[triggers_index[«bi»]] = «srcRange.destinations.size»; + «CUtil.reactionRef(reaction, sr)».triggered_sizes[triggers_index[«sr»]] = «srcRange.destinations.size»; // For reaction «reaction.index» of «name», allocate an // array of trigger pointers for downstream reactions through port «port.getFullName» trigger_t** trigger_array = (trigger_t**)calloc(«srcRange.destinations.size», sizeof(trigger_t*)); @@ -6361,7 +6365,7 @@ class CGenerator extends GeneratorBase { // Port is not in the federate or has no destinations. // Set the triggered_width fields to 0. pr(''' - «CUtil.reactionRef(reaction, bi)».triggered_sizes[«CUtil.channelIndex(port)»] = 0; + «CUtil.reactionRef(reaction, sr)».triggered_sizes[«sc»] = 0; ''') } endScopedRangeBlock(code, srcRange); @@ -6374,26 +6378,22 @@ class CGenerator extends GeneratorBase { ''') for (SendRange srcRange : port.eventualDestinations()) { if (currentFederate.contains(port.parent)) { + val srcNested = srcRange.instance.isInput; var multicastCount = 0; - val srcChannel = "src_channel"; - val srcBank = "src_bank"; - val dstBank = "dst_bank"; for (dstRange : srcRange.destinations) { val dst = dstRange.instance; - val srcNested = (port.isInput)? true : false; - val dstNested = (dst.isOutput)? true : false; - startScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank, srcNested, dstNested); + startScopedRangeBlock(code, srcRange, dstRange); // If the source is nested, need to take into account the parent's bank index // when indexing into the triggers array. var triggerArray = ""; - if (srcNested && port.parent.width > 1) { + if (srcNested && port.parent.width > 1 && !(isFederated && port.parent.depth == 1)) { triggerArray = ''' - «CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»] + «srcChannel» + src_range_mr.digits[1] * src_range_mr.radixes[0]] + «CUtil.reactionRef(reaction, sr)».triggers[triggers_index[«sr»] + «sc» + src_range_mr.digits[1] * src_range_mr.radixes[0]] ''' } else { - triggerArray = '''«CUtil.reactionRef(reaction, srcBank)».triggers[triggers_index[«srcBank»] + «srcChannel»]''' + triggerArray = '''«CUtil.reactionRef(reaction, sr)».triggers[triggers_index[«sr»] + «sc»]''' } if (dst.isOutput) { @@ -6409,7 +6409,7 @@ class CGenerator extends GeneratorBase { pr(''' // Port «port.getFullName» has reactions in its parent's parent. // Point to the trigger struct for those reactions. - «triggerArray»[«multicastCount»] = &«CUtil.triggerRefNested(dst, dstBank)»; + «triggerArray»[«multicastCount»] = &«CUtil.triggerRefNested(dst, dr, db)»; ''') } else { // Put in a NULL pointer. @@ -6423,10 +6423,10 @@ class CGenerator extends GeneratorBase { // Destination is an input port. pr(''' // Point to destination port «dst.getFullName»'s trigger struct. - «triggerArray»[«multicastCount»] = &«CUtil.triggerRef(dst, dstBank)»; + «triggerArray»[«multicastCount»] = &«CUtil.triggerRef(dst, dr)»; ''') } - endScopedRangeBlock(code, srcRange, dstRange, srcBank, srcChannel, dstBank); + endScopedRangeBlock(code, srcRange, dstRange); multicastCount++; } } diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index fe09603bb4..190e57b77e 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -112,18 +112,18 @@ static public String channelIndexName(PortInstance port) { * * The returned string will have one of the following forms: * - * * selfStruct->_lf_portName - * * selfStruct->_lf_portName - * * selfStruct->_lf_portName[i] - * * selfStruct->_lf_parent.portName - * * selfStruct->_lf_parent.portName[i] - * * selfStruct->_lf_parent[j].portName - * * selfStruct->_lf_parent[j].portName[i] + * * selfStructs[k]->_lf_portName + * * selfStructs[k]->_lf_portName + * * selfStructs[k]->_lf_portName[i] + * * selfStructs[k]->_lf_parent.portName + * * selfStructs[k]->_lf_parent.portName[i] + * * selfStructs[k]->_lf_parent[j].portName + * * selfStructs[k]->_lf_parent[j].portName[i] * - * where the index j is present if the parent is a bank and is - * the string returned by {@link bankIndex(ReactorInstance)}, and - * the index i is present if the port is a multiport and is - * the string returned by {@link channelIndex(PortInstance)}. + * where k is the runtime index of either the port's parent + * or the port's parent's parent, the latter when isNested is true. + * The index j is present if the parent is a bank, and + * the index i is present if the port is a multiport. * * The first two forms are used if isNested is false, * and the remaining four are used if isNested is true. @@ -133,13 +133,18 @@ static public String channelIndexName(PortInstance port) { * @param port The port. * @param isNested True to return a reference relative to the parent's parent. * @param includeChannelIndex True to include the channel index at the end. - * @param bankIndex A variable name to use to index the bank or null to use the default. - * @param channelIndex A variable name to use to index the channel or null to use the default. + * @param runtimeIndex A variable name to use to index the runtime instance or + * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the + * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to + * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. */ static public String portRef( PortInstance port, boolean isNested, - boolean includeChannelIndex, + boolean includeChannelIndex, + String runtimeIndex, String bankIndex, String channelIndex ) { @@ -149,9 +154,9 @@ static public String portRef( channel = "[" + channelIndex + "]"; } if (isNested) { - return reactorRefNested(port.getParent(), bankIndex) + "." + port.getName() + channel; + return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + "." + port.getName() + channel; } else { - String sourceStruct = CUtil.reactorRef(port.getParent(), bankIndex); + String sourceStruct = CUtil.reactorRef(port.getParent(), runtimeIndex); return sourceStruct + "->_lf_" + port.getName() + channel; } } @@ -165,7 +170,7 @@ static public String portRef( * @param port An instance of the port to be referenced. */ static public String portRef(PortInstance port) { - return portRef(port, false, true, null, null); + return portRef(port, false, true, null, null, null); } /** @@ -176,11 +181,17 @@ static public String portRef(PortInstance port) { * a reaction in the port's parent. * This is equivalent to calling `portRef(port, false, true, bankIndex, channelIndex)`. * @param port An instance of the port to be referenced. - * @param bankIndex A variable name to use to index the bank or null to use the default. - * @param channelIndex A variable name to use to index the channel or null to use the default. + * @param runtimeIndex A variable name to use to index the runtime instance or + * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the + * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to + * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. */ - static public String portRef(PortInstance port, String bankIndex, String channelIndex) { - return portRef(port, false, true, bankIndex, channelIndex); + static public String portRef( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex + ) { + return portRef(port, false, true, runtimeIndex, bankIndex, channelIndex); } /** @@ -189,18 +200,25 @@ static public String portRef(PortInstance port, String bankIndex, String channel * @param port An instance of the port to be referenced. */ static public String portRefName(PortInstance port) { - return portRef(port, false, false, null, null); + return portRef(port, false, false, null, null, null); } /** * Return the portRef without the channel indexing. * This is useful for deriving a reference to the _width variable. + * * @param port An instance of the port to be referenced. - * @param bankIndex A variable name to use to index the bank or null to use the default. - * @param channelIndex A variable name to use to index the channel or null to use the default. - */ - static public String portRefName(PortInstance port, String bankIndex, String channelIndex) { - return portRef(port, false, false, bankIndex, channelIndex); + * @param runtimeIndex A variable name to use to index the runtime instance or + * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the + * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to + * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + static public String portRefName( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex + ) { + return portRef(port, false, false, runtimeIndex, bankIndex, channelIndex); } /** @@ -208,13 +226,13 @@ static public String portRefName(PortInstance port, String bankIndex, String cha * parent of the port's parent. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the - * port's parent. - * This is equivalent to calling `portRef(port, true, true, null, null)`. + * port's parent. This is equivalent to calling + * `portRef(port, true, true, null, null, null)`. * * @param port The port. */ static public String portRefNested(PortInstance port) { - return portRef(port, true, true, null, null); + return portRef(port, true, true, null, null, null); } /** @@ -222,15 +240,21 @@ static public String portRefNested(PortInstance port) { * parent of the port's parent. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the - * port's parent. - * This is equivalent to calling `portRef(port, true, true, bankIndex, channelIndex)`. + * port's parent. This is equivalent to calling + * `portRef(port, true, true, runtimeIndex, bankIndex, channelIndex)`. * * @param port The port. - * @param bankIndex A variable name to use to index the bank or null to use the default. - * @param channelIndex A variable name to use to index the channel or null to use the default. - */ - static public String portRefNested(PortInstance port, String bankIndex, String channelIndex) { - return portRef(port, true, true, bankIndex, channelIndex); + * @param runtimeIndex A variable name to use to index the runtime instance or + * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the + * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to + * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + static public String portRefNested( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex + ) { + return portRef(port, true, true, runtimeIndex, bankIndex, channelIndex); } /** @@ -240,12 +264,12 @@ static public String portRefNested(PortInstance port, String bankIndex, String c * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the * port's parent. - * This is equivalent to calling `portRef(port, true, false, null, null)`. + * This is equivalent to calling `portRef(port, true, false, null, null, null)`. * * @param port The port. */ static public String portRefNestedName(PortInstance port) { - return portRef(port, true, false, null, null); + return portRef(port, true, false, null, null, null); } /** @@ -254,15 +278,21 @@ static public String portRefNestedName(PortInstance port) { * even if it is a multiport. This is used when an input port * is written to by a reaction in the parent of the port's parent, * or when an output port triggers a reaction in the parent of the - * port's parent. - * This is equivalent to calling `portRef(port, true, false, bankIndex, channelIndex)`. + * port's parent. This is equivalent to calling + * `portRefNested(port, true, false, runtimeIndex, bankIndex, channelIndex)`. * * @param port The port. - * @param bankIndex A variable name to use to index the bank or null to use the default. - * @param channelIndex A variable name to use to index the channel or null to use the default. - */ - static public String portRefNestedName(PortInstance port, String bankIndex, String channelIndex) { - return portRef(port, true, false, bankIndex, channelIndex); + * @param runtimeIndex A variable name to use to index the runtime instance or + * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the + * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. + * @param channelIndex A variable name to use to index the channel or null to + * use the default, the string returned by {@link CUtil#channelIndex(PortInstance)}. + */ + static public String portRefNestedName( + PortInstance port, String runtimeIndex, String bankIndex, String channelIndex + ) { + return portRef(port, true, false, runtimeIndex, bankIndex, channelIndex); } /** @@ -278,10 +308,10 @@ static public String reactionRef(ReactionInstance reaction) { * Return a reference to the reaction entry on the self struct * of the parent of the specified reaction. * @param reaction The reaction. - * @param bankIndex An index into the array of self structs for the parent. + * @param runtimeIndex An index into the array of self structs for the parent. */ - static public String reactionRef(ReactionInstance reaction, String bankIndex) { - return reactorRef(reaction.getParent(), bankIndex) + static public String reactionRef(ReactionInstance reaction, String runtimeIndex) { + return reactorRef(reaction.getParent(), runtimeIndex) + "->_lf__reaction_" + reaction.index; } @@ -337,7 +367,7 @@ static public String reactorRef(ReactorInstance instance, String runtimeIndex) { * @param reactor The contained reactor. */ static public String reactorRefNested(ReactorInstance reactor) { - return reactorRefNested(reactor, null); + return reactorRefNested(reactor, null, null); } /** @@ -348,18 +378,20 @@ static public String reactorRefNested(ReactorInstance reactor) { * a struct with fields corresponding to those inputs and outputs. * This method returns a reference to that struct or array of structs. * Note that the returned reference is not to the self struct of the - * contained reactor. Use {@link reactorRef(ReactorInstance)} for that. + * contained reactor. Use {@link CUtil#reactorRef(ReactorInstance)} for that. * * @param reactor The contained reactor. - * @param index An optional index epression to use to address bank members - * when the contained reactor is a bank. Note that the default runtime - * index expression will be used to obtain the self struct of the container. + * @param runtimeIndex A variable name to use to index the runtime instance or + * null to use the default, the string returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex A variable name to use to index the bank or null to use the + * default, the string returned by {@link CUtil#bankIndex(ReactorInstance)}. */ - static public String reactorRefNested(ReactorInstance reactor, String index) { - String result = reactorRef(reactor.getParent()) + "->_lf_" + reactor.getName(); + static public String reactorRefNested(ReactorInstance reactor, String runtimeIndex, String bankIndex) { + String result = reactorRef(reactor.getParent(), runtimeIndex) + "->_lf_" + reactor.getName(); if (reactor.isBank()) { - if (index == null) index = runtimeIndex(reactor); - result += "[" + index + "]"; + // Need the bank index not the runtimeIndex. + if (bankIndex == null) bankIndex = bankIndex(reactor); + result += "[" + bankIndex + "]"; } return result; } @@ -438,10 +470,10 @@ static public String triggerRef(TriggerInstance instance) { * trigger instance (input port or action). This trigger_t struct * is on the self struct. * @param instance The port or action instance. - * @param bankIndex An optional index variable name to use to address bank members. + * @param runtimeIndex An optional index variable name to use to address runtime instances. */ - static public String triggerRef(TriggerInstance instance, String bankIndex) { - return reactorRef(instance.getParent(), bankIndex) + static public String triggerRef(TriggerInstance instance, String runtimeIndex) { + return reactorRef(instance.getParent(), runtimeIndex) + "->_lf__" + instance.getName(); } @@ -452,17 +484,22 @@ static public String triggerRef(TriggerInstance instance, St * @param port The output port of a contained reactor. */ static public String triggerRefNested(PortInstance port) { - return triggerRefNested(port, null); + return triggerRefNested(port, null, null); } /** * Return a reference to the trigger_t struct for the specified * port of a contained reactor. * @param port The output port of a contained reactor. - * @param bankIndex An optional index variable name to use to address bank members. - */ - static public String triggerRefNested(PortInstance port, String bankIndex) { - return reactorRefNested(port.getParent(), bankIndex) + "." + port.getName() + "_trigger"; + * @param runtimeIndex An optional index variable name to use to index + * the runtime instance of the port's parent's parent, or null to get the + * default returned by {@link CUtil#runtimeIndex(ReactorInstance)}. + * @param bankIndex An optional index variable name to use to index + * the the bank of the port's parent, or null to get the default returned by + * {@link CUtil#bankIndex(ReactorInstance)}. + */ + static public String triggerRefNested(PortInstance port, String runtimeIndex, String bankIndex) { + return reactorRefNested(port.getParent(), bankIndex, runtimeIndex) + "." + port.getName() + "_trigger"; } ////////////////////////////////////////////////////// From 34d00a8004706357afc4bbda7612cee73b43d41d Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 11 Jan 2022 08:27:43 -0800 Subject: [PATCH 135/221] Added function getLevelsList() --- .../org/lflang/generator/ReactionInstance.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index e5ca19e70d..08b7d1f084 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -28,6 +28,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import java.util.ArrayList; import java.util.LinkedHashSet; +import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -337,6 +338,22 @@ public Set getLevels() { return result; } + /** + * Return a list of levels that instances of this reaction have. + * The size of this list is the total number of runtime instances. + * A ReactionInstance may have more than one level if it lies within + * a bank and its dependencies on other reactions pass through multiports. + */ + public List getLevelsList() { + List result = new LinkedList(); + // Force calculation of levels if it has not been done. + parent.assignLevels(); + for (Runtime runtime : runtimeInstances) { + result.add(runtime.level); + } + return result; + } + /** * Return the name of this reaction, which is 'reaction_n', * where n is replaced by the reaction index. From dcb190e3f794b6cd4de26c5c98363535c0aff24b Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 11 Jan 2022 08:28:12 -0800 Subject: [PATCH 136/221] Use function getLevelsList, not the set function. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index c608166455..6aab093589 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4053,8 +4053,9 @@ class CGenerator extends GeneratorBase { startScopedBlock(prolog); endScopedBlock(epilog); } + // Cannot use the above set of levels because it is a set, not a list. pr(prolog, ''' - int «r.uniqueID»_levels[] = { «levels.join(", ")» }; + int «r.uniqueID»_levels[] = { «r.getLevelsList().join(", ")» }; ''') } } From 43825a77b7c3d60b0143d3d0b406a7973f704597 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 11 Jan 2022 18:23:39 -0800 Subject: [PATCH 137/221] Added argument to clearCaches() that enables preserving Runtime instances --- .../org/lflang/generator/ReactionInstance.java | 10 +++++++--- .../org/lflang/generator/ReactorInstance.java | 18 ++++++++++++++++-- 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 08b7d1f084..1d9088f066 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -245,8 +245,11 @@ public ReactionInstance( * Clear caches used in reporting dependentReactions() and dependsOnReactions(). * This method should be called if any changes are made to triggers, sources, * or effects. + * @param includingRuntimes If false, leave the runtime instances intact. + * This is useful for federated execution where levels are computed using + * the top-level connections, but then those connections are discarded. */ - public void clearCaches() { + public void clearCaches(boolean includingRuntimes) { dependentReactionsCache = null; dependsOnReactionsCache = null; } @@ -401,14 +404,15 @@ public List getRuntimeInstances() { /** * Purge 'portInstance' from this reaction, removing it from the list - * of triggers, sources, effects, and reads. + * of triggers, sources, effects, and reads. Note that this leaves + * the runtime instances intact, including their level information. */ public void removePortInstance(PortInstance portInstance) { this.triggers.remove(portInstance); this.sources.remove(portInstance); this.effects.remove(portInstance); this.reads.remove(portInstance); - clearCaches(); + clearCaches(false); portInstance.clearCaches(); } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index e2cf8b4d77..55f88fed4f 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -200,10 +200,21 @@ public ReactorInstance getChildReactorInstance(Instantiation definition) { * This is useful if a mutation has been realized. */ public void clearCaches() { - cachedReactionLoopGraph = null; + clearCaches(true); + } + + /** + * Clear any cached data in this reactor and its children. + * This is useful if a mutation has been realized. + * @param includingRuntimes If false, leave the runtime instances of reactions intact. + * This is useful for federated execution where levels are computed using + * the top-level connections, but then those connections are discarded. + */ + public void clearCaches(boolean includingRuntimes) { + if (includingRuntimes) cachedReactionLoopGraph = null; totalNumChildrenCache = -1; for (ReactorInstance child : children) { - child.clearCaches(); + child.clearCaches(includingRuntimes); } for (PortInstance port : inputs) { port.clearCaches(); @@ -211,6 +222,9 @@ public void clearCaches() { for (PortInstance port : outputs) { port.clearCaches(); } + for (ReactionInstance reaction : reactions) { + reaction.clearCaches(includingRuntimes); + } } /** From c652e438c78239aa387b469c6daf94fb9060f413 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 11 Jan 2022 18:48:16 -0800 Subject: [PATCH 138/221] Method renaming and comments only --- .../org/lflang/generator/GeneratorBase.xtend | 50 +++++++++---------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index f8f10f7c68..c3fc178101 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -258,7 +258,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { * If there is a main or federated reactor, then create a synthetic Instantiation * for that top-level reactor and set the field mainDef to refer to it. */ - def createMainInstance() { + private def createMainInstantiation() { // Find the main reactor and create an AST node for its instantiation. for (reactor : fileConfig.resource.allContents.toIterable.filter(Reactor)) { if (reactor.isMain || reactor.isFederated) { @@ -301,7 +301,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { ASTUtils.setMainName(fileConfig.resource, fileConfig.name) - createMainInstance() + createMainInstantiation() // Check if there are any conflicting main reactors elsewhere in the package. if (fileConfig.compilerMode == Mode.STANDALONE && mainDef !== null) { @@ -310,10 +310,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - // If federates are specified in the target, create a mapping - // from Instantiations in the main reactor to federate names. - // Also create a list of federate names or a list with a single - // empty name if there are no federates specified. // This must be done before desugaring delays below. analyzeFederates() @@ -323,7 +319,8 @@ abstract class GeneratorBase extends JavaGeneratorBase { // done here. copyUserFiles(this.targetConfig, this.fileConfig); - // Collect reactors and create an instantiation graph. These are needed to figure out which resources we need + // Collect reactors and create an instantiation graph. + // These are needed to figure out which resources we need // to validate, which happens in setResources(). setReactorsAndInstantiationGraph() @@ -337,11 +334,13 @@ abstract class GeneratorBase extends JavaGeneratorBase { JavaGeneratorUtils.accommodatePhysicalActionsIfPresent(allResources, target, targetConfig, errorReporter); // FIXME: Should the GeneratorBase pull in `files` from imported // resources? - - // Reroute connections that have delays associated with them via generated delay reactors. + + // Reroute connections that have delays associated with them via + // generated delay reactors. transformDelays() - // Invoke these functions a second time because transformations may have introduced new reactors! + // Invoke these functions a second time because transformations + // may have introduced new reactors! setReactorsAndInstantiationGraph() // First, produce any preamble code that the code generator needs @@ -973,24 +972,23 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - /** Analyze the resource (the .lf file) that is being parsed - * to determine whether code is being mapped to single or to - * multiple target machines. If it is being mapped to multiple - * machines, then set the 'federates' list, the 'federateIDs' - * map, and the 'federationRTIHost' and 'federationRTIPort' - * variables. + /** + * Analyze the AST to determine whether code is being mapped to + * single or to multiple target machines. If it is being mapped + * to multiple machines, then set the {@link #isFederated} field to true, + * create a FederateInstance for each federate, and record various + * properties of the federation. * - * In addition, analyze the connections between federates. - * Replace connections between federates with sending and - * receiving reactions. + * In addition, for each top-level connection, add top-level reactions to the AST + * that send and receive messages over the network. * - * This class is target independent, so the target code - * generator still has quite a bit of work to do. - * It needs to provide the body of the sending and - * receiving reactions. It also needs to provide the - * runtime infrastructure that uses the dependency - * information between federates. See the C target - * for a reference implementation. + * This class is target independent, so the target code + * generator still has quite a bit of work to do. + * It needs to provide the body of the sending and + * receiving reactions. It also needs to provide the + * runtime infrastructure that uses the dependency + * information between federates. See the C target + * for a reference implementation. */ private def analyzeFederates() { // Next, if there actually are federates, analyze the topology From 87623292645308e6d5dc24b4f4171bf8cb74182f Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 11 Jan 2022 18:48:41 -0800 Subject: [PATCH 139/221] Preserve runtimes when clearing caches --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 6aab093589..7054a7a95e 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -503,7 +503,7 @@ class CGenerator extends GeneratorBase { removeRemoteFederateConnectionPorts(main); // Force reconstruction of dependence information. // FIXME: Probably only need to do this for federated execution. - this.main.clearCaches(); + this.main.clearCaches(false); } } From 002dc38136d7eda72ee96cd42f0cf9c8f92998f8 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 12 Jan 2022 11:00:38 -0800 Subject: [PATCH 140/221] Fixed gating for proper federate on setting last_enabling_reaction --- .../src/org/lflang/generator/c/CGenerator.xtend | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 7054a7a95e..f0673fbf54 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6054,10 +6054,10 @@ class CGenerator extends GeneratorBase { // For federated systems, the above test may not be enough if there is a bank // of federates. Calculate the divisor needed to compute the federate bank // index from the instance index of the reaction. - var divisor = 1; // Value 1 will indicate nothing needs to be done. + var divisor = 1; if (isFederated) { var parent = reaction.parent; - while (parent !== null) { + while (parent.depth > 1) { divisor *= parent.width; parent = parent.parent; } @@ -6139,7 +6139,7 @@ class CGenerator extends GeneratorBase { // To really know whether the dominating reaction is in the federate, // we need to calculate a divisor for its runtime index. var parent = runtime.dominating.getReaction().parent; - while (parent !== null) { + while (parent.depth > 1) { domDivisor *= parent.width; parent = parent.parent; } @@ -6161,12 +6161,12 @@ class CGenerator extends GeneratorBase { // «runtime.reaction.getFullName» dominating upstream reaction. int j = «domStart»; for (int i = «start»; i < «end»; i++) { - «IF divisor > 1» + «IF isFederated» if (i / «divisor» != «currentFederate.bankIndex») continue; // Reaction is not in the federate. - «ENDIF» - «IF domDivisor > 1» + «IF runtime.dominating !== null» if (j / «domDivisor» != «currentFederate.bankIndex») continue; // Dominating reaction is not in the federate. «ENDIF» + «ENDIF» «reactionRef».last_enabling_reaction = «dominatingRef»; } ''') @@ -6178,7 +6178,10 @@ class CGenerator extends GeneratorBase { ) { dominatingRef = "&(" + CUtil.reactionRef(runtime.dominating.reaction, "" + domStart) + ")"; } - if (divisor == 1 || start/divisor == currentFederate.bankIndex) { + if (!isFederated + || (start/divisor == currentFederate.bankIndex) + && (runtime.dominating === null || domStart/domDivisor == currentFederate.bankIndex) + ) { pr(''' // «runtime.reaction.getFullName» dominating upstream reaction. «reactionRef».last_enabling_reaction = «dominatingRef»; From 122f5cb0b2d42bff1d80eee9c11ee9f33c38d6bc Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 12 Jan 2022 13:27:36 -0800 Subject: [PATCH 141/221] Updated reactor-c --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 9390df081e..45acfb87a4 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 9390df081e0f442b098d0b34469a5dae89fc9308 +Subproject commit 45acfb87a4178a2a4c1ab7e01b08ae3481f13479 From 5865bccf777ec5e86091b9cb18e6f20c75367e3a Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 12 Jan 2022 13:51:40 -0800 Subject: [PATCH 142/221] Fixed typo in generated code for _lf_intended_tags_fields --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index f0673fbf54..fe96196e27 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -3379,7 +3379,7 @@ class CGenerator extends GeneratorBase { // Intended_tag is only applicable to ports in federated execution with decentralized coordination. pr(temp, ''' // Add port «output.getFullName» to array of intended_tag fields. - _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)»[j].intended_tag; + _lf_intended_tag_fields[«startTimeStepIsPresentCount» + count] = &«CUtil.portRef(output)».intended_tag; ''') } From b81677d11e3b77a4bcf7e162815afcf82ff01c11 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 12 Jan 2022 14:25:43 -0800 Subject: [PATCH 143/221] Declare output port as an effect of the reaction so that memory is allocated for it. --- test/C/src/federated/DistributedNetworkOrder.lf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/C/src/federated/DistributedNetworkOrder.lf b/test/C/src/federated/DistributedNetworkOrder.lf index a30de8f138..d17de9cbc6 100644 --- a/test/C/src/federated/DistributedNetworkOrder.lf +++ b/test/C/src/federated/DistributedNetworkOrder.lf @@ -10,13 +10,14 @@ */ target C { - timeout: 1 sec + timeout: 1 sec, + build-type: RelWithDebInfo // Release with debug info }; reactor Sender { output out:int; timer t(0, 1 msec); - reaction(t) {= + reaction(t) -> out {= int payload = 1; if (get_elapsed_logical_time() == 0LL) { send_timed_message(MSEC(10), MSG_TYPE_TAGGED_MESSAGE, 0, 1, "federate 1", sizeof(int), From d1b62713ad71a93035256f48f3326f9377af9777 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 13 Jan 2022 14:34:13 -0800 Subject: [PATCH 144/221] Address failing "no cmake" tests. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index ac7795c3ab..c8b2e0094f 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -468,11 +468,11 @@ class CGenerator extends GeneratorBase { */ override void doGenerate(Resource resource, IFileSystemAccess2 fsa, LFGeneratorContext context) { - accommodatePhysicalActionsIfPresent() - setCSpecificDefaults(context) // The following generates code needed by all the reactors. super.doGenerate(resource, fsa, context) + accommodatePhysicalActionsIfPresent() + setCSpecificDefaults(context) printMain(); if (errorsOccurred) return; From fa43f9aa4b98bf52113298faa4ff5aef3e2dae4e Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 13 Jan 2022 15:00:38 -0800 Subject: [PATCH 145/221] Update submodule. --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 45acfb87a4..38d8257b68 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 45acfb87a4178a2a4c1ab7e01b08ae3481f13479 +Subproject commit 38d8257b68818207b113624529e95a3c3088f2f5 From cb5f3fb71789b7e30b263e48a9b304f8f2cf9d19 Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 13 Jan 2022 10:52:49 -0800 Subject: [PATCH 146/221] All federated tests pass. --- .../org/lflang/generator/c/CGenerator.xtend | 2 +- test/C/src/multiport/ReactionsToNested.lf | 38 +++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 test/C/src/multiport/ReactionsToNested.lf diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index c8b2e0094f..87fc46ffaf 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -5169,7 +5169,7 @@ class CGenerator extends GeneratorBase { // really necessary. if (currentFederate.contains(dst.parent)) { - val mod = (dst.isMultiport)? "" : "&"; + val mod = (dst.isMultiport || (src.isInput && src.isMultiport))? "" : "&"; pr(''' // Connect «srcRange.toString» to port «dstRange.toString» diff --git a/test/C/src/multiport/ReactionsToNested.lf b/test/C/src/multiport/ReactionsToNested.lf new file mode 100644 index 0000000000..daf3e8b195 --- /dev/null +++ b/test/C/src/multiport/ReactionsToNested.lf @@ -0,0 +1,38 @@ +// This test connects a simple counting source to tester +// that checks against its own count. +target C { + timeout: 1 sec +}; +reactor T(expected:int(0)) { + input z:int; + state received:bool(false); + reaction(z) {= + info_print("T received %d", z->value); + self->received = true; + if (z->value != self->expected) { + error_print_and_exit("Expected %d", self->expected); + } + =} + reaction(shutdown) {= + if (!self->received) { + error_print_and_exit("No input received"); + } + =} +} + +reactor D { + input[2] y:int; + t1 = new T(expected = 42); + t2 = new T(expected = 43); + y -> t1.z, t2.z; +} + +main reactor { + d = new D(); + reaction(startup) -> d.y {= + SET(d.y[0], 42); + =} + reaction(startup) -> d.y {= + SET(d.y[1], 43); + =} +} From b7972df68f51ceb8ae78f35db8587e407779ab49 Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 13 Jan 2022 17:17:47 -0800 Subject: [PATCH 147/221] Removed unused imports --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 3 --- 1 file changed, 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index e553eaf90e..8201936cc1 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -24,7 +24,6 @@ ***************/ package org.lflang.generator -import java.io.File import java.nio.file.Files import java.nio.file.Paths import java.util.ArrayList @@ -38,8 +37,6 @@ import java.util.stream.Collectors import org.eclipse.core.resources.IMarker import org.eclipse.emf.ecore.resource.Resource import org.eclipse.xtext.generator.IFileSystemAccess2 -import org.eclipse.xtext.nodemodel.util.NodeModelUtils -import org.eclipse.xtext.resource.XtextResource import org.eclipse.xtext.util.CancelIndicator import org.lflang.ASTUtils import org.lflang.ErrorReporter From fb447a27fb8cc646ccf38a8c328fff04de6bddba Mon Sep 17 00:00:00 2001 From: eal Date: Thu, 13 Jan 2022 17:41:02 -0800 Subject: [PATCH 148/221] Updated to use Either class in parameter --- org.lflang.diagram/src/org/lflang/diagram/lsp/Progress.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/lsp/Progress.java b/org.lflang.diagram/src/org/lflang/diagram/lsp/Progress.java index 73dd145555..f483d93c54 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/lsp/Progress.java +++ b/org.lflang.diagram/src/org/lflang/diagram/lsp/Progress.java @@ -101,6 +101,6 @@ public void end(String message) { * @param notification */ private void notifyProgress(WorkDoneProgressNotification notification) { - client.notifyProgress(new ProgressParams(Either.forRight(token), notification)); + client.notifyProgress(new ProgressParams(Either.forRight(token), Either.forLeft(notification))); } } From 83b2acafc6be46e3152d927460b07535e2cf6bf1 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Thu, 13 Jan 2022 21:08:24 -0800 Subject: [PATCH 149/221] Address test failures for TypeScript. --- org.lflang/src/org/lflang/generator/ts/TSGenerator.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 3dcf15d10c..f4f3a41675 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -98,7 +98,7 @@ class TSGenerator( private fun timeInTargetLanguage(value: TimeValue): String { return if (value.unit != null) { - "TimeValue.${value.unit}(${value.time})" + "TimeValue.${value.unit.canonicalName}(${value.magnitude})" } else { // The value must be zero. "TimeValue.zero()" @@ -115,7 +115,7 @@ class TSGenerator( // Wrappers to expose GeneratorBase methods. fun federationRTIPropertiesW() = federationRTIProperties - fun getTargetValueW(v: Value): String = VG.getTargetValue(v) + fun getTargetValueW(v: Value): String = VG.getTargetValue(v, false) fun getTargetTypeW(p: Parameter): String = TSTypes.getTargetType(p.inferredType) fun getTargetTypeW(state: StateVar): String = TSTypes.getTargetType(state) fun getTargetTypeW(t: Type): String = TSTypes.getTargetType(t) From 8521362376d5cbdc633786e7d9cd90f74cb159e8 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jan 2022 23:14:44 -0800 Subject: [PATCH 150/221] Address test failures due to missing core/reactor.c. This also addresses two FIXMEs: generatePreamble should not be in GeneratorBase, and the C version of generatePreamble should not be appending to the set of enabled serializers. --- .../org/lflang/generator/GeneratorBase.xtend | 49 +++---------------- .../org/lflang/generator/c/CGenerator.xtend | 29 ++++++----- .../generator/python/PythonGenerator.xtend | 2 +- .../org/lflang/generator/ts/TSGenerator.kt | 2 +- 4 files changed, 27 insertions(+), 55 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 8201936cc1..bba52c6d50 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -341,17 +341,7 @@ abstract class GeneratorBase extends JavaGeneratorBase { // may have introduced new reactors! setReactorsAndInstantiationGraph() - // First, produce any preamble code that the code generator needs - // to produce before anything else goes into the code generated files. - generatePreamble() // FIXME: Move this elsewhere. See awkwardness with CppGenerator because it will not even - // use the result. - - if (!enabledSerializers.isNullOrEmpty) { - // If serialization support is - // requested by the programmer - // enable support for them. - enableSupportForSerialization(context.cancelIndicator); - } + enableSupportForSerializationIfApplicable(context.cancelIndicator); } /** @@ -717,11 +707,13 @@ abstract class GeneratorBase extends JavaGeneratorBase { * Add necessary code to the source and necessary build support to * enable the requested serializations in 'enabledSerializations' */ - def void enableSupportForSerialization(CancelIndicator cancelIndicator) { - throw new UnsupportedOperationException( - "Serialization is target-specific "+ - " and is not implemented for the "+target.toString+" target." - ); + def void enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { + if (!enabledSerializers.isNullOrEmpty()) { + throw new UnsupportedOperationException( + "Serialization is target-specific "+ + " and is not implemented for the "+target.toString+" target." + ); + } } /** @@ -763,31 +755,6 @@ abstract class GeneratorBase extends JavaGeneratorBase { } } - /** - * Generate any preamble code that appears in the code generated - * file before anything else. - */ - protected def void generatePreamble() { - prComment("Code generated by the Lingua Franca compiler from:") - prComment("file:/" +FileConfig.toUnixString(fileConfig.srcFile)) - val models = new LinkedHashSet - - for (r : this.reactors ?: emptyList) { - // The following assumes all reactors have a container. - // This means that generated reactors **have** to be - // added to a resource; not doing so will result in a NPE. - models.add(r.toDefinition.eContainer as Model) - } - // Add the main reactor if it is defined - if (this.mainDef !== null) { - val mainModel = this.mainDef.reactorClass.toDefinition.eContainer as Model - models.add(mainModel) - for (p : mainModel.preambles) { - pr(p.code.toText) - } - } - } - /** * Given a line of text from the output of a compiler, return * an instance of ErrorFileAndLine if the line is recognized as diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 87fc46ffaf..27ab52058b 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -473,6 +473,7 @@ class CGenerator extends GeneratorBase { super.doGenerate(resource, fsa, context) accommodatePhysicalActionsIfPresent() setCSpecificDefaults(context) + generatePreamble() printMain(); if (errorsOccurred) return; @@ -4470,7 +4471,11 @@ class CGenerator extends GeneratorBase { * Add necessary code to the source and necessary build supports to * enable the requested serializer in 'enabledSerializers' */ - override enableSupportForSerialization(CancelIndicator cancelIndicator) { + override enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { + if (!targetConfig.protoFiles.isNullOrEmpty) { + // Enable support for proto serialization + enabledSerializers.add(SupportedSerializers.PROTO) + } for (serializer : enabledSerializers) { switch (serializer) { case SupportedSerializers.NATIVE: { @@ -4517,7 +4522,7 @@ class CGenerator extends GeneratorBase { * As a side effect, this populates the runCommand and compileCommand * private variables if such commands are specified in the target directive. */ - override generatePreamble() { + def generatePreamble() { pr(this.defineLogLevel) if (isFederated) { @@ -4532,7 +4537,7 @@ class CGenerator extends GeneratorBase { // The coordination is centralized. pr(''' #define FEDERATED_CENTRALIZED - ''') + ''') } else if (targetConfig.coordination === CoordinationType.DECENTRALIZED) { // The coordination is decentralized pr(''' @@ -4550,7 +4555,7 @@ class CGenerator extends GeneratorBase { // worker thread to process incoming messages. if (targetConfig.threads < federate.networkMessageActions.size + 1) { targetConfig.threads = federate.networkMessageActions.size + 1; - } + } } } @@ -4568,19 +4573,19 @@ class CGenerator extends GeneratorBase { // Do this after the above includes so that the preamble can // call built-in functions. - super.generatePreamble() + prComment("Code generated by the Lingua Franca compiler from:") + prComment("file:/" +FileConfig.toUnixString(fileConfig.srcFile)) + if (this.mainDef !== null) { + val mainModel = this.mainDef.reactorClass.toDefinition.eContainer as Model + for (p : mainModel.preambles) { + pr(p.code.toText) + } + } parseTargetParameters() // Make sure src-gen directory exists. fileConfig.getSrcGenPath.toFile.mkdirs - - // FIXME: Probably not the best place to do - // this. - if (!targetConfig.protoFiles.isNullOrEmpty) { - // Enable support for proto serialization - enabledSerializers.add(SupportedSerializers.PROTO) - } } /** diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index a858e0120d..9ae3672053 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -935,7 +935,7 @@ class PythonGenerator extends CGenerator { * Add necessary code to the source and necessary build supports to * enable the requested serializations in 'enabledSerializations' */ - override enableSupportForSerialization(CancelIndicator cancelIndicator) { + override enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { for (serialization : enabledSerializers) { switch (serialization) { case NATIVE: { diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index f4f3a41675..b320e17e03 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -578,7 +578,7 @@ class TSGenerator( * Add necessary code to the source and necessary build supports to * enable the requested serializations in 'enabledSerializations' */ - override fun enableSupportForSerialization(cancelIndicator: CancelIndicator?) { + override fun enableSupportForSerializationIfApplicable(cancelIndicator: CancelIndicator?) { for (serializer in enabledSerializers) { when (serializer) { SupportedSerializers.NATIVE -> { From bf8b59c1b92e6a5a7c63c8940ae81e0e5453d1fd Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jan 2022 23:52:21 -0800 Subject: [PATCH 151/221] Python: Fix "undefined type" errors. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 8 ++++++-- .../org/lflang/generator/python/PythonGenerator.xtend | 10 +++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 27ab52058b..8ef48ce276 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -364,10 +364,14 @@ class CGenerator extends GeneratorBase { var CTypes types; - new(FileConfig fileConfig, ErrorReporter errorReporter, boolean CCppMode) { + protected new(FileConfig fileConfig, ErrorReporter errorReporter, boolean CCppMode, CTypes types) { super(fileConfig, errorReporter) this.CCppMode = CCppMode; - types = new CTypes(errorReporter) + this.types = types + } + + new(FileConfig fileConfig, ErrorReporter errorReporter, boolean CCppMode) { + this(fileConfig, errorReporter, CCppMode, new CTypes(errorReporter)) } new(FileConfig fileConfig, ErrorReporter errorReporter) { diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 9ae3672053..8bee242723 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -101,14 +101,18 @@ class PythonGenerator extends CGenerator { var PythonTypes types; new(FileConfig fileConfig, ErrorReporter errorReporter) { - super(fileConfig, errorReporter) + this(fileConfig, errorReporter, new PythonTypes(errorReporter)) + } + + private new(FileConfig fileConfig, ErrorReporter errorReporter, PythonTypes types) { + super(fileConfig, errorReporter, false, types) // set defaults targetConfig.compiler = "gcc" targetConfig.compilerFlags = newArrayList // -Wall -Wconversion" targetConfig.linkerFlags = "" - types = new PythonTypes(errorReporter) + this.types = types } - + /** * Generic struct for ports with primitive types and * statically allocated arrays in Lingua Franca. From 93623637d9cf74b292c37ecd696301f2e1e2aee3 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Fri, 14 Jan 2022 23:55:40 -0800 Subject: [PATCH 152/221] Python: /* */ comments are invalid. --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 8bee242723..79a57b8c80 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -694,13 +694,13 @@ class PythonGenerator extends CGenerator { pythonClassesInstantiation. append(''' «instance.uniqueID»_lf = \ - _«className»(bank_index = 0 /* bank_index is specially assigned by us*/»,\ + _«className»(bank_index = 0»,\ «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR») ''') for (var i = 1; i < instance.width; i++) { pythonClassesInstantiation. append(''' - _«className»(bank_index = «i» /* bank_index is specially assigned by us*/,\ + _«className»(bank_index = «i»,\ «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), \\\n ''') } @@ -713,7 +713,7 @@ class PythonGenerator extends CGenerator { // FIXME: Why does this add a bank_index if it's not a bank? pythonClassesInstantiation.append(''' «instance.uniqueID»_lf = \ - [_«className»(bank_index = 0 /* bank_index is specially assigned by us*/, \ + [_«className»(bank_index = 0, \ «FOR param : instance.parameters SEPARATOR ", \\\n"»_«param.name»=«param.pythonInitializer»«ENDFOR»)] ''') } From efc30c261c0fb5e4cf69ccaee62fbc84328792f7 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Sat, 15 Jan 2022 00:29:27 -0800 Subject: [PATCH 153/221] Python: Address failing serialization tests. Related: 85213623. --- .../org/lflang/generator/python/PythonGenerator.xtend | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 79a57b8c80..2652f6b22b 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -926,13 +926,6 @@ class PythonGenerator extends CGenerator { super.includeTargetLanguageSourceFiles() super.parseTargetParameters() - - // FIXME: Probably not the best place to do - // this. - if (!targetConfig.protoFiles.isNullOrEmpty) { - // Enable support for proto serialization - enabledSerializers.add(SupportedSerializers.PROTO) - } } /** @@ -940,6 +933,10 @@ class PythonGenerator extends CGenerator { * enable the requested serializations in 'enabledSerializations' */ override enableSupportForSerializationIfApplicable(CancelIndicator cancelIndicator) { + if (!targetConfig.protoFiles.isNullOrEmpty) { + // Enable support for proto serialization + enabledSerializers.add(SupportedSerializers.PROTO) + } for (serialization : enabledSerializers) { switch (serialization) { case NATIVE: { From a544ad3e842f98098d73d67d7cbc16076e3e7b47 Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 15 Jan 2022 13:49:23 -0800 Subject: [PATCH 154/221] Tolerate errors when retrieving levels --- .../styles/LinguaFrancaShapeExtensions.xtend | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend index c73872e06b..929cea76c1 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend @@ -296,12 +296,16 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { // Force calculation of levels for reactions. This calculation // will only be done once. Note that if this fails due to a causality loop, // then some reactions will have level -1. - val levels = reaction.getLevels().join(", "); - contentContainer.addText("level: " + levels) => [ - fontBold = false - noSelectionStyle - suppressSelectability - ] + try { + val levels = reaction.getLevels().join(", "); + contentContainer.addText("level: " + levels) => [ + fontBold = false + noSelectionStyle + suppressSelectability + ] + } catch (Exception ex) { + // If the graph has cycles, the above fails. Continue without showing levels. + } } // optional code content From 844ad52f851271b7f8a2182c057352b3021bee8e Mon Sep 17 00:00:00 2001 From: eal Date: Sat, 15 Jan 2022 17:22:36 -0800 Subject: [PATCH 155/221] Removed obsolete methods and fields previously used to provide reactor indices --- .../org/lflang/generator/ReactorInstance.java | 67 ------------------- 1 file changed, 67 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 55f88fed4f..8d50658dc0 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -227,22 +227,6 @@ public void clearCaches(boolean includingRuntimes) { } } - /** - * Return an integer is equal to one plus the total number of reactor instances - * (including bank members) that have been instantiated before this one - * within the same parent. If the number of instances cannot be determined, - * this will be 0. - * - * When this number can be determined, it can be used, for example, - * to index into an array of data structures - * representing instances of reactors in generated code. - * This assumes that index 0 refers to the parent, hence the "one plus." - */ - public int getIndexOffset() { - // FIXME FIXME get rid of this. - return indexOffset; - } - /** * Return the specified input by name or null if there is no such input. * @param name The input name. @@ -266,17 +250,6 @@ public String getName() { return this.definition.getName(); } - /** - * Return one plus the number of contained reactor instances in this - * reactor, if this can be determined, or -1 if not. This is 1 plus - * the total number of reactors in any contained reactor instances, - * taking into account their bank widths if necessary. - */ - public int getNumReactorInstances() { - // FIXME FIXME get rid of this. - return numReactorInstances; - } - /** * Return the specified output by name or null if there is no such output. * @param name The output name. @@ -318,18 +291,6 @@ public TriggerInstance getShutdownTrigger() { return shutdownTrigger; } - /** - * Return the total number of reactor instances associated with - * this reactor, if it can be determined, or -1 if not. This is equal - * to the result of getNumReactorInstances() times the bank width, - * as returned by width(). - */ - public int getTotalNumReactorInstances() { - // FIXME FIXME: get rid of this. - if (width < 0 || numReactorInstances < 0) return -1; - return width * numReactorInstances; - } - /** * If this reactor is a bank or any of its parents is a bank, * return the total number of runtime instances, which is the product @@ -813,7 +774,6 @@ private ReactorInstance( if (!recursive && (desiredDepth < 0 || this.depth < desiredDepth)) { // Instantiate children for this reactor instance. // While doing this, assign an index offset to each. - int offset = 1; for (Instantiation child : ASTUtils.allInstantiations(reactorDefinition)) { var childInstance = new ReactorInstance( child, @@ -823,17 +783,6 @@ private ReactorInstance( this.unorderedReactions ); this.children.add(childInstance); - int numChildReactors = childInstance.getTotalNumReactorInstances(); - if (this.numReactorInstances >= 0 && numChildReactors > 0) { - this.numReactorInstances += numChildReactors; - - childInstance.indexOffset = offset; - // Next child will have an offset augmented by the total number of - // reactor instances in this one. - offset += childInstance.getTotalNumReactorInstances(); - } else { - numReactorInstances = -1; - } } // Instantiate timers for this reactor instance @@ -1090,20 +1039,4 @@ private void setInitialWidth() { * Cache of the deep number of children. */ private int totalNumChildrenCache = -1; - - // FIXME FIXME: Get rid of the following. - - /** - * One plus the number of contained reactor instances - * (if a bank, in a bank element). - */ - private int numReactorInstances = 1; - - /** - * The offset relative to the parent. This is the sum of - * the total number of reactor instances of peer reactors - * (those with the same parent) that are instantiated before - * this in the parent. - */ - private int indexOffset = 0; } From 8ebd97eaf6c00571a16e603b727bf349081ce40c Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 09:29:51 -0800 Subject: [PATCH 156/221] Removed unused reactor ID and related fields and methods --- .../org/lflang/generator/ReactorInstance.java | 34 ------------------- 1 file changed, 34 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 8d50658dc0..9ce5df81d1 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -125,13 +125,6 @@ public ReactorInstance(Reactor reactor, ReactorInstance parent, ErrorReporter re */ public final List children = new ArrayList<>(); - /** - * The ID of this reactor instance. This is 0 for a top-level (main) reactor - * and increases for each created reactor in the order created until it - * reaches main's {@link #totalNumberOfChildren()}. - */ - public final int id; - /** The input port instances belonging to this reactor instance. */ public final List inputs = new ArrayList<>(); @@ -212,7 +205,6 @@ public void clearCaches() { */ public void clearCaches(boolean includingRuntimes) { if (includingRuntimes) cachedReactionLoopGraph = null; - totalNumChildrenCache = -1; for (ReactorInstance child : children) { child.clearCaches(includingRuntimes); } @@ -580,20 +572,6 @@ public String toString() { return "ReactorInstance " + getFullName(); } - /** - * Return the total number of children in this reactor - * and all its contained reactors. Each bank counts as one child. - */ - public int totalNumberOfChildren() { - if (totalNumChildrenCache < 0) { - totalNumChildrenCache = children.size(); - for (ReactorInstance containedReactor : children) { - totalNumChildrenCache += containedReactor.totalNumberOfChildren(); - } - } - return totalNumChildrenCache; - } - /** * Assuming that the given value denotes a valid time, return a time value. * @@ -721,7 +699,6 @@ private ReactorInstance( this.reporter = reporter; this.reactorDeclaration = definition.getReactorClass(); this.reactorDefinition = ASTUtils.toDefinition(reactorDeclaration); - this.id = root().childCount++; if (unorderedReactions != null) { this.unorderedReactions = unorderedReactions; @@ -1028,15 +1005,4 @@ private void setInitialWidth() { * Cached reaction graph containing reactions that form a causality loop. */ private ReactionInstanceGraph cachedReactionLoopGraph = null; - - /** - * Count of children created for assigning IDs. This should only be - * used by a top-level reactor. - */ - private int childCount = 0; - - /** - * Cache of the deep number of children. - */ - private int totalNumChildrenCache = -1; } From 200388fdc00e5d9cab39937c0c94678cbc04cbb8 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 11:39:10 -0800 Subject: [PATCH 157/221] Added comment that this is no longer used. --- org.lflang/src/org/lflang/graph/TopologyGraph.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.lflang/src/org/lflang/graph/TopologyGraph.java b/org.lflang/src/org/lflang/graph/TopologyGraph.java index f0be88c566..7f45915737 100644 --- a/org.lflang/src/org/lflang/graph/TopologyGraph.java +++ b/org.lflang/src/org/lflang/graph/TopologyGraph.java @@ -42,6 +42,9 @@ * A graph with vertices that are ports or reactions and edges that denote * dependencies between them. * + * NOTE: This is not used anywhere anymore, but we keep it in case this particular + * graph structure proves useful in the future. + * * @author Marten Lohstroh */ public class TopologyGraph extends PrecedenceGraph> { From d6a48c5e89ea99c24dd25c94f80b89076894f2d0 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 11:40:10 -0800 Subject: [PATCH 158/221] Use ReactionInstanceGraph rather than TopologyGraph and update cycle detection visualization and validation --- .../synthesis/util/CycleVisualization.xtend | 82 +++++----- .../LinguaFrancaDependencyAnalysisTest.xtend | 12 +- org.lflang/src/org/lflang/ModelInfo.java | 11 +- .../generator/ReactionInstanceGraph.java | 5 +- .../org/lflang/generator/ReactorInstance.java | 74 +++++++++ .../org/lflang/generator/TriggerInstance.java | 4 +- .../org/lflang/validation/LFValidator.xtend | 148 +++++++++--------- 7 files changed, 199 insertions(+), 137 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/CycleVisualization.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/CycleVisualization.xtend index e0824703a9..8a6bbce65e 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/CycleVisualization.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/util/CycleVisualization.xtend @@ -37,7 +37,6 @@ import org.eclipse.elk.graph.properties.Property import org.lflang.diagram.synthesis.AbstractSynthesisExtensions import org.lflang.generator.NamedInstance import org.lflang.generator.ReactorInstance -import org.lflang.graph.TopologyGraph import org.lflang.lf.Connection import static extension org.lflang.diagram.synthesis.util.NamedInstanceUtil.* @@ -60,56 +59,55 @@ class CycleVisualization extends AbstractSynthesisExtensions { * Performs cycle detection based on the diagram's graph structure and applies given highlighting to the included elements */ def boolean detectAndHighlightCycles(ReactorInstance rootReactorInstance, Map allReactorNodes, Consumer highlighter) { - val graph = new TopologyGraph(rootReactorInstance) - if (graph.hasCycles() && highlighter !== null) { + if (rootReactorInstance.hasCycles() && highlighter !== null) { // Highlight cycles - for (cycle : graph.cycles) { - // A cycle consists of reactions and ports, first find the involved reactor instances - val cycleElementsByReactor = HashMultimap.create - for (element : cycle) { - if (element instanceof ReactorInstance) { - cycleElementsByReactor.put(element, element) - } else { - cycleElementsByReactor.put(element.parent, element) - } + // A cycle consists of reactions and ports. + val cycleElementsByReactor = HashMultimap.create + val cycles = rootReactorInstance.getCycles + for (element : cycles) { + // First find the involved reactor instances + if (element instanceof ReactorInstance) { + cycleElementsByReactor.put(element, element) + } else { + cycleElementsByReactor.put(element.parent, element) } + } - for (reactor : cycleElementsByReactor.keySet) { - val node = allReactorNodes.get(reactor) - if (node !== null) { - node.setProperty(DEPENDENCY_CYCLE, true) - highlighter.accept(node) + for (reactor : cycleElementsByReactor.keySet) { + val node = allReactorNodes.get(reactor) + if (node !== null) { + node.setProperty(DEPENDENCY_CYCLE, true) + highlighter.accept(node) - val reactorContentInCycle = cycleElementsByReactor.get(reactor) - - // Reactor edges - for (edge : node.outgoingEdges) { - if (edge.connectsCycleElements(cycle)) { - edge.setProperty(DEPENDENCY_CYCLE, true) - highlighter.accept(edge) - } + val reactorContentInCycle = cycleElementsByReactor.get(reactor) + + // Reactor edges + for (edge : node.outgoingEdges) { + if (edge.connectsCycleElements(cycles)) { + edge.setProperty(DEPENDENCY_CYCLE, true) + highlighter.accept(edge) } + } - // Reactor ports - for (port : node.ports) { - if (reactorContentInCycle.contains(port.linkedInstance)) { - port.setProperty(DEPENDENCY_CYCLE, true) - highlighter.accept(port) - } + // Reactor ports + for (port : node.ports) { + if (reactorContentInCycle.contains(port.linkedInstance)) { + port.setProperty(DEPENDENCY_CYCLE, true) + highlighter.accept(port) } + } + + // Child Nodes + for (childNode : node.children) { + if (reactorContentInCycle.contains(childNode.linkedInstance) && !childNode.sourceIsReactor) { + childNode.setProperty(DEPENDENCY_CYCLE, true) + highlighter.accept(childNode) - // Child Nodes - for (childNode : node.children) { - if (reactorContentInCycle.contains(childNode.linkedInstance) && !childNode.sourceIsReactor) { - childNode.setProperty(DEPENDENCY_CYCLE, true) - highlighter.accept(childNode) - - for (edge : childNode.outgoingEdges) { - if (edge.connectsCycleElements(cycle)) { - edge.setProperty(DEPENDENCY_CYCLE, true) - highlighter.accept(edge) - } + for (edge : childNode.outgoingEdges) { + if (edge.connectsCycleElements(cycles)) { + edge.setProperty(DEPENDENCY_CYCLE, true) + highlighter.accept(edge) } } } diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend index 9d5535dfaf..a0a6e489c4 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaDependencyAnalysisTest.xtend @@ -35,7 +35,6 @@ import org.junit.jupiter.api.Test import org.junit.jupiter.api.^extension.ExtendWith import org.lflang.DefaultErrorReporter import org.lflang.ModelInfo -import org.lflang.generator.InvalidSourceException import org.lflang.generator.ReactorInstance import org.lflang.lf.Instantiation import org.lflang.lf.LfFactory @@ -103,15 +102,8 @@ class LinguaFrancaDependencyAnalysisTest { } } - try { - val instance = new ReactorInstance(mainDef.reactorClass.toDefinition, new DefaultErrorReporter()); - // FIXME: Why is the following not visible?????????????? - // new ReactionInstanceGraph(instance) - Assertions.fail("No cycle detected") - } catch(InvalidSourceException e) { - Assertions.assertTrue(e.message != null && e.message.contains("Reactions form a cycle!"), - "Should be a message about cycles: " + e.message) - } + val instance = new ReactorInstance(mainDef.reactorClass.toDefinition, new DefaultErrorReporter()); + Assertions.assertFalse(instance.getCycles().isEmpty()); } /** diff --git a/org.lflang/src/org/lflang/ModelInfo.java b/org.lflang/src/org/lflang/ModelInfo.java index d20f07a216..5c2da9c2c0 100644 --- a/org.lflang/src/org/lflang/ModelInfo.java +++ b/org.lflang/src/org/lflang/ModelInfo.java @@ -29,6 +29,7 @@ import static org.eclipse.xtext.xbase.lib.IteratorExtensions.toIterable; import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; @@ -36,7 +37,6 @@ import org.lflang.generator.NamedInstance; import org.lflang.generator.ReactorInstance; import org.lflang.graph.InstantiationGraph; -import org.lflang.graph.TopologyGraph; import org.lflang.lf.Assignment; import org.lflang.lf.Deadline; import org.lflang.lf.Instantiation; @@ -87,7 +87,7 @@ public class ModelInfo { public Set overflowingParameters; /** Cycles found during topology analysis. */ - private List>> topologyCycles = List.of(); + private Set> topologyCycles = new LinkedHashSet>(); /** * Whether or not the model information has been updated at least once. @@ -116,8 +116,9 @@ public void update(Model model, ErrorReporter reporter) { ); } // don't store the graph into a field, only the cycles. - var topologyGraph = new TopologyGraph(topLevelReactorInstances); - this.topologyCycles = topologyGraph.getCycles(); + for (ReactorInstance top : topLevelReactorInstances) { + this.topologyCycles.addAll(top.getCycles()); + } } // may be null if the target is invalid @@ -129,7 +130,7 @@ public void update(Model model, ErrorReporter reporter) { } } - public List>> topologyCycles() { + public Set> topologyCycles() { return this.topologyCycles; } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index b00fd08107..dfc3204d33 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -88,8 +88,9 @@ public void rebuild() { assignLevels(); if (nodeCount() != 0) { // The graph has cycles. - main.reporter.reportError("Reactions form a cycle! " + toString()); - throw new InvalidSourceException("Reactions form a cycle!"); + // main.reporter.reportError("Reactions form a cycle! " + toString()); + // Do not throw an exception so that cycle visualization can proceed. + // throw new InvalidSourceException("Reactions form a cycle!"); } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 9ce5df81d1..4d918f39ea 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -217,6 +217,38 @@ public void clearCaches(boolean includingRuntimes) { for (ReactionInstance reaction : reactions) { reaction.clearCaches(includingRuntimes); } + cachedCycles = null; + } + + /** + * Return the set of ReactionInstance and PortInstance that form causality + * loops in the topmost parent reactor of this reactor. This will return an + * empty set if there are no causality loops. + */ + public Set> getCycles() { + if (depth != 0) return root().getCycles(); + if (cachedCycles != null) return cachedCycles; + Set reactions = new LinkedHashSet(); + + ReactionInstanceGraph reactionRuntimes = assignLevels(); + for (ReactionInstance.Runtime runtime : reactionRuntimes.nodes()) { + reactions.add(runtime.getReaction()); + } + Set ports = new LinkedHashSet(); + // Need to figure out which ports are involved in the cycles. + // It may not be all ports that depend on this reaction. + for (ReactionInstance r : reactions) { + for (TriggerInstance p : r.effects) { + if (p instanceof PortInstance) { + findPaths((PortInstance)p, reactions, ports); + } + } + } + + cachedCycles = new LinkedHashSet>(); + cachedCycles.addAll(reactions); + cachedCycles.addAll(ports); + return cachedCycles; } /** @@ -346,6 +378,13 @@ public Set> getTriggersAndReads() { return triggers; } + /** + * Return true if the top-level parent of this reactor has causality cycles. + */ + public boolean hasCycles() { + return (assignLevels().nodeCount() != 0); + } + /** * Given a parameter definition for this reactor, return the initial integer * value of the parameter. If the parameter is overridden when instantiating @@ -891,6 +930,36 @@ private void establishPortConnections() { } } + /** + * If path exists from the specified port to any reaction in the specified + * set of reactions, then add the specified port and all ports along the path + * to the specified set of ports. + * @return True if the specified port was added. + */ + private boolean findPaths( + PortInstance port, + Set reactions, + Set ports + ) { + if (ports.contains(port)) return false; + boolean result = false; + for (ReactionInstance d : port.getDependentReactions()) { + if (reactions.contains(d)) ports.add(port); + result = true; + } + // Perform a depth-first search. + for (SendRange r : port.dependentPorts) { + for (RuntimeRange p : r.destinations) { + boolean added = findPaths(p.instance, reactions, ports); + if (added) { + result = true; + ports.add(port); + } + } + } + return result; + } + /** * Given a list of port references, as found on either side of a connection, * return a list of the port instance ranges referenced. These may be multiports, @@ -1001,6 +1070,11 @@ private void setInitialWidth() { ////////////////////////////////////////////////////// //// Private fields. + /** + * Cached set of reactions and ports that form a causality loop. + */ + private Set> cachedCycles; + /** * Cached reaction graph containing reactions that form a causality loop. */ diff --git a/org.lflang/src/org/lflang/generator/TriggerInstance.java b/org.lflang/src/org/lflang/generator/TriggerInstance.java index b17f15f710..21b02527ac 100644 --- a/org.lflang/src/org/lflang/generator/TriggerInstance.java +++ b/org.lflang/src/org/lflang/generator/TriggerInstance.java @@ -89,7 +89,7 @@ public BuiltinTrigger getBuiltinTriggerType() { } /** - * Return the reaction instances that are triggered by this trigger. + * Return the reaction instances that are triggered or read by this trigger. * If this port is an output, then the reaction instances * belong to the parent of the port's parent. If the port * is an input, then the reaction instances belong to the @@ -145,7 +145,7 @@ public boolean isBuiltinTrigger() { BuiltinTrigger builtinTriggerType = null; /** - * Reaction instances that are triggered by this trigger. + * Reaction instances that are triggered or read by this trigger. * If this port is an output, then the reaction instances * belong to the parent of the port's parent. If the port * is an input, then the reaction instances belong to the diff --git a/org.lflang/src/org/lflang/validation/LFValidator.xtend b/org.lflang/src/org/lflang/validation/LFValidator.xtend index 902d3ea405..741401dca5 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.xtend +++ b/org.lflang/src/org/lflang/validation/LFValidator.xtend @@ -368,32 +368,30 @@ class LFValidator extends BaseLFValidator { def checkConnection(Connection connection) { // Report if connection is part of a cycle. - for (cycle : this.info.topologyCycles()) { - for (lp : connection.leftPorts) { - for (rp : connection.rightPorts) { - var leftInCycle = false - val reactorName = (connection.eContainer as Reactor).name - - if ((lp.container === null && cycle.exists [ - it.definition === lp.variable - ]) || cycle.exists [ - (it.definition === lp.variable && it.parent === lp.container) - ]) { - leftInCycle = true - } + val cycles = this.info.topologyCycles(); + for (lp : connection.leftPorts) { + for (rp : connection.rightPorts) { + var leftInCycle = false + if ((lp.container === null && cycles.exists [ + it.definition === lp.variable + ]) || cycles.exists [ + (it.definition === lp.variable && it.parent === lp.container) + ]) { + leftInCycle = true + } - if ((rp.container === null && cycle.exists [ - it.definition === rp.variable - ]) || cycle.exists [ - (it.definition === rp.variable && it.parent === rp.container) - ]) { - if (leftInCycle) { - // Only report of _both_ reference ports are in the cycle. - error('''Connection in reactor «reactorName» creates ''' + - '''a cyclic dependency between «lp.toText» and ''' + - '''«rp.toText».''', Literals.CONNECTION__DELAY - ) - } + if ((rp.container === null && cycles.exists [ + it.definition === rp.variable + ]) || cycles.exists [ + (it.definition === rp.variable && it.parent === rp.container) + ]) { + if (leftInCycle) { + val reactorName = (connection.eContainer as Reactor).name + // Only report of _both_ reference ports are in the cycle. + error('''Connection in reactor «reactorName» creates ''' + + '''a cyclic dependency between «lp.toText» and ''' + + '''«rp.toText».''', Literals.CONNECTION__DELAY + ) } } } @@ -856,66 +854,64 @@ class LFValidator extends BaseLFValidator { } // Report error if this reaction is part of a cycle. - for (cycle : this.info.topologyCycles()) { - val reactor = (reaction.eContainer) as Reactor - if (cycle.exists[it.definition === reaction]) { - // Report involved triggers. - val trigs = new ArrayList() - reaction.triggers.forEach [ t | - (t instanceof VarRef && cycle.exists [ c | - c.definition === (t as VarRef).variable - ]) ? trigs.add((t as VarRef).toText) : { - } - ] - if (trigs.size > 0) { - error('''Reaction triggers involved in cyclic dependency in reactor «reactor.name»: «trigs.join(', ')».''', - Literals.REACTION__TRIGGERS) + val cycles = this.info.topologyCycles(); + val reactor = (reaction.eContainer) as Reactor + if (cycles.exists[it.definition === reaction]) { + // Report involved triggers. + val trigs = new ArrayList() + reaction.triggers.forEach [ t | + (t instanceof VarRef && cycles.exists [ c | + c.definition === (t as VarRef).variable + ]) ? trigs.add((t as VarRef).toText) : { } + ] + if (trigs.size > 0) { + error('''Reaction triggers involved in cyclic dependency in reactor «reactor.name»: «trigs.join(', ')».''', + Literals.REACTION__TRIGGERS) + } - // Report involved sources. - val sources = new ArrayList() - reaction.sources.forEach [ t | - (cycle.exists[c|c.definition === t.variable]) - ? sources.add(t.toText) - : { - } - ] - if (sources.size > 0) { - error('''Reaction sources involved in cyclic dependency in reactor «reactor.name»: «sources.join(', ')».''', - Literals.REACTION__SOURCES) + // Report involved sources. + val sources = new ArrayList() + reaction.sources.forEach [ t | + (cycles.exists[c|c.definition === t.variable]) + ? sources.add(t.toText) + : { } + ] + if (sources.size > 0) { + error('''Reaction sources involved in cyclic dependency in reactor «reactor.name»: «sources.join(', ')».''', + Literals.REACTION__SOURCES) + } - // Report involved effects. - val effects = new ArrayList() - reaction.effects.forEach [ t | - (cycle.exists[c|c.definition === t.variable]) - ? effects.add(t.toText) - : { - } - ] - if (effects.size > 0) { - error('''Reaction effects involved in cyclic dependency in reactor «reactor.name»: «effects.join(', ')».''', - Literals.REACTION__EFFECTS) + // Report involved effects. + val effects = new ArrayList() + reaction.effects.forEach [ t | + (cycles.exists[c|c.definition === t.variable]) + ? effects.add(t.toText) + : { } + ] + if (effects.size > 0) { + error('''Reaction effects involved in cyclic dependency in reactor «reactor.name»: «effects.join(', ')».''', + Literals.REACTION__EFFECTS) + } - if (trigs.size + sources.size == 0) { - error( - '''Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', - reaction.eContainer, - Literals.REACTOR__REACTIONS, - reactor.reactions.indexOf(reaction)) - } else if (effects.size == 0) { - error( - '''Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', + if (trigs.size + sources.size == 0) { + error( + '''Cyclic dependency due to preceding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', reaction.eContainer, - Literals.REACTOR__REACTIONS, - reactor.reactions.indexOf(reaction)) - } - // Not reporting reactions that are part of cycle _only_ due to reaction ordering. - // Moving them won't help solve the problem. + Literals.REACTOR__REACTIONS, + reactor.reactions.indexOf(reaction)) + } else if (effects.size == 0) { + error( + '''Cyclic dependency due to succeeding reaction. Consider reordering reactions within reactor «reactor.name» to avoid causality loop.''', + reaction.eContainer, + Literals.REACTOR__REACTIONS, + reactor.reactions.indexOf(reaction)) } + // Not reporting reactions that are part of cycle _only_ due to reaction ordering. + // Moving them won't help solve the problem. } - // FIXME: improve error message. } @Check(FAST) From b877ea0eff2ea212381f1df67d8ac45b934c7e4f Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 13:40:28 -0800 Subject: [PATCH 159/221] Made class public --- org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index dfc3204d33..fbcfee645e 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -54,7 +54,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * @author{Marten Lohstroh } * @author{Edward A. Lee } */ -class ReactionInstanceGraph extends DirectedGraph { +public class ReactionInstanceGraph extends DirectedGraph { /** * Create a new graph by traversing the maps in the named instances From 1bbddef59409c6b57fc17c4b963a16ee7f90e993 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 13:41:23 -0800 Subject: [PATCH 160/221] Skip code generation if there are cuasality cycles. Apparently, validation errors no longer prevent code generation. --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 8ef48ce276..129367ab2b 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -505,7 +505,10 @@ class CGenerator extends GeneratorBase { // it is the same for all federates. this.main = new ReactorInstance(mainDef.reactorClass.toDefinition, errorReporter, this.unorderedReactions) - this.main.assignLevels(); + if (this.main.assignLevels().nodeCount > 0) { + errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); + return; + } // Avoid compile errors by removing disconnected network ports. // This must be done after assigning levels. removeRemoteFederateConnectionPorts(main); @@ -670,7 +673,8 @@ class CGenerator extends GeneratorBase { } // Generate main instance, if there is one. - // Note that any main reactors in imported files are ignored. + // Note that any main reactors in imported files are ignored. + // Skip generation if there are cycles. if (this.main !== null) { generateMain() // Generate function to set default command-line options. From f200a27ee0b67dd4d19d34a5909cd714c8a5a351 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 15:30:51 -0800 Subject: [PATCH 161/221] Filter out connections with after delays when finding eventual destinations --- org.lflang/src/org/lflang/generator/PortInstance.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 3262851227..3842074bfb 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -114,6 +114,8 @@ public void clearCaches() { * 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. + * Also, if there is an "after" delay anywhere along the path, + * then the destination is not in the resulting list. * * If this port itself has dependent reactions, * then this port will be included as a destination in all items @@ -280,7 +282,14 @@ private static List eventualDestinations(RuntimeRange s // Need to find send ranges that overlap with this srcRange. Iterator sendRanges = srcPort.dependentPorts.iterator(); while (sendRanges.hasNext()) { - SendRange wSendRange = sendRanges.next().overlap(srcRange); + + SendRange wSendRange = sendRanges.next(); + + if (wSendRange.connection != null && wSendRange.connection.getDelay() != null) { + continue; + } + + wSendRange = wSendRange.overlap(srcRange); if (wSendRange == null) { // This send range does not overlap with the desired range. Try the next one. continue; From 49b9bf0dc2ce722a38c8939d87fb6209f20980d6 Mon Sep 17 00:00:00 2001 From: eal Date: Sun, 16 Jan 2022 17:08:08 -0800 Subject: [PATCH 162/221] Tolerate unknown widths in more places --- .../diagram/synthesis/LinguaFrancaSynthesis.xtend | 5 +++-- .../styles/LinguaFrancaShapeExtensions.xtend | 7 +++++-- org.lflang/src/org/lflang/ASTUtils.xtend | 4 +++- .../src/org/lflang/generator/MixedRadixInt.java | 3 ++- .../src/org/lflang/generator/PortInstance.java | 13 +++++++------ .../org/lflang/generator/ReactionInstance.java | 2 ++ .../src/org/lflang/generator/ReactorInstance.java | 15 ++++++++++++--- .../src/org/lflang/generator/RuntimeRange.java | 10 ++++++++-- 8 files changed, 42 insertions(+), 17 deletions(-) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend index 7bcfc2eac1..014aa69ef6 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/LinguaFrancaSynthesis.xtend @@ -1039,8 +1039,9 @@ class LinguaFrancaSynthesis extends AbstractDiagramSynthesis { } if (SHOW_MULTIPORT_WIDTH.booleanValue) { if (lfPort.isMultiport) { - // TODO Fix unresolvable references in ReactorInstance - label += "[" + lfPort.width + "]" + label += (lfPort.width >= 0)? + "[" + lfPort.width + "]" + : "[?]" } } port.addOutsidePortLabel(label, 8).associateWith(lfPort.definition) diff --git a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend index 929cea76c1..57ad610cea 100644 --- a/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend +++ b/org.lflang.diagram/src/org/lflang/diagram/synthesis/styles/LinguaFrancaShapeExtensions.xtend @@ -228,9 +228,12 @@ class LinguaFrancaShapeExtensions extends AbstractSynthesisExtensions { addRectangle() => [ invisible = true setAreaPlacementData().from(LEFT, 12, 0, BOTTOM, 9, 0).to(RIGHT, 6, 0, BOTTOM, 0.5f, 0) - // TODO handle unresolved width + // Handle unresolved width. + val widthLabel = (reactorInstance.width >= 0)? + Integer.toString(reactorInstance.width) + : "?" // addText(instance.widthSpec.toText) => [ - addText(Integer.toString(reactorInstance.width)) => [ + addText(widthLabel) => [ horizontalAlignment = HorizontalAlignment.LEFT verticalAlignment = VerticalAlignment.BOTTOM fontSize = 6 diff --git a/org.lflang/src/org/lflang/ASTUtils.xtend b/org.lflang/src/org/lflang/ASTUtils.xtend index cc78dfae17..06a6d4b6f6 100644 --- a/org.lflang/src/org/lflang/ASTUtils.xtend +++ b/org.lflang/src/org/lflang/ASTUtils.xtend @@ -1290,8 +1290,10 @@ class ASTUtils { } else { return -1; } - } else { + } else if (term.width > 0) { result += term.width; + } else { + return -1; } } return result; diff --git a/org.lflang/src/org/lflang/generator/MixedRadixInt.java b/org.lflang/src/org/lflang/generator/MixedRadixInt.java index 9fd9fc7470..3ac703f226 100644 --- a/org.lflang/src/org/lflang/generator/MixedRadixInt.java +++ b/org.lflang/src/org/lflang/generator/MixedRadixInt.java @@ -77,7 +77,8 @@ public MixedRadixInt( ) { if (radixes == null || (digits != null && digits.size() > radixes.size()) - || (permutation != null && permutation.size() != radixes.size())) { + || (permutation != null && permutation.size() != radixes.size()) + || radixes.contains(0)) { throw new IllegalArgumentException("Invalid constructor arguments."); } this.radixes = radixes; diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 3842074bfb..254cebe704 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -395,7 +395,7 @@ private List> eventualSources(RuntimeRange getRuntimeInstances() { if (runtimeInstances != null) return runtimeInstances; int size = parent.getTotalWidth(); + // If the width cannot be determined, assume there is only one instance. + if (size < 0) size = 1; runtimeInstances = new ArrayList(size); for (int i = 0; i < size; i++) { Runtime r = new Runtime(); diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 4d918f39ea..51fbe49775 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -1020,7 +1020,14 @@ private List> listPortInstances( // but the range width may be reflective of bank structure higher // in the hierarchy. if (count < references.size() - 1) { - int widthBound = portInstance.width * portInstance.parent.width; + int portWidth = portInstance.width; + int portParentWidth = portInstance.parent.width; + int widthBound = portWidth * portParentWidth; + + // If either of these widths cannot be determined, assume infinite. + if (portWidth < 0) widthBound = Integer.MAX_VALUE; + if (portParentWidth < 0) widthBound = Integer.MAX_VALUE; + if (widthBound < range.width) { // Need to split the range. tails.add(range.tail(widthBound)); @@ -1040,6 +1047,9 @@ private List> listPortInstances( if (tail._interleaved.contains(tail.instance.parent)) { widthBound = tail.instance.parent.width; } + // If the width cannot be determined, assume infinite. + if (widthBound < 0) widthBound = Integer.MAX_VALUE; + if (widthBound < tail.width) { // Need to split the range again moreTails.add(tail.tail(widthBound)); @@ -1055,8 +1065,7 @@ private List> listPortInstances( /** * If this is a bank of reactors, set the width. - * It will be set to -1 if it cannot - * be determined. + * It will be set to -1 if it cannot be determined. */ private void setInitialWidth() { WidthSpec widthSpec = definition.getWidthSpec(); diff --git a/org.lflang/src/org/lflang/generator/RuntimeRange.java b/org.lflang/src/org/lflang/generator/RuntimeRange.java index fcbe4a4df2..d865cc9680 100644 --- a/org.lflang/src/org/lflang/generator/RuntimeRange.java +++ b/org.lflang/src/org/lflang/generator/RuntimeRange.java @@ -430,10 +430,16 @@ public List permutation() { */ public List radixes() { List result = new ArrayList(instance.depth); - result.add(instance.width); + int width = instance.width; + // If the width cannot be determined, assume 1. + if (width < 0) width = 1; + result.add(width); ReactorInstance p = instance.getParent(); while (p != null && p.getDepth() > 0) { - result.add(p.getWidth()); + width = p.getWidth(); + // If the width cannot be determined, assume 1. + if (width < 0) width = 1; + result.add(width); p = p.getParent(); } return result; From 194ec28789cac59b30ed90a516d69f8472e1162c Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 17 Jan 2022 08:52:10 -0800 Subject: [PATCH 163/221] Make minute not min the canonical name for minute units to not collide with min for minimum. --- org.lflang/src/org/lflang/TimeUnit.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/TimeUnit.java b/org.lflang/src/org/lflang/TimeUnit.java index c2ebbe3ac4..e3dc2fce78 100644 --- a/org.lflang/src/org/lflang/TimeUnit.java +++ b/org.lflang/src/org/lflang/TimeUnit.java @@ -46,8 +46,8 @@ public enum TimeUnit { MILLI("msec", "ms", "msecs"), /** Seconds. */ SECOND("sec", "s", "secs", "second", "seconds"), - /** Minute. */ - MINUTE("min", "mins", "minute", "minutes"), + /** Minute. */ // NOTE: Do not use MIN as the first entry. Common macro for minimum. + MINUTE("minute", "min", "mins", "minutes"), /** Hour. */ HOUR("hour", "h", "hours"), /** Day. */ From 1771216d37836050289b4c1734b9b976adc4fff6 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 17 Jan 2022 08:52:44 -0800 Subject: [PATCH 164/221] Prevent NPE on complicated combinations of levels --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 129367ab2b..c60d7c573d 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6055,12 +6055,14 @@ class CGenerator extends GeneratorBase { ); same = false; start = runtime.id; - domStart = runtime.dominating.id; + domStart = (runtime.dominating !== null) ? runtime.dominating.id : 0; } } else if (runtime.dominating == previousRuntime.dominating) { // Start of a streak of identical dominating reaction runtime instances. same = true; - } else if (runtime.dominating.reaction == previousRuntime.dominating.reaction) { + } else if (runtime.dominating !== null && previousRuntime.dominating !== null + && runtime.dominating.reaction == previousRuntime.dominating.reaction + ) { // Same dominating reaction even if not the same dominating runtime. if (runtime.dominating.id != previousRuntime.dominating.id + 1) { // End of a streak of contiguous runtimes. @@ -6078,7 +6080,7 @@ class CGenerator extends GeneratorBase { ); same = false; start = runtime.id; - domStart = runtime.dominating.id; + domStart = (runtime.dominating !== null) ? runtime.dominating.id : 0; } } first = false; From 5214b2d916ee9773f3b50035acefde25f07921c5 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Tue, 11 Jan 2022 00:38:56 -0600 Subject: [PATCH 165/221] Added exit code and stack trace printouts for failed tests --- org.lflang.tests/src/org/lflang/tests/LFTest.java | 6 +++++- org.lflang.tests/src/org/lflang/tests/TestBase.java | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/LFTest.java b/org.lflang.tests/src/org/lflang/tests/LFTest.java index 0f875036b1..64a6691fca 100644 --- a/org.lflang.tests/src/org/lflang/tests/LFTest.java +++ b/org.lflang.tests/src/org/lflang/tests/LFTest.java @@ -28,6 +28,9 @@ public class LFTest implements Comparable { /** The result of the test. */ public Result result = Result.UNKNOWN; + + /** The exit code of the test. **/ + public String exitValue = "?"; /** Object used to determine where the code generator puts files. */ public FileConfig fileConfig; @@ -128,7 +131,7 @@ public String reportErrors() { sb.append("+---------------------------------------------------------------------------+").append(System.lineSeparator()); sb.append("Failed: ").append(this.name).append(System.lineSeparator()); sb.append("-----------------------------------------------------------------------------").append(System.lineSeparator()); - sb.append("Reason: ").append(this.result.message).append(System.lineSeparator()); + sb.append("Reason: ").append(this.result.message).append(" Exit code: ").append(this.exitValue).append(System.lineSeparator()); appendIfNotEmpty("Reported issues", this.issues.toString(), sb); appendIfNotEmpty("Compilation output", this.compilationLog.toString(), sb); appendIfNotEmpty("Execution output", this.execLog.toString(), sb); @@ -164,6 +167,7 @@ public enum Result { CODE_GEN_FAIL("Error while generating code for test."), NO_EXEC_FAIL("Did not execute test."), TEST_FAIL("Test did not pass."), + TEST_EXCEPTION("Test exited with an exception."), TEST_TIMEOUT("Test timed out."), TEST_PASS("Test passed."); diff --git a/org.lflang.tests/src/org/lflang/tests/TestBase.java b/org.lflang.tests/src/org/lflang/tests/TestBase.java index 37b26b43af..da95042637 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestBase.java +++ b/org.lflang.tests/src/org/lflang/tests/TestBase.java @@ -5,6 +5,8 @@ import java.io.IOException; import java.io.PrintStream; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; @@ -466,12 +468,18 @@ private void execute(LFTest test) { } else { if (p.exitValue() != 0) { test.result = Result.TEST_FAIL; + test.exitValue = Integer.toString(p.exitValue()); return; } } } } catch (Exception e) { - test.result = Result.TEST_FAIL; + test.result = Result.TEST_EXCEPTION; + // Add the stack trace to the test output + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + e.printStackTrace(pw); + test.execLog.buffer.append(sw.toString()); return; } test.result = Result.TEST_PASS; From df7e250aa412d156346470db8ccb41e16d7bba53 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 18 Jan 2022 10:43:54 -0800 Subject: [PATCH 166/221] Fixed transposition error on arguments --- org.lflang/src/org/lflang/generator/c/CUtil.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index ed2fbcd850..ed5a31bd00 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -499,7 +499,7 @@ static public String triggerRefNested(PortInstance port) { * {@link CUtil#bankIndex(ReactorInstance)}. */ static public String triggerRefNested(PortInstance port, String runtimeIndex, String bankIndex) { - return reactorRefNested(port.getParent(), bankIndex, runtimeIndex) + "." + port.getName() + "_trigger"; + return reactorRefNested(port.getParent(), runtimeIndex, bankIndex) + "." + port.getName() + "_trigger"; } ////////////////////////////////////////////////////// From fd890d1650c3724fecd6ca795ae91b04a9c64d60 Mon Sep 17 00:00:00 2001 From: eal Date: Tue, 18 Jan 2022 10:44:23 -0800 Subject: [PATCH 167/221] Do not generate dead code --- .../src/org/lflang/generator/c/CGenerator.xtend | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index c60d7c573d..5ce477f9a4 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6309,13 +6309,17 @@ class CGenerator extends GeneratorBase { for (reaction: reactions) { val name = reaction.parent.getFullName; - // Need a separate index for the triggers array for each bank member. - startScopedBlock(code); - pr(''' - int triggers_index[«reaction.parent.totalWidth»] = { 0 }; // Number of banks. - ''') + var foundPort = false; for (port : reaction.effects.filter(PortInstance)) { + if (!foundPort) { + // Need a separate index for the triggers array for each bank member. + startScopedBlock(code); + pr(''' + int triggers_index[«reaction.parent.totalWidth»] = { 0 }; // Number of bank members with the reaction. + ''') + foundPort = true; + } // If the port is a multiport, then its channels may have different sets // of destinations. For ordinary ports, there will be only one range and // its width will be 1. @@ -6411,7 +6415,7 @@ class CGenerator extends GeneratorBase { } cumulativePortWidth += port.width; } - endScopedBlock(code); + if (foundPort) endScopedBlock(code); } } From 4eafd9888fcd78c72aafff9b3f0eee0e5b7af979 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 13:50:09 -0800 Subject: [PATCH 168/221] Python: Fix syntax error. --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 475d3337c9..3f208257a9 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -699,7 +699,7 @@ class PythonGenerator extends CGenerator { pythonClassesInstantiation. append(''' «instance.uniqueID»_lf = \ - _«className»(bank_index = 0»,\ + _«className»(bank_index = 0,\ «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR») ''') for (var i = 1; i < instance.width; i++) { From 8092f9ced0881aebce84649d6f930149ae94ed84 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 15:46:18 -0800 Subject: [PATCH 169/221] Python: Fix other syntax error. --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 3f208257a9..5543f3d917 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -698,7 +698,7 @@ class PythonGenerator extends CGenerator { // a list of instances of reactors and return. pythonClassesInstantiation. append(''' - «instance.uniqueID»_lf = \ + «instance.uniqueID»_lf = [\ _«className»(bank_index = 0,\ «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR») ''') @@ -706,7 +706,7 @@ class PythonGenerator extends CGenerator { pythonClassesInstantiation. append(''' _«className»(bank_index = «i»,\ - «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), \\\n + «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), \ ''') } pythonClassesInstantiation. From b2dfe3f11a3fdbc1ce8e198ea5b816b7c0838675 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 21:15:52 -0800 Subject: [PATCH 170/221] Update RELEASES.md. --- RELEASES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/RELEASES.md b/RELEASES.md index a892af9b00..5a02a8766c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,6 @@ # Version 0.1.0-beta-SNAPSHOT +- LF programs with the TypeScript target can now be compiled on Windows (#850). +- Generated code is validated on save for all targets except C (#828) ## Language From 2ceac10ac6d9b6def20d8a634dfbcc075653d794 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 22:33:33 -0800 Subject: [PATCH 171/221] Update RELEASES.md. --- RELEASES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASES.md b/RELEASES.md index 5a02a8766c..e35e3e07ee 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,6 +1,6 @@ # Version 0.1.0-beta-SNAPSHOT - LF programs with the TypeScript target can now be compiled on Windows (#850). -- Generated code is validated on save for all targets except C (#828) +- In the VS Code extension, generated code is validated when an LF file is saved for all targets except C (#828). Generated C code is only validated when it is fully compiled. ## Language From 01e7fee4734d25ccf04aa39e11e55446652a57ee Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 22:41:50 -0800 Subject: [PATCH 172/221] CI: Re-order steps. This responds to the following error: "pathspec 'D:\a\lingua-franca\lingua-franca\vcpkg' did not match any file(s) known to git". --- .github/workflows/ci.yml | 2 +- .github/workflows/lsp-tests.yml | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c9a048535..000312975b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: # Run language server tests. lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master + uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@vscode-validate-generated-code-cleanups # Run the C integration tests. c-tests: diff --git a/.github/workflows/lsp-tests.yml b/.github/workflows/lsp-tests.yml index a0b5e5ea7b..4e354bd2e5 100644 --- a/.github/workflows/lsp-tests.yml +++ b/.github/workflows/lsp-tests.yml @@ -29,12 +29,6 @@ jobs: uses: actions/setup-java@v1.4.3 with: java-version: 11 - - name: Check out lingua-franca repository - uses: actions/checkout@v2 - with: - repository: lf-lang/lingua-franca - submodules: true - ref: ${{ inputs.compiler-ref }} - name: Setup Node.js environment uses: actions/setup-node@v2.1.2 - name: Install pnpm @@ -61,6 +55,12 @@ jobs: vcpkgDirectory: ${{ github.workspace }}/vcpkg/ vcpkgTriplet: x64-windows-static if: ${{ runner.os == 'Windows' }} + - name: Check out lingua-franca repository + uses: actions/checkout@v2 + with: + repository: lf-lang/lingua-franca + submodules: true + ref: ${{ inputs.compiler-ref }} - name: Run language server Python tests without PyLint run: ./gradlew test --tests org.lflang.tests.lsp.LspTests.pythonSyntaxOnlyValidationTest - name: Report to CodeCov From f5aae38216298da7e0070cb2c9505a7b43f4f138 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Tue, 18 Jan 2022 23:20:58 -0800 Subject: [PATCH 173/221] Python: Handle empty output. This problem does not manifest on my machine, so I can only hope that this solves it. --- .../src/org/lflang/generator/python/PythonValidator.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index dbbd60fa89..8ac5aa33f6 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -246,7 +246,8 @@ public Strategy getErrorReportingStrategy() { public Strategy getOutputReportingStrategy() { return (validationOutput, errorReporter, codeMaps) -> { try { - for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) { + if (validationOutput.isBlank()) return; + for (PylintMessage message : mapper.readValue(validationOutput.strip(), PylintMessage[].class)) { if (shouldIgnore(message)) continue; CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath())); if (map != null) { From 5910b6816331f975c02d64d535665777a57a7218 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 10:00:19 -0800 Subject: [PATCH 174/221] Python: Related syntax error. --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 5543f3d917..aaa0e999cb 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -700,7 +700,7 @@ class PythonGenerator extends CGenerator { append(''' «instance.uniqueID»_lf = [\ _«className»(bank_index = 0,\ - «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR») + «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), ''') for (var i = 1; i < instance.width; i++) { pythonClassesInstantiation. From b6285a9557e220b34a32ebe8f19c08b00b698278 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 19 Jan 2022 13:44:38 -0800 Subject: [PATCH 175/221] Generate include of mixed_radix.h. --- org.lflang/src/lib/c/reactor-c | 2 +- .../org/lflang/generator/c/CGenerator.xtend | 22 ++++++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 38d8257b68..6594353019 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 38d8257b68818207b113624529e95a3c3088f2f5 +Subproject commit 65943530193ac6eba45e81878b35ff7ba93745ed diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index dce55fed61..dfb939086f 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -525,6 +525,7 @@ class CGenerator extends GeneratorBase { if (!dir.exists()) dir.mkdirs() targetConfig.compileAdditionalSources.add("ctarget.c"); + targetConfig.compileAdditionalSources.add("core" + File.separator + "mixed_radix.c"); // Copy the required core library files into the target file system. // This will overwrite previous versions. @@ -542,7 +543,9 @@ class CGenerator extends GeneratorBase { "util.h", "util.c", "platform.h", - "platform/Platform.cmake" + "platform/Platform.cmake", + "mixed_radix.c", + "mixed_radix.h" ); if (targetConfig.threads === 0) { coreFiles.add("reactor.c") @@ -4525,14 +4528,13 @@ class CGenerator extends GeneratorBase { } } - /** Generate #include of pqueue.c and either reactor.c or reactor_threaded.c - * depending on whether threads are specified in target directive. - * As a side effect, this populates the runCommand and compileCommand - * private variables if such commands are specified in the target directive. + /** + * Generate code that needs appear at the top of the generated + * C file, such as #define and #include statements. */ def generatePreamble() { pr(this.defineLogLevel) - + if (isFederated) { // FIXME: Instead of checking // #ifdef FEDERATED, we could @@ -4552,11 +4554,9 @@ class CGenerator extends GeneratorBase { #define FEDERATED_DECENTRALIZED ''') } - } - // Handle target parameters. - // First, if there are federates, then ensure that threading is enabled. - if (isFederated) { + // Handle target parameters. + // First, if there are federates, then ensure that threading is enabled. for (federate : federates) { // The number of threads needs to be at least one larger than the input ports // to allow the federate to wait on all input ports while allowing an additional @@ -4578,6 +4578,8 @@ class CGenerator extends GeneratorBase { } includeTargetLanguageSourceFiles() + + pr("#include \"core/mixed_radix.h\""); // Do this after the above includes so that the preamble can // call built-in functions. From 3f3e6de3dff8112f2b69d08f498b961d5938d040 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 15:31:21 -0800 Subject: [PATCH 176/221] Revert "Python: Handle empty output." This reverts commit f5aae38216298da7e0070cb2c9505a7b43f4f138. --- .../src/org/lflang/generator/python/PythonValidator.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index 8ac5aa33f6..dbbd60fa89 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -246,8 +246,7 @@ public Strategy getErrorReportingStrategy() { public Strategy getOutputReportingStrategy() { return (validationOutput, errorReporter, codeMaps) -> { try { - if (validationOutput.isBlank()) return; - for (PylintMessage message : mapper.readValue(validationOutput.strip(), PylintMessage[].class)) { + for (PylintMessage message : mapper.readValue(validationOutput, PylintMessage[].class)) { if (shouldIgnore(message)) continue; CodeMap map = codeMaps.get(message.getPath(fileConfig.getSrcGenPath())); if (map != null) { From 5195c9331372e89a76840ed21aaa8ee3a974d175 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 15:33:17 -0800 Subject: [PATCH 177/221] LFCommand: Do not forget to shut down thread pool. This seems to have prevented normal program termination (it hangs when done). --- org.lflang/src/org/lflang/generator/Validator.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index 718ee2a5d6..5951172825 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -13,6 +13,7 @@ import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.stream.Collectors; @@ -85,9 +86,11 @@ private static List> getFutures(List> tasks) throws In } break; default: - futures = Executors.newFixedThreadPool( + ExecutorService service = Executors.newFixedThreadPool( Math.min(Runtime.getRuntime().availableProcessors(), tasks.size()) - ).invokeAll(tasks); + ); + futures = service.invokeAll(tasks); + service.shutdown(); } return futures; } From 85683a581bfabd85616c0c142cfeef885f8ebc07 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 19 Jan 2022 15:35:34 -0800 Subject: [PATCH 178/221] Suppress warnings by using size_t rather than int --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index dfb939086f..12f5d4c574 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -1820,7 +1820,7 @@ class CGenerator extends GeneratorBase { for (reaction : reactor.reactions) { if (federate === null || federate.contains(reaction)) { pr(destructorCode, ''' - for(int i = 0; i < self->_lf__reaction_«reactionCount».num_outputs; i++) { + for(size_t i = 0; i < self->_lf__reaction_«reactionCount».num_outputs; i++) { free(self->_lf__reaction_«reactionCount».triggers[i]); } ''') From 422cf10482bba8e3a4ae45782bd10d77b6e114a4 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 19 Jan 2022 15:36:19 -0800 Subject: [PATCH 179/221] Use CUtil.bankIndex rather than hardcoding the bank index variable name. --- .../org/lflang/generator/python/PythonGenerator.xtend | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index aaa0e999cb..01f94c2c44 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -54,12 +54,11 @@ import org.lflang.generator.CodeMap import org.lflang.generator.GeneratorResult import org.lflang.generator.IntegratedBuilder import org.lflang.generator.JavaGeneratorUtils -import org.lflang.generator.ParameterInstance import org.lflang.generator.LFGeneratorContext -import org.lflang.generator.SubContext +import org.lflang.generator.ParameterInstance import org.lflang.generator.ReactionInstance import org.lflang.generator.ReactorInstance -import org.lflang.generator.c.CCompiler +import org.lflang.generator.SubContext import org.lflang.generator.c.CGenerator import org.lflang.generator.c.CUtil import org.lflang.lf.Action @@ -931,6 +930,8 @@ class PythonGenerator extends CGenerator { includeTargetLanguageHeaders() + pr("#include \"core/mixed_radix.h\""); + pr('#define NUMBER_OF_FEDERATES ' + federates.size); // Handle target parameters. @@ -1714,7 +1715,7 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» = get_python_function("«topLevelName»", «nameOfSelfStruct»->_lf_name, - «IF (instance.isBank)» i_«instance.depth» «ELSE» «0» «ENDIF», + «CUtil.bankIndex(instance)», "«pythonFunctionName»"); ''') @@ -1723,7 +1724,7 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = get_python_function("«topLevelName»", «nameOfSelfStruct»->_lf_name, - «IF (instance.isBank)» i_«instance.depth» «ELSE» «0» «ENDIF», + «CUtil.bankIndex(instance)», "deadline_function_«reaction.index»"); ''') } From 20691b122fd203b2c92f0814d7427f6f12da18d8 Mon Sep 17 00:00:00 2001 From: eal Date: Wed, 19 Jan 2022 15:36:37 -0800 Subject: [PATCH 180/221] Updated reactor-c version --- org.lflang/src/lib/c/reactor-c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 6594353019..4594f75988 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 65943530193ac6eba45e81878b35ff7ba93745ed +Subproject commit 4594f7598814e5d2b00f02fbde2ab7267d56c7e7 From 249904e33bddf59d996f939e26bc18aa40f3bdd3 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 16:27:29 -0800 Subject: [PATCH 181/221] Python: Tolerate output from an older Pylint version. --- .../generator/python/PythonValidator.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonValidator.java b/org.lflang/src/org/lflang/generator/python/PythonValidator.java index dbbd60fa89..f68a6520ef 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonValidator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonValidator.java @@ -21,6 +21,8 @@ import org.lflang.generator.Validator; import org.lflang.util.LFCommand; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.DeserializationFeature; @@ -62,8 +64,8 @@ private static final class PylintMessage { private String obj; private int line; private int column; - private int endLine; - private int endColumn; + private Integer endLine; + private Integer endColumn; private Path path; private String symbol; private String message; @@ -81,7 +83,10 @@ private static final class PylintMessage { @JsonProperty("message-id") public void setMessageId(String messageId) { this.messageId = messageId; } public Position getStart() { return Position.fromZeroBased(line - 1, column); } - public Position getEnd() { return Position.fromZeroBased(endLine - 1, endColumn); } + public Position getEnd() { + return endLine == null || endColumn == null ? getStart().plus(" ") : + Position.fromZeroBased(endLine - 1, endColumn); + } public Path getPath(Path relativeTo) { return relativeTo.resolve(path); } public DiagnosticSeverity getSeverity() { // The following is consistent with VS Code's default behavior for pure Python: @@ -272,8 +277,10 @@ public Strategy getOutputReportingStrategy() { } } } catch (JsonProcessingException e) { - // This should be impossible unless Pylint's API changes. Maybe it's fine to fail quietly - // like this in case that happens -- this will go to stderr, so you can see it if you look. + errorReporter.reportWarning( + "Failed to parse linter output. The Lingua Franca code generator is tested with Pylint " + + "version 2.12.2. Consider updating PyLint if you have an older version." + ); e.printStackTrace(); } }; From d16cb04d4a6446585a7795d144f0548b15f8c01a Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Wed, 19 Jan 2022 16:55:15 -0800 Subject: [PATCH 182/221] Error reporting: One more special case for code generators. --- org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt | 3 ++- org.lflang/src/org/lflang/generator/rust/RustGenerator.kt | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index 13d5c9d4d2..bf76bd9fb2 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -204,7 +204,8 @@ class CppGenerator( if (cmakeReturnCode == 0) { // If cmake succeeded, run make val makeCommand = createMakeCommand(cppFileConfig.buildPath, version) - val makeReturnCode = CppValidator(cppFileConfig, errorReporter, codeMaps).run(makeCommand, context.cancelIndicator) + val makeReturnCode = if (context.mode == Mode.STANDALONE) makeCommand.run() else + CppValidator(cppFileConfig, errorReporter, codeMaps).run(makeCommand, context.cancelIndicator) if (makeReturnCode == 0) { println("SUCCESS (compiling generated C++ code)") diff --git a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt index 1ff693f01f..69cf164104 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt @@ -128,7 +128,8 @@ class RustGenerator( fileConfig.srcGenPath.toAbsolutePath() ) ?: return - val cargoReturnCode = RustValidator(fileConfig, errorReporter, codeMaps).run(cargoCommand, context.cancelIndicator) + val cargoReturnCode = if (context.mode == TargetConfig.Mode.STANDALONE) cargoCommand.run() else + RustValidator(fileConfig, errorReporter, codeMaps).run(cargoCommand, context.cancelIndicator) if (cargoReturnCode == 0) { println("SUCCESS (compiling generated Rust code)") From a983b7d7d37a5e9210900c3e17069b4db81eb8f5 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 20 Jan 2022 15:03:50 -0800 Subject: [PATCH 183/221] cleanup scripts --- .../src/DigitalTwin/KeyFob/{ => scripts/cloud}/cleanup.sh | 0 .../DigitalTwin/KeyFob/{ => scripts/cloud}/run_local_copy.sh | 0 .../Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/setup.sh | 0 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh | 2 ++ example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh | 1 + example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh | 2 ++ 6 files changed, 5 insertions(+) rename example/Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/cleanup.sh (100%) rename example/Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/run_local_copy.sh (100%) rename example/Python/src/DigitalTwin/KeyFob/{ => scripts/cloud}/setup.sh (100%) create mode 100755 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh create mode 100755 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh create mode 100755 example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/cleanup.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/cloud/cleanup.sh similarity index 100% rename from example/Python/src/DigitalTwin/KeyFob/cleanup.sh rename to example/Python/src/DigitalTwin/KeyFob/scripts/cloud/cleanup.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/run_local_copy.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/cloud/run_local_copy.sh similarity index 100% rename from example/Python/src/DigitalTwin/KeyFob/run_local_copy.sh rename to example/Python/src/DigitalTwin/KeyFob/scripts/cloud/run_local_copy.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/setup.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/cloud/setup.sh similarity index 100% rename from example/Python/src/DigitalTwin/KeyFob/setup.sh rename to example/Python/src/DigitalTwin/KeyFob/scripts/cloud/setup.sh diff --git a/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh new file mode 100755 index 0000000000..cd41af8e74 --- /dev/null +++ b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_fob.sh @@ -0,0 +1,2 @@ +cd ../../../src-gen/DigitalTwin/KeyFob/KeyFobDemo +python3 fob/KeyFobDemo_fob.py -i 1 \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh new file mode 100755 index 0000000000..7321705ec1 --- /dev/null +++ b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_rti.sh @@ -0,0 +1 @@ +RTI -i 1 -n 2 \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh new file mode 100755 index 0000000000..99ae7bb6c5 --- /dev/null +++ b/example/Python/src/DigitalTwin/KeyFob/scripts/local/run_twin.sh @@ -0,0 +1,2 @@ +cd ../../../src-gen/DigitalTwin/KeyFob/KeyFobDemo +python3 twin/KeyFobDemo_twin.py -i 1 \ No newline at end of file From 04efc967270d690207568ad27cf31269e7e7dda9 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 20 Jan 2022 15:08:00 -0800 Subject: [PATCH 184/221] improve log readability --- example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 2 +- example/Python/src/DigitalTwin/KeyFob/README.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 4ca8b4e937..dfc4f90126 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -55,7 +55,7 @@ reactor KeyFob { elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " - f"{'Received' if remote else 'Set'} lock state as: {'Locked' if locked else 'Unlocked'}") + f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {'Locked' if locked else 'Unlocked'}") # log structure: (elapsed_physical_time:int, tag:int, remote:bool, locked:bool) def append_log(self, remote, locked): diff --git a/example/Python/src/DigitalTwin/KeyFob/README.md b/example/Python/src/DigitalTwin/KeyFob/README.md index 797978755b..043606b558 100644 --- a/example/Python/src/DigitalTwin/KeyFob/README.md +++ b/example/Python/src/DigitalTwin/KeyFob/README.md @@ -50,7 +50,7 @@ For clarity purposes, I will use `user$ ` to denote a local terminal, `user@rti- Run the `setup.sh` script to set up the RTI and the digital twin on the cloud: ```bash -user$ ./setup.sh +user$ ./scripts/cloud/setup.sh ``` When the script finishes, ssh into the digital twin: @@ -72,7 +72,7 @@ user@twin-vm ~ $ docker container attach CONTAINER_ID Open another terminal in the directory where the `docker-compose.yml` is located. Run `run_local_copy.sh` to run the local key fob: ```bash -user$ ./run_local_copy.sh +user$ ./scripts/cloud/run_local_copy.sh ``` Now you should see the key fobs in each terminal syncing with each other through the RTI on the cloud. @@ -81,7 +81,7 @@ Now you should see the key fobs in each terminal syncing with each other through Run the clean up script: ```bash -user$ ./cleanup.sh +user$ ./scripts/cloud/cleanup.sh ``` ### Conclusion From 8e61d6d6d667da31d8a123277e5987ffe3397f73 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Thu, 20 Jan 2022 15:15:35 -0800 Subject: [PATCH 185/221] update dev docs --- .../src/DigitalTwin/KeyFob/dev/README.md | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/dev/README.md b/example/Python/src/DigitalTwin/KeyFob/dev/README.md index 9d48fb4022..8eaf1e095e 100644 --- a/example/Python/src/DigitalTwin/KeyFob/dev/README.md +++ b/example/Python/src/DigitalTwin/KeyFob/dev/README.md @@ -28,30 +28,37 @@ NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP rti-vm us-central1-a n1-standard-1 10.128.0.7 34.133.143.163 RUNNING ``` +Export the `EXTERNAL_IP` of the RTI: +```bash +RTI_IP=`gcloud compute instances list | grep 'rti-vm' | awk '{print $5}'` +``` + ### Running the digital twin -Build Digital Twin example locally, after changing the “at” line of the federated reactor: +Build `KeyFobDemo.lf` locally: ```bash -user$ lfc DigitalTwin.lf +user$ lfc KeyFobDemo.lf ``` -Go to the directory where the generated code is located, which is usually located at `src-gen/DigitalTwin/DigitalTwin`. There should be a docker-compose.yml in that directory. Build the docker images of the two key fobs: +Go to the directory where the generated code is located, which is usually located at `src-gen/DigitalTwin/KeyFob/KeyFobDemo`. There should be a docker-compose.yml in that directory. Build the docker images of the two key fobs: ```bash -user$ docker compose build fob twin -—no-cache +user$ docker compose build fob twin --no-cache ``` Tag and push the digital twin's docker image to the cloud: ```bash -user$ docker tag digitaltwin_twin gcr.io/$PROJECT_ID/twin +user$ docker tag keyfobdemo_twin gcr.io/$PROJECT_ID/twin user$ docker push gcr.io/$PROJECT_ID/twin ``` -Create a VM for the digital twin: +Create a VM for the digital twin, passing the address of the RTI: ```bash -user$ gcloud compute instances create-with-container twin-vm \ +gcloud compute instances create-with-container twin-vm \ --container-image=gcr.io/$PROJECT_ID/twin \ --container-arg="-i" \ --container-arg=1 \ + --container-arg="--rti" \ + --container-arg=$RTI_IP \ --container-stdin \ --container-tty ``` @@ -73,9 +80,9 @@ user@twin-vm ~ $ docker container attach CONTAINER_ID ### Running the local key fob -Open another terminal in the directory where the `docker-compose.yml` is located. Run: +Open another terminal in the directory where the `docker-compose.yml` is located. Pass in the address of the RTI. Run: ```bash -user$ docker compose run --rm fob +user$ docker compose run --rm fob -i 1 --rti $RTI_IP ``` Now you should see the key fobs in each terminal syncing with each other through the RTI on the cloud. From d407b9d02d8f052bd46c88290fae6f48936f5d9e Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 02:01:48 -0600 Subject: [PATCH 186/221] Added special handling of bank_index (and some format fixes --- .../generator/python/PythonGenerator.xtend | 162 +++++++++--------- 1 file changed, 80 insertions(+), 82 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 01f94c2c44..9f8a56558b 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -308,13 +308,13 @@ class PythonGenerator extends CGenerator { * @param p The parameter instance to create initializers for * @return Initialization code */ - protected def String getPythonInitializer(ParameterInstance p) { - if (p.getInitialValue.size > 1) { - // parameters are initialized as immutable tuples - return p.getInitialValue.join('(', ', ', ')', [it.pythonTargetValue]) - } else { - return p.getInitialValue.get(0).getPythonTargetValue - } + protected def String getPythonInitializer(ParameterInstance p) { + if (p.getInitialValue.size > 1) { + // parameters are initialized as immutable tuples + return p.getInitialValue.join('(', ', ', ')', [it.pythonTargetValue]) + } else { + return p.getInitialValue.get(0).getPythonTargetValue + } } @@ -325,14 +325,13 @@ class PythonGenerator extends CGenerator { * @return Initialization code */ protected def String getPythonInitializer(Parameter p) { - if (p.init.size > 1) { - // parameters are initialized as immutable tuples - return p.init.join('(', ', ', ')', [it.pythonTargetValue]) - } else { - return p.init.get(0).pythonTargetValue - } - - } + if (p.init.size > 1) { + // parameters are initialized as immutable tuples + return p.init.join('(', ', ', ')', [it.pythonTargetValue]) + } else { + return p.init.get(0).pythonTargetValue + } + } /** * Generate parameters and their respective initialization code for a reaction function @@ -625,15 +624,28 @@ class PythonGenerator extends CGenerator { // Next, create getters for parameters for (param : decl.toDefinition.allParameters) { - temporary_code.append(''' @property - ''') - temporary_code.append(''' def «param.name»(self): - ''') - temporary_code.append(''' return self._«param.name» - - ''') + if (param.name.equals("bank_index")){ + // Do nothing + } else { + temporary_code.append(''' @property + ''') + temporary_code.append(''' def «param.name»(self): + ''') + temporary_code.append(''' return self._«param.name» + + ''') + } } + // Create a special property for bank_index + temporary_code.append(''' @property + ''') + temporary_code.append(''' def bank_index(self): + ''') + temporary_code.append(''' return self._bank_index + + ''') + return temporary_code; } @@ -692,35 +704,28 @@ class PythonGenerator extends CGenerator { // Do not instantiate the federated main reactor since it is generated in C if (!instance.definition.reactorClass.toDefinition.allReactions.isEmpty && !instance.definition.reactorClass.toDefinition.isFederated) { if (federate.contains(instance) && instance.width > 0 && !instance.definition.reactorClass.toDefinition.allReactions.isEmpty) { - if (instance.isBank) { - // If this reactor is a bank, then generate - // a list of instances of reactors and return. + // For each reactor instance, create a list regardless of whether it is a bank or not. + // Non-bank reactor instances will be a list of size 1. + pythonClassesInstantiation. + append(''' + «instance.uniqueID»_lf = [ + ''') + for (var i = 0; i < instance.totalWidth; i++) { pythonClassesInstantiation. append(''' - «instance.uniqueID»_lf = [\ - _«className»(bank_index = 0,\ - «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), + _«className»( + _bank_index = «i%instance.width», + «FOR param : instance.parameters SEPARATOR ", "» + «IF param.name.equals("bank_index")» + «/* Do nothing */» + «ELSE» + _«param.name»=«param.pythonInitializer»«ENDIF»«ENDFOR»), ''') - for (var i = 1; i < instance.width; i++) { - pythonClassesInstantiation. - append(''' - _«className»(bank_index = «i»,\ - «FOR param : instance.parameters SEPARATOR ", "»_«param.name»=«param.pythonInitializer»«ENDFOR»), \ - ''') - } - pythonClassesInstantiation. - append(''' - ] - ''') - return - } else { - // FIXME: Why does this add a bank_index if it's not a bank? - pythonClassesInstantiation.append(''' - «instance.uniqueID»_lf = \ - [_«className»(bank_index = 0, \ - «FOR param : instance.parameters SEPARATOR ", \\\n"»_«param.name»=«param.pythonInitializer»«ENDFOR»)] - ''') } + pythonClassesInstantiation. + append(''' + ] + ''') } } @@ -1715,7 +1720,7 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_py_reaction_function_«reaction.index» = get_python_function("«topLevelName»", «nameOfSelfStruct»->_lf_name, - «CUtil.bankIndex(instance)», + «CUtil.runtimeIndex(instance)», "«pythonFunctionName»"); ''') @@ -1724,7 +1729,7 @@ class PythonGenerator extends CGenerator { «nameOfSelfStruct»->_lf_py_deadline_function_«reaction.index» = get_python_function("«topLevelName»", «nameOfSelfStruct»->_lf_name, - «CUtil.bankIndex(instance)», + «CUtil.runtimeIndex(instance)», "deadline_function_«reaction.index»"); ''') } @@ -1793,24 +1798,18 @@ class PythonGenerator extends CGenerator { StringBuilder pyObjectDescriptor, StringBuilder pyObjects, VarRef port, - ReactorDecl decl - ) - { - if(port.variable instanceof Input) - { + ReactorDecl decl + ) { + if (port.variable instanceof Input) { generateInputVariablesToSendToPythonReaction(pyObjectDescriptor, pyObjects, port.variable as Input, decl) - } - else - { - if(!JavaAstUtils.isMultiport(port.variable as Port)) - { + } else { + if (!JavaAstUtils.isMultiport(port.variable as Port)) { pyObjectDescriptor.append("O") pyObjects.append(''', convert_C_port_to_py(«port.container.name».«port.variable.name», -2)''') - } - else - { + } else { pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«port.container.name».«port.variable.name», «port.container.name».«port.variable.name»_width) ''') + pyObjects. + append(''', convert_C_port_to_py(«port.container.name».«port.variable.name», «port.container.name».«port.variable.name»_width) ''') } } } @@ -1828,25 +1827,24 @@ class PythonGenerator extends CGenerator { StringBuilder pyObjectDescriptor, StringBuilder pyObjects, Output output, - ReactorDecl decl - ) - { - // Unfortunately, for the SET macros to work out-of-the-box for - // multiports, we need an array of *pointers* to the output structs, - // but what we have on the self struct is an array of output structs. - // So we have to handle multiports specially here a construct that - // array of pointers. - // FIXME: The C Generator also has this awkwardness. It makes the code generators - // unnecessarily difficult to maintain, and it may have performance consequences as well. - // Maybe we should change the SET macros. - if (!JavaAstUtils.isMultiport(output)) { - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«output.name», -2)''') - } else { - // Set the _width variable. - pyObjectDescriptor.append("O") - pyObjects.append(''', convert_C_port_to_py(«output.name»,«output.name»_width) ''') - } + ReactorDecl decl + ) { + // Unfortunately, for the SET macros to work out-of-the-box for + // multiports, we need an array of *pointers* to the output structs, + // but what we have on the self struct is an array of output structs. + // So we have to handle multiports specially here a construct that + // array of pointers. + // FIXME: The C Generator also has this awkwardness. It makes the code generators + // unnecessarily difficult to maintain, and it may have performance consequences as well. + // Maybe we should change the SET macros. + if (!JavaAstUtils.isMultiport(output)) { + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«output.name», -2)''') + } else { + // Set the _width variable. + pyObjectDescriptor.append("O") + pyObjects.append(''', convert_C_port_to_py(«output.name»,«output.name»_width) ''') + } } /** Generate into the specified string builder the code to From 2856284c2e10e2a1e0340f0e54729ac330b2f15d Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 02:20:19 -0600 Subject: [PATCH 187/221] Fixed merge artifact --- org.lflang/src/org/lflang/generator/GeneratorBase.xtend | 2 ++ 1 file changed, 2 insertions(+) diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index 4c6142972a..68405515c2 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -24,6 +24,7 @@ ***************/ package org.lflang.generator +import java.io.File import java.nio.file.Files import java.nio.file.Paths import java.util.ArrayList @@ -33,6 +34,7 @@ import java.util.LinkedHashSet import java.util.List import java.util.Map import java.util.Set +import java.util.regex.Pattern import java.util.stream.Collectors import org.eclipse.core.resources.IMarker import org.eclipse.emf.ecore.resource.Resource From 919b6db12f5a204fc55d6da570dfc4d0e86eb08a Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 03:08:49 -0600 Subject: [PATCH 188/221] Fixed syntax error --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 7e13234195..1e820ddf26 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -715,11 +715,11 @@ class PythonGenerator extends CGenerator { append(''' _«className»( _bank_index = «i%instance.width», - «FOR param : instance.parameters SEPARATOR ", "» + «FOR param : instance.parameters» «IF param.name.equals("bank_index")» «/* Do nothing */» «ELSE» - _«param.name»=«param.pythonInitializer»«ENDIF»«ENDFOR»), + _«param.name»=«param.pythonInitializer»,«ENDIF»«ENDFOR»), ''') } pythonClassesInstantiation. From 29aeec8e88a220b4d6ec5f6b28d63a5e00386051 Mon Sep 17 00:00:00 2001 From: Soroush Bateni Date: Fri, 21 Jan 2022 03:13:29 -0600 Subject: [PATCH 189/221] Purely aesthetic changes --- .../src/org/lflang/generator/python/PythonGenerator.xtend | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index 1e820ddf26..81229c1c42 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -716,9 +716,7 @@ class PythonGenerator extends CGenerator { _«className»( _bank_index = «i%instance.width», «FOR param : instance.parameters» - «IF param.name.equals("bank_index")» - «/* Do nothing */» - «ELSE» + «IF !param.name.equals("bank_index")» _«param.name»=«param.pythonInitializer»,«ENDIF»«ENDFOR»), ''') } From a39b456d7e7a5b167c420366055434b261af25a7 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 14 Jan 2022 18:32:12 -0800 Subject: [PATCH 190/221] nonfunctional double unlock demo --- .../DoubleUnlock/DoubleUnlockDemo.lf | 149 ++++++++++++++++++ .../src/DigitalTwin/DoubleUnlock/README.md | 0 2 files changed, 149 insertions(+) create mode 100644 example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf create mode 100644 example/Python/src/DigitalTwin/DoubleUnlock/README.md diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf new file mode 100644 index 0000000000..ac9b706434 --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -0,0 +1,149 @@ +/** + * Example of a basic digital twin setup, with two federates mutating and + * maintaining a "lock status" state. + * + * For run instructions, see README.md in the same directory. + * + * @author Hou Seng Wong (housengw@berkeley.edu) + */ + + target Python { + docker: true +}; + +preamble {= + import curses + import threading + + # lock states + LOCKED = "locked" + DRIVER_DOOR_UNLOCKED = "d_unlocked" + ALL_DOORS_UNLOCKED = "a_unlocked" + + # whether the lock action is remote or not. + REMOTE = True + LOCAL = False +=} + +/** + * A key fob that detects "lock" and "unlock" key presses, + * and sends and receives lock status to and from other key fobs. + */ +reactor KeyFobDoubleUnlock { + /* logger / window related state variables */ + state window({=None=}); + state listener({=None=}); + state log({=list()=}); + state max_log_len(3); + + /* KeyFob related state variables */ + state lock_status({=LOCKED=}); + input get_lock_action; + output send_lock_action; + logical action lock; + logical action unlock; + physical action press_lock; + physical action press_unlock; + + preamble {= + def print_intro_message(self): + self.window.addstr(0, 0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") + self.window.refresh() + + def print_lock_status(self): + self.window.addstr(1, 0, f"Lock Status: {self.lock_status}") + self.window.clrtoeol() + self.window.refresh() + + def print_log(self): + text = "Log is empty" + if len(self.log) > 0: + for i, line in enumerate(self.log): + log_message = self.format_log_message(line) + self.window.addstr(2 + i, 0, log_message) + self.window.clrtoeol() + self.window.refresh() + + def format_log_message(self, line): + elapsed_ptime, tag, remote, new_lock_status = line + return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " + f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " + f"{'Received' if remote else 'Set'} lock state as: {new_lock_status}") + + # log structure: (elapsed_physical_time:int, tag:int, remote:bool, new_lock_status:str) + def append_log(self, remote, new_lock_status): + elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) + log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, new_lock_status) + self.log.append(log_entry) + if len(self.log) > self.max_log_len: + self.log.pop(0) + + def update_lock_status(self, remote, new_lock_status): + self.append_log(remote=True, lock_status=new_lock_status) + self.lock_status = new_lock_status + self.print_lock_status() + self.print_log() + + def listen_for_keypress(self, press_lock, press_unlock): + key = "" + while key != ord("q"): + key = self.window.getch() + if key == ord("l"): + press_lock.schedule(0) + elif key == ord("u"): + press_unlock.schedule(0) + request_stop() + =} + + reaction(startup) -> press_lock, press_unlock {= + self.window = curses.initscr() + curses.cbreak() + curses.noecho() + self.window.keypad(True) + self.print_intro_message() + self.print_lock_status() + self.print_log() + t = threading.Thread(target=self.listen_for_keypress, args=(press_lock, press_unlock)) + self.listener = t + t.start() + =} + + reaction(press_lock) -> lock, send_lock_action {= + lock.schedule(0, LOCAL) + send_lock_action.set(True) + =} + + reaction(press_unlock) -> unlock, send_lock_action {= + unlock.schedule(0, LOCAL) + send_lock_action.set(False) + =} + + reaction(lock) {= + new_lock_status = self.derive_lock_status(self.lock_status, lock=True) + self.update_lock_status(remote=lock.value, new_lock_status=new_lock_status) + =} + + reaction(unlock) {= + new_lock_status = self.derive_lock_status(self.lock_status, lock=False) + self.update_lock_status(remote=unlock.value, new_lock_status=new_lock_status) + =} + + reaction(get_lock_action) -> lock, unlock {= + if get_lock_action.value: + lock.schedule(0, REMOTE) + else: + unlock.schedule(0, REMOTE) + =} + + reaction(shutdown) {= + self.listener.join() + curses.endwin() + =} +} + +federated reactor { + fob = new KeyFobDoubleUnlock(); + twin = new KeyFobDoubleUnlock(); + fob.send_lock_action -> twin.get_lock_action; + twin.send_lock_action -> fob.get_lock_action; +} \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/README.md b/example/Python/src/DigitalTwin/DoubleUnlock/README.md new file mode 100644 index 0000000000..e69de29bb2 From a5100eeed7164007c38a07f4d92e899a2fa4b0a6 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sun, 16 Jan 2022 16:47:04 -0800 Subject: [PATCH 191/221] refactor Key fob demo --- .../src/DigitalTwin/KeyFob/KeyFobDemo.lf | 55 ++++++++++--------- example/Python/src/DigitalTwin/utils.py | 36 ++++++++++++ 2 files changed, 64 insertions(+), 27 deletions(-) create mode 100644 example/Python/src/DigitalTwin/utils.py diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index dfc4f90126..55e3bf3751 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -8,12 +8,14 @@ */ target Python { - docker: true + docker: true, + files: ["../utils.py"] }; preamble {= import curses import threading + from utils import Logger, Window =} /** @@ -21,35 +23,31 @@ preamble {= * and sends and receives lock status to and from other key fobs. */ reactor KeyFob { - state locked({=False=}); + /* logger / window related state variables */ + state logger({=None=}); state window({=None=}); + + /* KeyFob related state variables */ + state locked({=False=}); state listener({=None=}); - state log({=list()=}); - state max_log_len(3); + + /* I/O ports and actions */ input get_lock_status; output send_lock_status; physical action press_lock; physical action press_unlock; preamble {= - def print_intro_message(self): - self.window.addstr(0, 0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.window.refresh() - + def lock_status_str(self, locked): + return "Locked" if locked else "Unlocked" + def print_lock_status(self): - status = "Locked" if self.locked else "Unlocked" - self.window.addstr(1, 0, f"Lock Status: {status}") - self.window.clrtoeol() - self.window.refresh() + self.window.change_line(1, f"Lock Status: {self.lock_status_str(self.locked)}") def print_log(self): - text = "Log is empty" - if len(self.log) > 0: - for i, line in enumerate(self.log): - log_message = self.format_log_message(line) - self.window.addstr(2 + i, 0, log_message) - self.window.clrtoeol() - self.window.refresh() + if self.logger.log_size() > 0: + for i, line in enumerate(self.logger.get_log()): + self.window.change_line(2 + i, line) def format_log_message(self, line): elapsed_ptime, tag, remote, locked = line @@ -61,9 +59,7 @@ reactor KeyFob { def append_log(self, remote, locked): elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, locked) - self.log.append(log_entry) - if len(self.log) > self.max_log_len: - self.log.pop(0) + self.logger.append_log(self.format_log_message(log_entry)) def listen_for_keypress(self, press_lock, press_unlock): key = "" @@ -77,13 +73,15 @@ reactor KeyFob { =} reaction(startup) -> press_lock, press_unlock {= - self.window = curses.initscr() - curses.cbreak() - curses.noecho() - self.window.keypad(True) - self.print_intro_message() + # Set up the logger and the curses window + self.window = Window() + self.logger = Logger() + self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") self.print_lock_status() self.print_log() + self.window.refresh() + + # Spawn thread to listen for key presses t = threading.Thread(target=self.listen_for_keypress, args=(press_lock, press_unlock)) self.listener = t t.start() @@ -94,6 +92,7 @@ reactor KeyFob { self.locked = True self.print_lock_status() self.print_log() + self.window.refresh() send_lock_status.set(True) =} @@ -102,6 +101,7 @@ reactor KeyFob { self.locked = False self.print_lock_status() self.print_log() + self.window.refresh() send_lock_status.set(False) =} @@ -110,6 +110,7 @@ reactor KeyFob { self.locked = get_lock_status.value self.print_lock_status() self.print_log() + self.window.refresh() =} reaction(shutdown) {= diff --git a/example/Python/src/DigitalTwin/utils.py b/example/Python/src/DigitalTwin/utils.py new file mode 100644 index 0000000000..769bd4d9b3 --- /dev/null +++ b/example/Python/src/DigitalTwin/utils.py @@ -0,0 +1,36 @@ +import curses + +class Logger: + def __init__(self, max_lines=3): + self.max_lines = max_lines + self.log = [] + + def append_log(self, msg): + self.log.append(msg) + if len(self.log) > self.max_lines: + self.log.pop(0) + + def get_log(self): + return self.log + + def log_size(self): + return len(self.log) + + +class Window: + def __init__(self): + self.window = curses.initscr() + curses.cbreak() + curses.noecho() + self.window.keypad(True) + + def change_line(self, y, new_msg): + self.window.addstr(y, 0, new_msg) + self.window.clrtoeol() + + def refresh(self): + self.window.refresh() + + def getch(self): + return self.window.getch() + From 5a442384834e556760c164985951481b8bc35576 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sun, 16 Jan 2022 16:51:37 -0800 Subject: [PATCH 192/221] rename lock_status to lock_state --- .../src/DigitalTwin/KeyFob/KeyFobDemo.lf | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 55e3bf3751..41823f1618 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -32,17 +32,17 @@ reactor KeyFob { state listener({=None=}); /* I/O ports and actions */ - input get_lock_status; - output send_lock_status; + input get_lock_state; + output send_lock_state; physical action press_lock; physical action press_unlock; preamble {= - def lock_status_str(self, locked): + def lock_state_str(self, locked): return "Locked" if locked else "Unlocked" - def print_lock_status(self): - self.window.change_line(1, f"Lock Status: {self.lock_status_str(self.locked)}") + def print_lock_state(self): + self.window.change_line(1, f"Lock Status: {self.lock_state_str(self.locked)}") def print_log(self): if self.logger.log_size() > 0: @@ -53,7 +53,11 @@ reactor KeyFob { elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " +<<<<<<< HEAD f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {'Locked' if locked else 'Unlocked'}") +======= + f"{'Received' if remote else 'Set'} lock state as: {self.lock_state_str(locked)}") +>>>>>>> rename lock_status to lock_state # log structure: (elapsed_physical_time:int, tag:int, remote:bool, locked:bool) def append_log(self, remote, locked): @@ -77,7 +81,7 @@ reactor KeyFob { self.window = Window() self.logger = Logger() self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.print_lock_status() + self.print_lock_state() self.print_log() self.window.refresh() @@ -87,28 +91,28 @@ reactor KeyFob { t.start() =} - reaction(press_lock) -> send_lock_status {= + reaction(press_lock) -> send_lock_state {= self.append_log(remote=False, locked=True) self.locked = True - self.print_lock_status() + self.print_lock_state() self.print_log() self.window.refresh() - send_lock_status.set(True) + send_lock_state.set(True) =} - reaction(press_unlock) -> send_lock_status {= + reaction(press_unlock) -> send_lock_state {= self.append_log(remote=False, locked=False) self.locked = False - self.print_lock_status() + self.print_lock_state() self.print_log() self.window.refresh() - send_lock_status.set(False) + send_lock_state.set(False) =} - reaction(get_lock_status) {= - self.append_log(remote=True, locked=get_lock_status.value) - self.locked = get_lock_status.value - self.print_lock_status() + reaction(get_lock_state) {= + self.append_log(remote=True, locked=get_lock_state.value) + self.locked = get_lock_state.value + self.print_lock_state() self.print_log() self.window.refresh() =} @@ -122,6 +126,6 @@ reactor KeyFob { federated reactor { fob = new KeyFob(); twin = new KeyFob(); - fob.send_lock_status -> twin.get_lock_status; - twin.send_lock_status -> fob.get_lock_status; + fob.send_lock_state -> twin.get_lock_state; + twin.send_lock_state -> fob.get_lock_state; } \ No newline at end of file From b7d5819b95ca52466539b832e63011b4e112216a Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sun, 16 Jan 2022 17:31:42 -0800 Subject: [PATCH 193/221] functional double unlock demo without autolock --- .../DoubleUnlock/DoubleUnlockDemo.lf | 155 ++++++++++-------- 1 file changed, 84 insertions(+), 71 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index ac9b706434..372ab14ada 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -8,81 +8,79 @@ */ target Python { - docker: true + docker: true, + files: ["../utils.py"] }; preamble {= import curses import threading - - # lock states - LOCKED = "locked" - DRIVER_DOOR_UNLOCKED = "d_unlocked" - ALL_DOORS_UNLOCKED = "a_unlocked" - - # whether the lock action is remote or not. - REMOTE = True - LOCAL = False + from utils import Logger, Window + import enum + + class LockStates(enum.Enum): + Locked = 0 + DriverUnlocked = 1 + AllUnlocked = 2 + + class LockStateNames(enum.Enum): + Locked = "Locked" + DriverUnlocked = "Driver's door unlocked" + AllUnlocked = "All doors unlocked" =} /** * A key fob that detects "lock" and "unlock" key presses, * and sends and receives lock status to and from other key fobs. */ -reactor KeyFobDoubleUnlock { +reactor DoubleUnlockKeyFob { /* logger / window related state variables */ + state logger({=None=}); state window({=None=}); - state listener({=None=}); - state log({=list()=}); - state max_log_len(3); + state main_message_begins(0); /* KeyFob related state variables */ - state lock_status({=LOCKED=}); + state lock_state(0); + state listener({=None=}); + state auto_lock_duration(5) # seconds + + /* I/O ports and actions */ input get_lock_action; output send_lock_action; - logical action lock; - logical action unlock; physical action press_lock; physical action press_unlock; + logical action do_lock; preamble {= - def print_intro_message(self): - self.window.addstr(0, 0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.window.refresh() - - def print_lock_status(self): - self.window.addstr(1, 0, f"Lock Status: {self.lock_status}") - self.window.clrtoeol() - self.window.refresh() + def lock_state_str(self, lock_state): + if lock_state == LockStates.Locked: + return LockStateNames.Locked.value + elif lock_state == LockStates.DriverUnlocked: + return LockStateNames.DriverUnlocked.value + elif lock_state == LockStates.AllUnlocked: + return LockStateNames.AllUnlocked.value + else: + return "ERROR: Lock state is invalid" + + def print_lock_state(self): + self.window.change_line(self.main_message_begins, f"Lock State: {self.lock_state_str(self.lock_state)}") def print_log(self): - text = "Log is empty" - if len(self.log) > 0: - for i, line in enumerate(self.log): - log_message = self.format_log_message(line) - self.window.addstr(2 + i, 0, log_message) - self.window.clrtoeol() - self.window.refresh() + if self.logger.log_size() > 0: + for i, line in enumerate(self.logger.get_log()): + self.window.change_line(self.main_message_begins + 1 + i, line) def format_log_message(self, line): - elapsed_ptime, tag, remote, new_lock_status = line + elapsed_ptime, tag, remote, do_lock = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " - f"{'Received' if remote else 'Set'} lock state as: {new_lock_status}") + f"{'Received' if remote else 'Sent'} lock action: {'Lock' if do_lock else 'Unlock'}") - # log structure: (elapsed_physical_time:int, tag:int, remote:bool, new_lock_status:str) - def append_log(self, remote, new_lock_status): + # log structure: (elapsed_physical_time:int, tag:int, remote:bool, do_lock:bool) + def append_log(self, remote, do_lock): elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) - log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, new_lock_status) - self.log.append(log_entry) - if len(self.log) > self.max_log_len: - self.log.pop(0) - - def update_lock_status(self, remote, new_lock_status): - self.append_log(remote=True, lock_status=new_lock_status) - self.lock_status = new_lock_status - self.print_lock_status() - self.print_log() + log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, do_lock) + self.logger.append_log(self.format_log_message(log_entry)) def listen_for_keypress(self, press_lock, press_unlock): key = "" @@ -96,43 +94,58 @@ reactor KeyFobDoubleUnlock { =} reaction(startup) -> press_lock, press_unlock {= - self.window = curses.initscr() - curses.cbreak() - curses.noecho() - self.window.keypad(True) - self.print_intro_message() - self.print_lock_status() + # Set up the logger and the curses window + self.window = Window() + self.logger = Logger() + self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") + self.window.change_line(1, "") + self.window.change_line(2, "Rules:") + self.window.change_line(3, "1. Pressing 'l' locks all doors.") + self.window.change_line(4, "2. Pressing 'u' unlocks driver's door if driver's door was locked.") + self.window.change_line(5, "3. Pressing 'u' unlocks all door if driver's door was NOT locked.") + self.window.change_line(6, "") + self.window.change_line(7, "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)") + self.window.change_line(8, "") + self.main_message_begins = 9 + self.auto_lock_duration = 5 + self.lock_state = LockStates.Locked + self.print_lock_state() self.print_log() + self.window.refresh() + + # Spawn thread to listen for key presses t = threading.Thread(target=self.listen_for_keypress, args=(press_lock, press_unlock)) self.listener = t t.start() =} - reaction(press_lock) -> lock, send_lock_action {= - lock.schedule(0, LOCAL) + reaction(press_lock) -> send_lock_action, do_lock {= + self.append_log(remote=False, do_lock=True) + do_lock.schedule(0, True) send_lock_action.set(True) =} - reaction(press_unlock) -> unlock, send_lock_action {= - unlock.schedule(0, LOCAL) + reaction(press_unlock) -> send_lock_action, do_lock {= + self.append_log(remote=False, do_lock=False) + do_lock.schedule(0, False) send_lock_action.set(False) =} - reaction(lock) {= - new_lock_status = self.derive_lock_status(self.lock_status, lock=True) - self.update_lock_status(remote=lock.value, new_lock_status=new_lock_status) + reaction(get_lock_action) -> do_lock {= + self.append_log(remote=True, do_lock=get_lock_action.value) + do_lock.schedule(0, get_lock_action.value) =} - reaction(unlock) {= - new_lock_status = self.derive_lock_status(self.lock_status, lock=False) - self.update_lock_status(remote=unlock.value, new_lock_status=new_lock_status) - =} - - reaction(get_lock_action) -> lock, unlock {= - if get_lock_action.value: - lock.schedule(0, REMOTE) - else: - unlock.schedule(0, REMOTE) + reaction(do_lock) {= + if do_lock.value: + self.lock_state = LockStates.Locked + elif self.lock_state == LockStates.Locked: + self.lock_state = LockStates.DriverUnlocked + elif self.lock_state == LockStates.DriverUnlocked: + self.lock_state = LockStates.AllUnlocked + self.print_lock_state() + self.print_log() + self.window.refresh() =} reaction(shutdown) {= @@ -142,8 +155,8 @@ reactor KeyFobDoubleUnlock { } federated reactor { - fob = new KeyFobDoubleUnlock(); - twin = new KeyFobDoubleUnlock(); + fob = new DoubleUnlockKeyFob(); + twin = new DoubleUnlockKeyFob(); fob.send_lock_action -> twin.get_lock_action; twin.send_lock_action -> fob.get_lock_action; } \ No newline at end of file From a73a8e38185307f73842c5238bd610d4d007f7c9 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 18:06:04 -0800 Subject: [PATCH 194/221] refactor messages --- .../DoubleUnlock/DoubleUnlockDemo.lf | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 372ab14ada..542b235b99 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -97,16 +97,20 @@ reactor DoubleUnlockKeyFob { # Set up the logger and the curses window self.window = Window() self.logger = Logger() - self.window.change_line(0, "Press 'l' to lock, 'u' to unlock, 'q' to quit") - self.window.change_line(1, "") - self.window.change_line(2, "Rules:") - self.window.change_line(3, "1. Pressing 'l' locks all doors.") - self.window.change_line(4, "2. Pressing 'u' unlocks driver's door if driver's door was locked.") - self.window.change_line(5, "3. Pressing 'u' unlocks all door if driver's door was NOT locked.") - self.window.change_line(6, "") - self.window.change_line(7, "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)") - self.window.change_line(8, "") - self.main_message_begins = 9 + messages = [ + "Press 'l' to send a lock signal, 'u' to send an unlock signal, 'q' to quit", + "", + "Rules:", + "1. A lock signal locks all doors.", + "2. An unlock signal unlocks driver's door if driver's door was locked.", + "3. An unlock signal unlocks all door if driver's door was NOT locked.", + "", + "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)", + "" + ] + for i, msg in enumerate(messages): + self.window.change_line(i, msg) + self.main_message_begins = len(messages) self.auto_lock_duration = 5 self.lock_state = LockStates.Locked self.print_lock_state() From 53a89d7cb897498898da7edb241576ccbd7bbae2 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 14:49:01 -0800 Subject: [PATCH 195/221] resolve conflicts --- example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 41823f1618..5f3d4c2d20 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -53,11 +53,7 @@ reactor KeyFob { elapsed_ptime, tag, remote, locked = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " -<<<<<<< HEAD - f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {'Locked' if locked else 'Unlocked'}") -======= - f"{'Received' if remote else 'Set'} lock state as: {self.lock_state_str(locked)}") ->>>>>>> rename lock_status to lock_state + f"{'[Remote]' if remote else '[Local]'} Updated lock state to: {self.lock_state_str(locked)}") # log structure: (elapsed_physical_time:int, tag:int, remote:bool, locked:bool) def append_log(self, remote, locked): From dc5b30592573042784a9cddfafd407fcaeec1ad1 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 19:17:41 -0800 Subject: [PATCH 196/221] add autolock feature --- .../DoubleUnlock/DoubleUnlockDemo.lf | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 542b235b99..e66c544b75 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -33,7 +33,7 @@ preamble {= * A key fob that detects "lock" and "unlock" key presses, * and sends and receives lock status to and from other key fobs. */ -reactor DoubleUnlockKeyFob { +reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { /* logger / window related state variables */ state logger({=None=}); state window({=None=}); @@ -42,7 +42,7 @@ reactor DoubleUnlockKeyFob { /* KeyFob related state variables */ state lock_state(0); state listener({=None=}); - state auto_lock_duration(5) # seconds + state auto_lock_counter; /* I/O ports and actions */ input get_lock_action; @@ -51,6 +51,9 @@ reactor DoubleUnlockKeyFob { physical action press_unlock; logical action do_lock; + /* Autolock timer */ + timer autolock_timer(0, 100 msec); + preamble {= def lock_state_str(self, lock_state): if lock_state == LockStates.Locked: @@ -71,15 +74,15 @@ reactor DoubleUnlockKeyFob { self.window.change_line(self.main_message_begins + 1 + i, line) def format_log_message(self, line): - elapsed_ptime, tag, remote, do_lock = line + elapsed_ptime, tag, remote, do_lock, auto = line return (f"At (tag: ({'{:,}'.format(tag.time)} ns, {tag.microstep}), " f"lag: {'{:,}'.format(elapsed_ptime - tag.time)} ns), " - f"{'Received' if remote else 'Sent'} lock action: {'Lock' if do_lock else 'Unlock'}") + f"{'[Auto] ' if auto else ''}{'[Remote]' if remote else '[Local]'} lock action: {'Lock' if do_lock else 'Unlock'}") - # log structure: (elapsed_physical_time:int, tag:int, remote:bool, do_lock:bool) - def append_log(self, remote, do_lock): + # log structure: (elapsed_physical_time:int, tag:int, remote:bool, do_lock:bool, auto:bool) + def append_log(self, auto, remote, do_lock): elapsed_tag = Tag(get_elapsed_logical_time(), get_microstep()) - log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, do_lock) + log_entry = (get_elapsed_physical_time(), elapsed_tag, remote, do_lock, auto) self.logger.append_log(self.format_log_message(log_entry)) def listen_for_keypress(self, press_lock, press_unlock): @@ -91,6 +94,9 @@ reactor DoubleUnlockKeyFob { elif key == ord("u"): press_unlock.schedule(0) request_stop() + + def reset_autolock_counter(self): + self.auto_lock_counter = self.auto_lock_duration =} reaction(startup) -> press_lock, press_unlock {= @@ -111,8 +117,8 @@ reactor DoubleUnlockKeyFob { for i, msg in enumerate(messages): self.window.change_line(i, msg) self.main_message_begins = len(messages) - self.auto_lock_duration = 5 self.lock_state = LockStates.Locked + self.reset_autolock_counter() self.print_lock_state() self.print_log() self.window.refresh() @@ -124,19 +130,19 @@ reactor DoubleUnlockKeyFob { =} reaction(press_lock) -> send_lock_action, do_lock {= - self.append_log(remote=False, do_lock=True) + self.append_log(auto=False, remote=False, do_lock=True) do_lock.schedule(0, True) send_lock_action.set(True) =} reaction(press_unlock) -> send_lock_action, do_lock {= - self.append_log(remote=False, do_lock=False) + self.append_log(auto=False, remote=False, do_lock=False) do_lock.schedule(0, False) send_lock_action.set(False) =} reaction(get_lock_action) -> do_lock {= - self.append_log(remote=True, do_lock=get_lock_action.value) + self.append_log(auto=False, remote=True, do_lock=get_lock_action.value) do_lock.schedule(0, get_lock_action.value) =} @@ -145,6 +151,7 @@ reactor DoubleUnlockKeyFob { self.lock_state = LockStates.Locked elif self.lock_state == LockStates.Locked: self.lock_state = LockStates.DriverUnlocked + self.reset_autolock_counter() elif self.lock_state == LockStates.DriverUnlocked: self.lock_state = LockStates.AllUnlocked self.print_lock_state() @@ -152,6 +159,14 @@ reactor DoubleUnlockKeyFob { self.window.refresh() =} + reaction(autolock_timer) -> do_lock {= + if self.auto_lock_counter > 0: + self.auto_lock_counter -= 0.1 + elif self.lock_state != LockStates.Locked: + self.append_log(auto=True, remote=False, do_lock=True) + do_lock.schedule(0, True) + =} + reaction(shutdown) {= self.listener.join() curses.endwin() From 523f47a62fd25826d105a5d5d1b7aada486b3973 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 20:12:03 -0800 Subject: [PATCH 197/221] added intermediate logical action --- .../DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index e66c544b75..e608c76ad6 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -49,6 +49,8 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { output send_lock_action; physical action press_lock; physical action press_unlock; + logical action handle_press_lock; + logical action handle_press_unlock; logical action do_lock; /* Autolock timer */ @@ -129,13 +131,21 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { t.start() =} - reaction(press_lock) -> send_lock_action, do_lock {= - self.append_log(auto=False, remote=False, do_lock=True) + reaction(press_lock) -> handle_press_lock {= + handle_press_lock.schedule(0) + =} + + reaction(press_unlock) -> handle_press_unlock {= + handle_press_unlock.schedule(0) + =} + + reaction(handle_press_lock) -> do_lock, send_lock_action {= + self.append_log(auto=False, remote=False, do_lock=True) do_lock.schedule(0, True) send_lock_action.set(True) =} - reaction(press_unlock) -> send_lock_action, do_lock {= + reaction(handle_press_unlock) -> do_lock, send_lock_action {= self.append_log(auto=False, remote=False, do_lock=False) do_lock.schedule(0, False) send_lock_action.set(False) From 9fad094dc40774f4cf8683bfab96bd15a83d605f Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 17 Jan 2022 21:00:05 -0800 Subject: [PATCH 198/221] add skeleton of Tester --- .../DoubleUnlock/DoubleUnlockDemo.lf | 9 ++ .../src/DigitalTwin/DoubleUnlock/Tester.lf | 86 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index e608c76ad6..02e636fe41 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -46,6 +46,7 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { /* I/O ports and actions */ input get_lock_action; + input get_lock_press_from_tester; output send_lock_action; physical action press_lock; physical action press_unlock; @@ -151,6 +152,14 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { send_lock_action.set(False) =} + reaction(get_lock_press_from_tester) -> handle_press_lock, handle_press_unlock {= + press_lock_val = get_lock_press_from_tester.value + if press_lock_val: + handle_press_lock.schedule(0) + else: + handle_press_unlock.schedule(0) + =} + reaction(get_lock_action) -> do_lock {= self.append_log(auto=False, remote=True, do_lock=get_lock_action.value) do_lock.schedule(0, get_lock_action.value) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf new file mode 100644 index 0000000000..2db851da3d --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf @@ -0,0 +1,86 @@ +target Python { + docker: true, + files: ["../utils.py"] +}; + +import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; + +preamble {= + import curses + import threading + from utils import Logger, Window +=} + +# TODO: Why does messages from tester to key fobs not get processed on the event QUEUE??? +reactor DoubleUnlockKeyFobTester { + state logger({=None=}); + state window({=None=}); + + physical action key_press; + logical action simulate_press_fob; + logical action simulate_press_twin; + output send_lock_press_to_fob; + output send_lock_press_to_twin; + + + preamble {= + def listen_for_keypress(self, key_press): + key = "" + while key != ord("q"): + key = self.window.getch() + if key != ord("q"): + key_press.schedule(0, key) + request_stop() + =} + + reaction(startup) -> key_press {= + self.window = Window() + self.logger = Logger() + messages = [ + "Press 'l'. ';' to send a lock signal, 'u', 'i' to send an unlock signal, 'q' to quit", + ] + for i, msg in enumerate(messages): + self.window.change_line(i, msg) + self.main_message_begins = len(messages) + + # Spawn thread to listen for key presses + t = threading.Thread(target=self.listen_for_keypress, args=(key_press, )) + self.listener = t + t.start() + =} + + reaction(key_press) -> simulate_press_fob, simulate_press_twin {= + key = key_press.value + if key == ord("l"): + simulate_press_fob.schedule(0, True) + elif key == ord(";"): + simulate_press_twin.schedule(0, True) + elif key == ord("u"): + simulate_press_fob.schedule(0, False) + elif key == ord("i"): + simulate_press_twin.schedule(0, False) + =} + + reaction(simulate_press_fob) -> send_lock_press_to_fob {= + send_lock_press_to_fob.set(simulate_press_fob.value) + =} + + reaction(simulate_press_twin) -> send_lock_press_to_twin {= + send_lock_press_to_twin.set(simulate_press_twin.value) + =} + + reaction(shutdown) {= + self.listener.join() + curses.endwin() + =} +} + +federated reactor { + fob = new DoubleUnlockKeyFob(); + twin = new DoubleUnlockKeyFob(); + tester = new DoubleUnlockKeyFobTester(); + tester.send_lock_press_to_fob -> fob.get_lock_press_from_tester; + tester.send_lock_press_to_twin -> twin.get_lock_press_from_tester; + fob.send_lock_action -> twin.get_lock_action; + twin.send_lock_action -> fob.get_lock_action; +} \ No newline at end of file From 4be2207a2df2d9c0bdfb62589749e5a5c78604c8 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Wed, 19 Jan 2022 12:13:19 -0800 Subject: [PATCH 199/221] add run scripts and fix tester --- .../src/DigitalTwin/DoubleUnlock/Tester.lf | 63 +++++-------------- .../src/DigitalTwin/DoubleUnlock/run_fob.sh | 3 + .../src/DigitalTwin/DoubleUnlock/run_rti.sh | 1 + .../DigitalTwin/DoubleUnlock/run_tester.sh | 3 + .../src/DigitalTwin/DoubleUnlock/run_twin.sh | 3 + 5 files changed, 25 insertions(+), 48 deletions(-) create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf index 2db851da3d..535bdfb953 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf @@ -5,74 +5,41 @@ target Python { import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; -preamble {= - import curses - import threading - from utils import Logger, Window -=} # TODO: Why does messages from tester to key fobs not get processed on the event QUEUE??? -reactor DoubleUnlockKeyFobTester { +reactor DoubleUnlockKeyFobTester(initial_delay(5)) { state logger({=None=}); state window({=None=}); - physical action key_press; + logical action do_test; logical action simulate_press_fob; logical action simulate_press_twin; output send_lock_press_to_fob; output send_lock_press_to_twin; - - - preamble {= - def listen_for_keypress(self, key_press): - key = "" - while key != ord("q"): - key = self.window.getch() - if key != ord("q"): - key_press.schedule(0, key) - request_stop() + + reaction(startup) -> do_test {= + print(f"Test starts in {self.initial_delay} seconds...") + do_test.schedule(SEC(self.initial_delay)) =} - reaction(startup) -> key_press {= - self.window = Window() - self.logger = Logger() - messages = [ - "Press 'l'. ';' to send a lock signal, 'u', 'i' to send an unlock signal, 'q' to quit", - ] - for i, msg in enumerate(messages): - self.window.change_line(i, msg) - self.main_message_begins = len(messages) - - # Spawn thread to listen for key presses - t = threading.Thread(target=self.listen_for_keypress, args=(key_press, )) - self.listener = t - t.start() - =} - - reaction(key_press) -> simulate_press_fob, simulate_press_twin {= - key = key_press.value - if key == ord("l"): - simulate_press_fob.schedule(0, True) - elif key == ord(";"): - simulate_press_twin.schedule(0, True) - elif key == ord("u"): - simulate_press_fob.schedule(0, False) - elif key == ord("i"): - simulate_press_twin.schedule(0, False) + reaction(do_test) -> simulate_press_fob, simulate_press_twin {= + # simulate_press_fob.schedule(0, True) + # simulate_press_twin.schedule(0, True) + simulate_press_fob.schedule(0, False) + simulate_press_twin.schedule(0, False) =} reaction(simulate_press_fob) -> send_lock_press_to_fob {= + tag = get_current_tag() + print(f"Sent lock press {simulate_press_fob.value} to fob at ({tag.time}, {tag.microstep})") send_lock_press_to_fob.set(simulate_press_fob.value) =} reaction(simulate_press_twin) -> send_lock_press_to_twin {= + tag = get_current_tag() + print(f"Sent lock press {simulate_press_twin.value} to twin at ({tag.time}, {tag.microstep})") send_lock_press_to_twin.set(simulate_press_twin.value) =} - - reaction(shutdown) {= - self.listener.join() - curses.endwin() - =} } federated reactor { diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh new file mode 100755 index 0000000000..a4c165aa72 --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/fob +python3 Tester_fob.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh new file mode 100755 index 0000000000..b865088205 --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_rti.sh @@ -0,0 +1 @@ +RTI -i 1 -n 3 \ No newline at end of file diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh new file mode 100755 index 0000000000..62c539e10b --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/tester +python3 Tester_tester.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh new file mode 100755 index 0000000000..38720f8dcf --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/twin +python3 Tester_twin.py -i 1 From 8ef76059a62c61641ecbd44458254cc937165a65 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 15:01:19 -0800 Subject: [PATCH 200/221] add readme for double unlock demo --- .../Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf | 6 +++--- example/Python/src/DigitalTwin/DoubleUnlock/README.md | 6 ++++++ example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf | 6 +++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index 02e636fe41..c416c291ab 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -1,6 +1,6 @@ /** - * Example of a basic digital twin setup, with two federates mutating and - * maintaining a "lock status" state. + * Example of a basic digital twin setup, with two federates + * maintaining a shared state "lock state". * * For run instructions, see README.md in the same directory. * @@ -31,7 +31,7 @@ preamble {= /** * A key fob that detects "lock" and "unlock" key presses, - * and sends and receives lock status to and from other key fobs. + * and sends and receives lock state to and from other key fobs. */ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { /* logger / window related state variables */ diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/README.md b/example/Python/src/DigitalTwin/DoubleUnlock/README.md index e69de29bb2..1b20e4c402 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/README.md +++ b/example/Python/src/DigitalTwin/DoubleUnlock/README.md @@ -0,0 +1,6 @@ +# Double Unlock Key Fob (Digital Twin) Example + +This example is similar to the Key Fob Demo, but with 3 states: Locked, Driver's Door Unlocked, and All Doors Unlocked. +A tester program `Tester.lf` is also included to demonstrate generating simulated signals at arbitrary logical time. + +To run the program, do `lfc Tester.lf` and use the bash scripts to launch each federate in a separate terminal window. diff --git a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf index 5f3d4c2d20..6272976afe 100644 --- a/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf +++ b/example/Python/src/DigitalTwin/KeyFob/KeyFobDemo.lf @@ -1,6 +1,6 @@ /** - * Example of a basic digital twin setup, with two federates mutating and - * maintaining a "lock status" state. + * Example of a basic digital twin setup, with two federates + * maintaining a shared state "locked". * * For run instructions, see README.md in the same directory. * @@ -20,7 +20,7 @@ preamble {= /** * A key fob that detects "lock" and "unlock" key presses, - * and sends and receives lock status to and from other key fobs. + * and sends and receives lock state to and from other key fobs. */ reactor KeyFob { /* logger / window related state variables */ From 3fee36ce1d2a0bb6f6846273461e3d231d3c4c7e Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 15:06:09 -0800 Subject: [PATCH 201/221] add link to demo --- example/Python/src/DigitalTwin/KeyFob/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/example/Python/src/DigitalTwin/KeyFob/README.md b/example/Python/src/DigitalTwin/KeyFob/README.md index 043606b558..e2d8a180a4 100644 --- a/example/Python/src/DigitalTwin/KeyFob/README.md +++ b/example/Python/src/DigitalTwin/KeyFob/README.md @@ -2,6 +2,8 @@ This example shows two federates, one hosted locally and the other hosted on Google Cloud, interacting via an RTI that is also hosted on Google Cloud. +Check out this (video)[https://www.youtube.com/watch?v=s7dYKLoHXVE] for a recorded demo. + ## Before we start Make sure you have a Google Cloud Platform (GCP) account and a project set up. From 17558484a7f58a04a742a292ce7c2de40586d252 Mon Sep 17 00:00:00 2001 From: housengw <46389172+housengw@users.noreply.github.com> Date: Fri, 21 Jan 2022 15:35:06 -0800 Subject: [PATCH 202/221] Update DoubleUnlockDemo.lf --- .../Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf index c416c291ab..28467318c0 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/DoubleUnlockDemo.lf @@ -114,7 +114,7 @@ reactor DoubleUnlockKeyFob(auto_lock_duration(5)) { "2. An unlock signal unlocks driver's door if driver's door was locked.", "3. An unlock signal unlocks all door if driver's door was NOT locked.", "", - "All doors automatically lock after 5 seconds if no unlock signal is received. (not yet implemented)", + "All doors automatically lock after 5 seconds if no unlock signal is received. ", "" ] for i, msg in enumerate(messages): @@ -197,4 +197,4 @@ federated reactor { twin = new DoubleUnlockKeyFob(); fob.send_lock_action -> twin.get_lock_action; twin.send_lock_action -> fob.get_lock_action; -} \ No newline at end of file +} From 02b60577fa3cb2d72401696a06f23fc20e5e77a8 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Fri, 21 Jan 2022 16:34:33 -0800 Subject: [PATCH 203/221] rename tester to simulator --- example/Python/src/DigitalTwin/DoubleUnlock/README.md | 4 ++-- .../src/DigitalTwin/DoubleUnlock/{Tester.lf => Simulator.lf} | 0 example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh | 4 ++-- example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh | 3 +++ example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh | 3 --- example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) rename example/Python/src/DigitalTwin/DoubleUnlock/{Tester.lf => Simulator.lf} (100%) create mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh delete mode 100755 example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/README.md b/example/Python/src/DigitalTwin/DoubleUnlock/README.md index 1b20e4c402..8cfde843b0 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/README.md +++ b/example/Python/src/DigitalTwin/DoubleUnlock/README.md @@ -1,6 +1,6 @@ # Double Unlock Key Fob (Digital Twin) Example This example is similar to the Key Fob Demo, but with 3 states: Locked, Driver's Door Unlocked, and All Doors Unlocked. -A tester program `Tester.lf` is also included to demonstrate generating simulated signals at arbitrary logical time. +A simulator program `Simulator.lf` is also included to demonstrate generating simulated signals at arbitrary logical time. -To run the program, do `lfc Tester.lf` and use the bash scripts to launch each federate in a separate terminal window. +To run the program, do `lfc Simulator.lf` and use the bash scripts to launch each federate in a separate terminal window. diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf similarity index 100% rename from example/Python/src/DigitalTwin/DoubleUnlock/Tester.lf rename to example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh index a4c165aa72..3c9dd7d2da 100755 --- a/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_fob.sh @@ -1,3 +1,3 @@ #!/bin/bash -cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/fob -python3 Tester_fob.py -i 1 +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Simulator/fob +python3 Simulator_fob.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh new file mode 100755 index 0000000000..666d22241a --- /dev/null +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_simulator.sh @@ -0,0 +1,3 @@ +#!/bin/bash +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Simulator/simulator +python3 Simulator_tester.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh deleted file mode 100755 index 62c539e10b..0000000000 --- a/example/Python/src/DigitalTwin/DoubleUnlock/run_tester.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/tester -python3 Tester_tester.py -i 1 diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh index 38720f8dcf..b37cc57d20 100755 --- a/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh +++ b/example/Python/src/DigitalTwin/DoubleUnlock/run_twin.sh @@ -1,3 +1,3 @@ #!/bin/bash -cd ../../../src-gen/DigitalTwin/DoubleUnlock/Tester/twin -python3 Tester_twin.py -i 1 +cd ../../../src-gen/DigitalTwin/DoubleUnlock/Simulator/twin +python3 Simulator_twin.py -i 1 From f8b2c09a309d2918759cec20bd1b17a2752f8b44 Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Sat, 22 Jan 2022 10:15:07 -0800 Subject: [PATCH 204/221] add comment and clean up --- .../src/DigitalTwin/DoubleUnlock/Simulator.lf | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf b/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf index 535bdfb953..38749705c8 100644 --- a/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf +++ b/example/Python/src/DigitalTwin/DoubleUnlock/Simulator.lf @@ -1,3 +1,12 @@ +/** + * Example of a reactor that sends simulated key presses at arbitrary + * logical time to the key fobs. + * + * For run instructions, see README.md in the same directory. + * + * @author Hou Seng Wong (housengw@berkeley.edu) + */ + target Python { docker: true, files: ["../utils.py"] @@ -6,7 +15,6 @@ target Python { import DoubleUnlockKeyFob from "DoubleUnlockDemo.lf"; -# TODO: Why does messages from tester to key fobs not get processed on the event QUEUE??? reactor DoubleUnlockKeyFobTester(initial_delay(5)) { state logger({=None=}); state window({=None=}); @@ -23,10 +31,12 @@ reactor DoubleUnlockKeyFobTester(initial_delay(5)) { =} reaction(do_test) -> simulate_press_fob, simulate_press_twin {= - # simulate_press_fob.schedule(0, True) - # simulate_press_twin.schedule(0, True) simulate_press_fob.schedule(0, False) simulate_press_twin.schedule(0, False) + + # Feel free to add other simulated key presses... + # simulate_press_fob.schedule(0, True) + # simulate_press_twin.schedule(0, True) =} reaction(simulate_press_fob) -> send_lock_press_to_fob {= From a4b6be8828b5ffd10a17501a5b1f847f2a79bf7f Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 13 Jan 2022 17:51:16 -0800 Subject: [PATCH 205/221] Ported LinguaFrancaScopingTest to Java --- .../compiler/LinguaFrancaScopingTest.java | 244 ++++++++++++++++++ .../compiler/LinguaFrancaScopingTest.xtend | 155 ----------- 2 files changed, 244 insertions(+), 155 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java delete mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java new file mode 100644 index 0000000000..0fbff89850 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java @@ -0,0 +1,244 @@ +/* Scoping unit tests. */ + +/************* +Copyright (c) 2019, 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.tests.compiler; + +import com.google.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.lflang.lf.Model; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Assertions; +import org.eclipse.xtext.testing.validation.ValidationTestHelper; +import org.lflang.lf.LfPackage; +import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic; +import org.lflang.tests.LFInjectorProvider; +import org.eclipse.emf.ecore.resource.ResourceSet; +import org.eclipse.xtext.generator.JavaIoFileSystemAccess; +import org.junit.jupiter.api.extension.ExtendWith; +import org.lflang.generator.LFGenerator; +import com.google.inject.Provider; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + +/** + * Test harness for ensuring that cross-references are + * established correctly and reported when faulty. + */ +public class LinguaFrancaScopingTest { + @Inject + ParseHelper parser; + + @Inject + LFGenerator generator; + + @Inject + JavaIoFileSystemAccess fileAccess; + + @Inject + Provider resourceSetProvider; + + @Inject + ValidationTestHelper validator; + + /** + * Ensure that invalid references to contained reactors are reported. + */ + @Test + public void unresolvedReactorReference() throws Exception { +// Java 17: +// Model model = """ +// target C; +// reactor From { +// output y:int; +// } +// reactor To { +// input x:int; +// } +// +// main reactor { +// a = new From(); +// d = new To(); +// s.y -> d.x; +// } +// """; +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "reactor From {", + " output y:int;", + "}", + "reactor To {", + " input x:int;", + "}", + "", + "main reactor {", + " a = new From();", + " d = new To();", + " s.y -> d.x;", + "}" + )); + + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing."); + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Instantiation 's'"); + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'y'"); + } + + + /** + * Ensure that invalid references to ports + * of contained reactors are reported. + */ + @Test + public void unresolvedHierarchicalPortReference() throws Exception { +// Java 17: +// Model model = """ +// target C; +// reactor From { +// output y:int; +// } +// reactor To { +// input x:int; +// } +// +// main reactor { +// a = new From(); +// d = new To(); +// a.x -> d.y; +// } +// """; +// Java 11: + + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "reactor From {", + " output y:int;", + "}", + "reactor To {", + " input x:int;", + "}\n", + "main reactor {", + " a = new From();", + " d = new To();", + " a.x -> d.y;", + "}" + )); + + + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing."); + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'x'"); + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'y'"); + } + + @Test + public void unresolvedReferenceInTriggerClause() throws Exception { +// Java 17: +// Model model = """ +// target C; +// main reactor { +// reaction(unknown) {==} +// } +// """ +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "main reactor {", + " reaction(unknown) {==}", + "}" + )); + + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'unknown'."); + } + + @Test + public void unresolvedReferenceInUseClause() throws Exception { +// Java 17: +// Model model = """ +// target C; +// main reactor { +// reaction() unknown {==} +// } +// """ +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "main reactor {", + " reaction() unknown {==}", + "}" + )); + + + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'unknown'."); + } + + @Test + public void unresolvedReferenceInEffectsClause() throws Exception { +// Java 17: +// Model model = """ +// target C; +// main reactor { +// reaction() -> unknown {==} +// } +// """ +// Java 11: + + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "main reactor {", + " reaction() -> unknown {==}", + "}" + )); + + + validator.assertError(model, LfPackage.eINSTANCE.getVarRef(), + XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, + "Couldn't resolve reference to Variable 'unknown'."); + } + +} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend deleted file mode 100644 index ea41293187..0000000000 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.xtend +++ /dev/null @@ -1,155 +0,0 @@ -/* Scoping unit tests. */ - -/************* -Copyright (c) 2019, 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.tests.compiler - -import com.google.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.testing.util.ParseHelper -import org.lflang.lf.Model -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.^extension.ExtendWith -import org.eclipse.xtext.testing.validation.ValidationTestHelper -import org.lflang.lf.LfPackage -import org.eclipse.xtext.linking.impl.XtextLinkingDiagnostic -import org.lflang.tests.LFInjectorProvider - -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) - -/** - * Test harness for ensuring that cross-references are - * established correctly and reported when faulty. - */ -class LinguaFrancaScopingTest { - @Inject extension ParseHelper - @Inject extension ValidationTestHelper - - /** - * Ensure that invalid references to contained reactors are reported. - */ - @Test - def void unresolvedReactorReference() { - val model = ''' - target C; - reactor From { - output y:int; - } - reactor To { - input x:int; - } - - main reactor WrongConnect { - a = new From(); - d = new To(); - s.y -> d.x; - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing.") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Instantiation 's'") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'y'") - } - - - /** - * Ensure that invalid references to ports - * of contained reactors are reported. - */ - @Test - def void unresolvedHierarchicalPortReference() { - val model = ''' - target C; - reactor From { - output y:int; - } - reactor To { - input x:int; - } - - main reactor WrongConnect { - a = new From(); - d = new To(); - a.x -> d.y; - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing.") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'x'") - model.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'y'") - } - - @Test - def void unresolvedReferenceInTriggerClause() { - ''' - target C; - main reactor { - reaction(unknown) {==} - } - '''.parse.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'.") - } - - @Test - def void unresolvedReferenceInUseClause() { - ''' - target C; - main reactor { - reaction() unknown {==} - } - '''.parse.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'.") - } - - @Test - def void unresolvedReferenceInEffectsClause() { - ''' - target C; - main reactor { - reaction() -> unknown {==} - } - '''.parse.assertError(LfPackage::eINSTANCE.varRef, - XtextLinkingDiagnostic.LINKING_DIAGNOSTIC, - "Couldn't resolve reference to Variable 'unknown'.") - } - -} From ea81a9e249fe967be966243b2d812e823a5fbac2 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Thu, 13 Jan 2022 23:27:38 -0800 Subject: [PATCH 206/221] Ported one more class and added a utility. --- .../compiler/LinguaFrancaASTUtilsTest.java | 299 ++++++++++++++++++ .../compiler/LinguaFrancaASTUtilsTest.xtend | 223 ------------- .../compiler/LinguaFrancaScopingTest.java | 2 +- org.lflang/src/org/lflang/util/XtendUtil.java | 29 +- 4 files changed, 328 insertions(+), 225 deletions(-) create mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java delete mode 100644 org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java new file mode 100644 index 0000000000..def20a0098 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -0,0 +1,299 @@ +/* ASTUtils Unit Tests. */ + +/************* +Copyright (c) 2019, 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.tests.compiler; + +import java.util.LinkedList; +import java.util.List; +import javax.inject.Inject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.eclipse.emf.ecore.EObject; +import org.lflang.ASTUtils; +import org.lflang.lf.Instantiation; +import org.lflang.lf.Model; +import org.lflang.lf.Parameter; +import org.lflang.lf.StateVar; +import java.util.stream.Collectors; +import org.lflang.tests.LFInjectorProvider; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static org.lflang.ASTUtils.*; +import static org.lflang.util.XtendUtil.*; + +/** + * Collection of unit tests on the ASTutils. + * + * @author{Christian Menard } + */ + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + +class LinguaFrancaASTUtilsTest { + @Inject + ParseHelper parser; + + /** + * Test that isInititialized returns true for inititialized state variables + */ + @Test + public void initializedState() throws Exception { +// Java 17: +// Model model = parser.parse(""" +// target Cpp; +// main reactor Foo { +// state a(); +// state b:int(3); +// state c:int[](1,2,3); +// state d(1 sec); +// state e(1 sec, 2 sec, 3 sec); +// } +// """); +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + " state a();", + " state b:int(3);", + " state c:int[](1,2,3);", + " state d(1 sec);", + " state e(1 sec, 2 sec, 3 sec);", + "}" + )); + + + + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + + model.eResource().getErrors()); + + model.eAllContents().forEachRemaining((obj) -> { + if (obj instanceof StateVar) { + Assertions.assertTrue(isInitialized((StateVar)obj)); + } + }); + } + + /** + * Test that isInititialized returns false for uninititialized state variables + */ + @Test + public void uninitializedState() throws Exception { +// Java 17: +// Model model = parser.parse(""" +// target Cpp; +// main reactor Foo { +// state a; +// state b:int; +// state c:int[]; +// state d:time; +// state e:time[]; +// } +// ''' +// Java 11: + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target Cpp;", + "main reactor {", + " state a;", + " state b:int;", + " state c:int[];", + " state d:time;", + " state e:time;", + "}" + )); + + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + + model.eResource().getErrors()); + + model.eAllContents().forEachRemaining((obj) -> { + if (obj instanceof StateVar) { + Assertions.assertFalse(isInitialized((StateVar)obj)); + } + }); + + } + + /** + * Test reading initial values of parameters. + * This checks that the example given in the documentation of the + * ASTUtils.initialValue() function behaves as stated in the docs. + */ + @Test + public void initialValue() throws Exception { + +// Java 17: +// Model model = parser.parse(""" +// target C; +// reactor A(x:int(1)) {} +// reactor B(y:int(2)) { +// a1 = new A(x = y); +// a2 = new A(x = -1); +// } +// reactor C(z:int(3)) { +// b1 = new B(y = z); +// b2 = new B(y = -2); +// } +// """ +// Java 11: + + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "reactor A(x:int(1)) {}", + "reactor B(y:int(2)) {", + " a1 = new A(x = y);", + " a2 = new A(x = -1);", + "}", + "reactor C(z:int(3)) {", + " b1 = new B(y = z);", + " b2 = new B(y = -2);", + "}" + )); + + Assertions.assertNotNull(model); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + + model.eResource().getErrors()); + + // Find all the Instantiations. + Instantiation a1 = null; + Instantiation a2 = null; + Instantiation b1 = null; + Instantiation b2 = null; + + + List objs = asStream(model.eAllContents()).filter(obj -> + (obj instanceof Instantiation) + ).collect(Collectors.toList()); + for (EObject obj : objs) { + if (obj instanceof Instantiation) { + Instantiation inst = (Instantiation)obj; + if (inst.getName() == "a1") { + a1 = inst; + } + if (inst.getName() == "a2") { + a2 = inst; + } + if (inst.getName() == "b1") { + b1 = inst; + } + if (inst.getName() == "b1") { + b2 = inst; + } + } + } + + // Construct all relevant instantiation lists. + var list_a1 = new LinkedList(); + list_a1.add(a1); + var list_a2 = new LinkedList(); + list_a2.add(a2); + var list_a1b1 = new LinkedList(); + list_a1b1.add(a1); + list_a1b1.add(b1); + var list_a2b1 = new LinkedList(); + list_a2b1.add(a2); + list_a2b1.add(b1); + var list_a1b2 = new LinkedList(); + list_a1b2.add(a1); + list_a1b2.add(b2); + var list_a2b2 = new LinkedList(); + list_a2b2.add(a2); + list_a2b2.add(b2); + var list_b1 = new LinkedList(); + list_b1.add(b1); + var list_b2 = new LinkedList(); + list_b2.add(b2); + + /* Check for this: + * initialValue(x, null) returns 1 + * initialValue(x, [a1]) returns 2 + * initialValue(x, [a2]) returns -1 + * initialValue(x, [a1, b1]) returns 3 + * initialValue(x, [a2, b1]) returns -1 + * initialValue(x, [a1, b2]) returns -2 + * initialValue(x, [a2, b2]) returns -1 + * + * initialValue(y, null) returns 2 + * initialValue(y, [a1]) throws an IllegalArgumentException + * initialValue(y, [b1]) returns 3 + * initialValue(y, [b2]) returns -2 + */ + + model.eAllContents().forEachRemaining((obj) -> { + if (obj instanceof Parameter) { + Parameter parameter = (Parameter)obj; + if (parameter.getName() == "x") { + var values = ASTUtils.initialValue(parameter, null); + Assertions.assertEquals(values.get(0).getLiteral(), "1"); + + values = ASTUtils.initialValue(parameter, list_a1); + Assertions.assertEquals(values.get(0).getLiteral(), "2"); + + values = ASTUtils.initialValue(parameter, list_a2); + Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + + values = ASTUtils.initialValue(parameter, list_a1b1); + Assertions.assertEquals(values.get(0).getLiteral(), "3"); + + values = ASTUtils.initialValue(parameter, list_a2b1); + Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + + values = ASTUtils.initialValue(parameter, list_a1b2); + Assertions.assertEquals(values.get(0).getLiteral(), "-2"); + + values = ASTUtils.initialValue(parameter, list_a2b2); + Assertions.assertEquals(values.get(0).getLiteral(), "-1"); + } else if (parameter.getName() == "y") { + var values = ASTUtils.initialValue(parameter, null); + Assertions.assertEquals(values.get(0).getLiteral(), "2"); + + try { + values = ASTUtils.initialValue(parameter, list_a1); + } catch (IllegalArgumentException ex) { + Assertions.assertTrue(ex.getMessage().startsWith("Parameter y is not")); + } + + values = ASTUtils.initialValue(parameter, list_b1); + Assertions.assertEquals(values.get(0).getLiteral(), "3"); + + values = ASTUtils.initialValue(parameter, list_b2); + Assertions.assertEquals(values.get(0).getLiteral(), "-2"); + } + } + }); + } +} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend deleted file mode 100644 index 48b88a71d3..0000000000 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.xtend +++ /dev/null @@ -1,223 +0,0 @@ -/* ASTUtils Unit Tests. */ - -/************* -Copyright (c) 2019, 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.tests.compiler - -import java.util.LinkedList -import javax.inject.Inject -import org.eclipse.xtext.testing.InjectWith -import org.eclipse.xtext.testing.extensions.InjectionExtension -import org.eclipse.xtext.testing.util.ParseHelper -import org.lflang.ASTUtils -import org.lflang.lf.Instantiation -import org.lflang.lf.Model -import org.lflang.lf.Parameter -import org.lflang.lf.StateVar -import org.lflang.tests.LFInjectorProvider -import org.junit.jupiter.api.Assertions -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.^extension.ExtendWith - -import static extension org.lflang.ASTUtils.* - -/** - * Collection of unit tests on the ASTutils. - * - * @author{Christian Menard } - */ -@ExtendWith(InjectionExtension) -@InjectWith(LFInjectorProvider) -class LinguaFrancaASTUtilsTest { - @Inject extension ParseHelper - - /** - * Test that isInititialized returns true for inititialized state variables - */ - @Test - def void initializedState() { - val model = ''' - target Cpp; - main reactor Foo { - state a(); - state b:int(3); - state c:int[](1,2,3); - state d(1 sec); - state e(1 sec, 2 sec, 3 sec); - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - - for (state : model.eAllContents.filter(StateVar).toList) { - Assertions.assertTrue(state.isInitialized) - } - } - - /** - * Test that isInititialized returns false for uninititialized state variables - */ - @Test - def void uninitializedState() { - val model = ''' - target Cpp; - main reactor Foo { - state a; - state b:int; - state c:int[]; - state d:time; - state e:time[]; - } - '''.parse - - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - - for (state : model.eAllContents.filter(StateVar).toList) { - Assertions.assertFalse(state.isInitialized) - } - } - - /** - * Test reading initial values of parameters. - * This checks that the example given in the documentation of the - * ASTUtils.initialValue() function behaves as stated in the docs. - */ - @Test - def void initialValue() { - val model = ''' - target C; - reactor A(x:int(1)) {} - reactor B(y:int(2)) { - a1 = new A(x = y); - a2 = new A(x = -1); - } - reactor C(z:int(3)) { - b1 = new B(y = z); - b2 = new B(y = -2); - } - '''.parse - Assertions.assertNotNull(model) - Assertions.assertTrue(model.eResource.errors.isEmpty, - "Encountered unexpected error while parsing: " + - model.eResource.errors) - - // Find all the Instantiations. - var a1 = null as Instantiation; - var a2 = null as Instantiation; - var b1 = null as Instantiation; - var b2 = null as Instantiation; - for (instantiation: model.eAllContents.filter(Instantiation).toList) { - switch (instantiation.name) { - case "a1": a1 = instantiation - case "a2": a2 = instantiation - case "b1": b1 = instantiation - case "b2": b2 = instantiation - } - } - - // Construct all relevant instantiation lists. - val list_a1 = new LinkedList(); - list_a1.add(a1); - val list_a2 = new LinkedList(); - list_a2.add(a2); - val list_a1b1 = new LinkedList(); - list_a1b1.add(a1); - list_a1b1.add(b1); - val list_a2b1 = new LinkedList(); - list_a2b1.add(a2); - list_a2b1.add(b1); - val list_a1b2 = new LinkedList(); - list_a1b2.add(a1); - list_a1b2.add(b2); - val list_a2b2 = new LinkedList(); - list_a2b2.add(a2); - list_a2b2.add(b2); - val list_b1 = new LinkedList(); - list_b1.add(b1); - val list_b2 = new LinkedList(); - list_b2.add(b2); - - /* Check for this: - * initialValue(x, null) returns 1 - * initialValue(x, [a1]) returns 2 - * initialValue(x, [a2]) returns -1 - * initialValue(x, [a1, b1]) returns 3 - * initialValue(x, [a2, b1]) returns -1 - * initialValue(x, [a1, b2]) returns -2 - * initialValue(x, [a2, b2]) returns -1 - * - * initialValue(y, null) returns 2 - * initialValue(y, [a1]) throws an IllegalArgumentException - * initialValue(y, [b1]) returns 3 - * initialValue(y, [b2]) returns -2 - */ - for (parameter : model.eAllContents.filter(Parameter).toList) { - if (parameter.name == 'x') { - var values = ASTUtils.initialValue(parameter, null); - Assertions.assertEquals(values.get(0).literal, "1"); - - values = ASTUtils.initialValue(parameter, list_a1); - Assertions.assertEquals(values.get(0).literal, "2"); - - values = ASTUtils.initialValue(parameter, list_a2); - Assertions.assertEquals(values.get(0).literal, "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b1); - Assertions.assertEquals(values.get(0).literal, "3"); - - values = ASTUtils.initialValue(parameter, list_a2b1); - Assertions.assertEquals(values.get(0).literal, "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b2); - Assertions.assertEquals(values.get(0).literal, "-2"); - - values = ASTUtils.initialValue(parameter, list_a2b2); - Assertions.assertEquals(values.get(0).literal, "-1"); - } else if (parameter.name == 'y') { - var values = ASTUtils.initialValue(parameter, null); - Assertions.assertEquals(values.get(0).literal, "2"); - - try { - values = ASTUtils.initialValue(parameter, list_a1); - } catch (IllegalArgumentException ex) { - Assertions.assertTrue(ex.message.startsWith("Parameter y is not")); - } - - values = ASTUtils.initialValue(parameter, list_b1); - Assertions.assertEquals(values.get(0).literal, "3"); - - values = ASTUtils.initialValue(parameter, list_b2); - Assertions.assertEquals(values.get(0).literal, "-2"); - } - } - } -} diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java index 0fbff89850..70c3880a49 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaScopingTest.java @@ -149,7 +149,7 @@ public void unresolvedHierarchicalPortReference() throws Exception { "}", "reactor To {", " input x:int;", - "}\n", + "}", "main reactor {", " a = new From();", " d = new To();", diff --git a/org.lflang/src/org/lflang/util/XtendUtil.java b/org.lflang/src/org/lflang/util/XtendUtil.java index 0cf944cee4..4a89307b4e 100644 --- a/org.lflang/src/org/lflang/util/XtendUtil.java +++ b/org.lflang/src/org/lflang/util/XtendUtil.java @@ -24,10 +24,15 @@ package org.lflang.util; +import java.util.Iterator; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + /** * A utility class for things missing from Xtend. * * @author Clément Fournier + * @author Marten Lohstroh */ public final class XtendUtil { @@ -36,10 +41,32 @@ private XtendUtil() { } /** - * Returns the bitwise OR of the two given long integers. + * Return the bitwise OR of the two given long integers. * Xtend doesn't support bitwise operators. */ public static long longOr(long a, long b) { return a | b; } + + /** + * Turn an iterator into a sequential stream. + * + * @param iterator The iterator to create a sequential stream for. + * @return A stream. + */ + public static Stream asStream(Iterator iterator) { + return asStream(iterator, false); + } + + /** + * Turn an iterator into a sequential or parallel stream. + * + * @param iterator The iterator to create a stream for. + * @param parallel Whether or not the stream should be parallel. + * @return A stream. + */ + public static Stream asStream(Iterator iterator, boolean parallel) { + Iterable iterable = () -> iterator; + return StreamSupport.stream(iterable.spliterator(), parallel); + } } From d4a0d10ff7736001ee13cb4fd6924fd7372af235 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Wed, 19 Jan 2022 21:06:06 -0800 Subject: [PATCH 207/221] Respond to comments from @oowekyala --- .../compiler/LinguaFrancaASTUtilsTest.java | 150 +++++++++--------- 1 file changed, 73 insertions(+), 77 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java index def20a0098..d0cbf1674a 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -27,26 +27,28 @@ package org.lflang.tests.compiler; -import java.util.LinkedList; +import static org.lflang.ASTUtils.isInitialized; +import static org.lflang.util.XtendUtil.asStream; + import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + import javax.inject.Inject; + +import org.eclipse.emf.ecore.EObject; import org.eclipse.xtext.testing.InjectWith; import org.eclipse.xtext.testing.extensions.InjectionExtension; import org.eclipse.xtext.testing.util.ParseHelper; -import org.eclipse.emf.ecore.EObject; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.lflang.ASTUtils; import org.lflang.lf.Instantiation; import org.lflang.lf.Model; import org.lflang.lf.Parameter; import org.lflang.lf.StateVar; -import java.util.stream.Collectors; import org.lflang.tests.LFInjectorProvider; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; - -import static org.lflang.ASTUtils.*; -import static org.lflang.util.XtendUtil.*; /** * Collection of unit tests on the ASTutils. @@ -145,6 +147,38 @@ public void uninitializedState() throws Exception { }); } + /** + * Return a map from strings to instantiations given a model. + * + * @param model The model to discover instantiations in. + */ + private Map getInsts(Model model) { + Instantiation a1 = null; + Instantiation a2 = null; + Instantiation b1 = null; + Instantiation b2 = null; + + List objs = asStream(model.eAllContents()) + .filter(obj -> (obj instanceof Instantiation)) + .collect(Collectors.toList()); + + for (EObject obj : objs) { + if (obj instanceof Instantiation) { + Instantiation inst = (Instantiation) obj; + if (inst.getName().equals("a1")) { + a1 = inst; + } else if (inst.getName().equals("a2")) { + a2 = inst; + } else if (inst.getName().equals("b1")) { + b1 = inst; + } else if (inst.getName().equals("b2")) { + b2 = inst; + } + } + } + + return Map.of("a1", a1, "a2", a2, "b1", b1, "b2", b2); + } /** * Test reading initial values of parameters. @@ -188,55 +222,7 @@ public void initialValue() throws Exception { "Encountered unexpected error while parsing: " + model.eResource().getErrors()); - // Find all the Instantiations. - Instantiation a1 = null; - Instantiation a2 = null; - Instantiation b1 = null; - Instantiation b2 = null; - - - List objs = asStream(model.eAllContents()).filter(obj -> - (obj instanceof Instantiation) - ).collect(Collectors.toList()); - for (EObject obj : objs) { - if (obj instanceof Instantiation) { - Instantiation inst = (Instantiation)obj; - if (inst.getName() == "a1") { - a1 = inst; - } - if (inst.getName() == "a2") { - a2 = inst; - } - if (inst.getName() == "b1") { - b1 = inst; - } - if (inst.getName() == "b1") { - b2 = inst; - } - } - } - - // Construct all relevant instantiation lists. - var list_a1 = new LinkedList(); - list_a1.add(a1); - var list_a2 = new LinkedList(); - list_a2.add(a2); - var list_a1b1 = new LinkedList(); - list_a1b1.add(a1); - list_a1b1.add(b1); - var list_a2b1 = new LinkedList(); - list_a2b1.add(a2); - list_a2b1.add(b1); - var list_a1b2 = new LinkedList(); - list_a1b2.add(a1); - list_a1b2.add(b2); - var list_a2b2 = new LinkedList(); - list_a2b2.add(a2); - list_a2b2.add(b2); - var list_b1 = new LinkedList(); - list_b1.add(b1); - var list_b2 = new LinkedList(); - list_b2.add(b2); + var map = getInsts(model); /* Check for this: * initialValue(x, null) returns 1 @@ -259,38 +245,48 @@ public void initialValue() throws Exception { if (parameter.getName() == "x") { var values = ASTUtils.initialValue(parameter, null); Assertions.assertEquals(values.get(0).getLiteral(), "1"); - - values = ASTUtils.initialValue(parameter, list_a1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"))); Assertions.assertEquals(values.get(0).getLiteral(), "2"); - - values = ASTUtils.initialValue(parameter, list_a2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"), map.get("b1"))); Assertions.assertEquals(values.get(0).getLiteral(), "3"); - - values = ASTUtils.initialValue(parameter, list_a2b1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a2"), map.get("b1"))); Assertions.assertEquals(values.get(0).getLiteral(), "-1"); - - values = ASTUtils.initialValue(parameter, list_a1b2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"), map.get("b2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-2"); - - values = ASTUtils.initialValue(parameter, list_a2b2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("a2"), map.get("b2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-1"); } else if (parameter.getName() == "y") { var values = ASTUtils.initialValue(parameter, null); Assertions.assertEquals(values.get(0).getLiteral(), "2"); - + try { - values = ASTUtils.initialValue(parameter, list_a1); + values = ASTUtils.initialValue(parameter, + List.of(map.get("a1"))); } catch (IllegalArgumentException ex) { - Assertions.assertTrue(ex.getMessage().startsWith("Parameter y is not")); + Assertions.assertTrue(ex.getMessage() + .startsWith("Parameter y is not")); } - - values = ASTUtils.initialValue(parameter, list_b1); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("b1"))); Assertions.assertEquals(values.get(0).getLiteral(), "3"); - - values = ASTUtils.initialValue(parameter, list_b2); + + values = ASTUtils.initialValue(parameter, + List.of(map.get("b2"))); Assertions.assertEquals(values.get(0).getLiteral(), "-2"); } } From 12124dc6058f40a9f43d78baa7771ea28c485f36 Mon Sep 17 00:00:00 2001 From: Marten Lohstroh Date: Sat, 22 Jan 2022 22:08:18 -0800 Subject: [PATCH 208/221] Update org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Clément Fournier --- .../compiler/LinguaFrancaASTUtilsTest.java | 29 +++---------------- 1 file changed, 4 insertions(+), 25 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java index d0cbf1674a..7b738bf043 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LinguaFrancaASTUtilsTest.java @@ -153,31 +153,10 @@ public void uninitializedState() throws Exception { * @param model The model to discover instantiations in. */ private Map getInsts(Model model) { - Instantiation a1 = null; - Instantiation a2 = null; - Instantiation b1 = null; - Instantiation b2 = null; - - List objs = asStream(model.eAllContents()) - .filter(obj -> (obj instanceof Instantiation)) - .collect(Collectors.toList()); - - for (EObject obj : objs) { - if (obj instanceof Instantiation) { - Instantiation inst = (Instantiation) obj; - if (inst.getName().equals("a1")) { - a1 = inst; - } else if (inst.getName().equals("a2")) { - a2 = inst; - } else if (inst.getName().equals("b1")) { - b1 = inst; - } else if (inst.getName().equals("b2")) { - b2 = inst; - } - } - } - - return Map.of("a1", a1, "a2", a2, "b1", b1, "b2", b2); + return asStream(model.eAllContents()) + .filter(obj -> obj instanceof Instantiation) + .map(obj -> (Instantiation) obj) + .collect(Collectors.toMap(Instantiation::getName, it -> it)); } /** From aaf8faf7049e03ef2f3fdce4d97a87a164427d9d Mon Sep 17 00:00:00 2001 From: Hou Seng Wong Date: Mon, 24 Jan 2022 13:33:01 -0800 Subject: [PATCH 209/221] change lsp ci yaml to master --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1278e6d55..45517c0ffc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,7 +36,7 @@ jobs: # Run language server tests. lsp-tests: - uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@vscode-validate-generated-code-cleanups + uses: lf-lang/lingua-franca/.github/workflows/lsp-tests.yml@master # Run the C integration tests. c-tests: From 89d14318cdd812d02ef603a099b87d33ac32238b Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 24 Jan 2022 16:56:50 -0800 Subject: [PATCH 210/221] Update org.lflang/src/org/lflang/generator/ReactorInstance.java Good suggestion. Co-authored-by: Marten Lohstroh --- org.lflang/src/org/lflang/generator/ReactorInstance.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 51fbe49775..309678c4df 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -222,7 +222,7 @@ public void clearCaches(boolean includingRuntimes) { /** * Return the set of ReactionInstance and PortInstance that form causality - * loops in the topmost parent reactor of this reactor. This will return an + * loops in the topmost parent reactor in the instantiation hierarchy. This will return an * empty set if there are no causality loops. */ public Set> getCycles() { From 47d6792d1e04c69a8fd9ef3075eef0bb172515c9 Mon Sep 17 00:00:00 2001 From: "Edward A. Lee" Date: Mon, 24 Jan 2022 16:57:58 -0800 Subject: [PATCH 211/221] Update org.lflang/src/org/lflang/generator/c/CGenerator.xtend Fixed typo. Co-authored-by: Marten Lohstroh --- org.lflang/src/org/lflang/generator/c/CGenerator.xtend | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index beee0fa35c..59692e7610 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -6454,7 +6454,7 @@ class CGenerator extends GeneratorBase { * For each port, this provides a list of reaction indices that * are triggered by the port, or an empty list if there are no * reactions triggered by the port. - * @param reactor The contianer. + * @param reactor The container. * @param federate The federate (used to determine whether a * reaction belongs to the federate). */ From 8ce66b886f8f9518f7fb49a759c895b7fb614047 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 24 Jan 2022 16:35:59 -0800 Subject: [PATCH 212/221] Moved and renamed function per code review --- org.lflang/src/org/lflang/JavaAstUtils.java | 30 ------------------- .../federated/PythonGeneratorExtension.java | 7 +++-- .../org/lflang/generator/c/CGenerator.xtend | 6 ++-- .../src/org/lflang/generator/c/CUtil.java | 28 +++++++++++++++++ 4 files changed, 35 insertions(+), 36 deletions(-) diff --git a/org.lflang/src/org/lflang/JavaAstUtils.java b/org.lflang/src/org/lflang/JavaAstUtils.java index 1c4ec20324..65299de374 100644 --- a/org.lflang/src/org/lflang/JavaAstUtils.java +++ b/org.lflang/src/org/lflang/JavaAstUtils.java @@ -204,36 +204,6 @@ public static String generateVarRef(VarRef reference) { return prefix + reference.getVariable().getName(); } - /** - * Generate code for referencing a port possibly indexed by - * a bank index and/or a multiport index. This assumes the target language uses - * the usual array indexing [n] for both cases. If the provided reference is - * not a port, then this returns the string "ERROR: not a port." - * @param reference The reference to the port. - * @param bankIndex A bank index or null or negative if not in a bank. - * @param multiportIndex A multiport index or null or negative if not in a multiport. - */ - public static String generatePortRef(VarRef reference, Integer bankIndex, Integer multiportIndex) { - // FIXME: Should this be moved to CUtil? It is intended to generalize beyond C, but as of this - // writing, only CGenerator and PythonGeneratorExtension use it. - if (!(reference.getVariable() instanceof Port)) { - return "ERROR: not a port."; // FIXME: This is not the fail-fast approach, and it seems arbitrary. - } - var prefix = ""; - if (reference.getContainer() != null) { - var bank = ""; - if (reference.getContainer().getWidthSpec() != null && bankIndex != null && bankIndex >= 0) { - bank = "[" + bankIndex + "]"; - } - prefix = reference.getContainer().getName() + bank + "."; - } - var multiport = ""; - if (((Port) reference.getVariable()).getWidthSpec() != null && multiportIndex != null && multiportIndex >= 0) { - multiport = "[" + multiportIndex + "]"; - } - return prefix + reference.getVariable().getName() + multiport; - } - /** * Assuming that the given value denotes a valid time literal, * return a time value. diff --git a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java index 39a842d42f..2d5b5117ab 100644 --- a/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java +++ b/org.lflang/src/org/lflang/federated/PythonGeneratorExtension.java @@ -27,11 +27,12 @@ package org.lflang.federated; import org.lflang.InferredType; -import org.lflang.TargetProperty.CoordinationType; import org.lflang.JavaAstUtils; +import org.lflang.TargetProperty.CoordinationType; import org.lflang.federated.serialization.FedNativePythonSerialization; import org.lflang.federated.serialization.FedSerialization; import org.lflang.federated.serialization.SupportedSerializers; +import org.lflang.generator.c.CUtil; import org.lflang.generator.python.PythonGenerator; import org.lflang.lf.Action; import org.lflang.lf.Delay; @@ -75,7 +76,7 @@ public static String generateNetworkSenderBody( SupportedSerializers serializer, PythonGenerator generator ) { - String sendRef = JavaAstUtils.generatePortRef(sendingPort, sendingBankIndex, sendingChannelIndex); + String sendRef = CUtil.portRefInReaction(sendingPort, sendingBankIndex, sendingChannelIndex); String receiveRef = JavaAstUtils.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. StringBuilder result = new StringBuilder(); result.append("// Sending from " + sendRef + @@ -179,7 +180,7 @@ public static String generateNetworkReceiverBody( PythonGenerator generator ) { - String receiveRef = JavaAstUtils.generatePortRef(receivingPort, receivingBankIndex, receivingChannelIndex); + String receiveRef = CUtil.portRefInReaction(receivingPort, receivingBankIndex, receivingChannelIndex); StringBuilder result = new StringBuilder(); // Transfer the physical time of arrival from the action to the port diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 59692e7610..2b17a30c48 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -4196,7 +4196,7 @@ class CGenerator extends GeneratorBase { (receivingPort.variable as Port).type.id = "char*" } - var receiveRef = JavaAstUtils.generatePortRef(receivingPort, receivingBankIndex, receivingChannelIndex) + var receiveRef = CUtil.portRefInReaction(receivingPort, receivingBankIndex, receivingChannelIndex) val result = new StringBuilder() // Transfer the physical time of arrival from the action to the port @@ -4288,7 +4288,7 @@ class CGenerator extends GeneratorBase { Delay delay, SupportedSerializers serializer ) { - var sendRef = JavaAstUtils.generatePortRef(sendingPort, sendingBankIndex, sendingChannelIndex); + var sendRef = CUtil.portRefInReaction(sendingPort, sendingBankIndex, sendingChannelIndex); val receiveRef = JavaAstUtils.generateVarRef(receivingPort); // Used for comments only, so no need for bank/multiport index. val result = new StringBuilder() result.append(''' @@ -4454,7 +4454,7 @@ class CGenerator extends GeneratorBase { ) { // Store the code val result = new StringBuilder(); - var sendRef = JavaAstUtils.generatePortRef(port, sendingBankIndex, sendingChannelIndex); + var sendRef = CUtil.portRefInReaction(port, sendingBankIndex, sendingChannelIndex); // Get the delay literal var String additionalDelayString = diff --git a/org.lflang/src/org/lflang/generator/c/CUtil.java b/org.lflang/src/org/lflang/generator/c/CUtil.java index ed5a31bd00..9e561cb8b6 100644 --- a/org.lflang/src/org/lflang/generator/c/CUtil.java +++ b/org.lflang/src/org/lflang/generator/c/CUtil.java @@ -44,6 +44,7 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Parameter; import org.lflang.lf.Port; import org.lflang.lf.ReactorDecl; +import org.lflang.lf.VarRef; import org.lflang.lf.Variable; import org.lflang.lf.WidthTerm; import org.lflang.util.LFCommand; @@ -295,6 +296,33 @@ static public String portRefNestedName( return portRef(port, true, false, runtimeIndex, bankIndex, channelIndex); } + /** + * Return code for referencing a port within a reaction body possibly indexed by + * a bank index and/or a multiport index. If the provided reference is + * not a port, then this returns the string "ERROR: not a port." + * @param reference The reference to the port. + * @param bankIndex A bank index or null or negative if not in a bank. + * @param multiportIndex A multiport index or null or negative if not in a multiport. + */ + public static String portRefInReaction(VarRef reference, Integer bankIndex, Integer multiportIndex) { + if (!(reference.getVariable() instanceof Port)) { + return "ERROR: not a port."; // FIXME: This is not the fail-fast approach, and it seems arbitrary. + } + var prefix = ""; + if (reference.getContainer() != null) { + var bank = ""; + if (reference.getContainer().getWidthSpec() != null && bankIndex != null && bankIndex >= 0) { + bank = "[" + bankIndex + "]"; + } + prefix = reference.getContainer().getName() + bank + "."; + } + var multiport = ""; + if (((Port) reference.getVariable()).getWidthSpec() != null && multiportIndex != null && multiportIndex >= 0) { + multiport = "[" + multiportIndex + "]"; + } + return prefix + reference.getVariable().getName() + multiport; + } + /** * Return a reference to the reaction entry on the self struct * of the parent of the specified reaction. From 909f88278ff9805fa409c3c9a22454866279e6d3 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 24 Jan 2022 16:52:56 -0800 Subject: [PATCH 213/221] Use imperative case in comments, per code review. --- org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index 69cbd24d6d..4ded957be6 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -154,7 +154,7 @@ public static void accommodatePhysicalActionsIfPresent( } /** - * Returns all instances of {@code eObjectType} in + * Return all instances of {@code eObjectType} in * {@code resource}. * @param resource A resource to be searched. * @param nodeType The type of the desired parse tree @@ -237,7 +237,7 @@ public static void validateImports( } /** - * Returns the resources that provide the given + * Return the resources that provide the given * reactors. * @param reactors The reactors for which to find * containing resources. @@ -258,7 +258,7 @@ public static List getResources(Iterable reactors) { } /** - * Returns the {@code LFResource} representation of the + * Return the {@code LFResource} representation of the * given resource. * @param resource The {@code Resource} to be * represented as an {@code LFResource} From 09034841d8344ba08ded61cd4386af4ab39a5586 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 24 Jan 2022 16:55:09 -0800 Subject: [PATCH 214/221] Clarified comment by referring to runtime instances --- org.lflang/src/org/lflang/generator/ReactionInstance.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index b8028c1477..7018d9265c 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -327,7 +327,7 @@ public Set dependsOnReactions() { } /** - * Return a set of levels that instances of this reaction have. + * 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 through multiports. */ @@ -342,7 +342,7 @@ public Set getLevels() { } /** - * Return a list of levels that instances of this reaction have. + * Return a list of levels that runtime instances of this reaction have. * The size of this list is the total number of runtime instances. * A ReactionInstance may have more than one level if it lies within * a bank and its dependencies on other reactions pass through multiports. From 7aad2c6688d3e5d0ae7df58f307f3e4ff8d0f957 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 24 Jan 2022 17:12:44 -0800 Subject: [PATCH 215/221] If clearCaches argument is true, remove runtimes. --- org.lflang/src/org/lflang/generator/ReactionInstance.java | 1 + 1 file changed, 1 insertion(+) diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 7018d9265c..86a2686242 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -252,6 +252,7 @@ public ReactionInstance( public void clearCaches(boolean includingRuntimes) { dependentReactionsCache = null; dependsOnReactionsCache = null; + if (includingRuntimes) runtimeInstances = null; } /** From d9e8b39854a12a2c1742cd54699cb3269cc679f9 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 24 Jan 2022 17:53:09 -0800 Subject: [PATCH 216/221] Improved class comments for ReactorInstance and related classes --- .../org/lflang/generator/NamedInstance.java | 6 +++++- .../lflang/generator/ParameterInstance.java | 2 +- .../src/org/lflang/generator/PortInstance.java | 15 ++++++++++++--- .../org/lflang/generator/ReactionInstance.java | 13 +++++++++---- .../org/lflang/generator/ReactorInstance.java | 18 ++++++++++++++---- 5 files changed, 41 insertions(+), 13 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/NamedInstance.java b/org.lflang/src/org/lflang/generator/NamedInstance.java index db73dd29e7..b047a53bbd 100644 --- a/org.lflang/src/org/lflang/generator/NamedInstance.java +++ b/org.lflang/src/org/lflang/generator/NamedInstance.java @@ -33,7 +33,11 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.eclipse.emf.ecore.EObject; /** - * Base class for instances with names in Lingua Franca. + * Base class for compile-time instances with names in Lingua Franca. + * An instance of concrete subclasses of this class represents one or + * more runtime instances of a reactor, port, reaction, etc. There + * will be more than one runtime instance if the object or any of its + * parents is a bank of reactors. * * @author{Marten Lohstroh } * @author{Edward A. Lee } diff --git a/org.lflang/src/org/lflang/generator/ParameterInstance.java b/org.lflang/src/org/lflang/generator/ParameterInstance.java index 3da8f1dc64..ee2d044e6e 100644 --- a/org.lflang/src/org/lflang/generator/ParameterInstance.java +++ b/org.lflang/src/org/lflang/generator/ParameterInstance.java @@ -37,7 +37,7 @@ import org.lflang.lf.Value; /** - * Representation of a runtime instance of a parameter. + * Representation of a compile-time instance of a parameter. * Upon creation, it is checked whether this parameter is overridden by an * assignment in the instantiation that this parameter instance is a result of. * If it is overridden, the parameter gets initialized using the value looked up diff --git a/org.lflang/src/org/lflang/generator/PortInstance.java b/org.lflang/src/org/lflang/generator/PortInstance.java index 254cebe704..2841d3c854 100644 --- a/org.lflang/src/org/lflang/generator/PortInstance.java +++ b/org.lflang/src/org/lflang/generator/PortInstance.java @@ -1,7 +1,7 @@ /** A data structure for a port instance. */ /************* -Copyright (c) 2019, The University of California at Berkeley. +Copyright (c) 2019-2022, 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: @@ -39,8 +39,17 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.WidthTerm; /** - * Representation of a runtime instance of a port. - * This may be a single port or a multiport. + * Representation of a compile-time instance of a port. + * Like {@link ReactorInstance}, one or more parents of this port + * is a bank of reactors, then there will be more than one runtime instance + * corresponding to this compile-time instance. + * + * This may be a single port or a multiport. If it is a multiport, then + * one instance of this PortInstance class represents all channels. + * If in addition any parent is a bank, then it represents all channels of all + * bank members. The {@link #eventualDestinations()} and {@link #eventualSources()} + * functions report the connectivity of all such channels as lists of + * {@link SendRange} and {@link RuntimeRange} objects. * * @author{Marten Lohstroh } * @author{Edward A. Lee } diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 86a2686242..f3b0991282 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -1,7 +1,7 @@ /** Representation of a runtime instance of a reaction. */ /************* -Copyright (c) 2019, The University of California at Berkeley. +Copyright (c) 2019-2022, 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: @@ -43,9 +43,14 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY import org.lflang.lf.Variable; /** - * Representation of a runtime instance of a reaction. - * A ReactionInstance object stores all dependency information necessary - * for constructing a directed acyclic precedece graph. + * Representation of a compile-time instance of a reaction. + * Like {@link ReactorInstance}, one or more parents of this reaction + * is a bank of reactors, then there will be more than one runtime instance + * corresponding to this compile-time instance. The {@link #getRuntimeInstances()} + * method returns a list of these runtime instances, each an instance of the + * inner class {@link #Runtime}. Each runtime instance has a "level", which is + * its depth an acyclic precedence graph representing the dependencies between + * reactions at a tag. * * @author{Edward A. Lee } * @author{Marten Lohstroh } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 309678c4df..09cc8c2c96 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -1,7 +1,7 @@ /** A data structure for a reactor instance. */ /************* -Copyright (c) 2019, The University of California at Berkeley. +Copyright (c) 2019-2022, 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: @@ -58,11 +58,21 @@ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY /** - * Representation of a runtime instance of a reactor. + * Representation of a compile-time instance of a reactor. + * If the reactor is instantiated as a bank of reactors, or if any + * of its parents is instantiated as a bank of reactors, then one instance + * of this ReactorInstance class represents all the runtime instances within + * these banks. The {@link #getTotalWidth()} method returns the number of such + * runtime instances, which is the product of the bank width of this reactor + * instance and the bank widths of all of its parents. + * There is exactly one instance of this ReactorInstance class for each + * graphical rendition of a reactor in the diagram view. + * * For the main reactor, which has no parent, once constructed, * this object represents the entire Lingua Franca program. - * The constructor analyzes the graph of dependencies between - * reactions and throws exception if this graph is cyclic. + * If the program has causality loops (a programming error), then + * {@link #hasCycles()} will return true and {@link #getCycles()} will + * return the ports and reaction instances involved in the cycles. * * @author{Marten Lohstroh } * @author{Edward A. Lee } From b7882ea914bf7fb9d66375891dc0e179f7153a14 Mon Sep 17 00:00:00 2001 From: eal Date: Mon, 24 Jan 2022 18:03:27 -0800 Subject: [PATCH 217/221] Used more compact list constructors --- .../tests/compiler/MixedRadixIntTest.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java index 424bb5fe54..2cee2468c8 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/MixedRadixIntTest.java @@ -1,18 +1,17 @@ package org.lflang.tests.compiler; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; - import org.lflang.generator.MixedRadixInt; public class MixedRadixIntTest { // Constants used many times below. - List radixes = new ArrayList(List.of(2, 3, 4, 5)); - List digits = new ArrayList(List.of(1, 2, 3, 4)); + List radixes = Arrays.asList(2, 3, 4, 5); + List digits = Arrays.asList(1, 2, 3, 4); @Test public void create() throws Exception { @@ -20,21 +19,21 @@ public void create() throws Exception { Assertions.assertEquals("1%2, 2%3, 3%4, 4%5", num.toString()); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); - List altDigits = new ArrayList(List.of(1, 2, 1)); + List altDigits = List.of(1, 2, 1); MixedRadixInt altNum = new MixedRadixInt(altDigits, radixes, null); Assertions.assertEquals(11, altNum.get()); } @Test public void createWithInfinity() throws Exception { - List radixes = new ArrayList(List.of(2, 3, 4, 10000)); + List radixes = List.of(2, 3, 4, 10000); MixedRadixInt num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals(1 + 2*2 + 3*6 + 4*24, num.get()); } @Test public void createWithError() throws Exception { - List radixes = new ArrayList(List.of(2, 3)); + List radixes = List.of(2, 3); try { new MixedRadixInt(digits, radixes, null); } catch (IllegalArgumentException ex) { @@ -64,9 +63,9 @@ public void createWithNullAndSet() throws Exception { @Test public void testPermutation() throws Exception { - List radixes = new ArrayList(List.of(2, 5)); - List digits = new ArrayList(List.of(1, 2)); - List permutation = new ArrayList(List.of(1, 0)); + List radixes = Arrays.asList(2, 5); + List digits = Arrays.asList(1, 2); + List permutation = Arrays.asList(1, 0); MixedRadixInt num = new MixedRadixInt(digits, radixes, permutation); Assertions.assertEquals(5, num.get()); Assertions.assertEquals(2, num.get(1)); @@ -81,14 +80,14 @@ public void testPermutation() throws Exception { Assertions.assertEquals(7, num.get()); // Test radix infinity digit (effectively). - digits = new ArrayList(List.of(1, 2, 1)); - radixes = new ArrayList(List.of(2, 5, 1000000)); + digits = Arrays.asList(1, 2, 1); + radixes = Arrays.asList(2, 5, 1000000); num = new MixedRadixInt(digits, radixes, null); Assertions.assertEquals(15, num.get()); Assertions.assertEquals(7, num.get(1)); Assertions.assertEquals(15, num.magnitude()); - permutation = new ArrayList(List.of(1, 0, 2)); + permutation = Arrays.asList(1, 0, 2); num = new MixedRadixInt(digits, radixes, permutation); num.increment(); Assertions.assertEquals(17, num.get()); @@ -102,8 +101,8 @@ public void testPermutation() throws Exception { @Test public void testIncrement() throws Exception { - List radixes = new ArrayList(List.of(2, 3)); - List digits = new ArrayList(List.of(0, 2)); + List radixes = Arrays.asList(2, 3); + List digits = Arrays.asList(0, 2); MixedRadixInt num = new MixedRadixInt(digits, radixes, null); num.increment(); Assertions.assertEquals(5, num.get()); From ccec4f9f7fe75498996aa4d729ec0fd84a272f03 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jan 2022 20:54:24 -0800 Subject: [PATCH 218/221] LFC: Disable generated code validation by default. --- org.lflang.lfc/src/org/lflang/lfc/Main.java | 3 ++- .../src/org/lflang/generator/Validator.java | 27 ++++++++++++++++--- .../org/lflang/generator/cpp/CppGenerator.kt | 2 +- .../generator/python/PythonGenerator.xtend | 2 +- .../lflang/generator/rust/RustGenerator.kt | 2 +- .../org/lflang/generator/ts/TSGenerator.kt | 4 +-- .../org/lflang/generator/ts/TSValidator.kt | 7 ++--- 7 files changed, 34 insertions(+), 13 deletions(-) diff --git a/org.lflang.lfc/src/org/lflang/lfc/Main.java b/org.lflang.lfc/src/org/lflang/lfc/Main.java index 51be01ce84..36f495abd4 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/Main.java +++ b/org.lflang.lfc/src/org/lflang/lfc/Main.java @@ -106,6 +106,7 @@ enum CLIOption { COMPILER(null, "target-compiler", true, false, "Target compiler to invoke.", true), CLEAN("c", "clean", false, false, "Clean before building.", true), HELP("h", "help", false, false, "Display this information.", true), + LINT("l", "lint", true, false, "Enable or disable linting of generated code.", true), NO_COMPILE("n", "no-compile", false, false, "Do not invoke target compiler.", true), FEDERATED("f", "federated", false, false, "Treat main reactor as federated.", false), THREADS("t", "threads", true, false, "Specify the default number of threads.", true), @@ -120,7 +121,7 @@ enum CLIOption { public final Option option; /** - * Whether or not to pass this option to the code generator. + * Whether to pass this option to the code generator. */ public final boolean passOn; diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index 5951172825..c64ebf2c8a 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -3,6 +3,7 @@ import org.eclipse.xtext.util.CancelIndicator; import org.lflang.ErrorReporter; +import org.lflang.TargetConfig.Mode; import org.lflang.util.LFCommand; import java.nio.file.Path; import java.util.ArrayList; @@ -50,13 +51,13 @@ protected Validator(ErrorReporter errorReporter, Map codeMaps) { /** * Validate this Validator's group of generated files. - * @param cancelIndicator The cancel indicator for the - * current operation. + * @param context The context of the current build. */ - public final void doValidate(CancelIndicator cancelIndicator) throws ExecutionException, InterruptedException { + public final void doValidate(LFGeneratorContext context) throws ExecutionException, InterruptedException { + if (!validationEnabled(context)) return; final List>> tasks = getValidationStrategies().stream().map( it -> (Callable>) () -> { - it.second.run(cancelIndicator, true); + it.second.run(context.getCancelIndicator(), true); return it; } ).collect(Collectors.toList()); @@ -66,6 +67,24 @@ public final void doValidate(CancelIndicator cancelIndicator) throws ExecutionEx } } + /** + * Return whether generated code validation is enabled for this build. + * @param context The context of the current build. + */ + private boolean validationEnabled(LFGeneratorContext context) { + String lint = context.getArgs().getProperty("lint", context.getMode() == Mode.STANDALONE ? "false" : "true"); + if (lint.equalsIgnoreCase("true")) return true; + else if (lint.equalsIgnoreCase("false")) return false; + else { + errorReporter.reportWarning(String.format( + "Unrecognized value for \"lint\" property: \"%s\". Acceptable values are \"true\" and \"false\". " + + "Assuming default value of \"false\".", + lint + )); + return false; + } + } + /** * Invoke all the given tasks. * @param tasks Any set of tasks. diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index b4ae8fa572..7230446cdb 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -83,7 +83,7 @@ class CppGenerator( // We must compile in order to install the dependencies. Future validations will be faster. doCompile(context, codeMaps) } else if (runCmake(context).first == 0) { - CppValidator(cppFileConfig, errorReporter, codeMaps).doValidate(context.cancelIndicator) + CppValidator(cppFileConfig, errorReporter, codeMaps).doValidate(context) context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(codeMaps)) } else { context.unsuccessfulFinish() diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend index ba928c9a36..8175e0f733 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.xtend @@ -1299,7 +1299,7 @@ class PythonGenerator extends CGenerator { 100 * federateCount / federates.size() ) // If there are no federates, compile and install the generated code - new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context.cancelIndicator) + new PythonValidator(fileConfig, errorReporter, codeMaps, protoNames).doValidate(context) if (!errorsOccurred() && context.mode != Mode.LSP_MEDIUM) { compilingFederatesContext.reportProgress( String.format("Validation complete. Compiling and installing %d/%d Python modules...", federateCount, federates.size()), diff --git a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt index 69cf164104..20e51bbd5a 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustGenerator.kt @@ -86,7 +86,7 @@ class RustGenerator( ) val exec = fileConfig.binPath.toAbsolutePath().resolve(gen.executableName) Files.deleteIfExists(exec) // cleanup, cargo doesn't do it - if (context.mode == TargetConfig.Mode.LSP_MEDIUM) RustValidator(fileConfig, errorReporter, codeMaps).doValidate(context.cancelIndicator) + if (context.mode == TargetConfig.Mode.LSP_MEDIUM) RustValidator(fileConfig, errorReporter, codeMaps).doValidate(context) else invokeRustCompiler(context, gen.executableName, codeMaps) } } diff --git a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt index 7c3669a909..90b5ff0ece 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSGenerator.kt @@ -389,10 +389,10 @@ class TSGenerator( */ private fun passesChecks(validator: TSValidator, parsingContext: LFGeneratorContext): Boolean { parsingContext.reportProgress("Linting generated code...", 0) - validator.doLint(parsingContext.cancelIndicator) + validator.doLint(parsingContext) if (errorsOccurred()) return false parsingContext.reportProgress("Validating generated code...", 25) - validator.doValidate(parsingContext.cancelIndicator) + validator.doValidate(parsingContext) return !errorsOccurred() } diff --git a/org.lflang/src/org/lflang/generator/ts/TSValidator.kt b/org.lflang/src/org/lflang/generator/ts/TSValidator.kt index e217c9d975..2b80ec39e2 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSValidator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSValidator.kt @@ -9,6 +9,7 @@ import org.lflang.ErrorReporter import org.lflang.generator.CodeMap import org.lflang.generator.DiagnosticReporting import org.lflang.generator.HumanReadableReportingStrategy +import org.lflang.generator.LFGeneratorContext import org.lflang.generator.Position import org.lflang.generator.ValidationStrategy import org.lflang.generator.Validator @@ -152,9 +153,9 @@ class TSValidator( /** * Run a relatively fast linter on the generated code. - * @param cancelIndicator The indicator of whether this build process is cancelled. + * @param context The context of the current build. */ - fun doLint(cancelIndicator: CancelIndicator) { - TSLinter(fileConfig, errorReporter, codeMaps).doValidate(cancelIndicator) + fun doLint(context: LFGeneratorContext) { + TSLinter(fileConfig, errorReporter, codeMaps).doValidate(context) } } From 48d95663efdf11c8125623427058c3995e6a94cc Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jan 2022 21:18:28 -0800 Subject: [PATCH 219/221] LFC: Never disable type checks for TypeScript. --- .../src/org/lflang/generator/Validator.java | 18 ++++++++++++++---- .../src/org/lflang/generator/ts/TSValidator.kt | 3 +++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index c64ebf2c8a..969dac71d2 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -72,19 +72,29 @@ public final void doValidate(LFGeneratorContext context) throws ExecutionExcepti * @param context The context of the current build. */ private boolean validationEnabled(LFGeneratorContext context) { - String lint = context.getArgs().getProperty("lint", context.getMode() == Mode.STANDALONE ? "false" : "true"); + boolean defaultValue = validationEnabledByDefault(context); + String lint = context.getArgs().getProperty("lint", defaultValue ? "true" : "false"); if (lint.equalsIgnoreCase("true")) return true; else if (lint.equalsIgnoreCase("false")) return false; else { errorReporter.reportWarning(String.format( "Unrecognized value for \"lint\" property: \"%s\". Acceptable values are \"true\" and \"false\". " - + "Assuming default value of \"false\".", - lint + + "Assuming default value of \"%s\".", + lint, defaultValue ? "true" : "false" )); - return false; + return defaultValue; } } + /** + * Return whether validation of generated code is enabled by default. + * @param context The context of the current build. + * @return Whether validation of generated code is enabled by default. + */ + protected boolean validationEnabledByDefault(LFGeneratorContext context) { + return context.getMode() != Mode.STANDALONE; + } + /** * Invoke all the given tasks. * @param tasks Any set of tasks. diff --git a/org.lflang/src/org/lflang/generator/ts/TSValidator.kt b/org.lflang/src/org/lflang/generator/ts/TSValidator.kt index 2b80ec39e2..0f361d2286 100644 --- a/org.lflang/src/org/lflang/generator/ts/TSValidator.kt +++ b/org.lflang/src/org/lflang/generator/ts/TSValidator.kt @@ -158,4 +158,7 @@ class TSValidator( fun doLint(context: LFGeneratorContext) { TSLinter(fileConfig, errorReporter, codeMaps).doValidate(context) } + + // If this is not true, then the user might as well be writing JavaScript. + override fun validationEnabledByDefault(context: LFGeneratorContext?): Boolean = true } From d1cc5ed9ab0150b766d1fa8e8ab4cd1d8dba02b7 Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jan 2022 21:28:35 -0800 Subject: [PATCH 220/221] LFC: --lint is just a flag (no argument). --- org.lflang.lfc/src/org/lflang/lfc/Main.java | 2 +- org.lflang/src/org/lflang/generator/Validator.java | 13 +------------ 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/org.lflang.lfc/src/org/lflang/lfc/Main.java b/org.lflang.lfc/src/org/lflang/lfc/Main.java index 36f495abd4..9ef9674c93 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/Main.java +++ b/org.lflang.lfc/src/org/lflang/lfc/Main.java @@ -106,7 +106,7 @@ enum CLIOption { COMPILER(null, "target-compiler", true, false, "Target compiler to invoke.", true), CLEAN("c", "clean", false, false, "Clean before building.", true), HELP("h", "help", false, false, "Display this information.", true), - LINT("l", "lint", true, false, "Enable or disable linting of generated code.", true), + LINT("l", "lint", false, false, "Enable or disable linting of generated code.", true), NO_COMPILE("n", "no-compile", false, false, "Do not invoke target compiler.", true), FEDERATED("f", "federated", false, false, "Treat main reactor as federated.", false), THREADS("t", "threads", true, false, "Specify the default number of threads.", true), diff --git a/org.lflang/src/org/lflang/generator/Validator.java b/org.lflang/src/org/lflang/generator/Validator.java index 969dac71d2..6106b13559 100644 --- a/org.lflang/src/org/lflang/generator/Validator.java +++ b/org.lflang/src/org/lflang/generator/Validator.java @@ -72,18 +72,7 @@ public final void doValidate(LFGeneratorContext context) throws ExecutionExcepti * @param context The context of the current build. */ private boolean validationEnabled(LFGeneratorContext context) { - boolean defaultValue = validationEnabledByDefault(context); - String lint = context.getArgs().getProperty("lint", defaultValue ? "true" : "false"); - if (lint.equalsIgnoreCase("true")) return true; - else if (lint.equalsIgnoreCase("false")) return false; - else { - errorReporter.reportWarning(String.format( - "Unrecognized value for \"lint\" property: \"%s\". Acceptable values are \"true\" and \"false\". " - + "Assuming default value of \"%s\".", - lint, defaultValue ? "true" : "false" - )); - return defaultValue; - } + return context.getArgs().containsKey("lint") || validationEnabledByDefault(context); } /** From 577911973b896a19710e0f81e3875d2126ef460e Mon Sep 17 00:00:00 2001 From: Peter Donovan Date: Mon, 24 Jan 2022 21:53:09 -0800 Subject: [PATCH 221/221] Tests: Add tautological check that option is listed in enum. --- .github/scripts/test-lfc.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/scripts/test-lfc.sh b/.github/scripts/test-lfc.sh index 447629d02b..c5132e1cd0 100755 --- a/.github/scripts/test-lfc.sh +++ b/.github/scripts/test-lfc.sh @@ -47,6 +47,10 @@ bin/lfc --federated --rti rti test/C/src/Minimal.lf # -h,--help Display this information. bin/lfc --help +# -l, --lint Enable linting during build. +bin/lfc -l test/Python/src/Minimal.lf +bin/lfc --lint test/Python/src/Minimal.lf + # -n,--no-compile Do not invoke target compiler. bin/lfc -n test/C/src/Minimal.lf bin/lfc --no-compile test/C/src/Minimal.lf