From 1ba97916933d858434294b3fde9631873bbf16c8 Mon Sep 17 00:00:00 2001 From: Mathias Magnusson Date: Fri, 30 May 2025 01:07:35 +0200 Subject: write a riscy little elf --- src/codegen.zig | 441 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/main.zig | 2 +- 2 files changed, 434 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/codegen.zig b/src/codegen.zig index da1acd2..89daaa4 100644 --- a/src/codegen.zig +++ b/src/codegen.zig @@ -4,9 +4,437 @@ const Allocator = std.mem.Allocator; const root = @import("root"); const Block = root.Block; +const Register = enum(u5) { + // zig fmt: off + zero, ra, sp, gp, tp, + t0, t1, t2, + s0, s1, + a0, a1, a2, a3, a4, a5, a6, a7, + s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, + t3, t4, t5, t6, + // zig fmt: on + + const fp = .s0; + + fn x(number: u5) @This() { + return @enumFromInt(number); + } +}; + +const Opcode = u7; + +const Instruction = packed union { + r: R, + i: I, + s: S, + b: B, + u: U, + j: J, + + const R = packed struct(u32) { + opcode: Opcode, + rd: Register, + funct3: u3, + rs1: Register, + rs2: Register, + funct7: u7, + + fn init(opcode: Opcode, rd: Register, funct3: u3, rs1: Register, rs2: Register, funct7: u7) Self { + return .{ .r = .{ + .opcode = opcode, + .rd = rd, + .funct3 = funct3, + .rs1 = rs1, + .rs2 = rs2, + .funct7 = funct7, + } }; + } + }; + /// rd = rs1 + rs2 + fn add(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 0, rs1, rs2, 0); + } + /// rd = rs1 + rs2 + fn addw(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0111011, rd, 0, rs1, rs2, 0); + } + /// rd = rs1 - rs2 + fn sub(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 0, rs1, rs2, 32); + } + /// rd = rs1 - rs2 + fn subw(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0111011, rd, 0, rs1, rs2, 32); + } + /// Bitwise xor. + /// rd = rs1 ^ rs2 + fn xor(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 4, rs1, rs2, 0); + } + /// Bitwise or. + /// rd = rs1 | rs2 + fn or_(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 6, rs1, rs2, 0); + } + /// Bitwise and. + /// rd = rs1 & rs2 + fn and_(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 7, rs1, rs2, 0); + } + /// Shift left logical. + /// rd = rs1 << rs2 + fn sll(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 1, rs1, rs2, 0); + } + /// Shift left logical word. + /// rd = rs1 << rs2 + fn sllw(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0111011, rd, 1, rs1, rs2, 0); + } + /// Shift right logical. + /// rd = rs1 >> rs2 + fn srl(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 5, rs1, rs2, 0); + } + /// Shift right logical word. + /// rd = rs1 >> rs2 + fn srlw(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0111011, rd, 5, rs1, rs2, 0); + } + /// Shift right arithmetic (preserves sign bit). + /// rd = (rs1 >> rs2) | (rs1 & (1 << (bits - 1))) + fn sra(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 5, rs1, rs2, 32); + } + /// Shift right arithmetic (preserves sign bit) word. + /// rd = (rs1 >> rs2) | (rs1 & (1 << (bits - 1))) + fn sraw(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0111011, rd, 5, rs1, rs2, 32); + } + /// Set less than, signed. + /// rd = rs1 s< rs2 ? 1 : 0 + fn slt(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 2, rs1, rs2, 0); + } + /// Set less than, unsigned. + /// rd = rs1 u< rs2 ? 1 : 0 + fn sltu(rd: Register, rs1: Register, rs2: Register) Self { + return R.init(0b0110011, rd, 3, rs1, rs2, 0); + } + + const I = packed struct(u32) { + opcode: Opcode, + rd: Register, + funct3: u3, + rs1: Register, + imm: u12, + + fn init(opcode: Opcode, rd: Register, funct3: u3, rs1: Register, imm: i12) Self { + return .{ .i = .{ + .opcode = opcode, + .rd = rd, + .funct3 = funct3, + .rs1 = rs1, + .imm = @bitCast(imm), + } }; + } + }; + /// Add immediate. + /// rd = rs1 + imm + fn addi(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0010011, rd, 0, rs1, imm); + } + /// Add immediate word. + /// rd = rs1 + imm + fn addiw(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0011011, rd, 0, rs1, imm); + } + /// Xor immediate. + /// rd = rs1 ^ imm + fn xori(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0010011, rd, 4, rs1, imm); + } + /// Or immediate. + /// rd = rs1 | imm + fn ori(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0010011, rd, 6, rs1, imm); + } + /// And immediate. + /// rd = rs1 & imm + fn andi(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0010011, rd, 7, rs1, imm); + } + /// Shift left logical immediate. + /// rd = rs1 << by + fn slli(rd: Register, rs1: Register, by: u6) Self { + return I.init(0b0010011, rd, 1, rs1, @intCast(by)); + } + /// Shift left logical word immediate. + /// rd = rs1 << by + fn slliw(rd: Register, rs1: Register, by: u5) Self { + return I.init(0b0011011, rd, 1, rs1, @intCast(by)); + } + /// Shift right logical immediate. + /// rd = rs1 >> by + fn srli(rd: Register, rs1: Register, by: u6) Self { + return I.init(0b0010011, rd, 5, rs1, @intCast(by)); + } + /// Shift right logical word immediate. + /// rd = rs1 >> by + fn srliw(rd: Register, rs1: Register, by: u6) Self { + return I.init(0b0011011, rd, 5, rs1, @intCast(by)); + } + /// Shift right arithmetic immediate (preserves sign bit). + /// rd = ((((~0 * (rs1 >> (bits - 1))) << bits) | rs1) >> by) + fn srai(rd: Register, rs1: Register, by: u6) Self { + return I.init(0b0010011, rd, 5, rs1, 0x20 << 5 | @as(i12, @intCast(by))); + } + /// Shift right arithmetic word immediate (preserves sign bit). + /// rd = ((((~0 * (rs1 >> (bits - 1))) << bits) | rs1) >> by) + fn sraiw(rd: Register, rs1: Register, by: u6) Self { + return I.init(0b0011011, rd, 5, rs1, 0x20 << 5 | @as(i12, @intCast(by))); + } + /// Set less than immediate, signed. + /// rd = rs1 s< rs2 ? 1 : 0 + fn slti(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0010011, rd, 2, rs1, imm); + } + /// Set less than sign extended immediate, unsigned comparison. + /// rd = rs1 u< rs2 ? 1 : 0 + fn sltiu(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0010011, rd, 3, rs1, imm); + } + /// Load byte and sign extend. + /// rd = @as(*i8, @ptrFromInt(rs1 + imm)).* + fn lb(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 0, rs1, imm); + } + /// Load half and sign extend. + /// rd = @as(*i16, @ptrFromInt(rs1 + imm)).* + fn lh(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 1, rs1, imm); + } + /// Load word and sign extend. + /// rd = @as(*i32, @ptrFromInt(rs1 + imm)).* + fn lw(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 2, rs1, imm); + } + /// Load double. + /// rd = @as(*u64, @ptrFromInt(rs1 + imm)).* + fn ld(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 3, rs1, imm); + } + /// Load byte and zero extend. + /// rd = @as(*u8, @ptrFromInt(rs1 + imm)).* + fn lbu(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 4, rs1, imm); + } + /// Load half and zero extend. + /// rd = @as(*u16, @ptrFromInt(rs1 + imm)).* + fn lhu(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 5, rs1, imm); + } + /// Load word and zero extend. + /// rd = @as(*u32, @ptrFromInt(rs1 + imm)).* + fn lwu(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b0000011, rd, 6, rs1, imm); + } + /// Jump and link register. + /// rd = pc + 4; pc = rs1 + imm + fn jalr(rd: Register, rs1: Register, imm: i12) Self { + return I.init(0b1100111, rd, 0, rs1, imm); + } + /// Environment call. Issue a syscall on linux + fn ecall() Self { + return I.init(0b1110011, .zero, 0, .zero, 0); + } + + const S = packed struct(u32) { + opcode: Opcode, + imm4_0: u5, + funct3: u3, + rs1: Register, + rs2: Register, + imm11_5: u7, + + fn init(opcode: Opcode, funct3: u3, rs1: Register, rs2: Register, imm: i12) Self { + const umm: u12 = @bitCast(imm); + return .{ .s = .{ + .opcode = opcode, + .imm4_0 = @truncate(umm), + .funct3 = funct3, + .rs1 = rs1, + .rs2 = rs2, + .imm11_5 = umm >> 5, + } }; + } + }; + /// Store byte. + /// @as(*u8, @ptrFromInt(rs1 + imm)).* = @truncate(rs2) + fn sb(rs1: Register, imm: i12, rs2: Register) Self { + return S.init(0b0100011, 0, rs1, rs2, imm); + } + /// Store half. + /// @as(*u16, @ptrFromInt(rs1 + imm)).* = @truncate(rs2) + fn sh(rs1: Register, imm: i12, rs2: Register) Self { + return S.init(0b0100011, 1, rs1, rs2, imm); + } + /// Store word. + /// @as(*u32, @ptrFromInt(rs1 + imm)).* = @truncate(rs2) + fn sw(rs1: Register, imm: i12, rs2: Register) Self { + return S.init(0b0100011, 2, rs1, rs2, imm); + } + /// Store double. + /// @as(*u64, @ptrFromInt(rs1 + imm)).* = @truncate(rs2) + fn sd(rs1: Register, imm: i12, rs2: Register) Self { + return S.init(0b0100011, 3, rs1, rs2, imm); + } + + /// The lowest bit of the immediate value, imm, is not stored as imm will always be even. + const B = packed struct(u32) { + opcode: Opcode, + imm11: u1, + imm4_1: u4, + funct3: u3, + rs1: Register, + rs2: Register, + imm10_5: u6, + imm12: u1, + + fn init(opcode: Opcode, funct3: u3, rs1: Register, rs2: Register, imm: i13) Self { + std.debug.assert(imm % 2 == 0); + const umm: u13 = @bitCast(imm); + return .{ .s = .{ + .opcode = opcode, + .imm11 = @truncate(umm >> 11), + .imm4_1 = @truncate(umm >> 1), + .funct3 = funct3, + .rs1 = rs1, + .rs2 = rs2, + .imm10_5 = @truncate(umm >> 5), + .imm12 = umm >> 12, + } }; + } + }; + /// Branch if equal. + /// pc = if (rs1 == rs2) then pc + imm else pc + 4 + /// + /// Advancing pc by 4 is done by all instructions, but shown here for clarity. + /// Note that `imm` must be even since function addresses always are. + fn beq(rs1: Register, rs2: Register, imm: i13) Self { + return B.init(0b1100011, 0, rs1, rs2, imm); + } + /// Branch if not equal. + /// pc = if (rs1 != rs2) then pc + imm else pc + 4 + /// + /// Advancing pc by 4 is done by all instructions, but shown here for clarity. + /// Note that `imm` must be even since function addresses always are. + fn bne(rs1: Register, rs2: Register, imm: i13) Self { + return B.init(0b1100011, 1, rs1, rs2, imm); + } + /// Branch if less than, signed. + /// pc = if (rs1 s< rs2) then pc + imm else pc + 4 + /// + /// Advancing pc by 4 is done by all instructions, but shown here for clarity. + /// Note that `imm` must be even since function addresses always are. + fn blt(rs1: Register, rs2: Register, imm: i13) Self { + return B.init(0b1100011, 4, rs1, rs2, imm); + } + /// Branch if greater than or equal, signed. + /// pc = if (rs1 s>= rs2) then pc + imm else pc + 4 + /// + /// Advancing pc by 4 is done by all instructions, but shown here for clarity. + /// Note that `imm` must be even since function addresses always are. + fn bge(rs1: Register, rs2: Register, imm: i13) Self { + return B.init(0b1100011, 5, rs1, rs2, imm); + } + /// Branch if less than, unsigned. + /// pc = if (rs1 u< rs2) then pc + imm else pc + 4 + /// + /// Advancing pc by 4 is done by all instructions, but shown here for clarity. + /// Note that `imm` must be even since function addresses always are. + fn bltu(rs1: Register, rs2: Register, imm: i13) Self { + return B.init(0b1100011, 6, rs1, rs2, imm); + } + /// Branch if greater than or equal, unsigned. + /// pc = if (rs1 u>= rs2) then pc + imm else pc + 4 + /// + /// Advancing pc by 4 is done by all instructions, but shown here for clarity. + /// Note that `imm` must be even since function addresses always are. + fn bgeu(rs1: Register, rs2: Register, imm: i13) Self { + return B.init(0b1100011, 7, rs1, rs2, imm); + } + + const U = packed struct(u32) { + opcode: Opcode, + rd: Register, + imm12_31: u20, + + fn init(opcode: Opcode, rd: Register, imm: i20) Self { + return .{ .s = .{ + .opcode = opcode, + .rd = rd, + .imm12_31 = @bitCast(imm), + } }; + } + }; + /// Load upper immediate. + /// rd = imm << 12 + fn lui(rd: Register, imm: i20) Self { + return U.init(0b0110111, rd, imm); + } + /// Add upper immediate to pc. + /// rd = pc + (imm << 12) + fn auipc(rd: Register, imm: i20) Self { + return U.init(0b0010111, rd, imm); + } + + const J = packed struct(u32) { + opcode: Opcode, + rd: Register, + imm12_19: u8, + imm11: u1, + imm1_10: u10, + imm20: u1, + + fn init(opcode: Opcode, rd: Register, imm: i21) Self { + std.debug.assert(imm % 2 == 0); + const umm: u21 = @bitCast(imm); + return .{ .j = .{ + .opcode = opcode, + .rd = rd, + .imm12_19 = @truncate(umm >> 12), + .imm11 = @truncate(umm >> 11), + .imm1_10 = @truncate(umm >> 1), + .imm20 = umm >> 20, + } }; + } + }; + /// Jump and link. + /// rd = pc + 4; pc = pc + imm + fn jal(rd: Register, imm: i21) Self { + return J.init(0b1101111, rd, imm); + } + + const Self = @This(); +}; + pub fn create_elf(allocator: Allocator, block: Block) ![]u8 { _ = block; + var output_buffer: std.ArrayList(u8) = .init(allocator); + errdefer output_buffer.deinit(); + try output_buffer.appendNTimes(undefined, @sizeOf(elf.Elf64_Ehdr) + @sizeOf(elf.Elf64_Phdr)); + const output = output_buffer.writer(); + for ([_]Instruction{ + .addi(.a7, .zero, 93), + .addi(.a0, .zero, 71), + .xori(.a0, .a0, 2), + .ecall(), + }) |instr| { + try output.writeInt(u32, @bitCast(instr), .little); + } + const base_addr = 0x10000000; const elf_header: elf.Elf64_Ehdr = .{ .e_ident = elf.MAGIC.* ++ [_]u8{ @@ -36,16 +464,13 @@ pub fn create_elf(allocator: Allocator, block: Block) ![]u8 { .p_offset = 0, .p_vaddr = base_addr, .p_paddr = base_addr, - .p_filesz = @sizeOf(elf.Elf64_Ehdr) + @sizeOf(elf.Elf64_Phdr) + 40, - .p_memsz = @sizeOf(elf.Elf64_Ehdr) + @sizeOf(elf.Elf64_Phdr) + 40, + .p_filesz = output_buffer.items.len, + .p_memsz = output_buffer.items.len, .p_align = 0x1000, }; - var output_buffer: std.ArrayList(u8) = .init(allocator); - errdefer output_buffer.deinit(); - const output = output_buffer.writer(); - try output.writeAll(std.mem.asBytes(&elf_header)); - try output.writeAll(std.mem.asBytes(&program_header)); - for (0..10) |_| try output.writeAll(&std.mem.toBytes(@as(u32, 0x13))); + @memcpy(output_buffer.items[0..@sizeOf(elf.Elf64_Ehdr)], std.mem.asBytes(&elf_header)); + @memcpy(output_buffer.items[@sizeOf(elf.Elf64_Ehdr)..][0..@sizeOf(elf.Elf64_Phdr)], std.mem.asBytes(&program_header)); + return output_buffer.toOwnedSlice(); } diff --git a/src/main.zig b/src/main.zig index a7874d8..34da76b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -17,7 +17,7 @@ pub fn main() !void { var args = std.process.args(); _ = args.next(); - const out_file = if (args.next()) |path| try std.fs.cwd().createFile(path, .{}) else std.io.getStdOut(); + const out_file = if (args.next()) |path| try std.fs.cwd().createFile(path, .{ .mode = 0o777 }) else std.io.getStdOut(); const output = out_file.writer(); // var br = std.io.bufferedReader(std.io.getStdIn().reader()); -- cgit v1.2.3