AYOUB.*
← EMERGING SYSTEMS ENGINEERINGREPOS ↗

Chapter 9 · Part III: Quantum Software Engineering and High-Efficiency Compute

The Next-Generation Tech Stack

Four choices in the current stack look likely to hold for a decade: a memory-safe systems language, a portable sandboxed runtime, a programmable kernel, and an architecture that expects to be partly replaced. This chapter covers what each one gives, what it costs, and how to adopt it without a rewrite.

In this chapter
  1. 9.1 Safe Modern Systems Programming
  2. 9.2 WebAssembly Beyond the Browser
  3. 9.3 Kernel-Level Software Innovation
  4. 9.4 Future-Proof Architectural Design Patterns
  5. Sources and further reading

9.1 Safe Modern Systems Programming

Roughly 70 percent of serious vulnerabilities in large C and C++ code bases are memory safety defects. That figure is consistent across Microsoft, Google, Mozilla, and the Linux kernel, across decades, and it did not move in response to better tooling, better review, or better training.

The two classes and why mitigations do not close them

Spatial errors access memory outside an object's bounds: buffer overflow, array index errors, and pointer arithmetic that leaves the allocation. Temporal errors access memory outside an object's lifetime: use after free, double free, and a returned pointer to a stack frame that already returned.

Hardware and compiler mitigations raise the cost of exploitation without removing the bugs. Address space layout randomization, stack canaries, non-executable memory, control flow integrity, shadow stacks, and memory tagging each close specific exploitation techniques, and attackers develop bypasses for each. The bug remains, and a mitigation converts an arbitrary code execution into a crash, which is a denial of service rather than a fix.

The language answer removes the class. Rust proves spatial and temporal safety at compile time in safe code, with no run-time cost for the proof itself.

What the borrow checker proves

Three rules do the work, and they are simpler than their reputation.

Every value has one owner, and the value is dropped when the owner goes out of scope. That gives deterministic destruction with no garbage collector and no manual free.

A value can have either one mutable reference or any number of shared references, never both at once. That single rule removes use after free, because a reference cannot outlive its referent, and it removes data races, because two threads cannot hold mutable access to the same value.

Every reference has a lifetime that the compiler tracks, and a program where a reference outlives its referent does not compile.

// Temporal safety. This is the class that produces most exploits.
fn dangling() -> &String {
    let s = String::from("hello");
    &s                      // compile error: `s` is dropped here
}                           // in C this returns a pointer to freed memory

// Aliasing. This is the rule that also removes data races.
let mut data = vec![1, 2, 3];
let first = &data[0];       // shared borrow
// data.push(4);            // compile error: a reallocation would
                            // invalidate `first`
println!("{}", first);

// Concurrency. The same rules apply across threads, checked by the
// same compiler pass. A data race is a borrow error.
use std::thread;
let mut counter = 0;
thread::scope(|s| {
    s.spawn(|| counter += 1);
    // s.spawn(|| counter += 1);   // compile error: two mutable borrows
});

The cost is a learning curve and a set of patterns the checker cannot prove: cyclic data structures, self-referential types, and some intrusive designs. Reference counting with interior mutability moves those checks to run time, and unsafe blocks remove them entirely while concentrating the risk in a small auditable region that a reviewer can enumerate.

Where the policy landscape stands

This stopped being a technical argument and became a procurement one. CISA and the FBI asked software makers serving critical infrastructure to publish a memory safety roadmap by January 1, 2026, and CISA and the NSA published joint adoption guidance in mid-2025.

Linux kernel maintainers ended Rust's experimental status in December 2025. Rust drivers now ship in Android, in file systems, and in a full GPU driver. Android reported that its memory safety vulnerability count fell substantially as new code moved to memory-safe languages, with the notable finding that the reduction tracked new code rather than rewritten old code.

Go, and the other end of the trade

Go gives memory safety by a different route: a garbage collector, bounds checking, and no pointer arithmetic. Spatial and temporal safety hold, at the cost of collector pauses and less control over layout.

Go's data race situation is worth stating precisely, because it is often overstated in both directions. Go does not prevent data races at compile time. A race on a map or a slice header is undefined behavior and can corrupt memory. The race detector finds races at run time with a five to ten times slowdown, and running it on the full test suite in continuous integration is the practical control.

The choice between them follows the constraint. Rust suits work needing predictable latency, minimal footprint, no collector, or a proof of thread safety: kernel code, embedded systems, cryptographic implementations, and latency-critical services. Go suits work needing fast development, easy concurrency, and a large standard library where a collector pause is acceptable: network services, command line tools, and most cloud infrastructure.

A memory safety migration that is achievable

