From d226b74ae8a408ca6d363295e00fdc2876d77fb0 Mon Sep 17 00:00:00 2001 From: kcbanner Date: Sat, 8 Jul 2023 02:52:42 -0400 Subject: [PATCH] dwarf: add ExpressionError to work around the compiler not being able to infer it dwarf: implement OP.entry_value, add tests --- lib/std/debug.zig | 1 - lib/std/dwarf.zig | 13 +- lib/std/dwarf/abi.zig | 13 +- lib/std/dwarf/expressions.zig | 223 ++++++++++++++++++++++++++-------- 4 files changed, 191 insertions(+), 59 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 1907fdc4a1..4c5b3f3121 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -628,7 +628,6 @@ pub const StackIterator = struct { // TODO: Unwind using __unwind_info, unreachable; - }, else => {}, } diff --git a/lib/std/dwarf.zig b/lib/std/dwarf.zig index c820814031..b284ac4443 100644 --- a/lib/std/dwarf.zig +++ b/lib/std/dwarf.zig @@ -1592,6 +1592,7 @@ pub const DwarfInfo = struct { entry_header.entry_bytes, -@as(i64, @intCast(@intFromPtr(binary_mem.ptr))), true, + entry_header.is_64, frame_section, entry_header.length_offset, @sizeOf(usize), @@ -1674,6 +1675,7 @@ pub const DwarfInfo = struct { } var expression_context = .{ + .is_64 = cie.is_64, .isValidMemory = context.isValidMemory, .compile_unit = di.findCompileUnit(fde.pc_begin) catch null, .thread_context = context.thread_context, @@ -2042,6 +2044,7 @@ pub const ExceptionFrameHeader = struct { cie_entry_header.entry_bytes, 0, true, + cie_entry_header.is_64, .eh_frame, cie_entry_header.length_offset, @sizeOf(usize), @@ -2135,8 +2138,8 @@ pub const CommonInformationEntry = struct { // This is the key that FDEs use to reference CIEs. length_offset: u64, version: u8, - address_size: u8, + is_64: bool, // Only present in version 4 segment_selector_size: ?u8, @@ -2175,11 +2178,12 @@ pub const CommonInformationEntry = struct { /// of `pc_rel_offset` and `is_runtime`. /// /// `length_offset` specifies the offset of this CIE's length field in the - /// .eh_frame / .debug_framesection. + /// .eh_frame / .debug_frame section. pub fn parse( cie_bytes: []const u8, pc_rel_offset: i64, is_runtime: bool, + is_64: bool, dwarf_section: DwarfSection, length_offset: u64, addr_size_bytes: u8, @@ -2280,6 +2284,7 @@ pub const CommonInformationEntry = struct { .length_offset = length_offset, .version = version, .address_size = address_size, + .is_64 = is_64, .segment_selector_size = segment_selector_size, .code_alignment_factor = code_alignment_factor, .data_alignment_factor = data_alignment_factor, @@ -2316,8 +2321,8 @@ pub const FrameDescriptionEntry = struct { /// where the section is currently stored in memory, to where it *would* be /// stored at runtime: section runtime offset - backing section data base ptr. /// - /// Similarly, `is_runtime` specifies this function is being called on a runtime section, and so - /// indirect pointers can be followed. + /// Similarly, `is_runtime` specifies this function is being called on a runtime + /// section, and so indirect pointers can be followed. pub fn parse( fde_bytes: []const u8, pc_rel_offset: i64, diff --git a/lib/std/dwarf/abi.zig b/lib/std/dwarf/abi.zig index d005e49827..f8a434dd7e 100644 --- a/lib/std/dwarf/abi.zig +++ b/lib/std/dwarf/abi.zig @@ -59,12 +59,23 @@ pub const RegisterContext = struct { is_macho: bool, }; +pub const AbiError = error{ + InvalidRegister, + UnimplementedArch, + UnimplementedOs, + ThreadContextNotSupported, +}; + /// Returns a slice containing the backing storage for `reg_number`. /// /// `reg_context` describes in what context the register number is used, as it can have different /// meanings depending on the DWARF container. It is only required when getting the stack or /// frame pointer register on some architectures. -pub fn regBytes(thread_context_ptr: anytype, reg_number: u8, reg_context: ?RegisterContext) !RegBytesReturnType(@TypeOf(thread_context_ptr)) { +pub fn regBytes( + thread_context_ptr: anytype, + reg_number: u8, + reg_context: ?RegisterContext, +) AbiError!RegBytesReturnType(@TypeOf(thread_context_ptr)) { if (builtin.os.tag == .windows) { return switch (builtin.cpu.arch) { .x86 => switch (reg_number) { diff --git a/lib/std/dwarf/expressions.zig b/lib/std/dwarf/expressions.zig index 38a70e3dbd..5708f12dfd 100644 --- a/lib/std/dwarf/expressions.zig +++ b/lib/std/dwarf/expressions.zig @@ -11,6 +11,9 @@ const assert = std.debug.assert; /// Callers should specify all the fields relevant to their context. If a field is required /// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned. pub const ExpressionContext = struct { + /// This expression is from a DWARF64 section + is_64: bool = false, + /// If specified, any addresses will pass through this function before being isValidMemory: ?*const fn (address: usize) bool = null, @@ -29,6 +32,9 @@ pub const ExpressionContext = struct { /// Call frame address, if in a CFI context cfa: ?usize = null, + + /// This expression is a sub-expression from an OP.entry_value instruction + entry_value_context: bool = false, }; pub const ExpressionOptions = struct { @@ -42,6 +48,28 @@ pub const ExpressionOptions = struct { call_frame_context: bool = false, }; +pub const ExpressionError = error{ + UnimplementedExpressionCall, + UnimplementedOpcode, + UnimplementedUserOpcode, + UnimplementedTypedComparison, + UnimplementedTypeConversion, + + UnknownExpressionOpcode, + + IncompleteExpressionContext, + + InvalidCFAOpcode, + InvalidExpression, + InvalidFrameBase, + InvalidIntegralTypeSize, + InvalidRegister, + InvalidSubExpression, + InvalidTypeLength, + + TruncatedIntegralType, +} || abi.AbiError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero }; + /// A stack machine that can decode and run DWARF expressions. /// Expressions can be decoded for non-native address size and endianness, /// but can only be executed if the current target matches the configuration. @@ -156,12 +184,14 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { } } - pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8) !?Operand { + pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8, context: ExpressionContext) !?Operand { const reader = stream.reader(); return switch (opcode) { - OP.addr, - OP.call_ref, - => generic(try reader.readInt(addr_type, options.endian)), + OP.addr => generic(try reader.readInt(addr_type, options.endian)), + OP.call_ref => if (context.is_64) + generic(try reader.readInt(u64, options.endian)) + else + generic(try reader.readInt(u32, options.endian)), OP.const1u, OP.pick, => generic(try reader.readByte()), @@ -267,7 +297,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { allocator: std.mem.Allocator, context: ExpressionContext, initial_value: ?usize, - ) !?Value { + ) ExpressionError!?Value { if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i }); var stream = std.io.fixedBufferStream(expression); while (try self.step(&stream, allocator, context)) {} @@ -281,12 +311,12 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { stream: *std.io.FixedBufferStream([]const u8), allocator: std.mem.Allocator, context: ExpressionContext, - ) !bool { + ) ExpressionError!bool { if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != comptime builtin.target.cpu.arch.endian()) @compileError("Execution of non-native address sizes / endianness is not supported"); const opcode = try stream.reader().readByte(); - if (options.call_frame_context and !opcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; + if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; switch (opcode) { // 2.5.1.1: Literal Encodings @@ -302,10 +332,10 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { OP.const8s, OP.constu, OP.consts, - => try self.stack.append(allocator, .{ .generic = (try readOperand(stream, opcode)).?.generic }), + => try self.stack.append(allocator, .{ .generic = (try readOperand(stream, opcode, context)).?.generic }), OP.const_type => { - const const_type = (try readOperand(stream, opcode)).?.const_type; + const const_type = (try readOperand(stream, opcode, context)).?.const_type; try self.stack.append(allocator, .{ .const_type = .{ .type_offset = const_type.type_offset, .value_bytes = const_type.value_bytes, @@ -315,9 +345,9 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { OP.addrx, OP.constx, => { - if (context.compile_unit == null) return error.ExpressionRequiresCompileUnit; - if (context.debug_addr == null) return error.ExpressionRequiresDebugAddr; - const debug_addr_index = (try readOperand(stream, opcode)).?.generic; + if (context.compile_unit == null) return error.IncompleteExpressionContext; + if (context.debug_addr == null) return error.IncompleteExpressionContext; + const debug_addr_index = (try readOperand(stream, opcode, context)).?.generic; const offset = context.compile_unit.?.addr_base + debug_addr_index; if (offset >= context.debug_addr.?.len) return error.InvalidExpression; const value = mem.readIntSliceNative(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)]); @@ -326,10 +356,10 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { // 2.5.1.2: Register Values OP.fbreg => { - if (context.compile_unit == null) return error.ExpressionRequiresCompileUnit; - if (context.compile_unit.?.frame_base == null) return error.ExpressionRequiresFrameBase; + if (context.compile_unit == null) return error.IncompleteExpressionContext; + if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext; - const offset: i64 = @intCast((try readOperand(stream, opcode)).?.generic); + const offset: i64 = @intCast((try readOperand(stream, opcode, context)).?.generic); _ = offset; switch (context.compile_unit.?.frame_base.?.*) { @@ -353,7 +383,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { => { if (context.thread_context == null) return error.IncompleteExpressionContext; - const base_register = (try readOperand(stream, opcode)).?.base_register; + const base_register = (try readOperand(stream, opcode, context)).?.base_register; var value: i64 = @intCast(mem.readIntSliceNative(usize, try abi.regBytes( context.thread_context.?, base_register.base_register, @@ -363,7 +393,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { try self.stack.append(allocator, .{ .generic = @intCast(value) }); }, OP.regval_type => { - const register_type = (try readOperand(stream, opcode)).?.register_type; + const register_type = (try readOperand(stream, opcode, context)).?.register_type; const value = mem.readIntSliceNative(usize, try abi.regBytes( context.thread_context.?, register_type.register, @@ -387,7 +417,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { _ = self.stack.pop(); }, OP.pick, OP.over => { - const stack_index = if (opcode == OP.over) 1 else (try readOperand(stream, opcode)).?.generic; + const stack_index = if (opcode == OP.over) 1 else (try readOperand(stream, opcode, context)).?.generic; if (stack_index >= self.stack.items.len) return error.InvalidExpression; try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]); }, @@ -429,7 +459,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { if (context.isValidMemory) |isValidMemory| if (!isValidMemory(addr)) return error.InvalidExpression; - const operand = try readOperand(stream, opcode); + const operand = try readOperand(stream, opcode, context); const size = switch (opcode) { OP.deref, OP.xderef, @@ -469,11 +499,15 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { } }, OP.push_object_address => { - if (context.object_address == null) return error.IncompleteExpressionContext; - try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) }); + // In sub-expressions, `push_object_address` is not meaningful (as per the + // spec), so treat it like a nop + if (!context.entry_value_context) { + if (context.object_address == null) return error.IncompleteExpressionContext; + try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) }); + } }, OP.form_tls_address => { - return error.UnimplementedExpressionOpcode; + return error.UnimplementedOpcode; }, OP.call_frame_cfa => { if (context.cfa) |cfa| { @@ -559,7 +593,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { }, OP.plus_uconst => { if (self.stack.items.len == 0) return error.InvalidExpression; - const constant = (try readOperand(stream, opcode)).?.generic; + const constant = (try readOperand(stream, opcode, context)).?.generic; self.stack.items[self.stack.items.len - 1] = .{ .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant), }; @@ -628,7 +662,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { } }, OP.skip, OP.bra => { - const branch_offset = (try readOperand(stream, opcode)).?.branch_offset; + const branch_offset = (try readOperand(stream, opcode, context)).?.branch_offset; const condition = if (opcode == OP.bra) blk: { if (self.stack.items.len == 0) return error.InvalidExpression; break :blk try self.stack.pop().asIntegral() != 0; @@ -648,7 +682,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { OP.call4, OP.call_ref, => { - const debug_info_offset = (try readOperand(stream, opcode)).?.generic; + const debug_info_offset = (try readOperand(stream, opcode, context)).?.generic; _ = debug_info_offset; // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it @@ -661,7 +695,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { // 2.5.1.6: Type Conversions OP.convert => { if (self.stack.items.len == 0) return error.InvalidExpression; - const type_offset = (try readOperand(stream, opcode)).?.generic; + const type_offset = (try readOperand(stream, opcode, context)).?.generic; // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size const value = self.stack.items[self.stack.items.len - 1]; @@ -675,7 +709,7 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { }, OP.reinterpret => { if (self.stack.items.len == 0) return error.InvalidExpression; - const type_offset = (try readOperand(stream, opcode)).?.generic; + const type_offset = (try readOperand(stream, opcode, context)).?.generic; // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size const value = self.stack.items[self.stack.items.len - 1]; @@ -710,15 +744,29 @@ pub fn StackMachine(comptime options: ExpressionOptions) type { // 2.5.1.7: Special Operations OP.nop => {}, OP.entry_value => { - const block = (try readOperand(stream, opcode)).?.block; - _ = block; + const block = (try readOperand(stream, opcode, context)).?.block; + if (block.len == 0) return error.InvalidSubExpression; - // TODO: If block is an expression, run it on a new stack. Push the resulting value onto this stack. - // TODO: If block is a register location, push the value that location had before running this program onto this stack. - // This implies capturing all register values before executing this block, in case this program modifies them. - // TODO: If the block contains, OP.push_object_address, treat it as OP.nop + // TODO: The spec states that this sub-expression needs to observe the state (ie. registers) + // as it was upon entering the current subprogram. If this isn't being called at the + // end of a frame unwind operation, an additional ThreadContext with this state will be needed. - return error.UnimplementedSubExpression; + if (isOpcodeRegisterLocation(block[0])) { + if (context.thread_context == null) return error.IncompleteExpressionContext; + + var block_stream = std.io.fixedBufferStream(block); + const register = (try readOperand(&block_stream, block[0], context)).?.register; + const value = mem.readIntSliceNative(usize, try abi.regBytes(context.thread_context.?, register, context.reg_context)); + try self.stack.append(allocator, .{ .generic = value }); + } else { + var stack_machine: Self = .{}; + defer stack_machine.deinit(allocator); + + var sub_context = context; + sub_context.entry_value_context = true; + const result = try stack_machine.run(block, allocator, sub_context, null); + try self.stack.append(allocator, result orelse return error.InvalidSubExpression); + } }, // These have already been handled by readOperand @@ -745,7 +793,7 @@ pub fn Builder(comptime options: ExpressionOptions) type { return struct { /// Zero-operand instructions pub fn writeOpcode(writer: anytype, comptime opcode: u8) !void { - if (options.call_frame_context and !comptime opcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; + if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode; switch (opcode) { OP.dup, OP.drop, @@ -778,6 +826,7 @@ pub fn Builder(comptime options: ExpressionOptions) type { OP.gt, OP.ne, OP.nop, + OP.stack_value, => try writer.writeByte(opcode), else => @compileError("This opcode requires operands, use `write()` instead"), } @@ -828,12 +877,12 @@ pub fn Builder(comptime options: ExpressionOptions) type { try leb.writeULEB128(writer, debug_addr_offset); } - pub fn writeConstType(writer: anytype, die_offset: anytype, size: u8, value_bytes: []const u8) !void { + pub fn writeConstType(writer: anytype, die_offset: anytype, value_bytes: []const u8) !void { if (options.call_frame_context) return error.InvalidCFAOpcode; - if (size != value_bytes.len) return error.InvalidValueSize; + if (value_bytes.len > 0xff) return error.InvalidTypeLength; try writer.writeByte(OP.const_type); try leb.writeULEB128(writer, die_offset); - try writer.writeByte(size); + try writer.writeByte(@intCast(value_bytes.len)); try writer.writeAll(value_bytes); } @@ -932,10 +981,10 @@ pub fn Builder(comptime options: ExpressionOptions) type { try writer.writeInt(T, offset, options.endian); } - pub fn writeCallRef(writer: anytype, debug_info_offset: addr_type) !void { + pub fn writeCallRef(writer: anytype, comptime is_64: bool, value: if (is_64) u64 else u32) !void { if (options.call_frame_context) return error.InvalidCFAOpcode; try writer.writeByte(OP.call_ref); - try writer.writeInt(addr_type, debug_info_offset, options.endian); + try writer.writeInt(if (is_64) u64 else u32, value, options.endian); } pub fn writeConvert(writer: anytype, die_offset: anytype) !void { @@ -959,13 +1008,29 @@ pub fn Builder(comptime options: ExpressionOptions) type { } // 2.6: Location Descriptions - // TODO + pub fn writeReg(writer: anytype, register: u8) !void { + try writer.writeByte(OP.reg0 + register); + } + + pub fn writeRegx(writer: anytype, register: anytype) !void { + try writer.writeByte(OP.regx); + try leb.writeULEB128(writer, register); + } + + pub fn writeImplicitValue(writer: anytype, value_bytes: []const u8) !void { + try writer.writeByte(OP.implicit_value); + try leb.writeULEB128(writer, value_bytes.len); + try writer.writeAll(value_bytes); + } + + // pub fn writeImplicitPointer(writer: anytype, ) void { + // } }; } // Certain opcodes are not allowed in a CFA context, see 6.4.2 -fn opcodeValidInCFA(opcode: u8) bool { +fn isOpcodeValidInCFA(opcode: u8) bool { return switch (opcode) { OP.addrx, OP.call2, @@ -984,6 +1049,13 @@ fn opcodeValidInCFA(opcode: u8) bool { }; } +fn isOpcodeRegisterLocation(opcode: u8) bool { + return switch (opcode) { + OP.reg0...OP.reg31, OP.regx => true, + else => false, + }; +} + const testing = std.testing; test "DWARF expressions" { const allocator = std.testing.allocator; @@ -1067,7 +1139,7 @@ test "DWARF expressions" { const die_offset: usize = @truncate(0xaabbccdd); const type_bytes: []const u8 = &.{ 1, 2, 3, 4 }; - try b.writeConstType(writer, die_offset, type_bytes.len, type_bytes); + try b.writeConstType(writer, die_offset, type_bytes); _ = try stack_machine.run(program.items, allocator, context, 0); @@ -1137,7 +1209,13 @@ test "DWARF expressions" { try testing.expectEqual(@as(usize, 202), stack_machine.stack.popOrNull().?.generic); try testing.expectEqual(@as(usize, 101), stack_machine.stack.popOrNull().?.generic); } else |err| { - if (err != error.UnimplementedArch and err != error.UnimplementedOs) return err; + switch (err) { + error.UnimplementedArch, + error.UnimplementedOs, + error.ThreadContextNotSupported, + => {}, + else => return err, + } } } @@ -1396,7 +1474,6 @@ test "DWARF expressions" { try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.popOrNull().?.generic); } - // Control Flow Operations { var context = ExpressionContext{}; @@ -1436,7 +1513,6 @@ test "DWARF expressions" { _ = try stack_machine.run(program.items, allocator, context, null); try testing.expectEqual(@as(usize, 2), stack_machine.stack.popOrNull().?.generic); - stack_machine.reset(); program.clearRetainingCapacity(); try b.writeLiteral(writer, 2); @@ -1470,7 +1546,7 @@ test "DWARF expressions" { // Convert to generic type stack_machine.reset(); program.clearRetainingCapacity(); - try b.writeConstType(writer, @as(usize, 0), options.addr_size, &value_bytes); + try b.writeConstType(writer, @as(usize, 0), &value_bytes); try b.writeConvert(writer, @as(usize, 0)); _ = try stack_machine.run(program.items, allocator, context, null); try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); @@ -1478,7 +1554,7 @@ test "DWARF expressions" { // Reinterpret to generic type stack_machine.reset(); program.clearRetainingCapacity(); - try b.writeConstType(writer, @as(usize, 0), options.addr_size, &value_bytes); + try b.writeConstType(writer, @as(usize, 0), &value_bytes); try b.writeReinterpret(writer, @as(usize, 0)); _ = try stack_machine.run(program.items, allocator, context, null); try testing.expectEqual(value, stack_machine.stack.popOrNull().?.generic); @@ -1488,7 +1564,7 @@ test "DWARF expressions" { stack_machine.reset(); program.clearRetainingCapacity(); - try b.writeConstType(writer, @as(usize, 0), options.addr_size, &value_bytes); + try b.writeConstType(writer, @as(usize, 0), &value_bytes); try b.writeReinterpret(writer, die_offset); _ = try stack_machine.run(program.items, allocator, context, null); const const_type = stack_machine.stack.popOrNull().?.const_type; @@ -1506,18 +1582,59 @@ test "DWARF expressions" { // Special operations { var context = ExpressionContext{}; + stack_machine.reset(); program.clearRetainingCapacity(); - try b.writeOpcode(writer, OP.nop); _ = try stack_machine.run(program.items, allocator, context, null); try testing.expect(stack_machine.stack.popOrNull() == null); + // Sub-expression + { + var sub_program = std.ArrayList(u8).init(allocator); + defer sub_program.deinit(); + const sub_writer = sub_program.writer(); + try b.writeLiteral(sub_writer, 3); + stack_machine.reset(); + program.clearRetainingCapacity(); + try b.writeEntryValue(writer, sub_program.items); + _ = try stack_machine.run(program.items, allocator, context, null); + try testing.expectEqual(@as(usize, 3), stack_machine.stack.popOrNull().?.generic); + } + // Register location description + const reg_context = abi.RegisterContext{ + .eh_frame = true, + .is_macho = builtin.os.tag == .macos, + }; + var thread_context: std.debug.ThreadContext = undefined; + context = ExpressionContext{ + .thread_context = &thread_context, + .reg_context = reg_context, + }; + if (abi.regBytes(&thread_context, 0, reg_context)) |reg_bytes| { + mem.writeIntSliceNative(usize, reg_bytes, 0xee); + + var sub_program = std.ArrayList(u8).init(allocator); + defer sub_program.deinit(); + const sub_writer = sub_program.writer(); + try b.writeReg(sub_writer, 0); + + stack_machine.reset(); + program.clearRetainingCapacity(); + try b.writeEntryValue(writer, sub_program.items); + _ = try stack_machine.run(program.items, allocator, context, null); + try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.popOrNull().?.generic); + } else |err| { + switch (err) { + error.UnimplementedArch, + error.UnimplementedOs, + error.ThreadContextNotSupported, + => {}, + else => return err, + } + } } - - } -