master
1const builtin = @import("builtin");
2const native_arch = builtin.cpu.arch;
3const native_endian = native_arch.endian();
4
5const std = @import("std");
6const leb = std.leb;
7const OP = std.dwarf.OP;
8const mem = std.mem;
9const assert = std.debug.assert;
10const testing = std.testing;
11const Writer = std.Io.Writer;
12
13const regNative = std.debug.Dwarf.SelfUnwinder.regNative;
14
15const ip_reg_num = std.debug.Dwarf.ipRegNum(native_arch).?;
16const fp_reg_num = std.debug.Dwarf.fpRegNum(native_arch);
17const sp_reg_num = std.debug.Dwarf.spRegNum(native_arch);
18
19/// Expressions can be evaluated in different contexts, each requiring its own set of inputs.
20/// Callers should specify all the fields relevant to their context. If a field is required
21/// by the expression and it isn't in the context, error.IncompleteExpressionContext is returned.
22pub const Context = struct {
23 /// The dwarf format of the section this expression is in
24 format: std.dwarf.Format = .@"32",
25 /// The compilation unit this expression relates to, if any
26 compile_unit: ?*const std.debug.Dwarf.CompileUnit = null,
27 /// When evaluating a user-presented expression, this is the address of the object being evaluated
28 object_address: ?*const anyopaque = null,
29 /// .debug_addr section
30 debug_addr: ?[]const u8 = null,
31 cpu_context: ?*std.debug.cpu_context.Native = null,
32 /// Call frame address, if in a CFI context
33 cfa: ?usize = null,
34 /// This expression is a sub-expression from an OP.entry_value instruction
35 entry_value_context: bool = false,
36};
37
38pub const Options = struct {
39 /// The address size of the target architecture
40 addr_size: u8 = @sizeOf(usize),
41 /// Endianness of the target architecture
42 endian: std.builtin.Endian = native_endian,
43 /// Restrict the stack machine to a subset of opcodes used in call frame instructions
44 call_frame_context: bool = false,
45};
46
47// Explicitly defined to support executing sub-expressions
48pub const Error = error{
49 UnimplementedExpressionCall,
50 UnimplementedOpcode,
51 UnimplementedUserOpcode,
52 UnimplementedTypedComparison,
53 UnimplementedTypeConversion,
54
55 UnknownExpressionOpcode,
56
57 IncompleteExpressionContext,
58
59 InvalidCFAOpcode,
60 InvalidExpression,
61 InvalidFrameBase,
62 InvalidIntegralTypeSize,
63 InvalidRegister,
64 InvalidSubExpression,
65 InvalidTypeLength,
66
67 TruncatedIntegralType,
68
69 IncompatibleRegisterSize,
70} || std.debug.cpu_context.DwarfRegisterError || error{ EndOfStream, Overflow, OutOfMemory, DivisionByZero, ReadFailed };
71
72/// A stack machine that can decode and run DWARF expressions.
73/// Expressions can be decoded for non-native address size and endianness,
74/// but can only be executed if the current target matches the configuration.
75pub fn StackMachine(comptime options: Options) type {
76 const addr_type = switch (options.addr_size) {
77 2 => u16,
78 4 => u32,
79 8 => u64,
80 else => @compileError("Unsupported address size of " ++ options.addr_size),
81 };
82
83 const addr_type_signed = switch (options.addr_size) {
84 2 => i16,
85 4 => i32,
86 8 => i64,
87 else => @compileError("Unsupported address size of " ++ options.addr_size),
88 };
89
90 return struct {
91 const Self = @This();
92
93 const Operand = union(enum) {
94 generic: addr_type,
95 register: u8,
96 type_size: u8,
97 branch_offset: i16,
98 base_register: struct {
99 base_register: u8,
100 offset: i64,
101 },
102 composite_location: struct {
103 size: u64,
104 offset: i64,
105 },
106 block: []const u8,
107 register_type: struct {
108 register: u8,
109 type_offset: addr_type,
110 },
111 const_type: struct {
112 type_offset: addr_type,
113 value_bytes: []const u8,
114 },
115 deref_type: struct {
116 size: u8,
117 type_offset: addr_type,
118 },
119 };
120
121 const Value = union(enum) {
122 generic: addr_type,
123
124 // Typed value with a maximum size of a register
125 regval_type: struct {
126 // Offset of DW_TAG_base_type DIE
127 type_offset: addr_type,
128 type_size: u8,
129 value: addr_type,
130 },
131
132 // Typed value specified directly in the instruction stream
133 const_type: struct {
134 // Offset of DW_TAG_base_type DIE
135 type_offset: addr_type,
136 // Backed by the instruction stream
137 value_bytes: []const u8,
138 },
139
140 pub fn asIntegral(self: Value) !addr_type {
141 return switch (self) {
142 .generic => |v| v,
143
144 // TODO: For these two prongs, look up the type and assert it's integral?
145 .regval_type => |regval_type| regval_type.value,
146 .const_type => |const_type| {
147 const value: u64 = switch (const_type.value_bytes.len) {
148 1 => mem.readInt(u8, const_type.value_bytes[0..1], native_endian),
149 2 => mem.readInt(u16, const_type.value_bytes[0..2], native_endian),
150 4 => mem.readInt(u32, const_type.value_bytes[0..4], native_endian),
151 8 => mem.readInt(u64, const_type.value_bytes[0..8], native_endian),
152 else => return error.InvalidIntegralTypeSize,
153 };
154
155 return std.math.cast(addr_type, value) orelse error.TruncatedIntegralType;
156 },
157 };
158 }
159 };
160
161 stack: std.ArrayList(Value) = .empty,
162
163 pub fn reset(self: *Self) void {
164 self.stack.clearRetainingCapacity();
165 }
166
167 pub fn deinit(self: *Self, allocator: std.mem.Allocator) void {
168 self.stack.deinit(allocator);
169 }
170
171 fn generic(value: anytype) Operand {
172 const int_info = @typeInfo(@TypeOf(value)).int;
173 if (@sizeOf(@TypeOf(value)) > options.addr_size) {
174 return .{ .generic = switch (int_info.signedness) {
175 .signed => @bitCast(@as(addr_type_signed, @truncate(value))),
176 .unsigned => @truncate(value),
177 } };
178 } else {
179 return .{ .generic = switch (int_info.signedness) {
180 .signed => @bitCast(@as(addr_type_signed, @intCast(value))),
181 .unsigned => @intCast(value),
182 } };
183 }
184 }
185
186 pub fn readOperand(reader: *std.Io.Reader, opcode: u8, context: Context) !?Operand {
187 return switch (opcode) {
188 OP.addr => generic(try reader.takeInt(addr_type, options.endian)),
189 OP.call_ref => switch (context.format) {
190 .@"32" => generic(try reader.takeInt(u32, options.endian)),
191 .@"64" => generic(try reader.takeInt(u64, options.endian)),
192 },
193 OP.const1u,
194 OP.pick,
195 => generic(try reader.takeByte()),
196 OP.deref_size,
197 OP.xderef_size,
198 => .{ .type_size = try reader.takeByte() },
199 OP.const1s => generic(try reader.takeByteSigned()),
200 OP.const2u,
201 OP.call2,
202 => generic(try reader.takeInt(u16, options.endian)),
203 OP.call4 => generic(try reader.takeInt(u32, options.endian)),
204 OP.const2s => generic(try reader.takeInt(i16, options.endian)),
205 OP.bra,
206 OP.skip,
207 => .{ .branch_offset = try reader.takeInt(i16, options.endian) },
208 OP.const4u => generic(try reader.takeInt(u32, options.endian)),
209 OP.const4s => generic(try reader.takeInt(i32, options.endian)),
210 OP.const8u => generic(try reader.takeInt(u64, options.endian)),
211 OP.const8s => generic(try reader.takeInt(i64, options.endian)),
212 OP.constu,
213 OP.plus_uconst,
214 OP.addrx,
215 OP.constx,
216 OP.convert,
217 OP.reinterpret,
218 => generic(try reader.takeLeb128(u64)),
219 OP.consts,
220 OP.fbreg,
221 => generic(try reader.takeLeb128(i64)),
222 OP.lit0...OP.lit31 => |n| generic(n - OP.lit0),
223 OP.reg0...OP.reg31 => |n| .{ .register = n - OP.reg0 },
224 OP.breg0...OP.breg31 => |n| .{ .base_register = .{
225 .base_register = n - OP.breg0,
226 .offset = try reader.takeLeb128(i64),
227 } },
228 OP.regx => .{ .register = try reader.takeLeb128(u8) },
229 OP.bregx => blk: {
230 const base_register = try reader.takeLeb128(u8);
231 const offset = try reader.takeLeb128(i64);
232 break :blk .{ .base_register = .{
233 .base_register = base_register,
234 .offset = offset,
235 } };
236 },
237 OP.regval_type => blk: {
238 const register = try reader.takeLeb128(u8);
239 const type_offset = try reader.takeLeb128(addr_type);
240 break :blk .{ .register_type = .{
241 .register = register,
242 .type_offset = type_offset,
243 } };
244 },
245 OP.piece => .{
246 .composite_location = .{
247 .size = try reader.takeLeb128(u8),
248 .offset = 0,
249 },
250 },
251 OP.bit_piece => blk: {
252 const size = try reader.takeLeb128(u8);
253 const offset = try reader.takeLeb128(i64);
254 break :blk .{ .composite_location = .{
255 .size = size,
256 .offset = offset,
257 } };
258 },
259 OP.implicit_value, OP.entry_value => blk: {
260 const size = try reader.takeLeb128(u8);
261 const block = try reader.take(size);
262 break :blk .{ .block = block };
263 },
264 OP.const_type => blk: {
265 const type_offset = try reader.takeLeb128(addr_type);
266 const size = try reader.takeByte();
267 const value_bytes = try reader.take(size);
268 break :blk .{ .const_type = .{
269 .type_offset = type_offset,
270 .value_bytes = value_bytes,
271 } };
272 },
273 OP.deref_type,
274 OP.xderef_type,
275 => .{
276 .deref_type = .{
277 .size = try reader.takeByte(),
278 .type_offset = try reader.takeLeb128(addr_type),
279 },
280 },
281 OP.lo_user...OP.hi_user => return error.UnimplementedUserOpcode,
282 else => null,
283 };
284 }
285
286 pub fn run(
287 self: *Self,
288 expression: []const u8,
289 allocator: std.mem.Allocator,
290 context: Context,
291 initial_value: ?usize,
292 ) Error!?Value {
293 if (initial_value) |i| try self.stack.append(allocator, .{ .generic = i });
294 var stream: std.Io.Reader = .fixed(expression);
295 while (try self.step(&stream, allocator, context)) {}
296 if (self.stack.items.len == 0) return null;
297 return self.stack.items[self.stack.items.len - 1];
298 }
299
300 /// Reads an opcode and its operands from `stream`, then executes it
301 pub fn step(
302 self: *Self,
303 stream: *std.Io.Reader,
304 allocator: std.mem.Allocator,
305 context: Context,
306 ) Error!bool {
307 if (@sizeOf(usize) != @sizeOf(addr_type) or options.endian != native_endian)
308 @compileError("Execution of non-native address sizes / endianness is not supported");
309
310 const opcode = try stream.takeByte();
311 if (options.call_frame_context and !isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode;
312 const operand = try readOperand(stream, opcode, context);
313 switch (opcode) {
314
315 // 2.5.1.1: Literal Encodings
316 OP.lit0...OP.lit31,
317 OP.addr,
318 OP.const1u,
319 OP.const2u,
320 OP.const4u,
321 OP.const8u,
322 OP.const1s,
323 OP.const2s,
324 OP.const4s,
325 OP.const8s,
326 OP.constu,
327 OP.consts,
328 => try self.stack.append(allocator, .{ .generic = operand.?.generic }),
329
330 OP.const_type => {
331 const const_type = operand.?.const_type;
332 try self.stack.append(allocator, .{ .const_type = .{
333 .type_offset = const_type.type_offset,
334 .value_bytes = const_type.value_bytes,
335 } });
336 },
337
338 OP.addrx,
339 OP.constx,
340 => {
341 if (context.compile_unit == null) return error.IncompleteExpressionContext;
342 if (context.debug_addr == null) return error.IncompleteExpressionContext;
343 const debug_addr_index = operand.?.generic;
344 const offset = context.compile_unit.?.addr_base + debug_addr_index;
345 if (offset >= context.debug_addr.?.len) return error.InvalidExpression;
346 const value = mem.readInt(usize, context.debug_addr.?[offset..][0..@sizeOf(usize)], native_endian);
347 try self.stack.append(allocator, .{ .generic = value });
348 },
349
350 // 2.5.1.2: Register Values
351 OP.fbreg => {
352 if (context.compile_unit == null) return error.IncompleteExpressionContext;
353 if (context.compile_unit.?.frame_base == null) return error.IncompleteExpressionContext;
354
355 const offset: i64 = @intCast(operand.?.generic);
356 _ = offset;
357
358 switch (context.compile_unit.?.frame_base.?.*) {
359 .exprloc => {
360 // TODO: Run this expression in a nested stack machine
361 return error.UnimplementedOpcode;
362 },
363 .loclistx => {
364 // TODO: Read value from .debug_loclists
365 return error.UnimplementedOpcode;
366 },
367 .sec_offset => {
368 // TODO: Read value from .debug_loclists
369 return error.UnimplementedOpcode;
370 },
371 else => return error.InvalidFrameBase,
372 }
373 },
374 OP.breg0...OP.breg31,
375 OP.bregx,
376 => {
377 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
378
379 const br = operand.?.base_register;
380 const value: i64 = @intCast((try regNative(cpu_context, br.base_register)).*);
381 try self.stack.append(allocator, .{ .generic = @intCast(value + br.offset) });
382 },
383 OP.regval_type => {
384 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
385 const rt = operand.?.register_type;
386 try self.stack.append(allocator, .{
387 .regval_type = .{
388 .type_offset = rt.type_offset,
389 .type_size = @sizeOf(addr_type),
390 .value = (try regNative(cpu_context, rt.register)).*,
391 },
392 });
393 },
394
395 // 2.5.1.3: Stack Operations
396 OP.dup => {
397 if (self.stack.items.len == 0) return error.InvalidExpression;
398 try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1]);
399 },
400 OP.drop => {
401 _ = self.stack.pop();
402 },
403 OP.pick, OP.over => {
404 const stack_index = if (opcode == OP.over) 1 else operand.?.generic;
405 if (stack_index >= self.stack.items.len) return error.InvalidExpression;
406 try self.stack.append(allocator, self.stack.items[self.stack.items.len - 1 - stack_index]);
407 },
408 OP.swap => {
409 if (self.stack.items.len < 2) return error.InvalidExpression;
410 mem.swap(Value, &self.stack.items[self.stack.items.len - 1], &self.stack.items[self.stack.items.len - 2]);
411 },
412 OP.rot => {
413 if (self.stack.items.len < 3) return error.InvalidExpression;
414 const first = self.stack.items[self.stack.items.len - 1];
415 self.stack.items[self.stack.items.len - 1] = self.stack.items[self.stack.items.len - 2];
416 self.stack.items[self.stack.items.len - 2] = self.stack.items[self.stack.items.len - 3];
417 self.stack.items[self.stack.items.len - 3] = first;
418 },
419 OP.deref,
420 OP.xderef,
421 OP.deref_size,
422 OP.xderef_size,
423 OP.deref_type,
424 OP.xderef_type,
425 => {
426 if (self.stack.items.len == 0) return error.InvalidExpression;
427 const addr = try self.stack.items[self.stack.items.len - 1].asIntegral();
428 const addr_space_identifier: ?usize = switch (opcode) {
429 OP.xderef,
430 OP.xderef_size,
431 OP.xderef_type,
432 => blk: {
433 _ = self.stack.pop();
434 if (self.stack.items.len == 0) return error.InvalidExpression;
435 break :blk try self.stack.items[self.stack.items.len - 1].asIntegral();
436 },
437 else => null,
438 };
439
440 // Usage of addr_space_identifier in the address calculation is implementation defined.
441 // This code will need to be updated to handle any architectures that utilize this.
442 _ = addr_space_identifier;
443
444 const size = switch (opcode) {
445 OP.deref,
446 OP.xderef,
447 => @sizeOf(addr_type),
448 OP.deref_size,
449 OP.xderef_size,
450 => operand.?.type_size,
451 OP.deref_type,
452 OP.xderef_type,
453 => operand.?.deref_type.size,
454 else => unreachable,
455 };
456
457 const value: addr_type = std.math.cast(addr_type, @as(u64, switch (size) {
458 1 => @as(*const u8, @ptrFromInt(addr)).*,
459 2 => @as(*const u16, @ptrFromInt(addr)).*,
460 4 => @as(*const u32, @ptrFromInt(addr)).*,
461 8 => @as(*const u64, @ptrFromInt(addr)).*,
462 else => return error.InvalidExpression,
463 })) orelse return error.InvalidExpression;
464
465 switch (opcode) {
466 OP.deref_type,
467 OP.xderef_type,
468 => {
469 self.stack.items[self.stack.items.len - 1] = .{
470 .regval_type = .{
471 .type_offset = operand.?.deref_type.type_offset,
472 .type_size = operand.?.deref_type.size,
473 .value = value,
474 },
475 };
476 },
477 else => {
478 self.stack.items[self.stack.items.len - 1] = .{ .generic = value };
479 },
480 }
481 },
482 OP.push_object_address => {
483 // In sub-expressions, `push_object_address` is not meaningful (as per the
484 // spec), so treat it like a nop
485 if (!context.entry_value_context) {
486 if (context.object_address == null) return error.IncompleteExpressionContext;
487 try self.stack.append(allocator, .{ .generic = @intFromPtr(context.object_address.?) });
488 }
489 },
490 OP.form_tls_address => {
491 return error.UnimplementedOpcode;
492 },
493 OP.call_frame_cfa => {
494 if (context.cfa) |cfa| {
495 try self.stack.append(allocator, .{ .generic = cfa });
496 } else return error.IncompleteExpressionContext;
497 },
498
499 // 2.5.1.4: Arithmetic and Logical Operations
500 OP.abs => {
501 if (self.stack.items.len == 0) return error.InvalidExpression;
502 const value: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
503 self.stack.items[self.stack.items.len - 1] = .{
504 .generic = @abs(value),
505 };
506 },
507 OP.@"and" => {
508 if (self.stack.items.len < 2) return error.InvalidExpression;
509 const a = try self.stack.pop().?.asIntegral();
510 self.stack.items[self.stack.items.len - 1] = .{
511 .generic = a & try self.stack.items[self.stack.items.len - 1].asIntegral(),
512 };
513 },
514 OP.div => {
515 if (self.stack.items.len < 2) return error.InvalidExpression;
516 const a: isize = @bitCast(try self.stack.pop().?.asIntegral());
517 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
518 self.stack.items[self.stack.items.len - 1] = .{
519 .generic = @bitCast(try std.math.divTrunc(isize, b, a)),
520 };
521 },
522 OP.minus => {
523 if (self.stack.items.len < 2) return error.InvalidExpression;
524 const b = try self.stack.pop().?.asIntegral();
525 self.stack.items[self.stack.items.len - 1] = .{
526 .generic = try std.math.sub(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
527 };
528 },
529 OP.mod => {
530 if (self.stack.items.len < 2) return error.InvalidExpression;
531 const a: isize = @bitCast(try self.stack.pop().?.asIntegral());
532 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
533 self.stack.items[self.stack.items.len - 1] = .{
534 .generic = @bitCast(@mod(b, a)),
535 };
536 },
537 OP.mul => {
538 if (self.stack.items.len < 2) return error.InvalidExpression;
539 const a: isize = @bitCast(try self.stack.pop().?.asIntegral());
540 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
541 self.stack.items[self.stack.items.len - 1] = .{
542 .generic = @bitCast(@mulWithOverflow(a, b)[0]),
543 };
544 },
545 OP.neg => {
546 if (self.stack.items.len == 0) return error.InvalidExpression;
547 self.stack.items[self.stack.items.len - 1] = .{
548 .generic = @bitCast(
549 try std.math.negate(
550 @as(isize, @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral())),
551 ),
552 ),
553 };
554 },
555 OP.not => {
556 if (self.stack.items.len == 0) return error.InvalidExpression;
557 self.stack.items[self.stack.items.len - 1] = .{
558 .generic = ~try self.stack.items[self.stack.items.len - 1].asIntegral(),
559 };
560 },
561 OP.@"or" => {
562 if (self.stack.items.len < 2) return error.InvalidExpression;
563 const a = try self.stack.pop().?.asIntegral();
564 self.stack.items[self.stack.items.len - 1] = .{
565 .generic = a | try self.stack.items[self.stack.items.len - 1].asIntegral(),
566 };
567 },
568 OP.plus => {
569 if (self.stack.items.len < 2) return error.InvalidExpression;
570 const b = try self.stack.pop().?.asIntegral();
571 self.stack.items[self.stack.items.len - 1] = .{
572 .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), b),
573 };
574 },
575 OP.plus_uconst => {
576 if (self.stack.items.len == 0) return error.InvalidExpression;
577 const constant = operand.?.generic;
578 self.stack.items[self.stack.items.len - 1] = .{
579 .generic = try std.math.add(addr_type, try self.stack.items[self.stack.items.len - 1].asIntegral(), constant),
580 };
581 },
582 OP.shl => {
583 if (self.stack.items.len < 2) return error.InvalidExpression;
584 const a = try self.stack.pop().?.asIntegral();
585 const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
586 self.stack.items[self.stack.items.len - 1] = .{
587 .generic = std.math.shl(usize, b, a),
588 };
589 },
590 OP.shr => {
591 if (self.stack.items.len < 2) return error.InvalidExpression;
592 const a = try self.stack.pop().?.asIntegral();
593 const b = try self.stack.items[self.stack.items.len - 1].asIntegral();
594 self.stack.items[self.stack.items.len - 1] = .{
595 .generic = std.math.shr(usize, b, a),
596 };
597 },
598 OP.shra => {
599 if (self.stack.items.len < 2) return error.InvalidExpression;
600 const a = try self.stack.pop().?.asIntegral();
601 const b: isize = @bitCast(try self.stack.items[self.stack.items.len - 1].asIntegral());
602 self.stack.items[self.stack.items.len - 1] = .{
603 .generic = @bitCast(std.math.shr(isize, b, a)),
604 };
605 },
606 OP.xor => {
607 if (self.stack.items.len < 2) return error.InvalidExpression;
608 const a = try self.stack.pop().?.asIntegral();
609 self.stack.items[self.stack.items.len - 1] = .{
610 .generic = a ^ try self.stack.items[self.stack.items.len - 1].asIntegral(),
611 };
612 },
613
614 // 2.5.1.5: Control Flow Operations
615 OP.le,
616 OP.ge,
617 OP.eq,
618 OP.lt,
619 OP.gt,
620 OP.ne,
621 => {
622 if (self.stack.items.len < 2) return error.InvalidExpression;
623 const a = self.stack.pop().?;
624 const b = self.stack.items[self.stack.items.len - 1];
625
626 if (a == .generic and b == .generic) {
627 const a_int: isize = @bitCast(a.asIntegral() catch unreachable);
628 const b_int: isize = @bitCast(b.asIntegral() catch unreachable);
629 const result = @intFromBool(switch (opcode) {
630 OP.le => b_int <= a_int,
631 OP.ge => b_int >= a_int,
632 OP.eq => b_int == a_int,
633 OP.lt => b_int < a_int,
634 OP.gt => b_int > a_int,
635 OP.ne => b_int != a_int,
636 else => unreachable,
637 });
638
639 self.stack.items[self.stack.items.len - 1] = .{ .generic = result };
640 } else {
641 // TODO: Load the types referenced by these values, find their comparison operator, and run it
642 return error.UnimplementedTypedComparison;
643 }
644 },
645 OP.skip, OP.bra => {
646 const branch_offset = operand.?.branch_offset;
647 const condition = if (opcode == OP.bra) blk: {
648 if (self.stack.items.len == 0) return error.InvalidExpression;
649 break :blk try self.stack.pop().?.asIntegral() != 0;
650 } else true;
651
652 if (condition) {
653 const new_pos = std.math.cast(
654 usize,
655 try std.math.add(isize, @as(isize, @intCast(stream.seek)), branch_offset),
656 ) orelse return error.InvalidExpression;
657
658 if (new_pos < 0 or new_pos > stream.buffer.len) return error.InvalidExpression;
659 stream.seek = new_pos;
660 }
661 },
662 OP.call2,
663 OP.call4,
664 OP.call_ref,
665 => {
666 const debug_info_offset = operand.?.generic;
667 _ = debug_info_offset;
668
669 // TODO: Load a DIE entry at debug_info_offset in a .debug_info section (the spec says that it
670 // can be in a separate exe / shared object from the one containing this expression).
671 // Transfer control to the DW_AT_location attribute, with the current stack as input.
672
673 return error.UnimplementedExpressionCall;
674 },
675
676 // 2.5.1.6: Type Conversions
677 OP.convert => {
678 if (self.stack.items.len == 0) return error.InvalidExpression;
679 const type_offset = operand.?.generic;
680
681 // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
682 const value = self.stack.items[self.stack.items.len - 1];
683 if (type_offset == 0) {
684 self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
685 } else {
686 // TODO: Load the DW_TAG_base_type entry in context.compile_unit, find a conversion operator
687 // from the old type to the new type, run it.
688 return error.UnimplementedTypeConversion;
689 }
690 },
691 OP.reinterpret => {
692 if (self.stack.items.len == 0) return error.InvalidExpression;
693 const type_offset = operand.?.generic;
694
695 // TODO: Load the DW_TAG_base_type entries in context.compile_unit and verify both types are the same size
696 const value = self.stack.items[self.stack.items.len - 1];
697 if (type_offset == 0) {
698 self.stack.items[self.stack.items.len - 1] = .{ .generic = try value.asIntegral() };
699 } else {
700 self.stack.items[self.stack.items.len - 1] = switch (value) {
701 .generic => |v| .{
702 .regval_type = .{
703 .type_offset = type_offset,
704 .type_size = @sizeOf(addr_type),
705 .value = v,
706 },
707 },
708 .regval_type => |r| .{
709 .regval_type = .{
710 .type_offset = type_offset,
711 .type_size = r.type_size,
712 .value = r.value,
713 },
714 },
715 .const_type => |c| .{
716 .const_type = .{
717 .type_offset = type_offset,
718 .value_bytes = c.value_bytes,
719 },
720 },
721 };
722 }
723 },
724
725 // 2.5.1.7: Special Operations
726 OP.nop => {},
727 OP.entry_value => {
728 const block = operand.?.block;
729 if (block.len == 0) return error.InvalidSubExpression;
730
731 // TODO: The spec states that this sub-expression needs to observe the state (ie. registers)
732 // as it was upon entering the current subprogram. If this isn't being called at the
733 // end of a frame unwind operation, an additional cpu_context.Native with this state will be needed.
734
735 if (isOpcodeRegisterLocation(block[0])) {
736 const cpu_context = context.cpu_context orelse return error.IncompleteExpressionContext;
737
738 var block_stream: std.Io.Reader = .fixed(block);
739 const register = (try readOperand(&block_stream, block[0], context)).?.register;
740 const value = (try regNative(cpu_context, register)).*;
741 try self.stack.append(allocator, .{ .generic = value });
742 } else {
743 var stack_machine: Self = .{};
744 defer stack_machine.deinit(allocator);
745
746 var sub_context = context;
747 sub_context.entry_value_context = true;
748 const result = try stack_machine.run(block, allocator, sub_context, null);
749 try self.stack.append(allocator, result orelse return error.InvalidSubExpression);
750 }
751 },
752
753 // These have already been handled by readOperand
754 OP.lo_user...OP.hi_user => unreachable,
755 else => {
756 //std.debug.print("Unknown DWARF expression opcode: {x}\n", .{opcode});
757 return error.UnknownExpressionOpcode;
758 },
759 }
760
761 return stream.seek < stream.buffer.len;
762 }
763 };
764}
765
766pub fn Builder(comptime options: Options) type {
767 const addr_type = switch (options.addr_size) {
768 2 => u16,
769 4 => u32,
770 8 => u64,
771 else => @compileError("Unsupported address size of " ++ options.addr_size),
772 };
773
774 return struct {
775 /// Zero-operand instructions
776 pub fn writeOpcode(writer: *Writer, comptime opcode: u8) !void {
777 if (options.call_frame_context and !comptime isOpcodeValidInCFA(opcode)) return error.InvalidCFAOpcode;
778 switch (opcode) {
779 OP.dup,
780 OP.drop,
781 OP.over,
782 OP.swap,
783 OP.rot,
784 OP.deref,
785 OP.xderef,
786 OP.push_object_address,
787 OP.form_tls_address,
788 OP.call_frame_cfa,
789 OP.abs,
790 OP.@"and",
791 OP.div,
792 OP.minus,
793 OP.mod,
794 OP.mul,
795 OP.neg,
796 OP.not,
797 OP.@"or",
798 OP.plus,
799 OP.shl,
800 OP.shr,
801 OP.shra,
802 OP.xor,
803 OP.le,
804 OP.ge,
805 OP.eq,
806 OP.lt,
807 OP.gt,
808 OP.ne,
809 OP.nop,
810 OP.stack_value,
811 => try writer.writeByte(opcode),
812 else => @compileError("This opcode requires operands, use `write<Opcode>()` instead"),
813 }
814 }
815
816 // 2.5.1.1: Literal Encodings
817 pub fn writeLiteral(writer: *Writer, literal: u8) !void {
818 switch (literal) {
819 0...31 => |n| try writer.writeByte(n + OP.lit0),
820 else => return error.InvalidLiteral,
821 }
822 }
823
824 pub fn writeConst(writer: *Writer, comptime T: type, value: T) !void {
825 if (@typeInfo(T) != .int) @compileError("Constants must be integers");
826
827 switch (T) {
828 u8, i8, u16, i16, u32, i32, u64, i64 => {
829 try writer.writeByte(switch (T) {
830 u8 => OP.const1u,
831 i8 => OP.const1s,
832 u16 => OP.const2u,
833 i16 => OP.const2s,
834 u32 => OP.const4u,
835 i32 => OP.const4s,
836 u64 => OP.const8u,
837 i64 => OP.const8s,
838 else => unreachable,
839 });
840
841 try writer.writeInt(T, value, options.endian);
842 },
843 else => switch (@typeInfo(T).int.signedness) {
844 .unsigned => {
845 try writer.writeByte(OP.constu);
846 try writer.writeUleb128(value);
847 },
848 .signed => {
849 try writer.writeByte(OP.consts);
850 try writer.writeLeb128(value);
851 },
852 },
853 }
854 }
855
856 pub fn writeConstx(writer: *Writer, debug_addr_offset: anytype) !void {
857 try writer.writeByte(OP.constx);
858 try writer.writeUleb128(debug_addr_offset);
859 }
860
861 pub fn writeConstType(writer: *Writer, die_offset: anytype, value_bytes: []const u8) !void {
862 if (options.call_frame_context) return error.InvalidCFAOpcode;
863 if (value_bytes.len > 0xff) return error.InvalidTypeLength;
864 try writer.writeByte(OP.const_type);
865 try writer.writeUleb128(die_offset);
866 try writer.writeByte(@intCast(value_bytes.len));
867 try writer.writeAll(value_bytes);
868 }
869
870 pub fn writeAddr(writer: *Writer, value: addr_type) !void {
871 try writer.writeByte(OP.addr);
872 try writer.writeInt(addr_type, value, options.endian);
873 }
874
875 pub fn writeAddrx(writer: *Writer, debug_addr_offset: anytype) !void {
876 if (options.call_frame_context) return error.InvalidCFAOpcode;
877 try writer.writeByte(OP.addrx);
878 try writer.writeUleb128(debug_addr_offset);
879 }
880
881 // 2.5.1.2: Register Values
882 pub fn writeFbreg(writer: *Writer, offset: anytype) !void {
883 try writer.writeByte(OP.fbreg);
884 try writer.writeSleb128(offset);
885 }
886
887 pub fn writeBreg(writer: *Writer, register: u8, offset: anytype) !void {
888 if (register > 31) return error.InvalidRegister;
889 try writer.writeByte(OP.breg0 + register);
890 try writer.writeSleb128(offset);
891 }
892
893 pub fn writeBregx(writer: *Writer, register: anytype, offset: anytype) !void {
894 try writer.writeByte(OP.bregx);
895 try writer.writeUleb128(register);
896 try writer.writeSleb128(offset);
897 }
898
899 pub fn writeRegvalType(writer: *Writer, register: anytype, offset: anytype) !void {
900 if (options.call_frame_context) return error.InvalidCFAOpcode;
901 try writer.writeByte(OP.regval_type);
902 try writer.writeUleb128(register);
903 try writer.writeUleb128(offset);
904 }
905
906 // 2.5.1.3: Stack Operations
907 pub fn writePick(writer: *Writer, index: u8) !void {
908 try writer.writeByte(OP.pick);
909 try writer.writeByte(index);
910 }
911
912 pub fn writeDerefSize(writer: *Writer, size: u8) !void {
913 try writer.writeByte(OP.deref_size);
914 try writer.writeByte(size);
915 }
916
917 pub fn writeXDerefSize(writer: *Writer, size: u8) !void {
918 try writer.writeByte(OP.xderef_size);
919 try writer.writeByte(size);
920 }
921
922 pub fn writeDerefType(writer: *Writer, size: u8, die_offset: anytype) !void {
923 if (options.call_frame_context) return error.InvalidCFAOpcode;
924 try writer.writeByte(OP.deref_type);
925 try writer.writeByte(size);
926 try writer.writeUleb128(die_offset);
927 }
928
929 pub fn writeXDerefType(writer: *Writer, size: u8, die_offset: anytype) !void {
930 try writer.writeByte(OP.xderef_type);
931 try writer.writeByte(size);
932 try writer.writeUleb128(die_offset);
933 }
934
935 // 2.5.1.4: Arithmetic and Logical Operations
936
937 pub fn writePlusUconst(writer: *Writer, uint_value: anytype) !void {
938 try writer.writeByte(OP.plus_uconst);
939 try writer.writeUleb128(uint_value);
940 }
941
942 // 2.5.1.5: Control Flow Operations
943
944 pub fn writeSkip(writer: *Writer, offset: i16) !void {
945 try writer.writeByte(OP.skip);
946 try writer.writeInt(i16, offset, options.endian);
947 }
948
949 pub fn writeBra(writer: *Writer, offset: i16) !void {
950 try writer.writeByte(OP.bra);
951 try writer.writeInt(i16, offset, options.endian);
952 }
953
954 pub fn writeCall(writer: *Writer, comptime T: type, offset: T) !void {
955 if (options.call_frame_context) return error.InvalidCFAOpcode;
956 switch (T) {
957 u16 => try writer.writeByte(OP.call2),
958 u32 => try writer.writeByte(OP.call4),
959 else => @compileError("Call operand must be a 2 or 4 byte offset"),
960 }
961
962 try writer.writeInt(T, offset, options.endian);
963 }
964
965 pub fn writeCallRef(writer: *Writer, comptime is_64: bool, value: if (is_64) u64 else u32) !void {
966 if (options.call_frame_context) return error.InvalidCFAOpcode;
967 try writer.writeByte(OP.call_ref);
968 try writer.writeInt(if (is_64) u64 else u32, value, options.endian);
969 }
970
971 pub fn writeConvert(writer: *Writer, die_offset: anytype) !void {
972 if (options.call_frame_context) return error.InvalidCFAOpcode;
973 try writer.writeByte(OP.convert);
974 try writer.writeUleb128(die_offset);
975 }
976
977 pub fn writeReinterpret(writer: *Writer, die_offset: anytype) !void {
978 if (options.call_frame_context) return error.InvalidCFAOpcode;
979 try writer.writeByte(OP.reinterpret);
980 try writer.writeUleb128(die_offset);
981 }
982
983 // 2.5.1.7: Special Operations
984
985 pub fn writeEntryValue(writer: *Writer, expression: []const u8) !void {
986 try writer.writeByte(OP.entry_value);
987 try writer.writeUleb128(expression.len);
988 try writer.writeAll(expression);
989 }
990
991 // 2.6: Location Descriptions
992 pub fn writeReg(writer: *Writer, register: u8) !void {
993 try writer.writeByte(OP.reg0 + register);
994 }
995
996 pub fn writeRegx(writer: *Writer, register: anytype) !void {
997 try writer.writeByte(OP.regx);
998 try writer.writeUleb128(register);
999 }
1000
1001 pub fn writeImplicitValue(writer: *Writer, value_bytes: []const u8) !void {
1002 try writer.writeByte(OP.implicit_value);
1003 try writer.writeUleb128(value_bytes.len);
1004 try writer.writeAll(value_bytes);
1005 }
1006 };
1007}
1008
1009// Certain opcodes are not allowed in a CFA context, see 6.4.2
1010fn isOpcodeValidInCFA(opcode: u8) bool {
1011 return switch (opcode) {
1012 OP.addrx,
1013 OP.call2,
1014 OP.call4,
1015 OP.call_ref,
1016 OP.const_type,
1017 OP.constx,
1018 OP.convert,
1019 OP.deref_type,
1020 OP.regval_type,
1021 OP.reinterpret,
1022 OP.push_object_address,
1023 OP.call_frame_cfa,
1024 => false,
1025 else => true,
1026 };
1027}
1028
1029fn isOpcodeRegisterLocation(opcode: u8) bool {
1030 return switch (opcode) {
1031 OP.reg0...OP.reg31, OP.regx => true,
1032 else => false,
1033 };
1034}
1035
1036test "basics" {
1037 const allocator = std.testing.allocator;
1038
1039 const options = Options{};
1040 var stack_machine = StackMachine(options){};
1041 defer stack_machine.deinit(allocator);
1042
1043 const b = Builder(options);
1044
1045 var program: std.Io.Writer.Allocating = .init(allocator);
1046 defer program.deinit();
1047
1048 const writer = &program.writer;
1049
1050 // Literals
1051 {
1052 const context = Context{};
1053 for (0..32) |i| {
1054 try b.writeLiteral(writer, @intCast(i));
1055 }
1056
1057 _ = try stack_machine.run(program.written(), allocator, context, 0);
1058
1059 for (0..32) |i| {
1060 const expected = 31 - i;
1061 try testing.expectEqual(expected, stack_machine.stack.pop().?.generic);
1062 }
1063 }
1064
1065 // Constants
1066 {
1067 stack_machine.reset();
1068 program.clearRetainingCapacity();
1069
1070 const input = [_]comptime_int{
1071 1,
1072 -1,
1073 @as(usize, @truncate(0x0fff)),
1074 @as(isize, @truncate(-0x0fff)),
1075 @as(usize, @truncate(0x0fffffff)),
1076 @as(isize, @truncate(-0x0fffffff)),
1077 @as(usize, @truncate(0x0fffffffffffffff)),
1078 @as(isize, @truncate(-0x0fffffffffffffff)),
1079 @as(usize, @truncate(0x8000000)),
1080 @as(isize, @truncate(-0x8000000)),
1081 @as(usize, @truncate(0x12345678_12345678)),
1082 @as(usize, @truncate(0xffffffff_ffffffff)),
1083 @as(usize, @truncate(0xeeeeeeee_eeeeeeee)),
1084 };
1085
1086 try b.writeConst(writer, u8, input[0]);
1087 try b.writeConst(writer, i8, input[1]);
1088 try b.writeConst(writer, u16, input[2]);
1089 try b.writeConst(writer, i16, input[3]);
1090 try b.writeConst(writer, u32, input[4]);
1091 try b.writeConst(writer, i32, input[5]);
1092 try b.writeConst(writer, u64, input[6]);
1093 try b.writeConst(writer, i64, input[7]);
1094 try b.writeConst(writer, u28, input[8]);
1095 try b.writeConst(writer, i28, input[9]);
1096 try b.writeAddr(writer, input[10]);
1097
1098 var mock_compile_unit: std.debug.Dwarf.CompileUnit = undefined;
1099 mock_compile_unit.addr_base = 1;
1100
1101 var mock_debug_addr: std.Io.Writer.Allocating = .init(allocator);
1102 defer mock_debug_addr.deinit();
1103
1104 try mock_debug_addr.writer.writeInt(u16, 0, native_endian);
1105 try mock_debug_addr.writer.writeInt(usize, input[11], native_endian);
1106 try mock_debug_addr.writer.writeInt(usize, input[12], native_endian);
1107
1108 const context: Context = .{
1109 .compile_unit = &mock_compile_unit,
1110 .debug_addr = mock_debug_addr.written(),
1111 };
1112
1113 try b.writeConstx(writer, @as(usize, 1));
1114 try b.writeAddrx(writer, @as(usize, 1 + @sizeOf(usize)));
1115
1116 const die_offset: usize = @truncate(0xaabbccdd);
1117 const type_bytes: []const u8 = &.{ 1, 2, 3, 4 };
1118 try b.writeConstType(writer, die_offset, type_bytes);
1119
1120 _ = try stack_machine.run(program.written(), allocator, context, 0);
1121
1122 const const_type = stack_machine.stack.pop().?.const_type;
1123 try testing.expectEqual(die_offset, const_type.type_offset);
1124 try testing.expectEqualSlices(u8, type_bytes, const_type.value_bytes);
1125
1126 const expected = .{
1127 .{ usize, input[12], usize },
1128 .{ usize, input[11], usize },
1129 .{ usize, input[10], usize },
1130 .{ isize, input[9], isize },
1131 .{ usize, input[8], usize },
1132 .{ isize, input[7], isize },
1133 .{ usize, input[6], usize },
1134 .{ isize, input[5], isize },
1135 .{ usize, input[4], usize },
1136 .{ isize, input[3], isize },
1137 .{ usize, input[2], usize },
1138 .{ isize, input[1], isize },
1139 .{ usize, input[0], usize },
1140 };
1141
1142 inline for (expected) |e| {
1143 try testing.expectEqual(@as(e[0], e[1]), @as(e[2], @bitCast(stack_machine.stack.pop().?.generic)));
1144 }
1145 }
1146
1147 // Register values
1148 if (std.debug.cpu_context.Native != noreturn) {
1149 stack_machine.reset();
1150 program.clearRetainingCapacity();
1151
1152 var cpu_context: std.debug.cpu_context.Native = undefined;
1153 const context = Context{
1154 .cpu_context = &cpu_context,
1155 };
1156
1157 const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
1158
1159 // TODO: Test fbreg (once implemented): mock a DIE and point compile_unit.frame_base at it
1160
1161 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1162 (try regNative(&cpu_context, fp_reg_num)).* = 1;
1163 (try regNative(&cpu_context, ip_reg_num)).* = 2;
1164
1165 try b.writeBreg(writer, fp_reg_num, @as(usize, 100));
1166 try b.writeBregx(writer, ip_reg_num, @as(usize, 200));
1167 try b.writeRegvalType(writer, @as(u8, 0), @as(usize, 300));
1168
1169 _ = try stack_machine.run(program.written(), allocator, context, 0);
1170
1171 const regval_type = stack_machine.stack.pop().?.regval_type;
1172 try testing.expectEqual(@as(usize, 300), regval_type.type_offset);
1173 try testing.expectEqual(@as(u8, @sizeOf(usize)), regval_type.type_size);
1174 try testing.expectEqual(@as(usize, 0xee), regval_type.value);
1175
1176 try testing.expectEqual(@as(usize, 202), stack_machine.stack.pop().?.generic);
1177 try testing.expectEqual(@as(usize, 101), stack_machine.stack.pop().?.generic);
1178 }
1179
1180 // Stack operations
1181 {
1182 var context = Context{};
1183
1184 stack_machine.reset();
1185 program.clearRetainingCapacity();
1186 try b.writeConst(writer, u8, 1);
1187 try b.writeOpcode(writer, OP.dup);
1188 _ = try stack_machine.run(program.written(), allocator, context, null);
1189 try testing.expectEqual(@as(usize, 1), stack_machine.stack.pop().?.generic);
1190 try testing.expectEqual(@as(usize, 1), stack_machine.stack.pop().?.generic);
1191
1192 stack_machine.reset();
1193 program.clearRetainingCapacity();
1194 try b.writeConst(writer, u8, 1);
1195 try b.writeOpcode(writer, OP.drop);
1196 _ = try stack_machine.run(program.written(), allocator, context, null);
1197 try testing.expect(stack_machine.stack.pop() == null);
1198
1199 stack_machine.reset();
1200 program.clearRetainingCapacity();
1201 try b.writeConst(writer, u8, 4);
1202 try b.writeConst(writer, u8, 5);
1203 try b.writeConst(writer, u8, 6);
1204 try b.writePick(writer, 2);
1205 _ = try stack_machine.run(program.written(), allocator, context, null);
1206 try testing.expectEqual(@as(usize, 4), stack_machine.stack.pop().?.generic);
1207
1208 stack_machine.reset();
1209 program.clearRetainingCapacity();
1210 try b.writeConst(writer, u8, 4);
1211 try b.writeConst(writer, u8, 5);
1212 try b.writeConst(writer, u8, 6);
1213 try b.writeOpcode(writer, OP.over);
1214 _ = try stack_machine.run(program.written(), allocator, context, null);
1215 try testing.expectEqual(@as(usize, 5), stack_machine.stack.pop().?.generic);
1216
1217 stack_machine.reset();
1218 program.clearRetainingCapacity();
1219 try b.writeConst(writer, u8, 5);
1220 try b.writeConst(writer, u8, 6);
1221 try b.writeOpcode(writer, OP.swap);
1222 _ = try stack_machine.run(program.written(), allocator, context, null);
1223 try testing.expectEqual(@as(usize, 5), stack_machine.stack.pop().?.generic);
1224 try testing.expectEqual(@as(usize, 6), stack_machine.stack.pop().?.generic);
1225
1226 stack_machine.reset();
1227 program.clearRetainingCapacity();
1228 try b.writeConst(writer, u8, 4);
1229 try b.writeConst(writer, u8, 5);
1230 try b.writeConst(writer, u8, 6);
1231 try b.writeOpcode(writer, OP.rot);
1232 _ = try stack_machine.run(program.written(), allocator, context, null);
1233 try testing.expectEqual(@as(usize, 5), stack_machine.stack.pop().?.generic);
1234 try testing.expectEqual(@as(usize, 4), stack_machine.stack.pop().?.generic);
1235 try testing.expectEqual(@as(usize, 6), stack_machine.stack.pop().?.generic);
1236
1237 const deref_target: usize = @truncate(0xffeeffee_ffeeffee);
1238
1239 stack_machine.reset();
1240 program.clearRetainingCapacity();
1241 try b.writeAddr(writer, @intFromPtr(&deref_target));
1242 try b.writeOpcode(writer, OP.deref);
1243 _ = try stack_machine.run(program.written(), allocator, context, null);
1244 try testing.expectEqual(deref_target, stack_machine.stack.pop().?.generic);
1245
1246 stack_machine.reset();
1247 program.clearRetainingCapacity();
1248 try b.writeLiteral(writer, 0);
1249 try b.writeAddr(writer, @intFromPtr(&deref_target));
1250 try b.writeOpcode(writer, OP.xderef);
1251 _ = try stack_machine.run(program.written(), allocator, context, null);
1252 try testing.expectEqual(deref_target, stack_machine.stack.pop().?.generic);
1253
1254 stack_machine.reset();
1255 program.clearRetainingCapacity();
1256 try b.writeAddr(writer, @intFromPtr(&deref_target));
1257 try b.writeDerefSize(writer, 1);
1258 _ = try stack_machine.run(program.written(), allocator, context, null);
1259 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.pop().?.generic);
1260
1261 stack_machine.reset();
1262 program.clearRetainingCapacity();
1263 try b.writeLiteral(writer, 0);
1264 try b.writeAddr(writer, @intFromPtr(&deref_target));
1265 try b.writeXDerefSize(writer, 1);
1266 _ = try stack_machine.run(program.written(), allocator, context, null);
1267 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), stack_machine.stack.pop().?.generic);
1268
1269 const type_offset: usize = @truncate(0xaabbaabb_aabbaabb);
1270
1271 stack_machine.reset();
1272 program.clearRetainingCapacity();
1273 try b.writeAddr(writer, @intFromPtr(&deref_target));
1274 try b.writeDerefType(writer, 1, type_offset);
1275 _ = try stack_machine.run(program.written(), allocator, context, null);
1276 const deref_type = stack_machine.stack.pop().?.regval_type;
1277 try testing.expectEqual(type_offset, deref_type.type_offset);
1278 try testing.expectEqual(@as(u8, 1), deref_type.type_size);
1279 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), deref_type.value);
1280
1281 stack_machine.reset();
1282 program.clearRetainingCapacity();
1283 try b.writeLiteral(writer, 0);
1284 try b.writeAddr(writer, @intFromPtr(&deref_target));
1285 try b.writeXDerefType(writer, 1, type_offset);
1286 _ = try stack_machine.run(program.written(), allocator, context, null);
1287 const xderef_type = stack_machine.stack.pop().?.regval_type;
1288 try testing.expectEqual(type_offset, xderef_type.type_offset);
1289 try testing.expectEqual(@as(u8, 1), xderef_type.type_size);
1290 try testing.expectEqual(@as(usize, @as(*const u8, @ptrCast(&deref_target)).*), xderef_type.value);
1291
1292 context.object_address = &deref_target;
1293
1294 stack_machine.reset();
1295 program.clearRetainingCapacity();
1296 try b.writeOpcode(writer, OP.push_object_address);
1297 _ = try stack_machine.run(program.written(), allocator, context, null);
1298 try testing.expectEqual(@as(usize, @intFromPtr(context.object_address.?)), stack_machine.stack.pop().?.generic);
1299
1300 // TODO: Test OP.form_tls_address
1301
1302 context.cfa = @truncate(0xccddccdd_ccddccdd);
1303
1304 stack_machine.reset();
1305 program.clearRetainingCapacity();
1306 try b.writeOpcode(writer, OP.call_frame_cfa);
1307 _ = try stack_machine.run(program.written(), allocator, context, null);
1308 try testing.expectEqual(context.cfa.?, stack_machine.stack.pop().?.generic);
1309 }
1310
1311 // Arithmetic and Logical Operations
1312 {
1313 const context = Context{};
1314
1315 stack_machine.reset();
1316 program.clearRetainingCapacity();
1317 try b.writeConst(writer, i16, -4096);
1318 try b.writeOpcode(writer, OP.abs);
1319 _ = try stack_machine.run(program.written(), allocator, context, null);
1320 try testing.expectEqual(@as(usize, 4096), stack_machine.stack.pop().?.generic);
1321
1322 stack_machine.reset();
1323 program.clearRetainingCapacity();
1324 try b.writeConst(writer, u16, 0xff0f);
1325 try b.writeConst(writer, u16, 0xf0ff);
1326 try b.writeOpcode(writer, OP.@"and");
1327 _ = try stack_machine.run(program.written(), allocator, context, null);
1328 try testing.expectEqual(@as(usize, 0xf00f), stack_machine.stack.pop().?.generic);
1329
1330 stack_machine.reset();
1331 program.clearRetainingCapacity();
1332 try b.writeConst(writer, i16, -404);
1333 try b.writeConst(writer, i16, 100);
1334 try b.writeOpcode(writer, OP.div);
1335 _ = try stack_machine.run(program.written(), allocator, context, null);
1336 try testing.expectEqual(@as(isize, -404 / 100), @as(isize, @bitCast(stack_machine.stack.pop().?.generic)));
1337
1338 stack_machine.reset();
1339 program.clearRetainingCapacity();
1340 try b.writeConst(writer, u16, 200);
1341 try b.writeConst(writer, u16, 50);
1342 try b.writeOpcode(writer, OP.minus);
1343 _ = try stack_machine.run(program.written(), allocator, context, null);
1344 try testing.expectEqual(@as(usize, 150), stack_machine.stack.pop().?.generic);
1345
1346 stack_machine.reset();
1347 program.clearRetainingCapacity();
1348 try b.writeConst(writer, u16, 123);
1349 try b.writeConst(writer, u16, 100);
1350 try b.writeOpcode(writer, OP.mod);
1351 _ = try stack_machine.run(program.written(), allocator, context, null);
1352 try testing.expectEqual(@as(usize, 23), stack_machine.stack.pop().?.generic);
1353
1354 stack_machine.reset();
1355 program.clearRetainingCapacity();
1356 try b.writeConst(writer, u16, 0xff);
1357 try b.writeConst(writer, u16, 0xee);
1358 try b.writeOpcode(writer, OP.mul);
1359 _ = try stack_machine.run(program.written(), allocator, context, null);
1360 try testing.expectEqual(@as(usize, 0xed12), stack_machine.stack.pop().?.generic);
1361
1362 stack_machine.reset();
1363 program.clearRetainingCapacity();
1364 try b.writeConst(writer, u16, 5);
1365 try b.writeOpcode(writer, OP.neg);
1366 try b.writeConst(writer, i16, -6);
1367 try b.writeOpcode(writer, OP.neg);
1368 _ = try stack_machine.run(program.written(), allocator, context, null);
1369 try testing.expectEqual(@as(usize, 6), stack_machine.stack.pop().?.generic);
1370 try testing.expectEqual(@as(isize, -5), @as(isize, @bitCast(stack_machine.stack.pop().?.generic)));
1371
1372 stack_machine.reset();
1373 program.clearRetainingCapacity();
1374 try b.writeConst(writer, u16, 0xff0f);
1375 try b.writeOpcode(writer, OP.not);
1376 _ = try stack_machine.run(program.written(), allocator, context, null);
1377 try testing.expectEqual(~@as(usize, 0xff0f), stack_machine.stack.pop().?.generic);
1378
1379 stack_machine.reset();
1380 program.clearRetainingCapacity();
1381 try b.writeConst(writer, u16, 0xff0f);
1382 try b.writeConst(writer, u16, 0xf0ff);
1383 try b.writeOpcode(writer, OP.@"or");
1384 _ = try stack_machine.run(program.written(), allocator, context, null);
1385 try testing.expectEqual(@as(usize, 0xffff), stack_machine.stack.pop().?.generic);
1386
1387 stack_machine.reset();
1388 program.clearRetainingCapacity();
1389 try b.writeConst(writer, i16, 402);
1390 try b.writeConst(writer, i16, 100);
1391 try b.writeOpcode(writer, OP.plus);
1392 _ = try stack_machine.run(program.written(), allocator, context, null);
1393 try testing.expectEqual(@as(usize, 502), stack_machine.stack.pop().?.generic);
1394
1395 stack_machine.reset();
1396 program.clearRetainingCapacity();
1397 try b.writeConst(writer, u16, 4096);
1398 try b.writePlusUconst(writer, @as(usize, 8192));
1399 _ = try stack_machine.run(program.written(), allocator, context, null);
1400 try testing.expectEqual(@as(usize, 4096 + 8192), stack_machine.stack.pop().?.generic);
1401
1402 stack_machine.reset();
1403 program.clearRetainingCapacity();
1404 try b.writeConst(writer, u16, 0xfff);
1405 try b.writeConst(writer, u16, 1);
1406 try b.writeOpcode(writer, OP.shl);
1407 _ = try stack_machine.run(program.written(), allocator, context, null);
1408 try testing.expectEqual(@as(usize, 0xfff << 1), stack_machine.stack.pop().?.generic);
1409
1410 stack_machine.reset();
1411 program.clearRetainingCapacity();
1412 try b.writeConst(writer, u16, 0xfff);
1413 try b.writeConst(writer, u16, 1);
1414 try b.writeOpcode(writer, OP.shr);
1415 _ = try stack_machine.run(program.written(), allocator, context, null);
1416 try testing.expectEqual(@as(usize, 0xfff >> 1), stack_machine.stack.pop().?.generic);
1417
1418 stack_machine.reset();
1419 program.clearRetainingCapacity();
1420 try b.writeConst(writer, u16, 0xfff);
1421 try b.writeConst(writer, u16, 1);
1422 try b.writeOpcode(writer, OP.shr);
1423 _ = try stack_machine.run(program.written(), allocator, context, null);
1424 try testing.expectEqual(@as(usize, @bitCast(@as(isize, 0xfff) >> 1)), stack_machine.stack.pop().?.generic);
1425
1426 stack_machine.reset();
1427 program.clearRetainingCapacity();
1428 try b.writeConst(writer, u16, 0xf0ff);
1429 try b.writeConst(writer, u16, 0xff0f);
1430 try b.writeOpcode(writer, OP.xor);
1431 _ = try stack_machine.run(program.written(), allocator, context, null);
1432 try testing.expectEqual(@as(usize, 0x0ff0), stack_machine.stack.pop().?.generic);
1433 }
1434
1435 // Control Flow Operations
1436 {
1437 const context = Context{};
1438 const expected = .{
1439 .{ OP.le, 1, 1, 0 },
1440 .{ OP.ge, 1, 0, 1 },
1441 .{ OP.eq, 1, 0, 0 },
1442 .{ OP.lt, 0, 1, 0 },
1443 .{ OP.gt, 0, 0, 1 },
1444 .{ OP.ne, 0, 1, 1 },
1445 };
1446
1447 inline for (expected) |e| {
1448 stack_machine.reset();
1449 program.clearRetainingCapacity();
1450
1451 try b.writeConst(writer, u16, 0);
1452 try b.writeConst(writer, u16, 0);
1453 try b.writeOpcode(writer, e[0]);
1454 try b.writeConst(writer, u16, 0);
1455 try b.writeConst(writer, u16, 1);
1456 try b.writeOpcode(writer, e[0]);
1457 try b.writeConst(writer, u16, 1);
1458 try b.writeConst(writer, u16, 0);
1459 try b.writeOpcode(writer, e[0]);
1460 _ = try stack_machine.run(program.written(), allocator, context, null);
1461 try testing.expectEqual(@as(usize, e[3]), stack_machine.stack.pop().?.generic);
1462 try testing.expectEqual(@as(usize, e[2]), stack_machine.stack.pop().?.generic);
1463 try testing.expectEqual(@as(usize, e[1]), stack_machine.stack.pop().?.generic);
1464 }
1465
1466 stack_machine.reset();
1467 program.clearRetainingCapacity();
1468 try b.writeLiteral(writer, 2);
1469 try b.writeSkip(writer, 1);
1470 try b.writeLiteral(writer, 3);
1471 _ = try stack_machine.run(program.written(), allocator, context, null);
1472 try testing.expectEqual(@as(usize, 2), stack_machine.stack.pop().?.generic);
1473
1474 stack_machine.reset();
1475 program.clearRetainingCapacity();
1476 try b.writeLiteral(writer, 2);
1477 try b.writeBra(writer, 1);
1478 try b.writeLiteral(writer, 3);
1479 try b.writeLiteral(writer, 0);
1480 try b.writeBra(writer, 1);
1481 try b.writeLiteral(writer, 4);
1482 try b.writeLiteral(writer, 5);
1483 _ = try stack_machine.run(program.written(), allocator, context, null);
1484 try testing.expectEqual(@as(usize, 5), stack_machine.stack.pop().?.generic);
1485 try testing.expectEqual(@as(usize, 4), stack_machine.stack.pop().?.generic);
1486 try testing.expect(stack_machine.stack.pop() == null);
1487
1488 // TODO: Test call2, call4, call_ref once implemented
1489
1490 }
1491
1492 // Type conversions
1493 {
1494 const context = Context{};
1495 stack_machine.reset();
1496 program.clearRetainingCapacity();
1497
1498 // TODO: Test typed OP.convert once implemented
1499
1500 const value: usize = @truncate(0xffeeffee_ffeeffee);
1501 var value_bytes: [options.addr_size]u8 = undefined;
1502 mem.writeInt(usize, &value_bytes, value, native_endian);
1503
1504 // Convert to generic type
1505 stack_machine.reset();
1506 program.clearRetainingCapacity();
1507 try b.writeConstType(writer, @as(usize, 0), &value_bytes);
1508 try b.writeConvert(writer, @as(usize, 0));
1509 _ = try stack_machine.run(program.written(), allocator, context, null);
1510 try testing.expectEqual(value, stack_machine.stack.pop().?.generic);
1511
1512 // Reinterpret to generic type
1513 stack_machine.reset();
1514 program.clearRetainingCapacity();
1515 try b.writeConstType(writer, @as(usize, 0), &value_bytes);
1516 try b.writeReinterpret(writer, @as(usize, 0));
1517 _ = try stack_machine.run(program.written(), allocator, context, null);
1518 try testing.expectEqual(value, stack_machine.stack.pop().?.generic);
1519
1520 // Reinterpret to new type
1521 const die_offset: usize = 0xffee;
1522
1523 stack_machine.reset();
1524 program.clearRetainingCapacity();
1525 try b.writeConstType(writer, @as(usize, 0), &value_bytes);
1526 try b.writeReinterpret(writer, die_offset);
1527 _ = try stack_machine.run(program.written(), allocator, context, null);
1528 const const_type = stack_machine.stack.pop().?.const_type;
1529 try testing.expectEqual(die_offset, const_type.type_offset);
1530
1531 stack_machine.reset();
1532 program.clearRetainingCapacity();
1533 try b.writeLiteral(writer, 0);
1534 try b.writeReinterpret(writer, die_offset);
1535 _ = try stack_machine.run(program.written(), allocator, context, null);
1536 const regval_type = stack_machine.stack.pop().?.regval_type;
1537 try testing.expectEqual(die_offset, regval_type.type_offset);
1538 }
1539
1540 // Special operations
1541 {
1542 var context = Context{};
1543
1544 stack_machine.reset();
1545 program.clearRetainingCapacity();
1546 try b.writeOpcode(writer, OP.nop);
1547 _ = try stack_machine.run(program.written(), allocator, context, null);
1548 try testing.expect(stack_machine.stack.pop() == null);
1549
1550 // Sub-expression
1551 {
1552 var sub_program: std.Io.Writer.Allocating = .init(allocator);
1553 defer sub_program.deinit();
1554 const sub_writer = &sub_program.writer;
1555 try b.writeLiteral(sub_writer, 3);
1556
1557 stack_machine.reset();
1558 program.clearRetainingCapacity();
1559 try b.writeEntryValue(writer, sub_program.written());
1560 _ = try stack_machine.run(program.written(), allocator, context, null);
1561 try testing.expectEqual(@as(usize, 3), stack_machine.stack.pop().?.generic);
1562 }
1563
1564 // Register location description
1565 var cpu_context: std.debug.cpu_context.Native = undefined;
1566 context = .{ .cpu_context = &cpu_context };
1567
1568 const reg_bytes = try cpu_context.dwarfRegisterBytes(0);
1569 mem.writeInt(usize, reg_bytes[0..@sizeOf(usize)], 0xee, native_endian);
1570
1571 var sub_program: std.Io.Writer.Allocating = .init(allocator);
1572 defer sub_program.deinit();
1573 const sub_writer = &sub_program.writer;
1574 try b.writeReg(sub_writer, 0);
1575
1576 stack_machine.reset();
1577 program.clearRetainingCapacity();
1578 try b.writeEntryValue(writer, sub_program.written());
1579 _ = try stack_machine.run(program.written(), allocator, context, null);
1580 try testing.expectEqual(@as(usize, 0xee), stack_machine.stack.pop().?.generic);
1581 }
1582}