Write new components in a memory-safe language, because Android's data shows that the reduction comes from new code rather than from rewrites. Rewrite in order of exposure: parsers first, then anything that handles untrusted input, then the rest, and accept that most of the rest never gets rewritten. Run sanitizers and continuous fuzzing on the code that remains, which Chapter 6 covers and which finds memory bugs faster than review. Use the foreign function interface to migrate a module at a time rather than a program at a time. And publish the roadmap, because a customer or a regulator will ask for it.

9.2 WebAssembly Beyond the Browser

WebAssembly is a portable binary instruction format with a sandboxed execution model. It left the browser because those two properties solve a server-side problem: running untrusted or multi-tenant code with strong isolation and a start time measured in microseconds.

The security model

A module executes against a linear memory, which is a contiguous byte array that the runtime bounds-checks on every access. A module cannot address memory outside it, cannot form a pointer to the host, and cannot execute data as code, because code and data are separate address spaces in the format.

More important is that a module has no ambient authority. It can call only the functions the host explicitly imports into it. A module with no imported file function cannot touch a file, not because a policy forbids it but because no mechanism exists. That is capability-based security, and it is a stronger property than a permission check, because there is nothing to bypass.

The trade against other isolation boundaries is worth stating plainly. A WebAssembly sandbox is enforced by the runtime, so a runtime vulnerability crosses it, and the runtime is a much smaller and more auditable component than a kernel or a hypervisor. A hypervisor boundary is stronger and costs two orders of magnitude more to start.

Table 9.1: isolation boundaries, compared for a per-request sandbox

Boundary Cold start Memory Enforced by Language support
Process plus seccomp 1 to 50 ms varies kernel any
Container 100 to 500 ms 30 to 200 MiB kernel namespaces any
MicroVM ~125 ms 5 MiB plus guest hypervisor any
V8 isolate 1 to 5 ms 2 to 10 MiB the JavaScript engine JS, TS, Wasm
WebAssembly module under 1 ms 1 to 5 MiB the Wasm runtime many, compiled

The component model and WASI 0.3

Raw WebAssembly exchanges only numbers. Passing a string between a module and its host means agreeing on a memory layout by convention, which is exactly the brittleness that made cross-language interoperation expensive for decades.

The component model fixes it with an interface definition language. A component declares its imports and exports with real types: strings, records, variants, lists, and resources. The toolchain generates the bindings, so a component written in Rust calls one written in Go through a typed interface with no glue code and no shared memory layout.

The Bytecode Alliance ratified WASI 0.3 in June 2026, rebasing the system interface on the component model's native asynchronous primitives with stream and future types and full socket support. That removed the impedance mismatch that made asynchronous work awkward in earlier versions, and it is what makes network servers a reasonable target rather than a workaround.

Where it fits, and where it does not

The honest 2026 assessment is that WebAssembly is production-ready for several specific shapes and is not a general replacement for containers.

It fits edge functions and serverless, where the sub-millisecond start is decisive and the work is short. It fits plugin systems, where a host runs third-party extensions and needs isolation without a process boundary: proxies, databases, and editors all use it this way. It fits any place untrusted code must run with a small trusted computing base. And it fits genuine portability, where the same artifact runs on different architectures and operating systems without recompilation.

It does not yet fit general backend microservices, and the honest reason is that threading support is unfinished. It also does not fit work needing direct hardware access, work needing a mature debugging story, or work whose ecosystem depends on libraries with no WebAssembly target.

The pattern worth noting is that WebAssembly appears most often as a component inside a system rather than as the system. A proxy's filter, a database's user-defined function, an edge function, a policy evaluator: each is a small piece of untrusted or multi-tenant logic inside a larger conventional service. That is where its properties are decisive, and it is a large and growing class.

9.3 Kernel-Level Software Innovation

eBPF made the kernel programmable. Before it, extending kernel behavior meant writing a module that can crash the machine, or patching the kernel and maintaining a fork. Neither was available to an application team.

Volume I covers the verifier, the maps, and the attach points, and Volume II covers the networking and observability applications. This section covers what eBPF means as an architectural choice.

Why an in-kernel verifier changes the economics

The safety argument is what makes eBPF usable by ordinary teams. Before a program loads, the kernel proves several properties: every memory access stays inside a known object, every loop terminates, and the program finishes within an instruction budget. A program that fails any of these never loads.

That proof converts kernel extension from a specialist activity with catastrophic failure modes into an ordinary one. A bug in an eBPF program is a load-time rejection or a wrong answer, not a kernel panic.

