authorgravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-05-09 01:35:22-04:00
committergravatar for kcbanner@gmail.comCasey Banner <kcbanner@gmail.com> 2023-07-20 22:58:13-04:00
logf3f3c877e0da444e6e5208d7c7179776ddb8ecd8
treef9bc05e28a57b6de90e275804169b5758f35d866
parent38303d7b9cd3eb12c59636e84b9a411b07ad79af

- add DWARF expression parser

- change read apis to use a stream - add register formatters

3 files changed, 395 insertions(+), 73 deletions(-)

lib/std/dwarf/abi.zig+27-7
......@@ -1,7 +1,7 @@
11const std = @import("../std.zig");
22
33fn writeUnknownReg(writer: anytype, reg_number: u8) !void {
4 try writer.print("reg{}", .{ reg_number });
4 try writer.print("reg{}", .{reg_number});
55}
66
77pub fn writeRegisterName(writer: anytype, arch: ?std.Target.Cpu.Arch, reg_number: u8) !void {
......@@ -17,11 +17,11 @@ pub fn writeRegisterName(writer: anytype, arch: ?std.Target.Cpu.Arch, reg_number
1717 5 => try writer.writeAll("RDI"),
1818 6 => try writer.writeAll("RBP"),
1919 7 => try writer.writeAll("RSP"),
20 8...15 => try writer.print("R{}", .{ reg_number }),
20 8...15 => try writer.print("R{}", .{reg_number}),
2121 16 => try writer.writeAll("RIP"),
22 17...32 => try writer.print("XMM{}", .{ reg_number - 17 }),
23 33...40 => try writer.print("ST{}", .{ reg_number - 33 }),
24 41...48 => try writer.print("MM{}", .{ reg_number - 41 }),
22 17...32 => try writer.print("XMM{}", .{reg_number - 17}),
23 33...40 => try writer.print("ST{}", .{reg_number - 33}),
24 41...48 => try writer.print("MM{}", .{reg_number - 41}),
2525 49 => try writer.writeAll("RFLAGS"),
2626 50 => try writer.writeAll("ES"),
2727 51 => try writer.writeAll("CS"),
......@@ -38,9 +38,9 @@ pub fn writeRegisterName(writer: anytype, arch: ?std.Target.Cpu.Arch, reg_number
3838 64 => try writer.writeAll("MXCSR"),
3939 65 => try writer.writeAll("FCW"),
4040 66 => try writer.writeAll("FSW"),
41 67...82 => try writer.print("XMM{}", .{ reg_number - 51 }),
41 67...82 => try writer.print("XMM{}", .{reg_number - 51}),
4242 // 83-117 Reserved
43 118...125 => try writer.print("K{}", .{ reg_number - 118 }),
43 118...125 => try writer.print("K{}", .{reg_number - 118}),
4444 // 126-129 Reserved
4545 else => try writeUnknownReg(writer, reg_number),
4646 }
......@@ -52,3 +52,23 @@ pub fn writeRegisterName(writer: anytype, arch: ?std.Target.Cpu.Arch, reg_number
5252 }
5353 } else try writeUnknownReg(writer, reg_number);
5454}
55
56const FormatRegisterData = struct {
57 reg_number: u8,
58 arch: ?std.Target.Cpu.Arch,
59};
60
61pub fn formatRegister(
62 data: FormatRegisterData,
63 comptime fmt: []const u8,
64 options: std.fmt.FormatOptions,
65 writer: anytype,
66) !void {
67 _ = fmt;
68 _ = options;
69 try writeRegisterName(writer, data.arch, data.reg_number);
70}
71
72pub fn fmtRegister(reg_number: u8, arch: ?std.Target.Cpu.Arch) std.fmt.Formatter(formatRegister) {
73 return .{ .data = .{ .reg_number = reg_number, .arch = arch } };
74}
lib/std/dwarf/call_frame.zig+171-66
......@@ -3,18 +3,13 @@ const debug = std.debug;
33const leb = @import("../leb128.zig");
44const abi = @import("abi.zig");
55const dwarf = @import("../dwarf.zig");
6const expressions = @import("expressions.zig");
67
7// These enum values correspond to the opcode encoding itself, with
8// the exception of the opcodes that include data in the opcode itself.
9// For those, the enum value is the opcode with the lower 6 bits (the data) masked to 0.
108const Opcode = enum(u8) {
11 // These are placeholders that define the range of vendor-specific opcodes
12 const lo_user = 0x1c;
13 const hi_user = 0x3f;
14
159 advance_loc = 0x1 << 6,
1610 offset = 0x2 << 6,
1711 restore = 0x3 << 6,
12
1813 nop = 0x00,
1914 set_loc = 0x01,
2015 advance_loc1 = 0x02,
......@@ -39,7 +34,17 @@ const Opcode = enum(u8) {
3934 val_offset_sf = 0x15,
4035 val_expression = 0x16,
4136
42 _,
37 // These opcodes encode an operand in the lower 6 bits of the opcode itself
38 pub const lo_inline = Opcode.advance_loc;
39 pub const hi_inline = Opcode.restore;
40
41 // These opcodes are trailed by zero or more operands
42 pub const lo_reserved = Opcode.nop;
43 pub const hi_reserved = Opcode.val_expression;
44
45 // Vendor-specific opcodes
46 pub const lo_user = 0x1c;
47 pub const hi_user = 0x3f;
4348};
4449
4550const Operand = enum {
......@@ -70,11 +75,12 @@ const Operand = enum {
7075
7176 fn read(
7277 comptime self: Operand,
73 reader: anytype,
78 stream: *std.io.FixedBufferStream([]const u8),
7479 opcode_value: ?u6,
7580 addr_size_bytes: u8,
7681 endian: std.builtin.Endian,
7782 ) !Storage(self) {
83 const reader = stream.reader();
7884 return switch (self) {
7985 .opcode_delta, .opcode_register => opcode_value orelse return error.InvalidOperand,
8086 .uleb128_register => try leb.readULEB128(u8, reader),
......@@ -91,13 +97,13 @@ const Operand = enum {
9197 .u32_delta => try reader.readInt(u32, endian),
9298 .block => {
9399 const block_len = try leb.readULEB128(u64, reader);
100 if (stream.pos + block_len > stream.buffer.len) return error.InvalidOperand;
94101
95 // TODO: This feels like a kludge, change to FixedBufferStream param?
96 const block = reader.context.buffer[reader.context.pos..][0..block_len];
102 const block = stream.buffer[stream.pos..][0..block_len];
97103 reader.context.pos += block_len;
98104
99105 return block;
100 }
106 },
101107 };
102108 }
103109};
......@@ -133,11 +139,16 @@ fn InstructionType(comptime definition: anytype) type {
133139 const Self = @This();
134140 operands: InstructionOperands,
135141
136 pub fn read(reader: anytype, opcode_value: ?u6, addr_size_bytes: u8, endian: std.builtin.Endian) !Self {
142 pub fn read(
143 stream: *std.io.FixedBufferStream([]const u8),
144 opcode_value: ?u6,
145 addr_size_bytes: u8,
146 endian: std.builtin.Endian,
147 ) !Self {
137148 var operands: InstructionOperands = undefined;
138149 inline for (definition_type.Struct.fields) |definition_field| {
139150 const operand = comptime std.enums.nameCast(Operand, @field(definition, definition_field.name));
140 @field(operands, definition_field.name) = try operand.read(reader, opcode_value, addr_size_bytes, endian);
151 @field(operands, definition_field.name) = try operand.read(stream, opcode_value, addr_size_bytes, endian);
141152 }
142153
143154 return .{ .operands = operands };
......@@ -173,37 +184,44 @@ pub const Instruction = union(Opcode) {
173184 val_offset_sf: InstructionType(.{ .a = .uleb128_offset, .b = .sleb128_offset }),
174185 val_expression: InstructionType(.{ .a = .uleb128_offset, .block = .block }),
175186
176 pub fn read(reader: anytype, addr_size_bytes: u8, endian: std.builtin.Endian) !Instruction {
177 const opcode = try reader.readByte();
178 const upper = opcode & 0b11000000;
179 return switch (upper) {
180 inline @enumToInt(Opcode.advance_loc), @enumToInt(Opcode.offset), @enumToInt(Opcode.restore) => |u| @unionInit(
181 Instruction,
182 @tagName(@intToEnum(Opcode, u)),
183 try std.meta.TagPayload(Instruction, @intToEnum(Opcode, u)).read(reader, @intCast(u6, opcode & 0b111111), addr_size_bytes, endian),
184 ),
185 0 => blk: {
186 inline for (@typeInfo(Opcode).Enum.fields) |field| {
187 if (field.value == opcode) {
188 break :blk @unionInit(
189 Instruction,
190 @tagName(@intToEnum(Opcode, field.value)),
191 try std.meta.TagPayload(Instruction, @intToEnum(Opcode, field.value)).read(reader, null, addr_size_bytes, endian),
192 );
193 }
194 }
195 break :blk error.UnknownOpcode;
187 pub fn read(
188 stream: *std.io.FixedBufferStream([]const u8),
189 addr_size_bytes: u8,
190 endian: std.builtin.Endian,
191 ) !Instruction {
192 @setEvalBranchQuota(1800);
193
194 return switch (try stream.reader().readByte()) {
195 inline @enumToInt(Opcode.lo_inline)...@enumToInt(Opcode.hi_inline) => |opcode| blk: {
196 const e = @intToEnum(Opcode, opcode & 0b11000000);
197 const payload_type = std.meta.TagPayload(Instruction, e);
198 const value = try payload_type.read(stream, @intCast(u6, opcode & 0b111111), addr_size_bytes, endian);
199 break :blk @unionInit(Instruction, @tagName(e), value);
196200 },
197 else => error.UnknownOpcode,
201 inline @enumToInt(Opcode.lo_reserved)...@enumToInt(Opcode.hi_reserved) => |opcode| blk: {
202 const e = @intToEnum(Opcode, opcode);
203 const payload_type = std.meta.TagPayload(Instruction, e);
204 const value = try payload_type.read(stream, null, addr_size_bytes, endian);
205 break :blk @unionInit(Instruction, @tagName(e), value);
206 },
207 Opcode.lo_user...Opcode.hi_user => error.UnimplementedUserOpcode,
208 else => error.InvalidOpcode,
198209 };
199210 }
200211
201 pub fn writeOperands(self: Instruction, writer: anytype, cie: dwarf.CommonInformationEntry, arch: ?std.Target.Cpu.Arch) !void {
212 pub fn writeOperands(
213 self: Instruction,
214 writer: anytype,
215 cie: dwarf.CommonInformationEntry,
216 arch: ?std.Target.Cpu.Arch,
217 addr_size_bytes: u8,
218 endian: std.builtin.Endian,
219 ) !void {
202220 switch (self) {
203 inline .advance_loc, .advance_loc1, .advance_loc2, .advance_loc4 => |i| try writer.print("{}", .{ i.operands.delta * cie.code_alignment_factor }),
221 inline .advance_loc, .advance_loc1, .advance_loc2, .advance_loc4 => |i| try writer.print("{}", .{i.operands.delta * cie.code_alignment_factor}),
204222 .offset => |i| {
205223 try abi.writeRegisterName(writer, arch, i.operands.register);
206 try writer.print(" {}", .{ @intCast(i64, i.operands.offset) * cie.data_alignment_factor });
224 try writer.print(" {}", .{@intCast(i64, i.operands.offset) * cie.data_alignment_factor});
207225 },
208226 .restore => {},
209227 .nop => {},
......@@ -217,14 +235,14 @@ pub const Instruction = union(Opcode) {
217235 .restore_state => {},
218236 .def_cfa => |i| {
219237 try abi.writeRegisterName(writer, arch, i.operands.register);
220 try writer.print(" {}", .{ fmtOffset(@intCast(i64, i.operands.offset)) });
238 try writer.print(" {d:<1}", .{@intCast(i64, i.operands.offset)});
221239 },
222240 .def_cfa_register => {},
223241 .def_cfa_offset => |i| {
224 try writer.print("{}", .{ fmtOffset(@intCast(i64, i.operands.offset)) });
242 try writer.print("{d:<1}", .{@intCast(i64, i.operands.offset)});
225243 },
226244 .def_cfa_expression => |i| {
227 try writer.print("TODO(parse expressions data {x})", .{ std.fmt.fmtSliceHexLower(i.operands.block) });
245 try writeExpression(writer, i.operands.block, arch, addr_size_bytes, endian);
228246 },
229247 .expression => {},
230248 .offset_extended_sf => {},
......@@ -235,23 +253,83 @@ pub const Instruction = union(Opcode) {
235253 .val_expression => {},
236254 }
237255 }
238
239256};
240257
258fn writeExpression(
259 writer: anytype,
260 block: []const u8,
261 arch: ?std.Target.Cpu.Arch,
262 addr_size_bytes: u8,
263 endian: std.builtin.Endian,
264) !void {
265 var stream = std.io.fixedBufferStream(block);
266
267 // Generate a lookup table from opcode value to name
268 const opcode_lut_len = 256;
269 const opcode_lut: [opcode_lut_len]?[]const u8 = comptime blk: {
270 var lut: [opcode_lut_len]?[]const u8 = [_]?[]const u8{null} ** opcode_lut_len;
271 for (@typeInfo(dwarf.OP).Struct.decls) |decl| {
272 lut[@as(u8, @field(dwarf.OP, decl.name))] = decl.name;
273 }
241274
242fn formatOffset(data: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
243 _ = fmt;
244 if (data >= 0) try writer.writeByte('+');
245 return std.fmt.formatInt(data, 10, .lower, options, writer);
246}
275 break :blk lut;
276 };
247277
248fn fmtOffset(offset: i64) std.fmt.Formatter(formatOffset) {
249 return .{ .data = offset };
278 switch (endian) {
279 inline .Little, .Big => |e| {
280 switch (addr_size_bytes) {
281 inline 2, 4, 8 => |size| {
282 const StackMachine = expressions.StackMachine(.{
283 .addr_size = size,
284 .endian = e,
285 .call_frame_mode = true,
286 });
287
288 const reader = stream.reader();
289 while (stream.pos < stream.buffer.len) {
290 if (stream.pos > 0) try writer.writeAll(", ");
291
292 const opcode = try reader.readByte();
293 if (opcode_lut[opcode]) |opcode_name| {
294 try writer.print("DW_OP_{s}", .{opcode_name});
295 } else {
296 // TODO: See how llvm-dwarfdump prints these?
297 if (opcode >= dwarf.OP.lo_user and opcode <= dwarf.OP.lo_user) {
298 try writer.print("<unknown vendor opcode: 0x{x}>", .{opcode});
299 } else {
300 try writer.print("<invalid opcode: 0x{x}>", .{opcode});
301 }
302 }
303
304 if (try StackMachine.readOperand(&stream, opcode)) |value| {
305 switch (value) {
306 //.generic => |v| try writer.print("{d}", .{v}),
307 .generic => {}, // Constant values are implied by the opcode name
308 .register => |v| try writer.print(" {}", .{ abi.fmtRegister(v, arch) }),
309 .base_register => |v| try writer.print(" {}{d:<1}", .{ abi.fmtRegister(v.base_register, arch), v.offset }),
310 else => try writer.print(" TODO({s})", .{@tagName(value)}),
311 }
312 }
313 }
314 },
315 else => return error.InvalidAddrSize,
316 }
317 },
318 }
250319}
251320
321// fn formatOffset(data: i64, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype) !void {
322// _ = fmt;
323// if (data >= 0) try writer.writeByte('+');
324// return std.fmt.formatInt(data, 10, .lower, options, writer);
325// }
326
327// fn fmtOffset(offset: i64) std.fmt.Formatter(formatOffset) {
328// return .{ .data = offset };
329// }
330
252331/// See section 6.4.1 of the DWARF5 specification
253332pub const VirtualMachine = struct {
254
255333 const RegisterRule = union(enum) {
256334 undefined: void,
257335 same_value: void,
......@@ -263,11 +341,18 @@ pub const VirtualMachine = struct {
263341 architectural: void,
264342 };
265343
266 const Column = struct {
344 pub const Column = struct {
267345 register: u8 = undefined,
268346 rule: RegisterRule = .{ .undefined = {} },
269347
270 pub fn writeRule(self: Column, writer: anytype, is_cfa: bool, arch: ?std.Target.Cpu.Arch) !void {
348 pub fn writeRule(
349 self: Column,
350 writer: anytype,
351 is_cfa: bool,
352 arch: ?std.Target.Cpu.Arch,
353 addr_size_bytes: u8,
354 endian: std.builtin.Endian,
355 ) !void {
271356 if (is_cfa) {
272357 try writer.writeAll("CFA");
273358 } else {
......@@ -281,48 +366,54 @@ pub const VirtualMachine = struct {
281366 .offset => |offset| {
282367 if (is_cfa) {
283368 try abi.writeRegisterName(writer, arch, self.register);
284 try writer.print("{}", .{ fmtOffset(offset) });
369 try writer.print("{d:<1}", .{offset});
285370 } else {
286 try writer.print("[CFA{}]", .{ fmtOffset(offset) });
371 try writer.print("[CFA{d:<1}]", .{offset});
287372 }
288373 },
289374 .val_offset => |offset| {
290375 if (is_cfa) {
291376 try abi.writeRegisterName(writer, arch, self.register);
292 try writer.print("{}", .{ fmtOffset(offset) });
377 try writer.print("{d:<1}", .{offset});
293378 } else {
294 try writer.print("CFA{}", .{ fmtOffset(offset) });
379 try writer.print("CFA{d:<1}", .{offset});
295380 }
296381 },
297382 .register => |register| try abi.writeRegisterName(writer, arch, register),
298 .expression => try writer.writeAll("TODO(expression)"),
383 .expression => |expression| try writeExpression(writer, expression, arch, addr_size_bytes, endian),
299384 .val_expression => try writer.writeAll("TODO(val_expression)"),
300385 .architectural => try writer.writeAll("TODO(architectural)"),
301386 }
302387 }
303388 };
304389
390 /// Each row contains unwinding rules for a set of registers at a specific location in the program.
305391 pub const Row = struct {
306392 /// Offset from pc_begin
307393 offset: u64 = 0,
394 /// Special-case column that defines the CFA (Canonical Frame Address) rule.
395 /// The register field of this column defines the register that CFA is derived
396 /// from, while other columns define registers in terms of the CFA.
308397 cfa: Column = .{},
309 /// Index into `columns` of the first column in this row
398 /// Index into `columns` of the first column in this row.
310399 columns_start: usize = undefined,
311400 columns_len: u8 = 0,
312401 };
313402
314 rows: std.ArrayListUnmanaged(Row) = .{},
315403 columns: std.ArrayListUnmanaged(Column) = .{},
404 row_stack: std.ArrayListUnmanaged(Row) = .{},
316405 current_row: Row = .{},
317406
407 // TODO: Add stack machine stack
408
318409 pub fn reset(self: *VirtualMachine) void {
319 self.rows.clearRetainingCapacity();
410 self.row_stack.clearRetainingCapacity();
320411 self.columns.clearRetainingCapacity();
321412 self.current_row = .{};
322413 }
323414
324415 pub fn deinit(self: *VirtualMachine, allocator: std.mem.Allocator) void {
325 self.rows.deinit(allocator);
416 self.row_stack.deinit(allocator);
326417 self.columns.deinit(allocator);
327418 self.* = undefined;
328419 }
......@@ -366,8 +457,20 @@ pub const VirtualMachine = struct {
366457 .undefined => {},
367458 .same_value => {},
368459 .register => {},
369 .remember_state => {},
370 .restore_state => {},
460 .remember_state => {
461
462 // TODO: The row stack only actually needs the column information
463 // TODO: Also it needs to copy the columns because changes can edit the referenced columns
464 // TODO: This function could push the column range onto the stack, the copy the columns and update current row
465
466 try self.row_stack.append(allocator, self.current_row);
467 },
468 .restore_state => {
469 if (self.row_stack.items.len == 0) return error.InvalidOperation;
470 const row = self.row_stack.pop();
471 self.current_row.columns_len = row.columns_len;
472 self.current_row.columns_start = row.columns_start;
473 },
371474 .def_cfa => |i| {
372475 self.current_row.cfa = .{
373476 .register = i.operands.register,
......@@ -376,11 +479,14 @@ pub const VirtualMachine = struct {
376479 },
377480 .def_cfa_register => {},
378481 .def_cfa_offset => |i| {
482 self.current_row.cfa.rule = .{ .offset = @intCast(i64, i.operands.offset) };
483 },
484 .def_cfa_expression => |i| {
485 self.current_row.cfa.register = undefined;
379486 self.current_row.cfa.rule = .{
380 .offset = @intCast(i64, i.operands.offset)
487 .expression = i.operands.block,
381488 };
382489 },
383 .def_cfa_expression => {},
384490 .expression => {},
385491 .offset_extended_sf => {},
386492 .def_cfa_sf => {},
......@@ -390,5 +496,4 @@ pub const VirtualMachine = struct {
390496 .val_expression => {},
391497 }
392498 }
393
394499};
lib/std/dwarf/expressions.zig created+197
......@@ -0,0 +1,197 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const OP = @import("OP.zig");
4const leb = @import("../leb128.zig");
5
6pub const StackMachineOptions = struct {
7 /// The address size of the target architecture
8 addr_size: u8 = @sizeOf(usize),
9
10 /// Endianess of the target architecture
11 endian: std.builtin.Endian = .Little,
12
13 /// Restrict the stack machine to a subset of opcodes used in call frame instructions
14 call_frame_mode: bool = false,
15};
16
17/// A stack machine that can decode and run DWARF expressions.
18/// Expressions can be decoded for non-native address size and endianness,
19/// but can only be executed if the current target matches the configuration.
20pub fn StackMachine(comptime options: StackMachineOptions) type {
21 const addr_type = switch(options.addr_size) {
22 2 => u16,
23 4 => u32,
24 8 => u64,
25 else => @compileError("Unsupported address size of " ++ options.addr_size),
26 };
27
28 const addr_type_signed = switch(options.addr_size) {
29 2 => i16,
30 4 => i32,
31 8 => i64,
32 else => @compileError("Unsupported address size of " ++ options.addr_size),
33 };
34
35 return struct {
36 const Value = union(enum) {
37 generic: addr_type,
38 const_type: []const u8,
39 register: u8,
40 base_register: struct {
41 base_register: u8,
42 offset: i64,
43 },
44 composite_location: struct {
45 size: u64,
46 offset: i64,
47 },
48 block: []const u8,
49 base_type: struct {
50 type_offset: u64,
51 value_bytes: []const u8,
52 },
53 deref_type: struct {
54 size: u8,
55 offset: u64,
56 },
57 };
58
59 stack: std.ArrayListUnmanaged(Value) = .{},
60
61 fn generic(value: anytype) Value {
62 const int_info = @typeInfo(@TypeOf(value)).Int;
63 if (@sizeOf(@TypeOf(value)) > options.addr_size) {
64 return .{
65 .generic = switch (int_info.signedness) {
66 .signed => @bitCast(addr_type, @truncate(addr_type_signed, value)),
67 .unsigned => @truncate(addr_type, value),
68 }
69 };
70 } else {
71 return .{
72 .generic = switch (int_info.signedness) {
73 .signed => @bitCast(addr_type, @intCast(addr_type_signed, value)),
74 .unsigned => @intCast(addr_type, value),
75 }
76 };
77 }
78 }
79
80 pub fn readOperand(stream: *std.io.FixedBufferStream([]const u8), opcode: u8) !?Value {
81 const reader = stream.reader();
82 return switch (opcode) {
83 OP.addr,
84 OP.call_ref,
85 => generic(try reader.readInt(addr_type, options.endian)),
86 OP.const1u,
87 OP.pick,
88 OP.deref_size,
89 OP.xderef_size,
90 => generic(try reader.readByte()),
91 OP.const1s => generic(try reader.readByteSigned()),
92 OP.const2u,
93 OP.call2,
94 OP.call4,
95 => generic(try reader.readInt(u16, options.endian)),
96 OP.const2s,
97 OP.bra,
98 OP.skip,
99 => generic(try reader.readInt(i16, options.endian)),
100 OP.const4u => generic(try reader.readInt(u32, options.endian)),
101 OP.const4s => generic(try reader.readInt(i32, options.endian)),
102 OP.const8u => generic(try reader.readInt(u64, options.endian)),
103 OP.const8s => generic(try reader.readInt(i64, options.endian)),
104 OP.constu,
105 OP.plus_uconst,
106 OP.addrx,
107 OP.constx,
108 OP.convert,
109 OP.reinterpret,
110 => generic(try leb.readULEB128(u64, reader)),
111 OP.consts,
112 OP.fbreg,
113 => generic(try leb.readILEB128(i64, reader)),
114 OP.lit0...OP.lit31 => |n| generic(n - OP.lit0),
115 OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 },
116 OP.breg0...OP.breg31 => |n| .{
117 .base_register = .{
118 .base_register = n - OP.breg0,
119 .offset = try leb.readILEB128(i64, reader),
120 }
121 },
122 OP.regx => .{ .register = try leb.readULEB128(u8, reader) },
123 OP.bregx,
124 OP.regval_type => .{
125 .base_register = .{
126 .base_register = try leb.readULEB128(u8, reader),
127 .offset = try leb.readILEB128(i64, reader),
128 }
129 },
130 OP.piece => .{
131 .composite_location = .{
132 .size = try leb.readULEB128(u8, reader),
133 .offset = 0,
134 },
135 },
136 OP.bit_piece => .{
137 .composite_location = .{
138 .size = try leb.readULEB128(u8, reader),
139 .offset = try leb.readILEB128(i64, reader),
140 },
141 },
142 OP.implicit_value,
143 OP.entry_value
144 => blk: {
145 const size = try leb.readULEB128(u8, reader);
146 if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
147 const block = stream.buffer[stream.pos..][0..size];
148 stream.pos += size;
149 break :blk .{
150 .block = block,
151 };
152 },
153 OP.const_type => blk: {
154 const type_offset = try leb.readULEB128(u8, reader);
155 const size = try reader.readByte();
156 if (stream.pos + size > stream.buffer.len) return error.InvalidExpression;
157 const value_bytes = stream.buffer[stream.pos..][0..size];
158 stream.pos += size;
159 break :blk .{
160 .base_type = .{
161 .type_offset = type_offset,
162 .value_bytes = value_bytes,
163 }
164 };
165 },
166 OP.deref_type,
167 OP.xderef_type,
168 => .{
169 .deref_type = .{
170 .size = try reader.readByte(),
171 .offset = try leb.readULEB128(u64, reader),
172 },
173 },
174 OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode,
175 else => null,
176 };
177 }
178
179 pub fn step(
180 self: *StackMachine,
181 stream: std.io.FixedBufferStream([]const u8),
182 allocator: std.mem.Allocator,
183 ) !void {
184 if (@sizeOf(usize) != addr_type or options.endian != builtin.target.cpu.arch.endian())
185 @compileError("Execution of non-native address sizees / endianness is not supported");
186
187 const opcode = try stream.reader.readByte();
188 _ = opcode;
189 _ = self;
190 _ = allocator;
191
192 // switch (opcode) {
193 // OP.addr => try self.stack.append(allocator, try readOperand(stream, opcode)),
194 // }
195 }
196 };
197}