authorgravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2022-11-27 16:22:01+01:00
committergravatar for robin@voetter.nlRobin Voetter <robin@voetter.nl> 2023-04-09 01:51:50+02:00
loge443b1bed7ccce45bf039a304fa8fa271f1faa0b
tree951e6558d8bdfdc6a2b85b5e3e654abdf9e9838d
parent205d928b24d46fd1fd5798c5d66e66ef25aa013f
signaturelock-open Commit is signed but in an unrecognized format.

spirv: switch_br lowering

Implements lowering switch statements in the SPIR-V backend.

2 files changed, 122 insertions(+), 0 deletions(-)

src/codegen/spirv.zig+117
...@@ -887,11 +887,13 @@ pub const DeclGen = struct {...@@ -887,11 +887,13 @@ pub const DeclGen = struct {
887 .breakpoint => return,887 .breakpoint => return,
888 .cond_br => return self.airCondBr(inst),888 .cond_br => return self.airCondBr(inst),
889 .constant => unreachable,889 .constant => unreachable,
890 .const_ty => unreachable,
890 .dbg_stmt => return self.airDbgStmt(inst),891 .dbg_stmt => return self.airDbgStmt(inst),
891 .loop => return self.airLoop(inst),892 .loop => return self.airLoop(inst),
892 .ret => return self.airRet(inst),893 .ret => return self.airRet(inst),
893 .ret_load => return self.airRetLoad(inst),894 .ret_load => return self.airRetLoad(inst),
894 .store => return self.airStore(inst),895 .store => return self.airStore(inst),
896 .switch_br => return self.airSwitchBr(inst),
895 .unreach => return self.airUnreach(),897 .unreach => return self.airUnreach(),
896898
897 .assembly => try self.airAssembly(inst),899 .assembly => try self.airAssembly(inst),
...@@ -1679,6 +1681,121 @@ pub const DeclGen = struct {...@@ -1679,6 +1681,121 @@ pub const DeclGen = struct {
1679 });1681 });
1680 }1682 }
16811683
1684 fn airSwitchBr(self: *DeclGen, inst: Air.Inst.Index) !void {
1685 const target = self.getTarget();
1686 const pl_op = self.air.instructions.items(.data)[inst].pl_op;
1687 const cond = try self.resolve(pl_op.operand);
1688 const cond_ty = self.air.typeOf(pl_op.operand);
1689 const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
1690
1691 const cond_words: u32 = switch (cond_ty.zigTypeTag()) {
1692 .Int => blk: {
1693 const bits = cond_ty.intInfo(target).bits;
1694 const backing_bits = self.backingIntBits(bits) orelse {
1695 return self.todo("implement composite int switch", .{});
1696 };
1697 break :blk if (backing_bits <= 32) 1 else 2;
1698 },
1699 .Enum => blk: {
1700 var buffer: Type.Payload.Bits = undefined;
1701 const int_ty = cond_ty.intTagType(&buffer);
1702 const int_info = int_ty.intInfo(target);
1703 const backing_bits = self.backingIntBits(int_info.bits) orelse {
1704 return self.todo("implement composite int switch", .{});
1705 };
1706 break :blk if (backing_bits <= 32) 1 else 2;
1707 },
1708 else => return self.todo("implement switch for type {s}", .{@tagName(cond_ty.zigTypeTag())}), // TODO: Figure out which types apply here, and work around them as we can only do integers.
1709 };
1710
1711 const num_cases = switch_br.data.cases_len;
1712
1713 // Compute the total number of arms that we need.
1714 // Zig switches are grouped by condition, so we need to loop through all of them
1715 const num_conditions = blk: {
1716 var extra_index: usize = switch_br.end;
1717 var case_i: u32 = 0;
1718 var num_conditions: u32 = 0;
1719 while (case_i < num_cases) : (case_i += 1) {
1720 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
1721 const case_body = self.air.extra[case.end + case.data.items_len ..][0..case.data.body_len];
1722 extra_index = case.end + case.data.items_len + case_body.len;
1723 num_conditions += case.data.items_len;
1724 }
1725 break :blk num_conditions;
1726 };
1727
1728 // First, pre-allocate the labels for the cases.
1729 const first_case_label = self.spv.allocIds(num_cases);
1730 // We always need the default case - if zig has none, we will generate unreachable there.
1731 const default = self.spv.allocId();
1732
1733 // Emit the instruction before generating the blocks.
1734 try self.func.body.emitRaw(self.spv.gpa, .OpSwitch, 2 + (cond_words + 1) * num_conditions);
1735 self.func.body.writeOperand(IdRef, cond);
1736 self.func.body.writeOperand(IdRef, default.toRef());
1737
1738 // Emit each of the cases
1739 {
1740 var extra_index: usize = switch_br.end;
1741 var case_i: u32 = 0;
1742 while (case_i < num_cases) : (case_i += 1) {
1743 // SPIR-V needs a literal here, which' width depends on the case condition.
1744 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
1745 const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
1746 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
1747 extra_index = case.end + case.data.items_len + case_body.len;
1748
1749 const label = IdRef{ .id = first_case_label.id + case_i };
1750
1751 for (items) |item| {
1752 const value = self.air.value(item) orelse {
1753 return self.todo("switch on runtime value???", .{});
1754 };
1755 const int_val = switch (cond_ty.zigTypeTag()) {
1756 .Int => if (cond_ty.isSignedInt()) @bitCast(u64, value.toSignedInt()) else value.toUnsignedInt(target),
1757 .Enum => blk: {
1758 var int_buffer: Value.Payload.U64 = undefined;
1759 // TODO: figure out of cond_ty is correct (something with enum literals)
1760 break :blk value.enumToInt(cond_ty, &int_buffer).toUnsignedInt(target); // TODO: composite integer constants
1761 },
1762 else => unreachable,
1763 };
1764 const int_lit: spec.LiteralContextDependentNumber = switch (cond_words) {
1765 1 => .{ .uint32 = @intCast(u32, int_val) },
1766 2 => .{ .uint64 = int_val },
1767 else => unreachable,
1768 };
1769 self.func.body.writeOperand(spec.LiteralContextDependentNumber, int_lit);
1770 self.func.body.writeOperand(IdRef, label);
1771 }
1772 }
1773 }
1774
1775 // Now, finally, we can start emitting each of the cases.
1776 var extra_index: usize = switch_br.end;
1777 var case_i: u32 = 0;
1778 while (case_i < num_cases) : (case_i += 1) {
1779 const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
1780 const items = @ptrCast([]const Air.Inst.Ref, self.air.extra[case.end..][0..case.data.items_len]);
1781 const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
1782 extra_index = case.end + case.data.items_len + case_body.len;
1783
1784 const label = IdResult{ .id = first_case_label.id + case_i };
1785
1786 try self.beginSpvBlock(label);
1787 try self.genBody(case_body);
1788 }
1789
1790 const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
1791 try self.beginSpvBlock(default);
1792 if (else_body.len != 0) {
1793 try self.genBody(else_body);
1794 } else {
1795 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
1796 }
1797 }
1798
1682 fn airUnreach(self: *DeclGen) !void {1799 fn airUnreach(self: *DeclGen) !void {
1683 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});1800 try self.func.body.emit(self.spv.gpa, .OpUnreachable, {});
1684 }1801 }
src/codegen/spirv/Module.zig+5
...@@ -132,6 +132,11 @@ pub fn allocId(self: *Module) spec.IdResult {...@@ -132,6 +132,11 @@ pub fn allocId(self: *Module) spec.IdResult {
132 return .{ .id = self.next_result_id };132 return .{ .id = self.next_result_id };
133}133}
134134
135pub fn allocIds(self: *Module, n: u32) spec.IdResult {
136 defer self.next_result_id += n;
137 return .{ .id = self.next_result_id };
138}
139
135pub fn idBound(self: Module) Word {140pub fn idBound(self: Module) Word {
136 return self.next_result_id;141 return self.next_result_id;
137}142}