The Compile Once Run Everywhere mechanism solves the portability half. Kernel structure layouts differ between versions, so a program compiled against one kernel's headers normally does not run on another. CO-RE records the field accesses symbolically and the loader relocates them against the running kernel's own type information, so one binary runs across kernel versions.

What it displaced

The pattern across four domains is the same: a mechanism that previously required either a kernel module, a userspace proxy, or a linear rule walk became a small verifier-checked program with a hash map.

In networking, per-packet rule chains whose cost grew with rule count became constant-time map lookups, with programs attachable in the driver before a socket buffer is even allocated.

In observability, instrumentation that required modifying an application became attachment to an existing function, so a running process is profiled or traced with no restart and no code change.

In security, policy enforcement that required a kernel module or a static filter became a context-aware program at a security hook, loadable and unloadable at run time.

In scheduling, a policy change that required patching the scheduler became a loadable program, with a watchdog that returns the system to the built-in scheduler when the loaded one misbehaves.

The limits, stated honestly

Four constraints are permanent enough to design around.

The verifier is strict, and writing eBPF means writing code the verifier accepts, which is a smaller language than C. Complex logic hits the instruction budget or fails the bounds proof, and the answer is to keep the kernel program small and do the complex work in userspace.

Kernel version differences remain despite CO-RE, because a helper function or an attach point that does not exist cannot be relocated into existence. Feature detection at load time with a fallback path is necessary for anything that runs across a fleet.

Loading eBPF requires privilege, which makes the loader a trusted component and a target. The bpf system call itself is one of the calls a hardened seccomp profile blocks.

And the interface surface is large and moves, which is a maintenance cost for any project that depends deeply on it.

Where eBPF is the right tool, and where it is not

Use it when you need to see or affect something at a boundary you do not control: the kernel network path, the system call boundary, a running process you cannot modify, or the scheduler. Use it when the alternative is a linear rule walk whose cost grows with a count you expect to grow. Do not use it for application logic, for anything needing complex data structures, or for anything that a userspace library already does well. The rule of thumb is that the eBPF program collects or decides and userspace does the work, because that division respects the verifier and keeps the kernel program auditable.

9.4 Future-Proof Architectural Design Patterns

The previous eight chapters described technologies that will change. A system built on any of them needs to survive their replacement, and that survivability is a design property rather than a technology choice.

Design for replacement, not for permanence

The durable pattern is the one this book used throughout: define a boundary in terms of what a component does, implement it separately, and depend on the boundary rather than on the implementation.

Chapter 1 applied it to cryptography. Application code names a purpose, never an algorithm, and the algorithm travels with the artifact. Chapter 7 applied it to a quantum coprocessor. A solver interface with a classical implementation makes a future quantum backend a configuration change. Chapter 5 applied it to infrastructure. A platform interface lets the underlying orchestrator be replaced without every team rewriting.

Three properties make a boundary hold under replacement.

It is expressed in domain terms rather than in implementation terms. An interface named store_document survives a storage engine change. One named write_s3_object does not.

The data format carries its own version and its own algorithm identifier. Chapter 1 made this concrete for cryptography and it generalizes: any artifact that outlives the code that wrote it needs to say what produced it.

Every buffer and field is variable-length. A fixed 64 byte signature field was a reasonable decision for a decade and it is now a migration blocker, and the same reasoning applies to any size that current technology makes obvious.

The strangler pattern for genuine replacement

Replacing a component in a running system is the same shape at every scale, and it is the expand-and-contract sequence that Volume II describes for schema migration.

  1. INTERPOSE          all callers go through an interface, old impl behind it
     caller -> [ interface ] -> OLD

  2. DUAL PATH          new impl behind the same interface, off by default
     caller -> [ interface ] -> OLD
                          \--> NEW   (not called)

  3. SHADOW             call both, return old, compare and log differences
     caller -> [ interface ] -> OLD ----> returned
                          \--> NEW ----> compared, logged
     exit condition: mismatch rate zero over a full business cycle

  4. CANARY             route a growing percentage to new, by key or by weight
     caller -> [ interface ] -> 95% OLD
                          \--> 5%  NEW
     exit condition: metrics equal or better at each step

  5. SWITCH             new is primary, old still present and reachable
  6. CONTRACT           remove old, after a soak long enough to roll back

  Every step is independently revertible. The only irreversible step is
  the last one, and it happens after nothing has used the old path for
  a period long enough to be confident.

Figure 9.1 Replacing a core component without a rewrite.

The structure does not change with the size of the change. Replacing a hashing function, a storage engine, a message broker, or a cloud provider all follow the same six steps, and the difference is only how long each one takes.

Modularity that survives

A modular system is one where a module can be replaced without understanding the rest. Two properties produce that and both are enforceable rather than aspirational.

