Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial support for childref multiports #1228

Merged
merged 22 commits into from
Jan 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;

Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion org.lflang/src/lib/rs/reactor-rs
2 changes: 1 addition & 1 deletion org.lflang/src/lib/rs/runtime-version.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
rs = bef9d10abde77dc95336cba8cda18032070aba15
rs = 98991f3c3ce360cfc67511103bb58511f6faba06
27 changes: 22 additions & 5 deletions org.lflang/src/org/lflang/generator/rust/PortEmitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;"
}
}

/**
Expand Down Expand Up @@ -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})"
Expand All @@ -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)"
Expand Down
50 changes: 16 additions & 34 deletions org.lflang/src/org/lflang/generator/rust/RustModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down Expand Up @@ -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()
}

/**
Expand Down Expand Up @@ -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
}

/**
Expand All @@ -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) =
Expand Down Expand Up @@ -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(
Expand Down
72 changes: 64 additions & 8 deletions org.lflang/src/org/lflang/generator/rust/RustReactorEmitter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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


/**
Expand All @@ -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) {
"""
Expand Down Expand Up @@ -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}" }}
|}
|
|//------------------------//
Expand All @@ -99,7 +104,8 @@ ${" | "..otherComponents.joinWithCommasLn { it.toStructField() }}
| #[inline]
| fn user_assemble(__assembler: &mut $rsRuntime::assembly::ComponentCreator<Self>,
| __id: $rsRuntime::ReactorId,
| __params: $paramStructName$typeArgs) -> $rsRuntime::assembly::AssemblyResult<Self> {
| __params: $paramStructName$typeArgs,
| $privateParamsVarName: $privateParamStruct) -> $rsRuntime::assembly::AssemblyResult<Self> {
| let $ctorParamsDeconstructor = __params;
|
| let __impl = {
Expand Down Expand Up @@ -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<TargetCode> =
// 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")
Expand All @@ -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
Expand Down Expand Up @@ -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<PortLike>().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})?;"
Expand Down Expand Up @@ -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"
Expand All @@ -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)"
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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<PrivateParamSpec>
get() {
val result = mutableListOf<PrivateParamSpec>()

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"
}
48 changes: 48 additions & 0 deletions test/Rust/src/multiport/ReadOutputOfContainedBank.lf
Original file line number Diff line number Diff line change
@@ -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");
=}
}
Loading