|
This document is a working draft for what might become the FMI Layered Standard for WebAssembly (FMI-LS-WASM). Everything is unofficial and subject to change prior to any official release. Feedback is welcome via the GitHub repository. |
This layered standard defines a portable, binary target platform for FMI 3.0 Functional Mock-up Units (FMUs) using WebAssembly Component Model components. It specifies the WIT interface mapping from the FMI 3.0 C API, the FMU archive layout, and the runtime conventions that hosts and FMUs must follow.
1. Introduction
1.1. Intent of This Document
The Functional Mock-up Interface (FMI) standard defines a C API for exchanging simulation models.
Current FMU binaries are native shared or static libraries bound to a specific operating system and CPU architecture.
This limits portability: an FMU compiled for linux-x86_64 cannot run on win64, macOS, or embedded targets without recompilation or emulation.
Alternatively (or additionaly) FMUs can include C source code, however that leads to the usual portability problems of C source, as well as restrictions in terms of IP protection and ease of use.
Neither of those options lead to any simple ability to sandbox the execution of the FMU, as native code execution (within the same process as the host) is not easily sandboxed. While this approach can produce the best results in terms of execution speed, sometimes the concerns of portability, or security (e.g. via sandboxing of untrusted code) are of more concern.
The WebAssembly Component Model provides a portable, sandboxed binary format with a well-defined interface description language (WIT) that can be executed by any conforming WebAssembly runtime. By mapping FMI 3.0 to the Component Model, FMUs become:
-
Portable — one binary runs on any platform with a conforming WebAssembly runtime.
-
Sandboxed — the FMU can only access resources explicitly granted by the host through the Component Model’s capability model.
-
Interoperable — component toolchains in any language (Rust, C, Python, …) can produce or consume conforming FMUs.
This layered standard does not change the FMI 3.0 model description XML, the state machine, or the semantics of the simulation protocol. It specifies only the binary interface and packaging.
It does this by defining an additional binary target for FMUs, which can be used in conjunction with existing native binary targes and/or source code, or stand-alone, allowing for flexible use of the different targets within one FMU.
1.2. How to Read This Document
Readers should be familiar with:
The normative WIT files are the canonical interface definitions. Prose in this document explains the mapping rationale and conformance rules. Where prose and WIT differ, the WIT files take precedence.
1.3. Normative Language
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119.
2. Scope
This layered standard applies to FMI 3.0 compliant FMUs that are distributed as WebAssembly Component Model components. It covers:
-
The WIT interface package
fmi:fmi3@3.0.0, including all types, callback interfaces, functional interfaces, and world definitions. -
The FMU archive layout for wasm-based FMUs.
-
The conformance rules for FMU implementors and for simulation environment (importer) implementors.
The following are out of scope:
-
The FMI 3.0 model description XML format (
modelDescription.xml) — unchanged from the base standard. -
The FMI 3.0 simulation state machine — unchanged from the base standard.
-
WebAssembly runtime selection or configuration.
-
WASI interfaces beyond those explicitly referenced.
3. Overview of the WebAssembly Component Model
The WebAssembly Component Model is a specification that adds a higher-level interface layer on top of core WebAssembly modules. Its key elements relevant to this layered standard are:
- WIT (WebAssembly Interface Types)
-
A language-neutral interface description language used to define the types and functions that a component exports (provides) or imports (requires).
- Component
-
A self-describing WebAssembly binary that encodes its WIT interfaces, dependencies, and exports. A component is the binary artefact distributed inside the FMU archive.
- World
-
A WIT construct that lists the complete set of imports and exports for a component. Each FMU kind (Model Exchange, Co-Simulation, Scheduled Execution) has its own world definition.
- Resource
-
A WIT type representing an owned, opaque handle with associated methods. Each FMU instance kind is modelled as a resource, ensuring type-safe, lifecycle-managed handles.
4. FMU Archive Format
An FMI-LS-WASM compliant FMU is a standard FMI 3.0 FMU archive (a ZIP file with the .fmu extension) with the following minimal layout:
<modelName>.fmu (ZIP archive)
├── modelDescription.xml (FMI 3.0 model description — required)
├── extra/
│ └── org.modelica.fmi-ls-wasm/
│ └── manifest.xml (layered standard manifest — required)
└── binaries/
└── wasm32-wasip2/
└── <modelIdentifier>.wasm (WebAssembly component — required)
4.1. modelDescription.xml
The model description file is identical to the FMI 3.0 specification.
The <CoSimulation>, <ModelExchange>, or <ScheduledExecution> element’s modelIdentifier attribute provides the stem of the .wasm filename (i.e., <modelIdentifier>.wasm).
4.2. The WebAssembly Component
The .wasm file MUST be a valid WebAssembly Component Model component.
It MUST implement the world corresponding to its FMU type:
| FMU type | Required WIT world |
|---|---|
Model Exchange |
|
Co-Simulation |
|
Scheduled Execution |
|
The component MUST export all interfaces listed as export in its world and MUST be able to satisfy all interfaces listed as import from its host.
4.3. Platform Directory
The platform directory for wasm-based FMUs is binaries/wasm32-wasip2/.
This follows the WASI Preview 2 target triple (wasm32-wasip2) used by the Bytecode Alliance toolchain ecosystem.
An FMU MAY include both a wasm component and native binaries for other platforms in the same archive. Importers are free to prefer either native binaries or the wasm component when a conforming runtime is available.
4.4. Layered Standard Manifest
The extra/org.modelica.fmi-ls-wasm/manifest.xml file MUST be present and MUST conform to the FMI 3.0 layered standard manifest schema with:
-
fmi-ls-name="org.modelica.fmi-ls-wasm" -
fmi-ls-version= a semantic version string (e.g."0.1.0") -
fmi-ls-description="FMI Layered Standard for WebAssembly"
5. WIT Package Definition
All WIT definitions in this layered standard belong to the package:
package fmi:fmi3@3.0.0;
The package name fmi and namespace fmi3 reflect the FMI standard family.
The version 3.0.0 denotes compatibility with FMI 3.0.
5.1. Type Definitions
All shared types are defined in the types interface.
package fmi:fmi3@3.0.0;
interface types {
// ── Value reference ─────────────────────────────────────────────────────────
/// Identifies a model variable. Corresponds to fmi3ValueReference.
type value-reference = u32;
// ── Status ──────────────────────────────────────────────────────────────────
/// Return status for most FMI functions. Corresponds to fmi3Status.
enum status {
/// All OK; no further action required.
ok,
/// Non-fatal issue; result may still be used.
warning,
/// Step was discarded; do not use results (only from fmi3DoStep).
discard,
/// Unrecoverable error; instance must be freed.
error,
/// Unexpected fatal error; process should be restarted.
fatal,
}
// ── Interval qualifier ───────────────────────────────────────────────────────
/// Qualifies a clock interval returned by fmi3GetIntervalDecimal /
/// fmi3GetIntervalFraction. Corresponds to fmi3IntervalQualifier.
enum interval-qualifier {
/// Interval value is not yet determined.
interval-not-yet-known,
/// Interval is the same as the last activation.
interval-unchanged,
/// Interval has changed and the new value is provided.
interval-changed,
}
// ── Dependency kind ──────────────────────────────────────────────────────────
/// Describes how one variable depends on another.
/// Corresponds to fmi3DependencyKind.
enum dependency-kind {
/// Output depends on the input in an unspecified way.
dependent,
/// Output is a constant with respect to the input.
constant,
/// Dependency is fixed (cannot change after initialisation).
fixed,
/// Dependency is tunable (may change at event boundaries).
tunable,
/// Dependency is discrete (changes only at events).
discrete,
}
// ── Opaque FMU state ─────────────────────────────────────────────────────────
/// Serialised snapshot of internal FMU state.
/// Corresponds to the data pointed to by fmi3FMUState.
/// The host must treat the content as opaque bytes.
type fmu-state = list<u8>;
// ── Clock interval as a fraction ─────────────────────────────────────────────
/// A rational representation of a clock interval or shift:
/// value = counter / resolution (seconds)
/// Corresponds to the (fmi3UInt64 counter, fmi3UInt64 resolution) pair
/// used by fmi3GetIntervalFraction / fmi3SetIntervalFraction and
/// fmi3GetShiftFraction / fmi3SetShiftFraction.
record interval-fraction {
counter: u64,
resolution: u64,
}
// ── Variable dependency entry ────────────────────────────────────────────────
/// One entry in the variable-dependency table returned by
/// fmi3GetVariableDependencies.
record variable-dependency {
/// Index into the model-description variable list for the dependent variable.
dependent-value-reference: value-reference,
/// Index of the element within a structured variable (0-based; 0 for scalars).
element-index-of-dependent: u32,
/// Index into the model-description variable list for the independent variable.
independent-value-reference: value-reference,
/// Index of the element within the independent structured variable.
element-index-of-independent: u32,
/// Nature of the dependency.
kind: dependency-kind,
}
// ── Event info ───────────────────────────────────────────────────────────────
/// Aggregated event information returned after event iteration.
/// Corresponds to the output parameters of fmi3UpdateDiscreteStates
/// (FMI 3.0 § 2.3.5), which replaces the fmi2EventInfo struct.
record discrete-states-info {
/// Another internal event is pending; iterate again.
new-discrete-states-needed: bool,
/// No further event processing is required; proceed to continuous mode.
terminate-simulation: bool,
/// Nominals of continuous states have changed.
nominals-of-continuous-states-changed: bool,
/// Values of continuous states have changed (state re-initialisation needed).
values-of-continuous-states-changed: bool,
/// Next event time is defined.
next-event-time-defined: bool,
/// Value of the next event time (valid only when next-event-time-defined).
next-event-time: f64,
}
}
5.1.1. Type Mapping from FMI 3.0 C Types
| FMI 3.0 C type | WIT type | Notes |
|---|---|---|
|
|
Direct alias |
|
|
IEEE 754 double |
|
|
IEEE 754 single |
|
|
Signed 8-bit integer |
|
|
Signed 16-bit integer |
|
|
Signed 32-bit integer |
|
|
Signed 64-bit integer |
|
|
Unsigned 8-bit integer |
|
|
Unsigned 16-bit integer |
|
|
Unsigned 32-bit integer |
|
|
Unsigned 64-bit integer |
|
|
C |
|
|
Null-terminator elided; length-prefixed UTF-8 |
|
|
Length-prefixed blob; size array removed |
|
|
|
|
|
Serialised immediately (see Section 6.7) |
|
resource (see Section 6.1) |
One resource per interface kind |
|
|
Five variants |
|
|
Three variants |
|
|
Five variants |
Callback function pointer |
imported |
Host-supplied |
5.2. Callback Interfaces
Callbacks that the FMU calls back into the host are modelled as imported WIT interfaces.
The fmi3InstanceEnvironment opaque pointer is eliminated entirely; the Component Model’s closure semantics handle per-instance context automatically.
package fmi:fmi3@3.0.0;
interface callbacks {
use types.{status};
// ── Logging ──────────────────────────────────────────────────────────────────
/// Deliver a log message from the FMU to the master algorithm.
///
/// FMI C signature:
/// void fmi3LogMessageCallback(
/// fmi3InstanceEnvironment instanceEnvironment,
/// fmi3Status status,
/// fmi3String category,
/// fmi3String message);
///
/// Mapping notes:
/// • instanceEnvironment is implicit (handled by the component runtime).
/// • instance-name is added for diagnostics; it corresponds to the
/// instanceName passed to fmi3Instantiate*.
log-message: func(
instance-name: string,
status: status,
category: string,
message: string,
);
// ── Clock update (Scheduled Execution & Co-Simulation with event mode) ───────
/// Notify the master that one or more output clocks have ticked and that the
/// master should call fmi3GetClock to determine which ones.
///
/// FMI C signature:
/// void fmi3ClockUpdateCallback(
/// fmi3InstanceEnvironment instanceEnvironment);
clock-update: func();
// ── Scheduled Execution preemption lock / unlock ─────────────────────────────
/// Prevent preemption of the currently executing model partition.
///
/// FMI C signature:
/// void fmi3LockPreemptionCallback(void);
lock-preemption: func();
/// Re-enable preemption after a critical section.
///
/// FMI C signature:
/// void fmi3UnlockPreemptionCallback(void);
unlock-preemption: func();
}
// Callbacks required only by Co-Simulation FMUs that support
// intermediate-update mode (canReturnEarlyAfterIntermediateUpdate = true).
//
// Separated from the base `callbacks` interface so that simpler CS FMUs
// do not need to depend on it.
interface intermediate-update-callbacks {
use types.{status};
/// Notify the master of an intermediate internal state that can optionally
/// be observed or rolled back to.
///
/// FMI C signature:
/// void fmi3IntermediateUpdateCallback(
/// fmi3InstanceEnvironment instanceEnvironment,
/// fmi3Float64 intermediateUpdateTime,
/// fmi3Boolean intermediateVariableSetRequested,
/// fmi3Boolean intermediateVariableGetAllowed,
/// fmi3Boolean intermediateStepFinished,
/// fmi3Boolean canReturnEarly,
/// fmi3Boolean* earlyReturnRequested,
/// fmi3Float64* earlyReturnTime);
///
/// Mapping notes:
/// • Output parameters earlyReturnRequested and earlyReturnTime are
/// modelled as fields in the returned record rather than out-pointers.
intermediate-update: func(
intermediate-update-time: f64,
intermediate-variable-set-requested: bool,
intermediate-variable-get-allowed: bool,
intermediate-step-finished: bool,
can-return-early: bool,
) -> tuple<bool, f64>;
// ^ ^
// early-return-requested early-return-time
}
The callbacks interface provides the four callbacks used across all FMU types.
The intermediate-update-callbacks interface is separated so that simple Co-Simulation FMUs that do not support early return do not need to implement it.
5.3. Common Interface
package fmi:fmi3@3.0.0;
interface common {
// ── Version query ────────────────────────────────────────────────────────────
/// Return the FMI version string implemented by this FMU (always "3.0" for
/// FMI 3.0 compliant components).
///
/// FMI C: fmi3String fmi3GetVersion(void);
get-version: func() -> string;
}
The common interface contains only get-version, which is the one FMI 3.0 function that is not a method on an instance.
All other common operations (variable getters/setters, lifecycle, state save/restore) are methods on the typed instance resources defined in each interface kind.
5.4. Model Exchange Interface
package fmi:fmi3@3.0.0;
interface model-exchange {
use types.{
value-reference, status, fmu-state,
interval-qualifier, interval-fraction,
variable-dependency, discrete-states-info,
};
/// Result of fmi3CompletedIntegratorStep, indicating whether the master
/// should trigger event processing or discard the step.
record completed-step-result {
/// The FMU requests entering Event Mode.
enter-event-mode: bool,
/// The FMU requests step termination.
terminate-simulation: bool,
}
resource model-exchange-instance {
// ── Construction ──────────────────────────────────────────────────────────
/// Instantiate a Model Exchange FMU instance.
///
/// FMI C: fmi3Instance fmi3InstantiateModelExchange(
/// fmi3String instanceName, fmi3String instantiationToken,
/// fmi3String resourcePath, fmi3Boolean visible,
/// fmi3Boolean loggingOn,
/// fmi3InstanceEnvironment instanceEnvironment,
/// fmi3LogMessageCallback logMessage);
instantiate-model-exchange: static func(
instance-name: string,
instantiation-token: string,
resource-path: string,
visible: bool,
logging-on: bool,
) -> option<model-exchange-instance>;
// ── Debug logging ─────────────────────────────────────────────────────────
/// Enable or disable logging for specific categories.
///
/// FMI C: fmi3Status fmi3SetDebugLogging(...)
set-debug-logging: func(logging-on: bool, categories: list<string>) -> status;
// ── Lifecycle ─────────────────────────────────────────────────────────────
/// Transition to Initialisation Mode.
///
/// FMI C: fmi3Status fmi3EnterInitializationMode(...)
enter-initialization-mode: func(
tolerance: option<f64>,
start-time: f64,
stop-time: option<f64>,
) -> status;
/// Leave Initialisation Mode and enter the appropriate operating mode.
///
/// FMI C: fmi3Status fmi3ExitInitializationMode(fmi3Instance instance);
exit-initialization-mode: func() -> status;
/// Enter Event Mode.
///
/// FMI C: fmi3Status fmi3EnterEventMode(fmi3Instance instance);
enter-event-mode: func() -> status;
/// Update discrete states and check for further event iterations.
///
/// FMI C: fmi3Status fmi3UpdateDiscreteStates(...)
update-discrete-states: func() -> result<discrete-states-info, status>;
/// Terminate the simulation run (orderly shutdown).
///
/// FMI C: fmi3Status fmi3Terminate(fmi3Instance instance);
terminate: func() -> status;
/// Reset the instance to the state after fmi3Instantiate*.
///
/// FMI C: fmi3Status fmi3Reset(fmi3Instance instance);
reset: func() -> status;
// ── Configuration mode ───────────────────────────────────────────────────
/// Enter Configuration Mode to set structural parameters.
///
/// FMI C: fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance);
enter-configuration-mode: func() -> status;
/// Leave Configuration Mode.
///
/// FMI C: fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance);
exit-configuration-mode: func() -> status;
// ── Variable getters ──────────────────────────────────────────────────────
get-float32: func(value-references: list<value-reference>) -> result<list<f32>, status>;
get-float64: func(value-references: list<value-reference>) -> result<list<f64>, status>;
get-int8: func(value-references: list<value-reference>) -> result<list<s8>, status>;
get-int16: func(value-references: list<value-reference>) -> result<list<s16>, status>;
get-int32: func(value-references: list<value-reference>) -> result<list<s32>, status>;
get-int64: func(value-references: list<value-reference>) -> result<list<s64>, status>;
get-uint8: func(value-references: list<value-reference>) -> result<list<u8>, status>;
get-uint16: func(value-references: list<value-reference>) -> result<list<u16>, status>;
get-uint32: func(value-references: list<value-reference>) -> result<list<u32>, status>;
get-uint64: func(value-references: list<value-reference>) -> result<list<u64>, status>;
get-boolean: func(value-references: list<value-reference>) -> result<list<bool>, status>;
get-string: func(value-references: list<value-reference>) -> result<list<string>, status>;
get-binary: func(value-references: list<value-reference>) -> result<list<list<u8>>, status>;
get-clock: func(value-references: list<value-reference>) -> result<list<bool>, status>;
// ── Variable setters ──────────────────────────────────────────────────────
set-float32: func(value-references: list<value-reference>, values: list<f32>) -> status;
set-float64: func(value-references: list<value-reference>, values: list<f64>) -> status;
set-int8: func(value-references: list<value-reference>, values: list<s8>) -> status;
set-int16: func(value-references: list<value-reference>, values: list<s16>) -> status;
set-int32: func(value-references: list<value-reference>, values: list<s32>) -> status;
set-int64: func(value-references: list<value-reference>, values: list<s64>) -> status;
set-uint8: func(value-references: list<value-reference>, values: list<u8>) -> status;
set-uint16: func(value-references: list<value-reference>, values: list<u16>) -> status;
set-uint32: func(value-references: list<value-reference>, values: list<u32>) -> status;
set-uint64: func(value-references: list<value-reference>, values: list<u64>) -> status;
set-boolean: func(value-references: list<value-reference>, values: list<bool>) -> status;
set-string: func(value-references: list<value-reference>, values: list<string>) -> status;
set-binary: func(value-references: list<value-reference>, values: list<list<u8>>) -> status;
set-clock: func(value-references: list<value-reference>, values: list<bool>) -> status;
// ── Variable dependencies ─────────────────────────────────────────────────
get-number-of-variable-dependencies: func(vr: value-reference) -> result<u64, status>;
get-variable-dependencies: func(
dependent: value-reference,
) -> result<list<variable-dependency>, status>;
// ── FMU state (save / restore) ────────────────────────────────────────────
get-fmu-state: func() -> result<fmu-state, status>;
set-fmu-state: func(state: fmu-state) -> status;
// ── Directional derivatives ───────────────────────────────────────────────
get-directional-derivative: func(
unknowns: list<value-reference>,
knowns: list<value-reference>,
seed: list<f64>,
) -> result<list<f64>, status>;
get-adjoint-derivative: func(
unknowns: list<value-reference>,
knowns: list<value-reference>,
seed: list<f64>,
) -> result<list<f64>, status>;
// ── Clock interval / shift queries ────────────────────────────────────────
get-interval-decimal: func(
value-references: list<value-reference>,
) -> result<list<tuple<f64, interval-qualifier>>, status>;
get-interval-fraction: func(
value-references: list<value-reference>,
) -> result<list<tuple<interval-fraction, interval-qualifier>>, status>;
get-shift-decimal: func(
value-references: list<value-reference>,
) -> result<list<f64>, status>;
get-shift-fraction: func(
value-references: list<value-reference>,
) -> result<list<interval-fraction>, status>;
set-interval-decimal: func(
value-references: list<value-reference>,
intervals: list<f64>,
) -> status;
set-interval-fraction: func(
value-references: list<value-reference>,
intervals: list<interval-fraction>,
) -> status;
set-shift-decimal: func(
value-references: list<value-reference>,
shifts: list<f64>,
) -> status;
set-shift-fraction: func(
value-references: list<value-reference>,
shifts: list<interval-fraction>,
) -> status;
evaluate-discrete-states: func() -> status;
// ── Mode transitions ──────────────────────────────────────────────────────
/// Transition from Initialisation / Event Mode into Continuous-Time Mode.
///
/// FMI C: fmi3Status fmi3EnterContinuousTimeMode(fmi3Instance instance);
enter-continuous-time-mode: func() -> status;
/// Transition back into Step Mode.
///
/// FMI C: fmi3Status fmi3EnterStepMode(fmi3Instance instance);
enter-step-mode: func() -> status;
// ── Time & state setters ──────────────────────────────────────────────────
/// Inform the FMU of the current simulation time.
///
/// FMI C: fmi3Status fmi3SetTime(fmi3Instance instance, fmi3Float64 time);
set-time: func(time: f64) -> status;
/// Set the continuous state vector.
///
/// FMI C: fmi3Status fmi3SetContinuousStates(...)
set-continuous-states: func(continuous-states: list<f64>) -> status;
// ── Derivative & event-indicator getters ─────────────────────────────────
/// Retrieve the continuous-state derivatives ẋ = f(t, x, u).
///
/// FMI C: fmi3Status fmi3GetContinuousStateDerivatives(...)
get-continuous-state-derivatives: func() -> result<list<f64>, status>;
/// Retrieve zero-crossing (event-indicator) functions.
///
/// FMI C: fmi3Status fmi3GetEventIndicators(...)
get-event-indicators: func() -> result<list<f64>, status>;
/// Retrieve the current continuous state vector.
///
/// FMI C: fmi3Status fmi3GetContinuousStates(...)
get-continuous-states: func() -> result<list<f64>, status>;
/// Retrieve the nominal values of the continuous states.
///
/// FMI C: fmi3Status fmi3GetNominalsOfContinuousStates(...)
get-nominals-of-continuous-states: func() -> result<list<f64>, status>;
/// Query how many event indicators the FMU has.
///
/// FMI C: fmi3Status fmi3GetNumberOfEventIndicators(...)
get-number-of-event-indicators: func() -> result<u64, status>;
/// Query how many continuous states the FMU has.
///
/// FMI C: fmi3Status fmi3GetNumberOfContinuousStates(...)
get-number-of-continuous-states: func() -> result<u64, status>;
// ── Step completion ───────────────────────────────────────────────────────
/// Notify the FMU that the master has successfully completed an integrator
/// step and query whether event processing is needed.
///
/// FMI C: fmi3Status fmi3CompletedIntegratorStep(
/// fmi3Instance instance,
/// fmi3Boolean noSetFMUStatePriorToCurrentPoint,
/// fmi3Boolean* enterEventMode,
/// fmi3Boolean* terminateSimulation);
completed-integrator-step: func(
no-set-fmu-state-prior-to-current-point: bool,
) -> result<completed-step-result, status>;
// ── Output derivatives ────────────────────────────────────────────────────
/// Retrieve higher-order output derivatives.
///
/// FMI C: fmi3Status fmi3GetOutputDerivatives(...)
get-output-derivatives: func(
requests: list<tuple<value-reference, u32>>,
) -> result<list<f64>, status>;
}
}
5.5. Co-Simulation Interface
package fmi:fmi3@3.0.0;
interface co-simulation {
use types.{
value-reference, status, fmu-state,
interval-qualifier, interval-fraction,
variable-dependency, discrete-states-info,
};
/// Result of a fmi3DoStep call.
record do-step-result {
/// The FMU completed the step up to (at least) the requested time.
/// When early-return is enabled this may be less than end-point.
last-successful-time: f64,
/// True if the FMU returned early (canReturnEarlyAfterIntermediateUpdate).
event-handling-needed: bool,
/// True if the FMU requests termination.
terminate-simulation: bool,
/// True if the step was entirely discarded and must be retried.
early-return: bool,
}
resource co-simulation-instance {
// ── Construction ──────────────────────────────────────────────────────────
/// Instantiate a Co-Simulation FMU instance.
///
/// FMI C: fmi3Instance fmi3InstantiateCoSimulation(
/// fmi3String instanceName, fmi3String instantiationToken,
/// fmi3String resourcePath, fmi3Boolean visible,
/// fmi3Boolean loggingOn,
/// fmi3Boolean eventModeUsed,
/// fmi3Boolean earlyReturnAllowed,
/// const fmi3ValueReference requiredIntermediateVariables[],
/// size_t nRequiredIntermediateVariables,
/// fmi3InstanceEnvironment instanceEnvironment,
/// fmi3LogMessageCallback logMessage,
/// fmi3IntermediateUpdateCallback intermediateUpdate);
instantiate-co-simulation: static func(
instance-name: string,
instantiation-token: string,
resource-path: string,
visible: bool,
logging-on: bool,
event-mode-used: bool,
early-return-allowed: bool,
required-intermediate-variables: list<value-reference>,
) -> option<co-simulation-instance>;
// ── Debug logging ─────────────────────────────────────────────────────────
/// Enable or disable logging for specific categories.
///
/// FMI C: fmi3Status fmi3SetDebugLogging(
/// fmi3Instance instance, fmi3Boolean loggingOn,
/// size_t nCategories, const fmi3String categories[]);
set-debug-logging: func(logging-on: bool, categories: list<string>) -> status;
// ── Lifecycle ─────────────────────────────────────────────────────────────
/// Transition to Initialisation Mode.
///
/// FMI C: fmi3Status fmi3EnterInitializationMode(
/// fmi3Instance instance,
/// fmi3Boolean toleranceDefined, fmi3Float64 tolerance,
/// fmi3Float64 startTime,
/// fmi3Boolean stopTimeDefined, fmi3Float64 stopTime);
enter-initialization-mode: func(
tolerance: option<f64>,
start-time: f64,
stop-time: option<f64>,
) -> status;
/// Leave Initialisation Mode and enter the appropriate operating mode.
///
/// FMI C: fmi3Status fmi3ExitInitializationMode(fmi3Instance instance);
exit-initialization-mode: func() -> status;
/// Enter Event Mode (Co-Simulation with event mode).
///
/// FMI C: fmi3Status fmi3EnterEventMode(fmi3Instance instance);
enter-event-mode: func() -> status;
/// Update discrete states and check for further event iterations.
///
/// FMI C: fmi3Status fmi3UpdateDiscreteStates(...)
update-discrete-states: func() -> result<discrete-states-info, status>;
/// Terminate the simulation run (orderly shutdown).
///
/// FMI C: fmi3Status fmi3Terminate(fmi3Instance instance);
terminate: func() -> status;
/// Reset the instance to the state after fmi3Instantiate*.
///
/// FMI C: fmi3Status fmi3Reset(fmi3Instance instance);
reset: func() -> status;
// ── Configuration mode (structural parameters) ───────────────────────────
/// Enter Configuration Mode to set structural parameters.
///
/// FMI C: fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance);
enter-configuration-mode: func() -> status;
/// Leave Configuration Mode.
///
/// FMI C: fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance);
exit-configuration-mode: func() -> status;
// ── Variable getters ──────────────────────────────────────────────────────
/// fmi3Status fmi3GetFloat32(instance, vrs[], nVRs, values[], nValues);
get-float32: func(value-references: list<value-reference>) -> result<list<f32>, status>;
/// fmi3Status fmi3GetFloat64(instance, vrs[], nVRs, values[], nValues);
get-float64: func(value-references: list<value-reference>) -> result<list<f64>, status>;
/// fmi3Status fmi3GetInt8(instance, vrs[], nVRs, values[], nValues);
get-int8: func(value-references: list<value-reference>) -> result<list<s8>, status>;
/// fmi3Status fmi3GetInt16(instance, vrs[], nVRs, values[], nValues);
get-int16: func(value-references: list<value-reference>) -> result<list<s16>, status>;
/// fmi3Status fmi3GetInt32(instance, vrs[], nVRs, values[], nValues);
get-int32: func(value-references: list<value-reference>) -> result<list<s32>, status>;
/// fmi3Status fmi3GetInt64(instance, vrs[], nVRs, values[], nValues);
get-int64: func(value-references: list<value-reference>) -> result<list<s64>, status>;
/// fmi3Status fmi3GetUInt8(instance, vrs[], nVRs, values[], nValues);
get-uint8: func(value-references: list<value-reference>) -> result<list<u8>, status>;
/// fmi3Status fmi3GetUInt16(instance, vrs[], nVRs, values[], nValues);
get-uint16: func(value-references: list<value-reference>) -> result<list<u16>, status>;
/// fmi3Status fmi3GetUInt32(instance, vrs[], nVRs, values[], nValues);
get-uint32: func(value-references: list<value-reference>) -> result<list<u32>, status>;
/// fmi3Status fmi3GetUInt64(instance, vrs[], nVRs, values[], nValues);
get-uint64: func(value-references: list<value-reference>) -> result<list<u64>, status>;
/// fmi3Status fmi3GetBoolean(instance, vrs[], nVRs, values[], nValues);
get-boolean: func(value-references: list<value-reference>) -> result<list<bool>, status>;
/// fmi3Status fmi3GetString(instance, vrs[], nVRs, values[], nValues);
get-string: func(value-references: list<value-reference>) -> result<list<string>, status>;
/// fmi3Status fmi3GetBinary(instance, vrs[], nVRs, valueSizes[], values[], nValues);
get-binary: func(value-references: list<value-reference>) -> result<list<list<u8>>, status>;
/// fmi3Status fmi3GetClock(instance, vrs[], nVRs, values[]);
get-clock: func(value-references: list<value-reference>) -> result<list<bool>, status>;
// ── Variable setters ──────────────────────────────────────────────────────
/// fmi3Status fmi3SetFloat32(instance, vrs[], nVRs, values[], nValues);
set-float32: func(value-references: list<value-reference>, values: list<f32>) -> status;
/// fmi3Status fmi3SetFloat64(instance, vrs[], nVRs, values[], nValues);
set-float64: func(value-references: list<value-reference>, values: list<f64>) -> status;
/// fmi3Status fmi3SetInt8(instance, vrs[], nVRs, values[], nValues);
set-int8: func(value-references: list<value-reference>, values: list<s8>) -> status;
/// fmi3Status fmi3SetInt16(instance, vrs[], nVRs, values[], nValues);
set-int16: func(value-references: list<value-reference>, values: list<s16>) -> status;
/// fmi3Status fmi3SetInt32(instance, vrs[], nVRs, values[], nValues);
set-int32: func(value-references: list<value-reference>, values: list<s32>) -> status;
/// fmi3Status fmi3SetInt64(instance, vrs[], nVRs, values[], nValues);
set-int64: func(value-references: list<value-reference>, values: list<s64>) -> status;
/// fmi3Status fmi3SetUInt8(instance, vrs[], nVRs, values[], nValues);
set-uint8: func(value-references: list<value-reference>, values: list<u8>) -> status;
/// fmi3Status fmi3SetUInt16(instance, vrs[], nVRs, values[], nValues);
set-uint16: func(value-references: list<value-reference>, values: list<u16>) -> status;
/// fmi3Status fmi3SetUInt32(instance, vrs[], nVRs, values[], nValues);
set-uint32: func(value-references: list<value-reference>, values: list<u32>) -> status;
/// fmi3Status fmi3SetUInt64(instance, vrs[], nVRs, values[], nValues);
set-uint64: func(value-references: list<value-reference>, values: list<u64>) -> status;
/// fmi3Status fmi3SetBoolean(instance, vrs[], nVRs, values[], nValues);
set-boolean: func(value-references: list<value-reference>, values: list<bool>) -> status;
/// fmi3Status fmi3SetString(instance, vrs[], nVRs, values[], nValues);
set-string: func(value-references: list<value-reference>, values: list<string>) -> status;
/// fmi3Status fmi3SetBinary(instance, vrs[], nVRs, valueSizes[], values[], nValues);
set-binary: func(value-references: list<value-reference>, values: list<list<u8>>) -> status;
/// fmi3Status fmi3SetClock(instance, vrs[], nVRs, values[]);
set-clock: func(value-references: list<value-reference>, values: list<bool>) -> status;
// ── Variable dependencies ─────────────────────────────────────────────────
/// Query the number of rows in the variable-dependency table.
///
/// FMI C: fmi3Status fmi3GetNumberOfVariableDependencies(
/// fmi3Instance instance, fmi3ValueReference vr,
/// size_t* nDependencies);
get-number-of-variable-dependencies: func(vr: value-reference) -> result<u64, status>;
/// Retrieve the variable dependency table.
///
/// FMI C: fmi3Status fmi3GetVariableDependencies(...)
get-variable-dependencies: func(
dependent: value-reference,
) -> result<list<variable-dependency>, status>;
// ── FMU state (save / restore) ────────────────────────────────────────────
/// Capture the complete internal state of the FMU.
///
/// FMI C: fmi3Status fmi3GetFMUState(
/// fmi3Instance instance, fmi3FMUState* FMUState);
get-fmu-state: func() -> result<fmu-state, status>;
/// Restore the FMU to a previously saved state.
///
/// FMI C: fmi3Status fmi3SetFMUState(
/// fmi3Instance instance, fmi3FMUState FMUState);
set-fmu-state: func(state: fmu-state) -> status;
// ── Directional derivatives ───────────────────────────────────────────────
/// Compute directional derivatives dv = J · Δz.
///
/// FMI C: fmi3Status fmi3GetDirectionalDerivative(...)
get-directional-derivative: func(
unknowns: list<value-reference>,
knowns: list<value-reference>,
seed: list<f64>,
) -> result<list<f64>, status>;
/// Compute adjoint (transposed Jacobian) derivatives Δz = Jᵀ · dv.
///
/// FMI C: fmi3Status fmi3GetAdjointDerivative(...)
get-adjoint-derivative: func(
unknowns: list<value-reference>,
knowns: list<value-reference>,
seed: list<f64>,
) -> result<list<f64>, status>;
// ── Clock interval / shift queries ────────────────────────────────────────
/// fmi3Status fmi3GetIntervalDecimal(instance, vrs[], nVRs, intervals[], qualifiers[]);
get-interval-decimal: func(
value-references: list<value-reference>,
) -> result<list<tuple<f64, interval-qualifier>>, status>;
/// fmi3Status fmi3GetIntervalFraction(instance, vrs[], nVRs, counters[], resolutions[], qualifiers[]);
get-interval-fraction: func(
value-references: list<value-reference>,
) -> result<list<tuple<interval-fraction, interval-qualifier>>, status>;
/// fmi3Status fmi3GetShiftDecimal(instance, vrs[], nVRs, shifts[]);
get-shift-decimal: func(
value-references: list<value-reference>,
) -> result<list<f64>, status>;
/// fmi3Status fmi3GetShiftFraction(instance, vrs[], nVRs, counters[], resolutions[]);
get-shift-fraction: func(
value-references: list<value-reference>,
) -> result<list<interval-fraction>, status>;
/// fmi3Status fmi3SetIntervalDecimal(instance, vrs[], nVRs, intervals[]);
set-interval-decimal: func(
value-references: list<value-reference>,
intervals: list<f64>,
) -> status;
/// fmi3Status fmi3SetIntervalFraction(instance, vrs[], nVRs, counters[], resolutions[]);
set-interval-fraction: func(
value-references: list<value-reference>,
intervals: list<interval-fraction>,
) -> status;
/// fmi3Status fmi3SetShiftDecimal(instance, vrs[], nVRs, shifts[]);
set-shift-decimal: func(
value-references: list<value-reference>,
shifts: list<f64>,
) -> status;
/// fmi3Status fmi3SetShiftFraction(instance, vrs[], nVRs, counters[], resolutions[]);
set-shift-fraction: func(
value-references: list<value-reference>,
shifts: list<interval-fraction>,
) -> status;
/// Evaluate discrete states without consuming a full time step.
///
/// FMI C: fmi3Status fmi3EvaluateDiscreteStates(fmi3Instance instance);
evaluate-discrete-states: func() -> status;
// ── Input derivatives ─────────────────────────────────────────────────────
/// Provide higher-order input derivatives to the FMU before fmi3DoStep.
///
/// FMI C: fmi3Status fmi3SetInputDerivatives(
/// fmi3Instance instance,
/// const fmi3ValueReference vrs[], size_t nVRs,
/// const fmi3UInt32 orders[], size_t nOrders,
/// const fmi3Float64 values[], size_t nValues);
///
/// Mapping: (vr, order) pairs are interleaved in a list of tuples.
set-input-derivatives: func(
requests: list<tuple<value-reference, u32>>,
values: list<f64>,
) -> status;
// ── Output derivatives ────────────────────────────────────────────────────
/// Retrieve higher-order output derivatives after fmi3DoStep.
///
/// FMI C: fmi3Status fmi3GetOutputDerivatives(
/// fmi3Instance instance,
/// const fmi3ValueReference vrs[], size_t nVRs,
/// const fmi3UInt32 orders[], size_t nOrders,
/// fmi3Float64 values[], size_t nValues);
get-output-derivatives: func(
requests: list<tuple<value-reference, u32>>,
) -> result<list<f64>, status>;
// ── Step ──────────────────────────────────────────────────────────────────
/// Advance the simulation by one communication step.
///
/// FMI C: fmi3Status fmi3DoStep(
/// fmi3Instance instance,
/// fmi3Float64 currentCommunicationPoint,
/// fmi3Float64 communicationStepSize,
/// fmi3Boolean noSetFMUStatePriorToCurrentPoint,
/// fmi3Boolean* eventHandlingNeeded,
/// fmi3Boolean* terminateSimulation,
/// fmi3Boolean* earlyReturn,
/// fmi3Float64* lastSuccessfulTime);
///
/// Mapping notes:
/// • Output parameters aggregated into do-step-result.
/// • status::discard signals the step must be retried with a smaller
/// step size; in that case the returned do-step-result fields indicate
/// why and where the FMU stopped.
do-step: func(
current-communication-point: f64,
communication-step-size: f64,
no-set-fmu-state-prior-to-current-point: bool,
) -> result<do-step-result, status>;
// ── Step-mode transition (CS with event mode) ─────────────────────────────
/// Enter Step Mode from Event Mode (used when event-mode-used = true).
///
/// FMI C: fmi3Status fmi3EnterStepMode(fmi3Instance instance);
enter-step-mode: func() -> status;
}
}
5.6. Scheduled Execution Interface
package fmi:fmi3@3.0.0;
interface scheduled-execution {
use types.{
value-reference, status, fmu-state,
interval-qualifier, interval-fraction,
variable-dependency, discrete-states-info,
};
resource scheduled-execution-instance {
// ── Construction ──────────────────────────────────────────────────────────
/// Instantiate a Scheduled Execution FMU instance.
///
/// FMI C: fmi3Instance fmi3InstantiateScheduledExecution(
/// fmi3String instanceName, fmi3String instantiationToken,
/// fmi3String resourcePath, fmi3Boolean visible,
/// fmi3Boolean loggingOn,
/// fmi3InstanceEnvironment instanceEnvironment,
/// fmi3LogMessageCallback logMessage,
/// fmi3ClockUpdateCallback clockUpdate,
/// fmi3LockPreemptionCallback lockPreemption,
/// fmi3UnlockPreemptionCallback unlockPreemption);
instantiate-scheduled-execution: static func(
instance-name: string,
instantiation-token: string,
resource-path: string,
visible: bool,
logging-on: bool,
) -> option<scheduled-execution-instance>;
// ── Debug logging ─────────────────────────────────────────────────────────
/// Enable or disable logging for specific categories.
///
/// FMI C: fmi3Status fmi3SetDebugLogging(...)
set-debug-logging: func(logging-on: bool, categories: list<string>) -> status;
// ── Lifecycle ─────────────────────────────────────────────────────────────
/// Transition to Initialisation Mode.
///
/// FMI C: fmi3Status fmi3EnterInitializationMode(...)
enter-initialization-mode: func(
tolerance: option<f64>,
start-time: f64,
stop-time: option<f64>,
) -> status;
/// Leave Initialisation Mode and enter the appropriate operating mode.
///
/// FMI C: fmi3Status fmi3ExitInitializationMode(fmi3Instance instance);
exit-initialization-mode: func() -> status;
/// Enter Event Mode.
///
/// FMI C: fmi3Status fmi3EnterEventMode(fmi3Instance instance);
enter-event-mode: func() -> status;
/// Update discrete states and check for further event iterations.
///
/// FMI C: fmi3Status fmi3UpdateDiscreteStates(...)
update-discrete-states: func() -> result<discrete-states-info, status>;
/// Terminate the simulation run (orderly shutdown).
///
/// FMI C: fmi3Status fmi3Terminate(fmi3Instance instance);
terminate: func() -> status;
/// Reset the instance to the state after fmi3Instantiate*.
///
/// FMI C: fmi3Status fmi3Reset(fmi3Instance instance);
reset: func() -> status;
// ── Configuration mode ───────────────────────────────────────────────────
/// Enter Configuration Mode to set structural parameters.
///
/// FMI C: fmi3Status fmi3EnterConfigurationMode(fmi3Instance instance);
enter-configuration-mode: func() -> status;
/// Leave Configuration Mode.
///
/// FMI C: fmi3Status fmi3ExitConfigurationMode(fmi3Instance instance);
exit-configuration-mode: func() -> status;
// ── Variable getters ──────────────────────────────────────────────────────
get-float32: func(value-references: list<value-reference>) -> result<list<f32>, status>;
get-float64: func(value-references: list<value-reference>) -> result<list<f64>, status>;
get-int8: func(value-references: list<value-reference>) -> result<list<s8>, status>;
get-int16: func(value-references: list<value-reference>) -> result<list<s16>, status>;
get-int32: func(value-references: list<value-reference>) -> result<list<s32>, status>;
get-int64: func(value-references: list<value-reference>) -> result<list<s64>, status>;
get-uint8: func(value-references: list<value-reference>) -> result<list<u8>, status>;
get-uint16: func(value-references: list<value-reference>) -> result<list<u16>, status>;
get-uint32: func(value-references: list<value-reference>) -> result<list<u32>, status>;
get-uint64: func(value-references: list<value-reference>) -> result<list<u64>, status>;
get-boolean: func(value-references: list<value-reference>) -> result<list<bool>, status>;
get-string: func(value-references: list<value-reference>) -> result<list<string>, status>;
get-binary: func(value-references: list<value-reference>) -> result<list<list<u8>>, status>;
get-clock: func(value-references: list<value-reference>) -> result<list<bool>, status>;
// ── Variable setters ──────────────────────────────────────────────────────
set-float32: func(value-references: list<value-reference>, values: list<f32>) -> status;
set-float64: func(value-references: list<value-reference>, values: list<f64>) -> status;
set-int8: func(value-references: list<value-reference>, values: list<s8>) -> status;
set-int16: func(value-references: list<value-reference>, values: list<s16>) -> status;
set-int32: func(value-references: list<value-reference>, values: list<s32>) -> status;
set-int64: func(value-references: list<value-reference>, values: list<s64>) -> status;
set-uint8: func(value-references: list<value-reference>, values: list<u8>) -> status;
set-uint16: func(value-references: list<value-reference>, values: list<u16>) -> status;
set-uint32: func(value-references: list<value-reference>, values: list<u32>) -> status;
set-uint64: func(value-references: list<value-reference>, values: list<u64>) -> status;
set-boolean: func(value-references: list<value-reference>, values: list<bool>) -> status;
set-string: func(value-references: list<value-reference>, values: list<string>) -> status;
set-binary: func(value-references: list<value-reference>, values: list<list<u8>>) -> status;
set-clock: func(value-references: list<value-reference>, values: list<bool>) -> status;
// ── Variable dependencies ─────────────────────────────────────────────────
get-number-of-variable-dependencies: func(vr: value-reference) -> result<u64, status>;
get-variable-dependencies: func(
dependent: value-reference,
) -> result<list<variable-dependency>, status>;
// ── FMU state (save / restore) ────────────────────────────────────────────
get-fmu-state: func() -> result<fmu-state, status>;
set-fmu-state: func(state: fmu-state) -> status;
// ── Directional derivatives ───────────────────────────────────────────────
get-directional-derivative: func(
unknowns: list<value-reference>,
knowns: list<value-reference>,
seed: list<f64>,
) -> result<list<f64>, status>;
get-adjoint-derivative: func(
unknowns: list<value-reference>,
knowns: list<value-reference>,
seed: list<f64>,
) -> result<list<f64>, status>;
// ── Clock interval / shift queries ────────────────────────────────────────
get-interval-decimal: func(
value-references: list<value-reference>,
) -> result<list<tuple<f64, interval-qualifier>>, status>;
get-interval-fraction: func(
value-references: list<value-reference>,
) -> result<list<tuple<interval-fraction, interval-qualifier>>, status>;
get-shift-decimal: func(
value-references: list<value-reference>,
) -> result<list<f64>, status>;
get-shift-fraction: func(
value-references: list<value-reference>,
) -> result<list<interval-fraction>, status>;
set-interval-decimal: func(
value-references: list<value-reference>,
intervals: list<f64>,
) -> status;
set-interval-fraction: func(
value-references: list<value-reference>,
intervals: list<interval-fraction>,
) -> status;
set-shift-decimal: func(
value-references: list<value-reference>,
shifts: list<f64>,
) -> status;
set-shift-fraction: func(
value-references: list<value-reference>,
shifts: list<interval-fraction>,
) -> status;
evaluate-discrete-states: func() -> status;
// ── Partition activation ──────────────────────────────────────────────────
/// Activate a single model partition identified by its clock value reference.
///
/// FMI C: fmi3Status fmi3ActivateModelPartition(
/// fmi3Instance instance,
/// fmi3ValueReference clockReference,
/// fmi3Float64 activationTime);
///
/// Mapping notes:
/// • activationTime is the absolute simulation time at which the
/// partition is being activated (corresponds to the clock tick time).
/// • The master is responsible for acquiring the preemption lock before
/// calling this function and releasing it afterwards (via the
/// lock-preemption / unlock-preemption callbacks in the `callbacks`
/// imported interface).
activate-model-partition: func(
clock-reference: value-reference,
activation-time: f64,
) -> status;
}
}
5.7. World Definitions
A WIT world specifies the complete contract for a component: which interfaces it imports (uses from the host) and which it exports (provides to the host).
package fmi:fmi3@3.0.0;
// ── Model Exchange FMU ────────────────────────────────────────────────────────
// World implemented by a Model Exchange FMU component.
//
// The FMU exports the `common` and `model-exchange` interfaces.
// The host provides logging callbacks.
world model-exchange-fmu {
// ── Imports (host provides) ──────────────────────────────────────────────
import callbacks;
// ── Exports (FMU provides) ───────────────────────────────────────────────
/// Common variable access, lifecycle, and state-save/restore functions.
export common;
/// Model-Exchange–specific functions (set-time, derivatives, event indicators).
export model-exchange;
}
// ── Co-Simulation FMU ─────────────────────────────────────────────────────────
// World implemented by a Co-Simulation FMU component.
//
// The FMU exports the `common` and `co-simulation` interfaces.
// The host provides logging and (optionally) intermediate-update callbacks.
world co-simulation-fmu {
// ── Imports (host provides) ──────────────────────────────────────────────
import callbacks;
/// Optional: only required when instantiate-co-simulation is called with
/// early-return-allowed = true.
import intermediate-update-callbacks;
// ── Exports (FMU provides) ───────────────────────────────────────────────
export common;
export co-simulation;
}
// ── Scheduled Execution FMU ───────────────────────────────────────────────────
// World implemented by a Scheduled Execution FMU component.
//
// The FMU exports the `common` and `scheduled-execution` interfaces.
// The host provides logging, clock-update, and preemption callbacks.
world scheduled-execution-fmu {
// ── Imports (host provides) ──────────────────────────────────────────────
/// All four callbacks (log-message, clock-update,
/// lock-preemption, unlock-preemption) are mandatory for SE.
import callbacks;
// ── Exports (FMU provides) ───────────────────────────────────────────────
export common;
export scheduled-execution;
}
// ── Master Algorithm (importer) ───────────────────────────────────────────────
// World implemented by a master algorithm / simulation environment that
// can host all three FMU types simultaneously.
//
// This is a convenience world for toolchain authors; real masters typically
// target one interface kind at a time.
world master-algorithm {
// The master imports (i.e. uses) each FMU interface kind.
import common;
import model-exchange;
import co-simulation;
import scheduled-execution;
// The master exports the callback interfaces so FMUs can call back into it.
export callbacks;
export intermediate-update-callbacks;
}
| World | Exports (FMU provides) | Imports (host provides) |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
6. Design Decisions
This section documents the rationale for the mapping choices made in the WIT definitions.
6.1. fmi3Instance → Three Standalone Resources
The opaque C pointer fmi3Instance is mapped to three distinct WIT resources — one per interface kind:
| FMU kind | WIT resource | Static factory |
|---|---|---|
Model Exchange |
|
|
Co-Simulation |
|
|
Scheduled Execution |
|
|
Each resource owns the full lifecycle of the FMU instance: it carries the instantiation factory as a static method, all common lifecycle and variable access methods (enter-initialization-mode, get-float64, set-fmu-state, etc.), and the interface-kind-specific methods (do-step, set-time, …).
Resource ownership is linear: fmi3FreeInstance is called when the resource is dropped, so no explicit free binding is needed.
This design means the host never holds an intermediate handle shared across interface kinds, making it impossible to call ME-only methods (e.g. set-time) on a CS handle.
6.2. Callback Pointers → Imported Interfaces
FMI 3.0 passes callbacks as function pointers to fmi3Instantiate*.
In the Component Model these become imported interfaces supplied by the host world.
The fmi3InstanceEnvironment opaque pointer is dropped entirely; the Component Model’s closure semantics handle per-instance context automatically.
6.3. intermediateUpdateCallback is Optional
The intermediate-update-callbacks interface is a separate import so that simple CS FMUs that do not support early return can omit it.
This is permitted by the WIT world definition for co-simulation-fmu, which marks that import without making it mandatory at the component level.
6.4. Parallel C Arrays → list<T> / list<tuple<…>>
FMI getter/setter functions use parallel C arrays (pointer + size_t length).
These map to list<T> in WIT.
Where two arrays are logically paired (e.g. vrs[] + orders[] for output derivatives), they are collapsed into list<tuple<value-reference, u32>> to prevent length mismatches.
| FMI 3.0 C signature | WIT mapping |
|---|---|
|
|
|
|
6.5. Optional Parameters → option<T>
fmi3EnterInitializationMode takes a toleranceDefined boolean alongside tolerance, and similarly for stopTimeDefined / stopTime.
These pairs collapse to option<f64> in WIT, which is both more idiomatic and eliminates the possibility of passing toleranceDefined = false with a non-zero tolerance.
| FMI 3.0 C parameters | WIT parameter |
|---|---|
|
|
|
|
6.6. Output Parameters → Return Values / Records
Functions with multiple C out-parameters (e.g. fmi3UpdateDiscreteStates, fmi3DoStep, fmi3CompletedIntegratorStep) return a record instead of multiple pointer arguments.
This is idiomatic for WIT and eliminates null-pointer risks.
| FMI 3.0 C output parameters | WIT return type |
|---|---|
|
|
|
|
|
|
6.7. fmi3FMUState → list<u8>
The C API uses a two-phase serialise approach (fmi3GetFMUState + fmi3SerializeFMUState).
The WIT mapping merges these into a single get-fmu-state that returns list<u8> (serialised bytes immediately).
The host therefore never holds an un-serialised fmi3FMUState handle.
The corresponding restore operation (fmi3SetFMUState + fmi3DeserializeFMUState) is similarly merged into set-fmu-state(state: fmu-state).
6.8. fmi3Boolean → bool
FMI 3.0 defines fmi3Boolean as int-sized (for C ABI compatibility).
The Component Model uses a native 1-byte bool, which is correct for WIT and avoids ABI fragility.
6.9. fmi3String → string
FMI strings are null-terminated UTF-8.
WIT string is length-prefixed UTF-8; the null terminator is elided.
No semantic change is introduced; implementations MUST ensure the content is valid UTF-8.
6.10. fmi3Binary → list<u8>
Binary variables carry an explicit length in C (valueSizes[]).
In WIT each blob is a list<u8> whose length is intrinsic, so the size array is removed.
For fmi3GetBinary / fmi3SetBinary dealing with multiple variables the type becomes list<list<u8>>.
7. Conformance
7.1. FMU Implementors
An FMU is conformant with this layered standard if it:
-
Provides a WebAssembly component binary at
binaries/wasm32-wasip2/<modelIdentifier>.wasm. -
The component implements the WIT world corresponding to its FMU type as defined in Section 5.7.
-
Includes a valid
modelDescription.xmlconformant with FMI 3.0. -
Includes a valid
extra/org.modelica.fmi-ls-wasm/manifest.xml. -
Implements the semantics of all exported interface methods as specified by the FMI 3.0 standard.
7.2. Simulation Environment (Importer) Implementors
An importer is conformant if it:
-
Can load and instantiate WebAssembly components conforming to
wasm32-wasip2target. -
Provides all imported interfaces declared in the FMU’s world (callbacks, and
intermediate-update-callbackswhenearly-return-allowed = true). -
Honours the FMI 3.0 state machine when calling methods on the instance resource.
-
Drops the instance resource (triggering
fmi3FreeInstance) when the simulation is complete or aborted.
8. Known Limitations and Future Work
-
fmi3GetVersionis a free function — it is the only item in thecommoninterface, exposed at the interface level rather than as a resource method, matching the C API. -
Model description XML — FMI 3.0 ships metadata as
modelDescription.xmlinside the FMU archive. This mapping covers only the runtime API; the static metadata is out of scope. -
Threading model for Scheduled Execution — The preemption lock/unlock callbacks are mapped as host-imported functions, but the Component Model threading extensions (WASI threads or
shared-everything-threads) are still evolving. Platform integrators may need to adapt when those proposals stabilise. -
fmi3Char— Used only infmi3GetVersionreturn value; mapped tostring. -
Serialise/deserialise split — If round-tripping large state blobs proves expensive, the two-phase C API (
GetFMUState+SerializeFMUState) can be re-exposed as separate WIT functions without changing the type mapping. -
WASI imports — FMUs that need filesystem or clock access will depend on WASI interfaces. These dependencies are not yet specified by this layered standard and are left to the FMU implementor.
References
-
[1] Modelica Association Project FMI: Functional Mock-up Interface Specification, Version 3.0. https://svn.modelica.org/fmi/branches/public/specifications/v3.0/
-
[2] Bytecode Alliance: WebAssembly Component Model. https://component-model.bytecodealliance.org/
-
[3] Bytecode Alliance: WIT — WebAssembly Interface Types. https://component-model.bytecodealliance.org/design/wit.html
-
[4] Bradner, S.: Key words for use in RFCs to Indicate Requirement Levels. RFC 2119, March 1997. https://www.rfc-editor.org/rfc/rfc2119
-
[5] Bytecode Alliance: WASI Preview 2. https://github.com/WebAssembly/WASI/blob/main/preview2/README.md
-
[6] 2-Clause BSD License. https://opensource.org/licenses/BSD-2-Clause