From 12737c9a3071a49f8c05348fadd0b602b6952542 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 6 Jul 2020 09:21:57 +0000 Subject: [PATCH] stage2: codegen skeleton for cmp and sub --- src-self-hosted/Module.zig | 5 + src-self-hosted/codegen.zig | 297 +++++++++++++++++++++++++----------- src-self-hosted/ir.zig | 14 +- src-self-hosted/zir.zig | 30 ++++ 4 files changed, 254 insertions(+), 92 deletions(-) diff --git a/src-self-hosted/Module.zig b/src-self-hosted/Module.zig index 4240a1c6c880a4637a1750bebb4025e016c9346c..776e63018ba3cb7ac77170ab99ff7d716023664b 100644 --- a/src-self-hosted/Module.zig +++ b/src-self-hosted/Module.zig @@ -2402,6 +2402,7 @@ fn analyzeInst(self: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!*In .bitcast => return self.analyzeInstBitCast(scope, old_inst.cast(zir.Inst.BitCast).?), .elemptr => return self.analyzeInstElemPtr(scope, old_inst.cast(zir.Inst.ElemPtr).?), .add => return self.analyzeInstAdd(scope, old_inst.cast(zir.Inst.Add).?), + .sub => return self.analyzeInstSub(scope, old_inst.cast(zir.Inst.Sub).?), .cmp => return self.analyzeInstCmp(scope, old_inst.cast(zir.Inst.Cmp).?), .condbr => return self.analyzeInstCondBr(scope, old_inst.cast(zir.Inst.CondBr).?), .isnull => return self.analyzeInstIsNull(scope, old_inst.cast(zir.Inst.IsNull).?), @@ -2903,6 +2904,10 @@ fn analyzeInstElemPtr(self: *Module, scope: *Scope, inst: *zir.Inst.ElemPtr) Inn return self.fail(scope, inst.base.src, "TODO implement more analyze elemptr", .{}); } +fn analyzeInstSub(self: *Module, scope: *Scope, inst: *zir.Inst.Sub) InnerError!*Inst { + return self.fail(scope, inst.base.src, "TODO implement analysis of sub", .{}); +} + fn analyzeInstAdd(self: *Module, scope: *Scope, inst: *zir.Inst.Add) InnerError!*Inst { const tracy = trace(@src()); defer tracy.end(); diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig index e364763b0a98f384a198483c477121faa4c51ba6..aac1a53d6a0e643c3cd945cee1b242c48df68ed1 100644 --- a/src-self-hosted/codegen.zig +++ b/src-self-hosted/codegen.zig @@ -285,6 +285,9 @@ 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, fn isMemory(mcv: MCValue) bool { return switch (mcv) { @@ -292,6 +295,31 @@ const Function = struct { else => false, }; } + + fn isImmediate(mcv: MCValue) bool { + return switch (mcv) { + .immediate => true, + else => false, + }; + } + + fn isMutable(mcv: MCValue) bool { + return switch (mcv) { + .none => unreachable, + .unreach => unreachable, + .dead => unreachable, + + .immediate, + .embedded_in_code, + .memory, + .compare_flag, + => false, + + .register, + .stack_offset, + => true, + }; + } }; fn gen(self: *Function) !void { @@ -362,20 +390,21 @@ const Function = struct { switch (inst.tag) { .add => return self.genAdd(inst.cast(ir.Inst.Add).?, arch), .arg => return self.genArg(inst.cast(ir.Inst.Arg).?), + .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), + .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .block => return self.genBlock(inst.cast(ir.Inst.Block).?, arch), .breakpoint => return self.genBreakpoint(inst.src, arch), .call => return self.genCall(inst.cast(ir.Inst.Call).?, arch), - .unreach => return MCValue{ .unreach = {} }, + .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), + .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), .constant => unreachable, // excluded from function bodies - .assembly => return self.genAsm(inst.cast(ir.Inst.Assembly).?, arch), + .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), + .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), .ptrtoint => return self.genPtrToInt(inst.cast(ir.Inst.PtrToInt).?), - .bitcast => return self.genBitCast(inst.cast(ir.Inst.BitCast).?), .ret => return self.genRet(inst.cast(ir.Inst.Ret).?, arch), .retvoid => return self.genRetVoid(inst.cast(ir.Inst.RetVoid).?, arch), - .cmp => return self.genCmp(inst.cast(ir.Inst.Cmp).?, arch), - .condbr => return self.genCondBr(inst.cast(ir.Inst.CondBr).?, arch), - .isnull => return self.genIsNull(inst.cast(ir.Inst.IsNull).?, arch), - .isnonnull => return self.genIsNonNull(inst.cast(ir.Inst.IsNonNull).?, arch), + .sub => return self.genSub(inst.cast(ir.Inst.Sub).?, arch), + .unreach => return MCValue{ .unreach = {} }, } } @@ -385,96 +414,136 @@ const Function = struct { return MCValue.dead; switch (arch) { .x86_64 => { - // Biggest encoding of ADD is 8 bytes. - try self.code.ensureCapacity(self.code.items.len + 8); + return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 0, 0x00); + }, + else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), + } + } - // In x86, ADD has 2 operands, destination and source. - // Either one, but not both, can be a memory operand. - // Source operand can be an immediate, 8 bits or 32 bits. - // So, if either one of the operands dies with this instruction, we can use it - // as the result MCValue. - var dst_mcv: MCValue = undefined; - var src_mcv: MCValue = undefined; - if (inst.base.operandDies(0)) { - // LHS dies; use it as the destination. - dst_mcv = try self.resolveInst(inst.args.lhs); - // Both operands cannot be memory. - if (dst_mcv.isMemory()) { - src_mcv = try self.resolveInstImmOrReg(inst.args.rhs); - } else { - src_mcv = try self.resolveInst(inst.args.rhs); - } - } else if (inst.base.operandDies(1)) { - // RHS dies; use it as the destination. - dst_mcv = try self.resolveInst(inst.args.rhs); - // Both operands cannot be memory. - if (dst_mcv.isMemory()) { - src_mcv = try self.resolveInstImmOrReg(inst.args.lhs); - } else { - src_mcv = try self.resolveInst(inst.args.lhs); - } - } else { - const lhs = try self.resolveInst(inst.args.lhs); - const rhs = try self.resolveInst(inst.args.rhs); - if (lhs.isMemory()) { - dst_mcv = try self.copyToNewRegister(inst.base.src, lhs); - src_mcv = rhs; - } else { - dst_mcv = try self.copyToNewRegister(inst.base.src, rhs); - src_mcv = lhs; - } + fn genSub(self: *Function, inst: *ir.Inst.Sub, comptime arch: std.Target.Cpu.Arch) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; + switch (arch) { + .x86_64 => { + return try self.genX8664BinMath(&inst.base, inst.args.lhs, inst.args.rhs, 5, 0x28); + }, + else => return self.fail(inst.base.src, "TODO implement sub for {}", .{self.target.cpu.arch}), + } + } + + /// ADD, SUB + fn genX8664BinMath(self: *Function, inst: *ir.Inst, op_lhs: *ir.Inst, op_rhs: *ir.Inst, opx: u8, mr: u8) !MCValue { + try self.code.ensureCapacity(self.code.items.len + 8); + + const lhs = try self.resolveInst(op_lhs); + const rhs = try self.resolveInst(op_rhs); + + // There are 2 operands, destination and source. + // Either one, but not both, can be a memory operand. + // Source operand can be an immediate, 8 bits or 32 bits. + // So, if either one of the operands dies with this instruction, we can use it + // as the result MCValue. + var dst_mcv: MCValue = undefined; + var src_mcv: MCValue = undefined; + var src_inst: *ir.Inst = undefined; + if (inst.operandDies(0) and lhs.isMutable()) { + // LHS dies; use it as the destination. + // Both operands cannot be memory. + src_inst = op_rhs; + if (lhs.isMemory() and rhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(op_lhs); + src_mcv = rhs; + } else { + dst_mcv = lhs; + src_mcv = rhs; + } + } else if (inst.operandDies(1) and rhs.isMutable()) { + // RHS dies; use it as the destination. + // Both operands cannot be memory. + src_inst = op_lhs; + if (lhs.isMemory() and rhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(op_rhs); + src_mcv = lhs; + } else { + dst_mcv = rhs; + src_mcv = lhs; + } + } else { + if (lhs.isMemory()) { + dst_mcv = try self.copyToNewRegister(op_lhs); + src_mcv = rhs; + src_inst = op_rhs; + } else { + dst_mcv = try self.copyToNewRegister(op_rhs); + src_mcv = lhs; + src_inst = op_lhs; + } + } + // This instruction supports only signed 32-bit immediates at most. If the immediate + // value is larger than this, we put it in a register. + // A potential opportunity for future optimization here would be keeping track + // of the fact that the instruction is available both as an immediate + // and as a register. + switch (src_mcv) { + .immediate => |imm| { + if (imm > std.math.maxInt(u31)) { + src_mcv = try self.copyToNewRegister(src_inst); } - // x86 ADD supports only signed 32-bit immediates at most. If the immediate - // value is larger than this, we put it in a register. - // A potential opportunity for future optimization here would be keeping track - // of the fact that the instruction is available both as an immediate - // and as a register. + }, + else => {}, + } + + try self.genX8664BinMathCode(inst.src, dst_mcv, src_mcv, opx, mr); + + return dst_mcv; + } + + fn genX8664BinMathCode(self: *Function, src: usize, dst_mcv: MCValue, src_mcv: MCValue, opx: u8, mr: u8) !void { + switch (dst_mcv) { + .none => unreachable, + .dead, .unreach, .immediate => unreachable, + .compare_flag => unreachable, + .register => |dst_reg_usize| { + const dst_reg = @intToEnum(Reg(.x86_64), @intCast(u8, dst_reg_usize)); switch (src_mcv) { - .immediate => |imm| { - if (imm > std.math.maxInt(u31)) { - src_mcv = try self.copyToNewRegister(inst.base.src, src_mcv); - } - }, - else => {}, - } - - switch (dst_mcv) { .none => unreachable, - .dead, .unreach, .immediate => unreachable, - .register => |dst_reg_usize| { - const dst_reg = @intToEnum(Reg(arch), @intCast(@TagType(Reg(arch)), dst_reg_usize)); - switch (src_mcv) { - .none => unreachable, - .dead, .unreach => unreachable, - .register => |src_reg_usize| { - const src_reg = @intToEnum(Reg(arch), @intCast(@TagType(Reg(arch)), src_reg_usize)); - self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); - }, - .immediate => |imm| { - const imm32 = @intCast(u31, imm); // We handle this case above. - // 81 /0 id - if (imm32 <= std.math.maxInt(u7)) { - self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x83, 0xC0 | @as(u8, dst_reg.id() & 0b111), @intCast(u8, imm32)}); - } else { - self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); - self.code.appendSliceAssumeCapacity(&[_]u8{ 0x81, 0xC0 | @as(u8, dst_reg.id() & 0b111) }); - std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); - } - }, - .embedded_in_code, .memory, .stack_offset => { - return self.fail(inst.base.src, "TODO implement x86 add source memory", .{}); - }, + .dead, .unreach => unreachable, + .register => |src_reg_usize| { + const src_reg = @intToEnum(Reg(.x86_64), @intCast(u8, src_reg_usize)); + self.rex(.{ .b = dst_reg.isExtended(), .r = src_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ mr + 0x1, 0xC0 | (@as(u8, src_reg.id() & 0b111) << 3) | @as(u8, dst_reg.id() & 0b111) }); + }, + .immediate => |imm| { + const imm32 = @intCast(u31, imm); // This case must be handled before calling genX8664BinMathCode. + // 81 /opx id + if (imm32 <= std.math.maxInt(u7)) { + self.rex(.{ .b = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x83, + 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), + @intCast(u8, imm32), + }); + } else { + self.rex(.{ .r = dst_reg.isExtended(), .w = dst_reg.size() == 64 }); + self.code.appendSliceAssumeCapacity(&[_]u8{ + 0x81, + 0xC0 | (opx << 3) | @truncate(u3, dst_reg.id()), + }); + std.mem.writeIntLittle(u32, self.code.addManyAsArrayAssumeCapacity(4), imm32); } }, .embedded_in_code, .memory, .stack_offset => { - return self.fail(inst.base.src, "TODO implement x86 add destination memory", .{}); + 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", .{}); }, } - return dst_mcv; }, - else => return self.fail(inst.base.src, "TODO implement add for {}", .{self.target.cpu.arch}), + .embedded_in_code, .memory, .stack_offset => { + return self.fail(src, "TODO implement x86 ADD/SUB/CMP destination memory", .{}); + }, } } @@ -550,7 +619,29 @@ const Function = struct { } fn genCmp(self: *Function, inst: *ir.Inst.Cmp, comptime arch: std.Target.Cpu.Arch) !MCValue { + // No side effects, so if it's unreferenced, do nothing. + if (inst.base.isUnused()) + return MCValue.dead; switch (arch) { + .x86_64 => { + try self.code.ensureCapacity(self.code.items.len + 8); + + const lhs = try self.resolveInst(inst.args.lhs); + const rhs = try self.resolveInst(inst.args.rhs); + + // There are 2 operands, destination and source. + // Either one, but not both, can be a memory operand. + // Source operand can be an immediate, 8 bits or 32 bits. + const dst_mcv = if (lhs.isImmediate() or (lhs.isMemory() and rhs.isMemory())) + try self.copyToNewRegister(inst.args.lhs) + else + lhs; + // This instruction supports only signed 32-bit immediates at most. + 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}; + }, else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), } } @@ -668,6 +759,9 @@ const Function = struct { .dead => unreachable, .none => unreachable, .unreach => unreachable, + .compare_flag => |op| { + return self.fail(src, "TODO set register with compare flag value", .{}); + }, .immediate => |x| { if (reg.size() != 64) { return self.fail(src, "TODO decide whether to implement non-64-bit loads", .{}); @@ -871,14 +965,35 @@ const Function = struct { } } - fn resolveInstImmOrReg(self: *Function, inst: *ir.Inst) !MCValue { - return self.fail(inst.src, "TODO implement resolveInstImmOrReg", .{}); + fn copyToNewRegister(self: *Function, inst: *ir.Inst) !MCValue { + return self.fail(inst.src, "TODO implement copyToNewRegister", .{}); } - fn copyToNewRegister(self: *Function, src: usize, mcv: MCValue) !MCValue { - return self.fail(src, "TODO implement copyToNewRegister", .{}); + /// If the MCValue is an immediate, and it does not fit within this type, + /// we put it in a register. + /// A potential opportunity for future optimization here would be keeping track + /// of the fact that the instruction is available both as an immediate + /// and as a register. + fn limitImmediateType(self: *Function, inst: *ir.Inst, comptime T: type) !MCValue { + const mcv = try self.resolveInst(inst); + const ti = @typeInfo(T).Int; + switch (mcv) { + .immediate => |imm| { + // This immediate is unsigned. + const U = @Type(.{ .Int = .{ + .bits = ti.bits - @boolToInt(ti.is_signed), + .is_signed = false, + }}); + if (imm >= std.math.maxInt(U)) { + return self.copyToNewRegister(inst); + } + }, + else => {}, + } + return mcv; } + fn genTypedValue(self: *Function, src: usize, typed_value: TypedValue) !MCValue { const ptr_bits = self.target.cpu.arch.ptrBitWidth(); const ptr_bytes: u64 = @divExact(ptr_bits, 8); diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig index 17e6a54725f8449938759e71e0fa0aec807e3e95..c8acc14545ce21dfb0d022732192a610ee0cb0e0 100644 --- a/src-self-hosted/ir.zig +++ b/src-self-hosted/ir.zig @@ -51,6 +51,7 @@ pub const Inst = struct { ptrtoint, ret, retvoid, + sub, unreach, /// Returns whether the instruction is one of the control flow "noreturn" types. @@ -64,12 +65,13 @@ pub const Inst = struct { .bitcast, .block, .breakpoint, + .call, .cmp, .constant, .isnonnull, .isnull, .ptrtoint, - .call, + .sub, => false, .condbr, @@ -242,6 +244,16 @@ pub const Inst = struct { args: void, }; + pub const Sub = struct { + pub const base_tag = Tag.sub; + base: Inst, + + args: struct { + lhs: *Inst, + rhs: *Inst, + }, + }; + pub const Unreach = struct { pub const base_tag = Tag.unreach; base: Inst, diff --git a/src-self-hosted/zir.zig b/src-self-hosted/zir.zig index 95548657a9c7af14a9f4d722f725171e2286646a..628773e09c57b80121f33c9d6797089648d99407 100644 --- a/src-self-hosted/zir.zig +++ b/src-self-hosted/zir.zig @@ -73,6 +73,7 @@ pub const Inst = struct { bitcast, elemptr, add, + sub, cmp, condbr, isnull, @@ -110,6 +111,7 @@ pub const Inst = struct { .bitcast => BitCast, .elemptr => ElemPtr, .add => Add, + .sub => Sub, .cmp => Cmp, .condbr => CondBr, .isnull => IsNull, @@ -512,6 +514,17 @@ pub const Inst = struct { kw_args: struct {}, }; + pub const Sub = struct { + pub const base_tag = Tag.sub; + base: Inst, + + positionals: struct { + lhs: *Inst, + rhs: *Inst, + }, + kw_args: struct {}, + }; + pub const Cmp = struct { pub const base_tag = Tag.cmp; base: Inst, @@ -677,6 +690,7 @@ pub const Module = struct { .bitcast => return self.writeInstToStreamGeneric(stream, .bitcast, inst, inst_table), .elemptr => return self.writeInstToStreamGeneric(stream, .elemptr, inst, inst_table), .add => return self.writeInstToStreamGeneric(stream, .add, inst, inst_table), + .sub => return self.writeInstToStreamGeneric(stream, .sub, inst, inst_table), .cmp => return self.writeInstToStreamGeneric(stream, .cmp, inst, inst_table), .condbr => return self.writeInstToStreamGeneric(stream, .condbr, inst, inst_table), .isnull => return self.writeInstToStreamGeneric(stream, .isnull, inst, inst_table), @@ -1542,6 +1556,22 @@ const EmitZIR = struct { }; break :blk &new_inst.base; }, + .sub => blk: { + const old_inst = inst.cast(ir.Inst.Sub).?; + const new_inst = try self.arena.allocator.create(Inst.Sub); + new_inst.* = .{ + .base = .{ + .src = inst.src, + .tag = Inst.Sub.base_tag, + }, + .positionals = .{ + .lhs = try self.resolveInst(new_body, old_inst.args.lhs), + .rhs = try self.resolveInst(new_body, old_inst.args.rhs), + }, + .kw_args = .{}, + }; + break :blk &new_inst.base; + }, .arg => blk: { const old_inst = inst.cast(ir.Inst.Arg).?; const new_inst = try self.arena.allocator.create(Inst.Arg); -- 2.54.0