diff --git a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java index bcc4f0ac0a..d1ea81e5be 100644 --- a/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java +++ b/org.lflang.tests/src/org/lflang/tests/compiler/RoundTripTests.java @@ -1,5 +1,9 @@ package org.lflang.tests.compiler; +import static java.util.Collections.emptyList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; + import java.nio.file.Files; import java.nio.file.Path; @@ -36,12 +40,13 @@ public void roundTripTest() throws Exception { private void run(Path file) throws Exception { Model originalModel = LfParsingUtil.parse(file); - Assertions.assertTrue(originalModel.eResource().getErrors().isEmpty()); + assertThat(originalModel.eResource().getErrors(), equalTo(emptyList())); // TODO: Check that the output is a fixed point final int smallLineLength = 20; final String squishedTestCase = FormattingUtils.render(originalModel, smallLineLength); final Model resultingModel = LfParsingUtil.parseSourceAsIfInDirectory(file.getParent(), squishedTestCase); + assertThat(resultingModel.eResource().getErrors(), equalTo(emptyList())); Assertions.assertTrue( new IsEqual(originalModel).doSwitch(resultingModel), String.format( diff --git a/org.lflang/src/lib/rs/reactor-rs b/org.lflang/src/lib/rs/reactor-rs index bef9d10abd..98991f3c3c 160000 --- a/org.lflang/src/lib/rs/reactor-rs +++ b/org.lflang/src/lib/rs/reactor-rs @@ -1 +1 @@ -Subproject commit bef9d10abde77dc95336cba8cda18032070aba15 +Subproject commit 98991f3c3ce360cfc67511103bb58511f6faba06 diff --git a/org.lflang/src/lib/rs/runtime-version.properties b/org.lflang/src/lib/rs/runtime-version.properties index 6668771240..66b91bb698 100644 --- a/org.lflang/src/lib/rs/runtime-version.properties +++ b/org.lflang/src/lib/rs/runtime-version.properties @@ -1 +1 @@ -rs = bef9d10abde77dc95336cba8cda18032070aba15 +rs = 98991f3c3ce360cfc67511103bb58511f6faba06 diff --git a/org.lflang/src/org/lflang/generator/rust/PortEmitter.kt b/org.lflang/src/org/lflang/generator/rust/PortEmitter.kt index 9544e8c4ab..5567b7b12c 100644 --- a/org.lflang/src/org/lflang/generator/rust/PortEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/PortEmitter.kt @@ -62,8 +62,25 @@ object PortEmitter : RustEmitterBase() { val self = "&mut __self.$rustFieldName" val child = "&mut $rustChildName.$rustFieldOnChildName" - return if (isInput) "$assembler.bind_ports($self, $child)?;" - else "$assembler.bind_ports($child, $self)?;" + return if (isGeneratedAsMultiport) { + var lhsPorts = "$child.iter_mut()" + var rhsPorts = "$self.iter_mut()" + + if (isContainedInBank && !isMultiport) { + lhsPorts = "unsafe_iter_bank!($rustChildName # $rustFieldOnChildName)" + } else if (isContainedInBank && isMultiport) { + lhsPorts = "unsafe_iter_bank!($rustChildName # ($rustFieldOnChildName)+)" + } + + if (isInput) { + lhsPorts = rhsPorts.also { rhsPorts = lhsPorts } + } + + "$assembler.bind_ports_zip($lhsPorts, $rhsPorts)?;" + } else { + if (isInput) "$assembler.bind_ports($self, $child)?;" + else "$assembler.bind_ports($child, $self)?;" + } } /** @@ -91,9 +108,9 @@ object PortEmitter : RustEmitterBase() { val container: Instantiation? = container val port = PortData.from(variable as Port) - if (container?.isBank == true && port.isMultiport && isInterleaved) { + if (container?.isBank == true && port.isGeneratedAsMultiport && isInterleaved) { return "unsafe_iter_bank!(${container.name} # interleaved(${port.rustFieldName}))" - } else if (container?.isBank == true && port.isMultiport) { + } else if (container?.isBank == true && port.isGeneratedAsMultiport) { return "unsafe_iter_bank!(${container.name} # (${port.rustFieldName})+)" } else if (container?.isBank == true) { return "unsafe_iter_bank!(${container.name} # ${port.rustFieldName})" @@ -102,7 +119,7 @@ object PortEmitter : RustEmitterBase() { // todo this is missing some tests where we try to borrow several multiports from same reactor val ref = (container?.name ?: "__self") + "." + port.rustFieldName - return if (port.isMultiport) { + return if (port.isGeneratedAsMultiport) { "$ref.iter_mut()" } else { "std::iter::once(&mut $ref)" diff --git a/org.lflang/src/org/lflang/generator/rust/RustModel.kt b/org.lflang/src/org/lflang/generator/rust/RustModel.kt index 6bf21b5b3e..943bcf3f33 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustModel.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustModel.kt @@ -25,17 +25,8 @@ package org.lflang.generator.rust -import org.lflang.ASTUtils -import org.lflang.AttributeUtils -import org.lflang.ErrorReporter -import org.lflang.IDENT_REGEX -import org.lflang.InferredType -import org.lflang.TargetConfig +import org.lflang.* import org.lflang.TargetProperty.BuildType -import org.lflang.TimeUnit -import org.lflang.TimeValue -import org.lflang.allComponents -import org.lflang.camelToSnakeCase import org.lflang.generator.* import org.lflang.inBlock import org.lflang.indexInContainer @@ -44,31 +35,11 @@ import org.lflang.isBank import org.lflang.isInput import org.lflang.isLogical import org.lflang.isMultiport -import org.lflang.lf.Action -import org.lflang.lf.BuiltinTrigger -import org.lflang.lf.BuiltinTriggerRef -import org.lflang.lf.Code -import org.lflang.lf.Connection -import org.lflang.lf.Expression -import org.lflang.lf.Input -import org.lflang.lf.Instantiation -import org.lflang.lf.Literal -import org.lflang.lf.ParameterReference -import org.lflang.lf.Port -import org.lflang.lf.Reaction -import org.lflang.lf.Reactor -import org.lflang.lf.Time +import org.lflang.lf.* import org.lflang.lf.Timer -import org.lflang.lf.TypeParm -import org.lflang.lf.VarRef -import org.lflang.lf.Variable -import org.lflang.lf.WidthSpec -import org.lflang.reactor -import org.lflang.toPath -import org.lflang.toText -import org.lflang.toTimeValue import java.nio.file.Path import java.util.* +import kotlin.text.capitalize private typealias Ident = String const val PARALLEL_RT_FEATURE = "parallel-runtime" @@ -218,12 +189,18 @@ data class ChildPortReference( override val lfName: Ident, override val isInput: Boolean, override val dataType: TargetCode, - override val isMultiport: Boolean + val widthSpecMultiport: TargetCode?, + val widthSpecChild: TargetCode?, ) : PortLike() { + override val isMultiport: Boolean + get() = widthSpecMultiport != null + override val isContainedInBank: Boolean get() = widthSpecChild != null val rustFieldOnChildName: String = lfName.escapeRustIdent() /** Sync with [NestedReactorInstance.rustLocalName]. */ val rustChildName: TargetCode = childLfName.escapeRustIdent() + + val widthParamName: TargetCode = (rustFieldName + "__width").escapeRustIdent() } /** @@ -380,7 +357,10 @@ sealed class PortLike : ReactorComponent() { abstract val isInput: Boolean abstract val dataType: TargetCode + val isGeneratedAsMultiport: Boolean + get() = isMultiport || isContainedInBank abstract val isMultiport: Boolean + abstract val isContainedInBank: Boolean } /** @@ -395,6 +375,7 @@ data class PortData( val widthSpec: TargetCode?, ) : PortLike() { override val isMultiport: Boolean get() = widthSpec != null + override val isContainedInBank = false companion object { fun from(port: Port) = @@ -574,7 +555,8 @@ object RustModelBuilder { lfName = variable.name, isInput = variable is Input, dataType = container.reactor.instantiateType(formalType, it.container.typeParms), - isMultiport = variable.isMultiport + widthSpecMultiport = variable.widthSpec?.toRustExpr(), + widthSpecChild = container.widthSpec?.toRustExpr(), ) } else { components[variable.name] ?: throw UnsupportedGeneratorFeatureException( diff --git a/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt b/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt index 50b5a3c2b2..46be14327e 100644 --- a/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt +++ b/org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt @@ -28,7 +28,6 @@ import org.lflang.* import org.lflang.generator.PrependOperator import org.lflang.generator.PrependOperator.rangeTo import org.lflang.generator.TargetCode -import org.lflang.generator.UnsupportedGeneratorFeatureException /** @@ -47,6 +46,8 @@ object RustReactorEmitter : RustEmitterBase() { val typeParams = typeParamList.map { it.targetCode }.angle() val typeArgs = typeParamList.map { it.lfName }.angle() + val privateParams = reactor.extraConstructionParams; + with(reactor.names) { with(PrependOperator) { """ @@ -84,6 +85,10 @@ ${" | "..ctorParams.joinWithCommasLn { "${it.lfName.escapeRust | ) -> Self { | Self { __phantom: std::marker::PhantomData, ${ctorParams.joinWithCommas { it.lfName.escapeRustIdent() }} } | } + |} + | + |struct $privateParamStruct { +${" | "..privateParams.joinWithCommasLn { "${it.ident.escapeRustIdent()}: ${it.type}" }} |} | |//------------------------// @@ -99,7 +104,8 @@ ${" | "..otherComponents.joinWithCommasLn { it.toStructField() }} | #[inline] | fn user_assemble(__assembler: &mut $rsRuntime::assembly::ComponentCreator, | __id: $rsRuntime::ReactorId, - | __params: $paramStructName$typeArgs) -> $rsRuntime::assembly::AssemblyResult { + | __params: $paramStructName$typeArgs, + | $privateParamsVarName: $privateParamStruct) -> $rsRuntime::assembly::AssemblyResult { | let $ctorParamsDeconstructor = __params; | | let __impl = { @@ -174,9 +180,26 @@ ${" | "..otherComponents.mapNotNull { it.cleanupAction() }.jo "__ctx.with_child::<$type, _>(\"$lfName\", $params, |mut __ctx, $rustLocalName| {" } + val portRefs = this.portReferences + fun NestedReactorInstance.portWidthDecls(): List = + // if we refer to some port of the child as a bank, we need to surface its width here + portRefs.filter { it.childLfName == this.lfName && it.isGeneratedAsMultiport }.map { + val portWidthExpr = if (it.isMultiport) "${it.childLfName}.${it.rustFieldOnChildName}.len()" + else "1" // that's a single port + + // if we're in a bank, the total length is the sum + val sumExpr = + if (it.isContainedInBank) "${it.childLfName}.iter().map(|${it.childLfName}| $portWidthExpr).sum()" + else portWidthExpr + + "let ${it.widthParamName} = $sumExpr;" + } + + return buildString { for (inst in nestedInstances) { append(inst.childDeclaration()).append("\n") + inst.portWidthDecls().joinTo(this, "\n").append("\n") } append(assembleSelf).append("\n") @@ -200,9 +223,13 @@ ${" | "..otherComponents.mapNotNull { it.cleanupAction() }.jo val pattern = reactionIds.joinToString(prefix = "[", separator = ", ", postfix = "]") val debugLabelArray = debugLabels.joinToString(", ", "[", "]") + val privateParamsCtor = extraConstructionParams.joinWithCommas(prefix = "$privateParamStruct { ", postfix = " }") { + it.ident.escapeRustIdent() + } + return """ |__ctx.assemble_self( - | |cc, id| Self::user_assemble(cc, id, $ctorParamsDeconstructor), + | |cc, id| Self::user_assemble(cc, id, $ctorParamsDeconstructor, $privateParamsCtor), | // number of non-synthetic reactions | ${reactions.size}, | // reaction debug labels @@ -284,7 +311,7 @@ ${" | "..declareChildConnections()} this += "__assembler.declare_triggers($rsRuntime::assembly::TriggerId::SHUTDOWN, ${n.invokerId})?;" this += n.uses.map { trigger -> "__assembler.declare_uses(${n.invokerId}, __self.${trigger.rustFieldName}.get_id())?;" } this += n.effects.filterIsInstance().map { port -> - if (port.isMultiport) { + if (port.isGeneratedAsMultiport) { "__assembler.effects_multiport(${n.invokerId}, &__self.${port.rustFieldName})?;" } else { "__assembler.effects_port(${n.invokerId}, &__self.${port.rustFieldName})?;" @@ -337,7 +364,7 @@ ${" | "..declareChildConnections()} if (isLogical) "$rsRuntime::LogicalAction<${dataType ?: "()"}>" else "$rsRuntime::PhysicalActionRef<${dataType ?: "()"}>" is PortLike -> with(this) { - if (isMultiport) "$rsRuntime::Multiport<$dataType>" + if (isGeneratedAsMultiport) "$rsRuntime::Multiport<$dataType>" else "$rsRuntime::Port<$dataType>" } is TimerData -> "$rsRuntime::Timer" @@ -360,8 +387,8 @@ ${" | "..declareChildConnections()} } } is ChildPortReference -> { - if (isMultiport) { - throw UnsupportedGeneratorFeatureException("Multiport references from parent reactor") + if (isGeneratedAsMultiport) { + "__assembler.new_multiport::<$dataType>(\"$childLfName.$lfName\", $portKind, $privateParamsVarName.$widthParamName)?" } else { "__assembler.new_port::<$dataType>(\"$childLfName.$lfName\", $portKind)" } @@ -421,7 +448,7 @@ ${" | "..declareChildConnections()} if (isLogical) "ctx.cleanup_logical_action(&mut self.$rustFieldName);" else "ctx.cleanup_physical_action(&mut self.$rustFieldName);" is PortLike -> - if (isMultiport) "ctx.cleanup_multiport(&mut self.$rustFieldName);" + if (isGeneratedAsMultiport) "ctx.cleanup_multiport(&mut self.$rustFieldName);" else "ctx.cleanup_port(&mut self.$rustFieldName);" else -> null } @@ -457,5 +484,34 @@ ${" | "..body} } } + /** + * A list of parameters that are required for construction + * but are of internal use to the generator. + * The widths of port banks referred to by a reactor are + * saved in here, so that we use the actual runtime value. + */ + private val ReactorInfo.extraConstructionParams: List + get() { + val result = mutableListOf() + + for (ref in this.portReferences) { + if (ref.isGeneratedAsMultiport) { + result += PrivateParamSpec( + ident = ref.widthParamName, + type = "usize" + ) + } + } + + return result + } + + private data class PrivateParamSpec( + val ident: String, + val type: TargetCode + ) + + private const val privateParamStruct: String = "PrivateParams" + private const val privateParamsVarName = "__more_params" } diff --git a/test/Rust/src/multiport/ReadOutputOfContainedBank.lf b/test/Rust/src/multiport/ReadOutputOfContainedBank.lf new file mode 100644 index 0000000000..80103366db --- /dev/null +++ b/test/Rust/src/multiport/ReadOutputOfContainedBank.lf @@ -0,0 +1,48 @@ +// Test reacting to and reading outputs from a contained reactor bank in various +// permutations. +target Rust + +reactor Contained(bank_index: usize(0)) { + state bank_index(bank_index) + + output out: usize + + reaction(startup) -> out {= ctx.set(out, 42 * self.bank_index); =} +} + +main reactor { + c = new[4] Contained() + state count: usize(0) + + reaction(startup) c.out {= + for (i, chan) in c__out.iter().enumerate() { + let result = ctx.get(chan).unwrap(); + println!("Startup reaction reading output of contained reactor: {}", result); + assert_eq!(result, 42 * i); + } + self.count += 1; + =} + + reaction(c.out) {= + for (i, chan) in c__out.iter().enumerate() { + let result = ctx.get(chan).unwrap(); + println!("Reading output of contained reactor: {}", result); + assert_eq!(result, 42 * i); + } + self.count += 1; + =} + + reaction(startup, c.out) {= + for (i, chan) in c__out.iter().enumerate() { + let result = ctx.get(chan).unwrap(); + println!("Alternate triggering reading output of contained reactor: {}", result); + assert_eq!(result, 42 * i); + } + self.count += 1; + =} + + reaction(shutdown) {= + assert_eq!(self.count, 3); + println!("success"); + =} +} diff --git a/test/Rust/src/multiport/WidthWithParameter.lf b/test/Rust/src/multiport/WidthWithParameter.lf new file mode 100644 index 0000000000..e32f67bec5 --- /dev/null +++ b/test/Rust/src/multiport/WidthWithParameter.lf @@ -0,0 +1,17 @@ +target Rust + +reactor Some(value: usize(30)) { + output[value] finished: unit + + reaction(startup) -> finished {= + for p in finished { + ctx.set(p, ()); + } + =} +} + +main reactor { + some = new Some(value = 20) + + reaction(some.finished) {= println!("success"); =} +} diff --git a/test/Rust/src/multiport/WriteInputOfContainedBank.lf b/test/Rust/src/multiport/WriteInputOfContainedBank.lf new file mode 100644 index 0000000000..cab6e80e16 --- /dev/null +++ b/test/Rust/src/multiport/WriteInputOfContainedBank.lf @@ -0,0 +1,30 @@ +// Test writing inputs to a contained reactor bank +target Rust + +reactor Contained(bank_index: usize(0)) { + state bank_index(bank_index) + + input inpt: usize + state count: usize(0) + + reaction(inpt) {= + let result = ctx.get(inpt).unwrap(); + println!("Instance {} received {}", self.bank_index, result); + assert_eq!(result, self.bank_index * 42); + self.count += 1; + =} + + reaction(shutdown) {= + assert_eq!(self.count, 1, "One of the reactions failed to trigger"); + =} +} + +main reactor { + c = new[4] Contained() + + reaction(startup) -> c.inpt {= + for i in 0..c__inpt.len() { + ctx.set(&mut c__inpt[i], i * 42); + } + =} +}