# GRust: Bridging Modeling and Rust Development for Software-Defined Vehicles

Émilie Thomé, Matthieu Massif

<figure><img src="image_1.png" /></figure>

## To cite this version:

Émilie Thomé, Matthieu Massif. GRust: Bridging Modeling and Rust Development for Software-Defined Vehicles. 13th European Congress of Embedded Real Time Systems (ERTS), Feb 2026, Toulouse, France. 10.82331/ERTS.2026.51. hal-05513956

HAL Id: hal-05513956

https://hal.science/hal-05513956v1

Submitted on 16 Feb 2026

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_2.png" /></figure>

Distributed under a Creative Commons CC BY 4.0 - Attribution - International License
# GRUST: Bridging Modeling and RUST Development for Software-Defined Vehicles

Émilie Thomé¹, and Matthieu Massif²

1,2 Ampère, Toulouse, 31100, France
emilie.thome@ampere.cars
matthieu.massif-extern@ampere.cars

1,2 ISAE-SUPAERO, Toulouse, 31055, France
emilie.thome@isae-supaero.fr
matthieu.massif@student.isae-supaero.fr

¹ LIP6, Paris, 75005, France
emilie.thome@lip6.fr

## ABSTRACT

The evolution of embedded automotive software is driven by increasing demands in driver assistance, perception, and data processing. To meet these challenges, AMPÈRE is transitioning to a Software-Defined Vehicle (SDV) architecture. This architectural change centralizes computation on powerful platforms, reducing the number of Electronic Control Units to cut system complexity, cabling, and weight. This transition challenges the traditional development process, which generates C code from models based on a periodic execution model. The efficiency of this model depends on the assumption that data changes at every execution cycle. In a vehicle, however, many signals remain constant over multiple periods. Applying a strictly periodic model in this context forces components to re-compute and re-transmit unchanged data, leading to significant waste. This redundant processing consumes valuable CPU cycles and network bandwidth, creating critical bottlenecks in the data-intensive SDV architecture. To align with SDV requirements, AMPÈRE is adopting asynchronous RUST, a language offering strong safety and cybersecurity guarantees at compile time. However, RUST's low-level control and expressiveness introduce a steep learning curve for developers coming from model-based backgrounds. To bridge this gap, we propose GRUST, a declarative Domain-Specific Language (DSL) for automotive system modeling. GRUST combines the Synchronous paradigm—familiar to control engineers—with constructs from Functional Reactive Programming (FRP). It enables explicit modeling of temporal and data dependencies as part of system behavior, rather than relying on implicit execution semantics. GRUST compiles to safe and asynchronous RUST code, well-suited to modern SDV architectures. We evaluated GRUST by reimplementing existing RUST programs from the AMPÈRE SDV codebase. Our results demonstrate significant gains in development speed and code clarity while matching the performance of the handwritten code, validating GRUST as a practical solution for high-level modeling at AMPÈRE.

**Keywords:** Domain-Specific Language, Automotive Software, Functional Reactive Programming, Synchronous Paradigm

## 1. INTRODUCTION

Advanced Driver Assistance Systems, computer vision, sensor fusion, and AI-based features are becoming central to modern vehicle functionality. These capabilities significantly increase the complexity and computational demands of in-vehicle software. To address these challenges, AMPÈRE is moving away from traditional distributed electronic architectures and adopting a centralized Software-Defined Vehicle (SDV) architecture.

In an SDV, the core functionality is implemented and continuously updated through software, running on centralized or zonal high-performance computing platforms rather than numerous specialized Electronic Control Units (ECUs). This architecture reduces the number of ECUs, thus cutting system physical complexity, cabling, and overall vehicle weight. The result is a more efficient and maintainable design, with lower manufacturing costs, reduced energy consumption, and a foundation for continuous software innovation.

However, this shift exposes limitations in legacy, periodic architectures at AMPÈRE, which we detail in Section 2.1. An experiment at AMPÈRE highlighted severe communication inefficiencies under periodic transmission and the benefits of on-change filtering of messages (see Section 2.3 and Figure 1). These observations motivate adopting an event-driven execution model rather than purely periodic scheduling.

To support event-driven execution, AMPÈRE adopted asynchronous RUST programming, leveraging its memory safety, performance, and advanced concurrency features. Despite its strengths, the low-level construction of asynchronous RUST and its unfamiliar programming model present a steep learning curve, especially for control engineers accustomed to model-based design. While some abstractions are emerging, there remains a lack of high-level modeling tools tailored to control systems and real-time data flow, which complicates the transition from traditional development environments.

<footnote>Émilie Thomé et al.. This is an open-access article distributed under the terms of the Creative Commons Attribution 4.0 License, which permits unrestricted use, distribution, and reproduction in any medium, provided the original author and source are credited.</footnote>
To address this gap, we introduce GRUST, a *Domain-Specific Language* (DSL) for modeling asynchronous, distributed reactive systems. GRUST aims to bridge the gap between the high-level component-based abstraction and the safety, performance, and concurrency features of RUST. It provides the following:

* A declarative syntax based on the *Synchronous paradigm*, which underlies industrial tools such as SIMULINK [13] and SCADE [5], as well as academic languages like LUSTRE [1, 10]. This model is familiar to control engineers and supports deterministic, step-based computation.

* Constructs inspired by *Functional Reactive Programming* (FRP) [7, 21], particularly the REACTIVEX library [17], enabling explicit modeling of asynchronous, event-driven behavior with clear data and temporal dependencies.

* A compiler that translates GRUST into safe and asynchronous RUST, well-suited for integration with modern, performance-critical, RUST-based SDV software stacks.

We present in this paper an evaluation of GRUST: we have implemented several programs from AMPÈRE's existing codebase to compare development productivity and runtime performance. Early results suggest that GRUST improves clarity and reduces development effort without compromising execution efficiency, thus demonstrating its potential as a practical and effective bridge between component-based development and RUST-based embedded systems.

This article is structured as follows: Section 2 provides the background on technologies and paradigms that are relevant to this work; Section 3 introduces the GRUST language; Section 4 describes the implementation of the GRUST compiler; Section 5 presents experimental results; and Section 6 concludes and outlines future work.

## 2. BACKGROUND AND RELATED WORK

This section details the industrial and academic context for our work. We discuss the shift to SDV architectures, the rationale for adopting RUST, and the existing landscape of modeling paradigms and tools for reactive systems.

### 2.1. Challenges of Traditional Development

A common approach in the automotive industry is to model systems with tools like SIMULINK and STATEFLOW, which automatically generate standard-compliant C code. These models predominantly rely on a periodic execution model. The execution period must be chosen to satisfy the worst-case latency requirement, ensuring that the system can react to any input change within a specified time.

