diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 7e55e3cabc..ef66d24cd1 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -623,7 +623,7 @@ public static ReactorInstance createMainReactorInstance( messageReporter .nowhere() .error("Main reactor has causality cycles. Skipping code generation."); - return null; + return main; // Avoid NPE. } // Inform the run-time of the breadth/parallelism of the reaction graph var breadth = reactionInstanceGraph.getBreadth(); diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index 0713b863be..83eb540775 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -60,6 +60,7 @@ import org.lflang.lf.Mode; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; +import org.lflang.lf.VarRef; import org.lflang.target.Target; import org.lflang.target.TargetConfig; import org.lflang.target.property.FilesProperty; @@ -441,13 +442,7 @@ private void transformConflictingConnectionsInModalReactors(Set resour reaction.getEffects().add(destRef); var code = factory.createCode(); - var source = - (sourceRef.getContainer() != null ? sourceRef.getContainer().getName() + "." : "") - + sourceRef.getVariable().getName(); - var dest = - (destRef.getContainer() != null ? destRef.getContainer().getName() + "." : "") - + destRef.getVariable().getName(); - code.setBody(getConflictingConnectionsInModalReactorsBody(source, dest)); + code.setBody(getConflictingConnectionsInModalReactorsBody(sourceRef, destRef)); reaction.setCode(code); EcoreUtil.remove(connection); @@ -464,7 +459,7 @@ private void transformConflictingConnectionsInModalReactors(Set resour *

This method needs to be overridden in target specific code generators that support modal * reactors. */ - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { + protected String getConflictingConnectionsInModalReactorsBody(VarRef source, VarRef dest) { messageReporter .nowhere() .error( diff --git a/core/src/main/java/org/lflang/generator/c/CGenerator.java b/core/src/main/java/org/lflang/generator/c/CGenerator.java index f6ee943085..dda5c06d89 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -81,7 +81,9 @@ import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; import org.lflang.lf.StateVar; +import org.lflang.lf.VarRef; import org.lflang.lf.Variable; +import org.lflang.lf.WidthSpec; import org.lflang.target.Target; import org.lflang.target.TargetConfig; import org.lflang.target.property.BuildCommandsProperty; @@ -408,7 +410,10 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { if (!isOSCompatible()) return; // Incompatible OS and configuration // Perform set up that does not generate code - setUpGeneralParameters(); + if (!setUpGeneralParameters()) { + // Failure. + return; + } FileUtil.createDirectoryIfDoesNotExist(fileConfig.getSrcGenPath().toFile()); FileUtil.createDirectoryIfDoesNotExist(fileConfig.binPath.toFile()); @@ -659,12 +664,81 @@ public void checkModalReactorSupport(boolean __) { } @Override - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - return String.join( - "\n", - "// Generated forwarding reaction for connections with the same destination", - "// but located in mutually exclusive modes.", - "lf_set(" + dest + ", " + source + "->value);"); + protected String getConflictingConnectionsInModalReactorsBody(VarRef sourceRef, VarRef destRef) { + Instantiation sourceContainer = sourceRef.getContainer(); + Instantiation destContainer = destRef.getContainer(); + Port sourceAsPort = (Port) sourceRef.getVariable(); + Port destAsPort = (Port) destRef.getVariable(); + WidthSpec sourceWidth = sourceAsPort.getWidthSpec(); + WidthSpec destWidth = destAsPort.getWidthSpec(); + + // NOTE: Have to be careful with naming count variables because if the name matches + // that of a port, the program will fail to compile. + + // If the source or dest is a port of a bank, we need to iterate over it. + var isBank = false; + Instantiation bank = null; + var sourceContainerRef = ""; + if (sourceContainer != null) { + sourceContainerRef = sourceContainer.getName() + "."; + bank = sourceContainer; + if (bank.getWidthSpec() != null) { + isBank = true; + sourceContainerRef = sourceContainer.getName() + "[_lf_j]."; + } + } + var sourceIndex = isBank ? "_lf_i" : "_lf_c"; + var source = + sourceContainerRef + + sourceAsPort.getName() + + ((sourceWidth != null) ? "[" + sourceIndex + "]" : ""); + var destContainerRef = ""; + var destIndex = "_lf_c"; + if (destContainer != null) { + destIndex = "_lf_i"; + destContainerRef = destContainer.getName() + "."; + if (bank == null) { + bank = destContainer; + if (bank.getWidthSpec() != null) { + isBank = true; + destContainerRef = destContainer.getName() + "[_lf_j]."; + } + } + } + var dest = + destContainerRef + + destAsPort.getName() + + ((destWidth != null) ? "[" + destIndex + "]" : ""); + var result = new StringBuilder(); + result.append("{ int _lf_c = 0; SUPPRESS_UNUSED_WARNING(_lf_c); "); + // If either side is a bank (only one side should be), iterate over it. + if (isBank) { + var width = new StringBuilder(); + for (var term : bank.getWidthSpec().getTerms()) { + if (!width.isEmpty()) width.append(" + "); + if (term.getCode() != null) width.append(term.getCode().getBody()); + else if (term.getParameter() != null) + width.append("self->" + term.getParameter().getName()); + else width.append(term.getWidth()); + } + result.append("for(int _lf_j = 0; _lf_j < " + width.toString() + "; _lf_j++) { "); + } + // If either side is a multiport, iterate. + // Note that one side could be a multiport of width 1 and the other an ordinary port. + if (sourceWidth != null || destWidth != null) { + var width = + (sourceAsPort.getWidthSpec() != null) + ? sourceContainerRef + sourceAsPort.getName() + : destContainerRef + destAsPort.getName(); + result.append("for(int _lf_i = 0; _lf_i < " + width + "_width; _lf_i++) { "); + } + result.append("lf_set(" + dest + ", " + source + "->value); _lf_c++; "); + if (sourceWidth != null || destAsPort.getWidthSpec() != null) { + result.append(" }"); + } + if (isBank) result.append(" }"); + result.append(" }"); + return result.toString(); } /** Set the scheduler type in the target config as needed. */ @@ -1939,8 +2013,8 @@ protected DockerGenerator getDockerGenerator(LFGeneratorContext context) { // ////////////////////////////////////////// // // Protected methods. - // Perform set up that does not generate code - protected void setUpGeneralParameters() { + // Perform set up that does not generate code. Return false on failure. + protected boolean setUpGeneralParameters() { accommodatePhysicalActionsIfPresent(); CompileDefinitionsProperty.INSTANCE.update( targetConfig, @@ -1950,6 +2024,10 @@ protected void setUpGeneralParameters() { // Create the main reactor instance if there is a main reactor. this.main = ASTUtils.createMainReactorInstance(mainDef, reactors, messageReporter, targetConfig); + if (this.main == null) { + // Something went wrong (causality cycle?). Stop. + return false; + } if (hasModalReactors) { // So that each separate compile knows about modal reactors, do this: CompileDefinitionsProperty.INSTANCE.update(targetConfig, Map.of("MODAL_REACTORS", "TRUE")); @@ -2002,6 +2080,7 @@ protected void setUpGeneralParameters() { } pickCompilePlatform(); } + return true; } protected void handleProtoFiles() { diff --git a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java index 151d2a47fb..a2f2a93f7d 100644 --- a/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CReactionGenerator.java @@ -279,7 +279,10 @@ public static int maxContainedReactorBankWidth( nestedBreadcrumbs.add(mainDef); } int result = max; - Reactor parent = (Reactor) containedReactor.eContainer(); + Reactor parent = + containedReactor.eContainer() instanceof Mode + ? (Reactor) containedReactor.eContainer().eContainer() + : (Reactor) containedReactor.eContainer(); if (parent == ASTUtils.toDefinition(mainDef.getReactorClass())) { // The parent is main, so there can't be any other instantiations of it. return ASTUtils.width(containedReactor.getWidthSpec(), null); diff --git a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java index 854c8f108a..f486c64a47 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonGenerator.java @@ -54,17 +54,19 @@ import org.lflang.generator.docker.PythonDockerGenerator; import org.lflang.lf.Action; import org.lflang.lf.Input; +import org.lflang.lf.Instantiation; import org.lflang.lf.Model; import org.lflang.lf.Output; import org.lflang.lf.Port; import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; +import org.lflang.lf.VarRef; +import org.lflang.lf.WidthSpec; import org.lflang.target.Target; import org.lflang.target.property.DockerProperty; import org.lflang.target.property.ProtobufsProperty; import org.lflang.util.FileUtil; import org.lflang.util.LFCommand; -import org.lflang.util.StringUtil; /** * Generator for Python target. This class generates Python code defining each reactor class given @@ -539,22 +541,94 @@ protected void generateSelfStructExtension( } @Override - protected String getConflictingConnectionsInModalReactorsBody(String source, String dest) { - // NOTE: Strangely, a newline is needed at the beginning or indentation - // gets swallowed. - return String.join( - "\n", - "\n# Generated forwarding reaction for connections with the same destination", - "# but located in mutually exclusive modes.", - dest + ".set(" + source + ".value)\n"); + protected String getConflictingConnectionsInModalReactorsBody(VarRef sourceRef, VarRef destRef) { + Instantiation sourceContainer = sourceRef.getContainer(); + Instantiation destContainer = destRef.getContainer(); + Port sourceAsPort = (Port) sourceRef.getVariable(); + Port destAsPort = (Port) destRef.getVariable(); + WidthSpec sourceWidth = sourceAsPort.getWidthSpec(); + WidthSpec destWidth = destAsPort.getWidthSpec(); + + // NOTE: Have to be careful with naming count variables because if the name matches + // that of a port, the program will fail to compile. + + // If the source or dest is a port of a bank, we need to iterate over it. + var isBank = false; + Instantiation bank = null; + var sourceContainerRef = ""; + if (sourceContainer != null) { + sourceContainerRef = sourceContainer.getName() + "."; + bank = sourceContainer; + if (bank.getWidthSpec() != null) { + isBank = true; + sourceContainerRef = sourceContainer.getName() + "[_lf_j]."; + } + } + var sourceIndex = isBank ? "_lf_i" : "_lf_c"; + var source = + sourceContainerRef + + sourceAsPort.getName() + + ((sourceWidth != null) ? "[" + sourceIndex + "]" : ""); + var destContainerRef = ""; + var destIndex = "_lf_c"; + if (destContainer != null) { + destIndex = "_lf_i"; + destContainerRef = destContainer.getName() + "."; + if (bank == null) { + bank = destContainer; + if (bank.getWidthSpec() != null) { + isBank = true; + destContainerRef = destContainer.getName() + "[_lf_j]."; + } + } + } + var dest = + destContainerRef + + destAsPort.getName() + + ((destWidth != null) ? "[" + destIndex + "]" : ""); + var result = new CodeBuilder(); + // If either side is a bank (only one side should be), iterate over it. + result.pr("_lf_c = 0"); // Counter variable over nested loop if there is a bank and multiport. + if (isBank) { + var width = new StringBuilder(); + for (var term : bank.getWidthSpec().getTerms()) { + if (!width.isEmpty()) width.append(" + "); + if (term.getCode() != null) width.append(term.getCode().getBody()); + else if (term.getParameter() != null) width.append("self." + term.getParameter().getName()); + else width.append(term.getWidth()); + } + result.pr("for _lf_j in range(" + width + "):"); + result.indent(); + } + // If either side is a multiport, iterate. + // Note that one side could be a multiport of width 1 and the other an ordinary port. + if (sourceWidth != null || destWidth != null) { + var width = + (sourceAsPort.getWidthSpec() != null) + ? sourceContainerRef + sourceAsPort.getName() + : destContainerRef + destAsPort.getName(); + result.pr("for _lf_i in range(" + width + ".width):"); + result.indent(); + } + result.pr(dest + ".set(" + source + ".value)"); + result.pr("_lf_c += 1"); // Increment the count. + result.unindent(); + if (isBank) { + result.unindent(); + } + return result.toString(); } @Override - protected void setUpGeneralParameters() { - super.setUpGeneralParameters(); - if (hasModalReactors) { - targetConfig.compileAdditionalSources.add("lib/modal_models/impl.c"); + protected boolean setUpGeneralParameters() { + boolean result = super.setUpGeneralParameters(); + if (result) { + if (hasModalReactors) { + targetConfig.compileAdditionalSources.add("lib/modal_models/impl.c"); + } + return true; } + return false; } @Override @@ -622,18 +696,6 @@ private static String generateCmakeInstall(FileConfig fileConfig) { .replace("", pyMainName); } - /** - * Generate a ({@code key}, {@code val}) tuple pair for the {@code define_macros} field of the - * Extension class constructor from setuptools. - * - * @param key The key of the macro entry - * @param val The value of the macro entry - * @return A ({@code key}, {@code val}) tuple pair as String - */ - private static String generateMacroEntry(String key, String val) { - return "(" + StringUtil.addDoubleQuotes(key) + ", " + StringUtil.addDoubleQuotes(val) + ")"; - } - /** * Generate the name of the python module. * diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index 0031be283f..184567d3bd 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 0031be283fb3bf1cb6561fe0cb2c9604ecbb0ef1 +Subproject commit 184567d3bd06ae5c5f8a75a0b51093115e7fbe52 diff --git a/test/C/src/modal_models/ModalMultiport.lf b/test/C/src/modal_models/ModalMultiport.lf new file mode 100644 index 0000000000..e6714ec024 --- /dev/null +++ b/test/C/src/modal_models/ModalMultiport.lf @@ -0,0 +1,67 @@ +target C { + timeout: 1 ms +} + +reactor Destination(n_inputs: int = 2) { + input[n_inputs] req: int + output[n_inputs] rsp: int + + reaction(req) -> rsp {= + for (int i = 0; i < self->n_inputs; ++i) { + if (req[i]->is_present) { + lf_set (rsp[i], req[i]->value); + } + } + =} +} + +reactor Source(n_ports: int = 2) { + output[n_ports] req: int + input[n_ports] rsp: int + timer t(0, 1 ms) + + reaction(t) -> req {= + for (int i = 0; i < self->n_ports; ++i) { + lf_set (req[i], i); + } + =} + + reaction(rsp) {= + for (int i = 0; i < self->n_ports; ++i) { + lf_print("Received response:%d", rsp[i]->value); + if (rsp[i]->value != i) { + lf_print_error_and_exit("Expected %d", i); + } + } + =} +} + +reactor Selector(n_ports: int = 2) { + input[n_ports] in_req: int + output[n_ports] out_rsp: int + + initial mode DST_1 { + dst1 = new Destination() + + in_req -> dst1.req + dst1.rsp -> out_rsp + reaction(startup) -> reset(DST_2) {= + lf_set_mode(DST_2); + =} + } + + mode DST_2 { + dst2 = new Destination() + + in_req -> dst2.req + dst2.rsp -> out_rsp + } +} + +main reactor { + src = new Source() + sel = new Selector() + + src.req -> sel.in_req + sel.out_rsp -> src.rsp +} diff --git a/test/C/src/modal_models/ModalMultiportBank.lf b/test/C/src/modal_models/ModalMultiportBank.lf new file mode 100644 index 0000000000..9ab4213fd5 --- /dev/null +++ b/test/C/src/modal_models/ModalMultiportBank.lf @@ -0,0 +1,67 @@ +target C { + timeout: 1 ms +} + +reactor Destination(n_inputs: int = 2) { + input[n_inputs] req: int + output[n_inputs] rsp: int + + reaction(req) -> rsp {= + for (int i = 0; i < self->n_inputs; ++i) { + if (req[i]->is_present) { + lf_set (rsp[i], req[i]->value); + } + } + =} +} + +reactor Source(n_ports: int = 4) { + output[n_ports] req: int + input[n_ports] rsp: int + timer t(0, 1 ms) + + reaction(t) -> req {= + for (int i = 0; i < self->n_ports; ++i) { + lf_set (req[i], i); + } + =} + + reaction(rsp) {= + for (int i = 0; i < self->n_ports; ++i) { + lf_print("Received response:%d", rsp[i]->value); + if (rsp[i]->value != i) { + lf_print_error_and_exit("Expected %d", i); + } + } + =} +} + +reactor Selector(n_ports: int = 4) { + input[n_ports] in_req: int + output[n_ports] out_rsp: int + + initial mode DST_1 { + dst1 = new[2] Destination() + + in_req -> dst1.req + dst1.rsp -> out_rsp + reaction(startup) -> reset(DST_2) {= + lf_set_mode(DST_2); + =} + } + + mode DST_2 { + dst2 = new[2] Destination() + + in_req -> dst2.req + dst2.rsp -> out_rsp + } +} + +main reactor { + src = new Source() + sel = new Selector() + + src.req -> sel.in_req + sel.out_rsp -> src.rsp +} diff --git a/test/Python/src/concurrent/ConcurrentAction.lf b/test/Python/src/concurrent/failing/ConcurrentAction.lf similarity index 100% rename from test/Python/src/concurrent/ConcurrentAction.lf rename to test/Python/src/concurrent/failing/ConcurrentAction.lf diff --git a/test/Python/src/modal_models/ModalMultiport.lf b/test/Python/src/modal_models/ModalMultiport.lf new file mode 100644 index 0000000000..38790a7e80 --- /dev/null +++ b/test/Python/src/modal_models/ModalMultiport.lf @@ -0,0 +1,63 @@ +target Python { + timeout: 1 ms +} + +reactor Destination(n_inputs=2) { + input[n_inputs] req + output[n_inputs] rsp + + reaction(req) -> rsp {= + for n in range(self.n_inputs): + if req[n].is_present: + rsp[n].set(req[n].value) + =} +} + +reactor Source(n_ports=2) { + output[n_ports] req + input[n_ports] rsp + timer t(0, 1 ms) + + reaction(t) -> req {= + for n in range(self.n_ports): + req[n].set(n) + =} + + reaction(rsp) {= + for n in range(self.n_ports): + print("Received response: ", rsp[n].value); + if rsp[n].value != n: + sys.stderr.write("ERROR: Expected {:d}\n".format(n)) + exit(1) + =} +} + +reactor Selector(n_ports=2) { + input[n_ports] in_req + output[n_ports] out_rsp + + initial mode DST_1 { + dst1 = new Destination() + + in_req -> dst1.req + dst1.rsp -> out_rsp + reaction(startup) -> reset(DST_2) {= + DST_2.set() + =} + } + + mode DST_2 { + dst2 = new Destination() + + in_req -> dst2.req + dst2.rsp -> out_rsp + } +} + +main reactor { + src = new Source() + sel = new Selector() + + src.req -> sel.in_req + sel.out_rsp -> src.rsp +} diff --git a/test/Python/src/modal_models/ModalMultiportBank.lf b/test/Python/src/modal_models/ModalMultiportBank.lf new file mode 100644 index 0000000000..5521a8d2e3 --- /dev/null +++ b/test/Python/src/modal_models/ModalMultiportBank.lf @@ -0,0 +1,63 @@ +target Python { + timeout: 1 ms +} + +reactor Destination(n_inputs=2) { + input[n_inputs] req + output[n_inputs] rsp + + reaction(req) -> rsp {= + for n in range(self.n_inputs): + if req[n].is_present: + rsp[n].set(req[n].value) + =} +} + +reactor Source(n_ports=4) { + output[n_ports] req + input[n_ports] rsp + timer t(0, 1 ms) + + reaction(t) -> req {= + for n in range(self.n_ports): + req[n].set(n) + =} + + reaction(rsp) {= + for n in range(self.n_ports): + print("Received {:d} response: {:d}".format(n, rsp[n].value)) + if rsp[n].value != n: + sys.stderr.write("ERROR: Expected {:d}\n".format(n)) + exit(1) + =} +} + +reactor Selector(n_ports=4, n_dest=2) { + input[n_ports] in_req + output[n_ports] out_rsp + + initial mode DST_1 { + dst1 = new[n_dest] Destination() + + in_req -> dst1.req + dst1.rsp -> out_rsp + reaction(startup) -> reset(DST_2) {= + DST_2.set() + =} + } + + mode DST_2 { + dst2 = new[n_dest] Destination() + + in_req -> dst2.req + dst2.rsp -> out_rsp + } +} + +main reactor { + src = new Source() + sel = new Selector() + + src.req -> sel.in_req + sel.out_rsp -> src.rsp +}