From 2a8cf89dc29c9c9b4c382860fc69754690a22415 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Wed, 15 Jan 2025 13:53:55 -0800 Subject: [PATCH] svm: PQR support --- src/svm/elf.zig | 55 +++++--- src/svm/executable.zig | 55 ++++++-- src/svm/sbpf.zig | 151 ++++++++++++++++---- src/svm/tests.zig | 311 +++++++++++++++++++++++++++++++++++++++-- src/svm/vm.zig | 115 ++++++++++++++- 5 files changed, 626 insertions(+), 61 deletions(-) diff --git a/src/svm/elf.zig b/src/svm/elf.zig index 92993b7a8..e9708272e 100644 --- a/src/svm/elf.zig +++ b/src/svm/elf.zig @@ -32,18 +32,24 @@ pub const Elf = struct { shdrs: []align(1) const elf.Elf64_Shdr, phdrs: []align(1) const elf.Elf64_Phdr, - fn parse(bytes: []const u8) Headers { + fn parse(bytes: []const u8) !Headers { const header: elf.Elf64_Ehdr = @bitCast(bytes[0..@sizeOf(elf.Elf64_Ehdr)].*); const shoff = header.e_shoff; const shnum = header.e_shnum; const shsize = shnum * @sizeOf(elf.Elf64_Shdr); - const shdrs = std.mem.bytesAsSlice(elf.Elf64_Shdr, bytes[shoff..][0..shsize]); + const shdrs = std.mem.bytesAsSlice( + elf.Elf64_Shdr, + try safeSlice(bytes, shoff, shsize), + ); const phoff = header.e_phoff; const phnum = header.e_phnum; const phsize = phnum * @sizeOf(elf.Elf64_Phdr); - const phdrs = std.mem.bytesAsSlice(elf.Elf64_Phdr, bytes[phoff..][0..phsize]); + const phdrs = std.mem.bytesAsSlice( + elf.Elf64_Phdr, + try safeSlice(bytes, phoff, phsize), + ); return .{ .bytes = bytes, @@ -217,7 +223,7 @@ pub const Elf = struct { loader: *BuiltinProgram, config: Config, ) !Elf { - const headers = Headers.parse(bytes); + const headers = try Headers.parse(bytes); const data = try Data.parse(headers); const text_section = data.getShdrByName(headers, ".text") orelse @@ -246,6 +252,7 @@ pub const Elf = struct { _ = try self.function_registry.registerHashedLegacy( allocator, + !sbpf_version.enableStaticSyscalls(), "entrypoint", entry_pc, ); @@ -478,7 +485,11 @@ pub const Elf = struct { const text_section = self.headers.shdrs[text_section_index]; // fixup PC-relative call instructions - const text_bytes: []u8 = self.bytes[text_section.sh_offset..][0..text_section.sh_size]; + const text_bytes: []u8 = try safeSlice( + self.bytes, + text_section.sh_offset, + text_section.sh_size, + ); const instructions = try self.getInstructions(); for (instructions, 0..) |inst, i| { if (inst.opcode == .call_imm and @@ -490,13 +501,14 @@ pub const Elf = struct { return error.RelativeJumpOutOfBounds; const key = try self.function_registry.registerHashedLegacy( allocator, + !version.enableStaticSyscalls(), &.{}, @intCast(target_pc), ); // offset into the instruction where the immediate is stored const offset = (i *| 8) +| 4; const slice = text_bytes[offset..][0..4]; - std.mem.writeInt(u32, slice, key, .little); + std.mem.writeInt(u32, slice, @intCast(key), .little); } } @@ -565,14 +577,14 @@ pub const Elf = struct { // the target is a lddw instruction which takes up two instruction slots const va_low = val: { - const imm_slice = self.bytes[imm_offset..][0..4]; - break :val std.mem.readInt(u32, imm_slice, .little); + const imm_slice = try safeSlice(self.bytes, imm_offset, 4); + break :val std.mem.readInt(u32, imm_slice[0..4], .little); }; const va_high = val: { const imm_high_offset = r_offset +| 12; - const imm_slice = self.bytes[imm_high_offset..][0..4]; - break :val std.mem.readInt(u32, imm_slice, .little); + const imm_slice = try safeSlice(self.bytes, imm_high_offset, 4); + break :val std.mem.readInt(u32, imm_slice[0..4], .little); }; var ref_addr = (@as(u64, va_high) << 32) | va_low; @@ -583,14 +595,24 @@ pub const Elf = struct { } { - const imm_slice = self.bytes[imm_offset..][0..4]; - std.mem.writeInt(u32, imm_slice, @truncate(ref_addr), .little); + const imm_slice = try safeSlice(self.bytes, imm_offset, 4); + std.mem.writeInt( + u32, + imm_slice[0..4], + @truncate(ref_addr), + .little, + ); } { const imm_high_offset = r_offset +| 12; - const imm_slice = self.bytes[imm_high_offset..][0..4]; - std.mem.writeInt(u32, imm_slice, @intCast(ref_addr >> 32), .little); + const imm_slice = try safeSlice(self.bytes, imm_high_offset, 4); + std.mem.writeInt( + u32, + imm_slice[0..4], + @intCast(ref_addr >> 32), + .little, + ); } } else { const address: u64 = switch (version) { @@ -633,11 +655,12 @@ pub const Elf = struct { const target_pc = (symbol.st_value -| text_section.sh_addr) / 8; const key = try self.function_registry.registerHashedLegacy( allocator, + !version.enableStaticSyscalls(), symbol_name, @intCast(target_pc), ); const slice = try safeSlice(self.bytes, imm_offset, 4); - std.mem.writeInt(u32, slice[0..4], key, .little); + std.mem.writeInt(u32, slice[0..4], @intCast(key), .little); } else { const hash = sbpf.hashSymbolName(symbol_name); if (config.reject_broken_elfs and @@ -685,7 +708,7 @@ pub const Elf = struct { fn safeSlice(base: anytype, start: usize, len: usize) error{OutOfBounds}!@TypeOf(base) { if (start >= base.len) return error.OutOfBounds; - if (start +| len >= base.len) return error.OutOfBounds; + if (start +| len > base.len) return error.OutOfBounds; return base[start..][0..len]; } }; diff --git a/src/svm/executable.zig b/src/svm/executable.zig index 1771a28c5..0c104c26f 100644 --- a/src/svm/executable.zig +++ b/src/svm/executable.zig @@ -61,6 +61,36 @@ pub const Executable = struct { return Assembler.parse(allocator, source, config); } + pub fn fromTextBytes( + allocator: std.mem.Allocator, + source: []const u8, + version: sbpf.SBPFVersion, + registry: *Registry(u64), + config: Config, + ) !Executable { + const entry_pc = if (registry.lookupName("entrypoint")) |entry_pc| + entry_pc.value + else + try registry.registerHashedLegacy( + allocator, + !version.enableStaticSyscalls(), + "entrypoint", + 0, + ); + + return .{ + .instructions = std.mem.bytesAsSlice(sbpf.Instruction, source), + .bytes = source, + .version = version, + .config = config, + .function_registry = registry.*, + .entry_pc = entry_pc, + .ro_section = .{ .borrowed = .{ .offset = 0, .start = 0, .end = 0 } }, + .from_elf = false, + .text_vaddr = memory.PROGRAM_START, + }; + } + /// When the executable comes from the assembler, we need to guarantee that the /// instructions are aligned to `sbpf.Instruction` rather than 1 like they would be /// if we created the executable from the Elf file. The GPA requires allocations and @@ -176,7 +206,7 @@ pub const Assembler = struct { .dst = operands[0].register, .src = .r0, .off = 0, - .imm = @bitCast(@as(i32, @intCast(operands[1].integer))), + .imm = @truncate(@as(u64, @bitCast(operands[1].integer))), } else .{ .opcode = @enumFromInt(bind.opc | sbpf.Instruction.x), .dst = operands[0].register, @@ -335,7 +365,12 @@ pub const Assembler = struct { const entry_pc = if (function_registry.lookupName("entrypoint")) |entry| entry.value else pc: { - _ = try function_registry.registerHashedLegacy(allocator, "entrypoint", 0); + _ = try function_registry.registerHashedLegacy( + allocator, + !config.minimum_version.enableStaticSyscalls(), + "entrypoint", + 0, + ); break :pc 0; }; @@ -427,7 +462,7 @@ pub const Assembler = struct { pub fn Registry(T: type) type { return struct { - map: std.AutoHashMapUnmanaged(u32, Entry) = .{}, + map: std.AutoHashMapUnmanaged(u64, Entry) = .{}, const Entry = struct { name: []const u8, @@ -439,7 +474,7 @@ pub fn Registry(T: type) type { fn register( self: *Self, allocator: std.mem.Allocator, - key: u32, + key: u64, name: []const u8, value: T, ) !void { @@ -458,7 +493,7 @@ pub fn Registry(T: type) type { allocator: std.mem.Allocator, name: []const u8, value: T, - ) !u32 { + ) !u64 { const key = sbpf.hashSymbolName(name); try self.register(allocator, key, name, value); return key; @@ -467,18 +502,20 @@ pub fn Registry(T: type) type { pub fn registerHashedLegacy( self: *Self, allocator: std.mem.Allocator, + hash_symbol_name: bool, name: []const u8, value: T, - ) !u32 { + ) !u64 { const hash = if (std.mem.eql(u8, name, "entrypoint")) sbpf.hashSymbolName(name) else sbpf.hashSymbolName(&std.mem.toBytes(value)); - try self.register(allocator, hash, &.{}, value); - return hash; + const key: u64 = if (hash_symbol_name) hash else value; + try self.register(allocator, key, &.{}, value); + return key; } - pub fn lookupKey(self: *const Self, key: u32) ?Entry { + pub fn lookupKey(self: *const Self, key: u64) ?Entry { return self.map.get(key); } diff --git a/src/svm/sbpf.zig b/src/svm/sbpf.zig index a94bdbfd5..d39746a13 100644 --- a/src/svm/sbpf.zig +++ b/src/svm/sbpf.zig @@ -48,6 +48,14 @@ pub const SBPFVersion = enum { pub fn rejectRodataStackOverlap(version: SBPFVersion) bool { return version != .v1; } + + pub fn enablePqr(version: SBPFVersion) bool { + return version != .v1; + } + + pub fn swapSubRegImmOperands(version: SBPFVersion) bool { + return version != .v1; + } }; pub const Instruction = packed struct(u64) { @@ -205,6 +213,56 @@ pub const Instruction = packed struct(u64) { /// bpf opcode: `hor64 dst, imm` /// `dst |= imm << 32`. hor64_imm = alu64 | k | hor, + /// bpf opcode: `lmul32 dst, imm` /// `dst *= (dst * imm) as u32`. + lmul32_imm = pqr | k | lmul, + /// bpf opcode: `lmul32 dst, src` /// `dst *= (dst * src) as u32`. + lmul32_reg = pqr | x | lmul, + /// bpf opcode: `udiv32 dst, imm` /// `dst /= imm`. + udiv32_imm = pqr | k | udiv, + /// bpf opcode: `udiv32 dst, src` /// `dst /= src`. + udiv32_reg = pqr | x | udiv, + /// bpf opcode: `urem32 dst, imm` /// `dst %= imm`. + urem32_imm = pqr | k | urem, + /// bpf opcode: `urem32 dst, src` /// `dst %= src`. + urem32_reg = pqr | x | urem, + /// bpf opcode: `sdiv32 dst, imm` /// `dst /= imm`. + sdiv32_imm = pqr | k | sdiv, + /// bpf opcode: `sdiv32 dst, src` /// `dst /= src`. + sdiv32_reg = pqr | x | sdiv, + /// bpf opcode: `srem32 dst, imm` /// `dst %= imm`. + srem32_imm = pqr | k | srem, + /// bpf opcode: `srem32 dst, src` /// `dst %= src`. + srem32_reg = pqr | x | srem, + + /// bpf opcode: `lmul64 dst, imm` /// `dst = (dst * imm) as u64`. + lmul64_imm = pqr | b | k | lmul, + /// bpf opcode: `lmul64 dst, src` /// `dst = (dst * src) as u64`. + lmul64_reg = pqr | b | x | lmul, + /// bpf opcode: `uhmul64 dst, imm` /// `dst = (dst * imm) >> 64`. + uhmul64_imm = pqr | b | k | uhmul, + /// bpf opcode: `uhmul64 dst, src` /// `dst = (dst * src) >> 64`. + uhmul64_reg = pqr | b | x | uhmul, + /// bpf opcode: `udiv64 dst, imm` /// `dst /= imm`. + udiv64_imm = pqr | b | k | udiv, + /// bpf opcode: `udiv64 dst, src` /// `dst /= src`. + udiv64_reg = pqr | b | x | udiv, + /// bpf opcode: `urem64 dst, imm` /// `dst %= imm`. + urem64_imm = pqr | b | k | urem, + /// bpf opcode: `urem64 dst, src` /// `dst %= src`. + urem64_reg = pqr | b | x | urem, + /// bpf opcode: `shmul64 dst, imm` /// `dst = (dst * imm) >> 64`. + shmul64_imm = pqr | b | k | shmul, + /// bpf opcode: `shmul64 dst, src` /// `dst = (dst * src) >> 64`. + shmul64_reg = pqr | b | x | shmul, + /// bpf opcode: `sdiv64 dst, imm` /// `dst /= imm`. + sdiv64_imm = pqr | b | k | sdiv, + /// bpf opcode: `sdiv64 dst, src` /// `dst /= src`. + sdiv64_reg = pqr | b | x | sdiv, + /// bpf opcode: `srem64 dst, imm` /// `dst %= imm`. + srem64_imm = pqr | b | k | srem, + /// bpf opcode: `srem64 dst, src` /// `dst %= src`. + srem64_reg = pqr | b | x | srem, + /// bpf opcode: `ja +off` /// `pc += off`. ja = jmp | 0x0, /// bpf opcode: `jeq dst, imm, +off` /// `pc += off if dst == imm`. @@ -310,58 +368,86 @@ pub const Instruction = packed struct(u64) { pub const map = std.StaticStringMap(Entry).initComptime(&.{ // zig fmt: off - .{ "mov" , .{ .inst = .alu_binary, .opc = mov | alu64 } }, - .{ "mov64", .{ .inst = .alu_binary, .opc = mov | alu64 } }, + .{ "mov" , .{ .inst = .alu_binary, .opc = mov | alu64 } }, + .{ "mov64", .{ .inst = .alu_binary, .opc = mov | alu64 } }, .{ "mov32", .{ .inst = .alu_binary, .opc = mov | alu32 } }, - .{ "add" , .{ .inst = .alu_binary, .opc = add | alu64 } }, - .{ "add64", .{ .inst = .alu_binary, .opc = add | alu64 } }, + .{ "add" , .{ .inst = .alu_binary, .opc = add | alu64 } }, + .{ "add64", .{ .inst = .alu_binary, .opc = add | alu64 } }, .{ "add32", .{ .inst = .alu_binary, .opc = add | alu32 } }, - .{ "mul" , .{ .inst = .alu_binary, .opc = mul | alu64 } }, - .{ "mul64", .{ .inst = .alu_binary, .opc = mul | alu64 } }, + .{ "mul" , .{ .inst = .alu_binary, .opc = mul | alu64 } }, + .{ "mul64", .{ .inst = .alu_binary, .opc = mul | alu64 } }, .{ "mul32", .{ .inst = .alu_binary, .opc = mul | alu32 } }, - .{ "sub" , .{ .inst = .alu_binary, .opc = sub | alu64 } }, - .{ "sub64", .{ .inst = .alu_binary, .opc = sub | alu64 } }, + .{ "sub" , .{ .inst = .alu_binary, .opc = sub | alu64 } }, + .{ "sub64", .{ .inst = .alu_binary, .opc = sub | alu64 } }, .{ "sub32", .{ .inst = .alu_binary, .opc = sub | alu32 } }, - .{ "div" , .{ .inst = .alu_binary, .opc = div | alu64 } }, - .{ "div64", .{ .inst = .alu_binary, .opc = div | alu64 } }, + .{ "div" , .{ .inst = .alu_binary, .opc = div | alu64 } }, + .{ "div64", .{ .inst = .alu_binary, .opc = div | alu64 } }, .{ "div32", .{ .inst = .alu_binary, .opc = div | alu32 } }, - .{ "xor" , .{ .inst = .alu_binary, .opc = xor | alu64 } }, - .{ "xor64", .{ .inst = .alu_binary, .opc = xor | alu64 } }, + .{ "xor" , .{ .inst = .alu_binary, .opc = xor | alu64 } }, + .{ "xor64", .{ .inst = .alu_binary, .opc = xor | alu64 } }, .{ "xor32", .{ .inst = .alu_binary, .opc = xor | alu32 } }, - .{ "or" , .{ .inst = .alu_binary, .opc = @"or" | alu64 } }, - .{ "or64", .{ .inst = .alu_binary, .opc = @"or" | alu64 } }, + .{ "or" , .{ .inst = .alu_binary, .opc = @"or" | alu64 } }, + .{ "or64", .{ .inst = .alu_binary, .opc = @"or" | alu64 } }, .{ "or32", .{ .inst = .alu_binary, .opc = @"or" | alu32 } }, - .{ "and" , .{ .inst = .alu_binary, .opc = @"and" | alu64 } }, - .{ "and64", .{ .inst = .alu_binary, .opc = @"and" | alu64 } }, + .{ "and" , .{ .inst = .alu_binary, .opc = @"and" | alu64 } }, + .{ "and64", .{ .inst = .alu_binary, .opc = @"and" | alu64 } }, .{ "and32", .{ .inst = .alu_binary, .opc = @"and" | alu32 } }, - .{ "mod" , .{ .inst = .alu_binary, .opc = mod | alu64 } }, - .{ "mod64", .{ .inst = .alu_binary, .opc = mod | alu64 } }, + .{ "mod" , .{ .inst = .alu_binary, .opc = mod | alu64 } }, + .{ "mod64", .{ .inst = .alu_binary, .opc = mod | alu64 } }, .{ "mod32", .{ .inst = .alu_binary, .opc = mod | alu32 } }, - .{ "arsh" , .{ .inst = .alu_binary, .opc = arsh | alu64 } }, - .{ "arsh64", .{ .inst = .alu_binary, .opc = arsh | alu64 } }, + .{ "arsh" , .{ .inst = .alu_binary, .opc = arsh | alu64 } }, + .{ "arsh64", .{ .inst = .alu_binary, .opc = arsh | alu64 } }, .{ "arsh32", .{ .inst = .alu_binary, .opc = arsh | alu32 } }, .{ "lsh" , .{ .inst = .alu_binary, .opc = lsh | alu64 } }, .{ "lsh64", .{ .inst = .alu_binary, .opc = lsh | alu64 } }, - .{ "lsh32", .{ .inst = .alu_binary, .opc = lsh | alu32 } }, + .{ "lsh32", .{ .inst = .alu_binary, .opc = lsh | alu32 } }, .{ "rsh" , .{ .inst = .alu_binary, .opc = rsh | alu64 } }, .{ "rsh64", .{ .inst = .alu_binary, .opc = rsh | alu64 } }, - .{ "rsh32", .{ .inst = .alu_binary, .opc = rsh | alu32 } }, + .{ "rsh32", .{ .inst = .alu_binary, .opc = rsh | alu32 } }, .{ "hor64", .{ .inst = .alu_binary, .opc = hor | alu64 } }, + + .{ "lmul" , .{ .inst = .alu_binary, .opc = pqr | lmul | b } }, + .{ "lmul64", .{ .inst = .alu_binary, .opc = pqr | lmul | b } }, + .{ "lmul32", .{ .inst = .alu_binary, .opc = pqr | lmul } }, + + .{ "uhmul" , .{ .inst = .alu_binary, .opc = pqr | uhmul | b } }, + .{ "uhmul64", .{ .inst = .alu_binary, .opc = pqr | uhmul | b } }, + .{ "uhmul32", .{ .inst = .alu_binary, .opc = pqr | uhmul } }, + + .{ "shmul" , .{ .inst = .alu_binary, .opc = pqr | shmul | b } }, + .{ "shmul64", .{ .inst = .alu_binary, .opc = pqr | shmul | b } }, + .{ "shmul32", .{ .inst = .alu_binary, .opc = pqr | shmul } }, + + .{ "udiv" , .{ .inst = .alu_binary, .opc = pqr | udiv | b } }, + .{ "udiv64", .{ .inst = .alu_binary, .opc = pqr | udiv | b } }, + .{ "udiv32", .{ .inst = .alu_binary, .opc = pqr | udiv } }, + + .{ "urem" , .{ .inst = .alu_binary, .opc = pqr | urem | b } }, + .{ "urem64", .{ .inst = .alu_binary, .opc = pqr | urem | b } }, + .{ "urem32", .{ .inst = .alu_binary, .opc = pqr | urem } }, + + .{ "sdiv" , .{ .inst = .alu_binary, .opc = pqr | sdiv | b } }, + .{ "sdiv64", .{ .inst = .alu_binary, .opc = pqr | sdiv | b } }, + .{ "sdiv32", .{ .inst = .alu_binary, .opc = pqr | sdiv } }, + + .{ "srem" , .{ .inst = .alu_binary, .opc = pqr | srem | b } }, + .{ "srem64", .{ .inst = .alu_binary, .opc = pqr | srem | b } }, + .{ "srem32", .{ .inst = .alu_binary, .opc = pqr | srem } }, - .{ "neg" , .{ .inst = .alu_unary, .opc = neg | alu64 } }, - .{ "neg64", .{ .inst = .alu_unary, .opc = neg | alu64 } }, + .{ "neg" , .{ .inst = .alu_unary, .opc = neg | alu64 } }, + .{ "neg64", .{ .inst = .alu_unary, .opc = neg | alu64 } }, .{ "neg32", .{ .inst = .alu_unary, .opc = neg | alu32 } }, .{ "ja" , .{ .inst = .jump_unconditional, .opc = ja | jmp } }, @@ -519,6 +605,21 @@ pub const Instruction = packed struct(u64) { /// alu/alu64 operation code: high or pub const hor: u8 = 0xf0; + /// pqr operation code: unsigned high multiplication. + pub const uhmul: u8 = 0x20; + /// pqr operation code: unsigned division quotient. + pub const udiv: u8 = 0x40; + /// pqr operation code: unsigned division remainder. + pub const urem: u8 = 0x60; + /// pqr operation code: low multiplication. + pub const lmul: u8 = 0x80; + /// pqr operation code: signed high multiplication. + pub const shmul: u8 = 0xa0; + /// pqr operation code: signed division quotient. + pub const sdiv: u8 = 0xc0; + /// pqr operation code: signed division remainder. + pub const srem: u8 = 0xe0; + pub const Register = enum(u4) { /// Return Value r0, @@ -530,7 +631,7 @@ pub const Instruction = packed struct(u64) { r3, /// Argument 3 r4, - /// Argument 4 @"or" stack-spill ptr + /// Argument 4 or stack-spill ptr r5, /// Call-preserved r6, diff --git a/src/svm/tests.zig b/src/svm/tests.zig index 2c5514232..85c00e018 100644 --- a/src/svm/tests.zig +++ b/src/svm/tests.zig @@ -12,6 +12,7 @@ const BuiltinProgram = lib.BuiltinProgram; const Config = lib.Config; const Region = memory.Region; const MemoryMap = memory.MemoryMap; +const OpCode = sbpf.Instruction.OpCode; const expectEqual = std.testing.expectEqual; fn testAsm(config: Config, source: []const u8, expected: anytype) !void { @@ -134,6 +135,32 @@ test "alu32 logic" { \\ exit , 0x11); } + +test "alu32 arithmetic" { + try testAsm(.{}, + \\entrypoint: + \\ mov32 r0, 0 + \\ mov32 r1, 1 + \\ mov32 r2, 2 + \\ mov32 r3, 3 + \\ mov32 r4, 4 + \\ mov32 r5, 5 + \\ mov32 r6, 6 + \\ mov32 r7, 7 + \\ mov32 r8, 8 + \\ mov32 r9, 9 + \\ sub32 r0, 13 + \\ sub32 r0, r1 + \\ add32 r0, 23 + \\ add32 r0, r7 + \\ lmul32 r0, 7 + \\ lmul32 r0, r3 + \\ udiv32 r0, 2 + \\ udiv32 r0, r4 + \\ exit + , 110); +} + test "alu64 logic" { try testAsm(.{}, \\entrypoint: @@ -328,7 +355,7 @@ test "sub32 imm" { \\ mov32 r0, 3 \\ sub32 r0, 1 \\ exit - , 2); + , 0xFFFFFFFFFFFFFFFE); } test "sub32 reg" { @@ -347,7 +374,7 @@ test "sub64 imm" { \\ mov r0, 3 \\ sub r0, 1 \\ exit - , 2); + , 0xFFFFFFFFFFFFFFFE); } test "sub64 imm negative" { @@ -356,7 +383,7 @@ test "sub64 imm negative" { \\ mov r0, 3 \\ sub r0, -1 \\ exit - , 4); + , 0xFFFFFFFFFFFFFFFC); } test "sub64 reg" { @@ -1628,6 +1655,274 @@ test "jslt reg" { ); } +test "lmul loop" { + try testAsm( + .{}, + \\entrypoint: + \\ mov r0, 0x7 + \\ add r1, 0xa + \\ lsh r1, 0x20 + \\ rsh r1, 0x20 + \\ jeq r1, 0x0, +4 + \\ mov r0, 0x7 + \\ lmul r0, 0x7 + \\ add r1, -1 + \\ jne r1, 0x0, -3 + \\ exit + , + 0x75db9c97, + ); +} + +test "lmul128" { + try testAsmWithMemory(.{}, + \\entrypoint: + \\ mov r0, r1 + \\ mov r2, 30 + \\ mov r3, 0 + \\ mov r4, 20 + \\ mov r5, 0 + \\ lmul64 r3, r4 + \\ lmul64 r5, r2 + \\ add64 r5, r3 + \\ mov64 r0, r2 + \\ rsh64 r0, 0x20 + \\ mov64 r3, r4 + \\ rsh64 r3, 0x20 + \\ mov64 r6, r3 + \\ lmul64 r6, r0 + \\ add64 r5, r6 + \\ lsh64 r4, 0x20 + \\ rsh64 r4, 0x20 + \\ mov64 r6, r4 + \\ lmul64 r6, r0 + \\ lsh64 r2, 0x20 + \\ rsh64 r2, 0x20 + \\ lmul64 r4, r2 + \\ mov64 r0, r4 + \\ rsh64 r0, 0x20 + \\ add64 r0, r6 + \\ mov64 r6, r0 + \\ rsh64 r6, 0x20 + \\ add64 r5, r6 + \\ lmul64 r3, r2 + \\ lsh64 r0, 0x20 + \\ rsh64 r0, 0x20 + \\ add64 r0, r3 + \\ mov64 r2, r0 + \\ rsh64 r2, 0x20 + \\ add64 r5, r2 + \\ stxdw [r1+0x8], r5 + \\ lsh64 r0, 0x20 + \\ lsh64 r4, 0x20 + \\ rsh64 r4, 0x20 + \\ or64 r0, r4 + \\ stxdw [r1+0x0], r0 + \\ exit + , &(.{0} ** 16), 600); +} + +test "prime" { + try testAsm(.{}, + \\entrypoint: + \\ mov r1, 67 + \\ mov r0, 0x1 + \\ mov r2, 0x2 + \\ jgt r1, 0x2, +4 + \\ ja +10 + \\ add r2, 0x1 + \\ mov r0, 0x1 + \\ jge r2, r1, +7 + \\ mov r3, r1 + \\ udiv r3, r2 + \\ lmul r3, r2 + \\ mov r4, r1 + \\ sub r4, r3 + \\ mov r0, 0x0 + \\ jne r4, 0x0, -10 + \\ exit + , 1); +} + +test "subnet" { + try testAsmWithMemory(.{}, + \\entrypoint: + \\ mov r2, 0xe + \\ ldxh r3, [r1+12] + \\ jne r3, 0x81, +2 + \\ mov r2, 0x12 + \\ ldxh r3, [r1+16] + \\ and r3, 0xffff + \\ jne r3, 0x8, +5 + \\ add r1, r2 + \\ mov r0, 0x1 + \\ ldxw r1, [r1+16] + \\ and r1, 0xffffff + \\ jeq r1, 0x1a8c0, +1 + \\ mov r0, 0x0 + \\ exit + , &.{ + 0x00, 0x00, 0xc0, 0x9f, 0xa0, 0x97, 0x00, 0xa0, 0xcc, 0x3b, + 0xbf, 0xfa, 0x08, 0x00, 0x45, 0x10, 0x00, 0x3c, 0x46, 0x3c, + 0x40, 0x00, 0x40, 0x06, 0x73, 0x1c, 0xc0, 0xa8, 0x01, 0x02, + 0xc0, 0xa8, 0x01, 0x01, 0x06, 0x0e, 0x00, 0x17, 0x99, 0xc5, + 0xa0, 0xec, 0x00, 0x00, 0x00, 0x00, 0xa0, 0x02, 0x7d, 0x78, + 0xe0, 0xa3, 0x00, 0x00, 0x02, 0x04, 0x05, 0xb4, 0x04, 0x02, + 0x08, 0x0a, 0x00, 0x9c, 0x27, 0x24, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x03, 0x03, 0x00, + }, 0x1); +} + +test "pqr divide by zero" { + const allocator = std.testing.allocator; + var program: [24]u8 = .{0} ** 24; + program[0] = @intFromEnum(OpCode.mov32_imm); + program[16] = @intFromEnum(OpCode.exit); + + inline for (.{ + OpCode.udiv32_reg, + OpCode.udiv64_reg, + OpCode.urem32_reg, + OpCode.urem64_reg, + OpCode.sdiv32_reg, + OpCode.sdiv64_reg, + OpCode.srem32_reg, + OpCode.srem64_reg, + }) |opcode| { + program[8] = @intFromEnum(opcode); + + const config: Config = .{}; + + var registry: lib.Registry(u64) = .{}; + defer registry.deinit(allocator); + + var executable = try Executable.fromTextBytes( + allocator, + &program, + .v2, + ®istry, + config, + ); + + var loader: BuiltinProgram = .{}; + const map = try MemoryMap.init(&.{}, .v2); + + var vm = try Vm.init(allocator, &executable, map, &loader, 0); + defer vm.deinit(); + + try expectEqual(error.DivisionByZero, vm.run()); + } +} + +test "pqr" { + const allocator = std.testing.allocator; + var program: [48]u8 = .{0} ** 48; + // mov64 r0, X + program[0] = @intFromEnum(OpCode.mov64_imm); + // hor64 r0, X + program[8] = @intFromEnum(OpCode.hor64_imm); + + // mov64 r1, X + program[16] = @intFromEnum(OpCode.mov64_imm); + program[17] = 1; // dst = r1 + // hor64 r1, X + program[24] = @intFromEnum(OpCode.hor64_imm); + program[25] = 1; // dst = r1 + + // set the instruction we're testing to use r1 as the src + program[33] = 16; // src = r1 + program[40] = @intFromEnum(OpCode.exit); + + const max_int = std.math.maxInt(u64); + inline for ( + [_]struct { OpCode, u64, u64, u64 }{ + .{ OpCode.udiv32_reg, 13, 4, 3 }, + .{ OpCode.uhmul64_reg, 13, 4, 0 }, + .{ OpCode.udiv32_reg, 13, 4, 3 }, + .{ OpCode.udiv64_reg, 13, 4, 3 }, + .{ OpCode.urem32_reg, 13, 4, 1 }, + .{ OpCode.urem64_reg, 13, 4, 1 }, + .{ OpCode.uhmul64_reg, 13, max_int, 12 }, + .{ OpCode.udiv32_reg, 13, max_int, 0 }, + .{ OpCode.udiv64_reg, 13, max_int, 0 }, + .{ OpCode.urem32_reg, 13, max_int, 13 }, + .{ OpCode.urem64_reg, 13, max_int, 13 }, + .{ OpCode.uhmul64_reg, max_int, 4, 3 }, + .{ OpCode.udiv32_reg, max_int, 4, std.math.maxInt(u32) / 4 }, + .{ OpCode.udiv64_reg, max_int, 4, max_int / 4 }, + .{ OpCode.urem32_reg, max_int, 4, 3 }, + .{ OpCode.urem64_reg, max_int, 4, 3 }, + .{ OpCode.uhmul64_reg, max_int, max_int, max_int - 1 }, + .{ OpCode.udiv32_reg, max_int, max_int, 1 }, + .{ OpCode.udiv64_reg, max_int, max_int, 1 }, + .{ OpCode.urem32_reg, max_int, max_int, 0 }, + .{ OpCode.urem64_reg, max_int, max_int, 0 }, + + .{ OpCode.lmul32_reg, 13, 4, 52 }, + .{ OpCode.lmul64_reg, 13, 4, 52 }, + .{ OpCode.shmul64_reg, 13, 4, 0 }, + .{ OpCode.sdiv32_reg, 13, 4, 3 }, + .{ OpCode.sdiv64_reg, 13, 4, 3 }, + .{ OpCode.srem32_reg, 13, 4, 1 }, + .{ OpCode.srem64_reg, 13, 4, 1 }, + + .{ OpCode.lmul32_reg, 13, ~@as(u64, 3), ~@as(u64, 51) }, + .{ OpCode.lmul64_reg, 13, ~@as(u64, 3), ~@as(u64, 51) }, + .{ OpCode.shmul64_reg, 13, ~@as(u64, 3), ~@as(u64, 0) }, + .{ OpCode.sdiv32_reg, 13, ~@as(u64, 3), ~@as(u64, 2) }, + .{ OpCode.sdiv64_reg, 13, ~@as(u64, 3), ~@as(u64, 2) }, + .{ OpCode.srem32_reg, 13, ~@as(u64, 3), 1 }, + .{ OpCode.srem64_reg, 13, ~@as(u64, 3), 1 }, + + .{ OpCode.lmul32_reg, ~@as(u64, 12), 4, ~@as(u64, 51) }, + .{ OpCode.lmul64_reg, ~@as(u64, 12), 4, ~@as(u64, 51) }, + .{ OpCode.shmul64_reg, ~@as(u64, 12), 4, ~@as(u64, 0) }, + .{ OpCode.sdiv32_reg, ~@as(u64, 12), 4, ~@as(u64, 2) }, + .{ OpCode.sdiv64_reg, ~@as(u64, 12), 4, ~@as(u64, 2) }, + .{ OpCode.srem32_reg, ~@as(u64, 12), 4, ~@as(u64, 0) }, + .{ OpCode.srem64_reg, ~@as(u64, 12), 4, ~@as(u64, 0) }, + + .{ OpCode.lmul32_reg, ~@as(u64, 12), ~@as(u64, 3), 52 }, + .{ OpCode.lmul64_reg, ~@as(u64, 12), ~@as(u64, 3), 52 }, + .{ OpCode.shmul64_reg, ~@as(u64, 12), ~@as(u64, 3), 0 }, + .{ OpCode.sdiv32_reg, ~@as(u64, 12), ~@as(u64, 3), 3 }, + .{ OpCode.sdiv64_reg, ~@as(u64, 12), ~@as(u64, 3), 3 }, + .{ OpCode.srem32_reg, ~@as(u64, 12), ~@as(u64, 3), ~@as(u64, 0) }, + .{ OpCode.srem64_reg, ~@as(u64, 12), ~@as(u64, 3), ~@as(u64, 0) }, + }, + ) |entry| { + const opc, const dst, const src, const expected = entry; + std.mem.writeInt(u32, program[4..][0..4], @truncate(dst), .little); + std.mem.writeInt(u32, program[12..][0..4], @truncate(dst >> 32), .little); + std.mem.writeInt(u32, program[20..][0..4], @truncate(src), .little); + std.mem.writeInt(u32, program[28..][0..4], @truncate(src >> 32), .little); + std.mem.writeInt(u32, program[36..][0..4], @truncate(src), .little); + program[32] = @intFromEnum(opc); + + const config: Config = .{}; + + var registry: lib.Registry(u64) = .{}; + defer registry.deinit(allocator); + + var executable = try Executable.fromTextBytes( + allocator, + &program, + .v2, + ®istry, + config, + ); + + var loader: BuiltinProgram = .{}; + const map = try MemoryMap.init(&.{}, .v2); + + var vm = try Vm.init(allocator, &executable, map, &loader, 0); + defer vm.deinit(); + + const unsigned_expected: u64 = expected; + try expectEqual(unsigned_expected, try vm.run()); + } +} + test "stack1" { try testAsm( .{}, @@ -1757,11 +2052,7 @@ test "dynamic frame pointer" { , memory.STACK_START + config.stackSize() - 8); } -fn testElf( - config: Config, - path: []const u8, - expected: anytype, -) !void { +fn testElf(config: Config, path: []const u8, expected: anytype) !void { return testElfWithSyscalls(config, path, &.{}, expected); } @@ -1901,25 +2192,25 @@ test "struct func pointer" { test "data section" { // [ 6] .data PROGBITS 0000000000000250 000250 000004 00 WA 0 0 4 try expectEqual( + error.WritableSectionsNotSupported, testElfWithSyscalls( .{}, sig.ELF_DATA_DIR ++ "data_section.so", &.{}, 0, ), - error.WritableSectionsNotSupported, ); } test "bss section" { // [ 6] .bss NOBITS 0000000000000250 000250 000004 00 WA 0 0 4 try expectEqual( + error.WritableSectionsNotSupported, testElfWithSyscalls( .{}, sig.ELF_DATA_DIR ++ "bss_section.so", &.{}, 0, ), - error.WritableSectionsNotSupported, ); } diff --git a/src/svm/vm.zig b/src/svm/vm.zig index 8317261cb..ffd72373a 100644 --- a/src/svm/vm.zig +++ b/src/svm/vm.zig @@ -154,8 +154,15 @@ pub const Vm = struct { } break :value lhs +% rhs; }, + Instruction.sub => switch (opcode) { + .sub64_imm, .sub32_imm => if (version.swapSubRegImmOperands()) + rhs -% lhs + else + lhs -% rhs, + .sub64_reg, .sub32_reg => lhs -% rhs, + else => unreachable, + }, // zig fmt: off - Instruction.sub => lhs -% rhs, Instruction.div => try std.math.divTrunc(u64, lhs, rhs), Instruction.xor => lhs ^ rhs, Instruction.@"or" => lhs | rhs, @@ -205,6 +212,106 @@ pub const Vm = struct { registers.set(inst.dst, result); }, + .lmul32_reg, + .lmul32_imm, + .udiv32_reg, + .udiv32_imm, + .urem32_reg, + .urem32_imm, + .sdiv32_reg, + .sdiv32_imm, + .srem32_reg, + .srem32_imm, + => { + if (!version.enablePqr()) return error.UnknownInstruction; + const lhs_large = registers.get(inst.dst); + const rhs_large = if (opcode.isReg()) registers.get(inst.src) else inst.imm; + + const opc = @intFromEnum(opcode) & 0b11100000; + const extended: u64 = switch (opc) { + Instruction.lmul, + Instruction.sdiv, + Instruction.srem, + => result: { + const lhs: i32 = @truncate(@as(i64, @bitCast(lhs_large))); + const rhs: i32 = @truncate(@as(i64, @bitCast(rhs_large))); + const result = switch (opc) { + Instruction.lmul => lhs *% rhs, + Instruction.sdiv => try std.math.divTrunc(i32, lhs, rhs), + Instruction.srem => try rem(i32, lhs, rhs), + else => unreachable, + }; + break :result @bitCast(@as(i64, result)); + }, + Instruction.urem, + Instruction.udiv, + => result: { + const lhs: u32 = @truncate(lhs_large); + const rhs: u32 = @truncate(rhs_large); + break :result switch (opc) { + Instruction.urem => try std.math.rem(u32, lhs, rhs), + Instruction.udiv => try std.math.divTrunc(u32, lhs, rhs), + else => unreachable, + }; + }, + else => unreachable, + }; + + registers.set(inst.dst, extended); + }, + + .lmul64_reg, + .lmul64_imm, + .uhmul64_reg, + .uhmul64_imm, + .shmul64_reg, + .shmul64_imm, + .udiv64_reg, + .udiv64_imm, + .urem64_reg, + .urem64_imm, + .sdiv64_reg, + .sdiv64_imm, + .srem64_reg, + .srem64_imm, + => { + if (!version.enablePqr()) return error.UnknownInstruction; + const lhs = registers.get(inst.dst); + const rhs = if (opcode.isReg()) registers.get(inst.src) else inst.imm; + + const opc = @intFromEnum(opcode) & 0b11100000; + const result: u64 = switch (opc) { + Instruction.lmul => lhs *% rhs, + Instruction.udiv => try std.math.divTrunc(u64, lhs, rhs), + Instruction.urem => try std.math.rem(u64, lhs, rhs), + Instruction.sdiv => result: { + const result = try std.math.divTrunc(i64, @bitCast(lhs), @bitCast(rhs)); + break :result @bitCast(result); + }, + Instruction.uhmul => result: { + const result = @as(u128, lhs) *% @as(u128, rhs); + break :result @truncate(result >> 64); + }, + Instruction.shmul, + Instruction.srem, + => result: { + const signed_lhs: i64 = @bitCast(lhs); + const signed_rhs: i64 = @bitCast(rhs); + const result: i64 = switch (opc) { + Instruction.shmul => value: { + const result = @as(i128, signed_lhs) *% @as(i128, signed_rhs); + break :value @truncate(result >> 64); + }, + Instruction.srem => try rem(i64, signed_lhs, signed_rhs), + else => unreachable, + }; + break :result @bitCast(result); + }, + else => unreachable, + }; + registers.set(inst.dst, result); + }, + // loads/stores inline // .ld_b_reg, @@ -434,4 +541,10 @@ pub const Vm = struct { const extended: i64 = signed; return @bitCast(extended); } + + pub fn rem(comptime T: type, numerator: T, denominator: T) !T { + @setRuntimeSafety(false); + if (denominator == 0) return error.DivisionByZero; + return @rem(numerator, denominator); + } };