diff --git a/.github/workflows/c-tests.yml b/.github/workflows/c-tests.yml index 071341fdb6..70a3a83990 100644 --- a/.github/workflows/c-tests.yml +++ b/.github/workflows/c-tests.yml @@ -13,6 +13,9 @@ on: required: false type: boolean default: false + scheduler: + required: false + type: string jobs: run: @@ -53,14 +56,19 @@ jobs: - name: Build RTI docker image uses: ./.github/actions/build-rti-docker if: ${{ runner.os == 'Linux' }} - - name: Perform tests for C target + - name: Perform tests for C target with default scheduler run: | ./gradlew test --tests org.lflang.tests.runtime.CTest.* --tests org.lflang.tests.lsp.LspTests.lspWithDependenciesTestC - if: ${{ !inputs.use-cpp }} - - name: Perform tests for CCpp target + if: ${{ !inputs.use-cpp && !inputs.scheduler }} + - name: Perform tests for C target with specified scheduler (no LSP tests) + run: | + echo "Specified scheduler: ${{ inputs.scheduler }}" + ./gradlew test --tests org.lflang.tests.runtime.CSchedulerTest.* -Dscheduler=${{ inputs.scheduler }} + if: ${{ !inputs.use-cpp && inputs.scheduler }} + - name: Perform tests for CCpp target with default scheduler run: | ./gradlew test --tests org.lflang.tests.runtime.CCppTest.* - if: ${{ inputs.use-cpp }} + if: ${{ inputs.use-cpp && !inputs.scheduler }} - name: Report to CodeCov uses: codecov/codecov-action@v2.1.0 with: diff --git a/benchmark/C/Savina/src/concurrency/BoundedBuffer.lf b/benchmark/C/Savina/src/concurrency/BoundedBuffer.lf index b7272053a3..539822b3a6 100644 --- a/benchmark/C/Savina/src/concurrency/BoundedBuffer.lf +++ b/benchmark/C/Savina/src/concurrency/BoundedBuffer.lf @@ -24,6 +24,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/concurrency/CigaretteSmoker.lf b/benchmark/C/Savina/src/concurrency/CigaretteSmoker.lf index 3fa2223cff..17f034c65d 100644 --- a/benchmark/C/Savina/src/concurrency/CigaretteSmoker.lf +++ b/benchmark/C/Savina/src/concurrency/CigaretteSmoker.lf @@ -63,6 +63,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/concurrency/Dictionary.lf b/benchmark/C/Savina/src/concurrency/Dictionary.lf index 1e02b34e0c..c795b4164b 100644 --- a/benchmark/C/Savina/src/concurrency/Dictionary.lf +++ b/benchmark/C/Savina/src/concurrency/Dictionary.lf @@ -28,6 +28,7 @@ target CCpp { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/concurrency/LogisticMap.lf b/benchmark/C/Savina/src/concurrency/LogisticMap.lf index 2779d05d42..7577a60f58 100644 --- a/benchmark/C/Savina/src/concurrency/LogisticMap.lf +++ b/benchmark/C/Savina/src/concurrency/LogisticMap.lf @@ -12,6 +12,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/concurrency/Philosophers.lf b/benchmark/C/Savina/src/concurrency/Philosophers.lf index 2dd717669a..ac68f95ad4 100644 --- a/benchmark/C/Savina/src/concurrency/Philosophers.lf +++ b/benchmark/C/Savina/src/concurrency/Philosophers.lf @@ -11,6 +11,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ @@ -183,7 +184,7 @@ reactor Arbitrator(num_philosophers:int(20)) { if (finished[i]->is_present) { self->finished_philosophers++; if (self->num_philosophers == self->finished_philosophers) { - printf("Arbitrator: All philosophers are seated. Number of denials to philosophers: %d\n", self->retries); + printf("Arbitrator: All philosophers are sated. Number of denials to philosophers: %d\n", self->retries); SET(allFinished, true); } } diff --git a/benchmark/C/Savina/src/concurrency/SleepingBarber.lf b/benchmark/C/Savina/src/concurrency/SleepingBarber.lf index 0a0f0f65f0..412536ccb6 100644 --- a/benchmark/C/Savina/src/concurrency/SleepingBarber.lf +++ b/benchmark/C/Savina/src/concurrency/SleepingBarber.lf @@ -46,6 +46,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/concurrency/SortedList.lf b/benchmark/C/Savina/src/concurrency/SortedList.lf index 349d96bafb..9519de8c05 100644 --- a/benchmark/C/Savina/src/concurrency/SortedList.lf +++ b/benchmark/C/Savina/src/concurrency/SortedList.lf @@ -18,6 +18,7 @@ target CCpp { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/micro/Big.lf b/benchmark/C/Savina/src/micro/Big.lf index ce7b36cf5f..9e0b543239 100644 --- a/benchmark/C/Savina/src/micro/Big.lf +++ b/benchmark/C/Savina/src/micro/Big.lf @@ -29,6 +29,7 @@ target C{ /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/micro/Chameneos.lf b/benchmark/C/Savina/src/micro/Chameneos.lf index a2482e38d3..279f29080e 100644 --- a/benchmark/C/Savina/src/micro/Chameneos.lf +++ b/benchmark/C/Savina/src/micro/Chameneos.lf @@ -41,6 +41,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/micro/Counting.lf b/benchmark/C/Savina/src/micro/Counting.lf index e728b4b1ac..ebe4764ce0 100644 --- a/benchmark/C/Savina/src/micro/Counting.lf +++ b/benchmark/C/Savina/src/micro/Counting.lf @@ -42,6 +42,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/micro/PingPong.lf b/benchmark/C/Savina/src/micro/PingPong.lf index bf88f00572..917c1a38e8 100644 --- a/benchmark/C/Savina/src/micro/PingPong.lf +++ b/benchmark/C/Savina/src/micro/PingPong.lf @@ -26,6 +26,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/micro/ThreadRing.lf b/benchmark/C/Savina/src/micro/ThreadRing.lf index 7734dda9f0..2f0019ba33 100644 --- a/benchmark/C/Savina/src/micro/ThreadRing.lf +++ b/benchmark/C/Savina/src/micro/ThreadRing.lf @@ -55,6 +55,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/micro/Throughput.lf b/benchmark/C/Savina/src/micro/Throughput.lf index ca1ba8a072..38b417a1cf 100644 --- a/benchmark/C/Savina/src/micro/Throughput.lf +++ b/benchmark/C/Savina/src/micro/Throughput.lf @@ -56,12 +56,13 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ threads: 2, /// [[[end]]] - flags: "-lm", + flags: "-lm" }; import BenchmarkRunner from "../BenchmarkRunner.lf"; diff --git a/benchmark/C/Savina/src/parallelism/Apsp.lf b/benchmark/C/Savina/src/parallelism/Apsp.lf index be5157e68b..4b9f03394f 100644 --- a/benchmark/C/Savina/src/parallelism/Apsp.lf +++ b/benchmark/C/Savina/src/parallelism/Apsp.lf @@ -21,6 +21,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/FilterBank.lf b/benchmark/C/Savina/src/parallelism/FilterBank.lf index ef6d4c1950..5b1e09f0dd 100644 --- a/benchmark/C/Savina/src/parallelism/FilterBank.lf +++ b/benchmark/C/Savina/src/parallelism/FilterBank.lf @@ -33,6 +33,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/GuidedSearch.lf b/benchmark/C/Savina/src/parallelism/GuidedSearch.lf index b5e85f7503..a8a2425325 100644 --- a/benchmark/C/Savina/src/parallelism/GuidedSearch.lf +++ b/benchmark/C/Savina/src/parallelism/GuidedSearch.lf @@ -62,6 +62,7 @@ target CCpp { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/MatMul.lf b/benchmark/C/Savina/src/parallelism/MatMul.lf index 5bd2b778d7..fb6190f8f4 100644 --- a/benchmark/C/Savina/src/parallelism/MatMul.lf +++ b/benchmark/C/Savina/src/parallelism/MatMul.lf @@ -10,6 +10,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/NQueens.lf b/benchmark/C/Savina/src/parallelism/NQueens.lf index adb88da862..7da826d0c4 100644 --- a/benchmark/C/Savina/src/parallelism/NQueens.lf +++ b/benchmark/C/Savina/src/parallelism/NQueens.lf @@ -41,6 +41,7 @@ target C{ /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/PiPrecision.lf b/benchmark/C/Savina/src/parallelism/PiPrecision.lf index 2f397851d6..c0f0d79bb5 100644 --- a/benchmark/C/Savina/src/parallelism/PiPrecision.lf +++ b/benchmark/C/Savina/src/parallelism/PiPrecision.lf @@ -17,6 +17,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/RadixSort.lf b/benchmark/C/Savina/src/parallelism/RadixSort.lf index ca1bcef6e6..b5141a0d16 100644 --- a/benchmark/C/Savina/src/parallelism/RadixSort.lf +++ b/benchmark/C/Savina/src/parallelism/RadixSort.lf @@ -15,6 +15,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/C/Savina/src/parallelism/Trapezoidal.lf b/benchmark/C/Savina/src/parallelism/Trapezoidal.lf index b0b99f8810..5c0455d697 100644 --- a/benchmark/C/Savina/src/parallelism/Trapezoidal.lf +++ b/benchmark/C/Savina/src/parallelism/Trapezoidal.lf @@ -17,6 +17,7 @@ target C { /* [[[cog if (threaded_runtime=="True"): cog.outl(f"threads: {threads},") + cog.outl(f"scheduler: {scheduler},") else: cog.outl("threads: 0,") ]]] */ diff --git a/benchmark/runner/conf/default.yaml b/benchmark/runner/conf/default.yaml index e05cc70293..b109df8058 100644 --- a/benchmark/runner/conf/default.yaml +++ b/benchmark/runner/conf/default.yaml @@ -1,5 +1,6 @@ iterations: 12 threads: null +timeout: 1200 savina_path: "${oc.env:SAVINA_PATH}" lf_path: "${oc.env:LF_PATH}" continue_on_error: False diff --git a/benchmark/runner/conf/target/lf-c.yaml b/benchmark/runner/conf/target/lf-c.yaml index 9579c6f81a..5422ede6cd 100644 --- a/benchmark/runner/conf/target/lf-c.yaml +++ b/benchmark/runner/conf/target/lf-c.yaml @@ -3,6 +3,7 @@ prepare: ["mkdir", "src"] copy: ["cp", "-r", "${benchmark.targets.lf-c.copy_sources}", "src"] gen: ["cog", "-r", "${args:benchmark.targets.lf-c.gen_args}", "-D", "threads=${threads}", + "-D", "scheduler=${target.params.scheduler}", "-D", "numIterations=${iterations}", "-D", "threaded_runtime=True", "src/${benchmark.targets.lf-c.lf_file}"] @@ -10,3 +11,5 @@ compile: ["${lf_path}/bin/lfc", "src/${benchmark.targets.lf-c.lf_file}"] run: ["bin/${benchmark.targets.lf-c.binary}"] parser: _target_: "parser.parse_lfc_output" +params: + scheduler: "GEDF_NP" diff --git a/org.lflang.lfc/src/org/lflang/lfc/Main.java b/org.lflang.lfc/src/org/lflang/lfc/Main.java index 8a2b4bce24..829a854204 100644 --- a/org.lflang.lfc/src/org/lflang/lfc/Main.java +++ b/org.lflang.lfc/src/org/lflang/lfc/Main.java @@ -106,8 +106,8 @@ public class Main { * @author Marten Lohstroh */ enum CLIOption { - COMPILER(null, "target-compiler", true, false, "Target compiler to invoke.", true), CLEAN("c", "clean", false, false, "Clean before building.", true), + COMPILER(null, "target-compiler", true, false, "Target compiler to invoke.", true), EXTERNAL_RUNTIME_PATH(null, "external-runtime-path", true, false, "Specify an external runtime library to be used by the compiled binary.", true), FEDERATED("f", "federated", false, false, "Treat main reactor as federated.", false), HELP("h", "help", false, false, "Display this information.", true), @@ -117,6 +117,7 @@ enum CLIOption { QUIET("q", "quiet", false, false, "Suppress output of the target compiler and other commands", true), RTI("r", "rti", true, false, "Specify the location of the RTI.", true), RUNTIME_VERSION(null, "runtime-version", true, false, "Specify the version of the runtime library used for compiling LF programs.", true), + SCHEDULER("s", "scheduler", true, false, "Specify the runtime scheduler (if supported).", true), THREADS("t", "threads", true, false, "Specify the default number of threads.", true), VERSION(null, "version", false, false, "Print version inforomation.", false); diff --git a/org.lflang.tests/build.gradle b/org.lflang.tests/build.gradle index 4261fbc338..df44eaa000 100644 --- a/org.lflang.tests/build.gradle +++ b/org.lflang.tests/build.gradle @@ -46,6 +46,8 @@ test { events "passed", "skipped", "failed" showStandardStreams = true } + // Pass the scheduler property on to the Java VM + systemProperty 'scheduler', System.getProperty('scheduler') // Suggested by Gradle documentation: https://guides.gradle.org/performance/#parallel_test_execution maxParallelForks = Runtime.runtime.availableProcessors().intdiv(2) ?: 1 useJUnitPlatform() diff --git a/org.lflang.tests/src/org/lflang/tests/AbstractTest.java b/org.lflang.tests/src/org/lflang/tests/AbstractTest.java index 4e707e1fe2..5299475590 100644 --- a/org.lflang.tests/src/org/lflang/tests/AbstractTest.java +++ b/org.lflang.tests/src/org/lflang/tests/AbstractTest.java @@ -63,7 +63,6 @@ protected boolean supportsDockerOption() { return false; } - @Test public void runExampleTests() { runTestsForTargets(Message.DESC_EXAMPLE_TESTS, diff --git a/org.lflang.tests/src/org/lflang/tests/Configurators.java b/org.lflang.tests/src/org/lflang/tests/Configurators.java index 872a4341b7..1e64dc5f5e 100644 --- a/org.lflang.tests/src/org/lflang/tests/Configurators.java +++ b/org.lflang.tests/src/org/lflang/tests/Configurators.java @@ -51,7 +51,7 @@ public interface Configurator { * @param test The test to configure. * @return True if successful, false otherwise. */ - static boolean useSingleThread(LFTest test) { + public static boolean useSingleThread(LFTest test) { test.getContext().getArgs().setProperty("threads", "0"); return true; } @@ -62,7 +62,7 @@ static boolean useSingleThread(LFTest test) { * @param test The test to configure * @return True if successful, false otherwise. */ - static boolean useFourThreads(LFTest test) { + public static boolean useFourThreads(LFTest test) { test.getContext().getArgs().setProperty("threads", "4"); return true; } @@ -73,7 +73,7 @@ static boolean useFourThreads(LFTest test) { * @param test The test to configure. * @return True */ - static boolean noChanges(LFTest test) { + public static boolean noChanges(LFTest test) { return true; } diff --git a/org.lflang.tests/src/org/lflang/tests/TestBase.java b/org.lflang.tests/src/org/lflang/tests/TestBase.java index 5a2263e72d..46374aaaf4 100644 --- a/org.lflang.tests/src/org/lflang/tests/TestBase.java +++ b/org.lflang.tests/src/org/lflang/tests/TestBase.java @@ -139,6 +139,7 @@ public static class Message { public static final String DESC_TARGET_SPECIFIC = "Run target-specific tests (threads = 0)"; public static final String DESC_AS_CCPP = "Running C tests as CCpp."; public static final String DESC_FOUR_THREADS = "Run non-concurrent and non-federated tests (threads = 4)."; + public static final String DESC_SCHED_SWAPPING = "Running with non-default runtime scheduler "; /* Missing dependency messages */ public static final String MISSING_DOCKER = "Executable 'docker' not found or 'docker' daemon thread not running"; diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java new file mode 100644 index 0000000000..c4fa0a2b2b --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CSchedulerTest.java @@ -0,0 +1,75 @@ +package org.lflang.tests.runtime; + +import java.util.EnumSet; + +import org.junit.jupiter.api.Test; + +import org.lflang.Target; +import org.lflang.TargetProperty.SchedulerOption; +import org.lflang.tests.Configurators; +import org.lflang.tests.TestBase; +import org.lflang.tests.TestRegistry.TestCategory; + +/** + */ +public class CSchedulerTest extends TestBase { + + + public CSchedulerTest() { + super(Target.C); + } + + /** + * Swap the default runtime scheduler with other supported versions and + * run all the supported tests. Only run tests for a specific non-default + * scheduler if specified using a system property (e.g., -Dscheduler=GEDF_NP). + */ + @Test + public void runWithNonDefaultSchedulers() { + EnumSet categories = EnumSet.of(TestCategory.CONCURRENT, + TestCategory.MULTIPORT); + + // Add federated and docker tests if supported + if (!isWindows()) { + categories.add(TestCategory.FEDERATED); + if (isLinux()) { + categories.add(TestCategory.DOCKER_FEDERATED); + } + } + var name = System.getProperty("scheduler"); + + if (name != null) { + var option = EnumSet.allOf(SchedulerOption.class).stream() + .filter(it -> it.name().equals(name)).findFirst(); + if (option.isPresent()) { + this.runTest(option.get(), categories); + } else { + throw new RuntimeException("Cannot find runtime scheduler called " + name); + } + } else { + for (SchedulerOption scheduler: EnumSet.allOf(SchedulerOption.class)) { + if (scheduler == SchedulerOption.getDefault()) continue; + this.runTest(scheduler, categories); + } + } + } + + private void runTest(SchedulerOption scheduler, EnumSet categories) { + this.runTestsForTargets( + Message.DESC_SCHED_SWAPPING + scheduler.toString() +".", + categories::contains, + test -> { + test.getContext() + .getArgs() + .setProperty( + "scheduler", + scheduler.toString() + ); + return Configurators.useFourThreads(test); + }, + TestLevel.EXECUTION, + true + ); + } +} + diff --git a/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java b/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java index c8909202f2..4873ff1266 100644 --- a/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java +++ b/org.lflang.tests/src/org/lflang/tests/runtime/CTest.java @@ -1,5 +1,3 @@ -/* Scoping unit tests. */ - /************* Copyright (c) 2019, The University of California at Berkeley. @@ -100,7 +98,7 @@ public void runMultiportTests() { public void runWithFourThreads() { super.runWithFourThreads(); } - + @Test @Override public void runSerializationTests() { diff --git a/org.lflang/src/lib/c/reactor-c b/org.lflang/src/lib/c/reactor-c index d09316fbf5..e7306edb43 160000 --- a/org.lflang/src/lib/c/reactor-c +++ b/org.lflang/src/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit d09316fbf50ae6c7194e710ebbf9a56a78fea171 +Subproject commit e7306edb430560aca043dc47345e24bf5f1f87d3 diff --git a/org.lflang/src/lib/py/reactor-c-py b/org.lflang/src/lib/py/reactor-c-py index 4e19241ef2..9e78ad3596 160000 --- a/org.lflang/src/lib/py/reactor-c-py +++ b/org.lflang/src/lib/py/reactor-c-py @@ -1 +1 @@ -Subproject commit 4e19241ef26662a006c72e4dcc7543c0d3840179 +Subproject commit 9e78ad35961e559d88abfaf950f76a5f2d4323e7 diff --git a/org.lflang/src/org/lflang/TargetConfig.java b/org.lflang/src/org/lflang/TargetConfig.java index ae0631c0c5..ab277e2ae2 100644 --- a/org.lflang/src/org/lflang/TargetConfig.java +++ b/org.lflang/src/org/lflang/TargetConfig.java @@ -25,14 +25,17 @@ package org.lflang; import java.util.ArrayList; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import org.lflang.TargetProperty.BuildType; import org.lflang.TargetProperty.ClockSyncMode; import org.lflang.TargetProperty.CoordinationType; import org.lflang.TargetProperty.LogLevel; +import org.lflang.TargetProperty.SchedulerOption; import org.lflang.generator.rust.RustTargetConfig; /** @@ -100,6 +103,14 @@ public class TargetConfig { * Additional sources to add to the compile command if appropriate. */ public List compileAdditionalSources = new ArrayList<>(); + + /** + * Additional (preprocessor) definitions to add to the compile command if appropriate. + * + * The first string is the definition itself, and the second string is the value to attribute to that definition, if any. + * The second value could be left empty. + */ + public Map compileDefinitions = new HashMap<>(); /** * Additional libraries to add to the compile command using the "-l" command-line option. @@ -189,6 +200,9 @@ public class TargetConfig { /** Whether all reactors are to be generated into a single target language file. */ public boolean singleFileProject = false; + + /** What runtime scheduler to use. */ + public SchedulerOption schedulerType = SchedulerOption.getDefault(); /** * The number of worker threads to deploy. The default is zero (i.e., diff --git a/org.lflang/src/org/lflang/TargetProperty.java b/org.lflang/src/org/lflang/TargetProperty.java index 7307470af2..af80f02cce 100644 --- a/org.lflang/src/org/lflang/TargetProperty.java +++ b/org.lflang/src/org/lflang/TargetProperty.java @@ -28,14 +28,12 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Predicate; import java.util.stream.Collectors; import org.eclipse.xtext.util.RuntimeIOException; - import org.lflang.TargetConfig.DockerOptions; import org.lflang.TargetConfig.TracingOptions; import org.lflang.generator.InvalidLfSourceException; @@ -313,6 +311,16 @@ public enum TargetProperty { Arrays.asList(Target.CPP), (config, value, err) -> { config.runtimeVersion = ASTUtils.toText(value); }), + + + /** + * Directive for specifying a specific runtime scheduler, if supported. + */ + SCHEDULER("scheduler", UnionType.SCHEDULER_UNION, + Arrays.asList(Target.C, Target.CCPP, Target.Python), (config, value, err) -> { + config.schedulerType = (SchedulerOption) UnionType.SCHEDULER_UNION + .forName(ASTUtils.toText(value)); + }), /** * Directive to specify that all code is generated in a single file. @@ -760,6 +768,7 @@ public enum UnionType implements TargetPropertyType { BUILD_TYPE_UNION(Arrays.asList(BuildType.values()), null), COORDINATION_UNION(Arrays.asList(CoordinationType.values()), CoordinationType.CENTRALIZED), + SCHEDULER_UNION(Arrays.asList(SchedulerOption.values()), SchedulerOption.getDefault()), LOGGING_UNION(Arrays.asList(LogLevel.values()), LogLevel.INFO), CLOCK_SYNC_UNION(Arrays.asList(ClockSyncMode.values()), ClockSyncMode.INITIAL), @@ -1275,6 +1284,37 @@ public String toString() { return this.name().toLowerCase(); } } + + /** + * Supported schedulers. + * @author{Soroush Bateni } + */ + public enum SchedulerOption { + NP(false), // Non-preemptive + GEDF_NP(true), // Global EDF non-preemptive + GEDF_NP_CI(true); // Global EDF non-preemptive with chain ID + // PEDF_NP(true); // Partitioned EDF non-preemptive (FIXME: To be re-added in a future PR) + + /** + * Indicate whether or not the scheduler prioritizes reactions by deadline. + */ + private final Boolean prioritizesDeadline; + + /** + * Return true if the scheduler prioritizes reactions by deadline. + */ + public Boolean prioritizesDeadline() { + return this.prioritizesDeadline; + } + + private SchedulerOption(Boolean prioritizesDeadline) { + this.prioritizesDeadline = prioritizesDeadline; + } + + public static SchedulerOption getDefault() { + return NP; + } + } /** * Tracing options. diff --git a/org.lflang/src/org/lflang/generator/CodeBuilder.java b/org.lflang/src/org/lflang/generator/CodeBuilder.java index f0116b14d5..cbfd710a0e 100644 --- a/org.lflang/src/org/lflang/generator/CodeBuilder.java +++ b/org.lflang/src/org/lflang/generator/CodeBuilder.java @@ -69,6 +69,13 @@ public void insert(int position, String text) { public int length() { return code.length(); } + + /** + * Add a new line. + */ + public void newLine() { + this.pr(""); + } /** * Append the specified text plus a final newline. diff --git a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend index e928ce7b58..fb2de5efee 100644 --- a/org.lflang/src/org/lflang/generator/GeneratorBase.xtend +++ b/org.lflang/src/org/lflang/generator/GeneratorBase.xtend @@ -915,11 +915,13 @@ abstract class GeneratorBase extends AbstractLFValidator { // // Private functions /** - * Remove triggers in each federates' network reactions that are defined in remote federates. + * Remove triggers in each federates' network reactions that are defined + * in remote federates. * * This must be done in code generators after the dependency graphs * are built and levels are assigned. Otherwise, these disconnected ports - * might reference data structures in remote federates and cause compile errors. + * might reference data structures in remote federates and cause + * compile/runtime errors. * * @param instance The reactor instance to remove these ports from if any. * Can be null. diff --git a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java index 20773c8ffa..c46de3bd23 100644 --- a/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java +++ b/org.lflang/src/org/lflang/generator/JavaGeneratorUtils.java @@ -1,8 +1,5 @@ package org.lflang.generator; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -18,7 +15,6 @@ import org.eclipse.emf.common.util.URI; 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; @@ -30,6 +26,7 @@ import org.lflang.TargetConfig; import org.lflang.TargetConfig.Mode; import org.lflang.TargetProperty; +import org.lflang.TargetProperty.SchedulerOption; import org.lflang.graph.InstantiationGraph; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; @@ -98,6 +95,12 @@ public static void setTargetConfig( if (context.getArgs().containsKey("target-compiler")) { targetConfig.compiler = context.getArgs().getProperty("target-compiler"); } + if (context.getArgs().containsKey("scheduler")) { + targetConfig.schedulerType = SchedulerOption.valueOf( + context.getArgs().getProperty("scheduler") + ); + targetConfig.setByUser.add(TargetProperty.SCHEDULER); + } if (context.getArgs().containsKey("target-flags")) { targetConfig.compilerFlags.clear(); if (!context.getArgs().getProperty("target-flags").isEmpty()) { diff --git a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java index fbcfee645e..bc77c60939 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstanceGraph.java @@ -94,6 +94,14 @@ public void rebuild() { } } + /* + * Get an array of non-negative integers representing the number of reactions + * per each level, where levels are indices of the array. + */ + public Integer[] getNumReactionsPerLevel() { + return numReactionsPerLevel.toArray(new Integer[0]); + } + /////////////////////////////////////////////////////////// //// Protected methods @@ -206,6 +214,16 @@ protected void addNodesAndEdges(ReactorInstance reactor) { } } + /////////////////////////////////////////////////////////// + //// Private fields + + /** + * Number of reactions per level, represented as a list of + * integers where the indices are the levels. + */ + private List numReactionsPerLevel = new ArrayList<>( + List.of(Integer.valueOf(0))); + /////////////////////////////////////////////////////////// //// Private methods @@ -224,7 +242,7 @@ private void assignLevels() { // All root nodes start with level 0. for (Runtime origin : start) { - origin.level = 0; + assignLevel(origin, 0); } // No need to do any of this if there are no root nodes; @@ -232,13 +250,18 @@ private void assignLevels() { while (!start.isEmpty()) { Runtime origin = start.remove(0); Set toRemove = new LinkedHashSet(); + Set downstreamAdjacentNodes = getDownstreamAdjacentNodes(origin); + // All downstream adjacent nodes start with a level 0. Adjust the + // maxNumOfReactionPerLevel field accordingly (to be + // updated in the for loop below). + adjustNumReactionsPerLevel(0, downstreamAdjacentNodes.size()); // Visit effect nodes. - for (Runtime effect : getDownstreamAdjacentNodes(origin)) { + for (Runtime effect : downstreamAdjacentNodes) { // 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); + updateLevel(effect, origin.level+1); } // Remove visited edges. for (Runtime effect : toRemove) { @@ -254,4 +277,47 @@ private void assignLevels() { removeNode(origin); } } + + /** + * 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 + * adding to the previously recorded number valueToAdd. + * If there is no previously recorded number for this level, then + * create one with index level and value valueToAdd. + * @param level The level. + * @param valueToAdd The value to add to the number of levels. + */ + private void adjustNumReactionsPerLevel(int level, int valueToAdd) { + if (numReactionsPerLevel.size() > level) { + numReactionsPerLevel.set(level, numReactionsPerLevel.get(level) + valueToAdd); + } else { + while (numReactionsPerLevel.size() < level) { + numReactionsPerLevel.add(0); + } + numReactionsPerLevel.add(valueToAdd); + } + } } diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 09cc8c2c96..81a1d1c508 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -273,7 +273,7 @@ public PortInstance getInput(String name) { } return null; } - + /** * Override the base class to append [i_d], where d is the depth, * if this reactor is in a bank of reactors. diff --git a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java index ffa17aec2b..a9effdf5d1 100644 --- a/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java +++ b/org.lflang/src/org/lflang/generator/c/CCmakeGenerator.java @@ -84,69 +84,83 @@ CodeBuilder generateCMakeCode( fileConfig.getSrcGenPath().resolve(Paths.get(file))); additionalSources.add(FileConfig.toUnixString(relativePath)); } + cMakeCode.newLine(); cMakeCode.pr("cmake_minimum_required(VERSION 3.13)"); cMakeCode.pr("project("+executableName+" LANGUAGES C)"); - cMakeCode.pr(""); + cMakeCode.newLine(); cMakeCode.pr("# Require C11"); cMakeCode.pr("set(CMAKE_C_STANDARD 11)"); cMakeCode.pr("set(CMAKE_C_STANDARD_REQUIRED ON)"); - cMakeCode.pr(""); + cMakeCode.newLine(); cMakeCode.pr("# Require C++17"); cMakeCode.pr("set(CMAKE_CXX_STANDARD 17)"); cMakeCode.pr("set(CMAKE_CXX_STANDARD_REQUIRED ON)"); - cMakeCode.pr(""); + cMakeCode.newLine(); - // Follow the - cMakeCode.pr("set(DEFAULT_BUILD_TYPE " + targetConfig.cmakeBuildType + ")"); - cMakeCode.pr("if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)"); - cMakeCode.pr(" set(CMAKE_BUILD_TYPE ${DEFAULT_BUILD_TYPE} CACHE STRING \"Choose the type of build.\" FORCE)"); - cMakeCode.pr("endif()"); + cMakeCode.pr("# Compile definitions\n"); + targetConfig.compileDefinitions.forEach( (key, value) -> { + cMakeCode.pr("add_compile_definitions("+key+"="+value+")\n"); + }); + cMakeCode.newLine(); + + // Set the build type + cMakeCode.pr("set(DEFAULT_BUILD_TYPE " + targetConfig.cmakeBuildType + ")\n"); + cMakeCode.pr("if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)\n"); + cMakeCode.pr(" set(CMAKE_BUILD_TYPE ${DEFAULT_BUILD_TYPE} CACHE STRING \"Choose the type of build.\" FORCE)\n"); + cMakeCode.pr("endif()\n"); + cMakeCode.newLine(); cMakeCode.pr("set(CoreLib core)"); cMakeCode.pr("set(PlatformLib platform)"); - cMakeCode.pr(""); + cMakeCode.newLine(); if (CppMode) { // Suppress warnings about const char*. cMakeCode.pr("set(CMAKE_CXX_FLAGS \"${CMAKE_CXX_FLAGS} -Wno-write-strings\")"); + cMakeCode.newLine(); } cMakeCode.pr("include(${CoreLib}/platform/Platform.cmake)"); + cMakeCode.newLine(); cMakeCode.pr("include_directories(${CoreLib})"); cMakeCode.pr("include_directories(${CoreLib}/platform)"); cMakeCode.pr("include_directories(${CoreLib}/federated)"); + cMakeCode.newLine(); cMakeCode.pr("set(LF_MAIN_TARGET "+executableName+")"); + cMakeCode.newLine(); + if (hasMain) { cMakeCode.pr("# Declare a new executable target and list all its sources"); - cMakeCode.pr( - "add_executable(${LF_MAIN_TARGET} " + String.join("\n", sources) - + " ${CoreLib}/platform/${LF_PLATFORM_FILE} " + String.join("\n", additionalSources) + ")\n" - ); + cMakeCode.pr("add_executable("); } else { cMakeCode.pr("# Declare a new library target and list all its sources"); - cMakeCode.pr( - "add_library(${LF_MAIN_TARGET} " + String.join("\n", sources) - + " ${CoreLib}/platform/${LF_PLATFORM_FILE} " + String.join("\n", additionalSources) + ")\n" - ); + cMakeCode.pr("add_library("); } - cMakeCode.pr(""); + cMakeCode.indent(); + cMakeCode.pr("${LF_MAIN_TARGET}"); + sources.forEach(source -> {cMakeCode.pr(source);}); + cMakeCode.pr("${CoreLib}/platform/${LF_PLATFORM_FILE}"); + additionalSources.forEach(source -> {cMakeCode.pr(source);}); + cMakeCode.unindent(); + cMakeCode.pr(")"); + cMakeCode.newLine(); if (targetConfig.threads != 0 || targetConfig.tracing != null) { // If threaded computation is requested, add a the threads option. cMakeCode.pr("# Find threads and link to it"); cMakeCode.pr("find_package(Threads REQUIRED)"); cMakeCode.pr("target_link_libraries( ${LF_MAIN_TARGET} Threads::Threads)"); - cMakeCode.pr(""); + cMakeCode.newLine(); // If the LF program itself is threaded or if tracing is enabled, we need to define // NUMBER_OF_WORKERS so that platform-specific C files will contain the appropriate functions cMakeCode.pr("# Set the number of workers to enable threading"); cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC NUMBER_OF_WORKERS="+targetConfig.threads+")"); - cMakeCode.pr(""); + cMakeCode.newLine(); } // Check if CppMode is enabled @@ -163,6 +177,7 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("set_source_files_properties( "+source+" PROPERTIES LANGUAGE CXX)"); } cMakeCode.pr("set_source_files_properties(${CoreLib}/platform/${LF_PLATFORM_FILE} PROPERTIES LANGUAGE CXX)"); + cMakeCode.newLine(); } if (targetConfig.compiler != null && !targetConfig.compiler.isBlank()) { @@ -172,6 +187,7 @@ CodeBuilder generateCMakeCode( } else { cMakeCode.pr("set(CMAKE_C_COMPILER "+targetConfig.compiler+")"); } + cMakeCode.newLine(); } // Set the compiler flags @@ -206,17 +222,22 @@ CodeBuilder generateCMakeCode( cMakeCode.pr("add_link_options( "+compilerFlag+")"); } } - cMakeCode.pr(""); + cMakeCode.newLine(); // Add the install option - cMakeCode.pr("install(TARGETS ${LF_MAIN_TARGET}"); - cMakeCode.pr(" RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})"); - cMakeCode.pr(""); + cMakeCode.pr("install("); + cMakeCode.indent(); + cMakeCode.pr("TARGETS ${LF_MAIN_TARGET}"); + cMakeCode.pr("RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}"); + cMakeCode.unindent(); + cMakeCode.pr(")"); + cMakeCode.newLine(); // Add the include file for (String includeFile : targetConfig.cmakeIncludesWithoutPath) { cMakeCode.pr("include(\""+includeFile+"\")"); - } + } + cMakeCode.newLine(); return cMakeCode; } diff --git a/org.lflang/src/org/lflang/generator/c/CCompiler.java b/org.lflang/src/org/lflang/generator/c/CCompiler.java index e8b994248d..7d8ee9ec09 100644 --- a/org.lflang/src/org/lflang/generator/c/CCompiler.java +++ b/org.lflang/src/org/lflang/generator/c/CCompiler.java @@ -184,8 +184,12 @@ public LFCommand compileCCommand( } compileArgs.addAll(targetConfig.compileLibraries); + // Add compile definitions + targetConfig.compileDefinitions.forEach( (key,value) -> { + compileArgs.add("-D"+key+"="+value); + }); + // If threaded computation is requested, add a -pthread option. - if (targetConfig.threads != 0 || targetConfig.tracing != null) { compileArgs.add("-pthread"); // If the LF program itself is threaded or if tracing is enabled, we need to define diff --git a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend index 11100595a6..fda61f5d08 100644 --- a/org.lflang/src/org/lflang/generator/c/CGenerator.xtend +++ b/org.lflang/src/org/lflang/generator/c/CGenerator.xtend @@ -69,9 +69,6 @@ import org.lflang.generator.RuntimeRange import org.lflang.generator.SendRange import org.lflang.generator.SubContext import org.lflang.generator.TriggerInstance -import org.lflang.generator.c.CParameterGenerator; -import org.lflang.generator.c.CReactionGenerator; -import org.lflang.generator.c.CPreambleGenerator; import org.lflang.lf.Action import org.lflang.lf.ActionOrigin import org.lflang.lf.Delay @@ -336,7 +333,7 @@ class CGenerator extends GeneratorBase { } /** - * Set C-specific default target properties if needed. + * Set C-specific default target configurations if needed. */ def setCSpecificDefaults(LFGeneratorContext context) { if (!targetConfig.useCmake && targetConfig.compiler.isNullOrEmpty) { @@ -348,6 +345,17 @@ class CGenerator extends GeneratorBase { targetConfig.compilerFlags.addAll("-O2") // "-Wall -Wconversion" } } + if (isFederated) { + // Add compile definitions for federated execution + targetConfig.compileDefinitions.put("FEDERATED", ""); + if (targetConfig.coordination === CoordinationType.CENTRALIZED) { + // The coordination is centralized. + targetConfig.compileDefinitions.put("FEDERATED_CENTRALIZED", ""); + } else if (targetConfig.coordination === CoordinationType.DECENTRALIZED) { + // The coordination is decentralized + targetConfig.compileDefinitions.put("FEDERATED_DECENTRALIZED", ""); + } + } } /** @@ -450,14 +458,19 @@ class CGenerator extends GeneratorBase { var coreFiles = newArrayList( "reactor_common.c", "reactor.h", - "pqueue.c", - "pqueue.h", "tag.h", "tag.c", "trace.h", "trace.c", - "util.h", - "util.c", + "utils/pqueue.c", + "utils/pqueue.h", + "utils/pqueue_support.h", + "utils/vector.c", + "utils/vector.h", + "utils/semaphore.h", + "utils/semaphore.c", + "utils/util.h", + "utils/util.c", "platform.h", "platform/Platform.cmake", "mixed_radix.c", @@ -466,7 +479,8 @@ class CGenerator extends GeneratorBase { if (targetConfig.threads === 0) { coreFiles.add("reactor.c") } else { - coreFiles.add("reactor_threaded.c") + addSchedulerFiles(coreFiles); + coreFiles.add("threaded/reactor_threaded.c") } addPlatformFiles(coreFiles); @@ -507,7 +521,7 @@ class CGenerator extends GeneratorBase { ) ) val compileThreadPool = Executors.newFixedThreadPool(numOfCompileThreads); - System.out.println("******** Using "+numOfCompileThreads+" threads."); + System.out.println("******** Using "+numOfCompileThreads+" threads to compile the program."); var federateCount = 0; val LFGeneratorContext generatingContext = new SubContext( context, IntegratedBuilder.VALIDATED_PERCENT_PROGRESS, IntegratedBuilder.GENERATED_PERCENT_PROGRESS @@ -756,7 +770,7 @@ class CGenerator extends GeneratorBase { bool _lf_trigger_shutdown_reactions() { for (int i = 0; i < _lf_shutdown_reactions_size; i++) { if (_lf_shutdown_reactions[i] != NULL) { - _lf_enqueue_reaction(_lf_shutdown_reactions[i]); + _lf_trigger_reaction(_lf_shutdown_reactions[i], -1); } } // Return true if there are shutdown reactions. @@ -897,6 +911,41 @@ class CGenerator extends GeneratorBase { JavaGeneratorUtils.refreshProject(resource, context.mode) } + /** + * Add files needed for the proper function of the runtime scheduler to + * {@code coreFiles} and {@link TargetConfig#compileAdditionalSources}. + */ + def addSchedulerFiles(ArrayList coreFiles) { + coreFiles.add("threaded/scheduler.h") + coreFiles.add("threaded/scheduler_instance.h") + coreFiles.add("threaded/scheduler_sync_tag_advance.c") + // Don't use a scheduler that does not prioritize reactions based on deadlines + // if the program contains a deadline (handler). Use the GEDF_NP scheduler instead. + if (!targetConfig.schedulerType.prioritizesDeadline) { + // Check if a deadline is assigned to any reaction + if (reactors.filter[reactor | + // Filter reactors that contain at least one reaction + // that has a deadline handler. + return reactor.allReactions.filter[ reaction | + return reaction.deadline !== null + ].size > 0; + ].size > 0) { + if (!targetConfig.setByUser.contains(TargetProperty.SCHEDULER)) { + targetConfig.schedulerType = TargetProperty.SchedulerOption.GEDF_NP; + } + } + } + coreFiles.add("threaded/scheduler_" + targetConfig.schedulerType.toString() + ".c"); + targetConfig.compileAdditionalSources.add( + "core" + File.separator + "threaded" + File.separator + + "scheduler_" + targetConfig.schedulerType.toString() + ".c" + ); + System.out.println("******** Using the "+targetConfig.schedulerType.toString()+" runtime scheduler."); + targetConfig.compileAdditionalSources.add( + "core" + File.separator + "utils" + File.separator + "semaphore.c" + ); + } + /** * Generate the _lf_trigger_startup_reactions function. */ @@ -906,7 +955,7 @@ class CGenerator extends GeneratorBase { «IF startupReactionCount > 0» for (int i = 0; i < _lf_startup_reactions_size; i++) { if (_lf_startup_reactions[i] != NULL) { - _lf_enqueue_reaction(_lf_startup_reactions[i]); + _lf_trigger_reaction(_lf_startup_reactions[i], -1); } } «ENDIF» @@ -1008,6 +1057,9 @@ class CGenerator extends GeneratorBase { setReactionPriorities(main, code) initializeFederate(federate) + + initializeScheduler(); + code.unindent() code.pr("}\n") } @@ -1497,6 +1549,28 @@ class CGenerator extends GeneratorBase { } } + /** + * Generate code to initialize the scheduler foer the threaded C runtime. + */ + protected def initializeScheduler() { + if (targetConfig.threads > 0) { + val numReactionsPerLevel = this.main.assignLevels.getNumReactionsPerLevel(); + code.pr(''' + + // Initialize the scheduler + size_t num_reactions_per_level[«numReactionsPerLevel.size»] = + {«numReactionsPerLevel.join(", \\\n")»}; + sched_params_t sched_params = (sched_params_t) { + .num_reactions_per_level = &num_reactions_per_level[0], + .num_reactions_per_level_size = (size_t) «numReactionsPerLevel.size»}; + lf_sched_init( + «targetConfig.threads», + &sched_params + ); + ''') + } + } + /** * Copy target-specific header file to the src-gen directory. */ @@ -2857,25 +2931,10 @@ class CGenerator extends GeneratorBase { * Return a string that defines the log level. */ static def String defineLogLevel(GeneratorBase generator) { - // FIXME: if we align the levels with the ordinals of the - // enum (see CppGenerator), then we don't need this function. - switch(generator.targetConfig.logLevel) { - case ERROR: ''' - #define LOG_LEVEL 0 - ''' - case WARN: ''' - #define LOG_LEVEL 1 - ''' - case INFO: ''' - #define LOG_LEVEL 2 - ''' - case LOG: ''' - #define LOG_LEVEL 3 - ''' - case DEBUG: ''' - #define LOG_LEVEL 4 - ''' - } + generator.targetConfig.compileDefinitions.put("LOG_LEVEL" , generator.targetConfig.logLevel.ordinal.toString); + ''' + #define LOG_LEVEL «generator.targetConfig.logLevel.ordinal» + ''' } /** @@ -3871,7 +3930,7 @@ class CGenerator extends GeneratorBase { } /** - * Generate code that needs appear at the top of the generated + * Generate code that needs to appear at the top of the generated * C file, such as #define and #include statements. */ def generatePreamble() { @@ -3979,7 +4038,8 @@ class CGenerator extends GeneratorBase { /** Add necessary source files specific to the target language. */ protected def includeTargetLanguageSourceFiles() { if (targetConfig.threads > 0) { - code.pr("#include \"core/reactor_threaded.c\"") + code.pr("#include \"core/threaded/reactor_threaded.c\"") + code.pr("#include \"core/threaded/scheduler.h\"") } else { code.pr("#include \"core/reactor.c\"") } diff --git a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java index 20960e1c47..4c6cf4be18 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonGenerator.java @@ -27,10 +27,10 @@ package org.lflang.generator.python; import java.io.File; -import java.io.IOError; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -40,12 +40,11 @@ import java.util.Set; import java.util.stream.Collectors; -import com.google.common.base.Objects; - import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.xtext.util.CancelIndicator; import org.eclipse.xtext.xbase.lib.Exceptions; import org.eclipse.xtext.xbase.lib.IterableExtensions; +import org.lflang.ASTUtils; import org.lflang.ErrorReporter; import org.lflang.FileConfig; import org.lflang.InferredType; @@ -59,25 +58,16 @@ import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.generator.CodeBuilder; import org.lflang.generator.CodeMap; -import org.lflang.generator.GeneratorBase; import org.lflang.generator.GeneratorResult; import org.lflang.generator.IntegratedBuilder; import org.lflang.generator.JavaGeneratorUtils; import org.lflang.generator.LFGeneratorContext; -import org.lflang.generator.ReactionInstance; import org.lflang.generator.ReactorInstance; import org.lflang.generator.SubContext; import org.lflang.generator.TargetTypes; import org.lflang.generator.c.CGenerator; -import org.lflang.generator.c.CUtil; import org.lflang.generator.c.CPreambleGenerator; -import org.lflang.generator.python.PythonDockerGenerator; -import org.lflang.generator.python.PyUtil; -import org.lflang.generator.python.PythonReactionGenerator; -import org.lflang.generator.python.PythonReactorGenerator; -import org.lflang.generator.python.PythonParameterGenerator; -import org.lflang.generator.python.PythonNetworkGenerator; -import org.lflang.generator.python.PythonPreambleGenerator; +import org.lflang.generator.c.CUtil; import org.lflang.lf.Action; import org.lflang.lf.Delay; import org.lflang.lf.Input; @@ -87,13 +77,10 @@ import org.lflang.lf.Reaction; import org.lflang.lf.Reactor; import org.lflang.lf.ReactorDecl; -import org.lflang.lf.StateVar; -import org.lflang.lf.Value; import org.lflang.lf.VarRef; import org.lflang.util.LFCommand; -import org.lflang.generator.python.PythonInfoGenerator; -import org.lflang.ASTUtils; -import org.lflang.JavaAstUtils; + +import com.google.common.base.Objects; /** @@ -273,10 +260,19 @@ public String generatePythonSetupFile() { List sources = new ArrayList<>(targetConfig.compileAdditionalSources); sources.add(topLevelName + ".c"); - sources.replaceAll(PythonGenerator::addDoubleQuotes); + sources = sources.stream() + .map(Paths::get) + .map(FileConfig::toUnixString) + .map(PythonGenerator::addDoubleQuotes) + .collect(Collectors.toList()); List macros = new ArrayList<>(); macros.add(generateMacroEntry("MODULE_NAME", moduleName)); + + for (var entry : targetConfig.compileDefinitions.entrySet()) { + macros.add(generateMacroEntry(entry.getKey(), entry.getValue())); + } + if (targetConfig.threads != 0 || targetConfig.tracing != null) { macros.add(generateMacroEntry("NUMBER_OF_WORKERS", String.valueOf(targetConfig.threads))); } diff --git a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java b/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java index dd3bb51b86..c984b9934e 100644 --- a/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java +++ b/org.lflang/src/org/lflang/generator/python/PythonReactionGenerator.java @@ -368,9 +368,11 @@ public static String generateCDelayBody(Action action, VarRef port, boolean isTo * @param mainDef The definition of the main reactor * @param topLevelName The name of the module */ - public static String generateCPythonReactionLinkers(ReactorInstance instance, - Instantiation mainDef, - String topLevelName) { + public static String generateCPythonReactionLinkers( + ReactorInstance instance, + Instantiation mainDef, + String topLevelName + ) { String nameOfSelfStruct = CUtil.reactorRef(instance); Reactor reactor = ASTUtils.toDefinition(instance.getDefinition().getReactorClass()); CodeBuilder code = new CodeBuilder(); @@ -399,10 +401,12 @@ public static String generateCPythonReactionLinkers(ReactorInstance instance, * @param topLevelName The name of the module. * @param nameOfSelfStruct The name of the self struct in cpython. */ - public static String generateCPythonReactionLinker(ReactorInstance instance, - ReactionInstance reaction, - String topLevelName, - String nameOfSelfStruct) { + public static String generateCPythonReactionLinker( + ReactorInstance instance, + ReactionInstance reaction, + String topLevelName, + String nameOfSelfStruct + ) { CodeBuilder code = new CodeBuilder(); code.pr(generateCPythonFunctionLinker( nameOfSelfStruct, generateCPythonReactionFunctionName(reaction.index), @@ -430,7 +434,10 @@ private static String generateCPythonFunctionLinker(String nameOfSelfStruct, Str "get_python_function(\"__main__\", ", " "+nameOfSelfStruct+"->_lf_name,", " "+CUtil.runtimeIndex(instance)+",", - " \""+pythonFunctionName+"\");" + " \""+pythonFunctionName+"\");", + "if("+nameOfSelfStruct+"->"+cpythonFunctionName+" == NULL) {", + " error_print_and_exit(\"Could not load function "+pythonFunctionName+"\");", + "}" ); } diff --git a/org.lflang/src/org/lflang/validation/LFValidator.java b/org.lflang/src/org/lflang/validation/LFValidator.java index 8a95bf8f72..1b72a035f8 100644 --- a/org.lflang/src/org/lflang/validation/LFValidator.java +++ b/org.lflang/src/org/lflang/validation/LFValidator.java @@ -1109,6 +1109,36 @@ public void checkTargetProperties(KeyValuePairs targetProperties) { ); } } + + + EList schedulerTargetProperties = + new BasicEList<>(targetProperties.getPairs()); + schedulerTargetProperties.removeIf(pair -> TargetProperty + .forName(pair.getName()) != TargetProperty.SCHEDULER); + KeyValuePair schedulerTargetProperty = schedulerTargetProperties + .size() > 0 ? schedulerTargetProperties.get(0) : null; + if (schedulerTargetProperty != null) { + String schedulerName = schedulerTargetProperty.getValue().getId(); + if (!TargetProperty.SchedulerOption.valueOf(schedulerName) + .prioritizesDeadline()) { + // Check if a deadline is assigned to any reaction + if (info.model.getReactors().stream().filter(reactor -> { + // Filter reactors that contain at least one reaction that + // has a deadline handler. + return ASTUtils.allReactions(reactor).stream() + .filter(reaction -> { + return reaction.getDeadline() != null; + }).count() > 0; + }).count() > 0) { + warning("This program contains deadlines, but the chosen " + + schedulerName + + " scheduler does not prioritize reaction execution " + + "based on deadlines. This might result in a sub-optimal " + + "scheduling.", schedulerTargetProperty, + Literals.KEY_VALUE_PAIR__VALUE); + } + } + } } @Check(CheckType.FAST) diff --git a/test/C/src/concurrent/StopThreaded.lf b/test/C/src/concurrent/StopThreaded.lf index f73a67e9aa..8a8f8ca911 100644 --- a/test/C/src/concurrent/StopThreaded.lf +++ b/test/C/src/concurrent/StopThreaded.lf @@ -6,7 +6,9 @@ */ target C { timeout: 11 msec, - threads: 4 + threads: 4, + build-type: RelWithDebInfo, + // logging: DEBUG }; import Sender from "../lib/LoopedActionSender.lf" @@ -18,13 +20,15 @@ reactor Consumer { tag_t current_tag = get_current_tag(); if (compare_tags(current_tag, (tag_t) { .time = MSEC(10) + get_start_time(), .microstep = 9}) > 0) { - // The reaction should not have been called at tags larger than (10 msec, 9) - fprintf(stderr, "ERROR: Invoked reaction(in) at tag bigger than shutdown.\n"); - exit(1); + // The reaction should not have been called at tags larger than (10 + // msec, 9) + char time[255]; + error_print_and_exit("Invoked reaction(in) at tag (%llu, %d) which is bigger than shutdown.", + current_tag.time - get_start_time(), current_tag.microstep); } else if (compare_tags(current_tag, (tag_t) { .time = MSEC(10) + get_start_time(), .microstep = 8}) == 0) { // Call request_stop() at relative tag (10 msec, 8) - printf("Requesting stop.\n"); + info_print("Requesting stop."); request_stop(); } else if (compare_tags(current_tag, (tag_t) { .time = MSEC(10) + get_start_time(), .microstep = 9}) == 0) { @@ -36,22 +40,20 @@ reactor Consumer { reaction(shutdown) {= tag_t current_tag = get_current_tag(); - printf("Shutdown invoked at tag (%lld, %u).\n", current_tag.time - get_start_time(), current_tag.microstep); + info_print("Shutdown invoked at tag (%lld, %u).", current_tag.time - get_start_time(), current_tag.microstep); // Check to see if shutdown is called at relative tag (10 msec, 9) if (compare_tags(current_tag, (tag_t) { .time = MSEC(10) + get_start_time(), .microstep = 9}) == 0 && self->reaction_invoked_correctly == true) { - printf("SUCCESS: successfully enforced stop.\n"); + info_print("SUCCESS: successfully enforced stop."); } else if(compare_tags(current_tag, (tag_t) { .time = MSEC(10) + get_start_time(), .microstep = 9}) > 0) { - fprintf(stderr,"ERROR: Shutdown invoked at tag (%llu, %d). Failed to enforce timeout.\n", + error_print_and_exit("Shutdown invoked at tag (%llu, %d). Failed to enforce timeout.", current_tag.time - get_start_time(), current_tag.microstep); - exit(1); } else if (self->reaction_invoked_correctly == false) { // Check to see if reactions were called correctly - fprintf(stderr,"ERROR: Failed to invoke reaction(in) at tag (%llu, %d).\n", + error_print_and_exit("Failed to invoke reaction(in) at tag (%llu, %d).", current_tag.time - get_start_time(), current_tag.microstep); - exit(1); } =} }