lib/machine/src/instance/reference/execute.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const core = @import("machine_instance_core");
2 const isa = @import("isa");
3 const os = @import("os");
4 const reference = @import("root.zig");
5 const std = @import("std");
6
7 const backend = core.backend;
8 const protection = core.protection;
9 const x86 = isa.x86;
10 const Cpu = reference.Cpu;
11 const integer = reference.integer;
12 const memory = reference.memory;
13 const manifest = os.boot.kernel.manifest;
14
15 const direction_flag: u64 = 1 << 10;
16 const divide_exception: u32 = 0;
17 const invalid_opcode_exception: u32 = 6;
18
19 pub const Error = error{
20 DivideFault,
21 InvalidControlTarget,
22 MemoryFault,
23 UnsupportedInstruction,
24 } || core.memory.Error;
25
26 pub const Boundary = union(enum) {
27 continuing,
28 io: backend.Io,
29 halted,
30 };
31
32 const MemoryTarget = struct {
33 address: u64,
34 width: x86.Width,
35 };
36
37 const Target = union(enum) {
38 register: x86.Register,
39 memory: MemoryTarget,
40 };
41
42 const Explicit = enum(u2) {
43 register,
44 operand,
45 embedded,
46 };
47
48 pub fn validate(instruction: x86.Instruction) Error!void {
49 if (instruction.form.prefix.locked()) return error.UnsupportedInstruction;
50 try validateRegisters(instruction);
51 try validateOperand(instruction.operand);
52 switch (instruction.operation) {
53 .mov => try validateMove(instruction),
54 .movsx, .movzx => try validateMoveExtend(instruction),
55 .lea => try validateLea(instruction),
56 .add, .and_, .or_, .sbb, .sub, .xor_ => try validateBinary(instruction),
57 .cmp, .test_ => try validateCompare(instruction),
58 .inc, .dec, .not_ => try validateUnary(instruction),
59 .rol, .shl, .shr => try validateShift(instruction),
60 .imul, .mul, .div => try validateMultiplyDivide(instruction),
61 .ja, .jae, .jb, .jbe, .je, .jmp, .jne => try validateBranch(instruction),
62 .cmovae, .cmove, .cmovne => try validateConditionalMove(instruction),
63 .seta, .setae, .setb, .setbe, .sete, .setne => try validateSet(instruction),
64 .call => try validateCall(instruction),
65 .ret => try validateRet(instruction),
66 .push, .pop => try validateStack(instruction),
67 .movs, .stos => try validateString(instruction),
68 .bswap => try validateBswap(instruction),
69 .cmc, .nop, .out, .hlt, .ud2 => try validateBoundary(instruction),
70 else => return error.UnsupportedInstruction,
71 }
72 }
73
74 pub fn step(
75 cpu: *Cpu,
76 ram: anytype,
77 plan: *const protection.Plan,
78 execution: manifest.View,
79 instruction: x86.Instruction,
80 fault: *memory.Fault,
81 ) Error!Boundary {
82 fault.* = .{};
83 var next = cpu.*;
84 const fallthrough = next.rip +% instruction.bytes;
85 next.rip = fallthrough;
86 const boundary = switch (instruction.operation) {
87 .mov => try move(&next, ram, plan, instruction, fault),
88 .movsx, .movzx => try moveExtend(&next, ram, plan, instruction, fault),
89 .lea => try loadAddress(&next, instruction, fault),
90 .add => try binary(&next, ram, plan, instruction, fault, .add),
91 .and_ => try binary(&next, ram, plan, instruction, fault, .and_),
92 .or_ => try binary(&next, ram, plan, instruction, fault, .or_),
93 .sbb => try binary(&next, ram, plan, instruction, fault, .sbb),
94 .sub => try binary(&next, ram, plan, instruction, fault, .sub),
95 .xor_ => try binary(&next, ram, plan, instruction, fault, .xor_),
96 .cmp => try compare(&next, ram, instruction, fault),
97 .test_ => try bitTest(&next, ram, instruction, fault),
98 .inc => try unary(&next, ram, plan, instruction, fault, .increment),
99 .dec => try unary(&next, ram, plan, instruction, fault, .decrement),
100 .not_ => try unary(&next, ram, plan, instruction, fault, .bit_not),
101 .rol => try shift(&next, ram, plan, instruction, fault, .rotate_left),
102 .shl => try shift(&next, ram, plan, instruction, fault, .left),
103 .shr => try shift(&next, ram, plan, instruction, fault, .right),
104 .imul => try signedMultiply(&next, ram, plan, instruction, fault),
105 .mul => try multiply(&next, ram, instruction, fault),
106 .div => try divide(&next, ram, instruction, fault),
107 .ja, .jae, .jb, .jbe, .je, .jmp, .jne => try branch(
108 &next,
109 execution,
110 instruction,
111 ),
112 .cmovae, .cmove, .cmovne => try conditionalMove(
113 &next,
114 ram,
115 plan,
116 instruction,
117 fault,
118 ),
119 .seta, .setae, .setb, .setbe, .sete, .setne => try setCondition(
120 &next,
121 ram,
122 plan,
123 instruction,
124 fault,
125 ),
126 .call => try call(&next, ram, plan, execution, instruction, fault),
127 .ret => try ret(&next, ram, execution, instruction, fault),
128 .push => try push(&next, ram, plan, instruction, fault),
129 .pop => try pop(&next, ram, instruction, fault),
130 .movs => try moveString(&next, ram, plan, instruction, fault),
131 .stos => try storeString(&next, ram, plan, instruction, fault),
132 .bswap => try byteSwap(&next, instruction),
133 .cmc => carryComplement(&next),
134 .nop => Boundary.continuing,
135 .out => try output(&next),
136 .hlt => Boundary.halted,
137 .ud2 => return error.UnsupportedInstruction,
138 else => return error.UnsupportedInstruction,
139 };
140 cpu.* = next;
141 return boundary;
142 }
143
144 pub fn exceptionFor(failure: Error) ?u32 {
145 return switch (failure) {
146 error.DivideFault => divide_exception,
147 error.InvalidControlTarget, error.UnsupportedInstruction => invalid_opcode_exception,
148 error.MemoryAddressOverflow,
149 error.MemoryAuthenticationFailed,
150 error.MemoryCapacityExceeded,
151 error.MemoryFault,
152 error.MemoryOutOfBounds,
153 error.MemoryReadFailed,
154 => null,
155 };
156 }
157
158 fn validateRegisters(instruction: x86.Instruction) Error!void {
159 if (instruction.register) |value| try validateRegister(value);
160 if (instruction.embedded) |value| try validateRegister(value);
161 switch (instruction.operand) {
162 .register => |value| try validateRegister(value),
163 .memory => |value| {
164 if (value.base) |base| try validateAddressRegister(base);
165 if (value.index) |index| try validateAddressRegister(index);
166 },
167 .none => {},
168 }
169 for (instruction.implicitRegisters()) |value| try validateRegister(value.register);
170 }
171
172 fn validateRegister(value: x86.Register) Error!void {
173 if (value.bank != .gpr) return error.UnsupportedInstruction;
174 switch (value.lane) {
175 .low8, .word, .dword, .qword => {},
176 .high8, .vector128 => return error.UnsupportedInstruction,
177 }
178 }
179
180 fn validateAddressRegister(value: x86.Register) Error!void {
181 if (value.bank != .gpr or value.lane != .qword) {
182 return error.UnsupportedInstruction;
183 }
184 }
185
186 fn validateOperand(value: x86.Operand) Error!void {
187 switch (value) {
188 .none, .register => {},
189 .memory => |source| {
190 if ((source.segment != .none and source.width != .none) or
191 source.width == .vector128)
192 {
193 return error.UnsupportedInstruction;
194 }
195 const valid = switch (source.kind) {
196 .base => source.base != null and source.index == null,
197 .base_indexed => source.base != null and source.index != null,
198 .absolute => source.base == null and source.index == null,
199 .absolute_indexed => source.base == null and source.index != null,
200 .none, .rip_relative => false,
201 };
202 if (!valid) return error.UnsupportedInstruction;
203 },
204 }
205 }
206
207 fn validateMove(instruction: x86.Instruction) Error!void {
208 if (countAccess(instruction, .write) != 1 or
209 countAccess(instruction, .read_write) != 0)
210 {
211 return error.UnsupportedInstruction;
212 }
213 const sources = countAccess(instruction, .read) +
214 countBool(instruction.immediate != null);
215 if (sources != 1 or instruction.relative != null) {
216 return error.UnsupportedInstruction;
217 }
218 try validateScalarWidth(instruction.form.width);
219 }
220
221 fn validateMoveExtend(instruction: x86.Instruction) Error!void {
222 if (countAccess(instruction, .write) != 1 or
223 countAccess(instruction, .read) != 1 or
224 countAccess(instruction, .read_write) != 0 or
225 instruction.immediate != null)
226 {
227 return error.UnsupportedInstruction;
228 }
229 try validateScalarWidth(instruction.form.width);
230 const source_width = try operandScalarWidth(instruction.operand);
231 if (source_width.bytes() >= instruction.form.width.bytes()) {
232 return error.UnsupportedInstruction;
233 }
234 }
235
236 fn validateLea(instruction: x86.Instruction) Error!void {
237 if (instruction.register == null or
238 instruction.register_access != .write or
239 std.meta.activeTag(instruction.operand) != .memory or
240 instruction.operand_access != .none or
241 instruction.immediate != null or
242 (instruction.form.width != .dword and
243 instruction.form.width != .qword))
244 {
245 return error.UnsupportedInstruction;
246 }
247 }
248
249 fn validateBinary(instruction: x86.Instruction) Error!void {
250 if (countAccess(instruction, .read_write) != 1 or
251 countAccess(instruction, .write) != 0)
252 {
253 return error.UnsupportedInstruction;
254 }
255 const sources = countAccess(instruction, .read) +
256 countBool(instruction.immediate != null);
257 if (sources != 1) return error.UnsupportedInstruction;
258 try validateScalarWidth(instruction.form.width);
259 }
260
261 fn validateCompare(instruction: x86.Instruction) Error!void {
262 if (countAccess(instruction, .write) != 0 or
263 countAccess(instruction, .read_write) != 0)
264 {
265 return error.UnsupportedInstruction;
266 }
267 const sources = countAccess(instruction, .read) +
268 countBool(instruction.immediate != null);
269 if (sources != 2) return error.UnsupportedInstruction;
270 try validateScalarWidth(instruction.form.width);
271 }
272
273 fn validateUnary(instruction: x86.Instruction) Error!void {
274 if (countAccess(instruction, .read_write) != 1 or
275 countAccess(instruction, .read) != 0 or
276 countAccess(instruction, .write) != 0 or
277 instruction.immediate != null)
278 {
279 return error.UnsupportedInstruction;
280 }
281 try validateScalarWidth(instruction.form.width);
282 }
283
284 fn validateShift(instruction: x86.Instruction) Error!void {
285 if (countAccess(instruction, .read_write) != 1 or
286 countAccess(instruction, .write) != 0 or
287 countAccess(instruction, .read) != 0)
288 {
289 return error.UnsupportedInstruction;
290 }
291 const count_sources = countBool(instruction.immediate != null) +
292 countBool(hasImplicitCl(instruction)) +
293 countBool(instruction.form.opcode == 0xd0 or instruction.form.opcode == 0xd1);
294 if (count_sources != 1) return error.UnsupportedInstruction;
295 try validateScalarWidth(instruction.form.width);
296 }
297
298 fn validateMultiplyDivide(instruction: x86.Instruction) Error!void {
299 if (instruction.operation == .imul) {
300 if ((instruction.form.opcode != 0x69 and instruction.form.opcode != 0x6b) or
301 instruction.register == null or
302 instruction.register_access != .write or
303 instruction.operand_access != .read or
304 instruction.embedded_access != .none or
305 explicitAccessCount(instruction) != 2 or
306 instruction.immediate == null or
307 instruction.relative != null or
308 (instruction.form.width != .dword and
309 instruction.form.width != .qword))
310 {
311 return error.UnsupportedInstruction;
312 }
313 return;
314 }
315 if (countAccess(instruction, .read) != 1 or
316 countAccess(instruction, .write) != 0 or
317 countAccess(instruction, .read_write) != 0 or
318 instruction.immediate != null)
319 {
320 return error.UnsupportedInstruction;
321 }
322 const supported = switch (instruction.operation) {
323 .mul => instruction.form.width == .qword,
324 .div => instruction.form.width == .dword or instruction.form.width == .qword,
325 else => false,
326 };
327 if (!supported) return error.UnsupportedInstruction;
328 }
329
330 fn validateConditionalMove(instruction: x86.Instruction) Error!void {
331 if (instruction.register == null or
332 instruction.register_access != .read_write or
333 instruction.operand_access != .read or
334 instruction.embedded_access != .none or
335 instruction.immediate != null or
336 instruction.relative != null or
337 explicitAccessCount(instruction) != 2)
338 {
339 return error.UnsupportedInstruction;
340 }
341 try validateScalarWidth(instruction.form.width);
342 }
343
344 fn validateBranch(instruction: x86.Instruction) Error!void {
345 if (instruction.relative == null or explicitAccessCount(instruction) != 0) {
346 return error.UnsupportedInstruction;
347 }
348 }
349
350 fn validateSet(instruction: x86.Instruction) Error!void {
351 if (countAccess(instruction, .write) != 1 or
352 explicitAccessCount(instruction) != 1 or
353 instruction.form.width != .byte)
354 {
355 return error.UnsupportedInstruction;
356 }
357 }
358
359 fn validateCall(instruction: x86.Instruction) Error!void {
360 if (instruction.relative == null or explicitAccessCount(instruction) != 0 or
361 !hasImplicitRsp(instruction, .read_write) or
362 instruction.form.width != .qword or
363 instruction.form.opcode != 0xe8)
364 {
365 return error.UnsupportedInstruction;
366 }
367 }
368
369 fn validateRet(instruction: x86.Instruction) Error!void {
370 if (instruction.relative != null or instruction.immediate != null or
371 explicitAccessCount(instruction) != 0 or
372 !hasImplicitRsp(instruction, .read_write) or
373 instruction.form.width != .qword or
374 instruction.form.opcode != 0xc3)
375 {
376 return error.UnsupportedInstruction;
377 }
378 }
379
380 fn validateStack(instruction: x86.Instruction) Error!void {
381 if (instruction.form.width != .qword or
382 !hasImplicitRsp(instruction, .read_write) or
383 instruction.operand != .none)
384 {
385 return error.UnsupportedInstruction;
386 }
387 if (instruction.operation == .push and instruction.form.opcode == 0x6a) {
388 if (instruction.immediate == null or instruction.embedded != null or
389 explicitAccessCount(instruction) != 0)
390 {
391 return error.UnsupportedInstruction;
392 }
393 return;
394 }
395 if (instruction.immediate != null or instruction.embedded == null) {
396 return error.UnsupportedInstruction;
397 }
398 const expected: x86.Access = if (instruction.operation == .push) .read else .write;
399 if (instruction.embedded_access != expected or explicitAccessCount(instruction) != 1) {
400 return error.UnsupportedInstruction;
401 }
402 }
403
404 fn validateString(instruction: x86.Instruction) Error!void {
405 if (!instruction.form.prefix.repeated() or
406 explicitAccessCount(instruction) != 0)
407 {
408 return error.UnsupportedInstruction;
409 }
410 try validateScalarWidth(instruction.form.width);
411 if (instruction.implicit_count != 3) return error.UnsupportedInstruction;
412 }
413
414 fn validateBswap(instruction: x86.Instruction) Error!void {
415 if (instruction.embedded == null or instruction.embedded_access != .read_write or
416 explicitAccessCount(instruction) != 1 or
417 (instruction.form.width != .dword and instruction.form.width != .qword))
418 {
419 return error.UnsupportedInstruction;
420 }
421 }
422
423 fn validateBoundary(instruction: x86.Instruction) Error!void {
424 switch (instruction.operation) {
425 .out => {
426 if (instruction.form.width != .byte or instruction.implicit_count != 2 or
427 explicitAccessCount(instruction) != 0)
428 {
429 return error.UnsupportedInstruction;
430 }
431 },
432 .cmc, .nop, .hlt, .ud2 => {
433 if (explicitAccessCount(instruction) != 0 or instruction.implicit_count != 0) {
434 return error.UnsupportedInstruction;
435 }
436 },
437 else => unreachable,
438 }
439 }
440
441 fn validateScalarWidth(width: x86.Width) Error!void {
442 switch (width) {
443 .byte, .word, .dword, .qword => {},
444 .none, .vector128 => return error.UnsupportedInstruction,
445 }
446 }
447
448 fn countAccess(instruction: x86.Instruction, access: x86.Access) usize {
449 return countBool(instruction.register_access == access) +
450 countBool(instruction.operand_access == access) +
451 countBool(instruction.embedded_access == access);
452 }
453
454 fn explicitAccessCount(instruction: x86.Instruction) usize {
455 return countBool(instruction.register_access != .none) +
456 countBool(instruction.operand_access != .none) +
457 countBool(instruction.embedded_access != .none);
458 }
459
460 fn countBool(value: bool) usize {
461 return @intFromBool(value);
462 }
463
464 fn hasImplicitCl(instruction: x86.Instruction) bool {
465 for (instruction.implicitRegisters()) |value| {
466 if (value.register.number == 1 and value.register.lane == .low8 and
467 value.access == .read)
468 {
469 return true;
470 }
471 }
472 return false;
473 }
474
475 fn hasImplicitRsp(instruction: x86.Instruction, access: x86.Access) bool {
476 for (instruction.implicitRegisters()) |value| {
477 if (value.register.number == 4 and value.register.lane == .qword and
478 value.access == access)
479 {
480 return true;
481 }
482 }
483 return false;
484 }
485
486 const BinaryKind = enum(u3) {
487 add,
488 and_,
489 or_,
490 sbb,
491 sub,
492 xor_,
493 };
494
495 const UnaryKind = enum(u2) {
496 increment,
497 decrement,
498 bit_not,
499 };
500
501 const ShiftKind = enum(u2) {
502 left,
503 right,
504 rotate_left,
505 };
506
507 fn move(
508 cpu: *Cpu,
509 ram: anytype,
510 plan: *const protection.Plan,
511 instruction: x86.Instruction,
512 fault: *memory.Fault,
513 ) Error!Boundary {
514 const destination = try targetForAccess(instruction, .write, cpu, fault);
515 const value = try sourceValue(instruction, cpu, ram, fault);
516 try writeTarget(cpu, ram, plan, destination, value, fault);
517 return .continuing;
518 }
519
520 fn moveExtend(
521 cpu: *Cpu,
522 ram: anytype,
523 plan: *const protection.Plan,
524 instruction: x86.Instruction,
525 fault: *memory.Fault,
526 ) Error!Boundary {
527 const destination = try targetForAccess(instruction, .write, cpu, fault);
528 const source = try targetForAccess(instruction, .read, cpu, fault);
529 const raw = try readTarget(cpu, ram, source, fault);
530 const value = switch (instruction.operation) {
531 .movsx => integer.signExtend(try operandScalarWidth(instruction.operand), raw),
532 .movzx => raw,
533 else => return error.UnsupportedInstruction,
534 };
535 try writeTarget(cpu, ram, plan, destination, value, fault);
536 return .continuing;
537 }
538
539 fn loadAddress(
540 cpu: *Cpu,
541 instruction: x86.Instruction,
542 fault: *memory.Fault,
543 ) Error!Boundary {
544 const destination = instruction.register orelse return error.UnsupportedInstruction;
545 const source = switch (instruction.operand) {
546 .memory => |value| value,
547 else => return error.UnsupportedInstruction,
548 };
549 const address = memory.effectiveAddress(source, cpu) catch |failure| {
550 return addressFailure(failure, fault);
551 };
552 cpu.write(destination, address) catch return error.UnsupportedInstruction;
553 return .continuing;
554 }
555
556 fn binary(
557 cpu: *Cpu,
558 ram: anytype,
559 plan: *const protection.Plan,
560 instruction: x86.Instruction,
561 fault: *memory.Fault,
562 kind: BinaryKind,
563 ) Error!Boundary {
564 const destination = try targetForAccess(instruction, .read_write, cpu, fault);
565 const lhs = try readTarget(cpu, ram, destination, fault);
566 const rhs = try sourceValue(instruction, cpu, ram, fault);
567 const result = switch (kind) {
568 .add => integer.add(instruction.form.width, lhs, rhs),
569 .and_ => integer.bitAnd(instruction.form.width, lhs, rhs),
570 .or_ => integer.bitOr(instruction.form.width, lhs, rhs),
571 .sbb => integer.sbb(
572 instruction.form.width,
573 lhs,
574 rhs,
575 cpu.flag(integer.flag.carry),
576 ),
577 .sub => integer.sub(instruction.form.width, lhs, rhs),
578 .xor_ => integer.bitXor(instruction.form.width, lhs, rhs),
579 };
580 try writeTarget(cpu, ram, plan, destination, result.value, fault);
581 cpu.writeFlagsDefined(result.flags.defined, result.flags.bits);
582 return .continuing;
583 }
584
585 fn compare(
586 cpu: *Cpu,
587 ram: anytype,
588 instruction: x86.Instruction,
589 fault: *memory.Fault,
590 ) Error!Boundary {
591 var lhs: u64 = undefined;
592 var rhs: u64 = undefined;
593 if (instruction.immediate) |value| {
594 const target = try targetForAccess(instruction, .read, cpu, fault);
595 lhs = try readTarget(cpu, ram, target, fault);
596 rhs = immediateValue(value);
597 } else switch (instruction.form.opcode) {
598 0x38, 0x39 => {
599 lhs = try readExplicit(cpu, ram, instruction, .operand, fault);
600 rhs = try readExplicit(cpu, ram, instruction, .register, fault);
601 },
602 0x3a, 0x3b => {
603 lhs = try readExplicit(cpu, ram, instruction, .register, fault);
604 rhs = try readExplicit(cpu, ram, instruction, .operand, fault);
605 },
606 else => return error.UnsupportedInstruction,
607 }
608 const result = integer.cmp(instruction.form.width, lhs, rhs);
609 cpu.writeFlagsDefined(result.flags.defined, result.flags.bits);
610 return .continuing;
611 }
612
613 fn bitTest(
614 cpu: *Cpu,
615 ram: anytype,
616 instruction: x86.Instruction,
617 fault: *memory.Fault,
618 ) Error!Boundary {
619 var values: [2]u64 = undefined;
620 var count: usize = 0;
621 if (instruction.register_access == .read) {
622 values[count] = try readExplicit(cpu, ram, instruction, .register, fault);
623 count += 1;
624 }
625 if (instruction.operand_access == .read) {
626 values[count] = try readExplicit(cpu, ram, instruction, .operand, fault);
627 count += 1;
628 }
629 if (instruction.embedded_access == .read) {
630 values[count] = try readExplicit(cpu, ram, instruction, .embedded, fault);
631 count += 1;
632 }
633 if (instruction.immediate) |value| {
634 if (count >= values.len) return error.UnsupportedInstruction;
635 values[count] = immediateValue(value);
636 count += 1;
637 }
638 if (count != values.len) return error.UnsupportedInstruction;
639 const result = integer.bitTest(instruction.form.width, values[0], values[1]);
640 cpu.writeFlagsDefined(result.flags.defined, result.flags.bits);
641 return .continuing;
642 }
643
644 fn unary(
645 cpu: *Cpu,
646 ram: anytype,
647 plan: *const protection.Plan,
648 instruction: x86.Instruction,
649 fault: *memory.Fault,
650 kind: UnaryKind,
651 ) Error!Boundary {
652 const destination = try targetForAccess(instruction, .read_write, cpu, fault);
653 const value = try readTarget(cpu, ram, destination, fault);
654 const result = switch (kind) {
655 .increment => integer.increment(instruction.form.width, value),
656 .decrement => integer.decrement(instruction.form.width, value),
657 .bit_not => integer.bitNot(instruction.form.width, value),
658 };
659 try writeTarget(cpu, ram, plan, destination, result.value, fault);
660 cpu.writeFlagsDefined(result.flags.defined, result.flags.bits);
661 return .continuing;
662 }
663
664 fn shift(
665 cpu: *Cpu,
666 ram: anytype,
667 plan: *const protection.Plan,
668 instruction: x86.Instruction,
669 fault: *memory.Fault,
670 kind: ShiftKind,
671 ) Error!Boundary {
672 const destination = try targetForAccess(instruction, .read_write, cpu, fault);
673 const value = try readTarget(cpu, ram, destination, fault);
674 const count = try shiftCount(cpu, instruction);
675 const result = switch (kind) {
676 .left => integer.shl(instruction.form.width, value, count),
677 .right => integer.shr(instruction.form.width, value, count),
678 .rotate_left => integer.rol(instruction.form.width, value, count),
679 };
680 try writeTarget(cpu, ram, plan, destination, result.value, fault);
681 cpu.writeFlagsDefined(result.flags.defined, result.flags.bits);
682 return .continuing;
683 }
684
685 fn signedMultiply(
686 cpu: *Cpu,
687 ram: anytype,
688 plan: *const protection.Plan,
689 instruction: x86.Instruction,
690 fault: *memory.Fault,
691 ) Error!Boundary {
692 const source = try targetForAccess(instruction, .read, cpu, fault);
693 const destination = try targetForAccess(instruction, .write, cpu, fault);
694 const lhs = try readTarget(cpu, ram, source, fault);
695 const rhs = immediateValue(instruction.immediate orelse
696 return error.UnsupportedInstruction);
697 const result = integer.signedMultiplyLow(instruction.form.width, lhs, rhs);
698 try writeTarget(cpu, ram, plan, destination, result.value, fault);
699 cpu.writeFlagsDefined(result.flags.defined, result.flags.bits);
700 return .continuing;
701 }
702
703 fn multiply(
704 cpu: *Cpu,
705 ram: anytype,
706 instruction: x86.Instruction,
707 fault: *memory.Fault,
708 ) Error!Boundary {
709 const source = try targetForAccess(instruction, .read, cpu, fault);
710 const rhs = try readTarget(cpu, ram, source, fault);
711 const accumulator = try scalarRegister(0, instruction.form.width);
712 const high = try scalarRegister(2, instruction.form.width);
713 const lhs = cpu.read(accumulator) catch return error.UnsupportedInstruction;
714 const product = integer.unsignedMultiply(instruction.form.width, lhs, rhs);
715 cpu.write(accumulator, product.low) catch return error.UnsupportedInstruction;
716 cpu.write(high, product.high) catch return error.UnsupportedInstruction;
717 cpu.writeFlagsDefined(product.flags.defined, product.flags.bits);
718 return .continuing;
719 }
720
721 fn divide(
722 cpu: *Cpu,
723 ram: anytype,
724 instruction: x86.Instruction,
725 fault: *memory.Fault,
726 ) Error!Boundary {
727 const source = try targetForAccess(instruction, .read, cpu, fault);
728 const divisor = try readTarget(cpu, ram, source, fault);
729 const accumulator = try scalarRegister(0, instruction.form.width);
730 const high = try scalarRegister(2, instruction.form.width);
731 const low_value = cpu.read(accumulator) catch return error.UnsupportedInstruction;
732 const high_value = cpu.read(high) catch return error.UnsupportedInstruction;
733 const result = integer.unsignedDivide(
734 instruction.form.width,
735 high_value,
736 low_value,
737 divisor,
738 ) catch return error.DivideFault;
739 cpu.write(accumulator, result.quotient) catch return error.UnsupportedInstruction;
740 cpu.write(high, result.remainder) catch return error.UnsupportedInstruction;
741 return .continuing;
742 }
743
744 fn branch(
745 cpu: *Cpu,
746 execution: manifest.View,
747 instruction: x86.Instruction,
748 ) Error!Boundary {
749 const taken = condition(cpu, instruction.operation);
750 const target = if (taken)
751 relativeTarget(cpu.rip, instruction.relative orelse
752 return error.UnsupportedInstruction)
753 else
754 cpu.rip;
755 try requireSite(execution, target);
756 cpu.rip = target;
757 return .continuing;
758 }
759
760 fn conditionalMove(
761 cpu: *Cpu,
762 ram: anytype,
763 plan: *const protection.Plan,
764 instruction: x86.Instruction,
765 fault: *memory.Fault,
766 ) Error!Boundary {
767 const source = try targetForAccess(instruction, .read, cpu, fault);
768 const destination = try targetForAccess(instruction, .read_write, cpu, fault);
769 const value = try readTarget(cpu, ram, source, fault);
770 if (condition(cpu, instruction.operation)) {
771 try writeTarget(cpu, ram, plan, destination, value, fault);
772 }
773 return .continuing;
774 }
775
776 fn setCondition(
777 cpu: *Cpu,
778 ram: anytype,
779 plan: *const protection.Plan,
780 instruction: x86.Instruction,
781 fault: *memory.Fault,
782 ) Error!Boundary {
783 const destination = try targetForAccess(instruction, .write, cpu, fault);
784 const value: u8 = @intFromBool(condition(cpu, instruction.operation));
785 try writeTarget(cpu, ram, plan, destination, value, fault);
786 return .continuing;
787 }
788
789 fn call(
790 cpu: *Cpu,
791 ram: anytype,
792 plan: *const protection.Plan,
793 execution: manifest.View,
794 instruction: x86.Instruction,
795 fault: *memory.Fault,
796 ) Error!Boundary {
797 const target = relativeTarget(
798 cpu.rip,
799 instruction.relative orelse return error.UnsupportedInstruction,
800 );
801 try requireSite(execution, target);
802 const rsp = cpu.read(.{ .number = 4 }) catch return error.UnsupportedInstruction;
803 const next_rsp = rsp -% @sizeOf(u64);
804 try writeMemory(ram, plan, next_rsp, .qword, cpu.rip, fault);
805 cpu.write(.{ .number = 4 }, next_rsp) catch return error.UnsupportedInstruction;
806 cpu.rip = target;
807 return .continuing;
808 }
809
810 fn ret(
811 cpu: *Cpu,
812 ram: anytype,
813 execution: manifest.View,
814 _: x86.Instruction,
815 fault: *memory.Fault,
816 ) Error!Boundary {
817 const rsp = cpu.read(.{ .number = 4 }) catch return error.UnsupportedInstruction;
818 const target = readMemory(ram, rsp, .qword, fault) catch |failure| return failure;
819 try requireSite(execution, target);
820 cpu.write(.{ .number = 4 }, rsp +% @sizeOf(u64)) catch
821 return error.UnsupportedInstruction;
822 cpu.rip = target;
823 return .continuing;
824 }
825
826 fn push(
827 cpu: *Cpu,
828 ram: anytype,
829 plan: *const protection.Plan,
830 instruction: x86.Instruction,
831 fault: *memory.Fault,
832 ) Error!Boundary {
833 const value = if (instruction.immediate) |immediate|
834 immediateValue(immediate)
835 else
836 try readExplicit(cpu, ram, instruction, .embedded, fault);
837 const rsp = cpu.read(.{ .number = 4 }) catch return error.UnsupportedInstruction;
838 const next_rsp = rsp -% @sizeOf(u64);
839 try writeMemory(ram, plan, next_rsp, .qword, value, fault);
840 cpu.write(.{ .number = 4 }, next_rsp) catch return error.UnsupportedInstruction;
841 return .continuing;
842 }
843
844 fn pop(
845 cpu: *Cpu,
846 ram: anytype,
847 instruction: x86.Instruction,
848 fault: *memory.Fault,
849 ) Error!Boundary {
850 const rsp = cpu.read(.{ .number = 4 }) catch return error.UnsupportedInstruction;
851 const value = try readMemory(ram, rsp, .qword, fault);
852 const destination = instruction.embedded orelse return error.UnsupportedInstruction;
853 cpu.write(destination, value) catch return error.UnsupportedInstruction;
854 if (destination.number != 4) {
855 cpu.write(.{ .number = 4 }, rsp +% @sizeOf(u64)) catch
856 return error.UnsupportedInstruction;
857 }
858 return .continuing;
859 }
860
861 fn moveString(
862 cpu: *Cpu,
863 ram: anytype,
864 plan: *const protection.Plan,
865 instruction: x86.Instruction,
866 fault: *memory.Fault,
867 ) Error!Boundary {
868 const source = cpu.read(.{ .number = 6 }) catch return error.UnsupportedInstruction;
869 const destination = cpu.read(.{ .number = 7 }) catch return error.UnsupportedInstruction;
870 const count = cpu.read(.{ .number = 1 }) catch return error.UnsupportedInstruction;
871 const backwards = cpu.flag(direction_flag);
872 const element_bytes = instruction.form.width.bytes();
873 const source_range = try stringRange(
874 ram,
875 source,
876 count,
877 element_bytes,
878 backwards,
879 fault,
880 );
881 const destination_range = try stringRange(
882 ram,
883 destination,
884 count,
885 element_bytes,
886 backwards,
887 fault,
888 );
889 try protectRange(plan, destination_range, fault);
890 try prepareMemoryWrite(ram, destination_range, fault);
891 const iterations: usize = @intCast(count);
892 for (0..iterations) |index| {
893 const source_address = stringElementAddress(
894 source,
895 index,
896 element_bytes,
897 backwards,
898 );
899 const destination_address = stringElementAddress(
900 destination,
901 index,
902 element_bytes,
903 backwards,
904 );
905 const value = try readMemory(
906 ram,
907 source_address,
908 instruction.form.width,
909 fault,
910 );
911 try writeMemory(
912 ram,
913 plan,
914 destination_address,
915 instruction.form.width,
916 value,
917 fault,
918 );
919 }
920 const total_bytes: u64 = @intCast(source_range.end - source_range.start);
921 updateStringRegisters(cpu, source, destination, total_bytes, backwards, true);
922 return .continuing;
923 }
924
925 fn storeString(
926 cpu: *Cpu,
927 ram: anytype,
928 plan: *const protection.Plan,
929 instruction: x86.Instruction,
930 fault: *memory.Fault,
931 ) Error!Boundary {
932 const destination = cpu.read(.{ .number = 7 }) catch return error.UnsupportedInstruction;
933 const count = cpu.read(.{ .number = 1 }) catch return error.UnsupportedInstruction;
934 const accumulator = try scalarRegister(0, instruction.form.width);
935 const value = cpu.read(accumulator) catch return error.UnsupportedInstruction;
936 const backwards = cpu.flag(direction_flag);
937 const element_bytes = instruction.form.width.bytes();
938 const destination_range = try stringRange(
939 ram,
940 destination,
941 count,
942 element_bytes,
943 backwards,
944 fault,
945 );
946 try protectRange(plan, destination_range, fault);
947 try prepareMemoryWrite(ram, destination_range, fault);
948 const iterations: usize = @intCast(count);
949 for (0..iterations) |index| {
950 const address = stringElementAddress(
951 destination,
952 index,
953 element_bytes,
954 backwards,
955 );
956 try writeMemory(
957 ram,
958 plan,
959 address,
960 instruction.form.width,
961 value,
962 fault,
963 );
964 }
965 const total_bytes: u64 = @intCast(destination_range.end - destination_range.start);
966 updateStringRegisters(cpu, 0, destination, total_bytes, backwards, false);
967 return .continuing;
968 }
969
970 fn byteSwap(cpu: *Cpu, instruction: x86.Instruction) Error!Boundary {
971 const register = instruction.embedded orelse return error.UnsupportedInstruction;
972 const value = cpu.read(register) catch return error.UnsupportedInstruction;
973 const swapped: u64 = switch (instruction.form.width) {
974 .dword => @byteSwap(@as(u32, @truncate(value))),
975 .qword => @byteSwap(value),
976 else => return error.UnsupportedInstruction,
977 };
978 cpu.write(register, swapped) catch return error.UnsupportedInstruction;
979 return .continuing;
980 }
981
982 fn carryComplement(cpu: *Cpu) Boundary {
983 const bits = if (cpu.flag(integer.flag.carry)) 0 else integer.flag.carry;
984 cpu.writeFlagsDefined(integer.flag.carry, bits);
985 return .continuing;
986 }
987
988 fn output(cpu: *Cpu) Error!Boundary {
989 const port = cpu.read(.{ .number = 2, .lane = .word }) catch
990 return error.UnsupportedInstruction;
991 const value = cpu.read(.{ .number = 0, .lane = .low8 }) catch
992 return error.UnsupportedInstruction;
993 return .{ .io = .{
994 .direction = .output,
995 .port = @intCast(port),
996 .size = 1,
997 .count = 1,
998 .data_bytes = 1,
999 .first_byte = @truncate(value),
1000 } };
1001 }
1002
1003 fn targetForAccess(
1004 instruction: x86.Instruction,
1005 access: x86.Access,
1006 cpu: *const Cpu,
1007 fault: *memory.Fault,
1008 ) Error!Target {
1009 var result: ?Target = null;
1010 if (instruction.register_access == access) {
1011 try storeTarget(&result, .{ .register = instruction.register orelse
1012 return error.UnsupportedInstruction });
1013 }
1014 if (instruction.operand_access == access) {
1015 try storeTarget(&result, try operandTarget(cpu, instruction.operand, fault));
1016 }
1017 if (instruction.embedded_access == access) {
1018 try storeTarget(&result, .{ .register = instruction.embedded orelse
1019 return error.UnsupportedInstruction });
1020 }
1021 return result orelse error.UnsupportedInstruction;
1022 }
1023
1024 fn storeTarget(destination: *?Target, value: Target) Error!void {
1025 if (destination.* != null) return error.UnsupportedInstruction;
1026 destination.* = value;
1027 }
1028
1029 fn operandTarget(
1030 cpu: *const Cpu,
1031 operand: x86.Operand,
1032 fault: *memory.Fault,
1033 ) Error!Target {
1034 return switch (operand) {
1035 .none => error.UnsupportedInstruction,
1036 .register => |value| .{ .register = value },
1037 .memory => |value| .{ .memory = .{
1038 .address = memory.effectiveAddress(value, cpu) catch |failure| {
1039 return addressFailure(failure, fault);
1040 },
1041 .width = value.width,
1042 } },
1043 };
1044 }
1045
1046 fn readTarget(
1047 cpu: *const Cpu,
1048 ram: anytype,
1049 target: Target,
1050 fault: *memory.Fault,
1051 ) Error!u64 {
1052 return switch (target) {
1053 .register => |value| cpu.read(value) catch error.UnsupportedInstruction,
1054 .memory => |value| readMemory(ram, value.address, value.width, fault),
1055 };
1056 }
1057
1058 fn writeTarget(
1059 cpu: *Cpu,
1060 ram: anytype,
1061 plan: *const protection.Plan,
1062 target: Target,
1063 value: u64,
1064 fault: *memory.Fault,
1065 ) Error!void {
1066 switch (target) {
1067 .register => |destination| cpu.write(destination, value) catch
1068 return error.UnsupportedInstruction,
1069 .memory => |destination| try writeMemory(
1070 ram,
1071 plan,
1072 destination.address,
1073 destination.width,
1074 value,
1075 fault,
1076 ),
1077 }
1078 }
1079
1080 fn sourceValue(
1081 instruction: x86.Instruction,
1082 cpu: *const Cpu,
1083 ram: anytype,
1084 fault: *memory.Fault,
1085 ) Error!u64 {
1086 if (instruction.immediate) |value| return immediateValue(value);
1087 const source = try targetForAccess(instruction, .read, cpu, fault);
1088 return readTarget(cpu, ram, source, fault);
1089 }
1090
1091 fn readExplicit(
1092 cpu: *const Cpu,
1093 ram: anytype,
1094 instruction: x86.Instruction,
1095 field: Explicit,
1096 fault: *memory.Fault,
1097 ) Error!u64 {
1098 const target: Target = switch (field) {
1099 .register => .{ .register = instruction.register orelse
1100 return error.UnsupportedInstruction },
1101 .operand => try operandTarget(cpu, instruction.operand, fault),
1102 .embedded => .{ .register = instruction.embedded orelse
1103 return error.UnsupportedInstruction },
1104 };
1105 return readTarget(cpu, ram, target, fault);
1106 }
1107
1108 fn immediateValue(value: x86.Immediate) u64 {
1109 return switch (value.extension) {
1110 .none => value.value,
1111 .sign => @bitCast(value.signed()),
1112 };
1113 }
1114
1115 fn readMemory(
1116 ram: anytype,
1117 address: u64,
1118 width: x86.Width,
1119 fault: *memory.Fault,
1120 ) Error!u64 {
1121 return memory.read(ram, address, width, fault) catch |failure| {
1122 if (fault.valid) return error.MemoryFault;
1123 return switch (failure) {
1124 error.UnsupportedWidth, error.RamBytesMismatch => error.UnsupportedInstruction,
1125 error.MemoryAddressOverflow,
1126 error.MemoryAuthenticationFailed,
1127 error.MemoryCapacityExceeded,
1128 error.MemoryOutOfBounds,
1129 error.MemoryReadFailed,
1130 => |access_failure| access_failure,
1131 else => error.MemoryFault,
1132 };
1133 };
1134 }
1135
1136 fn writeMemory(
1137 ram: anytype,
1138 plan: *const protection.Plan,
1139 address: u64,
1140 width: x86.Width,
1141 value: u64,
1142 fault: *memory.Fault,
1143 ) Error!void {
1144 const bytes = width.bytes();
1145 _ = memory.check(ram, address, bytes, fault) catch {
1146 if (!fault.valid) fault.* = .{ .address = address, .valid = true };
1147 return error.MemoryFault;
1148 };
1149 const overlaps = plan.overlaps(address, bytes) catch {
1150 fault.* = .{ .address = address, .valid = true };
1151 return error.MemoryFault;
1152 };
1153 if (overlaps) {
1154 fault.* = .{ .address = address, .valid = true };
1155 return error.MemoryFault;
1156 }
1157 memory.write(ram, address, width, value, fault) catch |failure| {
1158 switch (failure) {
1159 error.MemoryAddressOverflow,
1160 error.MemoryAuthenticationFailed,
1161 error.MemoryCapacityExceeded,
1162 error.MemoryOutOfBounds,
1163 error.MemoryReadFailed,
1164 => |access_failure| return access_failure,
1165 else => {},
1166 }
1167 if (!fault.valid) return error.UnsupportedInstruction;
1168 return error.MemoryFault;
1169 };
1170 }
1171
1172 fn prepareMemoryWrite(
1173 ram: anytype,
1174 range: memory.Range,
1175 fault: *memory.Fault,
1176 ) Error!void {
1177 memory.prepareWrite(
1178 ram,
1179 range.start,
1180 range.end - range.start,
1181 fault,
1182 ) catch |failure| return switch (failure) {
1183 error.MemoryAddressOverflow,
1184 error.MemoryAuthenticationFailed,
1185 error.MemoryCapacityExceeded,
1186 error.MemoryOutOfBounds,
1187 error.MemoryReadFailed,
1188 => |access_failure| access_failure,
1189 else => error.MemoryFault,
1190 };
1191 }
1192
1193 fn addressFailure(
1194 failure: memory.AddressError,
1195 _: *memory.Fault,
1196 ) Error {
1197 return switch (failure) {
1198 error.InvalidAddress,
1199 error.UnsupportedRegister,
1200 error.UnsupportedSegment,
1201 error.UnsupportedWidth,
1202 => error.UnsupportedInstruction,
1203 };
1204 }
1205
1206 fn shiftCount(cpu: *const Cpu, instruction: x86.Instruction) Error!u8 {
1207 if (instruction.immediate) |value| return @truncate(value.value);
1208 if (hasImplicitCl(instruction)) {
1209 return @truncate(cpu.read(.{ .number = 1, .lane = .low8 }) catch
1210 return error.UnsupportedInstruction);
1211 }
1212 if (instruction.form.opcode == 0xd0 or instruction.form.opcode == 0xd1) return 1;
1213 return error.UnsupportedInstruction;
1214 }
1215
1216 fn scalarRegister(number: u4, width: x86.Width) Error!x86.Register {
1217 const lane: x86.RegisterLane = switch (width) {
1218 .byte => .low8,
1219 .word => .word,
1220 .dword => .dword,
1221 .qword => .qword,
1222 .none, .vector128 => return error.UnsupportedInstruction,
1223 };
1224 return .{ .number = number, .lane = lane };
1225 }
1226
1227 fn operandScalarWidth(operand: x86.Operand) Error!x86.Width {
1228 const width = switch (operand) {
1229 .none => return error.UnsupportedInstruction,
1230 .register => |value| switch (value.lane) {
1231 .low8, .high8 => x86.Width.byte,
1232 .word => x86.Width.word,
1233 .dword => x86.Width.dword,
1234 .qword => x86.Width.qword,
1235 .vector128 => x86.Width.vector128,
1236 },
1237 .memory => |value| value.width,
1238 };
1239 try validateScalarWidth(width);
1240 return width;
1241 }
1242
1243 fn condition(cpu: *const Cpu, operation: x86.Operation) bool {
1244 const carry = cpu.flag(integer.flag.carry);
1245 const zero = cpu.flag(integer.flag.zero);
1246 return switch (operation) {
1247 .ja, .seta => !carry and !zero,
1248 .jae, .cmovae, .setae => !carry,
1249 .jb, .setb => carry,
1250 .jbe, .setbe => carry or zero,
1251 .je, .cmove, .sete => zero,
1252 .jne, .cmovne, .setne => !zero,
1253 .jmp => true,
1254 else => false,
1255 };
1256 }
1257
1258 fn relativeTarget(fallthrough: u64, displacement: i32) u64 {
1259 const signed: i64 = displacement;
1260 return fallthrough +% @as(u64, @bitCast(signed));
1261 }
1262
1263 fn requireSite(
1264 execution: manifest.View,
1265 target: u64,
1266 ) Error!void {
1267 _ = reference.fetch.findSite(execution, target) catch
1268 return error.InvalidControlTarget;
1269 }
1270
1271 fn stringRange(
1272 ram: anytype,
1273 address: u64,
1274 count: u64,
1275 element_bytes: u64,
1276 backwards: bool,
1277 fault: *memory.Fault,
1278 ) Error!memory.Range {
1279 if (count == 0) return memory.check(ram, address, 0, fault) catch
1280 return error.MemoryFault;
1281 const total_bytes = std.math.mul(u64, count, element_bytes) catch {
1282 fault.* = .{ .address = address, .valid = true };
1283 return error.MemoryFault;
1284 };
1285 const start = if (backwards)
1286 std.math.sub(u64, address, total_bytes - element_bytes) catch {
1287 fault.* = .{ .address = 0, .valid = true };
1288 return error.MemoryFault;
1289 }
1290 else
1291 address;
1292 return memory.check(ram, start, total_bytes, fault) catch {
1293 if (!fault.valid) fault.* = .{ .address = start, .valid = true };
1294 return error.MemoryFault;
1295 };
1296 }
1297
1298 fn stringElementAddress(
1299 initial: u64,
1300 index: usize,
1301 element_bytes: u64,
1302 backwards: bool,
1303 ) u64 {
1304 const offset = @as(u64, @intCast(index)) * element_bytes;
1305 return if (backwards) initial - offset else initial + offset;
1306 }
1307
1308 fn protectRange(
1309 plan: *const protection.Plan,
1310 range: memory.Range,
1311 fault: *memory.Fault,
1312 ) Error!void {
1313 const address: u64 = @intCast(range.start);
1314 const bytes: u64 = @intCast(range.end - range.start);
1315 const overlaps = plan.overlaps(address, bytes) catch {
1316 fault.* = .{ .address = address, .valid = true };
1317 return error.MemoryFault;
1318 };
1319 if (overlaps) {
1320 fault.* = .{ .address = address, .valid = true };
1321 return error.MemoryFault;
1322 }
1323 }
1324
1325 fn updateStringRegisters(
1326 cpu: *Cpu,
1327 source: u64,
1328 destination: u64,
1329 bytes: u64,
1330 backwards: bool,
1331 update_source: bool,
1332 ) void {
1333 const next_source = if (backwards) source -% bytes else source +% bytes;
1334 const next_destination = if (backwards)
1335 destination -% bytes
1336 else
1337 destination +% bytes;
1338 if (update_source) cpu.write(.{ .number = 6 }, next_source) catch unreachable;
1339 cpu.write(.{ .number = 7 }, next_destination) catch unreachable;
1340 cpu.write(.{ .number = 1 }, 0) catch unreachable;
1341 }
1342
1343 var test_ram: [memory.ram_bytes]u8 align(core.layout.page_bytes) = undefined;
1344
1345 fn testExecution(encoded_output: []u8) !manifest.View {
1346 const instruction = try x86.decode(&.{0x90});
1347 const loads = [_]manifest.Load{.{
1348 .input_offset = 0,
1349 .source_offset = 0,
1350 .file_bytes = 1,
1351 .memory_bytes = core.layout.page_bytes,
1352 .physical_offset = 0,
1353 .virtual_offset = 0,
1354 .flags = manifest.load_flag_read | manifest.load_flag_execute,
1355 }};
1356 const forms = [_]u64{instruction.form.key()};
1357 const sites = [_]manifest.Site{.{
1358 .virtual_offset = 0,
1359 .form_index = 0,
1360 .instruction_bytes = 1,
1361 }};
1362 const facts = manifest.k0V1Facts(0, 1, core.layout.page_bytes);
1363 const evidence: manifest.Evidence = .{
1364 .loaded_image = @splat(0),
1365 .executable_image = @splat(0),
1366 .load_plan = try manifest.loadPlanDigest(facts.entry_offset, &loads),
1367 .noncode = @splat(0),
1368 .policy = manifest.k0_v1_policy_digest,
1369 .forms = try manifest.formsDigest(&forms),
1370 .sites = try manifest.sitesDigest(&sites),
1371 };
1372 const encoded = try manifest.encode(.{
1373 .facts = facts,
1374 .evidence = evidence,
1375 .loads = &loads,
1376 .forms = &forms,
1377 .sites = &sites,
1378 }, encoded_output);
1379 return manifest.parse(encoded);
1380 }
1381
1382 test "qword sign immediates retain their architectural extension" {
1383 var encoded: [1_024]u8 = undefined;
1384 const execution = try testExecution(&encoded);
1385 const plan = try protection.Plan.init(execution);
1386 var cpu = Cpu.init(execution.header.facts.initial);
1387 try cpu.write(.{ .number = 4 }, 0x123f);
1388 const instruction = try x86.decode(&.{ 0x48, 0x83, 0xe4, 0xf0 });
1389 try validate(instruction);
1390 var fault: memory.Fault = .{};
1391 try std.testing.expectEqual(
1392 Boundary.continuing,
1393 try step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1394 );
1395 try std.testing.expectEqual(@as(u64, 0x1230), try cpu.read(.{ .number = 4 }));
1396 try std.testing.expect(!fault.valid);
1397 }
1398
1399 test "dword LEA zero extends the modular effective address" {
1400 var encoded: [1_024]u8 = undefined;
1401 const execution = try testExecution(&encoded);
1402 const plan = try protection.Plan.init(execution);
1403 var cpu = Cpu.init(execution.header.facts.initial);
1404 const source: u64 = 0xffff_ffff_8000_0001;
1405 try cpu.write(.{ .number = 2 }, source);
1406 try cpu.write(.{ .number = 0 }, std.math.maxInt(u64));
1407 const instruction = try x86.decode(&.{ 0x8d, 0x04, 0x92 });
1408 try validate(instruction);
1409 var fault: memory.Fault = .{};
1410 try std.testing.expectEqual(
1411 Boundary.continuing,
1412 try step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1413 );
1414 const expected = (source +% source *% 4) & std.math.maxInt(u32);
1415 try std.testing.expectEqual(expected, try cpu.read(.{ .number = 0 }));
1416 try std.testing.expect(!fault.valid);
1417 }
1418
1419 test "compiler scalar forms execute exact register and flag transitions" {
1420 var encoded: [1_024]u8 = undefined;
1421 const execution = try testExecution(&encoded);
1422 const plan = try protection.Plan.init(execution);
1423 var fault: memory.Fault = .{};
1424
1425 {
1426 var cpu = Cpu.init(execution.header.facts.initial);
1427 try cpu.write(.{ .number = 6 }, 0x1234_5678_9abc_8001);
1428 const instruction = try x86.decode(&.{ 0x48, 0x0f, 0xbf, 0xf6 });
1429 try validate(instruction);
1430 try std.testing.expectEqual(
1431 Boundary.continuing,
1432 try step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1433 );
1434 try std.testing.expectEqual(
1435 @as(u64, 0xffff_ffff_ffff_8001),
1436 try cpu.read(.{ .number = 6 }),
1437 );
1438 }
1439
1440 {
1441 var cpu = Cpu.init(execution.header.facts.initial);
1442 cpu.rflags |= integer.flag.carry;
1443 try cpu.write(.{ .number = 0 }, std.math.maxInt(u64));
1444 const instruction = try x86.decode(&.{ 0x48, 0xff, 0xc0 });
1445 try validate(instruction);
1446 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1447 try std.testing.expectEqual(@as(u64, 0), try cpu.read(.{ .number = 0 }));
1448 try std.testing.expect(cpu.flag(integer.flag.carry));
1449 try std.testing.expect(cpu.flag(integer.flag.zero));
1450 }
1451
1452 {
1453 var cpu = Cpu.init(execution.header.facts.initial);
1454 try cpu.write(.{ .number = 2 }, 0);
1455 const instruction = try x86.decode(&.{ 0x48, 0xff, 0xca });
1456 try validate(instruction);
1457 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1458 try std.testing.expectEqual(
1459 std.math.maxInt(u64),
1460 try cpu.read(.{ .number = 2 }),
1461 );
1462 try std.testing.expect(!cpu.flag(integer.flag.carry));
1463 try std.testing.expect(cpu.flag(integer.flag.sign));
1464 }
1465
1466 {
1467 var cpu = Cpu.init(execution.header.facts.initial);
1468 try cpu.write(.{ .number = 6 }, 0xffff_ffff_00ff_00ff);
1469 const instruction = try x86.decode(&.{ 0xf7, 0xd6 });
1470 try validate(instruction);
1471 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1472 try std.testing.expectEqual(
1473 @as(u64, 0xff00_ff00),
1474 try cpu.read(.{ .number = 6 }),
1475 );
1476 }
1477 }
1478
1479 test "compiler scalar forms execute exact rotate and multiply transitions" {
1480 var encoded: [1_024]u8 = undefined;
1481 const execution = try testExecution(&encoded);
1482 const plan = try protection.Plan.init(execution);
1483 var fault: memory.Fault = .{};
1484
1485 var cpu = Cpu.init(execution.header.facts.initial);
1486 const before: u32 = 0x8000_0001;
1487 try cpu.write(.{ .number = 6 }, before);
1488 const rotate = try x86.decode(&.{ 0xc1, 0xc6, 0x19 });
1489 try validate(rotate);
1490 _ = try step(&cpu, &test_ram, &plan, execution, rotate, &fault);
1491 const expected = before << 25 | before >> 7;
1492 try std.testing.expectEqual(
1493 @as(u64, expected),
1494 try cpu.read(.{ .number = 6 }),
1495 );
1496
1497 cpu = Cpu.init(execution.header.facts.initial);
1498 try cpu.write(.{ .number = 2 }, 2);
1499 try cpu.write(.{ .number = 13 }, 0xa5a5_a5a5_a5a5_a5a5);
1500 const multiply_instruction = try x86.decode(&.{
1501 0x4c, 0x69, 0xea, 0x00, 0xf1, 0xff, 0xff,
1502 });
1503 try validate(multiply_instruction);
1504 _ = try step(
1505 &cpu,
1506 &test_ram,
1507 &plan,
1508 execution,
1509 multiply_instruction,
1510 &fault,
1511 );
1512 try std.testing.expectEqual(
1513 @as(u64, @bitCast(@as(i64, -0x1e00))),
1514 try cpu.read(.{ .number = 13 }),
1515 );
1516 try std.testing.expect(!cpu.flag(integer.flag.carry));
1517 try std.testing.expect(!cpu.flag(integer.flag.overflow));
1518 }
1519
1520 test "conditional moves read sources and commit only on their condition" {
1521 var encoded: [1_024]u8 = undefined;
1522 const execution = try testExecution(&encoded);
1523 const plan = try protection.Plan.init(execution);
1524 var fault: memory.Fault = .{};
1525 var cpu = Cpu.init(execution.header.facts.initial);
1526 try cpu.write(.{ .number = 0 }, 0x11);
1527 try cpu.write(.{ .number = 2 }, 0x22);
1528 const above_equal = try x86.decode(&.{ 0x48, 0x0f, 0x43, 0xc2 });
1529 try validate(above_equal);
1530 _ = try step(&cpu, &test_ram, &plan, execution, above_equal, &fault);
1531 try std.testing.expectEqual(@as(u64, 0x22), try cpu.read(.{ .number = 0 }));
1532
1533 try cpu.write(.{ .number = 0 }, 0x33);
1534 cpu.rflags |= integer.flag.carry;
1535 _ = try step(&cpu, &test_ram, &plan, execution, above_equal, &fault);
1536 try std.testing.expectEqual(@as(u64, 0x33), try cpu.read(.{ .number = 0 }));
1537
1538 try cpu.write(.{ .number = 0 }, 0x44);
1539 try cpu.write(.{ .number = 1 }, 0x55);
1540 cpu.rflags |= integer.flag.zero;
1541 const equal = try x86.decode(&.{ 0x0f, 0x44, 0xc1 });
1542 try validate(equal);
1543 _ = try step(&cpu, &test_ram, &plan, execution, equal, &fault);
1544 try std.testing.expectEqual(@as(u64, 0x55), try cpu.read(.{ .number = 0 }));
1545
1546 try cpu.write(.{ .number = 0 }, 0x66);
1547 const not_equal = try x86.decode(&.{ 0x0f, 0x45, 0xc1 });
1548 try validate(not_equal);
1549 _ = try step(&cpu, &test_ram, &plan, execution, not_equal, &fault);
1550 try std.testing.expectEqual(@as(u64, 0x66), try cpu.read(.{ .number = 0 }));
1551
1552 try cpu.write(.{ .number = 0 }, memory.ram_bytes);
1553 cpu.rflags |= integer.flag.carry;
1554 const cpu_before = cpu;
1555 const faulting_source = try x86.decode(&.{ 0x48, 0x0f, 0x43, 0x00 });
1556 try validate(faulting_source);
1557 try std.testing.expectError(
1558 error.MemoryFault,
1559 step(&cpu, &test_ram, &plan, execution, faulting_source, &fault),
1560 );
1561 try std.testing.expectEqualDeep(cpu_before, cpu);
1562 try std.testing.expectEqual(@as(u64, memory.ram_bytes), fault.address);
1563 }
1564
1565 test "immediate pushes sign extend and commit one qword" {
1566 var encoded: [1_024]u8 = undefined;
1567 const execution = try testExecution(&encoded);
1568 const plan = try protection.Plan.init(execution);
1569 var cpu = Cpu.init(execution.header.facts.initial);
1570 try cpu.write(.{ .number = 4 }, 0x9000);
1571 const instruction = try x86.decode(&.{ 0x6a, 0xf8 });
1572 try validate(instruction);
1573 var fault: memory.Fault = .{};
1574 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1575 try std.testing.expectEqual(@as(u64, 0x8ff8), try cpu.read(.{ .number = 4 }));
1576 try std.testing.expectEqual(
1577 @as(u64, @bitCast(@as(i64, -8))),
1578 try memory.read(&test_ram, 0x8ff8, .qword, &fault),
1579 );
1580 }
1581
1582 test "widened REP forms preserve element width and direction" {
1583 var encoded: [1_024]u8 = undefined;
1584 const execution = try testExecution(&encoded);
1585 const plan = try protection.Plan.init(execution);
1586 var fault: memory.Fault = .{};
1587
1588 {
1589 var cpu = Cpu.init(execution.header.facts.initial);
1590 try cpu.write(.{ .number = 6 }, 0x10_000);
1591 try cpu.write(.{ .number = 7 }, 0x11_000);
1592 try cpu.write(.{ .number = 1 }, 2);
1593 try memory.write(&test_ram, 0x10_000, .qword, 0x1122_3344_5566_7788, &fault);
1594 try memory.write(&test_ram, 0x10_008, .qword, 0x99aa_bbcc_ddee_ff00, &fault);
1595 const instruction = try x86.decode(&.{ 0xf3, 0x48, 0xa5 });
1596 try validate(instruction);
1597 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1598 try std.testing.expectEqual(
1599 @as(u64, 0x1122_3344_5566_7788),
1600 try memory.read(&test_ram, 0x11_000, .qword, &fault),
1601 );
1602 try std.testing.expectEqual(
1603 @as(u64, 0x99aa_bbcc_ddee_ff00),
1604 try memory.read(&test_ram, 0x11_008, .qword, &fault),
1605 );
1606 try std.testing.expectEqual(@as(u64, 0x10_010), try cpu.read(.{ .number = 6 }));
1607 try std.testing.expectEqual(@as(u64, 0x11_010), try cpu.read(.{ .number = 7 }));
1608 try std.testing.expectEqual(@as(u64, 0), try cpu.read(.{ .number = 1 }));
1609 }
1610
1611 {
1612 var cpu = Cpu.init(execution.header.facts.initial);
1613 try cpu.write(.{ .number = 0 }, 0xdead_beef_1122_3344);
1614 try cpu.write(.{ .number = 7 }, 0x12_000);
1615 try cpu.write(.{ .number = 1 }, 3);
1616 const instruction = try x86.decode(&.{ 0xf3, 0xab });
1617 try validate(instruction);
1618 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1619 for (0..3) |index| {
1620 try std.testing.expectEqual(
1621 @as(u64, 0x1122_3344),
1622 try memory.read(
1623 &test_ram,
1624 0x12_000 + @as(u64, @intCast(index)) * 4,
1625 .dword,
1626 &fault,
1627 ),
1628 );
1629 }
1630 try std.testing.expectEqual(@as(u64, 0x12_00c), try cpu.read(.{ .number = 7 }));
1631 try std.testing.expectEqual(@as(u64, 0), try cpu.read(.{ .number = 1 }));
1632 }
1633 }
1634
1635 test "widened REP forms preserve reverse direction" {
1636 var encoded: [1_024]u8 = undefined;
1637 const execution = try testExecution(&encoded);
1638 const plan = try protection.Plan.init(execution);
1639 var fault: memory.Fault = .{};
1640 var cpu = Cpu.init(execution.header.facts.initial);
1641 cpu.rflags |= direction_flag;
1642 try cpu.write(.{ .number = 6 }, 0x13_018);
1643 try cpu.write(.{ .number = 7 }, 0x14_018);
1644 try cpu.write(.{ .number = 1 }, 2);
1645 try memory.write(&test_ram, 0x13_010, .qword, 0x0123_4567_89ab_cdef, &fault);
1646 try memory.write(&test_ram, 0x13_018, .qword, 0xfedc_ba98_7654_3210, &fault);
1647 const instruction = try x86.decode(&.{ 0xf3, 0x48, 0xa5 });
1648 try validate(instruction);
1649 _ = try step(&cpu, &test_ram, &plan, execution, instruction, &fault);
1650 try std.testing.expectEqual(
1651 @as(u64, 0x0123_4567_89ab_cdef),
1652 try memory.read(&test_ram, 0x14_010, .qword, &fault),
1653 );
1654 try std.testing.expectEqual(
1655 @as(u64, 0xfedc_ba98_7654_3210),
1656 try memory.read(&test_ram, 0x14_018, .qword, &fault),
1657 );
1658 try std.testing.expectEqual(@as(u64, 0x13_008), try cpu.read(.{ .number = 6 }));
1659 try std.testing.expectEqual(@as(u64, 0x14_008), try cpu.read(.{ .number = 7 }));
1660 try std.testing.expectEqual(@as(u64, 0), try cpu.read(.{ .number = 1 }));
1661 }
1662
1663 test "protected guest writes fault before CPU or RAM commit" {
1664 var encoded: [1_024]u8 = undefined;
1665 const execution = try testExecution(&encoded);
1666 const plan = try protection.Plan.init(execution);
1667 var cpu = Cpu.init(execution.header.facts.initial);
1668 const cpu_before = cpu;
1669 const address: usize = @intCast(execution.header.facts.physical_base);
1670 test_ram[address] = 0xc6;
1671 const instruction = try x86.decode(&.{
1672 0xc6, 0x04, 0x25, 0x00, 0x00, 0x00, 0x02, 0x90,
1673 });
1674 try validate(instruction);
1675 var fault: memory.Fault = .{};
1676 try std.testing.expectError(
1677 error.MemoryFault,
1678 step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1679 );
1680 try std.testing.expectEqualDeep(cpu_before, cpu);
1681 try std.testing.expectEqual(@as(u8, 0xc6), test_ram[address]);
1682 try std.testing.expectEqual(execution.header.facts.physical_base, fault.address);
1683 }
1684
1685 test "divide faults preserve the complete scalar transaction" {
1686 var encoded: [1_024]u8 = undefined;
1687 const execution = try testExecution(&encoded);
1688 const plan = try protection.Plan.init(execution);
1689 var cpu = Cpu.init(execution.header.facts.initial);
1690 try cpu.write(.{ .number = 0 }, 7);
1691 try cpu.write(.{ .number = 2 }, 0);
1692 try cpu.write(.{ .number = 3 }, 0);
1693 const cpu_before = cpu;
1694 const instruction = try x86.decode(&.{ 0x48, 0xf7, 0xf3 });
1695 try validate(instruction);
1696 var fault: memory.Fault = .{};
1697 try std.testing.expectError(
1698 error.DivideFault,
1699 step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1700 );
1701 try std.testing.expectEqualDeep(cpu_before, cpu);
1702 try std.testing.expect(!fault.valid);
1703 }
1704
1705 test "REP source faults are whole-instruction atomic" {
1706 var encoded: [1_024]u8 = undefined;
1707 const execution = try testExecution(&encoded);
1708 const plan = try protection.Plan.init(execution);
1709 var cpu = Cpu.init(execution.header.facts.initial);
1710 try cpu.write(.{ .number = 6 }, memory.ram_bytes - 1);
1711 try cpu.write(.{ .number = 7 }, 0x15_000);
1712 try cpu.write(.{ .number = 1 }, 2);
1713 const cpu_before = cpu;
1714 test_ram[0x15_000] = 0xa5;
1715 const instruction = try x86.decode(&.{ 0xf3, 0xa4 });
1716 try validate(instruction);
1717 var fault: memory.Fault = .{};
1718 try std.testing.expectError(
1719 error.MemoryFault,
1720 step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1721 );
1722 try std.testing.expectEqualDeep(cpu_before, cpu);
1723 try std.testing.expectEqual(@as(u8, 0xa5), test_ram[0x15_000]);
1724 try std.testing.expectEqual(@as(u64, memory.ram_bytes), fault.address);
1725 }
1726
1727 test "widened REP faults are whole-instruction atomic" {
1728 var encoded: [1_024]u8 = undefined;
1729 const execution = try testExecution(&encoded);
1730 const plan = try protection.Plan.init(execution);
1731 var fault: memory.Fault = .{};
1732
1733 {
1734 var cpu = Cpu.init(execution.header.facts.initial);
1735 try cpu.write(.{ .number = 6 }, memory.ram_bytes - 4);
1736 try cpu.write(.{ .number = 7 }, 0x16_000);
1737 try cpu.write(.{ .number = 1 }, 1);
1738 const cpu_before = cpu;
1739 try memory.write(&test_ram, 0x16_000, .qword, 0xa5a5_a5a5_a5a5_a5a5, &fault);
1740 const instruction = try x86.decode(&.{ 0xf3, 0x48, 0xa5 });
1741 try validate(instruction);
1742 try std.testing.expectError(
1743 error.MemoryFault,
1744 step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1745 );
1746 const fault_address = fault.address;
1747 try std.testing.expectEqualDeep(cpu_before, cpu);
1748 try std.testing.expectEqual(
1749 @as(u64, 0xa5a5_a5a5_a5a5_a5a5),
1750 try memory.read(&test_ram, 0x16_000, .qword, &fault),
1751 );
1752 try std.testing.expectEqual(@as(u64, memory.ram_bytes), fault_address);
1753 }
1754
1755 {
1756 var cpu = Cpu.init(execution.header.facts.initial);
1757 try cpu.write(.{ .number = 0 }, 0x1122_3344_5566_7788);
1758 try cpu.write(.{ .number = 7 }, execution.header.facts.physical_base);
1759 try cpu.write(.{ .number = 1 }, 2);
1760 const cpu_before = cpu;
1761 const start: usize = @intCast(execution.header.facts.physical_base);
1762 const ram_before = test_ram[start..][0..16].*;
1763 const instruction = try x86.decode(&.{ 0xf3, 0x48, 0xab });
1764 try validate(instruction);
1765 try std.testing.expectError(
1766 error.MemoryFault,
1767 step(&cpu, &test_ram, &plan, execution, instruction, &fault),
1768 );
1769 try std.testing.expectEqualDeep(cpu_before, cpu);
1770 try std.testing.expectEqualSlices(
1771 u8,
1772 &ram_before,
1773 test_ram[start..][0..ram_before.len],
1774 );
1775 try std.testing.expectEqual(execution.header.facts.physical_base, fault.address);
1776 }
1777 }