While effective, this approach presents challenges in systems where data changes infrequently. Because the schedule is fixed, components are executed in every cycle regardless of whether their inputs have changed. This leads to redundant computations and data transfers, wasting CPU cycles and overloading communication buses. Consequently, the generated C code requires manual optimization to meet timing and resource constraints, compromising both maintainability and scalability.

### 2.2. The Interest in RUST

To strengthen the requirements of SDV architectures, there is growing interest in general-purpose programming languages that combine strong memory safety, concurrency control, and low-level performance. AMPÈRE, along with other automotive industry players [9, 20], is adopting the RUST program-

ming language. This choice represents a strategic shift away from traditional C code. While performant, C provides no memory safety guarantees. Ensuring the correctness of C code requires extensive verification through tool qualification, static analysis, or formal proofs.

In contrast, RUST provides safety by construction. Its compiler enforces memory safety at compile time through a powerful system of ownership, borrowing, and lifetimes. The ownership model ensures that every value has a unique owner, preventing memory leaks and premature deallocations. The borrow checker statically guarantees that references never outlive the data they point to and enforces a key rule: data can have either multiple immutable references or one mutable reference, but not both. This rule eliminates entire classes of bugs—such as memory corruption, buffer overflows, and data races—without the runtime overhead of a garbage collector.

For low-level operations that are inherently unsafe, such as inner operations of a mutex, RUST provides the unsafe keyword. This feature allows programmers to bypass certain compiler checks in a delimited block, explicitly marking where the responsibility for upholding safety invariants falls on them. The RUSTBELT project [11] provides a formal model of the RUST safety rules. This enables the formal verification of fundamental libraries that use unsafe code, ensuring they provide a safe interface.

## 2.3. Bus Overload at AMPÈRE

The transition to SDV, relying on high-bandwidth buses like Automotive Ethernet, introduces challenges not present in previous architectures. A primary issue is the protocol overhead: the extra data required to format, address, and transmit a message, beyond the payload (the useful data). A second challenge is the shift in network topology. The SDV architecture replaces dedicated point-to-point links and deterministic CAN buses with a switched network. This consolidation of traffic onto fewer shared links introduces contention and congestion, making network latency less predictable.

The CAN bus is an extremely lightweight protocol. A CAN message contains a short payload (up to 8 bytes, 64 bytes with CAN-FD), a message identifier, and a few bits for control and error-checking. The protocol manages message priority without a central coordinator, with the non-destructive bitwise arbitration. This feature makes CAN highly efficient and deterministic for small, frequent messages.

In contrast, Automotive Ethernet relies on networking stacks like TCP/IP. This layered approach introduces a significant overhead. To send data, the system must construct a packet by encapsulating the payload within multiple layers:

* The Ethernet frame, which uses MAC addresses to deliver data between devices on the same local network segment.

* The IP packet, which uses IP addresses for routing across interconnected networks within the vehicle.

* The TCP or UDP segment, which uses port numbers to direct the data to the correct application (e.g., the infotainment system or a sensor fusion) on the destination device.

This high overhead manifested as a critical problem during an experiment at AMPÈRE.<fnref n="1" />

<footnote n="1">¹This experiment was conducted internally at AMPÈRE, and the results are cited here with permission. As the authors were not involved in its execution, further methodological details are unavailable. The data serves to provide industrial context for the on-change strategy that motivates this work.</footnote>
<figure><img src="image_3.png" /><figcaption>(a) Bus traffic with periodic transmission (exp in 2023).</figcaption></figure>

<figure><img src="image_4.png" /><figcaption>(b) Traffic reduction with on-change transmission (exp in 2025).</figcaption></figure>

<caption>Figure 1. Comparison of bus load on a prototype vehicle board at AMPÈRE.</caption>

During full-scale tests on a vehicle board prototype, engineers observed that the communication bus was overloaded. The system, following a traditional periodic model, was configured to transmit every signal at a fixed 10 ms interval. This approach, inherited from the predictable world of CAN, is proved unsustainable. A detailed analysis, summarized in Figure 1a, revealed a critical inefficiency. For some elements, as few as 3% of received messages was effectively used. The remaining 97% were redundant, and the high traffic volume led to dropped packets, consuming valuable network bandwidth and CPU cycles while causing data loss.

To address this, engineers shifted to a hybrid on-change communication paradigm. This model was implemented by applying a filtering algorithm on receiving ends, so that applications are only notified when data actually change. The model is then augmented with two temporal constraints:

* A *debounce time*, which sets a maximum notification frequency to prevent the application from being flooded by rapid signal fluctuations.

* A *refresh time*, which enforces a minimum transmission frequency, acting as a heartbeat to confirm that a component is still live even if its data has not changed.

As shown in Figure 1b, this strategy is proved highly effective. By filtering redundant data at the receiver, engineers reduced the number of events processed by the application by an average of 75%, with projections indicating a corresponding 50% reduction in CPU load from packet processing.

## 2.4. The Choice for Asynchronous RUST

The experiment demonstrated that the periodic model implemented in the AMPÈRE prototype was ill-suited for dynamic workloads, highlighting the benefit of an event-driven model to manage resources efficiently in this specific configuration.

Asynchronous programming is an attractive solution, as its event-driven model allows tasks to yield control of the processor when idle (e.g., waiting for I/O), improving overall resource management. In RUST, this is realized through the *Future* trait, a state machine representing a computation that may not be complete. An *async* function returns such a *Future*, which remains inert until polled by a runtime. When a task awaits an operation that cannot complete immediately, it yields control, allowing the runtime to execute other tasks. Once the awaited event occurs, the runtime reschedules the

original task to resume. For these reasons, AMPÈRE selected the *tokio* runtime [19]. It implements this cooperative model on a pool of OS threads and uses a work-stealing algorithm to maximize throughput by keeping all CPU busy.

However, this decision has downsides. First, asynchronous programming is known to be complex and error-prone. Reasoning about program flow becomes more difficult, and it introduces new classes of potential bugs, such as deadlocks or incorrect state management across await points. Second, the approach also creates a strong dependency on the runtime; the correctness and performance of the system hinge on the implementation of *tokio*, which uses internal unsafe code for its operation. Finally, this design is optimized for high throughput, lacking task priority and predictable, low-latency execution required for hard real-time tasks. This makes it unsuitable for the most critical (ASIL D) components, creating a fundamental tension between efficiency and predictability in a mixed-criticality system. This tension is resolved through system partitioning, which runs critical components in a dedicated real-time environment, separate from the best-effort domain of high-throughput applications.

## 2.5. Modeling Paradigms and Tools

