const std = @import("std");
const builtin = @import("builtin");
const native_endian = builtin.target.cpu.arch.endian();
const mem = std.mem;
const math = std.math;
const random = std.crypto.random;
const assert = std.debug.assert;
const expectEqual = std.testing.expectEqual;
const expectError = std.testing.expectError;
const expect = std.testing.expect;
pub const LD = 0x00;
pub const LDX = 0x01;
pub const ST = 0x02;
pub const STX = 0x03;
pub const ALU = 0x04;
pub const JMP = 0x05;
pub const RET = 0x06;
pub const MISC = 0x07;
pub const W = 0x00;
pub const H = 0x08;
pub const B = 0x10;
pub const IMM = 0x00;
pub const ABS = 0x20;
pub const IND = 0x40;
pub const MEM = 0x60;
pub const LEN = 0x80;
pub const MSH = 0xa0;
pub const RND = 0xc0;
pub const K = 0x00;
pub const X = 0x08;
pub const A = 0x10;
pub const ADD = 0x00;
pub const SUB = 0x10;
pub const MUL = 0x20;
pub const DIV = 0x30;
pub const OR = 0x40;
pub const AND = 0x50;
pub const LSH = 0x60;
pub const RSH = 0x70;
pub const NEG = 0x80;
pub const MOD = 0x90;
pub const XOR = 0xa0;
pub const JA = 0x00;
pub const JEQ = 0x10;
pub const JGT = 0x20;
pub const JGE = 0x30;
pub const JSET = 0x40;
pub const TAX = 0x00;
pub const TXA = 0x80;
pub const Scratch = enum(u4) { m0, m1, m2, m3, m4, m5, m6, m7, m8, m9, m10, m11, m12, m13, m14, m15 };
pub const MEMWORDS = 16;
pub const MAXINSNS = switch (builtin.os.tag) {
.linux => 4096,
else => 512,
};
pub const MINBUFSIZE = 32;
pub const MAXBUFSIZE = 1 << 21;
pub const Insn = extern struct {
opcode: u16,
jt: u8,
jf: u8,
k: u32,
pub fn format(
self: Insn,
comptime layout: []const u8,
opts: std.fmt.FormatOptions,
writer: anytype,
) !void {
_ = opts;
if (comptime layout.len != 0 and layout[0] != 's')
@compileError("Unsupported format specifier for BPF Insn type '" ++ layout ++ "'.");
try std.fmt.format(
writer,
"Insn{{ 0x{X:0<2}, {d}, {d}, 0x{X:0<8} }}",
.{ self.opcode, self.jt, self.jf, self.k },
);
}
const Size = enum(u8) {
word = W,
half_word = H,
byte = B,
};
fn stmt(opcode: u16, k: u32) Insn {
return .{
.opcode = opcode,
.jt = 0,
.jf = 0,
.k = k,
};
}
pub fn ld_imm(value: u32) Insn {
return stmt(LD | IMM, value);
}
pub fn ld_abs(size: Size, offset: u32) Insn {
return stmt(LD | ABS | @enumToInt(size), offset);
}
pub fn ld_ind(size: Size, offset: u32) Insn {
return stmt(LD | IND | @enumToInt(size), offset);
}
pub fn ld_mem(reg: Scratch) Insn {
return stmt(LD | MEM, @enumToInt(reg));
}
pub fn ld_len() Insn {
return stmt(LD | LEN | W, 0);
}
pub fn ld_rnd() Insn {
return stmt(LD | RND | W, 0);
}
pub fn ldx_imm(value: u32) Insn {
return stmt(LDX | IMM, value);
}
pub fn ldx_mem(reg: Scratch) Insn {
return stmt(LDX | MEM, @enumToInt(reg));
}
pub fn ldx_len() Insn {
return stmt(LDX | LEN | W, 0);
}
pub fn ldx_msh(offset: u32) Insn {
return stmt(LDX | MSH | B, offset);
}
pub fn st(reg: Scratch) Insn {
return stmt(ST, @enumToInt(reg));
}
pub fn stx(reg: Scratch) Insn {
return stmt(STX, @enumToInt(reg));
}
const AluOp = enum(u16) {
add = ADD,
sub = SUB,
mul = MUL,
div = DIV,
@"or" = OR,
@"and" = AND,
lsh = LSH,
rsh = RSH,
mod = MOD,
xor = XOR,
};
const Source = enum(u16) {
k = K,
x = X,
};
const KOrX = union(Source) {
k: u32,
x: void,
};
pub fn alu_neg() Insn {
return stmt(ALU | NEG, 0);
}
pub fn alu(op: AluOp, source: KOrX) Insn {
return stmt(
ALU | @enumToInt(op) | @enumToInt(source),
if (source == .k) source.k else 0,
);
}
const JmpOp = enum(u16) {
jeq = JEQ,
jgt = JGT,
jge = JGE,
jset = JSET,
};
pub fn jmp_ja(location: u32) Insn {
return stmt(JMP | JA, location);
}
pub fn jmp(op: JmpOp, source: KOrX, jt: u8, jf: u8) Insn {
return Insn{
.opcode = JMP | @enumToInt(op) | @enumToInt(source),
.jt = jt,
.jf = jf,
.k = if (source == .k) source.k else 0,
};
}
const Verdict = enum(u16) {
k = K,
a = A,
};
const KOrA = union(Verdict) {
k: u32,
a: void,
};
pub fn ret(verdict: KOrA) Insn {
return stmt(
RET | @enumToInt(verdict),
if (verdict == .k) verdict.k else 0,
);
}
pub fn tax() Insn {
return stmt(MISC | TAX, 0);
}
pub fn txa() Insn {
return stmt(MISC | TXA, 0);
}
};
fn opcodeEqual(opcode: u16, insn: Insn) !void {
try expectEqual(opcode, insn.opcode);
}
test "opcodes" {
try opcodeEqual(0x00, Insn.ld_imm(0));
try opcodeEqual(0x20, Insn.ld_abs(.word, 0));
try opcodeEqual(0x28, Insn.ld_abs(.half_word, 0));
try opcodeEqual(0x30, Insn.ld_abs(.byte, 0));
try opcodeEqual(0x40, Insn.ld_ind(.word, 0));
try opcodeEqual(0x48, Insn.ld_ind(.half_word, 0));
try opcodeEqual(0x50, Insn.ld_ind(.byte, 0));
try opcodeEqual(0x60, Insn.ld_mem(.m0));
try opcodeEqual(0x80, Insn.ld_len());
try opcodeEqual(0xc0, Insn.ld_rnd());
try opcodeEqual(0x01, Insn.ldx_imm(0));
try opcodeEqual(0x61, Insn.ldx_mem(.m0));
try opcodeEqual(0x81, Insn.ldx_len());
try opcodeEqual(0xb1, Insn.ldx_msh(0));
try opcodeEqual(0x02, Insn.st(.m0));
try opcodeEqual(0x03, Insn.stx(.m0));
try opcodeEqual(0x04, Insn.alu(.add, .{ .k = 0 }));
try opcodeEqual(0x14, Insn.alu(.sub, .{ .k = 0 }));
try opcodeEqual(0x24, Insn.alu(.mul, .{ .k = 0 }));
try opcodeEqual(0x34, Insn.alu(.div, .{ .k = 0 }));
try opcodeEqual(0x44, Insn.alu(.@"or", .{ .k = 0 }));
try opcodeEqual(0x54, Insn.alu(.@"and", .{ .k = 0 }));
try opcodeEqual(0x64, Insn.alu(.lsh, .{ .k = 0 }));
try opcodeEqual(0x74, Insn.alu(.rsh, .{ .k = 0 }));
try opcodeEqual(0x94, Insn.alu(.mod, .{ .k = 0 }));
try opcodeEqual(0xa4, Insn.alu(.xor, .{ .k = 0 }));
try opcodeEqual(0x84, Insn.alu_neg());
try opcodeEqual(0x0c, Insn.alu(.add, .x));
try opcodeEqual(0x1c, Insn.alu(.sub, .x));
try opcodeEqual(0x2c, Insn.alu(.mul, .x));
try opcodeEqual(0x3c, Insn.alu(.div, .x));
try opcodeEqual(0x4c, Insn.alu(.@"or", .x));
try opcodeEqual(0x5c, Insn.alu(.@"and", .x));
try opcodeEqual(0x6c, Insn.alu(.lsh, .x));
try opcodeEqual(0x7c, Insn.alu(.rsh, .x));
try opcodeEqual(0x9c, Insn.alu(.mod, .x));
try opcodeEqual(0xac, Insn.alu(.xor, .x));
try opcodeEqual(0x05, Insn.jmp_ja(0));
try opcodeEqual(0x15, Insn.jmp(.jeq, .{ .k = 0 }, 0, 0));
try opcodeEqual(0x25, Insn.jmp(.jgt, .{ .k = 0 }, 0, 0));
try opcodeEqual(0x35, Insn.jmp(.jge, .{ .k = 0 }, 0, 0));
try opcodeEqual(0x45, Insn.jmp(.jset, .{ .k = 0 }, 0, 0));
try opcodeEqual(0x1d, Insn.jmp(.jeq, .x, 0, 0));
try opcodeEqual(0x2d, Insn.jmp(.jgt, .x, 0, 0));
try opcodeEqual(0x3d, Insn.jmp(.jge, .x, 0, 0));
try opcodeEqual(0x4d, Insn.jmp(.jset, .x, 0, 0));
try opcodeEqual(0x06, Insn.ret(.{ .k = 0 }));
try opcodeEqual(0x16, Insn.ret(.a));
try opcodeEqual(0x07, Insn.tax());
try opcodeEqual(0x87, Insn.txa());
}
pub const Error = error{
InvalidOpcode,
InvalidOffset,
InvalidLocation,
DivisionByZero,
NoReturn,
};
pub fn simulate(
packet: []const u8,
filter: []const Insn,
byte_order: std.builtin.Endian,
) Error!u32 {
assert(filter.len > 0 and filter.len < MAXINSNS);
assert(packet.len < MAXBUFSIZE);
const len = @intCast(u32, packet.len);
var a: u32 = 0;
var x: u32 = 0;
var m = mem.zeroes([MEMWORDS]u32);
var pc: usize = 0;
while (pc < filter.len) : (pc += 1) {
const i = filter[pc];
const k = @as(u64, i.k);
const remaining = filter.len - (pc + 1);
switch (i.opcode) {
LD | ABS | W => if (k + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset,
LD | ABS | H => if (k + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset,
LD | ABS | B => if (k >= packet.len) return error.InvalidOffset,
LD | IND | W => if (k + x + @sizeOf(u32) - 1 >= packet.len) return error.InvalidOffset,
LD | IND | H => if (k + x + @sizeOf(u16) - 1 >= packet.len) return error.InvalidOffset,
LD | IND | B => if (k + x >= packet.len) return error.InvalidOffset,
LDX | MSH | B => if (k >= packet.len) return error.InvalidOffset,
ST, STX, LD | MEM, LDX | MEM => if (i.k >= MEMWORDS) return error.InvalidOffset,
JMP | JA => if (remaining <= i.k) return error.InvalidOffset,
JMP | JEQ | K,
JMP | JGT | K,
JMP | JGE | K,
JMP | JSET | K,
JMP | JEQ | X,
JMP | JGT | X,
JMP | JGE | X,
JMP | JSET | X,
=> if (remaining <= i.jt or remaining <= i.jf) return error.InvalidLocation,
else => {},
}
switch (i.opcode) {
LD | IMM => a = i.k,
LD | MEM => a = m[i.k],
LD | LEN | W => a = len,
LD | RND | W => a = random.int(u32),
LD | ABS | W => a = mem.readInt(u32, packet[i.k..][0..@sizeOf(u32)], byte_order),
LD | ABS | H => a = mem.readInt(u16, packet[i.k..][0..@sizeOf(u16)], byte_order),
LD | ABS | B => a = packet[i.k],
LD | IND | W => a = mem.readInt(u32, packet[i.k + x ..][0..@sizeOf(u32)], byte_order),
LD | IND | H => a = mem.readInt(u16, packet[i.k + x ..][0..@sizeOf(u16)], byte_order),
LD | IND | B => a = packet[i.k + x],
LDX | IMM => x = i.k,
LDX | MEM => x = m[i.k],
LDX | LEN | W => x = len,
LDX | MSH | B => x = @as(u32, @truncate(u4, packet[i.k])) << 2,
ST => m[i.k] = a,
STX => m[i.k] = x,
ALU | ADD | K => a +%= i.k,
ALU | SUB | K => a -%= i.k,
ALU | MUL | K => a *%= i.k,
ALU | DIV | K => a = try math.divTrunc(u32, a, i.k),
ALU | OR | K => a |= i.k,
ALU | AND | K => a &= i.k,
ALU | LSH | K => a = math.shl(u32, a, i.k),
ALU | RSH | K => a = math.shr(u32, a, i.k),
ALU | MOD | K => a = try math.mod(u32, a, i.k),
ALU | XOR | K => a ^= i.k,
ALU | ADD | X => a +%= x,
ALU | SUB | X => a -%= x,
ALU | MUL | X => a *%= x,
ALU | DIV | X => a = try math.divTrunc(u32, a, x),
ALU | OR | X => a |= x,
ALU | AND | X => a &= x,
ALU | LSH | X => a = math.shl(u32, a, x),
ALU | RSH | X => a = math.shr(u32, a, x),
ALU | MOD | X => a = try math.mod(u32, a, x),
ALU | XOR | X => a ^= x,
ALU | NEG => a = @bitCast(u32, -%@bitCast(i32, a)),
JMP | JA => pc += i.k,
JMP | JEQ | K => pc += if (a == i.k) i.jt else i.jf,
JMP | JGT | K => pc += if (a > i.k) i.jt else i.jf,
JMP | JGE | K => pc += if (a >= i.k) i.jt else i.jf,
JMP | JSET | K => pc += if (a & i.k > 0) i.jt else i.jf,
JMP | JEQ | X => pc += if (a == x) i.jt else i.jf,
JMP | JGT | X => pc += if (a > x) i.jt else i.jf,
JMP | JGE | X => pc += if (a >= x) i.jt else i.jf,
JMP | JSET | X => pc += if (a & x > 0) i.jt else i.jf,
RET | K => return i.k,
RET | A => return a,
MISC | TAX => x = a,
MISC | TXA => a = x,
else => return error.InvalidOpcode,
}
}
return error.NoReturn;
}
const tcpdump_filter = [_]Insn{
Insn.ld_abs(.half_word, 12),
Insn.jmp(.jeq, .{ .k = 0x800 }, 0, 14),
Insn.ld_abs(.word, 26),
Insn.jmp(.jeq, .{ .k = 0x96658703 }, 2, 0),
Insn.ld_abs(.word, 30),
Insn.jmp(.jeq, .{ .k = 0x96658703 }, 0, 10),
Insn.ld_abs(.byte, 23),
Insn.jmp(.jeq, .{ .k = 0x6 }, 0, 8),
Insn.ld_abs(.half_word, 20),
Insn.jmp(.jset, .{ .k = 0x1fff }, 6, 0),
Insn.ldx_msh(14),
Insn.ld_ind(.half_word, 14),
Insn.jmp(.jeq, .{ .k = 0x14 }, 2, 0),
Insn.ld_ind(.half_word, 16),
Insn.jmp(.jeq, .{ .k = 0x14 }, 0, 1),
Insn.ret(.{ .k = 0x40000 }),
Insn.ret(.{ .k = 0 }),
};
const ftp_data = [_]u8{
0xde, 0xad, 0xbe, 0xef, 0xf0, 0x0f, 0xa4, 0x71, 0x74, 0xad, 0x4b, 0xf0, 0x08, 0x00,
0x45, 0x00, 0x01, 0xf2, 0x70, 0x3b, 0x40, 0x00, 0x37, 0x06, 0xf2, 0xb6,
0x96, 0x65, 0x87, 0x03, 0xc0, 0xa8, 0x01, 0x03,
0x00, 0x14, 0x80, 0x6d, 0x35, 0x81, 0x2d, 0x40, 0x4f, 0x8a, 0x29, 0x9e, 0x80, 0x18, 0x00, 0x2e,
0x88, 0x8d, 0x00, 0x00, 0x01, 0x01, 0x08, 0x0a, 0x0b, 0x59, 0x5d, 0x09, 0x32, 0x8b, 0x51, 0xa0
} ++
"lrwxrwxrwx 1 root root 12 Feb 14 2012 debian -> .pub2/debian\r\n" ++
"lrwxrwxrwx 1 root root 15 Feb 14 2012 debian-cd -> .pub2/debian-cd\r\n" ++
"lrwxrwxrwx 1 root root 9 Mar 9 2018 linux -> pub/linux\r\n" ++
"drwxr-xr-X 3 mirror mirror 4096 Sep 20 08:10 pub\r\n" ++
"lrwxrwxrwx 1 root root 12 Feb 14 2012 ubuntu -> .pub2/ubuntu\r\n" ++
"-rw-r--r-- 1 root root 1044 Jan 20 2015 welcome.msg\r\n";
test "tcpdump filter" {
try expectEqual(
@as(u32, 0x40000),
try simulate(ftp_data, &tcpdump_filter, .Big),
);
}
fn expectPass(data: anytype, filter: []const Insn) !void {
try expectEqual(
@as(u32, 0),
try simulate(mem.asBytes(data), filter, .Big),
);
}
fn expectFail(expected_error: anyerror, data: anytype, filter: []const Insn) !void {
try expectError(
expected_error,
simulate(mem.asBytes(data), filter, native_endian),
);
}
test "simulator coverage" {
const some_data = [_]u8{
0xaa, 0xbb, 0xcc, 0xdd, 0x7f,
};
try expectPass(&some_data, &.{
Insn.ld_imm(10),
Insn.ldx_imm(1),
Insn.st(.m0),
Insn.stx(.m1),
Insn.jmp(.jeq, .{ .k = 10 }, 1, 0),
Insn.ret(.{ .k = 1 }),
Insn.ld_abs(.word, 0),
Insn.jmp(.jeq, .{ .k = 0xaabbccdd }, 1, 0),
Insn.ret(.{ .k = 2 }),
Insn.ld_abs(.half_word, 0),
Insn.jmp(.jeq, .{ .k = 0xaabb }, 1, 0),
Insn.ret(.{ .k = 3 }),
Insn.ld_abs(.byte, 0),
Insn.jmp(.jeq, .{ .k = 0xaa }, 1, 0),
Insn.ret(.{ .k = 4 }),
Insn.ld_ind(.word, 0),
Insn.jmp(.jeq, .{ .k = 0xbbccdd7f }, 1, 0),
Insn.ret(.{ .k = 5 }),
Insn.ld_ind(.half_word, 0),
Insn.jmp(.jeq, .{ .k = 0xbbcc }, 1, 0),
Insn.ret(.{ .k = 6 }),
Insn.ld_ind(.byte, 0),
Insn.jmp(.jeq, .{ .k = 0xbb }, 1, 0),
Insn.ret(.{ .k = 7 }),
Insn.ld_mem(.m0),
Insn.jmp(.jeq, .{ .k = 10 }, 1, 0),
Insn.ret(.{ .k = 8 }),
Insn.ld_len(),
Insn.jmp(.jeq, .{ .k = some_data.len }, 1, 0),
Insn.ret(.{ .k = 9 }),
Insn.ld_imm(0),
Insn.ld_rnd(),
Insn.jmp(.jgt, .{ .k = 0 }, 1, 0),
Insn.ret(.{ .k = 10 }),
Insn.ld_imm(3),
Insn.ldx_imm(10),
Insn.st(.m2),
Insn.txa(),
Insn.jmp(.jeq, .x, 1, 0),
Insn.ret(.{ .k = 11 }),
Insn.ldx_mem(.m2),
Insn.jmp(.jgt, .x, 1, 0),
Insn.ret(.{ .k = 12 }),
Insn.ldx_len(),
Insn.jmp(.jgt, .x, 1, 0),
Insn.ret(.{ .k = 13 }),
Insn.ld_imm(4 * (0x7f & 0xf)),
Insn.ldx_msh(4),
Insn.jmp(.jeq, .x, 1, 0),
Insn.ret(.{ .k = 14 }),
Insn.ld_imm(0xffffffff),
Insn.ldx_imm(2),
Insn.alu(.add, .{ .k = 1 }),
Insn.jmp(.jeq, .{ .k = 0 }, 1, 0),
Insn.ret(.{ .k = 15 }),
Insn.alu(.sub, .{ .k = 1 }),
Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0),
Insn.ret(.{ .k = 16 }),
Insn.alu(.add, .x),
Insn.jmp(.jeq, .{ .k = 1 }, 1, 0),
Insn.ret(.{ .k = 17 }),
Insn.alu(.sub, .x),
Insn.jmp(.jeq, .{ .k = 0xffffffff }, 1, 0),
Insn.ret(.{ .k = 18 }),
Insn.ld_imm(16),
Insn.alu(.mul, .{ .k = 2 }),
Insn.jmp(.jeq, .{ .k = 32 }, 1, 0),
Insn.ret(.{ .k = 19 }),
Insn.alu(.mul, .x),
Insn.jmp(.jeq, .{ .k = 64 }, 1, 0),
Insn.ret(.{ .k = 20 }),
Insn.alu(.div, .{ .k = 2 }),
Insn.jmp(.jeq, .{ .k = 32 }, 1, 0),
Insn.ret(.{ .k = 21 }),
Insn.alu(.div, .x),
Insn.jmp(.jeq, .{ .k = 16 }, 1, 0),
Insn.ret(.{ .k = 22 }),
Insn.alu(.@"or", .{ .k = 4 }),
Insn.jmp(.jeq, .{ .k = 20 }, 1, 0),
Insn.ret(.{ .k = 23 }),
Insn.alu(.@"or", .x),
Insn.jmp(.jeq, .{ .k = 22 }, 1, 0),
Insn.ret(.{ .k = 24 }),
Insn.alu(.@"and", .{ .k = 0b110 }),
Insn.jmp(.jeq, .{ .k = 6 }, 1, 0),
Insn.ret(.{ .k = 25 }),
Insn.alu(.@"and", .x),
Insn.jmp(.jeq, .x, 1, 0),
Insn.ret(.{ .k = 26 }),
Insn.alu(.xor, .{ .k = 0b1111 }),
Insn.jmp(.jeq, .{ .k = 0b1101 }, 1, 0),
Insn.ret(.{ .k = 27 }),
Insn.alu(.xor, .x),
Insn.jmp(.jeq, .{ .k = 0b1111 }, 1, 0),
Insn.ret(.{ .k = 28 }),
Insn.alu(.rsh, .{ .k = 1 }),
Insn.jmp(.jeq, .{ .k = 0b0111 }, 1, 0),
Insn.ret(.{ .k = 29 }),
Insn.alu(.rsh, .x),
Insn.jmp(.jeq, .{ .k = 0b0001 }, 1, 0),
Insn.ret(.{ .k = 30 }),
Insn.alu(.lsh, .{ .k = 1 }),
Insn.jmp(.jeq, .{ .k = 0b0010 }, 1, 0),
Insn.ret(.{ .k = 31 }),
Insn.alu(.lsh, .x),
Insn.jmp(.jeq, .{ .k = 0b1000 }, 1, 0),
Insn.ret(.{ .k = 32 }),
Insn.alu(.mod, .{ .k = 6 }),
Insn.jmp(.jeq, .{ .k = 2 }, 1, 0),
Insn.ret(.{ .k = 33 }),
Insn.alu(.mod, .x),
Insn.jmp(.jeq, .{ .k = 0 }, 1, 0),
Insn.ret(.{ .k = 34 }),
Insn.txa(),
Insn.alu_neg(),
Insn.jmp(.jeq, .{ .k = ~@as(u32, 2) + 1 }, 1, 0),
Insn.ret(.{ .k = 35 }),
Insn.jmp_ja(1),
Insn.ret(.{ .k = 36 }),
Insn.ld_imm(20),
Insn.tax(),
Insn.jmp(.jeq, .{ .k = 20 }, 1, 0),
Insn.ret(.{ .k = 37 }),
Insn.jmp(.jeq, .x, 1, 0),
Insn.ret(.{ .k = 38 }),
Insn.ld_imm(19),
Insn.jmp(.jeq, .{ .k = 20 }, 0, 1),
Insn.ret(.{ .k = 39 }),
Insn.jmp(.jeq, .x, 0, 1),
Insn.ret(.{ .k = 40 }),
Insn.jmp(.jgt, .{ .k = 20 }, 0, 1),
Insn.ret(.{ .k = 41 }),
Insn.jmp(.jgt, .x, 0, 1),
Insn.ret(.{ .k = 42 }),
Insn.ld_imm(21),
Insn.jmp(.jgt, .{ .k = 20 }, 1, 0),
Insn.ret(.{ .k = 43 }),
Insn.jmp(.jgt, .x, 1, 0),
Insn.ret(.{ .k = 44 }),
Insn.ldx_imm(22),
Insn.jmp(.jge, .{ .k = 22 }, 0, 1),
Insn.ret(.{ .k = 45 }),
Insn.jmp(.jge, .x, 0, 1),
Insn.ret(.{ .k = 46 }),
Insn.ld_imm(23),
Insn.jmp(.jge, .{ .k = 22 }, 1, 0),
Insn.ret(.{ .k = 47 }),
Insn.jmp(.jge, .x, 1, 0),
Insn.ret(.{ .k = 48 }),
Insn.ldx_imm(0b10100),
Insn.jmp(.jset, .{ .k = 0b10100 }, 1, 0),
Insn.ret(.{ .k = 47 }),
Insn.jmp(.jset, .x, 1, 0),
Insn.ret(.{ .k = 48 }),
Insn.ldx_imm(0),
Insn.jmp(.jset, .{ .k = 0 }, 0, 1),
Insn.ret(.{ .k = 49 }),
Insn.jmp(.jset, .x, 0, 1),
Insn.ret(.{ .k = 50 }),
Insn.ret(.{ .k = 0 }),
});
try expectPass(&some_data, &.{
Insn.ld_imm(35),
Insn.ld_imm(0),
Insn.ret(.a),
});
try expectFail(error.NoReturn, &some_data, &.{
Insn.ld_imm(10),
});
try expectFail(error.InvalidOpcode, &some_data, &.{
Insn.stmt(0x7f, 0xdeadbeef),
});
try expectFail(error.InvalidOffset, &some_data, &.{
Insn.stmt(LD | ABS | W, 10),
});
try expectFail(error.InvalidLocation, &some_data, &.{
Insn.jmp(.jeq, .{ .k = 0 }, 10, 0),
});
try expectFail(error.InvalidLocation, &some_data, &.{
Insn.jmp(.jeq, .{ .k = 0 }, 0, 10),
});
}