diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java new file mode 100644 index 0000000000..e97916d3c9 --- /dev/null +++ b/org.lflang.tests/src/org/lflang/tests/compiler/LetInferenceTests.java @@ -0,0 +1,141 @@ +package org.lflang.tests.compiler;/* Parsing unit tests. */ + +/************* + Copyright (c) 2022, The University of California at Berkeley. + + Redistribution and use in source and binary forms, with or without modification, + are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ***************/ + +import java.nio.file.Path; +import javax.inject.Inject; + +import org.eclipse.emf.common.util.TreeIterator; +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.extensions.InjectionExtension; +import org.eclipse.xtext.testing.util.ParseHelper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import org.lflang.ASTUtils; +import org.lflang.DefaultErrorReporter; +import org.lflang.FileConfig; +import org.lflang.TimeUnit; +import org.lflang.TimeValue; +import org.lflang.generator.ReactionInstance; +import org.lflang.generator.ReactorInstance; +import org.lflang.generator.c.CGenerator; +import org.lflang.lf.Instantiation; +import org.lflang.lf.LfFactory; +import org.lflang.lf.Model; +import org.lflang.lf.Reactor; +import org.lflang.tests.LFInjectorProvider; +import static org.lflang.ASTUtils.*; + +@ExtendWith(InjectionExtension.class) +@InjectWith(LFInjectorProvider.class) + +/** + * Test for getting minimum delay in reactions. + * Checking the actions and port's delay,then get the minimum reaction delay. + * @author{Wonseo Choi } + * @author{Yunsang Cho } + * @author{Marten Lohstroh } + * @author{Hokeun Kim } + */ +class LetInferenceTest { + + @Inject + ParseHelper parser; + + + @Test + public void testLet() throws Exception { + Model model = parser.parse(String.join( + System.getProperty("line.separator"), + "target C;", + "main reactor {", + " ramp = new Ramp();", + " print = new Print();", + " print2 = new Print();", + " ramp.y -> print.x after 20 msec;", + " ramp.y -> print2.x after 30 msec;", + "}", + "reactor Ramp {", + " logical action a(60 msec):int;", + " logical action b(100 msec):int;", + " input x:int;", + " output y:int;", + " output z:int;", + " reaction(startup) -> y, z, a, b{=", + " =}", + "}", + "reactor Print {", + " input x:int;", + " output z:int;", + " reaction(x) -> z {=", + " =}", + "}" + )); + + Assertions.assertNotNull(model); + ASTUtils.insertGeneratedDelays(model.eResource(), new CGenerator(new FileConfig(model.eResource(), Path.of("./a/"), true), new DefaultErrorReporter())); + Assertions.assertTrue(model.eResource().getErrors().isEmpty(), + "Encountered unexpected error while parsing: " + + model.eResource().getErrors()); + + Instantiation mainDef = null; + + TreeIterator it = model.eResource().getAllContents(); + while (it.hasNext()) { + EObject obj = it.next(); + if (!(obj instanceof Reactor)) { + continue; + } + Reactor reactor = (Reactor) obj; + if (reactor.isMain()) { + mainDef = LfFactory.eINSTANCE.createInstantiation(); + mainDef.setName(reactor.getName()); + mainDef.setReactorClass(reactor); + } + } + + ReactorInstance mainInstance = new ReactorInstance(toDefinition(mainDef.getReactorClass()), new DefaultErrorReporter()); + + for (ReactorInstance reactorInstance : mainInstance.children) { + if (reactorInstance.isGeneratedDelay()) { + for (ReactionInstance reactionInstance : reactorInstance.reactions) { + Assertions.assertEquals(reactionInstance.assignLogicalExecutionTime(), TimeValue.ZERO); + } + } else if (reactorInstance.getName().contains("ramp")) { + for (ReactionInstance reactionInstance : reactorInstance.reactions) { + Assertions.assertEquals(new TimeValue(20L, TimeUnit.MILLI), reactionInstance.assignLogicalExecutionTime()); + } + } else if (reactorInstance.getName().contains("print")) { + for (ReactionInstance reactionInstance : reactorInstance.reactions) { + Assertions.assertEquals(TimeValue.ZERO, reactionInstance.assignLogicalExecutionTime()); + } + } + } + } +} diff --git a/org.lflang/src/org/lflang/TimeValue.java b/org.lflang/src/org/lflang/TimeValue.java index 0a2c205667..95d18145e1 100644 --- a/org.lflang/src/org/lflang/TimeValue.java +++ b/org.lflang/src/org/lflang/TimeValue.java @@ -74,6 +74,24 @@ public TimeValue(long time, TimeUnit unit) { this.unit = unit; } + @Override + public boolean equals(Object t1) { + if (t1 instanceof TimeValue) { + return this.compareTo((TimeValue) t1) == 0; + } + return false; + } + + public static int compare(TimeValue t1, TimeValue t2) { + if (t1.isEarlierThan(t2)) { + return -1; + } + if (t2.isEarlierThan(t1)) { + return 1; + } + return 0; + } + private static long makeNanosecs(long time, TimeUnit unit) { if (unit == null) { return time; // == 0, see constructor. @@ -147,6 +165,11 @@ public static TimeValue max(TimeValue t1, TimeValue t2) { return t1.isEarlierThan(t2) ? t2 : t1; } + /** Return the earliest of both values. */ + public static TimeValue min(TimeValue t1, TimeValue t2) { + return t1.isEarlierThan(t2) ? t1 : t2; + } + /** * Return the sum of this duration and the one represented by b. *

diff --git a/org.lflang/src/org/lflang/generator/ReactionInstance.java b/org.lflang/src/org/lflang/generator/ReactionInstance.java index 6465a74a18..f20962d70d 100644 --- a/org.lflang/src/org/lflang/generator/ReactionInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactionInstance.java @@ -448,6 +448,57 @@ public String toString() { return getName() + " of " + parent.getFullName(); } + /** + * Determine logical execution time for each reaction during compile + * time based on immediate downstream logical delays (after delays and actions) + * and label each reaction with the minimum of all such delays. + */ + public TimeValue assignLogicalExecutionTime() { + if (this.let != null) { + return this.let; + } + + if (this.parent.isGeneratedDelay()) { + return this.let = TimeValue.ZERO; + } + + TimeValue let = null; + + // Iterate over effect and find minimum delay. + for (TriggerInstance effect : effects) { + if (effect instanceof PortInstance) { + var afters = this.parent.getParent().children.stream().filter(c -> { + if (c.isGeneratedDelay()) { + return c.inputs.get(0).getDependsOnPorts().get(0).instance + .equals((PortInstance) effect); + } + return false; + }).map(c -> c.actions.get(0).getMinDelay()) + .min(TimeValue::compare); + + if (afters.isPresent()) { + if (let == null) { + let = afters.get(); + } else { + let = TimeValue.min(afters.get(), let); + } + } + } else if (effect instanceof ActionInstance) { + var action = ((ActionInstance) effect).getMinDelay(); + if (let == null) { + let = action; + } else { + let = TimeValue.min(action, let); + } + } + } + + if (let == null) { + let = TimeValue.ZERO; + } + return this.let = let; + } + ////////////////////////////////////////////////////// //// Private variables. @@ -472,6 +523,8 @@ public String toString() { */ private List runtimeInstances; + private TimeValue let = null; + /////////////////////////////////////////////////////////// //// Inner classes diff --git a/org.lflang/src/org/lflang/generator/ReactorInstance.java b/org.lflang/src/org/lflang/generator/ReactorInstance.java index 32a4e40426..8306805567 100644 --- a/org.lflang/src/org/lflang/generator/ReactorInstance.java +++ b/org.lflang/src/org/lflang/generator/ReactorInstance.java @@ -1114,4 +1114,17 @@ private void setInitialWidth() { * Cached reaction graph containing reactions that form a causality loop. */ private ReactionInstanceGraph cachedReactionLoopGraph = null; + + /** + * Return true if this is a generated delay reactor that originates from + * an "after" delay on a connection. + * + * @return True if this is a generated delay, false otherwise. + */ + public boolean isGeneratedDelay() { + if (this.definition.getReactorClass().getName().contains(GeneratorBase.GEN_DELAY_CLASS_NAME)) { + return true; + } + return false; + } }