The challenge of programming reactive systems has given rise to various high-level modeling paradigms and tools.

**Synchronous Languages and Modeling.** Synchronous languages like LUSTRE [10], and certified tools like SCADE [5], provide strong formal guarantees for critical systems. Separately, model-based environments like SIMULINK [13] are the widespread *de facto* standard for control design and simulation in industry. While these tools provide mechanisms for event-triggered logic, the periodic execution model is often favored in practice for its simplicity and predictability. This dominant, periodic approach, however, becomes inefficient in data-intensive systems where inputs change infrequently. As discussed in Section 2.1, this leads to the redundant computations and communication overhead that motivate our work.

GRUST does not aim to replace these tools but to bridge this specific gap. It combines the deterministic, locally synchronous semantics familiar to control engineers with a globally asynchronous execution model, realizing a *Globally Asynchronous, Locally Synchronous* (GALS) architecture [3].
**Functional Reactive Programming.** In FRP [7, 21], which enables high-level modeling of asynchronous data flows, research efforts have improved predictability and resource usage by incorporating techniques from synchronous languages, such as static scheduling [14, 4] and memory-safe stream processing [12]. In contrast, GRUST adopts the reverse approach: it builds upon the deterministic structure of synchronous models familiar to control engineers and augments them with reactive constructs inspired by REACTIVEX [17] to support event-driven behaviors.

**Reactive System Execution Models.** Computations in a reactive system can be triggered in two primary ways: pull-based, where a request for outputs initiates a computation, or push-based, where computations are triggered by input updates [8]. Traditional synchronous languages like LUSTRE define nodes that execute in an infinite loop, driven by a base clock. In real-time systems, these nodes are generally scheduled periodically, based on factors like Worst-Case Execution Time or deadlines. Even with advanced techniques like multi-periodic scheduling in languages such as Prelude [15] and extensions of the synchronous-reactive model [2]—which automate the management of multi-rate systems, the underlying pull-based execution often leads to redundant computations and communication overhead when data changes infrequently. While ensuring reliability and predictability, this traditional approach lacks the flexibility to adapt efficiently to dynamic workloads. Our work, in contrast, builds upon synchronous determinism while integrating reactive, event-driven constructs, thereby enabling a push-based execution.

**Existing RUST tools.** Several tools aim to simplify RUST development for embedded systems, but none fully address the abstraction gap for control modeling. Reactive RUST libraries such as `tokio` and `futures` [19, 18] provide powerful concurrency primitives, but they operate at a low abstraction level and are not intended for control system modeling. `VEECLE` [20] provides a modular runtime and high-level APIs designed for `SDV` architectures, abstracting middleware and enabling asynchronous execution. However, it focuses on runtime infrastructure rather than providing modeling abstractions familiar to control engineers. `PICTORUS` [16], in contrast, offers a modeling environment with block-diagrams that generates RUST code and supports real-time collaboration. Yet, its scheduling model remains sequential, lacking native support for event-driven behaviors.

This overview reveals a gap: existing tools either lack high-level abstractions or struggle with event-driven execution. We aim to bridge this gap with GRUST.

### 3. GRUST BY EXAMPLE

We introduce the syntax and key constructs of the GRUST language using a simplified Over-Speed Alert (OSA) service inspired by AMPÈRE's codebase. As shown in Figure 2, the OSA service monitors vehicle's speed (`speed_ms`) and raises alerts via `os-family` depending on speed thresholds: a high speed triggers `Alert::Level2`, a medium speed triggers `Alert::Level1`, and low speed results in `Alert::None`. Its implementation combines built-in operators (`on_change` and `timeout`) and user-defined components and functions

(`get_range` and `get_alert`), showcasing the core structure of a GRUST program.

```Rust
1 /// Interface with the SDV platform
2 import signal speed_ms: float;
3 export signal os_alert: Alert;
4
5 /// Speed range type
6 enum Range { Low, Mid, High }
7 /// Alert type
8 enum Alert { None, Level1, Level2 }
9 /// Over-Speed Alert service
10 service over_speedalert @ [10, 5000] {
11   let signal speed_range: Range = get_range speed_ms;
12   let event range_change: Range = on_change speed_range;
13   let event timer: unit = timeout range_change, 1000;
14   os_alert = get_alert speed_range, range_change, timer;
15 }
16
17 /// Main over-speed component
18 component get_alert (range: Range, change: Range?,
19                      timer: unit?) -> (alert: Alert) {
20   alert = when {
21     init => Alert::None,
22     // alert rising
23     timer? if last alert == Alert::None
24       && range == Range::Mid => Alert::Level1,
25     timer? if last alert == Alert::None
26       && range == Range::High => Alert::Level1,
27     timer? if last alert == Alert::Level1
28       && range == Range::High => Alert::Level2,
29     // alert decrease
30     change? if last alert == Alert::Level2
31       && range == Range::Mid => Alert::Level1,
32     change? if last alert == Alert::Level2
33       && range == Range::Low => Alert::None,
34     change? if last alert == Alert::Level1
35       && range == Range::Low => Alert::None,
36   };
37 }
38
39 /// Speed range computation
40 function get_range (speed: float) -> Range {
41   let range: Range = match speed {
42     s if s > 33.0 => Range::High,
43     s if s > 22.0 => Range::Mid,
44     _ => Range::Low,
45   };
46 }
47
48 return range;
49 }
50
```

<caption>Figure 2. Over-Speed Alert program in GRUST.</caption>

The EBNF grammar<fnref n="2" /> of GRUST, presented in Figures 3 to 5, shows that it is composed of three core elements:

**GRFRP** a functional reactive kernel for service modeling,
**GREACT** a standard library of built-in reactive operators,
**GRSYNC** a synchronous kernel for defining components.

**Service modeling with GRFRP.** Services describe, through GRFRP constructs, the processing of data flows. Data flows are of two kinds: *signals* represent continuous data, and *events* represent discrete occurrences. An *interface* with the `SDV` platform defines the flows that are *imported* and those that are *exported* by the service.

```
Interface ::= import kind ID: type; | export kind ID: type;
Service    ::= service ID @ [INT, INT] {Stmt*}
Stmt       ::= let (kind ID: type)* = FlowOp; | ID* = FlowOp;
FlowOp     ::= ID | ID (FlowOp*) | GReactOp
kind       ::= signal | event
type       ::= bool | int | float | unit
```

<caption>Figure 3. EBNF grammar of GRFRP.</caption>

In Figure 2, the `over_speed Alert` service (at Lines 9 to 14) imports the vehicle speed (Line 2) and exports the computed alert level (Line 3). Its definition includes the timing

