| ... | ... | @@ -12,6 +12,18 @@ const Target = std.Target; |
| 12 | 12 | const Allocator = mem.Allocator; |
| 13 | 13 | const trace = @import("tracy.zig").trace; |
| 14 | 14 | |
| 15 | /// The codegen-related data that is stored in `ir.Inst.Block` instructions. |
| 16 | pub const BlockData = struct { |
| 17 | relocs: std.ArrayListUnmanaged(Reloc) = .{}, |
| 18 | }; |
| 19 | |
| 20 | pub const Reloc = union(enum) { |
| 21 | /// The value is an offset into the `Function` `code` from the beginning. |
| 22 | /// To perform the reloc, write 32-bit signed little-endian integer |
| 23 | /// which is a relative jump, based on the address following the reloc. |
| 24 | rel32: usize, |
| 25 | }; |
| 26 | |
| 15 | 27 | pub const Result = union(enum) { |
| 16 | 28 | /// The `code` parameter passed to `generateSymbol` has the value appended. |
| 17 | 29 | appended: void, |
| ... | ... | @@ -290,9 +302,12 @@ const Function = struct { |
| 290 | 302 | memory: u64, |
| 291 | 303 | /// The value is one of the stack variables. |
| 292 | 304 | stack_offset: u64, |
| 293 | | /// The value is the compare flag, with this operator |
| 294 | | /// applied on top of it. |
| 295 | | compare_flag: std.math.CompareOperator, |
| 305 | /// The value is in the compare flags assuming an unsigned operation, |
| 306 | /// with this operator applied on top of it. |
| 307 | compare_flags_unsigned: std.math.CompareOperator, |
| 308 | /// The value is in the compare flags assuming a signed operation, |
| 309 | /// with this operator applied on top of it. |
| 310 | compare_flags_signed: std.math.CompareOperator, |
| 296 | 311 | |
| 297 | 312 | fn isMemory(mcv: MCValue) bool { |
| 298 | 313 | return switch (mcv) { |
| ... | ... | @@ -317,7 +332,8 @@ const Function = struct { |
| 317 | 332 | .immediate, |
| 318 | 333 | .embedded_in_code, |
| 319 | 334 | .memory, |
| 320 | | .compare_flag, |
| 335 | .compare_flags_unsigned, |
| 336 | .compare_flags_signed, |
| 321 | 337 | => false, |
| 322 | 338 | |
| 323 | 339 | .register, |
| ... | ... | @@ -513,7 +529,8 @@ const Function = struct { |
| 513 | 529 | switch (dst_mcv) { |
| 514 | 530 | .none => unreachable, |
| 515 | 531 | .dead, .unreach, .immediate => unreachable, |
| 516 | | .compare_flag => unreachable, |
| 532 | .compare_flags_unsigned => unreachable, |
| 533 | .compare_flags_signed => unreachable, |
| 517 | 534 | .register => |dst_reg_usize| { |
| 518 | 535 | const dst_reg = @intToEnum(Reg(.x86_64), @intCast(u8, dst_reg_usize)); |
| 519 | 536 | switch (src_mcv) { |
| ... | ... | @@ -546,8 +563,11 @@ const Function = struct { |
| 546 | 563 | .embedded_in_code, .memory, .stack_offset => { |
| 547 | 564 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source memory", .{}); |
| 548 | 565 | }, |
| 549 | | .compare_flag => { |
| 550 | | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag", .{}); |
| 566 | .compare_flags_unsigned => { |
| 567 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (unsigned)", .{}); |
| 568 | }, |
| 569 | .compare_flags_signed => { |
| 570 | return self.fail(src, "TODO implement x86 ADD/SUB/CMP source compare flag (signed)", .{}); |
| 551 | 571 | }, |
| 552 | 572 | } |
| 553 | 573 | }, |
| ... | ... | @@ -650,7 +670,12 @@ const Function = struct { |
| 650 | 670 | const src_mcv = try self.limitImmediateType(inst.args.rhs, i32); |
| 651 | 671 | |
| 652 | 672 | try self.genX8664BinMathCode(inst.base.src, dst_mcv, src_mcv, 7, 0x38); |
| 653 | | return MCValue{.compare_flag = inst.args.op}; |
| 673 | const info = inst.args.lhs.ty.intInfo(self.target.*); |
| 674 | if (info.signed) { |
| 675 | return MCValue{.compare_flags_signed = inst.args.op}; |
| 676 | } else { |
| 677 | return MCValue{.compare_flags_unsigned = inst.args.op}; |
| 678 | } |
| 654 | 679 | }, |
| 655 | 680 | else => return self.fail(inst.base.src, "TODO implement cmp for {}", .{self.target.cpu.arch}), |
| 656 | 681 | } |
| ... | ... | @@ -658,8 +683,34 @@ const Function = struct { |
| 658 | 683 | |
| 659 | 684 | fn genCondBr(self: *Function, inst: *ir.Inst.CondBr, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 660 | 685 | switch (arch) { |
| 686 | .i386, .x86_64 => { |
| 687 | try self.code.ensureCapacity(self.code.items.len + 6); |
| 688 | |
| 689 | const cond = try self.resolveInst(inst.args.condition); |
| 690 | switch (cond) { |
| 691 | .compare_flags_signed => |cmp_op| { |
| 692 | // Here we map to the opposite opcode because the jump is to the false branch. |
| 693 | const opcode: u8 = switch (cmp_op) { |
| 694 | .gte => 0x8c, |
| 695 | .gt => 0x8e, |
| 696 | .neq => 0x84, |
| 697 | .lt => 0x8d, |
| 698 | .lte => 0x8f, |
| 699 | .eq => 0x85, |
| 700 | }; |
| 701 | self.code.appendSliceAssumeCapacity(&[_]u8{0x0f, opcode}); |
| 702 | const reloc = Reloc{ .rel32 = self.code.items.len }; |
| 703 | self.code.items.len += 4; |
| 704 | try self.genBody(inst.args.true_body, arch); |
| 705 | try self.performReloc(inst.base.src, reloc); |
| 706 | try self.genBody(inst.args.false_body, arch); |
| 707 | }, |
| 708 | else => return self.fail(inst.base.src, "TODO implement condbr {} when condition not already in the compare flags", .{self.target.cpu.arch}), |
| 709 | } |
| 710 | }, |
| 661 | 711 | else => return self.fail(inst.base.src, "TODO implement condbr for {}", .{self.target.cpu.arch}), |
| 662 | 712 | } |
| 713 | return MCValue.unreach; |
| 663 | 714 | } |
| 664 | 715 | |
| 665 | 716 | fn genIsNull(self: *Function, inst: *ir.Inst.IsNull, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| ... | ... | @@ -676,33 +727,43 @@ const Function = struct { |
| 676 | 727 | } |
| 677 | 728 | } |
| 678 | 729 | |
| 679 | | fn genRelativeFwdJump(self: *Function, src: usize, comptime arch: std.Target.Cpu.Arch, amount: u32) !void { |
| 680 | | switch (arch) { |
| 681 | | .i386, .x86_64 => { |
| 682 | | // TODO x86 treats the operands as signed |
| 683 | | if (amount <= std.math.maxInt(u8)) { |
| 684 | | try self.code.resize(self.code.items.len + 2); |
| 685 | | self.code.items[self.code.items.len - 2] = 0xeb; |
| 686 | | self.code.items[self.code.items.len - 1] = @intCast(u8, amount); |
| 687 | | } else { |
| 688 | | try self.code.resize(self.code.items.len + 5); |
| 689 | | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 |
| 690 | | const imm_ptr = self.code.items[self.code.items.len - 4 ..][0..4]; |
| 691 | | mem.writeIntLittle(u32, imm_ptr, amount); |
| 692 | | } |
| 693 | | }, |
| 694 | | else => return self.fail(src, "TODO implement relative forward jump for {}", .{self.target.cpu.arch}), |
| 695 | | } |
| 696 | | } |
| 697 | | |
| 698 | 730 | fn genBlock(self: *Function, inst: *ir.Inst.Block, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 731 | if (inst.base.ty.hasCodeGenBits()) { |
| 732 | return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{}); |
| 733 | } |
| 699 | 734 | // A block is nothing but a setup to be able to jump to the end. |
| 735 | defer inst.codegen.relocs.deinit(self.gpa); |
| 700 | 736 | try self.genBody(inst.args.body, arch); |
| 701 | | return self.fail(inst.base.src, "TODO process jump relocs after block end", .{}); |
| 737 | |
| 738 | for (inst.codegen.relocs.items) |reloc| try self.performReloc(inst.base.src, reloc); |
| 739 | |
| 740 | return MCValue.none; |
| 741 | } |
| 742 | |
| 743 | fn performReloc(self: *Function, src: usize, reloc: Reloc) !void { |
| 744 | switch (reloc) { |
| 745 | .rel32 => |pos| { |
| 746 | const amt = self.code.items.len - (pos + 4); |
| 747 | const s32_amt = std.math.cast(i32, amt) catch |
| 748 | return self.fail(src, "unable to perform relocation: jump too far", .{}); |
| 749 | mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); |
| 750 | }, |
| 751 | } |
| 702 | 752 | } |
| 703 | 753 | |
| 704 | 754 | fn genBreakVoid(self: *Function, inst: *ir.Inst.BreakVoid, comptime arch: std.Target.Cpu.Arch) !MCValue { |
| 755 | // Emit a jump with a relocation. It will be patched up after the block ends. |
| 756 | try inst.args.block.codegen.relocs.ensureCapacity(self.gpa, inst.args.block.codegen.relocs.items.len + 1); |
| 757 | |
| 705 | 758 | switch (arch) { |
| 759 | .i386, .x86_64 => { |
| 760 | // TODO optimization opportunity: figure out when we can emit this as a 2 byte instruction |
| 761 | // which is available if the jump is 127 bytes or less forward. |
| 762 | try self.code.resize(self.code.items.len + 5); |
| 763 | self.code.items[self.code.items.len - 5] = 0xe9; // jmp rel32 |
| 764 | // Leave the jump offset undefined |
| 765 | inst.args.block.codegen.relocs.appendAssumeCapacity(.{ .rel32 = self.code.items.len - 4 }); |
| 766 | }, |
| 706 | 767 | else => return self.fail(inst.base.src, "TODO implement breakvoid for {}", .{self.target.cpu.arch}), |
| 707 | 768 | } |
| 708 | 769 | return .none; |
| ... | ... | @@ -776,8 +837,11 @@ const Function = struct { |
| 776 | 837 | .dead => unreachable, |
| 777 | 838 | .none => unreachable, |
| 778 | 839 | .unreach => unreachable, |
| 779 | | .compare_flag => |op| { |
| 780 | | return self.fail(src, "TODO set register with compare flag value", .{}); |
| 840 | .compare_flags_unsigned => |op| { |
| 841 | return self.fail(src, "TODO set register with compare flags value (unsigned)", .{}); |
| 842 | }, |
| 843 | .compare_flags_signed => |op| { |
| 844 | return self.fail(src, "TODO set register with compare flags value (signed)", .{}); |
| 781 | 845 | }, |
| 782 | 846 | .immediate => |x| { |
| 783 | 847 | if (reg.size() != 64) { |