lib/machine/src/instance/owner.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const accelerator = @import("machine_accelerator");
3 const checkpoint = @import("../checkpoint/root.zig");
4 const core = @import("machine_instance_core");
5 const reference = @import("machine_reference");
6 const os = @import("os");
7 const delivery = @import("delivery.zig");
8 const image_admission = @import("admission.zig");
9 const input_admission = @import("../admission/root.zig");
10 const profile = @import("../profile/root.zig");
11 const quiescence_receipt = @import("receipt/root.zig");
12 const types = @import("types.zig");
13
14 const backend = core.backend;
15 const layout = core.layout;
16 const manifest = os.boot.kernel.manifest;
17 const protection = core.protection;
18
19 const BackendKind = enum(u8) {
20 none,
21 accelerator,
22 reference,
23 };
24
25 const MemoryKind = enum(u8) {
26 none,
27 linear,
28 branch,
29 };
30
31 const backend_storage_alignment = @max(
32 accelerator.storage_alignment,
33 reference.storage_alignment,
34 );
35 const backend_storage_bytes = @max(
36 accelerator.storage_bytes,
37 reference.storage_bytes,
38 );
39 pub const run_stutter_limit: usize = 128;
40
41 var next_session_identity = std.atomic.Value(u64).init(1);
42
43 const ActivationStage = enum(u8) {
44 ready,
45 prompt,
46 complete,
47 };
48
49 const InputStage = enum(u8) {
50 semantic,
51 terminal,
52 block_root,
53 quiescence,
54 complete,
55 };
56
57 const Pending = struct {
58 delivery: input_admission.Delivery,
59 request_frontiers: os.abi.ring.Frontiers,
60 event_frontiers: os.abi.ring.Frontiers,
61 request_sequence: u64,
62 final_event_sequence: u64,
63 stage: InputStage,
64 block_root: ?os.abi.BlockRoot,
65 quiescence: ?os.abi.Quiescence,
66 event_transcript_digest: os.abi.Digest,
67 semantic_transcript_digest: os.abi.Digest,
68 };
69
70 const CommittedTurn = struct {
71 basis: input_admission.Basis,
72 block_root: os.abi.BlockRoot,
73 terminal_offset: u64,
74 semantic_frontier: u64,
75 root_generation: u32,
76 terminal_count: u8,
77 receipt: types.QuiescenceReceipt,
78 };
79
80 const RestoreTargetPreparation = union(enum) {
81 accelerator,
82 reference: reference.PreparedRestore,
83 };
84
85 const RestorePreparation = struct {
86 backend: backend.Prepared,
87 target: RestoreTargetPreparation,
88 };
89
90 const ExpectedSemantic = struct {
91 interface: [16]u8,
92 event: u32,
93 position: u64,
94 bytes: []const u8,
95 };
96
97 const OwnerState = struct {
98 session_identity: u64 = 0,
99 ram_address: usize = 0,
100 memory_identity: usize = 0,
101 memory_kind: MemoryKind = .none,
102 phase: types.RunPhase = .closed,
103 active_backend: BackendKind = .none,
104 manifest_bytes: u32 = 0,
105 manifest: [os.boot.kernel.manifest.encoded_bytes_max]u8 = undefined,
106 contract: profile.ContractFingerprint = undefined,
107 profile_fingerprint: profile.ProfileFingerprint = undefined,
108 execution_fingerprint: types.ExecutionFingerprint = undefined,
109 initial: manifest.InitialState = undefined,
110 immutable_image: core.provenance.ImmutableImage = undefined,
111 fence: os.abi.ActivationFence = undefined,
112 image_digest: os.abi.Digest = undefined,
113 source_root: os.abi.Digest = undefined,
114 frontiers: input_admission.Frontiers = undefined,
115 outstanding_effect: ?input_admission.EffectRequest = null,
116 block_root: os.abi.Digest = undefined,
117 terminal_offset: u64 = 0,
118 semantic_frontier: u64 = 0,
119 root_generation: u32 = 0,
120 terminal_count: u8 = 0,
121 activation_stage: ActivationStage = .ready,
122 pending: ?Pending = null,
123 quiescence_receipt: ?types.QuiescenceReceipt = null,
124 branch: checkpoint.roots.branch.Branch = undefined,
125 backend: [backend_storage_bytes]u8 align(backend_storage_alignment) = undefined,
126 };
127
128 pub const storage_alignment: usize = @alignOf(OwnerState);
129 pub const storage_bytes: usize = @sizeOf(OwnerState);
130
131 /// Holds the bytes a caller sets aside for one running instance, so the caller
132 /// supplies the storage and holds it still. The address has to stay put from
133 /// the moment it is built until `deinit`. Once a lifecycle closes, the same
134 /// bytes serve the next one.
135 pub const Storage = struct {
136 bytes: [storage_bytes]u8 align(storage_alignment),
137
138 /// Hands back closed storage waiting for a first start or restore, so a
139 /// caller starts storage setup here. The call invokes no allocator.
140 pub fn init() Storage {
141 var result: Storage = undefined;
142 ownerState(&result).* = .{};
143 return result;
144 }
145 };
146
147 const OwnerError = error{
148 StorageInUse,
149 StorageBytesMismatch,
150 StorageAliasesRam,
151 UnsupportedBackend,
152 ImageAliasesRam,
153 OutputAliasesInstance,
154 OutputAliasesOwner,
155 OutputAliasesRam,
156 StateCapacityExceeded,
157 };
158
159 const RestoreOwnerError = error{
160 CheckpointContractMismatch,
161 ManifestAliasesCheckpoint,
162 ManifestAliasesRam,
163 ManifestAliasesStorage,
164 RamAliasesCheckpoint,
165 StorageAliasesCheckpoint,
166 };
167
168 const ProtocolError = error{
169 AcknowledgementRequired,
170 ActivationGenerationStale,
171 ActivationEventsPending,
172 ActivationTokenStale,
173 ActivationWorldMismatch,
174 Closed,
175 DeliveryReceiptMismatch,
176 EventDrainRequired,
177 EventReceiptMismatch,
178 InputAlreadyDelivered,
179 InputRequired,
180 InputUnavailable,
181 ReactivationRequired,
182 UnexpectedDoorbell,
183 UnexpectedEvent,
184 QuiescenceUnavailable,
185 };
186
187 const QuiescenceProtocolError = error{
188 Closed,
189 EventReceiptMismatch,
190 QuiescenceUnavailable,
191 };
192
193 const MachineError = OwnerError || backend.InitFailure;
194
195 pub const InitError = MachineError ||
196 profile.Error ||
197 image_admission.Error ||
198 input_admission.Error ||
199 os.abi.boot.Error ||
200 os.abi.ring.Error ||
201 layout.Error;
202
203 pub const RunError = backend.RunFailure || ProtocolError;
204 pub const MemoryError = core.memory.Error || error{Closed};
205 pub const DeliveryError = input_admission.Error ||
206 delivery.Error || os.abi.ring.Error || ProtocolError || core.memory.Error;
207 pub const EventError = os.abi.message.Error ||
208 os.abi.ring.Error || ProtocolError || OwnerError || core.memory.Error;
209 pub const AcknowledgeError = input_admission.Error ||
210 os.abi.ring.Error || quiescence_receipt.Error || ProtocolError || OwnerError ||
211 core.memory.Error;
212 pub const QuiescenceReceiptError = os.abi.ring.Error ||
213 quiescence_receipt.Error || QuiescenceProtocolError || core.memory.Error;
214 pub const BasisError = ProtocolError || os.abi.ring.Error || core.memory.Error;
215 pub const ReactivateError = os.abi.boot.Error ||
216 os.abi.ring.Error || backend.RestartFailure || ProtocolError ||
217 OwnerError || layout.Error;
218 pub const CaptureCheckpointError = QuiescenceReceiptError || OwnerError ||
219 checkpoint.Error || core.provenance.Error || layout.Error || core.memory.Error;
220 pub const CaptureHotError = QuiescenceReceiptError || OwnerError ||
221 checkpoint.Error || core.provenance.Error || layout.Error || core.memory.Error;
222 pub const RestoreError = MachineError || RestoreOwnerError ||
223 profile.Error || image_admission.RestoreError || checkpoint.Error ||
224 checkpoint.roots.branch.Error || os.abi.boot.Error || os.abi.ring.Error ||
225 layout.Error;
226
227 /// Reports how a cold start turned out, so the caller learns the state of its
228 /// buffers across the three outcomes. On ready, the instance holds the caller's
229 /// storage and RAM until `deinit`. On unavailable, the storage stays closed and
230 /// the RAM is untouched. On rejected, the value names the validation or backend
231 /// failure.
232 pub const StartResult = union(enum) {
233 ready: Instance,
234 unavailable: types.Unavailable,
235 rejected: InitError,
236 };
237
238 /// Reports how a restore turned out, so the caller learns the state of the
239 /// restored buffers across the three outcomes. On ready, the instance holds the
240 /// caller's storage and whatever backs its memory until `deinit`. On
241 /// unavailable, the storage stays closed and the destination RAM is untouched.
242 /// On rejected, the value names the restore failure.
243 pub const RestoreResult = union(enum) {
244 ready: Instance,
245 unavailable: types.Unavailable,
246 rejected: RestoreError,
247 };
248
249 /// Accepts exactly one byte count, the value of `storage_bytes`, so a caller
250 /// allocating storage knows the size is right before starting anything. Any
251 /// other count comes back as `StorageBytesMismatch`.
252 pub fn validateStorageBytes(bytes: usize) error{StorageBytesMismatch}!void {
253 if (bytes != storage_bytes) return error.StorageBytesMismatch;
254 }
255
256 /// Operates one K0 guest that is running now, borrowing its storage and
257 /// whatever backs its guest memory, so every lifecycle call goes through this
258 /// handle. Each borrowed region keeps its address until `deinit`. Starting a
259 /// new lifecycle in the same storage makes copied handles stale, because the
260 /// session identity moves on.
261 pub const Instance = struct {
262 storage: *Storage,
263 ram: []align(layout.page_bytes) u8,
264 memory: core.memory.Access,
265 session_identity: u64,
266
267 /// Boots K0 from an ELF image and its execution manifest under the profile
268 /// the input names, so a caller performs a cold start of a guest. That
269 /// profile decides whether Linux KVM or the reference interpreter carries
270 /// the guest. A ready outcome keeps `storage` and exactly `ram_bytes` bytes
271 /// of RAM until `deinit`. Anything the validator turns away, and any
272 /// backend that fails to come up, comes back as rejected. A host lacking
273 /// the backend comes back as unavailable.
274 pub fn init(
275 storage: *Storage,
276 ram: []align(layout.page_bytes) u8,
277 input: types.Input,
278 ) StartResult {
279 return switch (input.profile.backend) {
280 .linux_kvm_single_vcpu_v1 => initSelected(
281 storage,
282 ram,
283 input,
284 .accelerator,
285 null,
286 accelerator.start,
287 ),
288 .portable_x86_64_interpreter_v1 => initSelected(
289 storage,
290 ram,
291 input,
292 .reference,
293 null,
294 reference.start,
295 ),
296 };
297 }
298
299 /// Rebuilds the guest named by the expected checkpoint root inside
300 /// caller-owned RAM, under the selected profile, so the caller brings a
301 /// captured guest back into RAM it owns. A ready outcome keeps the storage
302 /// and the RAM until `deinit`. A host lacking the backend comes back as
303 /// unavailable, before anything reaches RAM. Every other failure comes back
304 /// as rejected.
305 pub fn restore(
306 storage: *Storage,
307 ram: []align(layout.page_bytes) u8,
308 input: types.RestoreInput,
309 ) RestoreResult {
310 return switch (input.profile.backend) {
311 .linux_kvm_single_vcpu_v1 => restoreSelected(
312 storage,
313 ram,
314 input,
315 .accelerator,
316 null,
317 accelerator.acquireRestore,
318 ),
319 .portable_x86_64_interpreter_v1 => restoreSelected(
320 storage,
321 ram,
322 input,
323 .reference,
324 null,
325 null,
326 ),
327 };
328 }
329
330 /// Brings the portable interpreter up over pages read from a store that
331 /// authenticates them, so a caller restores a captured guest through store
332 /// pages. The root provider and every buffer of branch storage have to
333 /// outlive the instance, up to `deinit`. Each write takes a branch page
334 /// from a fixed supply, and running out comes back as
335 /// `MemoryCapacityExceeded`.
336 pub fn restoreShared(
337 storage: *Storage,
338 root_storage: checkpoint.roots.Storage,
339 branch_storage: checkpoint.roots.branch.Storage,
340 input: types.SharedRestoreInput,
341 ) RestoreResult {
342 return restoreSharedSelected(
343 storage,
344 root_storage,
345 branch_storage,
346 input,
347 );
348 }
349
350 /// Gives up whatever the backend holds and closes this lifecycle, so the
351 /// caller gets its buffers back. Calling it twice, or calling it on a stale
352 /// copied handle, does nothing. Afterward the storage and the memory
353 /// backing are the caller's to use again.
354 pub fn deinit(self: *@This()) void {
355 const owner = ownerState(self.storage);
356 if (!ownsLifecycle(self, owner)) return;
357 if (owner.phase == .closed) return;
358 switch (owner.active_backend) {
359 .accelerator => accelerator.deinit(backendState(owner)),
360 .reference => reference.deinit(backendState(owner)),
361 .none => {},
362 }
363 owner.phase = .closed;
364 owner.active_backend = .none;
365 owner.manifest_bytes = 0;
366 owner.ram_address = 0;
367 owner.memory_identity = 0;
368 owner.memory_kind = .none;
369 owner.quiescence_receipt = null;
370 }
371
372 /// Says whether this handle is the one driving the lifecycle its storage
373 /// currently holds, so a caller can tell whether a copied handle remains
374 /// the live one.
375 pub fn active(self: *const @This()) bool {
376 const owner = ownerState(self.storage);
377 return ownsLifecycle(self, owner) and owner.phase != .closed;
378 }
379
380 /// Names the stage the lifecycle has reached, so the caller knows which
381 /// calls are legal right now. A closed storage and a stale handle both
382 /// answer `closed`.
383 pub fn phase(self: *const @This()) types.RunPhase {
384 const owner = ownerState(self.storage);
385 if (!ownsLifecycle(self, owner)) return .closed;
386 return owner.phase;
387 }
388
389 /// Copies bytes out of guest memory, starting at a byte offset counted from
390 /// zero, into a buffer the caller owns, so the caller can read guest state
391 /// from outside the guest. A closed handle, and any failure reaching the
392 /// memory, come back as `MemoryError`.
393 pub fn readMemory(
394 self: *const @This(),
395 address: usize,
396 output: []u8,
397 ) MemoryError!void {
398 const owner = ownerState(self.storage);
399 if (!ownsLifecycle(self, owner)) return error.Closed;
400 return self.memory.read(address, output);
401 }
402
403 /// Copies caller-owned bytes into guest memory at a byte offset counted
404 /// from zero, so the caller can change guest state from outside the guest.
405 /// A closed handle, a range outside the guest, a page that fails its digest
406 /// check, and exhausted branch storage all come back as `MemoryError`.
407 pub fn writeMemory(
408 self: *@This(),
409 address: usize,
410 input: []const u8,
411 ) MemoryError!void {
412 const owner = ownerState(self.storage);
413 if (!ownsLifecycle(self, owner)) return error.Closed;
414 return self.memory.write(address, input);
415 }
416
417 /// Counts the writable guest pages this instance has resident, so the
418 /// caller can see how many pages a shared restore has copied. A guest
419 /// living in the caller's RAM counts every page of it. A guest restored
420 /// from a store counts the pages copied into branch storage.
421 pub fn privatePageCount(self: *const @This()) MemoryError!u16 {
422 const owner = ownerState(self.storage);
423 if (!ownsLifecycle(self, owner)) return error.Closed;
424 return switch (owner.memory_kind) {
425 .linear => @intCast(checkpoint.page_count),
426 .branch => owner.branch.privatePageCount(),
427 .none => error.Closed,
428 };
429 }
430
431 /// Measures what this instance's guest memory holds in bytes, so the caller
432 /// can determine what one instance is holding. A guest living in the
433 /// caller's RAM measures `ram_bytes`. A guest restored from a store adds up
434 /// its branch bookkeeping, the digests it has authenticated, and the pages
435 /// it has copied.
436 pub fn residentMemoryBytes(self: *const @This()) MemoryError!u64 {
437 const owner = ownerState(self.storage);
438 if (!ownsLifecycle(self, owner)) return error.Closed;
439 return switch (owner.memory_kind) {
440 .linear => layout.ram_bytes,
441 .branch => owner.branch.residentBytes(),
442 .none => error.Closed,
443 };
444 }
445
446 /// Lets the selected backend carry the guest until it reaches one boundary
447 /// this namespace reports, so the caller advances the guest. A doorbell
448 /// taken while the guest is ready, and one taken while it is quiescent,
449 /// both move the lifecycle along. Any other boundary that comes back puts
450 /// the lifecycle into its failed stage. A boundary carrying no guest
451 /// progress is passed over and the backend is asked again, up to a limit.
452 /// The limit is 128 tries, and reaching it comes back as `WouldBlock`.
453 pub fn run(self: *@This()) RunError!types.Exit {
454 const owner = ownerState(self.storage);
455 if (!ownsLifecycle(self, owner)) return error.Closed;
456 const running_phase = owner.phase;
457 switch (running_phase) {
458 .booting, .input_delivered => {},
459 .draining_activation => return error.EventDrainRequired,
460 .awaiting_input => return error.InputRequired,
461 .draining_input => return error.EventDrainRequired,
462 .awaiting_acknowledgement => return error.AcknowledgementRequired,
463 .awaiting_reactivation => return error.ReactivationRequired,
464 .closed, .failed, .capturing_checkpoint, .completing_io => return error.Closed,
465 }
466 for (0..run_stutter_limit) |_| {
467 const raw = runBackend(owner) catch |failure| {
468 owner.phase = .failed;
469 return mapRunFailure(failure);
470 };
471 if (std.meta.activeTag(raw) == .stutter) continue;
472 const normalized = normalize(raw);
473 if (std.meta.activeTag(raw) == .io) {
474 owner.phase = .completing_io;
475 completePendingIo(owner) catch |failure| {
476 owner.phase = .failed;
477 return mapRunFailure(failure);
478 };
479 owner.phase = running_phase;
480 }
481 return applyExit(owner, running_phase, normalized);
482 }
483 return error.WouldBlock;
484 }
485
486 /// Reports the position an input is admitted against once the events the
487 /// activation produced have been taken, so the caller obtains the guest
488 /// position needed to admit an input. The guest has to be waiting for
489 /// input. Both rings have to be settled, the one carrying requests and the
490 /// one carrying events.
491 pub fn admissionBasis(
492 self: *const @This(),
493 ) BasisError!input_admission.Basis {
494 const owner = ownerState(self.storage);
495 if (!ownsLifecycle(self, owner)) return error.Closed;
496 if (owner.phase != .awaiting_input) return error.InputUnavailable;
497 if (owner.activation_stage != .complete) {
498 return error.ActivationEventsPending;
499 }
500 const requests = try os.abi.RequestRing.frontiers(
501 try layout.requestRingAccess(self.memory),
502 owner.fence,
503 );
504 const events = try os.abi.EventRing.frontiers(
505 try layout.eventRingAccess(self.memory),
506 owner.fence,
507 );
508 if (requests.consumed != 0 or requests.produced != 0 or
509 events.consumed != os.k0.events_per_activation or
510 events.produced != os.k0.events_per_activation)
511 {
512 return error.EventReceiptMismatch;
513 }
514 return basis(owner);
515 }
516
517 /// Checks one admitted input against the position and the authority in
518 /// force, then writes it where the guest reads, so an admitted input
519 /// reaches the guest. The delivery value is copied during the call. A phase
520 /// that forbids the call, a ring that will not take the input, a memory
521 /// failure, and an admission that does not check out all leave the turn
522 /// unacknowledged.
523 pub fn deliverAdmitted(
524 self: *@This(),
525 value: *const input_admission.Delivery,
526 ) DeliveryError!void {
527 const owner = ownerState(self.storage);
528 if (!ownsLifecycle(self, owner)) return error.Closed;
529 const owned = value.*;
530 if (owner.phase == .input_delivered or owner.phase == .draining_input) {
531 return error.InputAlreadyDelivered;
532 }
533 if (owner.phase == .awaiting_acknowledgement) {
534 return error.AcknowledgementRequired;
535 }
536 if (owner.phase != .awaiting_input) return error.InputUnavailable;
537 if (owner.activation_stage != .complete) {
538 return error.ActivationEventsPending;
539 }
540 try input_admission.verifyDelivery(basis(owner), owner.fence, &owned);
541 const requests = try layout.requestRingAccess(self.memory);
542 const events = try layout.eventRingAccess(self.memory);
543 const request_frontiers = try os.abi.RequestRing.frontiers(
544 requests,
545 owner.fence,
546 );
547 const event_frontiers = try os.abi.EventRing.frontiers(
548 events,
549 owner.fence,
550 );
551 if (request_frontiers.consumed != 0 or
552 request_frontiers.produced != 0 or
553 event_frontiers.consumed != os.k0.events_per_activation or
554 event_frontiers.produced != os.k0.events_per_activation)
555 {
556 return error.EventReceiptMismatch;
557 }
558 const request_sequence = std.math.add(
559 u64,
560 request_frontiers.produced,
561 1,
562 ) catch return error.SequenceExhausted;
563 const event_count: u64 = switch (owned.admission.record) {
564 .terminal => os.k0.events_per_terminal_input,
565 else => os.k0.events_per_nonterminal_input,
566 };
567 const final_event_sequence = std.math.add(
568 u64,
569 event_frontiers.produced,
570 event_count,
571 ) catch return error.SequenceExhausted;
572 var wire: os.abi.MessageWire = undefined;
573 try delivery.encode(
574 owner.fence,
575 request_sequence,
576 &owned.admission,
577 &wire,
578 );
579 try os.abi.RequestRing.push(requests, owner.fence, &wire);
580 owner.pending = .{
581 .delivery = owned,
582 .request_frontiers = request_frontiers,
583 .event_frontiers = event_frontiers,
584 .request_sequence = request_sequence,
585 .final_event_sequence = final_event_sequence,
586 .stage = .semantic,
587 .block_root = null,
588 .quiescence = null,
589 .event_transcript_digest = @splat(0),
590 .semantic_transcript_digest = @splat(0),
591 };
592 owner.phase = .input_delivered;
593 }
594
595 /// Moves every event of the current doorbell's group into a buffer the
596 /// caller owns, for the caller has to take them before the turn can be
597 /// acknowledged. That buffer may not overlap the handle, its storage, or
598 /// guest memory. When validation fails, the ring keeps its entries.
599 pub fn takeEvents(
600 self: *@This(),
601 output: *types.EventBatch,
602 ) EventError!void {
603 const owner = ownerState(self.storage);
604 if (!ownsLifecycle(self, owner)) return error.Closed;
605 const output_bytes = std.mem.asBytes(output);
606 if (image_admission.buffersOverlap(output_bytes, std.mem.asBytes(self))) {
607 return error.OutputAliasesInstance;
608 }
609 if (image_admission.buffersOverlap(output_bytes, &self.storage.bytes)) {
610 return error.OutputAliasesOwner;
611 }
612 if (self.memory.aliases(output_bytes)) {
613 return error.OutputAliasesRam;
614 }
615 if (owner.phase == .draining_activation) {
616 return takeActivationEvents(owner, self.memory, output);
617 }
618 if (owner.phase != .draining_input) return error.InputUnavailable;
619 return takeInputEvents(owner, self.memory, output);
620 }
621
622 /// Closes a turn whose events have all been taken, after the delivery
623 /// digest matches and both ring cursors have settled, so the caller
624 /// concludes the turn. Closing it builds the evidence for the quiescent
625 /// turn and moves the lifecycle to `awaiting_reactivation`.
626 pub fn acknowledge(
627 self: *@This(),
628 receipt: input_admission.DeliveryReceipt,
629 ) AcknowledgeError!void {
630 const owner = ownerState(self.storage);
631 if (!ownsLifecycle(self, owner)) return error.Closed;
632 if (owner.phase != .awaiting_acknowledgement) {
633 return error.InputUnavailable;
634 }
635 const pending = owner.pending orelse return error.InputUnavailable;
636 if (pending.stage != .complete) return error.EventDrainRequired;
637 if (!std.meta.eql(receipt, pending.delivery.receipt)) {
638 return error.DeliveryReceiptMismatch;
639 }
640 const settled = try settledRings(owner, self.memory, pending);
641 const committed = try prepareCommit(owner, pending, settled);
642 owner.source_root = committed.basis.source_root;
643 owner.frontiers = committed.basis.frontiers;
644 owner.outstanding_effect = committed.basis.outstanding_effect;
645 owner.block_root = committed.block_root.digest;
646 owner.terminal_offset = committed.terminal_offset;
647 owner.semantic_frontier = committed.semantic_frontier;
648 owner.root_generation = committed.root_generation;
649 owner.terminal_count = committed.terminal_count;
650 owner.pending = null;
651 owner.quiescence_receipt = committed.receipt;
652 owner.phase = .awaiting_reactivation;
653 }
654
655 /// Hands back the checked evidence for the turn that has settled, so the
656 /// caller receives the receipt for the completed turn. The guest has to be
657 /// waiting for reactivation. Both rings have to stand where the evidence
658 /// says they stand.
659 pub fn quiescenceReceipt(
660 self: *const @This(),
661 ) QuiescenceReceiptError!types.QuiescenceReceipt {
662 const owner = ownerState(self.storage);
663 if (!ownsLifecycle(self, owner)) return error.Closed;
664 if (owner.phase != .awaiting_reactivation) {
665 return error.QuiescenceUnavailable;
666 }
667 const value = owner.quiescence_receipt orelse
668 return error.QuiescenceUnavailable;
669 try quiescence_receipt.verifyForFence(value, owner.fence);
670 try validateReceiptOwner(owner, value);
671 const requests = try os.abi.RequestRing.frontiers(
672 try layout.requestRingAccess(self.memory),
673 owner.fence,
674 );
675 const events = try os.abi.EventRing.frontiers(
676 try layout.eventRingAccess(self.memory),
677 owner.fence,
678 );
679 if (requests.consumed != value.settled.request_cursor or
680 requests.produced != value.settled.request_cursor or
681 events.consumed != value.settled.event_cursor or
682 events.produced != value.settled.event_cursor)
683 {
684 return error.EventReceiptMismatch;
685 }
686 return value;
687 }
688
689 /// Writes the settled guest state into checkpoint storage and destination
690 /// RAM the caller owns, so the caller turns settled state into a retained
691 /// checkpoint. Both have to stay alive for as long as the checkpoint is
692 /// used. Buffers that overlap, evidence that does not check out, memory
693 /// that cannot be read, and a checkpoint that will not build all turn the
694 /// capture away.
695 pub fn captureCheckpoint(
696 self: *@This(),
697 storage: *checkpoint.Storage,
698 ram: []align(layout.page_bytes) u8,
699 ) CaptureCheckpointError!checkpoint.Checkpoint {
700 const owner = ownerState(self.storage);
701 if (!ownsLifecycle(self, owner)) return error.Closed;
702 try layout.validateRamBytes(ram.len);
703 if (image_admission.buffersOverlap(ram, std.mem.asBytes(self))) {
704 return error.OutputAliasesInstance;
705 }
706 if (image_admission.buffersOverlap(ram, &self.storage.bytes)) {
707 return error.OutputAliasesOwner;
708 }
709 if (self.memory.aliases(ram)) {
710 return error.OutputAliasesRam;
711 }
712 if (image_admission.buffersOverlap(&storage.bytes, std.mem.asBytes(self))) {
713 return error.StorageAliasesInstance;
714 }
715 if (image_admission.buffersOverlap(&storage.bytes, &self.storage.bytes)) {
716 return error.StorageAliasesOwner;
717 }
718 if (self.memory.aliases(&storage.bytes)) {
719 return error.StorageAliasesRam;
720 }
721 if (image_admission.buffersOverlap(&storage.bytes, ram)) {
722 return error.MemoryAliasesStorage;
723 }
724 try checkpoint.ensureAvailable(storage);
725 const receipt_value = try self.quiescenceReceipt();
726 owner.phase = .capturing_checkpoint;
727 defer owner.phase = .awaiting_reactivation;
728 const material = try checkpointMaterial(owner, receipt_value);
729 if (self.memory.linearRam()) |linear| {
730 return checkpoint.publish(material, storage, linear, ram);
731 }
732 try self.memory.read(0, ram);
733 const memory = try checkpoint.validatedMemoryDigest(
734 ram,
735 material.immutable_image,
736 );
737 const identity = try checkpoint.identify(material, memory);
738 var candidate = try checkpoint.beginMaterialization(storage, ram);
739 return checkpoint.publishMaterialized(
740 &candidate,
741 material,
742 identity.root,
743 memory,
744 );
745 }
746
747 /// Captures the pages that differ from a parent checkpoint the caller owns,
748 /// so the caller records changed pages against a durable parent. What comes
749 /// back borrows that parent and the hot storage passed in. A guest whose
750 /// memory comes from a store answers `MemoryReadFailed`.
751 pub fn captureHot(
752 self: *@This(),
753 parent: *const checkpoint.Checkpoint,
754 storage: checkpoint.hot.Storage,
755 ) CaptureHotError!checkpoint.hot.Snapshot {
756 const owner = ownerState(self.storage);
757 if (!ownsLifecycle(self, owner)) return error.Closed;
758 const index_bytes = std.mem.sliceAsBytes(storage.indices);
759 if (image_admission.buffersOverlap(index_bytes, std.mem.asBytes(self)) or
760 image_admission.buffersOverlap(storage.pages, std.mem.asBytes(self)))
761 {
762 return error.OutputAliasesInstance;
763 }
764 if (image_admission.buffersOverlap(index_bytes, &self.storage.bytes) or
765 image_admission.buffersOverlap(storage.pages, &self.storage.bytes))
766 {
767 return error.OutputAliasesOwner;
768 }
769 const receipt_value = try self.quiescenceReceipt();
770 owner.phase = .capturing_checkpoint;
771 defer owner.phase = .awaiting_reactivation;
772 const linear = self.memory.linearRam() orelse
773 return error.MemoryReadFailed;
774 return checkpoint.hot.capture(
775 parent,
776 try checkpointMaterial(owner, receipt_value),
777 linear,
778 storage,
779 );
780 }
781
782 /// Sets a settled instance running again under a newer authority, so the
783 /// caller installs the authority required for the next turn. The new
784 /// authority names the same world, counts one generation higher, and
785 /// carries a different token. When it succeeds the lifecycle stands at
786 /// `booting` again.
787 pub fn reactivate(
788 self: *@This(),
789 fence: os.abi.ActivationFence,
790 ) ReactivateError!void {
791 const owner = ownerState(self.storage);
792 if (!ownsLifecycle(self, owner)) return error.Closed;
793 if (owner.phase != .awaiting_reactivation) {
794 return error.InputUnavailable;
795 }
796 try os.abi.wire.validateFence(fence);
797 if (!std.mem.eql(u8, &owner.fence.world, &fence.world)) {
798 return error.ActivationWorldMismatch;
799 }
800 if (fence.generation <= owner.fence.generation) {
801 return error.ActivationGenerationStale;
802 }
803 if (std.mem.eql(u8, &owner.fence.token, &fence.token)) {
804 return error.ActivationTokenStale;
805 }
806 _ = std.math.add(u64, owner.terminal_offset, os.k0.ready_prompt.len) catch
807 return error.StateCapacityExceeded;
808 const frame = continuationFrame(owner, fence);
809 var wire: os.abi.BootWire = undefined;
810 try os.abi.encodeBootFrame(frame, &wire);
811 restartBackend(owner, self.memory) catch |failure| {
812 owner.phase = .failed;
813 return failure;
814 };
815 try layout.reactivateAccess(self.memory, frame, &wire);
816 owner.fence = fence;
817 owner.activation_stage = .ready;
818 owner.quiescence_receipt = null;
819 owner.phase = .booting;
820 }
821 };
822
823 /// Boots a guest with accelerator semantics through a caller-supplied callback,
824 /// so a backend harness can supply its own start callback while keeping the
825 /// rest of the path unchanged. The call answers with the same start outcome as
826 /// `init`.
827 pub fn initWithStart(
828 storage: *Storage,
829 ram: []align(layout.page_bytes) u8,
830 input: types.Input,
831 context: ?*anyopaque,
832 start: backend.StartFunction,
833 ) StartResult {
834 return initSelected(
835 storage,
836 ram,
837 input,
838 .accelerator,
839 context,
840 start,
841 );
842 }
843
844 /// Restores a guest with accelerator semantics through a caller-supplied
845 /// callback, so a backend harness can substitute the backend acquire callback
846 /// during a restore. The checkpoint and the profile are checked first, and the
847 /// callback runs after.
848 pub fn restoreWithAcquire(
849 storage: *Storage,
850 ram: []align(layout.page_bytes) u8,
851 input: types.RestoreInput,
852 context: ?*anyopaque,
853 acquire: backend.RestoreAcquireFunction,
854 ) RestoreResult {
855 return restoreSelected(
856 storage,
857 ram,
858 input,
859 .accelerator,
860 context,
861 acquire,
862 );
863 }
864
865 /// Checks whether a direct restore would work and brings no backend up, so a
866 /// caller verifies the operation before committing resources to it. The storage
867 /// and the destination RAM are left as they were. When the restore would work,
868 /// the call reports the checkpoint identity it verified.
869 pub fn validateRestore(
870 storage: *Storage,
871 ram: []align(layout.page_bytes) u8,
872 input: types.RestoreInput,
873 ) RestoreError!checkpoint.Identity {
874 const selected: BackendKind = switch (input.profile.backend) {
875 .linux_kvm_single_vcpu_v1 => .accelerator,
876 .portable_x86_64_interpreter_v1 => .reference,
877 };
878 return validateRestoreSelected(storage, ram, input, selected);
879 }
880
881 fn initSelected(
882 storage: *Storage,
883 ram: []align(layout.page_bytes) u8,
884 input: types.Input,
885 selected: BackendKind,
886 context: ?*anyopaque,
887 start: backend.StartFunction,
888 ) StartResult {
889 if (image_admission.buffersOverlap(&storage.bytes, ram)) {
890 return .{ .rejected = error.StorageAliasesRam };
891 }
892 const prepared = prepareStart(storage, ram, input, selected) catch |failure| {
893 return .{ .rejected = failure };
894 };
895 const attempt = start(
896 context,
897 backendState(ownerState(storage)),
898 ram,
899 input.elf,
900 &prepared,
901 ) catch |failure| {
902 ownerState(storage).manifest_bytes = 0;
903 return .{ .rejected = mapInitFailure(failure) };
904 };
905 return switch (attempt) {
906 .ready => ready(storage, ram, selected),
907 .unavailable => |receipt| unavailable(storage, receipt),
908 };
909 }
910
911 fn ready(
912 storage: *Storage,
913 ram: []align(layout.page_bytes) u8,
914 selected: BackendKind,
915 ) StartResult {
916 const owner = ownerState(storage);
917 const memory = core.memory.Access.initLinear(ram);
918 owner.phase = .booting;
919 owner.active_backend = selected;
920 owner.memory_kind = .linear;
921 owner.memory_identity = memory.identity();
922 return .{ .ready = .{
923 .storage = storage,
924 .ram = ram,
925 .memory = memory,
926 .session_identity = owner.session_identity,
927 } };
928 }
929
930 fn unavailable(
931 storage: *Storage,
932 receipt: backend.Unavailable,
933 ) StartResult {
934 ownerState(storage).manifest_bytes = 0;
935 ownerState(storage).active_backend = .none;
936 return .{ .unavailable = .{
937 .availability = receipt.availability,
938 .stage = receipt.stage,
939 .code = receipt.code,
940 } };
941 }
942
943 fn prepareStart(
944 storage: *Storage,
945 ram: []align(layout.page_bytes) u8,
946 input: types.Input,
947 selected: BackendKind,
948 ) InitError!backend.Prepared {
949 const owner = ownerState(storage);
950 if (owner.phase != .closed) return error.StorageInUse;
951 owner.manifest_bytes = 0;
952 try profile.validate(input.profile);
953 if (!backendMatches(input.profile.backend, selected)) {
954 return error.UnsupportedBackend;
955 }
956 try layout.validateRamBytes(ram.len);
957 try validateProfileGeometry(input.profile);
958 if (image_admission.buffersOverlap(input.elf, ram)) {
959 return error.ImageAliasesRam;
960 }
961 const admitted = try image_admission.admit(
962 &owner.manifest,
963 input.execution_manifest,
964 input.expected_execution_fingerprint,
965 input.elf,
966 );
967 owner.manifest_bytes = @intCast(admitted.execution.bytes.len);
968 const protection_plan = protection.Plan.init(admitted.execution) catch
969 return error.BackendFailure;
970 const contract = try profile.contractFingerprint(input.profile);
971 const profile_fingerprint = try profile.profileFingerprint(input.profile);
972 const input_basis = inputBasis(input, contract);
973 try input_admission.validateBasis(input_basis);
974 try initializeProtocol(
975 owner,
976 input,
977 contract,
978 profile_fingerprint,
979 admitted.image_digest,
980 );
981 owner.initial = admitted.initial;
982 owner.immutable_image = admitted.immutable_image;
983 const frame = bootFrame(input, contract, admitted.image_digest);
984 var wire: os.abi.BootWire = undefined;
985 try os.abi.encodeBootFrame(frame, &wire);
986 owner.session_identity = claimSessionIdentity();
987 owner.ram_address = @intFromPtr(ram.ptr);
988 return .{
989 .execution = admitted.execution,
990 .protection_plan = protection_plan,
991 .initial = admitted.initial,
992 .frame = frame,
993 .wire = wire,
994 };
995 }
996
997 fn restoreSelected(
998 storage: *Storage,
999 ram: []align(layout.page_bytes) u8,
1000 input: types.RestoreInput,
1001 selected: BackendKind,
1002 context: ?*anyopaque,
1003 acquire: ?backend.RestoreAcquireFunction,
1004 ) RestoreResult {
1005 if (image_admission.buffersOverlap(&storage.bytes, ram)) {
1006 return .{ .rejected = error.StorageAliasesRam };
1007 }
1008 const prepared = prepareRestore(storage, ram, input, selected) catch |failure| {
1009 return .{ .rejected = failure };
1010 };
1011 return switch (selected) {
1012 .accelerator => restoreAccelerator(
1013 storage,
1014 ram,
1015 input,
1016 prepared,
1017 context,
1018 acquire orelse unreachable,
1019 ),
1020 .reference => restoreReference(storage, ram, input, prepared),
1021 .none => unreachable,
1022 };
1023 }
1024
1025 fn restoreSharedSelected(
1026 storage: *Storage,
1027 root_storage: checkpoint.roots.Storage,
1028 branch_storage: checkpoint.roots.branch.Storage,
1029 input: types.SharedRestoreInput,
1030 ) RestoreResult {
1031 const owner = ownerState(storage);
1032 if (owner.phase != .closed) return .{ .rejected = error.StorageInUse };
1033 if (branchStorageAliases(branch_storage, &storage.bytes)) {
1034 return .{ .rejected = error.StorageAliasesRam };
1035 }
1036 if (branchStorageAliases(branch_storage, input.execution_manifest)) {
1037 return .{ .rejected = error.ManifestAliasesRam };
1038 }
1039 profile.validate(input.profile) catch |failure| {
1040 return .{ .rejected = failure };
1041 };
1042 if (input.profile.backend != .portable_x86_64_interpreter_v1) {
1043 return .{ .rejected = error.UnsupportedBackend };
1044 }
1045 validateProfileGeometry(input.profile) catch |failure| {
1046 return .{ .rejected = failure };
1047 };
1048 os.abi.wire.validateFence(input.fence) catch |failure| {
1049 return .{ .rejected = failure };
1050 };
1051 owner.branch = checkpoint.roots.branch.restore(
1052 root_storage,
1053 input.expected_root,
1054 branch_storage,
1055 ) catch |failure| return .{ .rejected = failure };
1056 const access = branchMemory(&owner.branch);
1057 const prepared = prepareSharedRestore(
1058 owner,
1059 access,
1060 input,
1061 ) catch |failure| return rejectPreparedRestore(storage, failure);
1062 const restored = switch (prepared.target) {
1063 .reference => |value| value,
1064 .accelerator => unreachable,
1065 };
1066 const attempt = reference.startFromAccess(
1067 backendState(owner),
1068 access,
1069 &prepared.backend,
1070 restored,
1071 ) catch |failure| return rejectPreparedRestore(storage, failure);
1072 return switch (attempt) {
1073 .ready => restoreSharedReady(storage, access),
1074 .unavailable => |receipt| restoreUnavailable(storage, receipt),
1075 };
1076 }
1077
1078 fn prepareSharedRestore(
1079 owner: *OwnerState,
1080 access: core.memory.Access,
1081 input: types.SharedRestoreInput,
1082 ) RestoreError!RestorePreparation {
1083 owner.manifest_bytes = 0;
1084 const material = owner.branch.manifest.material;
1085 const contract = try profile.contractFingerprint(input.profile);
1086 const profile_fingerprint = try profile.profileFingerprint(input.profile);
1087 if (!std.meta.eql(contract, material.receipt.basis.contract)) {
1088 return error.CheckpointContractMismatch;
1089 }
1090 const admitted = try image_admission.admitRestoredAccess(
1091 &owner.manifest,
1092 input.execution_manifest,
1093 material.receipt.execution_fingerprint,
1094 material.receipt.image_digest,
1095 material.cpu,
1096 material.immutable_image,
1097 access,
1098 );
1099 owner.manifest_bytes = @intCast(admitted.execution.bytes.len);
1100 const protection_plan = protection.Plan.init(admitted.execution) catch
1101 return error.BackendFailure;
1102 const frame = try restoreFrame(material, contract, input.fence);
1103 try initializeRestore(
1104 owner,
1105 material,
1106 contract,
1107 profile_fingerprint,
1108 input.fence,
1109 );
1110 var wire: os.abi.BootWire = undefined;
1111 try os.abi.encodeBootFrame(frame, &wire);
1112 return .{
1113 .backend = .{
1114 .execution = admitted.execution,
1115 .protection_plan = protection_plan,
1116 .initial = material.cpu,
1117 .frame = frame,
1118 .wire = wire,
1119 },
1120 .target = .{ .reference = try reference.prepareRestoreAccess(
1121 admitted.execution,
1122 &protection_plan,
1123 access,
1124 ) },
1125 };
1126 }
1127
1128 fn restoreAccelerator(
1129 storage: *Storage,
1130 ram: []align(layout.page_bytes) u8,
1131 input: types.RestoreInput,
1132 prepared: RestorePreparation,
1133 context: ?*anyopaque,
1134 acquire: backend.RestoreAcquireFunction,
1135 ) RestoreResult {
1136 std.debug.assert(std.meta.activeTag(prepared.target) == .accelerator);
1137 const state_pointer = backendState(ownerState(storage));
1138 const attempt = acquire(context, state_pointer) catch |failure| {
1139 return rejectPreparedRestore(storage, failure);
1140 };
1141 switch (attempt) {
1142 .unavailable => |receipt| return restoreUnavailable(storage, receipt),
1143 .ready => {},
1144 }
1145 accelerator.admitRestore(
1146 state_pointer,
1147 &prepared.backend,
1148 ) catch |failure| {
1149 accelerator.deinit(state_pointer);
1150 return rejectPreparedRestore(storage, failure);
1151 };
1152 _ = input.checkpoint.materializeForRestore(
1153 input.expected_root,
1154 ram,
1155 ) catch |failure| {
1156 accelerator.deinit(state_pointer);
1157 return rejectPreparedRestore(storage, failure);
1158 };
1159 accelerator.activateRestore(
1160 state_pointer,
1161 ram,
1162 &prepared.backend,
1163 ) catch |failure| {
1164 accelerator.deinit(state_pointer);
1165 return rejectPreparedRestore(storage, failure);
1166 };
1167 return restoreReady(storage, ram, .accelerator);
1168 }
1169
1170 fn restoreReference(
1171 storage: *Storage,
1172 ram: []align(layout.page_bytes) u8,
1173 input: types.RestoreInput,
1174 prepared: RestorePreparation,
1175 ) RestoreResult {
1176 const restored = switch (prepared.target) {
1177 .reference => |value| value,
1178 .accelerator => unreachable,
1179 };
1180 _ = input.checkpoint.materializeForRestore(
1181 input.expected_root,
1182 ram,
1183 ) catch |failure| return rejectPreparedRestore(storage, failure);
1184 const attempt = reference.startFromRam(
1185 backendState(ownerState(storage)),
1186 ram,
1187 &prepared.backend,
1188 restored,
1189 );
1190 return switch (attempt) {
1191 .ready => restoreReady(storage, ram, .reference),
1192 .unavailable => |receipt| restoreUnavailable(storage, receipt),
1193 };
1194 }
1195
1196 fn rejectPreparedRestore(
1197 storage: *Storage,
1198 failure: RestoreError,
1199 ) RestoreResult {
1200 const owner = ownerState(storage);
1201 std.debug.assert(owner.phase == .closed);
1202 owner.manifest_bytes = 0;
1203 owner.active_backend = .none;
1204 return .{ .rejected = failure };
1205 }
1206
1207 fn prepareRestore(
1208 storage: *Storage,
1209 ram: []align(layout.page_bytes) u8,
1210 input: types.RestoreInput,
1211 selected: BackendKind,
1212 ) RestoreError!RestorePreparation {
1213 const owner = ownerState(storage);
1214 try validateRestoreAliases(storage, ram, input);
1215 if (owner.phase != .closed) return error.StorageInUse;
1216 owner.manifest_bytes = 0;
1217 try profile.validate(input.profile);
1218 if (!backendMatches(input.profile.backend, selected)) {
1219 return error.UnsupportedBackend;
1220 }
1221 try layout.validateRamBytes(ram.len);
1222 try validateProfileGeometry(input.profile);
1223 const contract = try profile.contractFingerprint(input.profile);
1224 const profile_fingerprint = try profile.profileFingerprint(input.profile);
1225 try os.abi.wire.validateFence(input.fence);
1226 const source = try input.checkpoint.prepareForRestore(
1227 input.expected_root,
1228 ram,
1229 );
1230 const material = source.material;
1231 if (!std.meta.eql(contract, material.receipt.basis.contract)) {
1232 return error.CheckpointContractMismatch;
1233 }
1234 const admitted = try image_admission.admitRestored(
1235 &owner.manifest,
1236 input.execution_manifest,
1237 material.receipt.execution_fingerprint,
1238 material.receipt.image_digest,
1239 material.cpu,
1240 material.immutable_image,
1241 input.checkpoint.evidenceRam(),
1242 );
1243 owner.manifest_bytes = @intCast(admitted.execution.bytes.len);
1244 const protection_plan = protection.Plan.init(admitted.execution) catch
1245 return error.BackendFailure;
1246 const frame = try restoreFrame(material, contract, input.fence);
1247 try initializeRestore(
1248 owner,
1249 material,
1250 contract,
1251 profile_fingerprint,
1252 input.fence,
1253 );
1254 var wire: os.abi.BootWire = undefined;
1255 try os.abi.encodeBootFrame(frame, &wire);
1256 const target_preparation: RestoreTargetPreparation = switch (selected) {
1257 .reference => .{ .reference = try reference.prepareRestore(
1258 admitted.execution,
1259 &protection_plan,
1260 input.checkpoint.evidenceRam(),
1261 ) },
1262 .accelerator => .accelerator,
1263 .none => unreachable,
1264 };
1265 return .{
1266 .backend = .{
1267 .execution = admitted.execution,
1268 .protection_plan = protection_plan,
1269 .initial = material.cpu,
1270 .frame = frame,
1271 .wire = wire,
1272 },
1273 .target = target_preparation,
1274 };
1275 }
1276
1277 fn validateRestoreSelected(
1278 storage: *Storage,
1279 ram: []align(layout.page_bytes) u8,
1280 input: types.RestoreInput,
1281 selected: BackendKind,
1282 ) RestoreError!checkpoint.Identity {
1283 try validateRestoreAliases(storage, ram, input);
1284 if (ownerState(storage).phase != .closed) return error.StorageInUse;
1285 try profile.validate(input.profile);
1286 if (!backendMatches(input.profile.backend, selected)) {
1287 return error.UnsupportedBackend;
1288 }
1289 try layout.validateRamBytes(ram.len);
1290 try validateProfileGeometry(input.profile);
1291 try os.abi.wire.validateFence(input.fence);
1292 const source = try input.checkpoint.prepareForRestore(
1293 input.expected_root,
1294 ram,
1295 );
1296 const material = source.material;
1297 const contract = try profile.contractFingerprint(input.profile);
1298 if (!std.meta.eql(contract, material.receipt.basis.contract)) {
1299 return error.CheckpointContractMismatch;
1300 }
1301 const admitted = try image_admission.validateRestored(
1302 input.execution_manifest,
1303 material.receipt.execution_fingerprint,
1304 material.receipt.image_digest,
1305 material.cpu,
1306 material.immutable_image,
1307 input.checkpoint.evidenceRam(),
1308 );
1309 const plan = protection.Plan.init(admitted.execution) catch
1310 return error.BackendFailure;
1311 const frame = try restoreFrame(material, contract, input.fence);
1312 var wire: os.abi.BootWire = undefined;
1313 try os.abi.encodeBootFrame(frame, &wire);
1314 if (selected == .reference) {
1315 _ = try reference.prepareRestore(
1316 admitted.execution,
1317 &plan,
1318 input.checkpoint.evidenceRam(),
1319 );
1320 }
1321 return source.identity;
1322 }
1323
1324 fn validateProfileGeometry(value: profile.Profile) profile.Error!void {
1325 return profile.capacity.geometry(value, .{
1326 .vcpu_count = @intCast(manifest.k0_v1_vcpu_count),
1327 .page_bytes = layout.page_bytes,
1328 .ram_base = manifest.k0_v1_ram_base,
1329 .ram_bytes = layout.ram_bytes,
1330 });
1331 }
1332
1333 fn validateRestoreAliases(
1334 storage: *Storage,
1335 ram: []align(layout.page_bytes) u8,
1336 input: types.RestoreInput,
1337 ) RestoreOwnerError!void {
1338 if (input.checkpoint.aliases(&storage.bytes)) {
1339 return error.StorageAliasesCheckpoint;
1340 }
1341 if (input.checkpoint.aliases(ram)) {
1342 return error.RamAliasesCheckpoint;
1343 }
1344 if (image_admission.buffersOverlap(input.execution_manifest, &storage.bytes)) {
1345 return error.ManifestAliasesStorage;
1346 }
1347 if (image_admission.buffersOverlap(input.execution_manifest, ram)) {
1348 return error.ManifestAliasesRam;
1349 }
1350 if (input.checkpoint.aliases(input.execution_manifest)) {
1351 return error.ManifestAliasesCheckpoint;
1352 }
1353 }
1354
1355 fn initializeRestore(
1356 owner: *OwnerState,
1357 material: checkpoint.Material,
1358 contract: profile.ContractFingerprint,
1359 profile_fingerprint: profile.ProfileFingerprint,
1360 fence: os.abi.ActivationFence,
1361 ) RestoreError!void {
1362 const root_generation = try validateRestoreMaterial(material);
1363 owner.contract = contract;
1364 owner.profile_fingerprint = profile_fingerprint;
1365 owner.execution_fingerprint = material.receipt.execution_fingerprint;
1366 owner.initial = material.cpu;
1367 owner.immutable_image = material.immutable_image;
1368 owner.fence = fence;
1369 owner.image_digest = material.receipt.image_digest;
1370 owner.source_root = material.receipt.basis.source_root;
1371 owner.frontiers = material.receipt.basis.frontiers;
1372 owner.outstanding_effect = material.receipt.basis.outstanding_effect;
1373 owner.block_root = material.receipt.block_root.digest;
1374 owner.terminal_offset = material.receipt.boundary.terminal_offset;
1375 owner.semantic_frontier = material.receipt.boundary.semantic_frontier;
1376 owner.root_generation = root_generation;
1377 owner.terminal_count = material.receipt.k0.counter;
1378 owner.activation_stage = .ready;
1379 owner.pending = null;
1380 owner.quiescence_receipt = null;
1381 }
1382
1383 fn restoreFrame(
1384 material: checkpoint.Material,
1385 contract: profile.ContractFingerprint,
1386 fence: os.abi.ActivationFence,
1387 ) OwnerError!os.abi.BootFrame {
1388 const root_generation = try validateRestoreMaterial(material);
1389 return .{
1390 .fence = fence,
1391 .contract_digest = contract.digest,
1392 .image_digest = material.receipt.image_digest,
1393 .ram_bytes = layout.ram_bytes,
1394 .request_ring_address = layout.request_ring_address,
1395 .event_ring_address = layout.event_ring_address,
1396 .initial_time_tick = material.receipt.basis.frontiers.virtual_time_tick,
1397 .entropy_generation = material.receipt.basis.frontiers.entropy_generation,
1398 .terminal_offset = material.receipt.boundary.terminal_offset,
1399 .effect_frontier = material.receipt.basis.frontiers.effect,
1400 .block_root = material.receipt.block_root.digest,
1401 .input_frontier = material.receipt.basis.frontiers.input,
1402 .terminal_input_offset = material.receipt.basis.frontiers.terminal_input_offset,
1403 .restart = .{
1404 .root_generation = root_generation,
1405 .counter = material.receipt.k0.counter,
1406 .semantic_frontier = material.receipt.boundary.semantic_frontier,
1407 },
1408 };
1409 }
1410
1411 fn validateRestoreMaterial(
1412 material: checkpoint.Material,
1413 ) OwnerError!u32 {
1414 _ = std.math.add(
1415 u64,
1416 material.receipt.boundary.terminal_offset,
1417 os.k0.ready_prompt.len,
1418 ) catch return error.StateCapacityExceeded;
1419 return std.math.cast(
1420 u32,
1421 material.receipt.block_root.generation,
1422 ) orelse return error.StateCapacityExceeded;
1423 }
1424
1425 fn restoreReady(
1426 storage: *Storage,
1427 ram: []align(layout.page_bytes) u8,
1428 selected: BackendKind,
1429 ) RestoreResult {
1430 const owner = ownerState(storage);
1431 owner.session_identity = claimSessionIdentity();
1432 owner.ram_address = @intFromPtr(ram.ptr);
1433 const memory = core.memory.Access.initLinear(ram);
1434 owner.memory_identity = memory.identity();
1435 owner.memory_kind = .linear;
1436 owner.phase = .booting;
1437 owner.active_backend = selected;
1438 return .{ .ready = .{
1439 .storage = storage,
1440 .ram = ram,
1441 .memory = memory,
1442 .session_identity = owner.session_identity,
1443 } };
1444 }
1445
1446 fn restoreSharedReady(
1447 storage: *Storage,
1448 memory: core.memory.Access,
1449 ) RestoreResult {
1450 const owner = ownerState(storage);
1451 owner.session_identity = claimSessionIdentity();
1452 owner.ram_address = 0;
1453 owner.memory_identity = memory.identity();
1454 owner.memory_kind = .branch;
1455 owner.phase = .booting;
1456 owner.active_backend = .reference;
1457 return .{ .ready = .{
1458 .storage = storage,
1459 .ram = owner.branch.storage.pages[0..0],
1460 .memory = memory,
1461 .session_identity = owner.session_identity,
1462 } };
1463 }
1464
1465 fn restoreUnavailable(
1466 storage: *Storage,
1467 receipt: backend.Unavailable,
1468 ) RestoreResult {
1469 ownerState(storage).manifest_bytes = 0;
1470 ownerState(storage).active_backend = .none;
1471 return .{ .unavailable = .{
1472 .availability = receipt.availability,
1473 .stage = receipt.stage,
1474 .code = receipt.code,
1475 } };
1476 }
1477
1478 fn checkpointMaterial(
1479 owner: *const OwnerState,
1480 receipt_value: types.QuiescenceReceipt,
1481 ) quiescence_receipt.Error!checkpoint.Material {
1482 return .{
1483 .profile = owner.profile_fingerprint,
1484 .receipt = try quiescence_receipt.projectSemantic(receipt_value),
1485 .cpu = owner.initial,
1486 .immutable_image = owner.immutable_image,
1487 };
1488 }
1489
1490 fn initializeProtocol(
1491 owner: *OwnerState,
1492 input: types.Input,
1493 contract: profile.ContractFingerprint,
1494 profile_fingerprint: profile.ProfileFingerprint,
1495 image_digest: os.abi.Digest,
1496 ) OwnerError!void {
1497 _ = std.math.add(u64, input.terminal_offset, os.k0.ready_prompt.len) catch
1498 return error.StateCapacityExceeded;
1499 owner.contract = contract;
1500 owner.profile_fingerprint = profile_fingerprint;
1501 owner.execution_fingerprint = input.expected_execution_fingerprint;
1502 owner.fence = input.fence;
1503 owner.image_digest = image_digest;
1504 owner.source_root = input.source_root;
1505 owner.frontiers = inputBasis(input, contract).frontiers;
1506 owner.outstanding_effect = input.outstanding_effect;
1507 owner.block_root = input.block_root;
1508 owner.terminal_offset = input.terminal_offset;
1509 owner.semantic_frontier = 0;
1510 owner.root_generation = os.k0.cold_root_generation;
1511 owner.terminal_count = 0;
1512 owner.activation_stage = .ready;
1513 owner.pending = null;
1514 owner.quiescence_receipt = null;
1515 }
1516
1517 fn inputBasis(
1518 input: types.Input,
1519 contract: profile.ContractFingerprint,
1520 ) input_admission.Basis {
1521 return .{
1522 .contract = contract,
1523 .source_root = input.source_root,
1524 .frontiers = .{
1525 .input = input.input_frontier,
1526 .terminal_input_offset = input.terminal_input_offset,
1527 .virtual_time_tick = input.initial_time_tick,
1528 .entropy_generation = input.entropy_generation,
1529 .effect = input.effect_frontier,
1530 },
1531 .outstanding_effect = input.outstanding_effect,
1532 };
1533 }
1534
1535 fn bootFrame(
1536 input: types.Input,
1537 contract: profile.ContractFingerprint,
1538 image_digest: os.abi.Digest,
1539 ) os.abi.BootFrame {
1540 return .{
1541 .fence = input.fence,
1542 .contract_digest = contract.digest,
1543 .image_digest = image_digest,
1544 .ram_bytes = layout.ram_bytes,
1545 .request_ring_address = layout.request_ring_address,
1546 .event_ring_address = layout.event_ring_address,
1547 .initial_time_tick = input.initial_time_tick,
1548 .entropy_generation = input.entropy_generation,
1549 .terminal_offset = input.terminal_offset,
1550 .effect_frontier = input.effect_frontier,
1551 .block_root = input.block_root,
1552 .input_frontier = input.input_frontier,
1553 .terminal_input_offset = input.terminal_input_offset,
1554 .restart = .cold,
1555 };
1556 }
1557
1558 fn continuationFrame(
1559 owner: *const OwnerState,
1560 fence: os.abi.ActivationFence,
1561 ) os.abi.BootFrame {
1562 return .{
1563 .fence = fence,
1564 .contract_digest = owner.contract.digest,
1565 .image_digest = owner.image_digest,
1566 .ram_bytes = layout.ram_bytes,
1567 .request_ring_address = layout.request_ring_address,
1568 .event_ring_address = layout.event_ring_address,
1569 .initial_time_tick = owner.frontiers.virtual_time_tick,
1570 .entropy_generation = owner.frontiers.entropy_generation,
1571 .terminal_offset = owner.terminal_offset,
1572 .effect_frontier = owner.frontiers.effect,
1573 .block_root = owner.block_root,
1574 .input_frontier = owner.frontiers.input,
1575 .terminal_input_offset = owner.frontiers.terminal_input_offset,
1576 .restart = .{
1577 .root_generation = owner.root_generation,
1578 .counter = owner.terminal_count,
1579 .semantic_frontier = owner.semantic_frontier,
1580 },
1581 };
1582 }
1583
1584 fn basis(owner: *const OwnerState) input_admission.Basis {
1585 return .{
1586 .contract = owner.contract,
1587 .source_root = owner.source_root,
1588 .frontiers = owner.frontiers,
1589 .outstanding_effect = owner.outstanding_effect,
1590 };
1591 }
1592
1593 fn takeActivationEvents(
1594 owner: *OwnerState,
1595 memory: core.memory.Access,
1596 output: *types.EventBatch,
1597 ) EventError!void {
1598 const events = try layout.eventRingAccess(memory);
1599 const initial = try os.abi.EventRing.frontiers(events, owner.fence);
1600 if (initial.consumed != 0 or
1601 initial.produced != os.k0.events_per_activation)
1602 {
1603 return error.EventReceiptMismatch;
1604 }
1605 var working = events.*;
1606 var batch: types.EventBatch = .{
1607 .count = os.k0.events_per_activation,
1608 .storage = @splat(@splat(0)),
1609 };
1610 var stage = owner.activation_stage;
1611 var terminal_offset = owner.terminal_offset;
1612 for (0..os.k0.events_per_activation) |index| {
1613 const transaction = try os.abi.EventRing.peek(&working, owner.fence);
1614 const decoded = try os.abi.decodeEvent(&transaction.frame);
1615 try validateActivationEvent(
1616 owner,
1617 &stage,
1618 &terminal_offset,
1619 decoded,
1620 );
1621 batch.storage[index] = transaction.frame;
1622 try os.abi.EventRing.commit(&working, owner.fence, &transaction);
1623 }
1624 const final = try os.abi.EventRing.frontiers(&working, owner.fence);
1625 if (stage != .complete or final.count() != 0) {
1626 return error.EventReceiptMismatch;
1627 }
1628 events.* = working;
1629 owner.terminal_offset = terminal_offset;
1630 owner.activation_stage = stage;
1631 owner.phase = .awaiting_input;
1632 output.* = batch;
1633 }
1634
1635 fn validateActivationEvent(
1636 owner: *const OwnerState,
1637 stage: *ActivationStage,
1638 terminal_offset: *u64,
1639 decoded: os.abi.DecodedEvent,
1640 ) EventError!void {
1641 stage.* = switch (stage.*) {
1642 .ready => ready: {
1643 if (std.meta.activeTag(decoded.value) != .ready) {
1644 return error.UnexpectedEvent;
1645 }
1646 const value = decoded.value.ready;
1647 if (!sameDigest(value.image_digest, owner.image_digest) or
1648 !sameDigest(value.block_root, owner.block_root) or
1649 value.virtual_time_tick != owner.frontiers.virtual_time_tick or
1650 value.entropy_generation != owner.frontiers.entropy_generation or
1651 value.terminal_offset != terminal_offset.* or
1652 value.effect_frontier != owner.frontiers.effect or
1653 value.input_frontier != owner.frontiers.input or
1654 value.terminal_input_offset != owner.frontiers.terminal_input_offset)
1655 {
1656 return error.EventReceiptMismatch;
1657 }
1658 break :ready .prompt;
1659 },
1660 .prompt => prompt: {
1661 if (std.meta.activeTag(decoded.value) != .terminal_bytes) {
1662 return error.UnexpectedEvent;
1663 }
1664 const value = decoded.value.terminal_bytes;
1665 if (value.offset != terminal_offset.* or
1666 !std.mem.eql(u8, value.bytes, os.k0.ready_prompt))
1667 {
1668 return error.EventReceiptMismatch;
1669 }
1670 terminal_offset.* = std.math.add(
1671 u64,
1672 terminal_offset.*,
1673 os.k0.ready_prompt.len,
1674 ) catch return error.EventReceiptMismatch;
1675 break :prompt .complete;
1676 },
1677 .complete => return error.UnexpectedEvent,
1678 };
1679 }
1680
1681 fn takeInputEvents(
1682 owner: *OwnerState,
1683 memory: core.memory.Access,
1684 output: *types.EventBatch,
1685 ) EventError!void {
1686 const events = try layout.eventRingAccess(memory);
1687 const requests = try layout.requestRingAccess(memory);
1688 const request_frontiers = try os.abi.RequestRing.frontiers(
1689 requests,
1690 owner.fence,
1691 );
1692 var pending = owner.pending orelse return error.InputUnavailable;
1693 const initial = try os.abi.EventRing.frontiers(events, owner.fence);
1694 const event_count: usize = switch (pending.delivery.admission.record) {
1695 .terminal => os.k0.events_per_terminal_input,
1696 else => os.k0.events_per_nonterminal_input,
1697 };
1698 if (initial.consumed != pending.event_frontiers.consumed or
1699 initial.produced != pending.final_event_sequence or
1700 initial.count() != event_count)
1701 {
1702 return error.EventReceiptMismatch;
1703 }
1704 var working = events.*;
1705 var batch: types.EventBatch = .{
1706 .count = @intCast(event_count),
1707 .storage = @splat(@splat(0)),
1708 };
1709 for (0..event_count) |index| {
1710 const event_frontiers = try os.abi.EventRing.frontiers(
1711 &working,
1712 owner.fence,
1713 );
1714 const transaction = try os.abi.EventRing.peek(&working, owner.fence);
1715 const decoded = try os.abi.decodeEvent(&transaction.frame);
1716 try validateInputEvent(
1717 owner,
1718 &pending,
1719 decoded,
1720 request_frontiers,
1721 event_frontiers,
1722 );
1723 batch.storage[index] = transaction.frame;
1724 try os.abi.EventRing.commit(&working, owner.fence, &transaction);
1725 }
1726 const final = try os.abi.EventRing.frontiers(&working, owner.fence);
1727 if (pending.stage != .complete or final.count() != 0) {
1728 return error.EventReceiptMismatch;
1729 }
1730 pending.event_transcript_digest = quiescence_receipt.transcriptDigest(&batch);
1731 pending.semantic_transcript_digest = try quiescence_receipt.semanticTranscriptDigest(
1732 &batch,
1733 );
1734 events.* = working;
1735 owner.pending = pending;
1736 owner.phase = .awaiting_acknowledgement;
1737 output.* = batch;
1738 }
1739
1740 fn validateInputEvent(
1741 owner: *const OwnerState,
1742 pending: *Pending,
1743 decoded: os.abi.DecodedEvent,
1744 request_frontiers: os.abi.ring.Frontiers,
1745 event_frontiers: os.abi.ring.Frontiers,
1746 ) EventError!void {
1747 try validateEventSequence(pending.*, decoded.header.sequence);
1748 switch (pending.stage) {
1749 .semantic => {
1750 try validateSemantic(
1751 owner,
1752 &pending.delivery.admission,
1753 decoded,
1754 );
1755 pending.stage = switch (pending.delivery.admission.record) {
1756 .terminal => .terminal,
1757 else => .block_root,
1758 };
1759 },
1760 .terminal => {
1761 if (std.meta.activeTag(decoded.value) != .terminal_bytes or
1762 std.meta.activeTag(pending.delivery.admission.record) != .terminal)
1763 {
1764 return error.UnexpectedEvent;
1765 }
1766 const value = decoded.value.terminal_bytes;
1767 if (value.offset != owner.terminal_offset or
1768 !std.mem.eql(u8, value.bytes, os.k0.incremented_text))
1769 {
1770 return error.EventReceiptMismatch;
1771 }
1772 pending.stage = .block_root;
1773 },
1774 .block_root => {
1775 if (std.meta.activeTag(decoded.value) != .block_root) {
1776 return error.UnexpectedEvent;
1777 }
1778 const value = decoded.value.block_root;
1779 const generation = std.math.add(
1780 u32,
1781 owner.root_generation,
1782 1,
1783 ) catch return error.EventReceiptMismatch;
1784 if (value.generation != generation) {
1785 return error.EventReceiptMismatch;
1786 }
1787 pending.block_root = value;
1788 pending.stage = .quiescence;
1789 },
1790 .quiescence => {
1791 if (std.meta.activeTag(decoded.value) != .quiescent) {
1792 return error.UnexpectedEvent;
1793 }
1794 if (event_frontiers.produced != pending.final_event_sequence) {
1795 return error.EventReceiptMismatch;
1796 }
1797 try validateQuiescence(
1798 owner,
1799 pending.*,
1800 decoded.value.quiescent,
1801 request_frontiers,
1802 );
1803 pending.quiescence = decoded.value.quiescent;
1804 pending.stage = .complete;
1805 },
1806 .complete => return error.UnexpectedEvent,
1807 }
1808 }
1809
1810 fn validateEventSequence(pending: Pending, actual: u64) EventError!void {
1811 if (actual != try expectedEventSequence(pending)) {
1812 return error.EventReceiptMismatch;
1813 }
1814 }
1815
1816 fn validateSemantic(
1817 owner: *const OwnerState,
1818 value: *const input_admission.Admission,
1819 decoded: os.abi.DecodedEvent,
1820 ) EventError!void {
1821 if (std.meta.activeTag(decoded.value) != .semantic) {
1822 return error.UnexpectedEvent;
1823 }
1824 const semantic = decoded.value.semantic;
1825 const expected: ExpectedSemantic = switch (value.record) {
1826 .terminal => .{
1827 .interface = os.k0.counter_interface,
1828 .event = os.k0.counter_event,
1829 .position = @as(u64, std.math.add(
1830 u8,
1831 owner.terminal_count,
1832 1,
1833 ) catch return error.EventReceiptMismatch),
1834 .bytes = os.k0.semantic_name,
1835 },
1836 .virtual_time => .{
1837 .interface = os.k0.input_interface,
1838 .event = os.k0.time_event,
1839 .position = value.expected.input,
1840 .bytes = os.k0.time_semantic_name,
1841 },
1842 .entropy => .{
1843 .interface = os.k0.input_interface,
1844 .event = os.k0.entropy_event,
1845 .position = value.expected.input,
1846 .bytes = os.k0.entropy_semantic_name,
1847 },
1848 .effect_result => .{
1849 .interface = os.k0.input_interface,
1850 .event = os.k0.effect_event,
1851 .position = value.expected.input,
1852 .bytes = os.k0.effect_semantic_name,
1853 },
1854 };
1855 if (!std.mem.eql(u8, &semantic.interface_id, &expected.interface) or
1856 semantic.event_id != expected.event or
1857 semantic.position != expected.position or
1858 !std.mem.eql(u8, semantic.bytes, expected.bytes))
1859 {
1860 return error.EventReceiptMismatch;
1861 }
1862 }
1863
1864 fn validateQuiescence(
1865 owner: *const OwnerState,
1866 pending: Pending,
1867 value: os.abi.Quiescence,
1868 request_frontiers: os.abi.ring.Frontiers,
1869 ) EventError!void {
1870 const expected_terminal_offset = switch (pending.delivery.admission.record) {
1871 .terminal => std.math.add(
1872 u64,
1873 owner.terminal_offset,
1874 os.k0.incremented_text.len,
1875 ) catch return error.EventReceiptMismatch,
1876 else => owner.terminal_offset,
1877 };
1878 const expected_semantic_frontier = std.math.add(
1879 u64,
1880 owner.semantic_frontier,
1881 1,
1882 ) catch return error.EventReceiptMismatch;
1883 const expected = pending.delivery.admission.expected;
1884 if (value.request_sequence != pending.request_sequence or
1885 value.semantic_frontier != expected_semantic_frontier or
1886 value.effect_frontier != expected.effect or
1887 value.terminal_offset != expected_terminal_offset or
1888 value.virtual_time_tick != expected.virtual_time_tick or
1889 value.entropy_generation != expected.entropy_generation or
1890 value.request_consumed != pending.request_frontiers.consumed + 1 or
1891 value.request_produced != pending.request_frontiers.produced + 1 or
1892 request_frontiers.consumed != pending.request_frontiers.consumed + 1 or
1893 request_frontiers.produced != pending.request_frontiers.produced + 1 or
1894 value.event_consumed != pending.event_frontiers.consumed or
1895 value.event_produced != pending.final_event_sequence or
1896 value.capability_generation != owner.fence.generation or
1897 value.unresolved_effects != 0 or
1898 value.scheduler != .idle or
1899 !sameDigest(
1900 value.block_root,
1901 (pending.block_root orelse return error.EventReceiptMismatch).digest,
1902 ) or
1903 value.input_frontier != expected.input or
1904 value.terminal_input_offset != expected.terminal_input_offset)
1905 {
1906 return error.EventReceiptMismatch;
1907 }
1908 }
1909
1910 fn prepareCommit(
1911 owner: *const OwnerState,
1912 pending: Pending,
1913 settled: types.SettledTransport,
1914 ) AcknowledgeError!CommittedTurn {
1915 const terminal = std.meta.activeTag(
1916 pending.delivery.admission.record,
1917 ) == .terminal;
1918 const terminal_offset = if (terminal)
1919 std.math.add(u64, owner.terminal_offset, os.k0.incremented_text.len) catch
1920 return error.StateCapacityExceeded
1921 else
1922 owner.terminal_offset;
1923 const semantic_frontier = std.math.add(
1924 u64,
1925 owner.semantic_frontier,
1926 1,
1927 ) catch return error.StateCapacityExceeded;
1928 const root_generation = std.math.add(
1929 u32,
1930 owner.root_generation,
1931 1,
1932 ) catch return error.StateCapacityExceeded;
1933 const terminal_count = if (terminal)
1934 std.math.add(u8, owner.terminal_count, 1) catch
1935 return error.StateCapacityExceeded
1936 else
1937 owner.terminal_count;
1938 const block_root = pending.block_root orelse
1939 return error.EventReceiptMismatch;
1940 const boundary = pending.quiescence orelse
1941 return error.EventReceiptMismatch;
1942 const next_basis: input_admission.Basis = .{
1943 .contract = owner.contract,
1944 .source_root = pending.delivery.next_source_root,
1945 .frontiers = pending.delivery.admission.expected,
1946 .outstanding_effect = pending.delivery.admission.expected_outstanding_effect,
1947 };
1948 return .{
1949 .basis = next_basis,
1950 .block_root = block_root,
1951 .terminal_offset = terminal_offset,
1952 .semantic_frontier = semantic_frontier,
1953 .root_generation = root_generation,
1954 .terminal_count = terminal_count,
1955 .receipt = try quiescence_receipt.issue(.{
1956 .fence = owner.fence,
1957 .delivery = pending.delivery.receipt,
1958 .admission_receipt = pending.delivery.admission.receipt,
1959 .basis = next_basis,
1960 .image_digest = owner.image_digest,
1961 .execution_fingerprint = owner.execution_fingerprint,
1962 .block_root = block_root,
1963 .boundary = boundary,
1964 .settled = settled,
1965 .k0 = .{ .counter = terminal_count },
1966 .event_transcript_digest = pending.event_transcript_digest,
1967 .semantic_transcript_digest = pending.semantic_transcript_digest,
1968 }),
1969 };
1970 }
1971
1972 fn settledRings(
1973 owner: *const OwnerState,
1974 memory: core.memory.Access,
1975 pending: Pending,
1976 ) AcknowledgeError!types.SettledTransport {
1977 const requests = try os.abi.RequestRing.frontiers(
1978 try layout.requestRingAccess(memory),
1979 owner.fence,
1980 );
1981 const events = try os.abi.EventRing.frontiers(
1982 try layout.eventRingAccess(memory),
1983 owner.fence,
1984 );
1985 const request_final = pending.request_frontiers.produced + 1;
1986 if (requests.consumed != request_final or
1987 requests.produced != request_final or
1988 events.consumed != pending.final_event_sequence or
1989 events.produced != pending.final_event_sequence)
1990 {
1991 return error.EventReceiptMismatch;
1992 }
1993 return .{
1994 .request_cursor = request_final,
1995 .event_cursor = pending.final_event_sequence,
1996 };
1997 }
1998
1999 fn validateReceiptOwner(
2000 owner: *const OwnerState,
2001 value: types.QuiescenceReceipt,
2002 ) quiescence_receipt.Error!void {
2003 if (!os.abi.wire.equalFence(value.fence, owner.fence) or
2004 !std.meta.eql(value.basis, basis(owner)) or
2005 !sameDigest(value.image_digest, owner.image_digest) or
2006 !std.meta.eql(
2007 value.execution_fingerprint,
2008 owner.execution_fingerprint,
2009 ) or
2010 value.block_root.generation != @as(u64, owner.root_generation) or
2011 !sameDigest(value.block_root.digest, owner.block_root) or
2012 value.boundary.semantic_frontier != owner.semantic_frontier or
2013 value.boundary.terminal_offset != owner.terminal_offset or
2014 value.k0.counter != owner.terminal_count)
2015 {
2016 return error.InvalidQuiescenceReceipt;
2017 }
2018 }
2019
2020 fn expectedEventSequence(pending: Pending) EventError!u64 {
2021 const offset: u64 = switch (pending.stage) {
2022 .semantic => 1,
2023 .terminal => 2,
2024 .block_root => switch (pending.delivery.admission.record) {
2025 .terminal => 3,
2026 else => 2,
2027 },
2028 .quiescence => switch (pending.delivery.admission.record) {
2029 .terminal => 4,
2030 else => 3,
2031 },
2032 .complete => return error.UnexpectedEvent,
2033 };
2034 return std.math.add(
2035 u64,
2036 pending.event_frontiers.produced,
2037 offset,
2038 ) catch return error.EventReceiptMismatch;
2039 }
2040
2041 fn sameDigest(a: os.abi.Digest, b: os.abi.Digest) bool {
2042 return std.mem.eql(u8, &a, &b);
2043 }
2044
2045 const branch_memory_vtable: core.memory.Access.SharedVTable = .{
2046 .read = branchRead,
2047 .write = branchWrite,
2048 .fill = branchFill,
2049 .prepare_write = branchPrepareWrite,
2050 .prepare_pages = branchPreparePages,
2051 .writable_page = branchWritablePage,
2052 .aliases = branchAliases,
2053 };
2054
2055 fn branchMemory(branch: *checkpoint.roots.branch.Branch) core.memory.Access {
2056 return core.memory.Access.initShared(branch, &branch_memory_vtable);
2057 }
2058
2059 fn branchRead(
2060 context: *anyopaque,
2061 address: usize,
2062 output: []u8,
2063 ) core.memory.Error!void {
2064 branchState(context).read(address, output) catch |failure|
2065 return mapBranchFailure(failure);
2066 }
2067
2068 fn branchWrite(
2069 context: *anyopaque,
2070 address: usize,
2071 input: []const u8,
2072 ) core.memory.Error!void {
2073 branchState(context).write(address, input) catch |failure|
2074 return mapBranchFailure(failure);
2075 }
2076
2077 fn branchFill(
2078 context: *anyopaque,
2079 address: usize,
2080 byte_count: usize,
2081 value: u8,
2082 ) core.memory.Error!void {
2083 branchState(context).fill(address, byte_count, value) catch |failure|
2084 return mapBranchFailure(failure);
2085 }
2086
2087 fn branchPreparePages(
2088 context: *anyopaque,
2089 pages: []const u16,
2090 ) core.memory.Error!void {
2091 branchState(context).preparePages(pages) catch |failure|
2092 return mapBranchFailure(failure);
2093 }
2094
2095 fn branchPrepareWrite(
2096 context: *anyopaque,
2097 address: usize,
2098 byte_count: usize,
2099 ) core.memory.Error!void {
2100 branchState(context).prepareWrite(address, byte_count) catch |failure|
2101 return mapBranchFailure(failure);
2102 }
2103
2104 fn branchWritablePage(
2105 context: *anyopaque,
2106 page_index: u16,
2107 ) core.memory.Error!*align(layout.page_bytes) [layout.page_bytes]u8 {
2108 return branchState(context).writablePage(page_index) catch |failure|
2109 return mapBranchFailure(failure);
2110 }
2111
2112 fn branchAliases(context: *const anyopaque, bytes: []const u8) bool {
2113 const branch: *const checkpoint.roots.branch.Branch = @ptrCast(@alignCast(context));
2114 return branch.aliases(bytes);
2115 }
2116
2117 fn branchState(context: *anyopaque) *checkpoint.roots.branch.Branch {
2118 return @ptrCast(@alignCast(context));
2119 }
2120
2121 fn mapBranchFailure(
2122 failure: checkpoint.roots.branch.Error,
2123 ) core.memory.Error {
2124 return switch (failure) {
2125 error.BranchAddressOverflow => error.MemoryAddressOverflow,
2126 error.BranchOutOfBounds => error.MemoryOutOfBounds,
2127 error.BranchPageCapacityExceeded => error.MemoryCapacityExceeded,
2128 error.DeltaChainCapacityExceeded,
2129 error.DeltaPageCountMismatch,
2130 error.DeltaParentMismatch,
2131 error.ManifestCorrupt,
2132 error.ManifestRootMismatch,
2133 error.PageRootMismatch,
2134 error.RootObjectCorrupt,
2135 => error.MemoryAuthenticationFailed,
2136 else => error.MemoryReadFailed,
2137 };
2138 }
2139
2140 fn branchStorageAliases(
2141 storage: checkpoint.roots.branch.Storage,
2142 bytes: []const u8,
2143 ) bool {
2144 return image_admission.buffersOverlap(
2145 std.mem.sliceAsBytes(storage.indices),
2146 bytes,
2147 ) or image_admission.buffersOverlap(storage.pages, bytes) or
2148 image_admission.buffersOverlap(
2149 std.mem.asBytes(storage.authenticated),
2150 bytes,
2151 ) or image_admission.buffersOverlap(
2152 std.mem.asBytes(storage.digests),
2153 bytes,
2154 );
2155 }
2156
2157 fn backendMatches(value: profile.BackendSemantics, selected: BackendKind) bool {
2158 return switch (selected) {
2159 .accelerator => value == .linux_kvm_single_vcpu_v1,
2160 .reference => value == .portable_x86_64_interpreter_v1,
2161 .none => false,
2162 };
2163 }
2164
2165 fn runBackend(owner: *OwnerState) backend.RunFailure!backend.Exit {
2166 return switch (owner.active_backend) {
2167 .accelerator => accelerator.run(backendState(owner)),
2168 .reference => reference.run(backendState(owner)),
2169 .none => error.Closed,
2170 };
2171 }
2172
2173 fn completePendingIo(owner: *OwnerState) backend.RunFailure!void {
2174 return switch (owner.active_backend) {
2175 .accelerator => accelerator.completePendingIo(backendState(owner)),
2176 .reference => reference.completePendingIo(backendState(owner)),
2177 .none => error.Closed,
2178 };
2179 }
2180
2181 fn restartBackend(
2182 owner: *OwnerState,
2183 memory: core.memory.Access,
2184 ) backend.RestartFailure!void {
2185 return switch (owner.active_backend) {
2186 .accelerator => accelerator.restart(
2187 backendState(owner),
2188 memory.linearRam() orelse return error.BackendInvalidState,
2189 ),
2190 .reference => reference.restart(backendState(owner)),
2191 .none => error.Closed,
2192 };
2193 }
2194
2195 fn applyExit(
2196 owner: *OwnerState,
2197 running_phase: types.RunPhase,
2198 value: types.Exit,
2199 ) RunError!types.Exit {
2200 if (std.meta.activeTag(value) == .fault) {
2201 owner.phase = .failed;
2202 return value;
2203 }
2204 if (std.meta.activeTag(value) != .doorbell) {
2205 owner.phase = .failed;
2206 return value;
2207 }
2208 const code = value.doorbell.code;
2209 switch (running_phase) {
2210 .booting => {
2211 if (code != .ready) {
2212 owner.phase = .failed;
2213 return error.UnexpectedDoorbell;
2214 }
2215 owner.phase = .draining_activation;
2216 },
2217 .input_delivered => {
2218 if (code == .guest_fault) {
2219 owner.phase = .failed;
2220 return value;
2221 }
2222 if (code != .quiescent) {
2223 owner.phase = .failed;
2224 return error.UnexpectedDoorbell;
2225 }
2226 owner.phase = .draining_input;
2227 },
2228 else => unreachable,
2229 }
2230 return value;
2231 }
2232
2233 fn normalize(raw: backend.Exit) types.Exit {
2234 return switch (raw) {
2235 .io => |io| normalizeIo(io),
2236 .halted => .halted,
2237 .shutdown => .shutdown,
2238 .stutter => unreachable,
2239 .exception => |value| fault(.exception, value.number, value.error_code),
2240 .fail_entry => |value| fault(.entry, value.hardware_reason, value.cpu),
2241 .memory_fault => |value| fault(.memory, value.flags, value.guest_physical_address),
2242 .hypercall => |value| fault(.hypercall, value.number, 0),
2243 .debug => |value| fault(.debug, value.exception, value.program_counter),
2244 .system_event => |value| fault(.system, value.kind, 0),
2245 .mmio => |value| fault(.device, @intFromBool(value.write), value.physical_address),
2246 .unknown => |value| fault(.backend, value.hardware_reason, 0),
2247 .unhandled => |value| fault(.backend, value.reason, 0),
2248 .other => fault(.backend, 0, 0),
2249 };
2250 }
2251
2252 fn normalizeIo(io: backend.Io) types.Exit {
2253 if (io.direction != .output or
2254 io.port != os.abi.channel.doorbell_port or
2255 io.size != os.abi.channel.doorbell_bytes or
2256 io.count != 1 or
2257 io.data_bytes != os.abi.channel.doorbell_bytes)
2258 {
2259 return fault(.device, io.port, 0);
2260 }
2261 const code = std.enums.fromInt(os.abi.channel.DoorbellCode, io.first_byte) orelse {
2262 return fault(.device, io.first_byte, 0);
2263 };
2264 return .{ .doorbell = .{ .code = code } };
2265 }
2266
2267 fn fault(kind: types.FaultKind, code: anytype, address: anytype) types.Exit {
2268 return .{ .fault = .{
2269 .kind = kind,
2270 .code = @intCast(code),
2271 .address = @intCast(address),
2272 } };
2273 }
2274
2275 fn mapInitFailure(failure: backend.InitFailure) InitError {
2276 return failure;
2277 }
2278
2279 fn mapRunFailure(failure: backend.RunFailure) RunError {
2280 return failure;
2281 }
2282
2283 fn backendState(owner: *OwnerState) *anyopaque {
2284 return @ptrCast(&owner.backend);
2285 }
2286
2287 fn ownerState(storage: *Storage) *OwnerState {
2288 return @ptrCast(@alignCast(&storage.bytes));
2289 }
2290
2291 fn ownsLifecycle(self: *const Instance, owner: *const OwnerState) bool {
2292 return self.session_identity == owner.session_identity and
2293 owner.memory_kind != .none and
2294 self.memory.identity() == owner.memory_identity;
2295 }
2296
2297 fn claimSessionIdentity() u64 {
2298 var current = next_session_identity.load(.monotonic);
2299 while (true) {
2300 if (current == std.math.maxInt(u64)) {
2301 @panic("machine instance session identity capacity exhausted");
2302 }
2303 if (next_session_identity.cmpxchgWeak(
2304 current,
2305 current + 1,
2306 .monotonic,
2307 .monotonic,
2308 )) |observed| {
2309 current = observed;
2310 } else {
2311 return current;
2312 }
2313 }
2314 }
2315
2316 comptime {
2317 std.debug.assert(accelerator.storage_alignment > 0);
2318 std.debug.assert(reference.storage_alignment > 0);
2319 std.debug.assert(@alignOf(Storage) == storage_alignment);
2320 std.debug.assert(@sizeOf(Storage) == storage_bytes);
2321 std.debug.assert(@offsetOf(OwnerState, "backend") % accelerator.storage_alignment == 0);
2322 std.debug.assert(@offsetOf(OwnerState, "backend") % reference.storage_alignment == 0);
2323 }