<footnote n="2">²Terminal symbols are in purple. Repetitions of X are denoted by X*. Option of X is denoted by X?. ID are identifiers composed of ASCII characters. INT is an integer. CST are constants. DELTA is a constant representing the difference between two values, it can be an integer or a float. OP represents n-ary operators (e.g., addition).</footnote>
<table><caption>Table 1. Example chronogram for an execution of the OSA service from Figure 2. The legend for colors is: red for input changes, green for processing a buffered input, and blue for local/exported flow updates.</caption><thead><tr><th>timestamp (ms)</th><th>0</th><th>5</th><th>10</th><th>1010</th><th>2000</th><th>3000</th><th>4000</th><th>5000</th></tr></thead><tbody><tr><td>speed_ms</td><td>15.0</td><td>25.0</td><td>25.0</td><td>25.0</td><td>35.0</td><td>35.0</td><td>30.0</td><td>20.0</td></tr><tr><td>speed_range</td><td>Low</td><td>Low</td><td>Mid</td><td>Mid</td><td>High</td><td>High</td><td>Mid</td><td>Low</td></tr><tr><td>range_change</td><td></td><td></td><td>Mid</td><td></td><td>High</td><td></td><td>Mid</td><td>Low</td></tr><tr><td>timer</td><td></td><td></td><td></td><td>()</td><td></td><td>()</td><td></td><td></td></tr><tr><td>os</td><td>None</td><td>None</td><td>None</td><td>Level1</td><td>Level1</td><td>Level2</td><td>Level1</td><td>None</td></tr></tbody></table>

constraint `[10, 5000]`, which defines a hybrid temporal behavior. The minimum delay (10 ms) acts as a *debounce* time to filter rapid input fluctuations, while the maximum delay (5000 ms) acts as a *refresh* period to guarantee output liveliness. The GRUST compiler implements these bounds by generating asynchronous timers that integrate with the underlying runtime (e.g., `tokio`). This approach allows developers to specify temporal behavior declaratively.

Internally, the body of the service defines local flows such as `speed_range`, `range_change`, and `timer` (Lines 10 to 12), using a declarative style.

**Reactive operators from GREACT.** Event-driven behaviors are encoded through *flow operators*, either built-in or user-defined. The library GREACT provides operators like:

* `on_change` (Line 11), that emits an event whenever the signal `speed_range` changes;

* `timeout` (Line 12) that emits a unit value if the event `range_change` has not occurred for a specified duration (1000 milliseconds here).

The grammar of GREACT, in Figure 4, shows more operators.

<figure><img src="image_5.png" /><figcaption>Figure 4. EBNF grammar of GREACT operations.</figcaption></figure>

**Component definition with GRSYNC.** Beyond built-in operators, services may invoke user-defined flow operators called *components*, which are defined using the GRSYNC language. In Figure 2, the service uses `get_range` and `get_alert` to derive `speed_range` and `os_alert` (Lines 10 and 13). Their definitions appear in Lines 16 to 44.

<figure><img src="image_6.png" /><figcaption>Figure 5. EBNF grammar of GRSYNC.</figcaption></figure>

A component is defined as a set of synchronous equations that are evaluated at each logical instant. Those equations state the output values according to the input values. Optional inputs (like `change: Range?` and `timer: unit?`) represent the values of events, that may or may not be present.

A component may feature three kinds of equations:

* standard equations define flows using declaration or assignment (e.g., Lines 18 and 38);

* `when` equations trigger updates only on events arrivals (e.g., Line 21), with signal initialization (Line 19);

* `match` equations select computations based on pattern matching, that must be exhaustive (e.g., Line 41) to always produce a result.

The association of `when` and `match` equations enables state-machine modeling, with a transition system defined by a `when` equation, and state definitions by a `match` equation.

Stateful computations are enabled via the `last` keyword (e.g., Line 21), which accesses values from the previous step. Synchronous compilation models ensures an execution of the generated code in bounded time and bounded memory to guarantee predictable execution.

For combinatorial logic (without memory), GRUST provides *functions*, such as `get_range` (Lines 37 to 44), which statements are defined in sequential order like traditional programming functions.

**Execution example.** The behavior of the OSA service is illustrated by the execution chronogram in Table 1. We can follow the execution step by step:

* At $t = 0$, the service initializes. The input `speed_ms` has the value `15.0`, so `speed_range` is `Low` and the output `os_alert` is `None`. An output is emitted.

* At $t = 5$, `speed_ms` increases to `25.0`. Because this change occurs less than 10 ms after the last change, the input is buffered and its processing is deferred to respect the debounce time specified by `[10, 5000]`.

* At $t = 10$, the debounce time has elapsed, so the buffered input is processed. This causes `speed_range` to change to `Mid`, which in turn triggers the `on_change` operator. A `range_change` event is emitted, starting a 1000 ms timer. The alert level remains `None`. Since the value has not changed, no new output is emitted on the bus.

* At $t = 1010$, the 1000 ms timer fires, emitting a `timer` event. The `get_alert` component evaluates its logic: since `last Alert` was `None` and the current `range` is `Mid`, the alert escalates to `Level1` and is emitted.

* At $t = 2000$, `speed_ms` increases to `35.0`, causing `speed_range` to become `High`. This triggers another `range_change` event and starts a new 1000 ms timer. The alert level does not change, so no output is emitted.

* At $t = 3000$, the second timer fires. The condition in `get_alert` is met again: `last Alert` was `Level1` and the current `range` is `High`, so the alert escalates to `Level2` and is emitted.
* At $t = 4000$, speed_ms drops to 30.0. This causes an immediate range_change to Mid. The decrease logic in get is triggered by this change event, immediately lowering the alert to Level1, which is emitted.

* At $t = 5000$, speed_ms drops further to 20.0. The resulting range_change to Low again triggers the decrease logic, and the alert is reset to None and emitted.

This trace demonstrates the hybrid execution model of the service, combining event-driven reactions with time-based buffering to conform to its specified timing constraints.

# 4. IMPLEMENTATION

The compiler source code is publicly available in the GRUST repository: https://github.com/langrust/grust.

## 4.1. Compiler Prototype

The GRUST compiler is organized into three main stages. The first stage parses the raw token stream into an Abstract Syntax Tree (AST). The second stage performs semantic analysis on the AST. This includes type checking, causality analysis through dependency graph generation, and other correctness checks. This pass enriches the AST with semantic information, preparing it for code generation. The final stage generates code. It encodes the annotated AST into a target-agnostic Intermediate Representation (IR). This IR models high-level program semantics, representing components as state machines and services as dataflow propagators. The abstract nature of this IR simplifies the final code generation pass into idiomatic and safe asynchronous RUST.<fnref n="3" />

