io_v2_core_probe.zig (5456B)
1 //! Test-only executable: compiled source against the private core, no native IO 2 //! facilities. This is not the tricu-zig CLI or a compiler execution backend. 3 const std = @import("std"); 4 const Arena = @import("arena.zig").Arena; 5 const P = @import("io_v2_protocol.zig"); 6 const C = @import("codecs.zig"); 7 const Core = @import("io_v2_core.zig"); 8 const bundle = @import("bundle.zig"); 9 const reduce = @import("reduce.zig"); 10 pub fn main(init: std.process.Init) !void { 11 return mainFor(Core.UnsupportedHost, init); 12 } 13 pub fn mainFor(comptime Host: type, init: std.process.Init) !void { 14 const args = try init.minimal.args.toSlice(init.arena.allocator()); 15 if (args.len < 3) return error.ExpectedBundleAndExport; 16 const bytes = try std.Io.Dir.cwd().readFileAlloc(init.io, args[1], init.gpa, .limited(10 * 1024 * 1024)); 17 defer init.gpa.free(bytes); 18 var arena = Arena.init(init.gpa); 19 defer arena.deinit(); 20 var root = try bundle.loadBundleExport(&arena, bytes, args[2]); 21 for (args[3..]) |argument| { 22 const value = if (std.mem.startsWith(u8, argument, "tree-file:")) blk: { 23 const raw = try std.Io.Dir.cwd().readFileAlloc(init.io, argument[10..], init.gpa, .limited(10 * 1024 * 1024)); 24 defer init.gpa.free(raw); 25 break :blk try parseRaw(&arena, raw); 26 } else if (std.mem.startsWith(u8, argument, "text:")) try @import("source_text.zig").fromUtf8(&arena, argument[5..]) else try C.ofNumber(&arena, try std.fmt.parseUnsigned(u64, argument, 10)); 27 root = try arena.allocApp(root, value); 28 } 29 try arena.roots.append(init.gpa, root); 30 arena.gc_enabled = true; 31 try arena.setGcPolicy(128, 1); 32 const r: P.Reader = .{ .arena = &arena, .fuel = std.math.maxInt(u64) }; 33 const envelope = try r.pair(root) orelse return error.InvalidEnvelope; 34 var name = envelope.first; 35 for ("tricuIO") |ch| { 36 const cell = try r.pair(name) orelse return error.InvalidEnvelope; 37 if (try r.nat(cell.first, 7) != @as(?u64, ch)) return error.InvalidEnvelope; 38 name = cell.second; 39 } 40 if (try r.node(name) != .leaf) return error.InvalidEnvelope; 41 const body = try r.pair(envelope.second) orelse return error.InvalidEnvelope; 42 if (try r.nat(body.first, 16) != @as(?u64, 2)) return error.UnsupportedVersion; 43 // Drop the input envelope; private run will root the current computation. 44 arena.roots.clearRetainingCapacity(); 45 var host: Host = .{}; 46 const outcome = try Core.runWithHost(Host, &host, &arena, body.second, 1, std.math.maxInt(u64)); 47 if (outcome.kind != .completed) { 48 var error_output = std.Io.File.stderr().writer(init.io, &.{}); 49 try error_output.interface.writeAll(if (outcome.kind == .failed) "failed (payload not forced)\n" else "cancelled (reason not forced)\n"); 50 try error_output.flush(); 51 std.process.exit(1); 52 } 53 try arena.roots.append(init.gpa, outcome.value); 54 var stack: std.ArrayList(u32) = .empty; 55 defer stack.deinit(init.gpa); 56 try stack.append(init.gpa, outcome.value); 57 var buffer: [4096]u8 = undefined; 58 var output = std.Io.File.stdout().writer(init.io, &buffer); 59 var count: usize = 0; 60 while (stack.pop()) |id| { 61 count += 1; 62 if (count > 1048576) return error.ResultLimit; 63 const node = arena.get(try reduce.reduce(id, &arena, 1_000_000)).*; 64 switch (node) { 65 .leaf => try output.interface.writeByte('0'), 66 .stem => |s| { 67 try output.interface.writeByte('1'); 68 try stack.append(init.gpa, s.child); 69 }, 70 .fork => |f| { 71 try output.interface.writeByte('2'); 72 try stack.appendSlice(init.gpa, &.{ f.right, f.left }); 73 }, 74 .app => unreachable, 75 } 76 } 77 try output.interface.writeByte('\n'); 78 try output.flush(); 79 } 80 81 // Exact raw constructor tree (0/1/2 bytes), parsed without recursion or ignored 82 // trailing roots. Private source controls can pass NUL and malformed scalar data. 83 fn parseRaw(arena: *Arena, bytes: []const u8) !u32 { 84 var stack: std.ArrayList(u32) = .empty; 85 defer stack.deinit(arena.allocator); 86 var at = bytes.len; 87 while (at != 0) { 88 at -= 1; 89 const node: @import("tree.zig").Node = switch (bytes[at]) { 90 0 => .leaf, 91 1 => .{ .stem = .{ .child = stack.pop() orelse return error.InvalidRawTree } }, 92 2 => .{ .fork = .{ .left = stack.pop() orelse return error.InvalidRawTree, .right = stack.pop() orelse return error.InvalidRawTree } }, 93 else => return error.InvalidRawTree, 94 }; 95 try stack.append(arena.allocator, try arena.alloc(node)); 96 } 97 if (stack.items.len != 1) return error.InvalidRawTree; 98 return stack.items[0]; 99 } 100 101 test "private raw argument parser is exact, iterative and preserves constructor order" { 102 const t = std.testing; 103 var a = Arena.init(t.allocator); 104 defer a.deinit(); 105 const root = try parseRaw(&a, &.{ 2, 1, 0, 0 }); 106 const node = a.get(root).fork; 107 try t.expect(a.get(node.left).* == .stem and a.get(node.right).* == .leaf); 108 for ([_][]const u8{ &.{}, &.{ 0, 0 }, &.{1}, &.{ 2, 0 }, &.{3}, "0" }) |raw| try t.expectError(error.InvalidRawTree, parseRaw(&a, raw)); 109 const deep = try t.allocator.alloc(u8, 100001); 110 defer t.allocator.free(deep); 111 @memset(deep, 1); 112 deep[deep.len - 1] = 0; 113 _ = try parseRaw(&a, deep); 114 }