| ... | ... | @@ -273,8 +273,22 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 273 | 273 | /// across each runtime branch upon joining. |
| 274 | 274 | branch_stack: *std.ArrayList(Branch), |
| 275 | 275 | |
| 276 | /// The key must be canonical register. |
| 277 | registers: std.AutoHashMapUnmanaged(Register, *ir.Inst) = .{}, |
| 278 | free_registers: FreeRegInt = math.maxInt(FreeRegInt), |
| 279 | /// Maps offset to what is stored there. |
| 280 | stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, |
| 281 | |
| 282 | /// Offset from the stack base, representing the end of the stack frame. |
| 283 | max_end_stack: u32 = 0, |
| 284 | /// Represents the current end stack offset. If there is no existing slot |
| 285 | /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`. |
| 286 | next_stack_offset: u32 = 0, |
| 287 | |
| 276 | 288 | const MCValue = union(enum) { |
| 277 | 289 | /// No runtime bits. `void` types, empty structs, u0, enums with 1 tag, etc. |
| 290 | /// TODO Look into deleting this tag and using `dead` instead, since every use |
| 291 | /// of MCValue.none should be instead looking at the type and noticing it is 0 bits. |
| 278 | 292 | none, |
| 279 | 293 | /// Control flow will not allow this value to be observed. |
| 280 | 294 | unreach, |
| ... | ... | @@ -346,71 +360,55 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 346 | 360 | |
| 347 | 361 | const Branch = struct { |
| 348 | 362 | inst_table: std.AutoHashMapUnmanaged(*ir.Inst, MCValue) = .{}, |
| 349 | | /// The key must be canonical register. |
| 350 | | registers: std.AutoHashMapUnmanaged(Register, RegisterAllocation) = .{}, |
| 351 | | free_registers: FreeRegInt = math.maxInt(FreeRegInt), |
| 352 | | |
| 353 | | /// Maps offset to what is stored there. |
| 354 | | stack: std.AutoHashMapUnmanaged(u32, StackAllocation) = .{}, |
| 355 | | /// Offset from the stack base, representing the end of the stack frame. |
| 356 | | max_end_stack: u32 = 0, |
| 357 | | /// Represents the current end stack offset. If there is no existing slot |
| 358 | | /// to place a new stack allocation, it goes here, and then bumps `max_end_stack`. |
| 359 | | next_stack_offset: u32 = 0, |
| 360 | | |
| 361 | | fn markRegUsed(self: *Branch, reg: Register) void { |
| 362 | | if (FreeRegInt == u0) return; |
| 363 | | const index = reg.allocIndex() orelse return; |
| 364 | | const ShiftInt = math.Log2Int(FreeRegInt); |
| 365 | | const shift = @intCast(ShiftInt, index); |
| 366 | | self.free_registers &= ~(@as(FreeRegInt, 1) << shift); |
| 367 | | } |
| 368 | | |
| 369 | | fn markRegFree(self: *Branch, reg: Register) void { |
| 370 | | if (FreeRegInt == u0) return; |
| 371 | | const index = reg.allocIndex() orelse return; |
| 372 | | const ShiftInt = math.Log2Int(FreeRegInt); |
| 373 | | const shift = @intCast(ShiftInt, index); |
| 374 | | self.free_registers |= @as(FreeRegInt, 1) << shift; |
| 375 | | } |
| 376 | | |
| 377 | | /// Before calling, must ensureCapacity + 1 on branch.registers. |
| 378 | | /// Returns `null` if all registers are allocated. |
| 379 | | fn allocReg(self: *Branch, inst: *ir.Inst) ?Register { |
| 380 | | const free_index = @ctz(FreeRegInt, self.free_registers); |
| 381 | | if (free_index >= callee_preserved_regs.len) { |
| 382 | | return null; |
| 383 | | } |
| 384 | | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 385 | | const reg = callee_preserved_regs[free_index]; |
| 386 | | self.registers.putAssumeCapacityNoClobber(reg, .{ .inst = inst }); |
| 387 | | log.debug("alloc {} => {*}", .{reg, inst}); |
| 388 | | return reg; |
| 389 | | } |
| 390 | | |
| 391 | | /// Does not track the register. |
| 392 | | fn findUnusedReg(self: *Branch) ?Register { |
| 393 | | const free_index = @ctz(FreeRegInt, self.free_registers); |
| 394 | | if (free_index >= callee_preserved_regs.len) { |
| 395 | | return null; |
| 396 | | } |
| 397 | | return callee_preserved_regs[free_index]; |
| 398 | | } |
| 399 | 363 | |
| 400 | 364 | fn deinit(self: *Branch, gpa: *Allocator) void { |
| 401 | 365 | self.inst_table.deinit(gpa); |
| 402 | | self.registers.deinit(gpa); |
| 403 | | self.stack.deinit(gpa); |
| 404 | 366 | self.* = undefined; |
| 405 | 367 | } |
| 406 | 368 | }; |
| 407 | 369 | |
| 408 | | const RegisterAllocation = struct { |
| 409 | | inst: *ir.Inst, |
| 410 | | }; |
| 370 | fn markRegUsed(self: *Self, reg: Register) void { |
| 371 | if (FreeRegInt == u0) return; |
| 372 | const index = reg.allocIndex() orelse return; |
| 373 | const ShiftInt = math.Log2Int(FreeRegInt); |
| 374 | const shift = @intCast(ShiftInt, index); |
| 375 | self.free_registers &= ~(@as(FreeRegInt, 1) << shift); |
| 376 | } |
| 377 | |
| 378 | fn markRegFree(self: *Self, reg: Register) void { |
| 379 | if (FreeRegInt == u0) return; |
| 380 | const index = reg.allocIndex() orelse return; |
| 381 | const ShiftInt = math.Log2Int(FreeRegInt); |
| 382 | const shift = @intCast(ShiftInt, index); |
| 383 | self.free_registers |= @as(FreeRegInt, 1) << shift; |
| 384 | } |
| 385 | |
| 386 | /// Before calling, must ensureCapacity + 1 on self.registers. |
| 387 | /// Returns `null` if all registers are allocated. |
| 388 | fn allocReg(self: *Self, inst: *ir.Inst) ?Register { |
| 389 | const free_index = @ctz(FreeRegInt, self.free_registers); |
| 390 | if (free_index >= callee_preserved_regs.len) { |
| 391 | return null; |
| 392 | } |
| 393 | self.free_registers &= ~(@as(FreeRegInt, 1) << free_index); |
| 394 | const reg = callee_preserved_regs[free_index]; |
| 395 | self.registers.putAssumeCapacityNoClobber(reg, inst); |
| 396 | log.debug("alloc {} => {*}", .{reg, inst}); |
| 397 | return reg; |
| 398 | } |
| 399 | |
| 400 | /// Does not track the register. |
| 401 | fn findUnusedReg(self: *Self) ?Register { |
| 402 | const free_index = @ctz(FreeRegInt, self.free_registers); |
| 403 | if (free_index >= callee_preserved_regs.len) { |
| 404 | return null; |
| 405 | } |
| 406 | return callee_preserved_regs[free_index]; |
| 407 | } |
| 411 | 408 | |
| 412 | 409 | const StackAllocation = struct { |
| 413 | 410 | inst: *ir.Inst, |
| 411 | /// TODO do we need size? should be determined by inst.ty.abiSize() |
| 414 | 412 | size: u32, |
| 415 | 413 | }; |
| 416 | 414 | |
| ... | ... | @@ -435,8 +433,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 435 | 433 | branch_stack.items[0].deinit(bin_file.allocator); |
| 436 | 434 | branch_stack.deinit(); |
| 437 | 435 | } |
| 438 | | const branch = try branch_stack.addOne(); |
| 439 | | branch.* = .{}; |
| 436 | try branch_stack.append(.{}); |
| 440 | 437 | |
| 441 | 438 | const src_data: struct {lbrace_src: usize, rbrace_src: usize, source: []const u8} = blk: { |
| 442 | 439 | if (module_fn.owner_decl.scope.cast(Module.Scope.File)) |scope_file| { |
| ... | ... | @@ -476,6 +473,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 476 | 473 | .rbrace_src = src_data.rbrace_src, |
| 477 | 474 | .source = src_data.source, |
| 478 | 475 | }; |
| 476 | defer function.registers.deinit(bin_file.allocator); |
| 477 | defer function.stack.deinit(bin_file.allocator); |
| 479 | 478 | defer function.exitlude_jump_relocs.deinit(bin_file.allocator); |
| 480 | 479 | |
| 481 | 480 | var call_info = function.resolveCallingConventionValues(src, fn_type) catch |err| switch (err) { |
| ... | ... | @@ -487,7 +486,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 487 | 486 | function.args = call_info.args; |
| 488 | 487 | function.ret_mcv = call_info.return_value; |
| 489 | 488 | function.stack_align = call_info.stack_align; |
| 490 | | branch.max_end_stack = call_info.stack_byte_count; |
| 489 | function.max_end_stack = call_info.stack_byte_count; |
| 491 | 490 | |
| 492 | 491 | function.gen() catch |err| switch (err) { |
| 493 | 492 | error.CodegenFail => return Result{ .fail = function.err_msg.? }, |
| ... | ... | @@ -523,7 +522,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 523 | 522 | try self.dbgSetPrologueEnd(); |
| 524 | 523 | try self.genBody(self.mod_fn.analysis.success); |
| 525 | 524 | |
| 526 | | const stack_end = self.branch_stack.items[0].max_end_stack; |
| 525 | const stack_end = self.max_end_stack; |
| 527 | 526 | if (stack_end > math.maxInt(i32)) |
| 528 | 527 | return self.fail(self.src, "too much stack used in call parameters", .{}); |
| 529 | 528 | const aligned_stack_end = mem.alignForward(stack_end, self.stack_align); |
| ... | ... | @@ -580,13 +579,15 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 580 | 579 | } |
| 581 | 580 | |
| 582 | 581 | fn genBody(self: *Self, body: ir.Body) InnerError!void { |
| 583 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 584 | | const inst_table = &branch.inst_table; |
| 585 | 582 | for (body.instructions) |inst| { |
| 583 | try self.ensureProcessDeathCapacity(@popCount(@TypeOf(inst.deaths), inst.deaths)); |
| 584 | |
| 586 | 585 | const mcv = try self.genFuncInst(inst); |
| 587 | | log.debug("{*} => {}", .{inst, mcv}); |
| 588 | | // TODO don't put void or dead things in here |
| 589 | | try inst_table.putNoClobber(self.gpa, inst, mcv); |
| 586 | if (!inst.isUnused()) { |
| 587 | log.debug("{*} => {}", .{inst, mcv}); |
| 588 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 589 | try branch.inst_table.putNoClobber(self.gpa, inst, mcv); |
| 590 | } |
| 590 | 591 | |
| 591 | 592 | var i: ir.Inst.DeathsBitIndex = 0; |
| 592 | 593 | while (inst.getOperand(i)) |operand| : (i += 1) { |
| ... | ... | @@ -628,21 +629,27 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 628 | 629 | self.dbg_line.appendAssumeCapacity(DW.LNS_copy); |
| 629 | 630 | } |
| 630 | 631 | |
| 632 | /// Asserts there is already capacity to insert into top branch inst_table. |
| 631 | 633 | fn processDeath(self: *Self, inst: *ir.Inst) void { |
| 634 | if (inst.tag == .constant) return; // Constants are immortal. |
| 635 | const prev_value = self.getResolvedInstValue(inst); |
| 632 | 636 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 633 | | const entry = branch.inst_table.getEntry(inst) orelse return; |
| 634 | | const prev_value = entry.value; |
| 635 | | entry.value = .dead; |
| 637 | branch.inst_table.putAssumeCapacity(inst, .dead); |
| 636 | 638 | switch (prev_value) { |
| 637 | 639 | .register => |reg| { |
| 638 | 640 | const canon_reg = toCanonicalReg(reg); |
| 639 | | _ = branch.registers.remove(canon_reg); |
| 640 | | branch.markRegFree(canon_reg); |
| 641 | _ = self.registers.remove(canon_reg); |
| 642 | self.markRegFree(canon_reg); |
| 641 | 643 | }, |
| 642 | 644 | else => {}, // TODO process stack allocation death |
| 643 | 645 | } |
| 644 | 646 | } |
| 645 | 647 | |
| 648 | fn ensureProcessDeathCapacity(self: *Self, additional_count: usize) !void { |
| 649 | const table = &self.branch_stack.items[self.branch_stack.items.len - 1].inst_table; |
| 650 | try table.ensureCapacity(self.gpa, table.items().len + additional_count); |
| 651 | } |
| 652 | |
| 646 | 653 | /// Adds a Type to the .debug_info at the current position. The bytes will be populated later, |
| 647 | 654 | /// after codegen for this symbol is done. |
| 648 | 655 | fn addDbgInfoTypeReloc(self: *Self, ty: Type) !void { |
| ... | ... | @@ -705,13 +712,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 705 | 712 | fn allocMem(self: *Self, inst: *ir.Inst, abi_size: u32, abi_align: u32) !u32 { |
| 706 | 713 | if (abi_align > self.stack_align) |
| 707 | 714 | self.stack_align = abi_align; |
| 708 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 709 | 715 | // TODO find a free slot instead of always appending |
| 710 | | const offset = mem.alignForwardGeneric(u32, branch.next_stack_offset, abi_align); |
| 711 | | branch.next_stack_offset = offset + abi_size; |
| 712 | | if (branch.next_stack_offset > branch.max_end_stack) |
| 713 | | branch.max_end_stack = branch.next_stack_offset; |
| 714 | | try branch.stack.putNoClobber(self.gpa, offset, .{ |
| 716 | const offset = mem.alignForwardGeneric(u32, self.next_stack_offset, abi_align); |
| 717 | self.next_stack_offset = offset + abi_size; |
| 718 | if (self.next_stack_offset > self.max_end_stack) |
| 719 | self.max_end_stack = self.next_stack_offset; |
| 720 | try self.stack.putNoClobber(self.gpa, offset, .{ |
| 715 | 721 | .inst = inst, |
| 716 | 722 | .size = abi_size, |
| 717 | 723 | }); |
| ... | ... | @@ -737,15 +743,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 737 | 743 | const abi_align = elem_ty.abiAlignment(self.target.*); |
| 738 | 744 | if (abi_align > self.stack_align) |
| 739 | 745 | self.stack_align = abi_align; |
| 740 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 741 | 746 | |
| 742 | 747 | if (reg_ok) { |
| 743 | 748 | // Make sure the type can fit in a register before we try to allocate one. |
| 744 | 749 | const ptr_bits = arch.ptrBitWidth(); |
| 745 | 750 | const ptr_bytes: u64 = @divExact(ptr_bits, 8); |
| 746 | 751 | if (abi_size <= ptr_bytes) { |
| 747 | | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 748 | | if (branch.allocReg(inst)) |reg| { |
| 752 | try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1); |
| 753 | if (self.allocReg(inst)) |reg| { |
| 749 | 754 | return MCValue{ .register = registerAlias(reg, abi_size) }; |
| 750 | 755 | } |
| 751 | 756 | } |
| ... | ... | @@ -758,20 +763,18 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 758 | 763 | /// allocated. A second call to `copyToTmpRegister` may return the same register. |
| 759 | 764 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 760 | 765 | fn copyToTmpRegister(self: *Self, src: usize, mcv: MCValue) !Register { |
| 761 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 762 | | |
| 763 | | const reg = branch.findUnusedReg() orelse b: { |
| 766 | const reg = self.findUnusedReg() orelse b: { |
| 764 | 767 | // We'll take over the first register. Move the instruction that was previously |
| 765 | 768 | // there to a stack allocation. |
| 766 | 769 | const reg = callee_preserved_regs[0]; |
| 767 | | const regs_entry = branch.registers.remove(reg).?; |
| 768 | | const spilled_inst = regs_entry.value.inst; |
| 770 | const regs_entry = self.registers.remove(reg).?; |
| 771 | const spilled_inst = regs_entry.value; |
| 769 | 772 | |
| 770 | 773 | const stack_mcv = try self.allocRegOrMem(spilled_inst, false); |
| 771 | | const inst_entry = branch.inst_table.getEntry(spilled_inst).?; |
| 772 | | const reg_mcv = inst_entry.value; |
| 774 | const reg_mcv = self.getResolvedInstValue(spilled_inst); |
| 773 | 775 | assert(reg == toCanonicalReg(reg_mcv.register)); |
| 774 | | inst_entry.value = stack_mcv; |
| 776 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 777 | try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv); |
| 775 | 778 | try self.genSetStack(src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv); |
| 776 | 779 | |
| 777 | 780 | break :b reg; |
| ... | ... | @@ -784,22 +787,21 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 784 | 787 | /// `reg_owner` is the instruction that gets associated with the register in the register table. |
| 785 | 788 | /// This can have a side effect of spilling instructions to the stack to free up a register. |
| 786 | 789 | fn copyToNewRegister(self: *Self, reg_owner: *ir.Inst, mcv: MCValue) !MCValue { |
| 787 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 788 | | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 790 | try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1); |
| 789 | 791 | |
| 790 | | const reg = branch.allocReg(reg_owner) orelse b: { |
| 792 | const reg = self.allocReg(reg_owner) orelse b: { |
| 791 | 793 | // We'll take over the first register. Move the instruction that was previously |
| 792 | 794 | // there to a stack allocation. |
| 793 | 795 | const reg = callee_preserved_regs[0]; |
| 794 | | const regs_entry = branch.registers.getEntry(reg).?; |
| 795 | | const spilled_inst = regs_entry.value.inst; |
| 796 | | regs_entry.value = .{ .inst = reg_owner }; |
| 796 | const regs_entry = self.registers.getEntry(reg).?; |
| 797 | const spilled_inst = regs_entry.value; |
| 798 | regs_entry.value = reg_owner; |
| 797 | 799 | |
| 798 | 800 | const stack_mcv = try self.allocRegOrMem(spilled_inst, false); |
| 799 | | const inst_entry = branch.inst_table.getEntry(spilled_inst).?; |
| 800 | | const reg_mcv = inst_entry.value; |
| 801 | const reg_mcv = self.getResolvedInstValue(spilled_inst); |
| 801 | 802 | assert(reg == toCanonicalReg(reg_mcv.register)); |
| 802 | | inst_entry.value = stack_mcv; |
| 803 | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 804 | try branch.inst_table.put(self.gpa, spilled_inst, stack_mcv); |
| 803 | 805 | try self.genSetStack(reg_owner.src, spilled_inst.ty, stack_mcv.stack_offset, reg_mcv); |
| 804 | 806 | |
| 805 | 807 | break :b reg; |
| ... | ... | @@ -934,9 +936,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 934 | 936 | .register => |reg| { |
| 935 | 937 | // If it's in the registers table, need to associate the register with the |
| 936 | 938 | // new instruction. |
| 937 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 938 | | if (branch.registers.getEntry(toCanonicalReg(reg))) |entry| { |
| 939 | | entry.value = .{ .inst = inst }; |
| 939 | if (self.registers.getEntry(toCanonicalReg(reg))) |entry| { |
| 940 | entry.value = inst; |
| 940 | 941 | } |
| 941 | 942 | log.debug("reusing {} => {*}", .{reg, inst}); |
| 942 | 943 | }, |
| ... | ... | @@ -1231,8 +1232,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1231 | 1232 | if (inst.base.isUnused()) |
| 1232 | 1233 | return MCValue.dead; |
| 1233 | 1234 | |
| 1234 | | const branch = &self.branch_stack.items[self.branch_stack.items.len - 1]; |
| 1235 | | try branch.registers.ensureCapacity(self.gpa, branch.registers.items().len + 1); |
| 1235 | try self.registers.ensureCapacity(self.gpa, self.registers.items().len + 1); |
| 1236 | 1236 | |
| 1237 | 1237 | const result = self.args[self.arg_index]; |
| 1238 | 1238 | self.arg_index += 1; |
| ... | ... | @@ -1240,8 +1240,8 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1240 | 1240 | const name_with_null = inst.name[0..mem.lenZ(inst.name) + 1]; |
| 1241 | 1241 | switch (result) { |
| 1242 | 1242 | .register => |reg| { |
| 1243 | | branch.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), .{ .inst = &inst.base }); |
| 1244 | | branch.markRegUsed(reg); |
| 1243 | self.registers.putAssumeCapacityNoClobber(toCanonicalReg(reg), &inst.base); |
| 1244 | self.markRegUsed(reg); |
| 1245 | 1245 | |
| 1246 | 1246 | try self.dbg_info.ensureCapacity(self.dbg_info.items.len + 8 + name_with_null.len); |
| 1247 | 1247 | self.dbg_info.appendAssumeCapacity(link.File.Elf.abbrev_parameter); |
| ... | ... | @@ -1536,13 +1536,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1536 | 1536 | |
| 1537 | 1537 | fn genDbgStmt(self: *Self, inst: *ir.Inst.NoOp) !MCValue { |
| 1538 | 1538 | try self.dbgAdvancePCAndLine(inst.base.src); |
| 1539 | | return MCValue.none; |
| 1539 | assert(inst.base.isUnused()); |
| 1540 | return MCValue.dead; |
| 1540 | 1541 | } |
| 1541 | 1542 | |
| 1542 | 1543 | fn genCondBr(self: *Self, inst: *ir.Inst.CondBr) !MCValue { |
| 1543 | 1544 | const cond = try self.resolveInst(inst.condition); |
| 1544 | 1545 | |
| 1545 | | // TODO deal with liveness / deaths condbr's then_entry_deaths and else_entry_deaths |
| 1546 | 1546 | const reloc: Reloc = switch (arch) { |
| 1547 | 1547 | .i386, .x86_64 => reloc: { |
| 1548 | 1548 | try self.code.ensureCapacity(self.code.items.len + 6); |
| ... | ... | @@ -1595,9 +1595,117 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1595 | 1595 | }, |
| 1596 | 1596 | else => return self.fail(inst.base.src, "TODO implement condbr {}", .{ self.target.cpu.arch }), |
| 1597 | 1597 | }; |
| 1598 | |
| 1599 | // Capture the state of register and stack allocation state so that we can revert to it. |
| 1600 | const parent_next_stack_offset = self.next_stack_offset; |
| 1601 | const parent_free_registers = self.free_registers; |
| 1602 | var parent_stack = try self.stack.clone(self.gpa); |
| 1603 | defer parent_stack.deinit(self.gpa); |
| 1604 | var parent_registers = try self.registers.clone(self.gpa); |
| 1605 | defer parent_registers.deinit(self.gpa); |
| 1606 | |
| 1607 | try self.branch_stack.append(.{}); |
| 1608 | |
| 1609 | const then_deaths = inst.thenDeaths(); |
| 1610 | try self.ensureProcessDeathCapacity(then_deaths.len); |
| 1611 | for (then_deaths) |operand| { |
| 1612 | self.processDeath(operand); |
| 1613 | } |
| 1598 | 1614 | try self.genBody(inst.then_body); |
| 1615 | |
| 1616 | // Revert to the previous register and stack allocation state. |
| 1617 | |
| 1618 | var saved_then_branch = self.branch_stack.pop(); |
| 1619 | defer saved_then_branch.deinit(self.gpa); |
| 1620 | |
| 1621 | self.registers.deinit(self.gpa); |
| 1622 | self.registers = parent_registers; |
| 1623 | parent_registers = .{}; |
| 1624 | |
| 1625 | self.stack.deinit(self.gpa); |
| 1626 | self.stack = parent_stack; |
| 1627 | parent_stack = .{}; |
| 1628 | |
| 1629 | self.next_stack_offset = parent_next_stack_offset; |
| 1630 | self.free_registers = parent_free_registers; |
| 1631 | |
| 1599 | 1632 | try self.performReloc(inst.base.src, reloc); |
| 1633 | const else_branch = self.branch_stack.addOneAssumeCapacity(); |
| 1634 | else_branch.* = .{}; |
| 1635 | |
| 1636 | const else_deaths = inst.elseDeaths(); |
| 1637 | try self.ensureProcessDeathCapacity(else_deaths.len); |
| 1638 | for (else_deaths) |operand| { |
| 1639 | self.processDeath(operand); |
| 1640 | } |
| 1600 | 1641 | try self.genBody(inst.else_body); |
| 1642 | |
| 1643 | // At this point, each branch will possibly have conflicting values for where |
| 1644 | // each instruction is stored. They agree, however, on which instructions are alive/dead. |
| 1645 | // We use the first ("then") branch as canonical, and here emit |
| 1646 | // instructions into the second ("else") branch to make it conform. |
| 1647 | // We continue respect the data structure semantic guarantees of the else_branch so |
| 1648 | // that we can use all the code emitting abstractions. This is why at the bottom we |
| 1649 | // assert that parent_branch.free_registers equals the saved_then_branch.free_registers |
| 1650 | // rather than assigning it. |
| 1651 | const parent_branch = &self.branch_stack.items[self.branch_stack.items.len - 2]; |
| 1652 | try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len + |
| 1653 | else_branch.inst_table.items().len); |
| 1654 | for (else_branch.inst_table.items()) |else_entry| { |
| 1655 | const canon_mcv = if (saved_then_branch.inst_table.remove(else_entry.key)) |then_entry| blk: { |
| 1656 | // The instruction's MCValue is overridden in both branches. |
| 1657 | parent_branch.inst_table.putAssumeCapacity(else_entry.key, then_entry.value); |
| 1658 | if (else_entry.value == .dead) { |
| 1659 | assert(then_entry.value == .dead); |
| 1660 | continue; |
| 1661 | } |
| 1662 | break :blk then_entry.value; |
| 1663 | } else blk: { |
| 1664 | if (else_entry.value == .dead) |
| 1665 | continue; |
| 1666 | // The instruction is only overridden in the else branch. |
| 1667 | var i: usize = self.branch_stack.items.len - 2; |
| 1668 | while (true) { |
| 1669 | i -= 1; |
| 1670 | if (self.branch_stack.items[i].inst_table.get(else_entry.key)) |mcv| { |
| 1671 | assert(mcv != .dead); |
| 1672 | break :blk mcv; |
| 1673 | } |
| 1674 | } |
| 1675 | }; |
| 1676 | log.debug("consolidating else_entry {*} {}=>{}", .{else_entry.key, else_entry.value, canon_mcv}); |
| 1677 | // TODO make sure the destination stack offset / register does not already have something |
| 1678 | // going on there. |
| 1679 | try self.setRegOrMem(inst.base.src, else_entry.key.ty, canon_mcv, else_entry.value); |
| 1680 | // TODO track the new register / stack allocation |
| 1681 | } |
| 1682 | try parent_branch.inst_table.ensureCapacity(self.gpa, parent_branch.inst_table.items().len + |
| 1683 | saved_then_branch.inst_table.items().len); |
| 1684 | for (saved_then_branch.inst_table.items()) |then_entry| { |
| 1685 | // We already deleted the items from this table that matched the else_branch. |
| 1686 | // So these are all instructions that are only overridden in the then branch. |
| 1687 | parent_branch.inst_table.putAssumeCapacity(then_entry.key, then_entry.value); |
| 1688 | if (then_entry.value == .dead) |
| 1689 | continue; |
| 1690 | const parent_mcv = blk: { |
| 1691 | var i: usize = self.branch_stack.items.len - 2; |
| 1692 | while (true) { |
| 1693 | i -= 1; |
| 1694 | if (self.branch_stack.items[i].inst_table.get(then_entry.key)) |mcv| { |
| 1695 | assert(mcv != .dead); |
| 1696 | break :blk mcv; |
| 1697 | } |
| 1698 | } |
| 1699 | }; |
| 1700 | log.debug("consolidating then_entry {*} {}=>{}", .{then_entry.key, parent_mcv, then_entry.value}); |
| 1701 | // TODO make sure the destination stack offset / register does not already have something |
| 1702 | // going on there. |
| 1703 | try self.setRegOrMem(inst.base.src, then_entry.key.ty, parent_mcv, then_entry.value); |
| 1704 | // TODO track the new register / stack allocation |
| 1705 | } |
| 1706 | |
| 1707 | self.branch_stack.pop().deinit(self.gpa); |
| 1708 | |
| 1601 | 1709 | return MCValue.unreach; |
| 1602 | 1710 | } |
| 1603 | 1711 | |
| ... | ... | @@ -1671,11 +1779,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 1671 | 1779 | switch (reloc) { |
| 1672 | 1780 | .rel32 => |pos| { |
| 1673 | 1781 | const amt = self.code.items.len - (pos + 4); |
| 1674 | | // If it wouldn't jump at all, elide it. |
| 1675 | | if (amt == 0) { |
| 1676 | | self.code.items.len -= 5; |
| 1677 | | return; |
| 1678 | | } |
| 1782 | // Here it would be tempting to implement testing for amt == 0 and then elide the |
| 1783 | // jump. However, that will cause a problem because other jumps may assume that they |
| 1784 | // can jump to this code. Or maybe I didn't understand something when I was debugging. |
| 1785 | // It could be worth another look. Anyway, that's why that isn't done here. Probably the |
| 1786 | // best place to elide jumps will be in semantic analysis, by inlining blocks that only |
| 1787 | // only have 1 break instruction. |
| 1679 | 1788 | const s32_amt = math.cast(i32, amt) catch |
| 1680 | 1789 | return self.fail(src, "unable to perform relocation: jump too far", .{}); |
| 1681 | 1790 | mem.writeIntLittle(i32, self.code.items[pos..][0..4], s32_amt); |
| ... | ... | @@ -2280,8 +2389,12 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2280 | 2389 | } |
| 2281 | 2390 | |
| 2282 | 2391 | fn resolveInst(self: *Self, inst: *ir.Inst) !MCValue { |
| 2392 | // If the type has no codegen bits, no need to store it. |
| 2393 | if (!inst.ty.hasCodeGenBits()) |
| 2394 | return MCValue.none; |
| 2395 | |
| 2283 | 2396 | // Constants have static lifetimes, so they are always memoized in the outer most table. |
| 2284 | | if (inst.cast(ir.Inst.Constant)) |const_inst| { |
| 2397 | if (inst.castTag(.constant)) |const_inst| { |
| 2285 | 2398 | const branch = &self.branch_stack.items[0]; |
| 2286 | 2399 | const gop = try branch.inst_table.getOrPut(self.gpa, inst); |
| 2287 | 2400 | if (!gop.found_existing) { |
| ... | ... | @@ -2290,6 +2403,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type { |
| 2290 | 2403 | return gop.entry.value; |
| 2291 | 2404 | } |
| 2292 | 2405 | |
| 2406 | return self.getResolvedInstValue(inst); |
| 2407 | } |
| 2408 | |
| 2409 | fn getResolvedInstValue(self: *Self, inst: *ir.Inst) MCValue { |
| 2293 | 2410 | // Treat each stack item as a "layer" on top of the previous one. |
| 2294 | 2411 | var i: usize = self.branch_stack.items.len; |
| 2295 | 2412 | while (true) { |