A key feature GRSYNC shares with LUSTRE-like languages is causality analysis, which ensures that components are productive by analyzing their data dependencies. The compiler builds a dependency graph where nodes represent identifiers and edges represent dataflows. A cycle in this graph indicates a logical deadlock, where a value depends on itself within the same logical instant. A component is defined as causal if and only if its dependency graph is acyclic. The compiler detects such cycles using a topological sorting algorithm.

The normalization pass simplifies the AST by rewriting when equations into equivalent match equations. This process reduces the language to a smaller, canonical subset, which simplifies subsequent compiler passes. Since RUST provides native match expressions, this normalized form maps directly to the target language, requiring no further transformation.

GRFRP services are compiled into asynchronous RUST code that awaits input updates, processes them to compute outputs, and manages internal state. As shown in Table 1, services implement a push-based execution model: changes to inputs trigger a propagation of updates through the dataflow graph, ultimately producing new outputs. For each service, the compiler constructs a dependency graph that dictates the propagation logic, allowing the compiler to determine the precise sequence of operations to execute in response to an input change. This event-driven approach minimizes CPU usage during idle periods, though frequent input changes can introduce scheduling overhead.

## 4.2. Procedural Macro

The GRUST compiler prototype is implemented as a RUST procedural macro. This architectural choice favors usability

and integration with the RUST ecosystem over the traditional approach of a standalone executable.

A procedural macro is a function that operates on token streams during compilation. It is executed after parsing but before key static analyses like type and borrow checking. The macro consumes the token stream of its invocation, processes it, and outputs a new stream of tokens that constitutes RUST code. This output is then seamlessly integrated back into the program, allowing GRUST code to be compiled with standard tools like cargo build.

### Extensibility and Interoperability
Extensibility is a common challenge for DSLs, as developers often need to call external functions, such as mathematical libraries or specialized algorithms. GRUST makes it possible to import RUST functions directly. For DSLs that compile to C, integrating external libraries creates a safety gap, as the external code is not subject to the same verification. In contrast, by leveraging the RUST ecosystem, developers can use safe external crates with high confidence, knowing that the memory safety guarantees are preserved across the entire system by construction. There is for the moment no additional check on imported functions.

## 4.3. Usability and Debugging

A critical aspect of usability for any DSL is the debugging experience. Code generated from high-level models is generally complex and not intended for direct human inspection. Furthermore, because the macro generates a token stream that is never materialized into an editable file, it is impossible to manually insert debug prints. To bridge this gap, the prototype compiler of GRUST incorporates a built-in debugging statement: `log x;`. This statement instruments the generated code to print the value of an identifier `x` at each execution step, providing a simple yet effective form of trace-based debugging. This allows developers to inspect dataflows and internal states directly at the source level of the GRUST model.

## 4.4. Safety

The code generated by GRUST is asynchronous and relies on an external runtime for execution. The current prototype relies on the timer facilities of tokio [19], creating a dependency on its runtime. Consequently, compiled GRUST programs must be executed in a tokio-based environment. While tokio uses internal unsafe code for performance, the code generated by GRUST consists entirely of safe RUST. This design significantly strengthens the assurance that GRUST programs are free from memory corruption, buffer overflows, and deadlocks. This dependency is a known limitation. Future work will focus on abstracting the runtime interface to make the compiler backend-agnostic, thus enabling portability to other execution environments.

# 5. EVALUATION

As said in Section 2.4, AMPÈRE is adopting asynchronous RUST for certain applications. Our evaluation targets one such system: the *Display Compute Service (dispute)*, that is responsible for computing information for the cockpit display. Because handwriting asynchronous RUST code is complex and error-prone, GRUST aims to automate this process. To validate our approach, we must demonstrate that the code generated for the HMI system is as performant as a manually optimized, handwritten implementation.

<footnote n="3">³However, we rely on libraries (e.g., tokio) that uses unsafe code.</footnote>
## 5.1. Implemented programs

We reimplemented three programs from the *dispute* service within AMPÈRE's RUST SDV codebase. They cover a range of modeling paradigms—event-driven, purely periodic, and mixed behaviors—found in real-world automotive use cases.

The *over-speed alert*, an implementation from which the program in Figure 2 is derived, raises a warning when the vehicle speed remains in a specific range for a defined duration. Its behavior is well-suited to event-driven modeling, highlighting how GRUST concisely expresses temporal conditions with asynchronous triggers.

The *black-ice alert* monitors external temperature to detect potential black-ice conditions. This program is implemented as a purely periodic system, where computation is triggered at fixed intervals, demonstrating the support in GRUST for traditional, time-triggered control logic. Figure 6 presents a simplified GRUST version of the black-ice alert program.

The *trip mileage* tracker accumulates the total travel distance covered during the vehicle's lifetime, combining periodic sensor sampling with sporadic storage updates. A simplified trip mileage in GRUST is presented in Figure 7. As a mixed system, it requires both periodic sampling and event-driven responses, illustrating how GRUST integrates both paradigms.

```python
1 import signal ext_temp : float;
2 export signal black_ice Alert: BlackIce;
3 export signal filtered_temp : float;
4 /// Black-Ice Alert type
5 enum BlackIce { NoAlert, Alert }
6 /// Black-Ice Alert service
7 service black_ice @ [20, 4000] {
8  let event clock: float = period(1000);
9  filtered_temp = filter(ext_temp, clock);
10 black_ice Alert = get Alert(filtered_temp);
11 }
2
3 /// Temperature filtering
4 /// The clock 'ck' triggers all computations.
5 component filter( raw: float, ck: float?)
6
7 let raw_scan: float = when { init => 0., ck? => raw };
8 let tck: int = when { init => 0, ck? => last tck + 1 };
9
10 when {
11   init =>
12     { (temp, temp_tck) = (0., 0) }
13   ck? if (raw_scan - last temp) ^2 < 1.
14     && ! (raw_scan == last temp) =>
15       { temp = raw_scan; let temp_tck: int = tck; }
16   ck? if raw_scan >= last temp + 1.
17     && tck - last temp_tck >= 1 =>
18       { temp = last temp + 1.; let temp_tck: int = tck; }
19   ck? if raw_scan <= last temp - 1.
20     && tck - last temp_tck >= 20 =>
21       { temp = last temp - 1.; let temp_tck: int = tck; }
22   ck? if ! (raw_scan == last temp)
23     && last temp == 0. =>
24     { temp = raw_scan; }
25 }
3
4 /// Logic of tke black-ice alert
5 function get Alert(temp: float) => BlackIce {
6   let alert: BlackIce = if -3. <= temp && temp <= 3.1
7     then BlackIce::Alert else BlackIce::NoAlert;
8   return alert;
9 }
```

