io_v2_native.zig (4213B)
1 //! Native v2 entry boundary. Never probes by executing or retries another driver. 2 const std = @import("std"); 3 const Arena = @import("arena.zig").Arena; 4 const P = @import("io_v2_protocol.zig"); 5 const Core = @import("io_v2_core.zig"); 6 const Host = @import("io_v2_file_host.zig").Host; 7 const C = @import("codecs.zig"); 8 const Identities = struct { 9 next: std.atomic.Value(u64) = .init(1), 10 fn issue(self: *@This()) !u64 { 11 var value = self.next.load(.monotonic); 12 while (true) { 13 if (value == std.math.maxInt(u64)) return error.RunIdentityExhausted; 14 if (self.next.cmpxchgWeak(value, value + 1, .monotonic, .monotonic)) |changed| value = changed else return value; 15 } 16 } 17 }; 18 var identities: Identities = .{}; 19 pub const Envelope = struct { version: u64, action: u32 }; 20 pub fn envelope(arena: *Arena, program: u32, fuel: u64) !Envelope { 21 const base = arena.roots.items.len; 22 defer arena.roots.shrinkRetainingCapacity(base); 23 try arena.roots.append(arena.allocator, program); 24 const r = P.Reader{ .arena = arena, .fuel = fuel }; 25 const outer = try r.pair(program) orelse return error.InvalidIOEnvelope; 26 var rest = outer.first; 27 for ("tricuIO") |ch| { 28 const cell = try r.pair(rest) orelse return error.InvalidIOEnvelope; 29 if (try r.nat(cell.first, 7) != @as(?u64, ch)) return error.InvalidIOEnvelope; 30 rest = cell.second; 31 } 32 if (try r.node(rest) != .leaf) return error.InvalidIOEnvelope; 33 const inner = try r.pair(outer.second) orelse return error.InvalidIOEnvelope; 34 return .{ .version = try r.nat(inner.first, 16) orelse return error.InvalidIOEnvelope, .action = inner.second }; 35 } 36 pub fn run(arena: *Arena, program: u32, fuel: u64) !u32 { 37 const decoded = try envelope(arena, program, fuel); 38 if (decoded.version != 2) return error.UnsupportedIOVersion; 39 var host: Host = .{}; 40 const outcome = try Core.runWithHost(Host, &host, arena, decoded.action, try identities.issue(), fuel); 41 return switch (outcome.kind) { 42 .completed => outcome.value, 43 .failed => error.IOV2Failed, 44 .cancelled => error.IOV2Cancelled, 45 }; 46 } 47 test "native run identities never repeat or wrap" { 48 var ids: Identities = .{}; 49 try std.testing.expectEqual(@as(u64, 1), try ids.issue()); 50 try std.testing.expectEqual(@as(u64, 2), try ids.issue()); 51 ids.next.store(std.math.maxInt(u64) - 1, .monotonic); 52 _ = try ids.issue(); 53 try std.testing.expectError(error.RunIdentityExhausted, ids.issue()); 54 try std.testing.expectError(error.RunIdentityExhausted, ids.issue()); 55 } 56 test "retired versions rejected without demanding action" { 57 var a = Arena.init(std.testing.allocator); 58 defer a.deinit(); 59 const bad = try a.allocApp(try a.alloc(.leaf), try a.alloc(.leaf)); 60 for ([_]u64{ 0, 1, 3, 65535 }) |version| { 61 const program = try a.alloc(.{ .fork = .{ .left = try C.ofString(&a, "tricuIO"), .right = try a.alloc(.{ .fork = .{ .left = try C.ofNumber(&a, version), .right = bad } }) } }); 62 try std.testing.expectError(error.UnsupportedIOVersion, run(&a, program, 0)); 63 } 64 } 65 66 test "successive native runs produce distinct scope identities" { 67 var arena = Arena.init(std.testing.allocator); 68 defer arena.deinit(); 69 const leaf = try arena.alloc(.leaf); 70 const action = try arena.alloc(.{ .fork = .{ .left = try C.ofNumber(&arena, 4), .right = leaf } }); 71 const program = try arena.alloc(.{ .fork = .{ .left = try C.ofString(&arena, "tricuIO"), .right = try arena.alloc(.{ .fork = .{ .left = try C.ofNumber(&arena, 2), .right = action } }) } }); 72 const first = try run(&arena, program, 10000); 73 try arena.roots.append(arena.allocator, first); 74 const second = try run(&arena, program, 10000); 75 const reader = P.Reader{ .arena = &arena, .fuel = 10000 }; 76 const first_fields = (try reader.pair(first)).?; 77 const second_fields = (try reader.pair(second)).?; 78 const first_id = try reader.nat((try reader.pair(first_fields.second)).?.first, 64); 79 const second_id = try reader.nat((try reader.pair(second_fields.second)).?.first, 64); 80 try std.testing.expect(first_id != null and second_id != null and first_id != second_id); 81 }