diff --git a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java index 163c1f4099..92751487b6 100644 --- a/cli/lfc/src/main/java/org/lflang/cli/Lfc.java +++ b/cli/lfc/src/main/java/org/lflang/cli/Lfc.java @@ -22,7 +22,7 @@ import org.lflang.target.property.PrintStatisticsProperty; import org.lflang.target.property.RuntimeVersionProperty; import org.lflang.target.property.SchedulerProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.TracingProperty; import org.lflang.target.property.TracingProperty.TracingOptions; import org.lflang.target.property.VerifyProperty; @@ -33,6 +33,7 @@ import org.lflang.target.property.type.LoggingType.LogLevel; import org.lflang.target.property.type.SchedulerType; import org.lflang.target.property.type.SchedulerType.Scheduler; +import picocli.CommandLine.ArgGroup; import picocli.CommandLine.Command; import picocli.CommandLine.Option; @@ -139,22 +140,28 @@ public class Lfc extends CliBase { description = "Specify the runtime scheduler (if supported).") private String scheduler; - @Option( - names = {"-t", "--threading"}, - paramLabel = "", - description = "Specify whether the runtime should use multi-threading" + " (true/false).") - private String threading; - @Option( names = {"--tracing"}, arity = "0", description = "Specify whether to enable run-time tracing (if supported).") private Boolean tracing; - @Option( - names = {"-w", "--workers"}, - description = "Specify the default number of worker threads.") - private Integer workers; + /** Mutually exclusive options related to threading. */ + static class ThreadingMutuallyExclusive { + @Option( + names = "--single-threaded", + arity = "0", + description = "Specify whether the runtime should be single-threaded.") + private boolean singleThreaded; + + @Option( + names = {"-w", "--workers"}, + description = "Specify the number of worker threads.") + private Integer workers; + } + + @ArgGroup(exclusive = true, multiplicity = "0..1") + ThreadingMutuallyExclusive threading; /** * Main function of the stand-alone compiler. Caution: this will invoke System.exit. @@ -331,15 +338,25 @@ private TracingOptions getTracingOptions() { } } - /** Return whether threading has been enabled via the CLI arguments, or {@code null} otherwise. */ - private Boolean getThreading() { + /** Return the single threaded mode has been specified, or {@code null} if none was specified. */ + private Boolean getSingleThreaded() { + Boolean singleThreaded = null; + // Set one of the mutually-exclusive threading options. if (threading != null) { - return Boolean.parseBoolean(threading); - } else { - return null; + singleThreaded = threading.singleThreaded; } + return singleThreaded; } + /** Return the number of workers specified, or {@code null} if none was specified. */ + private Integer getWorkers() { + Integer workers = null; + // Set one of the mutually-exclusive threading options. + if (threading != null) { + workers = threading.workers; + } + return workers; + } /** Check the values of the commandline arguments and return them. */ public GeneratorArguments getArgs() { @@ -360,8 +377,8 @@ public GeneratorArguments getArgs() { new Argument<>(VerifyProperty.INSTANCE, verify), new Argument<>(RuntimeVersionProperty.INSTANCE, runtimeVersion), new Argument<>(SchedulerProperty.INSTANCE, getScheduler()), - new Argument<>(ThreadingProperty.INSTANCE, getThreading()), + new Argument<>(SingleThreadedProperty.INSTANCE, getSingleThreaded()), new Argument<>(TracingProperty.INSTANCE, getTracingOptions()), - new Argument<>(WorkersProperty.INSTANCE, workers))); + new Argument<>(WorkersProperty.INSTANCE, getWorkers()))); } } diff --git a/cli/lfc/src/test/java/org/lflang/cli/LfcCliTest.java b/cli/lfc/src/test/java/org/lflang/cli/LfcCliTest.java index ef7a7676c8..aa9090c480 100644 --- a/cli/lfc/src/test/java/org/lflang/cli/LfcCliTest.java +++ b/cli/lfc/src/test/java/org/lflang/cli/LfcCliTest.java @@ -50,9 +50,8 @@ import org.lflang.target.property.PrintStatisticsProperty; import org.lflang.target.property.RuntimeVersionProperty; import org.lflang.target.property.SchedulerProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.TargetProperty; -import org.lflang.target.property.ThreadingProperty; -import org.lflang.target.property.WorkersProperty; import org.lflang.target.property.type.BuildTypeType.BuildType; import org.lflang.target.property.type.LoggingType.LogLevel; import org.lflang.target.property.type.SchedulerType.Scheduler; @@ -92,8 +91,7 @@ public class LfcCliTest { "rti": "path/to/rti", "runtime-version": "rs", "scheduler": "GEDF_NP", - "threading": false, - "workers": "1" + "single-threaded": true } } """; @@ -136,6 +134,14 @@ public void testMutuallyExclusiveCliArgs() { result.checkStdErr(containsString("are mutually exclusive (specify only one)")); result.checkFailed(); }); + + lfcTester + .run("File.lf", "--single-threaded", "--workers", "1") + .verify( + result -> { + result.checkStdErr(containsString("are mutually exclusive (specify only one)")); + result.checkFailed(); + }); } @Test @@ -265,8 +271,7 @@ public void verifyGeneratorArgs(Path tempDir, String[] args) { checkOverrideValue(genArgs, PrintStatisticsProperty.INSTANCE, true); checkOverrideValue(genArgs, RuntimeVersionProperty.INSTANCE, "rs"); checkOverrideValue(genArgs, SchedulerProperty.INSTANCE, Scheduler.GEDF_NP); - checkOverrideValue(genArgs, ThreadingProperty.INSTANCE, false); - checkOverrideValue(genArgs, WorkersProperty.INSTANCE, 1); + checkOverrideValue(genArgs, SingleThreadedProperty.INSTANCE, true); assertEquals(true, genArgs.clean()); assertEquals("src", Path.of(genArgs.externalRuntimeUri()).getFileName().toString()); @@ -333,10 +338,7 @@ public void testGeneratorArgs(@TempDir Path tempDir) throws IOException { "rs", "--scheduler", "GEDF_NP", - "--threading", - "false", - "--workers", - "1", + "--single-threaded" }; verifyGeneratorArgs(tempDir, args); } diff --git a/core/src/main/java/org/lflang/ast/ASTUtils.java b/core/src/main/java/org/lflang/ast/ASTUtils.java index 1017d36dd3..b7709bec78 100644 --- a/core/src/main/java/org/lflang/ast/ASTUtils.java +++ b/core/src/main/java/org/lflang/ast/ASTUtils.java @@ -151,10 +151,10 @@ public static List getAllReactors(Resource resource) { } /** - * Get the main reactor defined in the given resource. + * Get the main reactor defined in the given resource, if there is one. * * @param resource the resource to extract reactors from - * @return An iterable over all reactors found in the resource + * @return An {@code Optional} reactor that may be present or absent. */ public static Optional getMainReactor(Resource resource) { return StreamSupport.stream( @@ -165,6 +165,21 @@ public static Optional getMainReactor(Resource resource) { .findFirst(); } + /** + * Get the federated reactor defined in the given resource, if there is one. + * + * @param resource the resource to extract reactors from + * @return An {@code Optional} reactor that may be present or absent. + */ + public static Optional getFederatedReactor(Resource resource) { + return StreamSupport.stream( + IteratorExtensions.toIterable(resource.getAllContents()).spliterator(), false) + .filter(Reactor.class::isInstance) + .map(Reactor.class::cast) + .filter(it -> it.isFederated()) + .findFirst(); + } + /** * Find connections in the given resource that would be conflicting writes if they were not * located in mutually exclusive modes. diff --git a/core/src/main/java/org/lflang/federated/extensions/CExtension.java b/core/src/main/java/org/lflang/federated/extensions/CExtension.java index 0d145bc810..29039010b4 100644 --- a/core/src/main/java/org/lflang/federated/extensions/CExtension.java +++ b/core/src/main/java/org/lflang/federated/extensions/CExtension.java @@ -61,7 +61,7 @@ import org.lflang.target.property.CoordinationProperty; import org.lflang.target.property.FedSetupProperty; import org.lflang.target.property.KeepaliveProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.type.CoordinationModeType.CoordinationMode; /** @@ -93,7 +93,7 @@ public void initializeTargetConfig( // Also, create the RTI C file and the launcher script. // Handle target parameters. // If the program is federated, then ensure that threading is enabled. - ThreadingProperty.INSTANCE.override(federate.targetConfig, true); + SingleThreadedProperty.INSTANCE.override(federate.targetConfig, false); // Include the fed setup file for this federate in the target property FedSetupProperty.INSTANCE.override(federate.targetConfig, getPreamblePath(federate)); diff --git a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java index f6bbac3b3f..c1cdc22f45 100644 --- a/core/src/main/java/org/lflang/federated/generator/FedGenerator.java +++ b/core/src/main/java/org/lflang/federated/generator/FedGenerator.java @@ -291,9 +291,7 @@ private Map compileFederates( TargetConfig subConfig = new TargetConfig( - GeneratorUtils.findTargetDecl(subFileConfig.resource), - GeneratorArguments.none(), - subContextMessageReporter); + subFileConfig.resource, GeneratorArguments.none(), subContextMessageReporter); if (targetConfig.get(DockerProperty.INSTANCE).enabled && targetConfig.target.buildsUsingDocker()) { NoCompileProperty.INSTANCE.override(subConfig, true); diff --git a/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java b/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java index 6fc29f7164..178120a95e 100644 --- a/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java +++ b/core/src/main/java/org/lflang/federated/generator/FederateTargetConfig.java @@ -11,11 +11,11 @@ import org.lflang.generator.GeneratorUtils; import org.lflang.generator.LFGeneratorContext; import org.lflang.lf.KeyValuePair; -import org.lflang.lf.LfFactory; import org.lflang.target.Target; import org.lflang.target.TargetConfig; import org.lflang.target.property.ClockSyncModeProperty; import org.lflang.target.property.ClockSyncOptionsProperty; +import org.lflang.target.property.FileListProperty; import org.lflang.util.FileUtil; /** @@ -34,20 +34,27 @@ public class FederateTargetConfig extends TargetConfig { * @param federateResource The resource in which to find the reactor class of the federate. */ public FederateTargetConfig(LFGeneratorContext context, Resource federateResource) { - // Create target config based on the main .lf file (but with the target of the federate, - // which could be different). - super( - Target.fromDecl(GeneratorUtils.findTargetDecl(federateResource)), - GeneratorUtils.findTargetDecl(context.getFileConfig().resource).getConfig(), - context.getArgs(), - context.getErrorReporter()); + // Create target config with the target based on the federate (not the main resource). + super(Target.fromDecl(GeneratorUtils.findTargetDecl(federateResource))); + var federationResource = context.getFileConfig().resource; + var reporter = context.getErrorReporter(); - mergeImportedConfig( - federateResource, context.getFileConfig().resource, context.getErrorReporter()); + this.mainResource = federationResource; + + // Load properties from the main file + load(federationResource, reporter); + + // Load properties from the federate file + mergeImportedConfig(federateResource, federationResource, reporter); + + // Load properties from the generator context + load(context.getArgs(), reporter); clearPropertiesToIgnore(); ((FederationFileConfig) context.getFileConfig()).relativizePaths(this); + + this.validate(reporter); } /** @@ -98,28 +105,22 @@ private void clearPropertiesToIgnore() { */ public void update( TargetConfig config, List pairs, Path relativePath, MessageReporter err) { - // FIXME: https://issue.lf-lang.org/2080 pairs.forEach( pair -> { var p = config.forName(pair.getName()); if (p.isPresent()) { var value = pair.getValue(); - if (pair.getName().equals("files")) { - var array = LfFactory.eINSTANCE.createArray(); - ASTUtils.elementToListOfStrings(pair.getValue()).stream() - .map(relativePath::resolve) // assume all paths are relative - .map(Objects::toString) - .map( - s -> { - var element = LfFactory.eINSTANCE.createElement(); - element.setLiteral(s); - return element; - }) - .forEach(array.getElements()::add); - value = LfFactory.eINSTANCE.createElement(); - value.setArray(array); + var property = p.get(); + if (property instanceof FileListProperty fileListProperty) { + var files = + ASTUtils.elementToListOfStrings(value).stream() + .map(relativePath::resolve) // assume all paths are relative + .map(Objects::toString) + .toList(); + fileListProperty.update(config, files); + } else { + p.get().update(this, pair, err); } - p.get().update(this, value, err); } }); } diff --git a/core/src/main/java/org/lflang/generator/GeneratorBase.java b/core/src/main/java/org/lflang/generator/GeneratorBase.java index c1851ec361..dade9fed71 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorBase.java +++ b/core/src/main/java/org/lflang/generator/GeneratorBase.java @@ -62,7 +62,7 @@ import org.lflang.target.Target; import org.lflang.target.TargetConfig; import org.lflang.target.property.FilesProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.VerifyProperty; import org.lflang.util.FileUtil; import org.lflang.validation.AbstractLFValidator; @@ -267,7 +267,9 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { // Check for the existence and support of watchdogs hasWatchdogs = IterableExtensions.exists(reactors, it -> !it.getWatchdogs().isEmpty()); - checkWatchdogSupport(getTarget() == Target.C && targetConfig.get(ThreadingProperty.INSTANCE)); + + checkWatchdogSupport( + getTarget() == Target.C && !targetConfig.get(SingleThreadedProperty.INSTANCE)); additionalPostProcessingForModes(); } diff --git a/core/src/main/java/org/lflang/generator/GeneratorUtils.java b/core/src/main/java/org/lflang/generator/GeneratorUtils.java index 43854e9217..88dc9fa075 100644 --- a/core/src/main/java/org/lflang/generator/GeneratorUtils.java +++ b/core/src/main/java/org/lflang/generator/GeneratorUtils.java @@ -21,7 +21,6 @@ import org.lflang.lf.KeyValuePairs; import org.lflang.lf.Reactor; import org.lflang.lf.TargetDecl; -import org.lflang.target.Target; import org.lflang.target.TargetConfig; import org.lflang.target.property.KeepaliveProperty; @@ -120,7 +119,7 @@ public static LFResource getLFResource( MessageReporter messageReporter) { var target = ASTUtils.targetDecl(resource); KeyValuePairs config = target.getConfig(); - var targetConfig = new TargetConfig(Target.fromDecl(target)); + var targetConfig = new TargetConfig(resource, context.getArgs(), messageReporter); if (config != null) { List pairs = config.getPairs(); targetConfig.load(pairs != null ? pairs : List.of(), messageReporter); diff --git a/core/src/main/java/org/lflang/generator/MainContext.java b/core/src/main/java/org/lflang/generator/MainContext.java index e670c5696d..294b9a61b0 100644 --- a/core/src/main/java/org/lflang/generator/MainContext.java +++ b/core/src/main/java/org/lflang/generator/MainContext.java @@ -160,7 +160,6 @@ public void reportProgress(String message, int percentage) { * in the target configuration. */ public void loadTargetConfig() { - this.targetConfig = - new TargetConfig(GeneratorUtils.findTargetDecl(fileConfig.resource), args, messageReporter); + this.targetConfig = new TargetConfig(fileConfig.resource, args, messageReporter); } } diff --git a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java index f3e4afeee8..e928cfbd3e 100644 --- a/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CCmakeGenerator.java @@ -42,7 +42,7 @@ import org.lflang.target.property.CompilerProperty; import org.lflang.target.property.PlatformProperty; import org.lflang.target.property.ProtobufsProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.WorkersProperty; import org.lflang.target.property.type.PlatformType.Platform; import org.lflang.util.FileUtil; @@ -268,11 +268,7 @@ CodeBuilder generateCMakeCode( .get(CompileDefinitionsProperty.INSTANCE) .forEach( (key, value) -> { - if (key.equals("LF_THREADED") || key.equals("LF_UNTHREADED")) { - cMakeCode.pr("if (NOT DEFINED LF_THREADED AND NOT DEFINED LF_UNTHREADED)\n"); - } else { - cMakeCode.pr("if (NOT DEFINED " + key + ")\n"); - } + cMakeCode.pr("if (NOT DEFINED " + key + ")\n"); cMakeCode.indent(); var v = "TRUE"; if (value != null && !value.isEmpty()) { @@ -354,7 +350,7 @@ CodeBuilder generateCMakeCode( cMakeCode.newLine(); } - if (targetConfig.get(ThreadingProperty.INSTANCE) + if (!targetConfig.get(SingleThreadedProperty.INSTANCE) && platformOptions.platform() != Platform.ZEPHYR) { // If threaded computation is requested, add the threads option. cMakeCode.pr("# Find threads and link to it"); @@ -365,18 +361,16 @@ CodeBuilder generateCMakeCode( // Add additional flags so runtime can distinguish between multi-threaded and single-threaded // mode - if (targetConfig.get(ThreadingProperty.INSTANCE)) { + if (!targetConfig.get(SingleThreadedProperty.INSTANCE)) { cMakeCode.pr("# Set the number of workers to enable threading/tracing"); cMakeCode.pr( "target_compile_definitions(${LF_MAIN_TARGET} PUBLIC NUMBER_OF_WORKERS=" + targetConfig.get(WorkersProperty.INSTANCE) + ")"); cMakeCode.newLine(); - cMakeCode.pr("# Set flag to indicate a multi-threaded runtime"); - cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_THREADED=1)"); } else { cMakeCode.pr("# Set flag to indicate a single-threaded runtime"); - cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_UNTHREADED=1)"); + cMakeCode.pr("target_compile_definitions( ${LF_MAIN_TARGET} PUBLIC LF_SINGLE_THREADED=1)"); } cMakeCode.newLine(); 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 567b04ba4f..b02c2ed5b0 100644 --- a/core/src/main/java/org/lflang/generator/c/CGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CGenerator.java @@ -99,7 +99,7 @@ import org.lflang.target.property.PlatformProperty.PlatformOption; import org.lflang.target.property.ProtobufsProperty; import org.lflang.target.property.SchedulerProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.TracingProperty; import org.lflang.target.property.WorkersProperty; import org.lflang.target.property.type.PlatformType.Platform; @@ -361,9 +361,9 @@ public void accommodatePhysicalActionsIfPresent() { // If the unthreaded runtime is not requested by the user, use the threaded runtime // instead // because it is the only one currently capable of handling asynchronous events. - var threading = ThreadingProperty.INSTANCE; - if (!targetConfig.get(threading) && !targetConfig.isSet(threading)) { - threading.override(targetConfig, true); + var singleThreaded = SingleThreadedProperty.INSTANCE; + if (!targetConfig.isSet(singleThreaded) && targetConfig.get(singleThreaded)) { + singleThreaded.override(targetConfig, true); String message = "Using the threaded C runtime to allow for asynchronous handling of physical action" + " " @@ -470,7 +470,7 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { try { Path include = fileConfig.getSrcGenPath().resolve("include/"); Path src = fileConfig.getSrcGenPath().resolve("src/"); - FileUtil.arduinoDeleteHelper(src, targetConfig.get(ThreadingProperty.INSTANCE)); + FileUtil.arduinoDeleteHelper(src, !targetConfig.get(SingleThreadedProperty.INSTANCE)); FileUtil.relativeIncludeHelper(src, include, messageReporter); FileUtil.relativeIncludeHelper(include, include, messageReporter); } catch (IOException e) { @@ -492,9 +492,9 @@ public void doGenerate(Resource resource, LFGeneratorContext context) { + "Grab the FQBN and PORT from the command and run the following command in the" + " generated sources directory:\n\n" + "\tarduino-cli compile -b --build-property" - + " compiler.c.extra_flags='-DLF_UNTHREADED -DPLATFORM_ARDUINO" + + " compiler.c.extra_flags='-DLF_SINGLE_THREADED -DPLATFORM_ARDUINO" + " -DINITIAL_EVENT_QUEUE_SIZE=10 -DINITIAL_REACT_QUEUE_SIZE=10'" - + " --build-property compiler.cpp.extra_flags='-DLF_UNTHREADED" + + " --build-property compiler.cpp.extra_flags='-DLF_SINGLE_THREADED" + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10" + " -DINITIAL_REACT_QUEUE_SIZE=10' .\n\n" + "To flash/upload your generated sketch to the board, run the following" @@ -714,6 +714,8 @@ private void inspectReactorEResource(ReactorDecl reactor) { break; } } + // FIXME: we're doing ad-hoc merging, and no validation. This is **not** the way to do it. + if (lfResource != null) { // Copy the user files and cmake-includes to the src-gen path of the main .lf file copyUserFiles(lfResource.getTargetConfig(), lfResource.getFileConfig()); @@ -1959,16 +1961,16 @@ protected void setUpGeneralParameters() { if (targetConfig.isSet(PlatformProperty.INSTANCE)) { final var platformOptions = targetConfig.get(PlatformProperty.INSTANCE); - if (targetConfig.get(ThreadingProperty.INSTANCE) + if (!targetConfig.get(SingleThreadedProperty.INSTANCE) && platformOptions.platform() == Platform.ARDUINO && (platformOptions.board() == null || !platformOptions.board().contains("mbed"))) { // non-MBED boards should not use threading messageReporter .nowhere() .info( - "Threading is incompatible on your current Arduino flavor. Setting threading to" + "Threading is incompatible with plain (non-MBED) Arduino. Setting threading to" + " false."); - ThreadingProperty.INSTANCE.override(targetConfig, false); + SingleThreadedProperty.INSTANCE.override(targetConfig, true); } if (platformOptions.platform() == Platform.ARDUINO @@ -1985,7 +1987,7 @@ protected void setUpGeneralParameters() { } if (platformOptions.platform() == Platform.ZEPHYR - && targetConfig.get(ThreadingProperty.INSTANCE) + && !targetConfig.get(SingleThreadedProperty.INSTANCE) && platformOptions.userThreads() >= 0) { targetConfig .get(CompileDefinitionsProperty.INSTANCE) @@ -1995,11 +1997,11 @@ protected void setUpGeneralParameters() { .nowhere() .warning( "Specifying user threads is only for threaded Lingua Franca on the Zephyr platform." - + " This option will be ignored."); + + " This option will be ignored."); // FIXME: do this during validation instead } - if (targetConfig.get( - ThreadingProperty.INSTANCE)) { // FIXME: This logic is duplicated in CMake + if (!targetConfig.get( + SingleThreadedProperty.INSTANCE)) { // FIXME: This logic is duplicated in CMake pickScheduler(); // FIXME: this and pickScheduler should be combined. var map = new HashMap(); diff --git a/core/src/main/java/org/lflang/generator/c/CPreambleGenerator.java b/core/src/main/java/org/lflang/generator/c/CPreambleGenerator.java index d76e86fc86..4eaf989981 100644 --- a/core/src/main/java/org/lflang/generator/c/CPreambleGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CPreambleGenerator.java @@ -10,7 +10,7 @@ import org.lflang.target.property.FedSetupProperty; import org.lflang.target.property.LoggingProperty; import org.lflang.target.property.PlatformProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.TracingProperty; import org.lflang.target.property.type.PlatformType.Platform; import org.lflang.util.StringUtil; @@ -48,7 +48,7 @@ public static String generateIncludeStatements(TargetConfig targetConfig, boolea .forEach(it -> code.pr("#include " + StringUtil.addDoubleQuotes(it))); code.pr("#include \"include/core/reactor.h\""); code.pr("#include \"include/core/reactor_common.h\""); - if (targetConfig.get(ThreadingProperty.INSTANCE)) { + if (!targetConfig.get(SingleThreadedProperty.INSTANCE)) { code.pr("#include \"include/core/threaded/scheduler.h\""); } @@ -87,16 +87,8 @@ public static String generateDefineDirectives(TargetConfig targetConfig, Path sr // targetConfig.clockSyncOptions // )); // } - - if (targetConfig.get(ThreadingProperty.INSTANCE)) { - definitions.put("LF_THREADED", "1"); - } else { - definitions.put("LF_UNTHREADED", "1"); - } - if (targetConfig.get(ThreadingProperty.INSTANCE)) { - definitions.put("LF_THREADED", "1"); - } else { - definitions.put("LF_UNTHREADED", "1"); + if (targetConfig.get(SingleThreadedProperty.INSTANCE)) { + definitions.put("LF_SINGLE_THREADED", "1"); } CompileDefinitionsProperty.INSTANCE.update(targetConfig, definitions); code.newLine(); diff --git a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java index 8dcbda1e3a..e183615ce9 100644 --- a/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java +++ b/core/src/main/java/org/lflang/generator/c/CTriggerObjectsGenerator.java @@ -24,7 +24,7 @@ import org.lflang.generator.SendRange; import org.lflang.target.TargetConfig; import org.lflang.target.property.LoggingProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.type.LoggingType.LogLevel; /** @@ -102,7 +102,7 @@ public static String generateInitializeTriggerObjects( /** Generate code to initialize the scheduler for the threaded C runtime. */ public static String generateSchedulerInitializerMain( ReactorInstance main, TargetConfig targetConfig) { - if (!targetConfig.get(ThreadingProperty.INSTANCE)) { + if (targetConfig.get(SingleThreadedProperty.INSTANCE)) { return ""; } var code = new CodeBuilder(); diff --git a/core/src/main/java/org/lflang/generator/python/PythonDelayBodyGenerator.java b/core/src/main/java/org/lflang/generator/python/PythonDelayBodyGenerator.java index 2feb887bf7..3ccb54f490 100644 --- a/core/src/main/java/org/lflang/generator/python/PythonDelayBodyGenerator.java +++ b/core/src/main/java/org/lflang/generator/python/PythonDelayBodyGenerator.java @@ -39,7 +39,7 @@ public String generateDelayBody(Action action, VarRef port) { return String.join( "\n", "// Create a token.", - "#if defined(LF_THREADED)", + "#if !defined(LF_SINGLE_THREADED)", "// Need to lock the mutex first.", "lf_mutex_lock(&self->base.environment->mutex);", "#endif", @@ -49,7 +49,7 @@ public String generateDelayBody(Action action, VarRef port) { + value + ", 1);", "Py_INCREF(" + value + ");", - "#if defined(LF_THREADED)", + "#if !defined(LF_SINGLE_THREADED)", "lf_mutex_unlock(&self->base.environment->mutex);", "#endif", "", diff --git a/core/src/main/java/org/lflang/target/Target.java b/core/src/main/java/org/lflang/target/Target.java index eb2cedd588..cb5505dc6c 100644 --- a/core/src/main/java/org/lflang/target/Target.java +++ b/core/src/main/java/org/lflang/target/Target.java @@ -55,7 +55,7 @@ import org.lflang.target.property.RustIncludeProperty; import org.lflang.target.property.SchedulerProperty; import org.lflang.target.property.SingleFileProjectProperty; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.TracingProperty; import org.lflang.target.property.VerifyProperty; import org.lflang.target.property.WorkersProperty; @@ -545,7 +545,8 @@ public boolean setsKeepAliveOptionAutomatically() { * @param string The string to match against candidates. * @param candidates The candidates to match the string against. */ - public static T match(final String string, final Iterable candidates) { + public static T match( + final String string, final Iterable candidates) { // FIXME: use Optional // kotlin: candidates.firstOrNull { it.toString().equalsIgnoreCase(string) } for (T candidate : candidates) { if (candidate.toString().equalsIgnoreCase(string)) { @@ -600,7 +601,7 @@ public void initialize(TargetConfig config) { PlatformProperty.INSTANCE, ProtobufsProperty.INSTANCE, SchedulerProperty.INSTANCE, - ThreadingProperty.INSTANCE, + SingleThreadedProperty.INSTANCE, TracingProperty.INSTANCE, VerifyProperty.INSTANCE, WorkersProperty.INSTANCE); @@ -632,7 +633,7 @@ public void initialize(TargetConfig config) { KeepaliveProperty.INSTANCE, ProtobufsProperty.INSTANCE, SchedulerProperty.INSTANCE, - ThreadingProperty.INSTANCE, + SingleThreadedProperty.INSTANCE, TracingProperty.INSTANCE, WorkersProperty.INSTANCE); case Rust -> config.register( @@ -648,7 +649,7 @@ public void initialize(TargetConfig config) { KeepaliveProperty.INSTANCE, RuntimeVersionProperty.INSTANCE, SingleFileProjectProperty.INSTANCE, - ThreadingProperty.INSTANCE, + SingleThreadedProperty.INSTANCE, WorkersProperty.INSTANCE); case TS -> config.register( CoordinationOptionsProperty.INSTANCE, diff --git a/core/src/main/java/org/lflang/target/TargetConfig.java b/core/src/main/java/org/lflang/target/TargetConfig.java index 584b4e4694..6170947db0 100644 --- a/core/src/main/java/org/lflang/target/TargetConfig.java +++ b/core/src/main/java/org/lflang/target/TargetConfig.java @@ -36,13 +36,15 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import org.eclipse.emf.ecore.resource.Resource; import org.lflang.MessageReporter; +import org.lflang.ast.ASTUtils; import org.lflang.generator.GeneratorArguments; +import org.lflang.generator.GeneratorUtils; import org.lflang.lf.KeyValuePair; import org.lflang.lf.KeyValuePairs; import org.lflang.lf.LfFactory; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; import org.lflang.lf.TargetDecl; import org.lflang.target.property.FastProperty; import org.lflang.target.property.FedSetupProperty; @@ -51,7 +53,6 @@ import org.lflang.target.property.TargetProperty; import org.lflang.target.property.TimeOutProperty; import org.lflang.target.property.type.TargetPropertyType; -import org.lflang.validation.ValidatorMessageReporter; /** * A class for keeping the current target configuration. @@ -65,12 +66,35 @@ public class TargetConfig { /** The target of this configuration (e.g., C, TypeScript, Python). */ public final Target target; + /** Additional sources to add to the compile command if appropriate. */ + public final List compileAdditionalSources = new ArrayList<>(); + + /** Map of target properties */ + protected final Map, Object> properties = new HashMap<>(); + + /** Map from */ + protected final Map, KeyValuePair> keyValuePairs = new HashMap<>(); + + /** Set of target properties that have been assigned a value */ + private final Set> setProperties = new HashSet<>(); + + /** The main resource that is under compilation. */ + protected Resource mainResource; + + /** + * Return mock instance to use for testing, which is not tied to a generator context or a + * resource. + */ + public static TargetConfig getMockInstance(Target target) { + return new TargetConfig(target); + } + /** * Create a new target configuration based on the given target declaration AST node only. * * @param target AST node of a target declaration. */ - public TargetConfig(Target target) { + protected TargetConfig(Target target) { this.target = target; // Register target-specific properties @@ -87,48 +111,46 @@ public TargetConfig(Target target) { TimeOutProperty.INSTANCE); } + /** + * Load configuration from the given resource. + * + * @param resource A resource to load from. + * @param reporter A reporter for reporting issues. + */ + protected void load(Resource resource, MessageReporter reporter) { + var targetDecl = GeneratorUtils.findTargetDecl(resource); + var properties = targetDecl.getConfig(); + // Load properties from file + if (properties != null) { + List pairs = properties.getPairs(); + this.load(pairs, reporter); + } + } + /** * Create a new target configuration based on the given target declaration AST node and the * arguments passed to the code generator. * - * @param target AST node of a target declaration. + * @param resource The main resource. * @param args The arguments passed to the code generator. - * @param messageReporter An error reporter. + * @param reporter An error reporter. */ - public TargetConfig(TargetDecl target, GeneratorArguments args, MessageReporter messageReporter) { - this(Target.fromDecl(target), target.getConfig(), args, messageReporter); + public TargetConfig(Resource resource, GeneratorArguments args, MessageReporter reporter) { + this(Target.fromDecl(GeneratorUtils.findTargetDecl(resource))); + this.mainResource = resource; + load(resource, reporter); + load(args, reporter); + // Validate to ensure consistency + validate(reporter); } /** - * Create a new target configuration based on the given commandline arguments and target - * declaration AST node. + * Update this configuration based on the given JSON object. * - * @param target The target of this configuration. - * @param properties The key-value pairs that represent the target properties. - * @param args Arguments passed on the commandline. - * @param messageReporter An error reporter to report problems. + * @param jsonObject The JSON object to read updates from. + * @param messageReporter A message reporter to report issues. */ - public TargetConfig( - Target target, - KeyValuePairs properties, - GeneratorArguments args, - MessageReporter messageReporter) { - this(target); - - // Load properties from file - if (properties != null) { - List pairs = properties.getPairs(); - this.load(pairs, messageReporter); - } - - // Load properties from Json - load(args.jsonObject(), messageReporter); - - // Load properties from CLI args - load(args, messageReporter); - } - - private void load(JsonObject jsonObject, MessageReporter messageReporter) { + protected void load(JsonObject jsonObject, MessageReporter messageReporter) { if (jsonObject != null && jsonObject.has("properties")) { var map = jsonObject.getAsJsonObject("properties").asMap(); map.keySet() @@ -155,16 +177,13 @@ public void reportUnsupportedTargetProperty(String name, MessageReporter.Stage2 String.format( "The target property '%s' is not supported by the %s target and is thus ignored.", name, this.target)); + stage2.info("Recognized properties are: " + this.listOfRegisteredProperties()); } - /** Additional sources to add to the compile command if appropriate. */ - public final List compileAdditionalSources = new ArrayList<>(); - - /** Map of target properties */ - protected final Map, Object> properties = new HashMap<>(); - - /** Set of target properties that have been assigned a value */ - private final Set> setProperties = new HashSet<>(); + /** Get the main resource that is under compilation. */ + public Resource getMainResource() { + return mainResource; + } /** * Register target properties and assign them their initial value. @@ -255,6 +274,7 @@ public String listOfRegisteredProperties() { * @param err Message reporter to report attempts to set unsupported target properties. */ public void load(GeneratorArguments args, MessageReporter err) { + load(args.jsonObject(), err); args.overrides().forEach(a -> a.update(this, err)); } @@ -266,15 +286,20 @@ public void load(GeneratorArguments args, MessageReporter err) { */ public void load(List pairs, MessageReporter err) { if (pairs != null) { - pairs.forEach( pair -> { var p = forName(pair.getName()); if (p.isPresent()) { var property = p.get(); - property.update(this, pair.getValue(), err); + // Record the pair. + keyValuePairs.put(property, pair); + if (property.checkType(pair, err)) { + // Only update the config is the pair matches the type. + property.update(this, pair, err); + } } else { - reportUnsupportedTargetProperty(pair.getName(), err.nowhere()); + reportUnsupportedTargetProperty( + pair.getName(), err.at(pair, Literals.KEY_VALUE_PAIR__NAME)); } }); } @@ -306,6 +331,23 @@ public String settings() { return sb.toString(); } + /** + * Return the AST node that was used to assign a value for the given target property. + * + * @param targetProperty The target property to find a matching AST node for. + * @param The Java type of values assigned to the given target property. + * @param The LF type of values assigned to the given target property. + */ + public KeyValuePair lookup( + TargetProperty targetProperty) { + return this.keyValuePairs.get(targetProperty); + } + + /** Return whether this configuration is used in the context of a federated program. */ + public boolean isFederated() { + return ASTUtils.getFederatedReactor(this.getMainResource()).isPresent(); + } + /** * Extract all properties as a list of key-value pairs from a TargetConfig. The returned list only * includes properties that were explicitly set. @@ -349,32 +391,14 @@ public TargetDecl extractTargetDecl() { } /** - * Validate the given key-value pairs and report issues via the given reporter. + * Validate all set properties and report issues via the given reporter. * - * @param pairs The key-value pairs to validate. - * @param ast The root node of the AST from which the key-value pairs were taken. * @param reporter A reporter to report errors and warnings through. */ - public void validate(KeyValuePairs pairs, Model ast, ValidatorMessageReporter reporter) { - pairs - .getPairs() - .forEach( - pair -> { - var match = - this.getRegisteredProperties().stream() - .filter(prop -> prop.name().equalsIgnoreCase(pair.getName())) - .findAny(); - if (match.isPresent()) { - var p = match.get(); - p.checkType(pair, reporter); - p.validate(pair, ast, reporter); - } else { - reportUnsupportedTargetProperty( - pair.getName(), reporter.at(pair, Literals.KEY_VALUE_PAIR__NAME)); - reporter - .at(pair, Literals.KEY_VALUE_PAIR__NAME) - .info("Recognized properties are: " + this.listOfRegisteredProperties()); - } - }); + public void validate(MessageReporter reporter) { + this.setProperties.forEach( + p -> { + p.validate(this, reporter); + }); } } diff --git a/core/src/main/java/org/lflang/target/property/CargoDependenciesProperty.java b/core/src/main/java/org/lflang/target/property/CargoDependenciesProperty.java index 764ab70c11..abb10975cd 100644 --- a/core/src/main/java/org/lflang/target/property/CargoDependenciesProperty.java +++ b/core/src/main/java/org/lflang/target/property/CargoDependenciesProperty.java @@ -3,6 +3,7 @@ import java.util.HashMap; import java.util.Map; import org.lflang.MessageReporter; +import org.lflang.generator.InvalidLfSourceException; import org.lflang.generator.rust.CargoDependencySpec; import org.lflang.generator.rust.CargoDependencySpec.CargoDependenciesPropertyType; import org.lflang.lf.Element; @@ -52,7 +53,13 @@ public Map initialValue() { @Override protected Map fromAst(Element node, MessageReporter reporter) { - return CargoDependencySpec.parseAll(node); + Map map = null; + try { + map = CargoDependencySpec.parseAll(node); + } catch (InvalidLfSourceException e) { + reporter.at(e.getNode()).error(e.getMessage()); + } + return map; } @Override diff --git a/core/src/main/java/org/lflang/target/property/ClockSyncModeProperty.java b/core/src/main/java/org/lflang/target/property/ClockSyncModeProperty.java index 115b781366..796f862c18 100644 --- a/core/src/main/java/org/lflang/target/property/ClockSyncModeProperty.java +++ b/core/src/main/java/org/lflang/target/property/ClockSyncModeProperty.java @@ -4,10 +4,8 @@ import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.lf.Element; -import org.lflang.lf.KeyValuePair; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; -import org.lflang.lf.Reactor; +import org.lflang.target.TargetConfig; import org.lflang.target.property.type.ClockSyncModeType; import org.lflang.target.property.type.ClockSyncModeType.ClockSyncMode; @@ -38,20 +36,11 @@ protected ClockSyncMode fromString(String string, MessageReporter reporter) { } @Override - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) { - super.validate(pair, ast, reporter); - if (pair != null) { - boolean federatedExists = false; - for (Reactor reactor : ast.getReactors()) { - if (reactor.isFederated()) { - federatedExists = true; - } - } - if (!federatedExists) { - reporter - .at(pair, Literals.KEY_VALUE_PAIR__NAME) - .warning("The clock-sync target property is incompatible with non-federated programs."); - } + public void validate(TargetConfig config, MessageReporter reporter) { + if (!config.isFederated()) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__NAME) + .warning("The 'clock-sync' target property is incompatible with non-federated programs."); } } diff --git a/core/src/main/java/org/lflang/target/property/ClockSyncOptionsProperty.java b/core/src/main/java/org/lflang/target/property/ClockSyncOptionsProperty.java index b5d08eaedd..fc24495ad2 100644 --- a/core/src/main/java/org/lflang/target/property/ClockSyncOptionsProperty.java +++ b/core/src/main/java/org/lflang/target/property/ClockSyncOptionsProperty.java @@ -36,14 +36,17 @@ public ClockSyncOptions fromAst(Element node, MessageReporter reporter) { for (KeyValuePair entry : node.getKeyvalue().getPairs()) { ClockSyncOption option = (ClockSyncOption) DictionaryType.CLOCK_SYNC_OPTION_DICT.forName(entry.getName()); - switch (option) { - case ATTENUATION -> options.attenuation = ASTUtils.toInteger(entry.getValue()); - case COLLECT_STATS -> options.collectStats = ASTUtils.toBoolean(entry.getValue()); - case LOCAL_FEDERATES_ON -> options.localFederatesOn = ASTUtils.toBoolean(entry.getValue()); - case PERIOD -> options.period = ASTUtils.toTimeValue(entry.getValue()); - case TEST_OFFSET -> options.testOffset = ASTUtils.toTimeValue(entry.getValue()); - case TRIALS -> options.trials = ASTUtils.toInteger(entry.getValue()); - default -> {} + if (option != null) { + switch (option) { + case ATTENUATION -> options.attenuation = ASTUtils.toInteger(entry.getValue()); + case COLLECT_STATS -> options.collectStats = ASTUtils.toBoolean(entry.getValue()); + case LOCAL_FEDERATES_ON -> options.localFederatesOn = + ASTUtils.toBoolean(entry.getValue()); + case PERIOD -> options.period = ASTUtils.toTimeValue(entry.getValue()); + case TEST_OFFSET -> options.testOffset = ASTUtils.toTimeValue(entry.getValue()); + case TRIALS -> options.trials = ASTUtils.toInteger(entry.getValue()); + default -> {} + } } } return options; diff --git a/core/src/main/java/org/lflang/target/property/CoordinationOptionsProperty.java b/core/src/main/java/org/lflang/target/property/CoordinationOptionsProperty.java index 9d0767ee31..913e46d1cc 100644 --- a/core/src/main/java/org/lflang/target/property/CoordinationOptionsProperty.java +++ b/core/src/main/java/org/lflang/target/property/CoordinationOptionsProperty.java @@ -1,6 +1,5 @@ package org.lflang.target.property; -import java.util.Objects; import org.lflang.MessageReporter; import org.lflang.TimeValue; import org.lflang.ast.ASTUtils; @@ -36,7 +35,7 @@ public CoordinationOptions fromAst(Element node, MessageReporter reporter) { for (KeyValuePair entry : node.getKeyvalue().getPairs()) { CoordinationOption option = (CoordinationOption) DictionaryType.COORDINATION_OPTION_DICT.forName(entry.getName()); - if (Objects.requireNonNull(option) == CoordinationOption.ADVANCE_MESSAGE_INTERVAL) { + if (option != null && option.equals(CoordinationOption.ADVANCE_MESSAGE_INTERVAL)) { options.advanceMessageInterval = ASTUtils.toTimeValue(entry.getValue()); } } diff --git a/core/src/main/java/org/lflang/target/property/DockerProperty.java b/core/src/main/java/org/lflang/target/property/DockerProperty.java index fc72cf245c..369fcac9a9 100644 --- a/core/src/main/java/org/lflang/target/property/DockerProperty.java +++ b/core/src/main/java/org/lflang/target/property/DockerProperty.java @@ -1,6 +1,5 @@ package org.lflang.target.property; -import java.util.Objects; import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.lf.Element; @@ -39,11 +38,12 @@ public DockerOptions fromAst(Element node, MessageReporter reporter) { if (ASTUtils.toBoolean(node)) { options.enabled = true; } - } else { + } else if (node.getKeyvalue() != null) { + + options.enabled = true; for (KeyValuePair entry : node.getKeyvalue().getPairs()) { - options.enabled = true; DockerOption option = (DockerOption) DictionaryType.DOCKER_DICT.forName(entry.getName()); - if (Objects.requireNonNull(option) == DockerOption.FROM) { + if (option != null && option.equals(DockerOption.FROM)) { options.from = ASTUtils.elementToSingleString(entry.getValue()); } } diff --git a/core/src/main/java/org/lflang/target/property/FastProperty.java b/core/src/main/java/org/lflang/target/property/FastProperty.java index d2509b81c3..e978bf0aa8 100644 --- a/core/src/main/java/org/lflang/target/property/FastProperty.java +++ b/core/src/main/java/org/lflang/target/property/FastProperty.java @@ -4,11 +4,10 @@ import org.lflang.ast.ASTUtils; import org.lflang.lf.Action; import org.lflang.lf.ActionOrigin; -import org.lflang.lf.KeyValuePair; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; import org.lflang.lf.Reactor; import org.lflang.target.Target; +import org.lflang.target.TargetConfig; /** * If true, configure the execution environment such that it does not wait for physical time to @@ -29,35 +28,28 @@ public String name() { } @Override - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) { - if (pair != null) { - // Check for federated - for (Reactor reactor : ast.getReactors()) { - // Check to see if the program has a federated reactor - if (reactor.isFederated()) { - reporter - .at(pair, Literals.KEY_VALUE_PAIR__NAME) - .error("The fast target property is incompatible with federated programs."); - break; - } - } + public void validate(TargetConfig config, MessageReporter reporter) { + var pair = config.lookup(this); + if (config.isSet(this) && config.isFederated()) { + reporter + .at(pair, Literals.KEY_VALUE_PAIR__NAME) + .error("The fast target property is incompatible with federated programs."); + } - final var target = ASTUtils.getTarget(ast); - if (target != Target.CPP) { - // Check for physical actions - for (Reactor reactor : ast.getReactors()) { - // Check to see if the program has a physical action in a reactor - for (Action action : reactor.getActions()) { - if (action.getOrigin().equals(ActionOrigin.PHYSICAL)) { - reporter - .at(pair, Literals.KEY_VALUE_PAIR__NAME) - .error( - String.format( - "In the %s target, the fast target property is incompatible with physical" - + " actions.", - target.toString())); - break; - } + if (config.target != Target.CPP) { + // Check for physical actions + for (Reactor reactor : ASTUtils.getAllReactors(config.getMainResource())) { + // Check to see if the program has a physical action in a reactor + for (Action action : reactor.getActions()) { + if (action.getOrigin().equals(ActionOrigin.PHYSICAL)) { + reporter + .at(pair, Literals.KEY_VALUE_PAIR__NAME) + .error( + String.format( + "In the %s target, the fast target property is incompatible with physical" + + " actions.", + config.target.toString())); + break; } } } diff --git a/core/src/main/java/org/lflang/target/property/PlatformProperty.java b/core/src/main/java/org/lflang/target/property/PlatformProperty.java index 98867d23e2..4e5ea72503 100644 --- a/core/src/main/java/org/lflang/target/property/PlatformProperty.java +++ b/core/src/main/java/org/lflang/target/property/PlatformProperty.java @@ -7,7 +7,7 @@ import org.lflang.lf.KeyValuePairs; import org.lflang.lf.LfFactory; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; +import org.lflang.target.TargetConfig; import org.lflang.target.property.PlatformProperty.PlatformOptions; import org.lflang.target.property.type.DictionaryType; import org.lflang.target.property.type.DictionaryType.DictionaryElement; @@ -45,22 +45,22 @@ public PlatformOptions fromAst(Element node, MessageReporter reporter) { var userThreads = 0; if (node.getLiteral() != null || node.getId() != null) { platform = new PlatformType().forName(ASTUtils.elementToSingleString(node)); - } else { + } else if (node.getKeyvalue() != null) { for (KeyValuePair entry : node.getKeyvalue().getPairs()) { PlatformOption option = (PlatformOption) DictionaryType.PLATFORM_DICT.forName(entry.getName()); - if (option == null) { - continue; // FIXME: should not be necessary - } - switch (option) { - case NAME -> { - platform = new PlatformType().forName(ASTUtils.elementToSingleString(entry.getValue())); + if (option != null) { + switch (option) { + case NAME -> { + platform = + new PlatformType().forName(ASTUtils.elementToSingleString(entry.getValue())); + } + case BAUDRATE -> baudRate = ASTUtils.toInteger(entry.getValue()); + case BOARD -> board = ASTUtils.elementToSingleString(entry.getValue()); + case FLASH -> flash = ASTUtils.toBoolean(entry.getValue()); + case PORT -> port = ASTUtils.elementToSingleString(entry.getValue()); + case USER_THREADS -> userThreads = ASTUtils.toInteger(entry.getValue()); } - case BAUDRATE -> baudRate = ASTUtils.toInteger(entry.getValue()); - case BOARD -> board = ASTUtils.elementToSingleString(entry.getValue()); - case FLASH -> flash = ASTUtils.toBoolean(entry.getValue()); - case PORT -> port = ASTUtils.elementToSingleString(entry.getValue()); - case USER_THREADS -> userThreads = ASTUtils.toInteger(entry.getValue()); } } } @@ -73,13 +73,12 @@ protected PlatformOptions fromString(String string, MessageReporter reporter) { } @Override - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) { - var config = fromAst(pair.getValue(), reporter); - var threading = TargetProperty.getKeyValuePair(ast, ThreadingProperty.INSTANCE); - if (threading != null && config.platform == Platform.RP2040) { + public void validate(TargetConfig config, MessageReporter reporter) { + var singleThreaded = config.get(SingleThreadedProperty.INSTANCE); + if (!singleThreaded && config.get(PlatformProperty.INSTANCE).platform == Platform.RP2040) { reporter - .at(pair, Literals.KEY_VALUE_PAIR__VALUE) - .error("Platform " + Platform.RP2040 + " does not support threading"); + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .error("Platform " + Platform.RP2040 + " does not support threading."); } } diff --git a/core/src/main/java/org/lflang/target/property/Ros2DependenciesProperty.java b/core/src/main/java/org/lflang/target/property/Ros2DependenciesProperty.java index ec21d58027..6c3f08758a 100644 --- a/core/src/main/java/org/lflang/target/property/Ros2DependenciesProperty.java +++ b/core/src/main/java/org/lflang/target/property/Ros2DependenciesProperty.java @@ -5,9 +5,8 @@ import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.lf.Element; -import org.lflang.lf.KeyValuePair; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; +import org.lflang.target.TargetConfig; import org.lflang.target.property.type.ArrayType; /** Directive to specify additional ROS2 packages that this LF program depends on. */ @@ -36,12 +35,11 @@ protected List fromString(String string, MessageReporter reporter) { } @Override - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) { - var ros2enabled = TargetProperty.getKeyValuePair(ast, Ros2Property.INSTANCE); - if (pair != null && (ros2enabled == null || !ASTUtils.toBoolean(ros2enabled.getValue()))) { + public void validate(TargetConfig config, MessageReporter reporter) { + if (config.isSet(this) && !config.get(Ros2Property.INSTANCE)) { reporter - .at(pair, Literals.KEY_VALUE_PAIR__NAME) - .warning("Ignoring ros2-dependencies as ros2 compilation is disabled"); + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__NAME) + .warning("Ignoring ros2-dependencies as ros2 compilation is disabled."); } } diff --git a/core/src/main/java/org/lflang/target/property/SchedulerProperty.java b/core/src/main/java/org/lflang/target/property/SchedulerProperty.java index 52bc48cc0b..2abadd7f75 100644 --- a/core/src/main/java/org/lflang/target/property/SchedulerProperty.java +++ b/core/src/main/java/org/lflang/target/property/SchedulerProperty.java @@ -3,9 +3,8 @@ import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.lf.Element; -import org.lflang.lf.KeyValuePair; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; +import org.lflang.target.TargetConfig; import org.lflang.target.property.type.SchedulerType; import org.lflang.target.property.type.SchedulerType.Scheduler; @@ -50,34 +49,27 @@ public String name() { } @Override - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) { - if (pair != null) { - String schedulerName = ASTUtils.elementToSingleString(pair.getValue()); - try { - if (!Scheduler.valueOf(schedulerName).prioritizesDeadline()) { - // Check if a deadline is assigned to any reaction - // Filter reactors that contain at least one reaction that - // has a deadline handler. - if (ast.getReactors().stream() - .anyMatch( - // Filter reactors that contain at least one reaction that - // has a deadline handler. - reactor -> - ASTUtils.allReactions(reactor).stream() - .anyMatch(reaction -> reaction.getDeadline() != null))) { - reporter - .at(pair, Literals.KEY_VALUE_PAIR__VALUE) - .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."); - } - } - } catch (IllegalArgumentException e) { - // the given scheduler is invalid, but this is already checked by - // checkTargetProperties + public void validate(TargetConfig config, MessageReporter reporter) { + var scheduler = config.get(this); + if (!scheduler.prioritizesDeadline()) { + // Check if a deadline is assigned to any reaction + // Filter reactors that contain at least one reaction that + // has a deadline handler. + if (ASTUtils.getAllReactors(config.getMainResource()).stream() + .anyMatch( + // Filter reactors that contain at least one reaction that + // has a deadline handler. + reactor -> + ASTUtils.allReactions(reactor).stream() + .anyMatch(reaction -> reaction.getDeadline() != null))) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .warning( + "This program contains deadlines, but the chosen " + + scheduler + + " scheduler does not prioritize reaction execution " + + "based on deadlines. This might result in a sub-optimal " + + "scheduling."); } } } diff --git a/core/src/main/java/org/lflang/target/property/SingleThreadedProperty.java b/core/src/main/java/org/lflang/target/property/SingleThreadedProperty.java new file mode 100644 index 0000000000..6fd051261c --- /dev/null +++ b/core/src/main/java/org/lflang/target/property/SingleThreadedProperty.java @@ -0,0 +1,30 @@ +package org.lflang.target.property; + +import org.lflang.MessageReporter; +import org.lflang.lf.LfPackage.Literals; +import org.lflang.target.TargetConfig; + +/** Directive to indicate whether the runtime should use multi-threading. */ +public class SingleThreadedProperty extends BooleanProperty { + + /** Singleton target property instance. */ + public static final SingleThreadedProperty INSTANCE = new SingleThreadedProperty(); + + private SingleThreadedProperty() { + super(); + } + + @Override + public String name() { + return "single-threaded"; + } + + @Override + public void validate(TargetConfig config, MessageReporter reporter) { + if (config.isFederated() && config.get(this).equals(true)) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__VALUE) + .error("Cannot enable single-threaded mode for federated program."); + } + } +} diff --git a/core/src/main/java/org/lflang/target/property/TargetProperty.java b/core/src/main/java/org/lflang/target/property/TargetProperty.java index 1f3dc5182e..d2d630d6b3 100644 --- a/core/src/main/java/org/lflang/target/property/TargetProperty.java +++ b/core/src/main/java/org/lflang/target/property/TargetProperty.java @@ -1,13 +1,11 @@ package org.lflang.target.property; import com.google.gson.JsonElement; -import java.util.List; import java.util.Optional; import org.lflang.MessageReporter; import org.lflang.lf.Element; import org.lflang.lf.KeyValuePair; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; import org.lflang.target.TargetConfig; import org.lflang.target.property.type.TargetPropertyType; @@ -40,12 +38,14 @@ protected TargetProperty(S type) { * @param reporter The reporter to issue an error through if the given key-value pair does not * match the type required by this property. */ - public void checkType(KeyValuePair pair, MessageReporter reporter) { - if (!this.type.check(pair.getValue(), pair.getName(), reporter)) { + public boolean checkType(KeyValuePair pair, MessageReporter reporter) { + boolean isOk = this.type.check(pair.getValue(), pair.getName(), reporter); + if (!isOk) { reporter .at(pair, Literals.KEY_VALUE_PAIR__VALUE) .error("Target property '" + pair.getName() + "' is required to be " + type + "."); } + return isOk; } @Override @@ -53,20 +53,19 @@ public String toString() { return this.name(); } + /** Return the initial value to assign to this target property. */ + public abstract T initialValue(); + /** * Override this method to implement additional checks. The base implementation does nothing. * *

This method is meant to perform additional validation above and beyond checking target * support and type checking which are done automatically. * - * @param pair The key-value pair to type check. - * @param ast The root of the abstract syntax tree. + * @param config The target configuration to check against. * @param reporter A reporter for reporting errors. */ - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) {} - - /** Return the initial value to assign to this target property. */ - public abstract T initialValue(); + public void validate(TargetConfig config, MessageReporter reporter) {} /** * Given an AST node, produce a corresponding value that is assignable to this target property, or @@ -130,26 +129,22 @@ public void update(TargetConfig config, T value) { * Update the given configuration based on the given corresponding AST node. * * @param config The configuration to update. - * @param node The node to perform the update with. + * @param pair The pair that holds the value to perform the update with. * @param reporter A reporter to report issues. */ - public final void update(TargetConfig config, Element node, MessageReporter reporter) { - this.update(config, fromAst(node, reporter)); - } - - public final void update(TargetConfig config, JsonElement element, MessageReporter reporter) { - this.update(config, fromJSON(element, reporter)); + public final void update(TargetConfig config, KeyValuePair pair, MessageReporter reporter) { + this.update(config, fromAst(pair.getValue(), reporter)); } /** - * Update the given configuration based on the given corresponding AST node. + * Update the given configuration based on the given JSON element. * * @param config The configuration to update. - * @param value The node to perform the update with. + * @param element The JSON element that holds the value to perform the update with. * @param reporter A reporter to report issues. */ - public final void update(TargetConfig config, String value, MessageReporter reporter) { - this.update(config, fromString(value, reporter)); + public final void update(TargetConfig config, JsonElement element, MessageReporter reporter) { + this.update(config, fromJSON(element, reporter)); } /** @@ -167,6 +162,12 @@ public int hashCode() { return this.getClass().getName().hashCode(); } + /** + * Return a value based on the given JSON element. + * + * @param element The JSON element to produce a value from/ + * @param reporter A message reporter for reporting issues. + */ protected T fromJSON(JsonElement element, MessageReporter reporter) { T value = null; if (element.isJsonPrimitive()) { @@ -176,21 +177,4 @@ protected T fromJSON(JsonElement element, MessageReporter reporter) { } return value; } - - /** - * Retrieve a key-value pair from the given AST that matches the given target property. - * - * @param ast The AST retrieve the key-value pair from. - * @param property The target property of interest. - * @return The found key-value pair, or {@code null} if no matching pair could be found. - */ - public static KeyValuePair getKeyValuePair(Model ast, TargetProperty property) { - var targetProperties = ast.getTarget().getConfig(); - List properties = - targetProperties.getPairs().stream() - .filter(pair -> pair.getName().equals(property.name())) - .toList(); - assert properties.size() <= 1; - return properties.size() > 0 ? properties.get(0) : null; - } } diff --git a/core/src/main/java/org/lflang/target/property/ThreadingProperty.java b/core/src/main/java/org/lflang/target/property/ThreadingProperty.java deleted file mode 100644 index f953dc8f1d..0000000000 --- a/core/src/main/java/org/lflang/target/property/ThreadingProperty.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.lflang.target.property; - -/** Directive to indicate whether the runtime should use multi-threading. */ -public class ThreadingProperty extends BooleanProperty { - - /** Singleton target property instance. */ - public static final ThreadingProperty INSTANCE = new ThreadingProperty(); - - private ThreadingProperty() { - super(); - } - - @Override - public String name() { - return "threading"; - } - - @Override - public Boolean initialValue() { - return true; - } -} diff --git a/core/src/main/java/org/lflang/target/property/TracingProperty.java b/core/src/main/java/org/lflang/target/property/TracingProperty.java index a403c906a9..b47793c717 100644 --- a/core/src/main/java/org/lflang/target/property/TracingProperty.java +++ b/core/src/main/java/org/lflang/target/property/TracingProperty.java @@ -8,7 +8,6 @@ import org.lflang.lf.KeyValuePairs; import org.lflang.lf.LfFactory; import org.lflang.lf.LfPackage.Literals; -import org.lflang.lf.Model; import org.lflang.target.Target; import org.lflang.target.TargetConfig; import org.lflang.target.property.TracingProperty.TracingOptions; @@ -57,27 +56,27 @@ protected TracingOptions fromString(String string, MessageReporter reporter) { } @Override - public void validate(KeyValuePair pair, Model ast, MessageReporter reporter) { - if (pair != null && this.fromAst(pair.getValue(), reporter) != null) { - // If tracing is anything but "false" and threading is off, error. - var threading = TargetProperty.getKeyValuePair(ast, ThreadingProperty.INSTANCE); - if (threading != null) { - if (!ASTUtils.toBoolean(threading.getValue())) { - reporter - .at(pair, Literals.KEY_VALUE_PAIR__NAME) - .error("Cannot enable tracing because threading support is disabled"); - reporter - .at(threading, Literals.KEY_VALUE_PAIR__NAME) - .error("Cannot disable treading support because tracing is enabled"); - } - } - } - if (ASTUtils.getTarget(ast).equals(Target.CPP) && pair.getValue().getKeyvalue() != null) { + public void validate(TargetConfig config, MessageReporter reporter) { + var noThreads = + config.isSet(SingleThreadedProperty.INSTANCE) + && config.get(SingleThreadedProperty.INSTANCE).equals(true); + var pair = config.lookup(this); + if (config.get(this).isEnabled() && noThreads) { reporter - .at(pair, Literals.KEY_VALUE_PAIR__VALUE) - .warning( - "The C++ target only supports 'true' or 'false' and ignores additional" - + " configuration"); + .at(pair, Literals.KEY_VALUE_PAIR__NAME) + .error("Cannot enable tracing because threading support is disabled"); + reporter + .at(config.lookup(SingleThreadedProperty.INSTANCE), Literals.KEY_VALUE_PAIR__NAME) + .error("Cannot disable treading support because tracing is enabled"); + } + if (pair != null) { + if (config.target.equals(Target.CPP) && pair.getValue().getKeyvalue() != null) { + reporter + .at(pair, Literals.KEY_VALUE_PAIR__VALUE) + .warning( + "The C++ target only supports 'true' or 'false' and ignores additional" + + " configuration"); + } } } diff --git a/core/src/main/java/org/lflang/target/property/WorkersProperty.java b/core/src/main/java/org/lflang/target/property/WorkersProperty.java index defbf97740..ce0cae056c 100644 --- a/core/src/main/java/org/lflang/target/property/WorkersProperty.java +++ b/core/src/main/java/org/lflang/target/property/WorkersProperty.java @@ -3,6 +3,8 @@ import org.lflang.MessageReporter; import org.lflang.ast.ASTUtils; import org.lflang.lf.Element; +import org.lflang.lf.LfPackage.Literals; +import org.lflang.target.TargetConfig; import org.lflang.target.property.type.PrimitiveType; /** @@ -28,6 +30,17 @@ protected Integer fromString(String string, MessageReporter reporter) { return Integer.parseInt(string); // FIXME: check for exception } + @Override + public void validate(TargetConfig config, MessageReporter reporter) { + if (config.isSet(this) + && config.isSet(SingleThreadedProperty.INSTANCE) + && config.get(SingleThreadedProperty.INSTANCE).equals(true)) { + reporter + .at(config.lookup(this), Literals.KEY_VALUE_PAIR__NAME) + .error("Cannot specify workers in single-threaded mode."); + } + } + @Override protected Integer fromAst(Element node, MessageReporter reporter) { return ASTUtils.toInteger(node); diff --git a/core/src/main/java/org/lflang/util/ArduinoUtil.java b/core/src/main/java/org/lflang/util/ArduinoUtil.java index 75da247a12..00d11431be 100644 --- a/core/src/main/java/org/lflang/util/ArduinoUtil.java +++ b/core/src/main/java/org/lflang/util/ArduinoUtil.java @@ -1,8 +1,5 @@ package org.lflang.util; -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; import java.io.IOException; import java.util.List; import org.eclipse.xtext.xbase.lib.Exceptions; @@ -56,45 +53,34 @@ private LFCommand arduinoCompileCommand(FileConfig fileConfig, TargetConfig targ throws IOException { if (!checkArduinoCLIExists()) { throw new IOException("Must have arduino-cli installed to auto-compile."); - } else { - var srcGenPath = fileConfig.getSrcGenPath(); - try { - // Write to a temporary file to execute since ProcessBuilder does not like spaces and double - // quotes in its arguments. - File testScript = File.createTempFile("arduino", null); - testScript.deleteOnExit(); - if (!testScript.setExecutable(true)) { - throw new IOException("Failed to make compile script executable"); - } - var fileWriter = new FileWriter(testScript.getAbsoluteFile(), true); - BufferedWriter bufferedWriter = new BufferedWriter(fileWriter); - String board = - targetConfig.get(PlatformProperty.INSTANCE).board() != null - ? targetConfig.get(PlatformProperty.INSTANCE).board() - : "arduino:avr:leonardo"; - String isThreaded = - targetConfig.get(PlatformProperty.INSTANCE).board().contains("mbed") - ? "-DLF_THREADED" - : "-DLF_UNTHREADED"; - bufferedWriter.write( - "arduino-cli compile -b " - + board - + " --build-property " - + "compiler.c.extra_flags=\"" - + isThreaded - + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10" - + " -DINITIAL_REACT_QUEUE_SIZE=10\" --build-property compiler.cpp.extra_flags=\"" - + isThreaded - + " -DPLATFORM_ARDUINO -DINITIAL_EVENT_QUEUE_SIZE=10" - + " -DINITIAL_REACT_QUEUE_SIZE=10\" " - + srcGenPath.toString()); - bufferedWriter.close(); - return commandFactory.createCommand(testScript.getAbsolutePath(), List.of(), null); - } catch (IOException e) { - e.printStackTrace(); - throw new IOException(e); - } } + + var srcGenPath = fileConfig.getSrcGenPath(); + String board = + targetConfig.get(PlatformProperty.INSTANCE).board() != null + ? targetConfig.get(PlatformProperty.INSTANCE).board() + : "arduino:avr:leonardo"; + + String compileDefs = + (targetConfig.get(PlatformProperty.INSTANCE).board().contains("mbed") + ? "" + : "-DLF_SINGLE_THREADED") + + " -DPLATFORM_ARDUINO" + + " -DINITIAL_EVENT_QUEUE_SIZE=10" + + " -DINITIAL_REACT_QUEUE_SIZE=10"; + + return commandFactory.createCommand( + "arduino-cli", + List.of( + "compile", + "-b", + board, + "--build-property", + "compiler.c.extra_flags=" + compileDefs, + "--build-property", + "compiler.cpp.extra_flags=" + compileDefs, + srcGenPath.toString()), + null); } /** diff --git a/core/src/main/java/org/lflang/validation/LFValidator.java b/core/src/main/java/org/lflang/validation/LFValidator.java index def6bcc8de..7dfa0e557d 100644 --- a/core/src/main/java/org/lflang/validation/LFValidator.java +++ b/core/src/main/java/org/lflang/validation/LFValidator.java @@ -62,6 +62,7 @@ import org.lflang.ast.ASTUtils; import org.lflang.federated.serialization.SupportedSerializers; import org.lflang.federated.validation.FedValidator; +import org.lflang.generator.GeneratorArguments; import org.lflang.generator.NamedInstance; import org.lflang.generator.c.TypeParameterizedReactor; import org.lflang.lf.Action; @@ -1076,7 +1077,7 @@ public void checkTargetProperties(KeyValuePairs targetProperties) { if (targetProperties.eContainer() instanceof TargetDecl) { // Skip dictionaries that may be part of a target property value because type checking is done // recursively. - new TargetConfig(this.target).validate(targetProperties, this.info.model, getErrorReporter()); + new TargetConfig(targetProperties.eResource(), GeneratorArguments.none(), getErrorReporter()); } } diff --git a/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt b/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt index 060a3b1772..232618a225 100644 --- a/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt +++ b/core/src/main/kotlin/org/lflang/generator/rust/RustModel.kt @@ -40,7 +40,7 @@ import org.lflang.target.property.KeepaliveProperty import org.lflang.target.property.RuntimeVersionProperty import org.lflang.target.property.RustIncludeProperty import org.lflang.target.property.SingleFileProjectProperty -import org.lflang.target.property.ThreadingProperty +import org.lflang.target.property.SingleThreadedProperty import org.lflang.target.property.TimeOutProperty import org.lflang.target.property.WorkersProperty import java.nio.file.Path @@ -490,7 +490,7 @@ object RustModelBuilder { // default configuration for the runtime crate // enable parallel feature if asked - val parallelFeature = listOf(PARALLEL_RT_FEATURE).takeIf { targetConfig.get(ThreadingProperty.INSTANCE) } + val parallelFeature = listOf(PARALLEL_RT_FEATURE).takeIf { !targetConfig.get(SingleThreadedProperty.INSTANCE) } val spec = newCargoSpec( features = parallelFeature, @@ -516,11 +516,11 @@ object RustModelBuilder { } // enable parallel feature if asked - if (targetConfig.get(ThreadingProperty.INSTANCE)) { + if (!targetConfig.get(SingleThreadedProperty.INSTANCE)) { userSpec.features += PARALLEL_RT_FEATURE } - if (!targetConfig.get(ThreadingProperty.INSTANCE) && PARALLEL_RT_FEATURE in userSpec.features) { + if (targetConfig.get(SingleThreadedProperty.INSTANCE) && PARALLEL_RT_FEATURE in userSpec.features) { messageReporter.nowhere().warning("Threading cannot be disabled as it was enabled manually as a runtime feature.") } diff --git a/core/src/main/resources/lib/c/reactor-c b/core/src/main/resources/lib/c/reactor-c index ef153af394..b19fff3339 160000 --- a/core/src/main/resources/lib/c/reactor-c +++ b/core/src/main/resources/lib/c/reactor-c @@ -1 +1 @@ -Subproject commit ef153af39474fca30ead6f9a36923616505e19a5 +Subproject commit b19fff33391b19a01fa45c37d425d76f9f6ea21b diff --git a/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java b/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java index a29655e4de..0897551b5f 100644 --- a/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java +++ b/core/src/test/java/org/lflang/tests/compiler/LinguaFrancaValidationTest.java @@ -1510,7 +1510,8 @@ private Model createModel(Target target, TargetProperty property, String value) public Collection checkTargetProperties() throws Exception { List result = new ArrayList<>(); - for (TargetProperty property : (new TargetConfig(Target.C)).getRegisteredProperties()) { + for (TargetProperty property : + TargetConfig.getMockInstance(Target.C).getRegisteredProperties()) { if (property instanceof CargoDependenciesProperty) { // we test that separately as it has better error messages continue; @@ -1547,6 +1548,7 @@ public Collection checkTargetProperties() throws Exception { "Property %s (%s) - known bad assignment: %s" .formatted(property.name(), type, it), () -> { + var issues = validator.validate(createModel(Target.C, property, it)); validator.assertError( createModel(Target.C, property, it), LfPackage.eINSTANCE.getKeyValuePair(), @@ -2301,4 +2303,19 @@ public void testUnspecifiedTransitionType() throws Exception { + "Reset and history transitions have different effects on this target mode. " + "Currently, a reset type is implicitly assumed."); } + + @Test + public void testMutuallyExclusiveThreadingParams() throws Exception { + String testCase = + """ + target C { single-threaded: true, workers: 1 } + main reactor {} + """; + + validator.assertError( + parseWithoutError(testCase), + LfPackage.eINSTANCE.getKeyValuePair(), + null, + "Cannot specify workers in single-threaded mode."); + } } diff --git a/core/src/testFixtures/java/org/lflang/tests/Configurators.java b/core/src/testFixtures/java/org/lflang/tests/Configurators.java index 5576f33dba..aed02e9d31 100644 --- a/core/src/testFixtures/java/org/lflang/tests/Configurators.java +++ b/core/src/testFixtures/java/org/lflang/tests/Configurators.java @@ -28,7 +28,7 @@ import org.lflang.target.property.LoggingProperty; import org.lflang.target.property.PlatformProperty; import org.lflang.target.property.PlatformProperty.PlatformOptions; -import org.lflang.target.property.ThreadingProperty; +import org.lflang.target.property.SingleThreadedProperty; import org.lflang.target.property.WorkersProperty; import org.lflang.target.property.type.LoggingType.LogLevel; import org.lflang.target.property.type.PlatformType.Platform; @@ -63,7 +63,7 @@ public interface Configurator { * @return True if successful, false otherwise. */ public static boolean disableThreading(TargetConfig config) { - ThreadingProperty.INSTANCE.override(config, false); + SingleThreadedProperty.INSTANCE.override(config, true); WorkersProperty.INSTANCE.override(config, 1); return true; } @@ -84,7 +84,6 @@ public static boolean makeZephyrCompatibleUnthreaded(TargetConfig config) { platform.baudRate(), false, platform.userThreads())); - return true; } diff --git a/core/src/testFixtures/java/org/lflang/tests/TestBase.java b/core/src/testFixtures/java/org/lflang/tests/TestBase.java index b2d5475efa..8f8d8f0021 100644 --- a/core/src/testFixtures/java/org/lflang/tests/TestBase.java +++ b/core/src/testFixtures/java/org/lflang/tests/TestBase.java @@ -148,7 +148,7 @@ public static class Message { public static final String DESC_ZEPHYR = "Running Zephyr tests."; public static final String DESC_AS_CCPP = "Running C tests as CCpp."; public static final String DESC_SINGLE_THREADED = - "Run non-concurrent and non-federated tests with threading = off."; + "Run non-concurrent and non-federated tests with in single-threaded mode."; public static final String DESC_SCHED_SWAPPING = "Running with non-default runtime scheduler "; public static final String DESC_ROS2 = "Running tests using ROS2."; public static final String DESC_MODAL = "Run modal reactor tests."; diff --git a/test/C/src/DeadlineInherited.lf b/test/C/src/DeadlineInherited.lf index 785087b18b..28d4205b3c 100644 --- a/test/C/src/DeadlineInherited.lf +++ b/test/C/src/DeadlineInherited.lf @@ -1,7 +1,7 @@ // Test to verify that deadline priority are inherited target C { timeout: 1 sec, - threading: false + single-threaded: true } preamble {= diff --git a/test/C/src/DeadlinePriority.lf b/test/C/src/DeadlinePriority.lf index 95db38a259..46ed5e1281 100644 --- a/test/C/src/DeadlinePriority.lf +++ b/test/C/src/DeadlinePriority.lf @@ -1,7 +1,7 @@ // Test to verify that deadline gives priority target C { timeout: 1 sec, - threading: false + single-threaded: true } preamble {= diff --git a/test/C/src/DeadlineWithAfterDelay.lf b/test/C/src/DeadlineWithAfterDelay.lf index 12bd80425b..b62407ba74 100644 --- a/test/C/src/DeadlineWithAfterDelay.lf +++ b/test/C/src/DeadlineWithAfterDelay.lf @@ -1,7 +1,7 @@ // Test to verify that deadline priority are inherited when using after delays target C { timeout: 1 sec, - threading: false + single-threaded: true } preamble {= diff --git a/test/C/src/DeadlineWithBanks.lf b/test/C/src/DeadlineWithBanks.lf index 7d8c06b8fd..01a764f317 100644 --- a/test/C/src/DeadlineWithBanks.lf +++ b/test/C/src/DeadlineWithBanks.lf @@ -3,7 +3,7 @@ * downstream reactions. A global variable is used to check execution order. Threading is disabled */ target C { - threading: false, + single-threaded: true, timeout: 300 msec, build-type: Debug } diff --git a/test/C/src/arduino/BlinkAttemptThreading.lf b/test/C/src/arduino/BlinkAttemptThreading.lf index fb08214d26..3cb37ec280 100644 --- a/test/C/src/arduino/BlinkAttemptThreading.lf +++ b/test/C/src/arduino/BlinkAttemptThreading.lf @@ -7,8 +7,7 @@ target C { platform: { name: "arduino", board: "arduino:avr:mega" - }, - threading: true + } } main reactor BlinkAttemptThreading { diff --git a/test/C/src/zephyr/threaded/UserThreads.lf b/test/C/src/zephyr/threaded/UserThreads.lf index 36f6e5b27a..ca515b5d6f 100644 --- a/test/C/src/zephyr/threaded/UserThreads.lf +++ b/test/C/src/zephyr/threaded/UserThreads.lf @@ -5,7 +5,6 @@ target C { name: Zephyr, user-threads: 3 }, - threading: true, workers: 2 }