<table><caption>Figure 6. Black-Ice Alert program in GRUST.</caption><thead><tr><td>Program</td><td>AMPÈRE</td><td>GRUST</td><td>Reduction (%)</td></tr></thead><tbody><tr><td>Over-speed</td><td>294</td><td>86</td><td>71</td></tr><tr><td>Black-ice</td><td>165</td><td>111</td><td>33</td></tr><tr><td>Trip Mileage</td><td>597</td><td>240</td><td>60</td></tr></tbody></table>

<caption>Table 2. Lines of code (excluding comments) for the original implementations at AMPÈRE (in RUST) and their GRUST reimplementations, with the relative reduction in size.</caption>

```python
1 import signal total_dist : float;
2 import event reset : unit;
3 export signal mileage : float;
4 export signal average_speed: float;
5
6 /// Trip Mileage service
7 service trip-mileage @ [20, 3000] {
8  let event dist changes: float = on_change(total_dist);
9  mileage = get-mileage(dist changes, reset);
10  let event clock: float = period(1000);
11  average_speed = get_speed(mileage, clock);
12 }
13
14 /// computes the mileage
15 component get-mileage (change: float?, reset: unit?)
16
17 when {
18  init => { (mem_dist, counter) = (0., 0);
19    (mileage, new-mileage) = (0., 0.); }
20  reset? => { mileage = 0.;
21    let mem_dist: float = last mem_dist;
22    let counter: int = last counter + 1; }
23  let dist = change? if dist >= 0. => {
24    let new-mileage: float = dist - last mem_dist;
25    match new-mileage < 10000. {
26      true => { mileage = new-mileage;
27        let mem_dist: float = last mem_dist;
28        let counter: int = last counter; }
29      false => { mileage = 0.;
30        let mem_dist: float = dist;
31        let counter: int = last counter + 1; }
32    }
33  }
34 }
35
36 /// computes the average speed
37 component get-speed (mileage: float, ck: float?)
38
39 when {
40  init => { (trip_time, speed) = (0., 0.); }
41  ck? => { let trip_time: float = last trip_time + 1.;
42    speed = mileage * 3600. / trip_time; }
43  mileage == 0. => { let trip_time: float = 0.; }
44 }
```

<caption>Figure 7. Trip Mileage program in GRUST.</caption>

We ensured functional equivalence by validating the new implementations against the original tests at AMPÈRE. As shown in Table 2, the GRUST versions reduce code size, with reductions ranging from 33% to 71%. The *over-speed alert* shows the greatest improvement, with less than a third of the original code. Notably, the original specifications reflect a periodic mindset rooted in legacy development practices. These specifications do not fully exploit the event-driven capabilities that GRUST supports. A key issue is that such specifications often dictate implementation details rather than defining behavioral requirements. For instance, the specification for the *black-ice alert* explicitly requires an implementation based on periodic temperature monitoring. This approach prescribes *how* the system should work, instead of stating *what* it must achieve. A more declarative requirement would be, for example, to filter the temperature signal to prevent changes of more than one degree per second. Prescriptive styles hinder the adoption of event-driven models.

## 5.2. Benchmarks strategy

To ensure the evaluation reflects real-world performance, both the GRUST-generated and handwritten RUST versions were deployed directly to the target hardware: a Cockpit Domain Controller (CDC). This platform uses a QNX hypervisor to isolate a proprietary, Android-based guest OS for HMI applications from safety-critical vehicle functions.

We generated inputs from a 15 minute driving scenario. This scenario simulates various driving phases—urban, rural, highway, and parking—to cover a wide range of operational conditions. The *dispute* service receives the vehicle speed (updated every 20 ms), the raw external temperature (updated every 20 ms).
ery 1000 ms), and the total accumulated distance (updated ev-
ery 300 ms). In response, it computes several outputs, includ-
ing: the over-speed alert, the black-ice alert, the filtered exter-
nal temperature, the mileage, and other outputs not activated
by this particular trace. From this scenario, we generated a
trace following the *on-change* strategy, where an input is sent
only when its value changes.

To quantify performance and resource consumption, we mea-
sured several key metrics for both the original *dispute* and its
reimplemented version, denoted by *disgrust*, using an internal
AMPÈRE data collection tool.<fnref n="4" />

**Log Volume:** We measured the total number of log messages to assess the communication load.

**CPU Load:** This metric measures the percentage of time the CPU is busy. In a real-time system, maintaining a low CPU load is essential to ensure that all tasks meet their deadlines and to accommodate future functionalities.

**Memory:** We measured the Resident Set Size (RSS), which is the total memory a process occupies in RAM, and the Proportional Set Size (PSS), which provides a more accurate representation of the memory overhead of a process by accounting for shared libraries. Minimizing memory footprint is crucial on embedded systems, where RAM is a scarce and costly resource.

**Page Jumps:** A page jump occurs when a program accesses a memory page that is not currently in RAM. Handling these jumps requires the operating system to load data from storage, introducing significant and unpredictable latency. For real-time systems, minimizing page jumps is critical for ensuring deterministic performance and preventing missed deadlines.

**Functional Correctness:** We compared the input and output logs, ensuring that for every input, the generated code produced a similar output as the original implementation.

5.3. Results

We now compare the performance of our GRUST implemen-
tation, *disgrust*, against the original *dispute*.

**Log Volume.** The number of log messages for each flow is detailed in Table 3, revealing behavioral differences between the two implementations.

<table><caption>Table 3. Number of logs for each execution and each flow. (1) Vehicle speed (2) Raw external temperature (3) Total accumulated distance (4) Over-speed alert (5) Black-ice alert (6) Filtered external temperature (7) Mileage</caption>
  <thead>
    <tr>
      <th>Input/output flows</th>
      <th>dispute</th>
      <th>disgrust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Flow (1)</td>
      <td>15315</td>
      <td>15161</td>
    </tr>
    <tr>
      <td>Flow (2)</td>
      <td>896</td>
      <td>886</td>
    </tr>
    <tr>
      <td>Flow (3)</td>
      <td>1828</td>
      <td>1813</td>
    </tr>
    <tr>
      <td>Flow (4)</td>
      <td>1</td>
      <td>185</td>
    </tr>
    <tr>
      <td>Flow (5)</td>
      <td>899</td>
      <td>966</td>
    </tr>
    <tr>
      <td>Flow (6)</td>
      <td>898</td>
      <td>237</td>
    </tr>
    <tr>
      <td>Flow (7)</td>
      <td>3920</td>
      <td>3990</td>
    </tr>
    <tr>
      <td>Others</td>
      <td>4525</td>
      <td>4731</td>
    </tr>
    <tr>
      <td>Total</td>
      <td>28282</td>
      <td>27969</td>
    </tr>
  </tbody>