Explicit boundaries, enforced by a tool. Language modules, package visibility, separate compilation units, or an architecture test that fails the build on a forbidden dependency. A convention that a reviewer enforces decays, because reviewers change and deadlines arrive.

No shared mutable state across a boundary. Two modules that share a database table, a global variable, or a cache share a schema, and neither can change it alone. This is the distributed monolith failure that Volume I describes, applied inside one process.

The measure of modularity is a question rather than a metric: how much of the system must a new engineer understand to change one module safely? If the answer is most of it, the boundaries are decorative.

Reversibility as the organizing principle

The single most useful property in a system facing uncertain technology change is that decisions are reversible.

A feature flag makes a behavior change reversible in seconds. A routing weight makes a deployment reversible faster than it was made. An expand-and-contract migration makes a schema change reversible at every step but the last. An abstraction over a vendor makes a vendor choice reversible at the cost of a new implementation rather than a rewrite.

The corollary is worth stating as a rule. Spend design effort in proportion to how hard a decision is to reverse. A data model, a public interface, a partition key, and a cryptographic artifact format are expensive to change, and they deserve care. A caching strategy, an internal module boundary, and a library choice are cheap to change, and deliberating over them is a poor use of the same effort.

What is likely to hold

Prediction is unreliable, so here is the reasoning rather than the conclusion. Four things in the current stack are durable because they are constrained by physics, by economics, or by an installed base rather than by fashion.

The memory hierarchy and its ratios, which Volume I establishes, follow from physics and from cost. The numbers move and the ratios do not.

The speed of light bounds distributed systems, so consensus across regions will always cost tens of milliseconds and no protocol improvement changes it.

Memory safety in systems programming is now a procurement requirement in several jurisdictions, which is a much stronger constraint than a technical preference.

And three patterns already survived several technology cycles: the append-only log as an integration primitive, the reconciliation loop as a control pattern, and the capability as a security model. That is the best available evidence that they survive another.

The specific technologies in this book will be replaced. The mechanisms under them, and the discipline of designing so that replacement is possible, are what carry forward.

Sources and further reading

The CISA and NSA joint guidance on memory-safe languages, and the CISA and FBI memory safety roadmap request. The Android security blog on memory safety vulnerability trends and the finding that reduction tracks new code. The Rust Reference and the Rustonomicon for the ownership model at its edges. The WebAssembly core specification, the component model specification, and the WASI 0.3 material from the Bytecode Alliance. The Cilium eBPF reference guide and the kernel documentation on the verifier and on CO-RE. Martin Fowler on the strangler fig pattern. Sam Newman, "Monolith to Microservices", for extraction sequencing. Michael Nygard, "Documenting Architecture Decisions", for recording what was decided and why.

What this chapter established

  • Roughly 70 percent of serious vulnerabilities in large C and C++ code bases are memory safety defects, and that figure did not respond to better tooling, review, or training.
  • Mitigations convert exploitation into a crash. Only a language change removes the class.
  • Three ownership rules give Rust spatial safety, temporal safety, and freedom from data races, proved at compile time with no run-time cost for the proof.
  • Go gives memory safety through a collector and bounds checks, and does not prevent data races. Run the race detector on the full suite in continuous integration.
  • Android's data shows that the vulnerability reduction tracks new code rather than rewrites, which is the argument for writing new components in a safe language rather than planning a rewrite.
  • A WebAssembly module has no ambient authority. It calls only what the host imports, which is stronger than a permission check because there is nothing to bypass.
  • WASI 0.3 rebased the interface on native asynchronous primitives in June 2026, which made network servers a reasonable target.
  • WebAssembly fits edge functions, plugin systems, and untrusted code with a small trusted computing base. Threading is the unfinished part that keeps general backend services out of reach.
  • eBPF converts kernel extension from a specialist activity with catastrophic failure modes into an ordinary one, because the verifier proves termination and memory safety before load.
  • Keep the kernel program small and do the complex work in userspace. That division respects the verifier and keeps the program auditable.
  • Name boundaries in domain terms, version every artifact with the algorithm that produced it, and make every buffer variable-length.
  • Replacement follows the same six steps at every scale: interpose, dual path, shadow, canary, switch, contract. Only the last step is irreversible.
  • Enforce module boundaries with a tool, because a convention that a reviewer enforces decays.
  • Spend design effort in proportion to how hard a decision is to reverse, and treat reversibility as the organizing principle under technological uncertainty.
  • The specific technologies in this book will be replaced. The mechanisms and the discipline of designing for replacement are what carry forward.

Chapter 9 of Emerging Systems Engineering, revision 1.0. Attribution and sole author credit: Ayoub Zulfiqar.