From ab9df5b04b862800ffee720635b6bec0185b5054 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 8 Jul 2020 05:35:41 +0000 Subject: [PATCH] stage2: machine code for condbr jumps --- lib/std/math.zig | 5 -- src-self-hosted/codegen.zig | 126 +++++++++++++++++++++++++++--------- src-self-hosted/ir.zig | 3 + src-self-hosted/zir.zig | 19 ++++-- 4 files changed, 110 insertions(+), 43 deletions(-) diff --git a/lib/std/math.zig b/lib/std/math.zig index 799c42846b..14ffd61c29 100644 --- a/lib/std/math.zig +++ b/lib/std/math.zig @@ -1047,19 +1047,14 @@ pub fn order(a: var, b: var) Order { pub const CompareOperator = enum { /// Less than (`<`) lt, - /// Less than or equal (`<=`) lte, - /// Equal (`==`) eq, - /// Greater than or equal (`>=`) gte, - /// Greater than (`>`) gt, - /// Not equal (`!=`) neq, }; diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index d1b32f4ed4..b3fa94f088 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -12,6 +12,18 @@ const Target = std.Target; const Allocator = mem.Allocator; const trace = @import("tracy.zig").trace; +/// The codegen-related data that is stored in `ir.Inst.Block` instructions. +pub const BlockData = struct { + relocs: std.ArrayListUnmanaged(Reloc) = .{}, +}; + +pub const Reloc = union(enum) { + /// The value is an offset into the `Function` `code` from the beginning. + /// To perform the reloc, write 32-bit signed little-endian integer + /// which is a relative jump, based on the address following the reloc. + rel32: usize, +}; + pub const Result = union(enum) { /// The `code` parameter passed to `generateSymbol` has the value appended. appended: void, @@ -290,9 +302,12 @@ const Function = struct { memory: u64, /// The value is one of the stack variables. stack_offset: u64, - /// The value is the compare flag, with this operator - /// applied on top of it. - compare_flag: std.math.CompareOperator, + /// The value is in the compare flags assuming an unsigned operation, + /// with this operator applied on top of it. + compare_flags_unsigned: std.math.CompareOperator, + /// The value is in the compare flags assuming a signed operation, + /// with this operator applied on top of it. + compare_flags_signed: std.math.CompareOperator, fn isMemory(mcv: MCValue) bool { return switch (mcv) { @@ -317,7 +332,8 @@ const Function = struct { .immediate, .embedded_in_code, .memory, - .compare_flag, + .compare_flags_unsigned, + .compare_flags_signed, => false, .register, @@ -513,7 +529,8 @@ const Function = struct { switch (dst_mcv) { .none => unreachable, .dead, .unreach, .immediate => unreachable, - .compare_flag => unreachable, + .compare_flags_unsigned => unreachable, + .compare_flags_signed => unreachable, .register => |dst_reg_usize| { const dst_reg = @intToEnum(Reg(.x86_64), @intCast(u8, dst_reg_usize)); switch (src_mcv) { @@ -546,8 +563,11 @@ const Function = struct { .embedded_in_code, .memory, .stack_offset => { return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); }, - .compare_flag => { - return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag", .{}); + .compare_flags_unsigned => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); + }, + .compare_flags_signed => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); }, } }, @@ -650,7 +670,12 @@ const Function = struct { const src_mcv = try self.limitImmediateType(inst.args.rhs, i32); try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); - return MCValue{.compare_flag = inst.args.op}; + const info = inst.args.lhs.ty.intInfo(self.target.*); + if (info.signed) { + return MCValue{.compare_flags_signed = inst.args.op}; + } else { + return MCValue{.compare_flags_unsigned = inst.args.op}; + } }, else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), } @@ -658,8 +683,34 @@ const Function = struct { fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { switch (arch) { + .i386, .x86_64 => { + try self.code.ensureCapacity(self.code.items.len + 6); + + const cond = try self.resolveInst(inst.args.condition); + switch (cond) { + .compare_flags_signed => |cmp_op| { + // Here we map to the opposite opcode because the jump is to the false branch. + const opcode: u8 = switch (cmp_op) { + .gte => 0x8c, + .gt => 0x8e, + .neq => 0x84, + .lt => 0x8d, + .lte => 0x8f, + .eq => 0x85, + }; + self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); + const reloc = Reloc{ .rel32 = self.code.items.len }; + self.code.items.len += 4; + try self.genBody(inst.args.true_body, arch); + try self.performReloc(inst.base.src, reloc); + try self.genBody(inst.args.false_body, arch); + }, + else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), + } + }, else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), } + return MCValue.unreach; } fn genIsNull(self: *Function, inst: *ir.Inst.IsNull, comptime arch: std.Target.Cpu.Arch) !MCValue { @@ -676,33 +727,43 @@ const Function = struct { } } - fn genRelativeFwdJump(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, amount: u32) !void { - switch (arch) { - .i386, .x86_64 => { - // TODO x86 treats the operands as signed - if (amount <= std.math.maxInt(u8)) { - try self.code.resize(self.code.items.len + 2); - self.code.items[self.code.items.len - 2] = 0xeb; - self.code.items[self.code.items.len - 1] = @intCast(u8, amount); - } else { - try self.code.resize(self.code.items.len + 5); - self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 - const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; - mem.writeIntLittle(u32, imm_ptr, amount); - } + fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { + if (inst.base.ty.hasCodeGenBits()) { + return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); + } + // A block is nothing but a setup to be able to jump to the end. + defer inst.codegen.relocs.deinit(self.gpa); + try self.genBody(inst.args.body, arch); + + for (inst.codegen.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc); + + return MCValue.none; + } + + fn performReloc(self: *Function, src: usize, reloc: Reloc) !void { + switch (reloc) { + .rel32 => |pos| { + const amt = self.code.items.len - (pos + 4); + const s32_amt = std.math.cast(i32, amt) catch + return self.fail(src, "unable to perform relocation: jump too far", .{}); + mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); }, - else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.target.cpu.arch}), } } - fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { - // A block is nothing but a setup to be able to jump to the end. - try self.genBody(inst.args.body, arch); - return self.fail(inst.base.src, "TODO process jump relocs after block end", .{}); - } - fn genBreakVoid(self: *Function, inst: *ir.Inst.BreakVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { + // Emit a jump with a relocation. It will be patched up after the block ends. + try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); + switch (arch) { + .i386, .x86_64 => { + // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction + // which is available if the jump is 127 bytes or less forward. + try self.code.resize(self.code.items.len + 5); + self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 + // Leave the jump offset undefined + inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); + }, else => return self.fail(inst.base.src, "TODO implement breakvoid for {}", .{self.target.cpu.arch}), } return .none; @@ -776,8 +837,11 @@ const Function = struct { .dead => unreachable, .none => unreachable, .unreach => unreachable, - .compare_flag => |op| { - return self.fail(src, "TODO set register with compare flag value", .{}); + .compare_flags_unsigned => |op| { + return self.fail(src, "TODO set register with compare flags value (unsigned)", .{}); + }, + .compare_flags_signed => |op| { + return self.fail(src, "TODO set register with compare flags value (signed)", .{}); }, .immediate => |x| { if (reg.size() != 64) { diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 81b617c8a2..f2ba3801a1 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -3,6 +3,7 @@ const Value = @import("value.zig").Value; const Type = @import("type.zig").Type; const Module = @import("Module.zig"); const assert = std.debug.assert; +const codegen = @import("codegen.zig"); /// These are in-memory, analyzed instructions. See `zir.Inst` for the representation /// of instructions that correspond to the ZIR text format. @@ -157,6 +158,8 @@ pub const Inst = struct { args: struct { body: Body, }, + /// This memory is reserved for codegen code to do whatever it needs to here. + codegen: codegen.BlockData = .{}, }; pub const Breakpoint = struct { diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 8cc6cdaf05..0f36ac99f3 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -1589,11 +1589,9 @@ const EmitZIR = struct { const old_inst = inst.cast(ir.Inst.Block).?; const new_inst = try self.arena.allocator.create(Inst.Block); - var block_body = std.ArrayList(*Inst).init(self.allocator); - defer block_body.deinit(); - - try self.emitBody(old_inst.args.body, inst_table, &block_body); - + // We do this now so that the break instructions within the block + // can find it. + try inst_table.put(&old_inst.base, &new_inst.base); new_inst.* = .{ .base = .{ .src = inst.src, @@ -1601,10 +1599,17 @@ const EmitZIR = struct { }, .positionals = .{ .label = try self.autoName(), - .body = .{ .instructions = block_body.toOwnedSlice() }, + .body = undefined, }, .kw_args = .{}, }; + + var block_body = std.ArrayList(*Inst).init(self.allocator); + defer block_body.deinit(); + + try self.emitBody(old_inst.args.body, inst_table, &block_body); + new_inst.positionals.body = .{ .instructions = block_body.toOwnedSlice() }; + break :blk &new_inst.base; }, .breakpoint => try self.emitTrivial(inst.src, Inst.Breakpoint), @@ -1811,7 +1816,7 @@ const EmitZIR = struct { }, }; try instructions.append(new_inst); - try inst_table.putNoClobber(inst, new_inst); + try inst_table.put(inst, new_inst); } }