</table>

For the *filtered external temperature* (6) output, the *dispute* implementation generates a new value for nearly every input (~900 logs). In contrast, *disgrust* emits a value only when the filtered temperature actually changes, resulting in a 75% reduction in logs for this flow (from ~900 to ~230).
Conversely, the *over-speed alert* (4) output shows a significant divergence. The original *dispute* implementation emits the alert only once. The *disgrust* version, however, produces around 185 logs. This difference stems from how liveness properties are handled. In the original system, the communication middleware is responsible for ensuring the alert persists, a behavior external to the *dispute* model. In GRUST, this temporal constraint is made explicit in the model itself. The *disgrust* service was specified to re-emit the alert after a timeout to ensure liveness, thus making the intended behavior part of the verifiable model.

For other outputs, the *black-ice alert* (5) and *mileage* (7), the log counts are comparable across both versions and traces.

**CPU Load.** The CPU load measures, presented in Table 4, reveal a slight overhead of 0.1% in the *disgrust* implementation compared to the original.

The GRUST project was conducted in parallel with the development of a new RUST-based architecture at AMPÈRE. Consequently, the interface between *disgrust* and the rest of the system was implemented manually and may lack production-level optimizations. This hand-crafted integration likely contributes to the measured overhead. Future work will focus on creating a seamless interface with the AMPÈRE system, which is expected to reduce these integration costs.

Another portion of the overhead can be attributed to the explicit liveness logic of the *over-speed alert* service (declared via the annotation `@[min, max]`). In production code at AMPÈRE, liveness is handled by the communication layer, configured outside the code. Having temporal requirements in the model improves the comprehension of the system.

<table><caption>Table 4. Total CPU load, RSS, PSS, and major and minor page jumps of *dispute* and *disgrust*.</caption>
  <thead>
    <tr>
      <th></th>
      <th>dispute</th>
      <th>disgrust</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>CPU load (%)</td>
      <td>0.87</td>
      <td>0.96</td>
    </tr>
    <tr>
      <td>RSS (KiB)</td>
      <td>34751</td>
      <td>36559</td>
    </tr>
    <tr>
      <td>PSS (KiB)</td>
      <td>30693</td>
      <td>30827</td>
    </tr>
    <tr>
      <td>Major page jumps</td>
      <td>4</td>
      <td>4</td>
    </tr>
    <tr>
      <td>Minor page jumps</td>
      <td>109</td>
      <td>14</td>
    </tr>
  </tbody>
</table>

**Memory.** The memory consumption of the *dispute* and *disgrust* processes is also detailed in Table 4. We evaluate memory usage using two standard metrics: the *Resident Set Size* (RSS) and the *Proportional Set Size* (PSS).

The RSS metric measures the physical memory occupied by a process in RAM. While it provides an upper bound on the memory footprint, it can overstate the cost of a process by attributing the full size of shared libraries. The results show that the RSS of *disgrust* is approximately 1800 KiB larger (about 5%) than the RSS of *dispute*. This difference suggests that the *disgrust* implementation and its dependencies require a larger set of pages to be loaded into memory.

The PSS gives a more precise assessment of the overhead introduced by *disgrust*. PSS divides the size of each shared li-

<footnote n="4">⁴The tool automates data collection by executing commands *via* the Android Debug Bridge (adb), using the dumpsys service to gather performance statistics.</footnote>
<table><caption>(a) Summary of the relative difference (%) between *dispute* and *disgrust*.</caption><thead><tr><th rowspan="2">Input/output flows</th><th rowspan="2">Mean</th><th rowspan="2">Median</th><th colspan="3">Quantiles</th><th rowspan="2">Max</th></tr><tr><th>1/4</th><th>1/2</th><th>3/4</th></tr></thead><tbody><tr><td>Vehicle speed (±0.001)</td><td>0.005</td><td>0.004</td><td>0.000</td><td>0.004</td><td>0.007</td><td>2.000</td></tr><tr><td>Raw external temperature (±0.001)</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.005</td></tr><tr><td>Total accumulated distance (±0.001)</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.001</td><td>0.002</td></tr><tr><td>Over-speed alert (±0.001)</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td></tr><tr><td>Black-ice alert (±0.001)</td><td>0.062</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.667</td></tr><tr><td>Filtered external temperature (±0.001)</td><td>0.203</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.016</td><td>1.529</td></tr><tr><td>Mileage (±0.001)</td><td>0.007</td><td>0.000</td><td>0.000</td><td>0.000</td><td>0.000</td><td>2.000</td></tr></tbody></table>

<figure><img src="image_7.png" /><figcaption>Figure 8. Comparison of *dispute* and *disgrust* using periodic and on-change traces.</figcaption></figure>

brary by the number of processes using it. The PSS results reveal a much smaller overhead of 134 KiB. This increase corresponds to a marginal overhead (<1%) compared to the memory usage of the handwritten *dispute* implementation.

**Page Jumps.** The page jump data, presented in Table 4, provides insight into the memory access patterns of both implementations. A page jump occurs when a program accesses a memory page that is not in its working set. *Major* jumps are costly as they require loading data from disk, while *minor* jumps are less expensive but still introduce latency as the kernel maps an already-resident page.

The results show that major page jumps are rare and identical for both implementations, occurring only a handful of times. This suggests that both programs load their necessary data into memory efficiently at startup, with no significant differences in disk I/O behavior during execution.

A notable difference appears in the number of minor page jumps. *dispute* recorded over 100 minor jumps, whereas *disgrust* generated only 14. This improvement could stems from the memory allocation strategy of GRUST. The *disgrust* implementation operates exclusively on the stack, which is allocated once at startup. This design avoids the dynamic memory requests that are a common source of minor page jumps, leading to a more stable and predictable memory access pattern.

While the rate of jumps for *dispute* is low—averaging less than one every few seconds—the near-elimination of these events in *disgrust* is an advantage. For high-assurance real-time systems, minimizing OS-level interruptions, even minor ones, is a desirable property for ensuring deterministic performance.

**Functional Correctness.** To verify functional equivalence, we compared the output values generated by *dispute* and *disgrust* for identical input streams. The statistical summary of the relative differences is presented in Table 8a. The results demonstrate a high degree of functional equivalence. For most outputs, the mean and median differences are zero, indicating that both implementations produce identical values for the vast

majority of the execution. The small, non-zero discrepancies observed can be attributed to minor timing variations in the on-board logging mechanism.

