diff --git a/.gitignore b/.gitignore index 6b847442..637468b7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ /requirements/export /target result +whitepaper/main.pdf diff --git a/flake.lock b/flake.lock index 671bc799..21b3ce75 100644 --- a/flake.lock +++ b/flake.lock @@ -2,15 +2,14 @@ "nodes": { "devshell": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" }, "locked": { - "lastModified": 1705332421, - "narHash": "sha256-USpGLPme1IuqG78JNqSaRabilwkCyHmVWY0M9vYyqEA=", + "lastModified": 1728330715, + "narHash": "sha256-xRJ2nPOXb//u1jaBnDP56M7v5ldavjbtR6lfGqSvcKg=", "owner": "numtide", "repo": "devshell", - "rev": "83cb93d6d063ad290beee669f4badf9914cc16ec", + "rev": "dd6b80932022cea34a019e2bb32f6fa9e494dfef", "type": "github" }, "original": { @@ -27,11 +26,11 @@ "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1706682087, - "narHash": "sha256-9AQQhjTRcVlqwOxM8im6wR+vDT1N0efUX7pvAyfAzLE=", + "lastModified": 1731479620, + "narHash": "sha256-jBYFboMLYs0LcIfZq+kprR4vOjz6cfqaS8trwogoXQE=", "ref": "main", - "rev": "7e29c0af8b70147f1f23c1387a0d774209889d1b", - "revCount": 1761, + "rev": "46c04695cd28be15ff88e3b8d2716818b034c084", + "revCount": 2076, "type": "git", "url": "https://github.com/nix-community/fenix.git" }, @@ -41,24 +40,6 @@ "url": "https://github.com/nix-community/fenix.git" } }, - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1701680307, - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "naersk": { "inputs": { "nixpkgs": [ @@ -66,11 +47,11 @@ ] }, "locked": { - "lastModified": 1698420672, - "narHash": "sha256-/TdeHMPRjjdJub7p7+w55vyABrsJlt5QkznPYy55vKA=", + "lastModified": 1721727458, + "narHash": "sha256-r/xppY958gmZ4oTfLiHN0ZGuQ+RSTijDblVgVLFi1mw=", "ref": "refs/heads/master", - "rev": "aeb58d5e8faead8980a807c840232697982d47b9", - "revCount": 333, + "rev": "3fb418eaf352498f6b6c30592e3beb63df42ef11", + "revCount": 345, "type": "git", "url": "https://github.com/nix-community/naersk.git" }, @@ -81,11 +62,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1704161960, - "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", + "lastModified": 1722073938, + "narHash": "sha256-OpX0StkL8vpXyWOGUD6G+MA26wAXK6SpT94kLJXo6B4=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "63143ac2c9186be6d9da6035fa22620018c85932", + "rev": "e36e9f57337d0ff0cf77aceb58af4c805472bfae", "type": "github" }, "original": { @@ -95,13 +76,13 @@ "type": "github" } }, - "nixpkgs_2": { + "nixpkgs-unstable": { "locked": { - "lastModified": 1719848872, - "narHash": "sha256-H3+EC5cYuq+gQW8y0lSrrDZfH71LB4DAf+TDFyvwCNA=", + "lastModified": 1731139594, + "narHash": "sha256-IigrKK3vYRpUu+HEjPL/phrfh7Ox881er1UEsZvw9Q4=", "owner": "nixos", "repo": "nixpkgs", - "rev": "00d80d13810dbfea8ab4ed1009b09100cca86ba8", + "rev": "76612b17c0ce71689921ca12d9ffdc9c23ce40b2", "type": "github" }, "original": { @@ -111,24 +92,43 @@ "type": "github" } }, + "nixpkgs_2": { + "locked": { + "lastModified": 1731239293, + "narHash": "sha256-q2yjIWFFcTzp5REWQUOU9L6kHdCDmFDpqeix86SOvDc=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "9256f7c71a195ebe7a218043d9f93390d49e6884", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "devshell": "devshell", "fenix": "fenix", "naersk": "naersk", "nixpkgs": "nixpkgs_2", + "nixpkgs-unstable": "nixpkgs-unstable", "treefmt-nix": "treefmt-nix", + "typix": "typix", + "typst-packages": "typst-packages", "utils": "utils" } }, "rust-analyzer-src": { "flake": false, "locked": { - "lastModified": 1706641601, - "narHash": "sha256-/4U323L9F6wlBGvGBoE3+D6Af+dLJA+mq0QXbuNxohc=", + "lastModified": 1731342671, + "narHash": "sha256-36eYDHoPzjavnpmEpc2MXdzMk557S0YooGms07mDuKk=", "owner": "rust-lang", "repo": "rust-analyzer", - "rev": "9a832c47e85e94496e10e1e81636d3d89afeb0d4", + "rev": "fc98e0657abf3ce07eed513e38274c89bbb2f8ad", "type": "github" }, "original": { @@ -153,51 +153,72 @@ "type": "github" } }, - "systems_2": { + "treefmt-nix": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "lastModified": 1730321837, + "narHash": "sha256-vK+a09qq19QNu2MlLcvN4qcRctJbqWkX7ahgPZ/+maI=", + "owner": "numtide", + "repo": "treefmt-nix", + "rev": "746901bb8dba96d154b66492a29f5db0693dbfcc", "type": "github" }, "original": { - "owner": "nix-systems", - "repo": "default", + "owner": "numtide", + "repo": "treefmt-nix", "type": "github" } }, - "treefmt-nix": { + "typix": { "inputs": { "nixpkgs": [ - "nixpkgs" + "nixpkgs-unstable" ] }, "locked": { - "lastModified": 1719243788, - "narHash": "sha256-9T9mSY35EZSM1KAwb7K9zwQ78qTlLjosZgtUGnw4rn4=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "065a23edceff48f948816b795ea8cc6c0dee7cdf", + "lastModified": 1731352170, + "narHash": "sha256-2JOC9lrFa0NfP9kfaT3dnm8WG5yAeDPONHJeMYl+O60=", + "owner": "loqusion", + "repo": "typix", + "rev": "7365cb7e6665350cb7b013abba1cbb70fcf07c53", "type": "github" }, "original": { - "owner": "numtide", - "repo": "treefmt-nix", + "owner": "loqusion", + "repo": "typix", + "type": "github" + } + }, + "typst-packages": { + "flake": false, + "locked": { + "lastModified": 1731485222, + "narHash": "sha256-uhXDznbGLS4s6d2S8OHUCzpgmJQ1H2HCJug7kRIY0Jw=", + "owner": "typst", + "repo": "packages", + "rev": "4979ee3923fb0143e5d076854cb0d94bbf367a2c", + "type": "github" + }, + "original": { + "owner": "typst", + "repo": "packages", "type": "github" } }, "utils": { "inputs": { - "systems": "systems_2" + "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "ref": "refs/heads/main", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", - "revCount": 90, + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", + "revCount": 101, "type": "git", "url": "https://github.com/numtide/flake-utils.git" }, diff --git a/flake.nix b/flake.nix index b25d2c54..d95c7100 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,16 @@ description = "a minimal WASM interpreter"; inputs = { - nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; + nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05"; + nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable"; + typst-packages = { + url = "github:typst/packages"; + flake = false; + }; + typix = { + url = "github:loqusion/typix"; + inputs.nixpkgs.follows = "nixpkgs-unstable"; + }; utils.url = "git+https://github.com/numtide/flake-utils.git"; devshell.url = "github:numtide/devshell"; fenix = { @@ -24,28 +33,34 @@ lib = nixpkgs.lib; pkgs = import nixpkgs { inherit system; - overlays = [ devshell.overlays.default ]; + overlays = [ + devshell.overlays.default + + # We unfortunately need the most up-to-date typst + (final: prev: { + typst = inputs.nixpkgs-unstable.legacyPackages.${pkgs.hostPlatform.system}.typst; + }) + ]; }; # universal formatter treefmtEval = treefmt-nix.lib.evalModule pkgs ./treefmt.nix; # rust target name of the `system` - rust-target = pkgs.rust.toRustTarget pkgs.pkgsStatic.targetPlatform; + rust-target = pkgs.pkgsStatic.targetPlatform.rust.rustcTarget; # Rust distribution for our hostSystem fenix = inputs.fenix.packages.${system}; - rust-toolchain = with fenix; - combine [ - latest.rustc - latest.cargo - latest.clippy - latest.rustfmt - targets.${rust-target}.latest.rust-std - targets.thumbv6m-none-eabi.latest.rust-std # for no_std test - targets.wasm32-unknown-unknown.latest.rust-std - ]; + rust-toolchain = with fenix; combine [ + latest.rustc + latest.cargo + latest.clippy + latest.rustfmt + targets.${rust-target}.latest.rust-std + targets.thumbv6m-none-eabi.latest.rust-std # for no_std test + targets.wasm32-unknown-unknown.latest.rust-std + ]; # overrides a naersk-lib which uses the stable toolchain expressed above naersk-lib = (naersk.lib.${system}.override { @@ -53,14 +68,31 @@ rustc = rust-toolchain; }); + typstPackagesCache = pkgs.stdenv.mkDerivation { + name = "typst-packages-cache"; + src = inputs.typst-packages; + dontBuild = true; + installPhase = '' + mkdir -p "$out/typst/packages" + cp --dereference --no-preserve=mode --recursive --reflink=auto \ + --target-directory="$out/typst/packages" -- "$src"/packages/* + ''; + }; in { # packages packages.wasm-interpreter = pkgs.callPackage pkgs/wasm-interpreter.nix { }; + packages.whitepaper = inputs.typix.lib.${system}.buildTypstProject { + name = "whitepaper.pdf"; + src = ./whitepaper; + XDG_CACHE_HOME = typstPackagesCache; + }; + packages.report = pkgs.callPackage pkgs/report.nix { - inherit (self.packages.${system}) wasm-interpreter; + inherit (self.packages.${system}) wasm-interpreter whitepaper; }; + # a devshell with all the necessary bells and whistles devShells.default = (pkgs.devshell.mkShell { imports = [ "${devshell}/extra/git/hooks.nix" ]; @@ -84,6 +116,7 @@ nixpkgs-fmt nodePackages.prettier treefmtEval.config.build.wrapper + typst # for the whitepaper ]; env = [ { @@ -132,6 +165,13 @@ ''; help = "start cargo watch loop for documentation"; } + { + name = "whitepaper-watch"; + command = '' + typst watch --root "$PRJ_ROOT/whitepaper" "$PRJ_ROOT/whitepaper/main.typ" + ''; + help = "start typst watch loop for the whitepaper"; + } ]; }); diff --git a/pkgs/report.nix b/pkgs/report.nix index 0bf701bf..3efff931 100644 --- a/pkgs/report.nix +++ b/pkgs/report.nix @@ -1,4 +1,4 @@ -{ lib, stdenvNoCC, python3Packages, strictdoc, wasm-interpreter }: +{ lib, stdenvNoCC, python3Packages, strictdoc, wasm-interpreter, whitepaper }: let evidenceRoot = lib.strings.escapeShellArg wasm-interpreter; @@ -22,6 +22,7 @@ stdenvNoCC.mkDerivation { cp --recursive -- ${evidenceRoot}/bench-html bench cp --recursive -- ${evidenceRoot}/lcov-html coverage cp --recursive -- ${evidenceRoot}/share/doc/ rustdoc + cp --dereference -- ${whitepaper} whitepaper.pdf mkdir test junit2html ${evidenceRoot}/junit.xml test/index.html diff --git a/pkgs/report_index.html b/pkgs/report_index.html index 5dd9d289..d8be26f0 100644 --- a/pkgs/report_index.html +++ b/pkgs/report_index.html @@ -47,5 +47,6 @@ Tests Coverage Benchmark + Whitepaper diff --git a/treefmt.nix b/treefmt.nix index 6de2f3e8..6754f37a 100644 --- a/treefmt.nix +++ b/treefmt.nix @@ -24,4 +24,5 @@ }; }; programs.rustfmt.enable = true; + programs.typstfmt.enable = true; } diff --git a/whitepaper/main.typ b/whitepaper/main.typ new file mode 100644 index 00000000..c5efa4e7 --- /dev/null +++ b/whitepaper/main.typ @@ -0,0 +1,150 @@ +/* imports */ +#import "@preview/acrostiche:0.4.0": * +#import "@preview/ccicons:1.0.0": * + +/* variables and setup */ +#let title = [WASM Interpreter for Safety -- White Paper] + +/* TODO make this multi-author capable */ +#let author = "Wanja Zaeske" +#let affiliation = [ + Department Safety Critical Systems & Systems Engineering \ + German Aerospace Center (DLR) \ + #link("mailto:wanja.zaeske@dlr.de") +] + +#set document( + title: title, author: author, keywords: ("WebAssembly", "Safety-Critical"), +) + +#init-acronyms( + ( + "AOT": ("Ahead-of-time"), "DAL": ("Design Assurance Level"), "DARPA": ("Defense Advanced Research Projects Agency"), "IR": ("Intermediate Representation"), "JIT": ("Just-in-time"), "SBOM": ("Software Bill of Materials"), "TQL": ("Tool Qualification Level"), "TRACTOR": ("Translating All C to Rust"), "WASM": ("WebAssembly"), + ), +) + +/* style */ +#set page( + paper: "a4", header: context{ + if counter(page).get().first() > 1 [ + #align(right, title), + ] + }, footer: context[ + #set text(8pt) + License: #link("https://creativecommons.org/licenses/by-sa/4.0/")[CC-BY-SA #cc-by-sa] + #h(1fr) #counter(page).display("1 of 1", both: true) \ + + Copyright © 2024-#datetime.today().year() German Aerospace Center (DLR). All + rights reserved. + ], columns: 1, +) + +#set heading(numbering: "1.") + +#align(center, text(17pt)[*#title*]) + +#grid(columns: (1fr), align(center)[ + #author \ + #affiliation +]) + += Introduction +This white paper provides an overview over our WebAssembly interpreter +@our-repo. It Primarily serves to highlight and explain design decisions and the +goals we aim to achieve. + +The development of this project currently takes place as a joint effort between +DLR-ft @dlr-ft-website and OxidOS @oxidos-website. Being truly Open Source, the +project already is willing to accept external contributions. As the project +matures, it might be worth considering moving the project to a vendor-agnostic +foundation, to assure for a fair process allowing contributions from multiple +commercial entities. However, for the time being the project is not mature +enough to justify the overhead doing so, thus for the time being the project +remains under control of DLR & OxidOS. + +== Design Drivers +The implementation of our interpreter is driven by several design goals. After +all, there are dozens of WebAssembly interpreters around, why would one have to +implement yet another one? + +=== Pure Interpretation +In fact, the prior sentence is somewhat imprecise. Most WebAssembly interpreters +are actually compilers; they compile the WebAssembly down to a different format +that seems more suitable for execution. A prominent example would be WasmTime +@wasmtime-website, which compiles WebAssembly down to object code, so called #acr("AOT") compilation. +Browsers commonly mix in #acr("JIT") compilation as well, starting with a +quick-to-compile but not so fast-to-run #acr("AOT") baseline build, which then +on demand is partially swapped for optimized sections obtained by #acr("JIT") compiling +code sections in the hot path (example: @mozilla-baseline-compiler). + +But even if neither #acr("AOT") nor #acr("JIT") are at hand (no object code for +the physical processor is created), there still is a common pattern of rewriting +the #acr("WASM") bytecode into a custom #acr("IR") (example: +@wasmi-instruction-enum). There are multiple reasons for this, this allows for +gentle optimizations, simpler code, and foremost explicit jumps. In ordinary #acr("WASM") bytecode, +branches are indirect; e.g. a branch could be "jump out of the innermost three +scopes". In practical terms, a branch boils down to modifying the program +counter/instruction pointer, i.e. advance it 15 instructions forward. To avoid +costly linear search at runtime (for where the innermost three scopes end), many +interpreters rewrite the #acr("WASM") bytecode into their own #acr("IR"), +calculating the direct offsets on the instruction pointer for the branches once, +so that in their #acr("IR") branches are direct. + +This is a good decision for many use-cases, but tricky when safety-certification +comes into play. Now, the currency in safety certification is evidence, and for +the special case of avionics, a significant amount of evidence has to be +provided for the object code @webassembly-in-avionics-paper. Thus, generating +object code at run-time (for example when #acr("JIT") compiling) is prohibitive +-- it simply does not fit together with the assumptions baked into current +certification guidelines such as DO-178C @do178c. Finally, DO-332 contains +specific language that allows for the directly interpreted format executed by a +virtual machine/bytecode interpreter to be treated as object code. Hence, when +compiling #acr("WASM") bytecode into a custom #acr("IR"), one would have to +provide certification evidence on that #acr("IR"). + +Our design alleviates all this; by directly interpreting the #acr("WASM") bytecode, +certification evidence has to be produced for the #acr("WASM") directly, and +thus is not tied to implementation details (as in particular #acr("IR")) used by +our interpreter. A comprehensive discussion of this can be found in our +publication @webassembly-in-avionics-paper. + +To avoid the problem of indirect branching, we borrow ideas from Ben L. Titzer's +wizard @wizard-engine, a pre-computed side-table that for all branches stores +the direct offset on the instruction pointer. A detailed discussion of the +technique and further implementation details of wizard can be found in his paper +@fast-in-place-interpreter-for-webassembly. + +=== Certification Friendly Source Code +Our implementation is written in Rust, which on its own poses a challenge: As of +now, we are not aware of any high-assurance deployment of Rust in the aviation +sector. Similarly, there is only little information on Rust being deployed in +automotive (such as @volvo-rust-assembly-line). At the same time, there is a +keen interest to push Rust in safety critical domains, and companies like +ferrous systems @ferrous-systems-website with their ISO 26262 ASIL D certified +Rust compiler @ferrocene-website and AdaCore with the GNAT Pro for Rust +@adacore-rust-website pave the way for Rust. + +Now, there some techniques often found in Rust programs pose risk for a smooth +certification. One such thing would be macros. While we are not aware of a +precedence case, we assume that Rust's various flavors of macros might be +treated like tools, after all they are computer programs that generated code, +which in term is compiled in to the final application. As such, they might be +subject to tool qualification as per DO-330 @do330, and in since macros can +easily sneak in broken code, they are likely to be treated as criteria 1 tool +@do330. If these assumptions hold, macros would have to be tool qualified to the +matching #acr("TQL") of an application's #acr("DAL"). As testing macros is more +complicated than testing normal code, we restrict our usage of macros to the +bare minimum. + +Another risk to certification is third party code. Thus, to keep our code +closure (and subsequently the #acr("SBOM") compact), we refrain from using +dependencies which get compiled into the code#footnote[Currently, two carefully selected run-time dependencies are allowed, however, + there is a roadmap to phase them out, and each such exception is tracked in our + requirements]. + +/* +TODO talk about Nix +=== Infrastructure as Code wherever possible +*/ + +#bibliography("refs.yaml") diff --git a/whitepaper/refs.yaml b/whitepaper/refs.yaml new file mode 100644 index 00000000..01724152 --- /dev/null +++ b/whitepaper/refs.yaml @@ -0,0 +1,148 @@ +# Heper to convert from BibTeX: +# https://jonasloos.github.io/bibtex-to-hayagriva-webapp/ + +# papers + +webassembly-in-avionics-paper: + type: article + title: "WebAssembly in Avionics : Decoupling Software from Hardware" + author: + - Zaeske, Wanja + - Friedrich, Sven + - Schubert, Tim + - Durak, Umut + date: 2023 + page-range: 1–10 + serial-number: + doi: 10.1109/DASC58513.2023.10311207 + parent: + type: proceedings + title: 2023 IEEE/AIAA 42nd Digital Avionics Systems Conference (DASC) + issue: "" + volume: 0 + +fast-in-place-interpreter-for-webassembly: + type: article + title: A fast in-place interpreter for WebAssembly + author: Titzer, Ben L. + date: 2022-10 + url: https://doi.org/10.1145/3563311 + serial-number: + doi: 10.1145/3563311 + parent: + type: periodical + title: Proc. ACM Program. Lang. + publisher: Association for Computing Machinery + location: New York, NY, USA + issue: OOPSLA2 + volume: 6 + +# standards etc. + +do178c: + type: report + title: "{DO-178C} Software Considerations in Airborne Systems and Equipment Certification" + author: RTCA + date: 2011 + organization: RTCA +do330: + type: report + title: "{DO-330} Software Tool Qualification Considerations" + author: RTCA + date: 2011 + organization: RTCA + +do331: + type: report + title: "{DO-331} Model-Based Development and Verification Supplement to {DO-178C} and {DO-278A}" + author: RTCA + date: 2011 + organization: RTCA + +do332: + type: report + title: "{DO-332} Object-Oriented Technology and Related Techniques Supplement to {DO-178C} and {DO-278A}" + author: RTCA + date: 2011 + organization: RTCA + +do333: + type: report + title: "{DO-333} Formal Methods Supplement to {DO-178C} and {DO-278A}" + author: RTCA + date: 2011 + organization: RTCA + +# websites + +our-repo: + type: Web + title: wasm-interpreter + author: + - DLR e. V. + - OxidOS + url: https://github.com/DLR-FT/wasm-interpreter + +dlr-ft-website: + type: Web + title: DLR | Institute of Flight Systems + author: DLR e. V. + url: https://www.dlr.de/en/ft + +oxidos-website: + type: Web + title: Welcome to OxidOS Automotive + author: OxidOS + url: https://oxidos.io/ + +wasmtime-website: + type: Web + title: A fast and secuire runtime for Webassembly + author: Bytecode Alliance + url: https://wasmtime.dev/ + +mozilla-baseline-compiler: + type: Web + title: "Making WebAssembly even faster: Firefox’s new streaming and tiering compiler" + author: Lin Clark + url: https://hacks.mozilla.org/2018/01/making-webassembly-even-faster-firefoxs-new-streaming-and-tiering-compiler/ + +wasmi-instruction-enum: + type: Web + title: "Making WebAssembly even faster: Firefox’s new streaming and tiering compiler" + author: Robin Freyler et al. + url: https://docs.rs/wasmi_ir/latest/wasmi_ir/enum.Instruction.html + +wizard-engine: + type: Web + title: The Wizard Research Engine + author: Ben L. Titzer et al. + url: https://github.com/titzer/wizard-engine + +volvo-rust-assembly-line: + type: Web + title: Rust is rolling off the Volvo assembly line + author: + - Dion Dokte + - Julius Gustavsson + url: https://tweedegolf.nl/en/blog/137/rust-is-rolling-off-the-volvo-assembly-line + +ferrous-systems-website: + type: Web + title: Ferrous Systems + url: https://ferrous-systems.com/ + +ferrocene-website: + type: Web + title: This is Rust for critical systems. + url: https://ferrocene.dev/ + +adacore-website: + type: Web + title: AdaCore + url: https://www.adacore.com/ + +adacore-rust-website: + type: Web + title: GNAT Pro for Rust + url: https://www.adacore.com/gnatpro-rust