diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index 5f472b3eb9..aaa5c455a3 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit 5f472b3eb958e986a63ad88b9ca0d32e0cc50d18 +Subproject commit aaa5c455a3943da29f398bb843b406613c5060d9 diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index 5f99791a04..76ed5bd633 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -101,7 +101,20 @@ public void rebuild() { public Integer[] getNumReactionsPerLevel() { return numReactionsPerLevel.toArray(new Integer[0]); } - + + /** + * Return the max breadth of the reaction dependency graph + */ + public int getBreadth() { + var maxBreadth = 0; + for (Integer breadth: numReactionsPerLevel ) { + if (breadth > maxBreadth) { + maxBreadth = breadth; + } + } + return maxBreadth; + } + /////////////////////////////////////////////////////////// //// Protected methods @@ -232,7 +245,7 @@ protected void addNodesAndEdges(ReactorInstance reactor) { */ private List numReactionsPerLevel = new ArrayList<>( List.of(Integer.valueOf(0))); - + /////////////////////////////////////////////////////////// //// Private methods @@ -251,7 +264,7 @@ private void assignLevels() { // All root nodes start with level 0. for (Runtime origin : start) { - assignLevel(origin, 0); + origin.level = 0; } // No need to do any of this if there are no root nodes; @@ -260,17 +273,14 @@ private void assignLevels() { Runtime origin = start.remove(0); Set toRemove = new LinkedHashSet(); Set downstreamAdjacentNodes = getDownstreamAdjacentNodes(origin); - // All downstream adjacent nodes start with a level 0. Adjust the - // numReactionsPerLevel field accordingly (to be - // updated in the for loop below). - adjustNumReactionsPerLevel(0, downstreamAdjacentNodes.size()); + // Visit effect nodes. for (Runtime effect : downstreamAdjacentNodes) { // Stage edge between origin and effect for removal. toRemove.add(effect); // Update level of downstream node. - updateLevel(effect, origin.level+1); + effect.level = origin.level + 1; } // Remove visited edges. for (Runtime effect : toRemove) { @@ -284,32 +294,12 @@ private void assignLevels() { // Remove visited origin. removeNode(origin); + + // Update numReactionsPerLevel info + adjustNumReactionsPerLevel(origin.level, 1); } } - /** - * Assign a level to a reaction runtime instance. - * - * @param runtime The reaction runtime instance. - * @param level The level to assign. - */ - private void assignLevel(Runtime runtime, Integer level) { - runtime.level = level; - adjustNumReactionsPerLevel(level, 1); - } - - /** - * Update the level of the reaction runtime instance - * to level if level is larger than the - * level already assigned to runtime. - */ - private void updateLevel(Runtime runtime, Integer level) { - if (runtime.level < level) { - adjustNumReactionsPerLevel(runtime.level, -1); - runtime.level = level; - adjustNumReactionsPerLevel(level, 1); - } - } /** * Adjust {@link #numReactionsPerLevel} at index level by diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.java b/org.lflang/src/org/lflang/generator/c/CGenerator.java index ef00dd1086..cd8278ff82 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.java @@ -529,7 +529,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { } } generateCodeForCurrentFederate(lfModuleName); - + // Derive target filename from the .lf filename. var cFilename = CCompiler.getTargetFileName(lfModuleName, this.CCppMode); var targetFile = fileConfig.getSrcGenPath() + File.separator + cFilename; @@ -560,18 +560,18 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Create docker file. if (targetConfig.dockerOptions != null && mainDef != null) { dockerGenerator.addFederate( - lfModuleName, federate.name, - fileConfig.getSrcGenPath().resolve(lfModuleName + ".Dockerfile"), + lfModuleName, federate.name, + fileConfig.getSrcGenPath().resolve(lfModuleName + ".Dockerfile"), targetConfig); } - + if (targetConfig.useCmake) { // If cmake is requested, generated the CMakeLists.txt var cmakeGenerator = new CCmakeGenerator(targetConfig, fileConfig); var cmakeFile = fileConfig.getSrcGenPath() + File.separator + "CMakeLists.txt"; var cmakeCode = cmakeGenerator.generateCMakeCode( - List.of(cFilename), - lfModuleName, + List.of(cFilename), + lfModuleName, errorReporter, CCppMode, mainDef != null, @@ -595,11 +595,11 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { ) { // FIXME: Currently, a lack of main is treated as a request to not produce // a binary and produce a .o file instead. There should be a way to control - // this. + // this. // Create an anonymous Runnable class and add it to the compileThreadPool // so that compilation can happen in parallel. var cleanCode = code.removeLines("#line"); - + var execName = lfModuleName; var threadFileConfig = fileConfig; var generator = this; // FIXME: currently only passed to report errors with line numbers in the Eclipse IDE @@ -639,11 +639,11 @@ public void run() { } fileConfig = oldFileConfig; } - - // Initiate an orderly shutdown in which previously submitted tasks are + + // Initiate an orderly shutdown in which previously submitted tasks are // executed, but no new tasks will be accepted. compileThreadPool.shutdown(); - + // Wait for all compile threads to finish (NOTE: Can block forever) try { compileThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); @@ -656,7 +656,7 @@ public void run() { createFederatedLauncher(); } catch (IOException e) { Exceptions.sneakyThrow(e); - } + } } if (targetConfig.dockerOptions != null && mainDef != null) { @@ -695,7 +695,7 @@ public void run() { } else { context.finish(GeneratorResult.GENERATED_NO_EXECUTABLE.apply(null)); } - + // In case we are in Eclipse, make sure the generated code is visible. GeneratorUtils.refreshProject(resource, context.getMode()); } @@ -713,9 +713,9 @@ private void generateCodeForCurrentFederate( // Generate main instance, if there is one. // Note that any main reactors in imported files are ignored. - // Skip generation if there are cycles. + // Skip generation if there are cycles. if (main != null) { - initializeTriggerObjects.pr(String.join("\n", + initializeTriggerObjects.pr(String.join("\n", "int _lf_startup_reactions_count = 0;", "int _lf_shutdown_reactions_count = 0;", "int _lf_timer_triggers_count = 0;", @@ -729,20 +729,20 @@ private void generateCodeForCurrentFederate( // reactors, which are arbitarily far away in the program graph. generateSelfStructs(main); generateReactorInstance(main); - + // If there are timers, create a table of timers to be initialized. code.pr(CTimerGenerator.generateDeclarations(timerCount)); - + // If there are shutdown reactions, create a table of triggers. code.pr(CReactionGenerator.generateShutdownTriggersTable(shutdownReactionCount)); - + // If there are modes, create a table of mode state to be checked for transitions. code.pr(CModesGenerator.generateModeStatesTable( hasModalReactors, modalReactorCount, modalStateResetCount )); - + // Generate function to return a pointer to the action trigger_t // that handles incoming network messages destined to the specified // port. This will only be used if there are federates. @@ -762,7 +762,7 @@ private void generateCodeForCurrentFederate( for (String trigger : triggers) { initializeTriggerObjects.pr("_lf_action_table["+(actionTableCount++)+"] = &"+trigger+";"); } - code.pr(String.join("\n", + code.pr(String.join("\n", "trigger_t* _lf_action_table["+currentFederate.networkMessageActions.size()+"];", "trigger_t* _lf_action_for_port(int port_id) {", " if (port_id < "+currentFederate.networkMessageActions.size()+") {", @@ -773,13 +773,13 @@ private void generateCodeForCurrentFederate( "}" )); } else { - code.pr(String.join("\n", + code.pr(String.join("\n", "trigger_t* _lf_action_for_port(int port_id) {", " return NULL;", "}" )); } - + // Generate function to initialize the trigger objects for all reactors. code.pr(CTriggerObjectsGenerator.generateInitializeTriggerObjects( currentFederate, @@ -796,7 +796,7 @@ private void generateCodeForCurrentFederate( isFederated, isFederatedAndDecentralized(), clockSyncIsOn() - )); + )); // Generate function to trigger startup reactions for all reactors. code.pr(CReactionGenerator.generateLfTriggerStartupReactions(startupReactionCount)); @@ -805,28 +805,28 @@ private void generateCodeForCurrentFederate( code.pr(CTimerGenerator.generateLfInitializeTimer(timerCount)); // Generate a function that will either do nothing - // (if there is only one federate or the coordination + // (if there is only one federate or the coordination // is set to decentralized) or, if there are // downstream federates, will notify the RTI // that the specified logical time is complete. - code.pr(String.join("\n", + code.pr(String.join("\n", "void logical_tag_complete(tag_t tag_to_send) {", - (isFederatedAndCentralized() ? + (isFederatedAndCentralized() ? " _lf_logical_tag_complete(tag_to_send);" : "" ), "}" )); - + if (isFederated) { code.pr(CFederateGenerator.generateFederateNeighborStructure(currentFederate).toString()); } - + // Generate function to schedule shutdown reactions if any // reactors have reactions to shutdown. code.pr(CReactionGenerator.generateLfTriggerShutdownReactions( shutdownReactionCount )); - + // Generate an empty termination function for non-federated // execution. For federated execution, an implementation is // provided in federate.c. That implementation will resign @@ -834,7 +834,7 @@ private void generateCodeForCurrentFederate( if (!isFederated) { code.pr("void terminate_execution() {}"); } - + code.pr(CModesGenerator.generateLfHandleModeChanges( hasModalReactors, modalStateResetCount @@ -2253,20 +2253,20 @@ protected void setUpFederateSpecificParameters(FederateInstance federate, CodeBu targetConfig.cmakeIncludesWithoutPath.clear(); targetConfig.fileNames.clear(); targetConfig.filesNamesWithoutPath.clear(); - + // Re-apply the cmake-include target property of the main .lf file. var target = GeneratorUtils.findTarget(mainDef.getReactorClass().eResource()); if (target.getConfig() != null) { // Update the cmake-include TargetProperty.updateOne( - this.targetConfig, + this.targetConfig, TargetProperty.CMAKE_INCLUDE, convertToEmptyListIfNull(target.getConfig().getPairs()), errorReporter ); // Update the files TargetProperty.updateOne( - this.targetConfig, + this.targetConfig, TargetProperty.FILES, convertToEmptyListIfNull(target.getConfig().getPairs()), errorReporter @@ -2617,20 +2617,32 @@ private void createMainReactorInstance() { // it is the same for all federates. this.main = new ReactorInstance(toDefinition(mainDef.getReactorClass()), errorReporter, this.unorderedReactions); - if (this.main.assignLevels().nodeCount() > 0) { + var reactionInstanceGraph = this.main.assignLevels(); + if (reactionInstanceGraph.nodeCount() > 0) { errorReporter.reportError("Main reactor has causality cycles. Skipping code generation."); return; } - // Force reconstruction of dependence information. - if (isFederated) { - // Avoid compile errors by removing disconnected network ports. - // This must be done after assigning levels. - removeRemoteFederateConnectionPorts(main); - // There will be AST transformations that invalidate some info - // cached in ReactorInstance. - this.main.clearCaches(false); + // Inform the run-time of the breadth/parallelism of the reaction graph + var breadth = reactionInstanceGraph.getBreadth(); + if (breadth == 0) { + errorReporter.reportWarning("Reaction graph breadth is computed to be 0. Indicates an error"); + } else { + targetConfig.compileDefinitions.put( + "LF_REACTION_GRAPH_BREADTH", + String.valueOf(reactionInstanceGraph.getBreadth()) + ); } - } + } + + // Force reconstruction of dependence information. + if (isFederated) { + // Avoid compile errors by removing disconnected network ports. + // This must be done after assigning levels. + removeRemoteFederateConnectionPorts(main); + // There will be AST transformations that invalidate some info + // cached in ReactorInstance. + this.main.clearCaches(false); + } } }