Two notable anomalies warrant further explanation. The first is the consistent, non-zero difference in the *filtered external temperature* and, consequently, the *black-ice alert*. This divergence is not a functional bug but a direct result of different initial state values for the temperature filter. As illustrated in Figure 8b, the *dispute* implementation starts with a low initial temperature, while *disgrust* starts high. Both traces then converge to the same value after approximately 5 minutes. For the remainder of the execution, their outputs are nearly identical. This confirms that while their initial states differ, their dynamic filtering logic is functionally equivalent. The second anomaly is the large maximum error for the *mileage* output. This is not a persistent error but an isolated singularity, as shown in Figure 8c. Further analysis, presented in Figure 8d, reveals that this spike originates from a singularity within the original *dispute* implementation, which manifests only under the on-change trace.

Therefore, after accounting for initialization differences, minor logging artifacts, and the identified singularity, we conclude that the core logic of the *disgrust* implementation is functionally equivalent to the original *dispute* service for this scenario.

## 5.4. Analysis

The comprehensive evaluation demonstrates that GRUST successfully generates code matching the performance of handwritten, manually optimized RUST, while offering significant advantages in conciseness, maintainability, and correctness.

Performance metrics confirm that the abstractions provided by GRUST introduce no significant overhead. The slight increase in CPU load and memory footprint observed in the *disgrust* implementation could be a result of the manually implemented interface and the explicit liveness properties—a design choice that integrates temporal requirements into the model. Furthermore, the evaluation revealed a notable improvement in memory behavior. The near-elimination of minor page jumps
indicates a more predictable, stack-based memory allocation
strategy, which is highly desirable for ensuring deterministic
execution in real-time systems.

6. CONCLUSION

This work introduces GRUST, a DSL designed to bridge the gap between component-based design and asynchronous software architectures. By compiling readable models into performant asynchronous RUST code, GRUST lowers the barrier to transition from traditional synchronous design methods. Our preliminary evaluations demonstrate that this approach significantly simplifies development without sacrificing performance, validating its applicability. Initial feedback from engineers at AMPÈRE further suggests that GRUST improves model clarity and reduces the complexity of handwritten asynchronous code. This offers a clear path toward improved maintainability and safety in asynchronous software development.

Future work will first expand the evaluation of GRUST to more computationally intensive domains, such as Advanced Driver Assistance Systems (ADAS) and sensor fusion. Second, we will advance the formal verification capabilities. GRUST supports contract-based verification through GREUSOT, which generates specifications for the CREUSOT deductive verifier [6]. We plan to enhance the expressive power of GREUSOT and apply it to verify safety properties drawn from real-world industrial systems.

ACKNOWLEDGMENT

The authors would like to thank the engineers at AMPÈRE for
their technical support in setting up the experimental environ-
ment and for their assistance with the performance evaluation.

REFERENCES

[1] Timothy Bourke, Lélio Brun, Pierre-Évariste Dagand, Xavier Leroy, Marc Pouzet, and Lionel Rieg. A formally verified compiler for lustre. In *PLDI*, pages 586–601. ACM, 2017.

[2] Timothy Bourke, Vincent Bregeon, and Marc Pouzet. Scheduling and compiling rate-synchronous programs with end-to-end latency constraints. In *ECRTS*, volume 262 of *LIPIcs*, pages 1:1–1:22, 2023.

[3] Daniel M. Chapiro. *Globally-asynchronous locally-synchronous systems*. PhD thesis, Stanford University, USA, 1985.

[4] Guerric Chupin and Henrik Nilsson. Functional reactive programming, restated. In *PPDP 2019, Porto, Portugal, October 7-9, 2019*, pages 7:1–7:14. ACM, 2019.

[5] Jean-Louis Colaço, Bruno Pagano, Cédric Pasteur, and Marc Pouzet. SCADE 6: From a kahn semantics to a kahn implementation for multicore. In *FDL*, pages 5–16. IEEE, 2018.

[6] Xavier Denis, Jacques-Henri Jourdan, and Claude Marché. Creusot: A foundry for the deductive verification of rust programs. In *ICFEM*, volume 13478 of *Lecture Notes in Computer Science*, pages 90–105. Springer, 2022.

[7] Conal Elliott and Paul Hudak. Functional reactive animation. In *ICFP*, pages 263–273. ACM, 1997.

[8] Conal M. Elliott. Push-pull functional reactive programming. In *Haskell*, pages 25–36. ACM, 2009.

[9] Ferrous Systems GmbH and AdaCore. Ferrocene: A Rust compiler toolchain for safety-critical systems. https://ferrocene.dev/, 2025. Accessed: 2025-09-16.

[10] Nicolas Halbwachs, Paul Caspi, Pascal Raymond, and Daniel Pilaud. The synchronous data flow programming language LUSTRE. *Proc. IEEE*, 79(9):1305–1320, 1991.

[11] Ralf Jung, Jacques-Henri Jourdan, Robbert Krebbers, and Derek Dreyer. Rustbelt: securing the foundations of the rust programming language. *Proc. ACM Program. Lang.*, 2(POPL):66:1–66:34, 2018.

[12] Neelakantan R. Krishnaswami. Higher-order functional reactive programming without spacetime leaks. In *ACM SIGPLAN ICFP’13, Boston, MA, USA - September 25 - 27, 2013*, pages 221–232. ACM, 2013.

[13] MathWorks. Simulink. https://www.mathworks.com/products/simulink.html, 2025. Accessed: 2025-09-16.

[14] Henrik Nilsson, Antony Courtney, and John Peterson. Functional reactive programming, continued. In *Haskell*, pages 51–64. ACM, 2002.

[15] Claire Pagetti, Julien Forget, Frédéric Boniol, Mikel Cordovilla, and David Lesens. Multi-task implementation of multi-periodic synchronous programs. *Discret. Event Dyn. Syst.*, 21(3):307–338, 2011.

[16] Pictorus Inc. Pictorus: A software development platform for control systems. https://www.pictorus.us/, 2025. Accessed: 2025-09-16.

[17] ReactiveX Contributors. ReactiveX: An API for asynchronous programming with observable streams. https://reactivex.io/, 2025. Accessed: 2025-09-16.

[18] The Rust Project Developers. futures: Zero-cost asynchronous programming in Rust. https://crates.io/crates/futures, 2025. Accessed: 2025-09-16.

[19] The Tokio project developers. Tokio: An asynchronous runtime for the Rust programming language. https://tokio.rs/, 2025. Accessed: 2025-09-16.

[20] Veecle GmbH. Veecle: A framework for software-defined vehicle architectures. https://veecle.io/, 2025. Accessed: 2025-09-16.

[21] Zhanyong Wan, Walid Taha, and Paul Hudak. Real-time FRP. In *ICFP*, pages 146–156. ACM, 2001.