aboutsummaryrefslogtreecommitdiff
path: root/src/codegen
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2024-09-04 18:31:28 -0700
committerGitHub <noreply@github.com>2024-09-04 18:31:28 -0700
commit3929cac154d71a3e19fd028fc67c1d1d15823ca2 (patch)
tree3ab01ff8d25313b57bbb485e30a09bc9947448f7 /src/codegen
parent7e31804870cac14063b2468f544fc77a4cbb616f (diff)
parent289c704b60c3e4b65bc00be55266b3f1c3fc27a3 (diff)
downloadzig-3929cac154d71a3e19fd028fc67c1d1d15823ca2.tar.gz
zig-3929cac154d71a3e19fd028fc67c1d1d15823ca2.zip
Merge pull request #21257 from mlugg/computed-goto-3
compiler: implement labeled switch/continue
Diffstat (limited to 'src/codegen')
-rw-r--r--src/codegen/c.zig244
-rw-r--r--src/codegen/llvm.zig553
-rw-r--r--src/codegen/llvm/Builder.zig97
-rw-r--r--src/codegen/llvm/ir.zig14
-rw-r--r--src/codegen/spirv.zig2
5 files changed, 753 insertions, 157 deletions
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index f3b8c7e72a..0ec5513b6f 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -321,6 +321,9 @@ pub const Function = struct {
/// by type alignment.
/// The value is whether the alloc needs to be emitted in the header.
allocs: std.AutoArrayHashMapUnmanaged(LocalIndex, bool) = .{},
+ /// Maps from `loop_switch_br` instructions to the allocated local used
+ /// for the switch cond. Dispatches should set this local to the new cond.
+ loop_switch_conds: std.AutoHashMapUnmanaged(Air.Inst.Index, LocalIndex) = .{},
fn resolveInst(f: *Function, ref: Air.Inst.Ref) !CValue {
const gop = try f.value_map.getOrPut(ref);
@@ -531,6 +534,7 @@ pub const Function = struct {
f.blocks.deinit(gpa);
f.value_map.deinit();
f.lazy_fns.deinit(gpa);
+ f.loop_switch_conds.deinit(gpa);
}
fn typeOf(f: *Function, inst: Air.Inst.Ref) Type {
@@ -3137,11 +3141,9 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.arg => try airArg(f, inst),
- .trap => try airTrap(f, f.object.writer()),
.breakpoint => try airBreakpoint(f.object.writer()),
.ret_addr => try airRetAddr(f, inst),
.frame_addr => try airFrameAddress(f, inst),
- .unreach => try airUnreach(f),
.fence => try airFence(f, inst),
.ptr_add => try airPtrAddSub(f, inst, '+'),
@@ -3248,21 +3250,13 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.alloc => try airAlloc(f, inst),
.ret_ptr => try airRetPtr(f, inst),
.assembly => try airAsm(f, inst),
- .block => try airBlock(f, inst),
.bitcast => try airBitcast(f, inst),
.intcast => try airIntCast(f, inst),
.trunc => try airTrunc(f, inst),
.int_from_bool => try airIntFromBool(f, inst),
.load => try airLoad(f, inst),
- .ret => try airRet(f, inst, false),
- .ret_safe => try airRet(f, inst, false), // TODO
- .ret_load => try airRet(f, inst, true),
.store => try airStore(f, inst, false),
.store_safe => try airStore(f, inst, true),
- .loop => try airLoop(f, inst),
- .cond_br => try airCondBr(f, inst),
- .br => try airBr(f, inst),
- .switch_br => try airSwitchBr(f, inst),
.struct_field_ptr => try airStructFieldPtr(f, inst),
.array_to_slice => try airArrayToSlice(f, inst),
.cmpxchg_weak => try airCmpxchg(f, inst, "weak"),
@@ -3296,14 +3290,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.try_ptr_cold => try airTryPtr(f, inst),
.dbg_stmt => try airDbgStmt(f, inst),
- .dbg_inline_block => try airDbgInlineBlock(f, inst),
.dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => try airDbgVar(f, inst),
- .call => try airCall(f, inst, .auto),
- .call_always_tail => .none,
- .call_never_tail => try airCall(f, inst, .never_tail),
- .call_never_inline => try airCall(f, inst, .never_inline),
-
.float_from_int,
.int_from_float,
.fptrunc,
@@ -3390,6 +3378,41 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
.work_group_size,
.work_group_id,
=> unreachable,
+
+ // Instructions that are known to always be `noreturn` based on their tag.
+ .br => return airBr(f, inst),
+ .repeat => return airRepeat(f, inst),
+ .switch_dispatch => return airSwitchDispatch(f, inst),
+ .cond_br => return airCondBr(f, inst),
+ .switch_br => return airSwitchBr(f, inst, false),
+ .loop_switch_br => return airSwitchBr(f, inst, true),
+ .loop => return airLoop(f, inst),
+ .ret => return airRet(f, inst, false),
+ .ret_safe => return airRet(f, inst, false), // TODO
+ .ret_load => return airRet(f, inst, true),
+ .trap => return airTrap(f, f.object.writer()),
+ .unreach => return airUnreach(f),
+
+ // Instructions which may be `noreturn`.
+ .block => res: {
+ const res = try airBlock(f, inst);
+ if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
+ break :res res;
+ },
+ .dbg_inline_block => res: {
+ const res = try airDbgInlineBlock(f, inst);
+ if (f.typeOfIndex(inst).isNoReturn(zcu)) return;
+ break :res res;
+ },
+ // TODO: calls should be in this category! The AIR we emit for them is a bit weird.
+ // The instruction has type `noreturn`, but there are instructions (and maybe a safety
+ // check) following nonetheless. The `unreachable` or safety check should be emitted by
+ // backends instead.
+ .call => try airCall(f, inst, .auto),
+ .call_always_tail => .none,
+ .call_never_tail => try airCall(f, inst, .never_tail),
+ .call_never_inline => try airCall(f, inst, .never_inline),
+
// zig fmt: on
};
if (result_value == .new_local) {
@@ -3401,6 +3424,7 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
else => result_value,
});
}
+ unreachable;
}
fn airSliceField(f: *Function, inst: Air.Inst.Index, is_ptr: bool, field_name: []const u8) !CValue {
@@ -3718,7 +3742,7 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
return local;
}
-fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
+fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !void {
const pt = f.object.dg.pt;
const zcu = pt.zcu;
const un_op = f.air.instructions.items(.data)[@intFromEnum(inst)].un_op;
@@ -3769,7 +3793,6 @@ fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue {
// Not even allowed to return void in a naked function.
if (!f.object.dg.is_naked_fn) try writer.writeAll("return;\n");
}
- return .none;
}
fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {
@@ -4741,7 +4764,7 @@ fn lowerTry(
return local;
}
-fn airBr(f: *Function, inst: Air.Inst.Index) !CValue {
+fn airBr(f: *Function, inst: Air.Inst.Index) !void {
const branch = f.air.instructions.items(.data)[@intFromEnum(inst)].br;
const block = f.blocks.get(branch.block_inst).?;
const result = block.result;
@@ -4761,7 +4784,52 @@ fn airBr(f: *Function, inst: Air.Inst.Index) !CValue {
}
try writer.print("goto zig_block_{d};\n", .{block.block_id});
- return .none;
+}
+
+fn airRepeat(f: *Function, inst: Air.Inst.Index) !void {
+ const repeat = f.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
+ const writer = f.object.writer();
+ try writer.print("goto zig_loop_{d};\n", .{@intFromEnum(repeat.loop_inst)});
+}
+
+fn airSwitchDispatch(f: *Function, inst: Air.Inst.Index) !void {
+ const pt = f.object.dg.pt;
+ const zcu = pt.zcu;
+ const br = f.air.instructions.items(.data)[@intFromEnum(inst)].br;
+ const writer = f.object.writer();
+
+ if (try f.air.value(br.operand, pt)) |cond_val| {
+ // Comptime-known dispatch. Iterate the cases to find the correct
+ // one, and branch directly to the corresponding case.
+ const switch_br = f.air.unwrapSwitch(br.block_inst);
+ var it = switch_br.iterateCases();
+ const target_case_idx: u32 = target: while (it.next()) |case| {
+ for (case.items) |item| {
+ const val = Value.fromInterned(item.toInterned().?);
+ if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
+ }
+ for (case.ranges) |range| {
+ const low = Value.fromInterned(range[0].toInterned().?);
+ const high = Value.fromInterned(range[1].toInterned().?);
+ if (cond_val.compareHetero(.gte, low, zcu) and
+ cond_val.compareHetero(.lte, high, zcu))
+ {
+ break :target case.idx;
+ }
+ }
+ } else switch_br.cases_len;
+ try writer.print("goto zig_switch_{d}_dispatch_{d};\n", .{ @intFromEnum(br.block_inst), target_case_idx });
+ return;
+ }
+
+ // Runtime-known dispatch. Set the switch condition, and branch back.
+ const cond = try f.resolveInst(br.operand);
+ const cond_local = f.loop_switch_conds.get(br.block_inst).?;
+ try f.writeCValue(writer, .{ .local = cond_local }, .Other);
+ try writer.writeAll(" = ");
+ try f.writeCValue(writer, cond, .Initializer);
+ try writer.writeAll(";\n");
+ try writer.print("goto zig_switch_{d}_loop;", .{@intFromEnum(br.block_inst)});
}
fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
@@ -4889,12 +4957,10 @@ fn bitcast(f: *Function, dest_ty: Type, operand: CValue, operand_ty: Type) !CVal
return local;
}
-fn airTrap(f: *Function, writer: anytype) !CValue {
+fn airTrap(f: *Function, writer: anytype) !void {
// Not even allowed to call trap in a naked function.
- if (f.object.dg.is_naked_fn) return .none;
-
+ if (f.object.dg.is_naked_fn) return;
try writer.writeAll("zig_trap();\n");
- return .none;
}
fn airBreakpoint(writer: anytype) !CValue {
@@ -4933,28 +4999,27 @@ fn airFence(f: *Function, inst: Air.Inst.Index) !CValue {
return .none;
}
-fn airUnreach(f: *Function) !CValue {
+fn airUnreach(f: *Function) !void {
// Not even allowed to call unreachable in a naked function.
- if (f.object.dg.is_naked_fn) return .none;
-
+ if (f.object.dg.is_naked_fn) return;
try f.object.writer().writeAll("zig_unreachable();\n");
- return .none;
}
-fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue {
+fn airLoop(f: *Function, inst: Air.Inst.Index) !void {
const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const loop = f.air.extraData(Air.Block, ty_pl.payload);
const body: []const Air.Inst.Index = @ptrCast(f.air.extra[loop.end..][0..loop.data.body_len]);
const writer = f.object.writer();
- try writer.writeAll("for (;;) ");
- try genBody(f, body); // no need to restore state, we're noreturn
- try writer.writeByte('\n');
-
- return .none;
+ // `repeat` instructions matching this loop will branch to
+ // this label. Since we need a label for arbitrary `repeat`
+ // anyway, there's actually no need to use a "real" looping
+ // construct at all!
+ try writer.print("zig_loop_{d}:\n", .{@intFromEnum(inst)});
+ try genBodyInner(f, body); // no need to restore state, we're noreturn
}
-fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue {
+fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {
const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const cond = try f.resolveInst(pl_op.operand);
try reap(f, inst, &.{pl_op.operand});
@@ -4983,19 +5048,33 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue {
// instance) `br` to a block (label).
try genBodyInner(f, else_body);
-
- return .none;
}
-fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {
+fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
const pt = f.object.dg.pt;
const zcu = pt.zcu;
+ const gpa = f.object.dg.gpa;
const switch_br = f.air.unwrapSwitch(inst);
- const condition = try f.resolveInst(switch_br.operand);
+ const init_condition = try f.resolveInst(switch_br.operand);
try reap(f, inst, &.{switch_br.operand});
const condition_ty = f.typeOf(switch_br.operand);
const writer = f.object.writer();
+ // For dispatches, we will create a local alloc to contain the condition value.
+ // This may not result in optimal codegen for switch loops, but it minimizes the
+ // amount of C code we generate, which is probably more desirable here (and is simpler).
+ const condition = if (is_dispatch_loop) cond: {
+ const new_local = try f.allocLocal(inst, condition_ty);
+ try f.copyCValue(try f.ctypeFromType(condition_ty, .complete), new_local, init_condition);
+ try writer.print("zig_switch_{d}_loop:\n", .{@intFromEnum(inst)});
+ try f.loop_switch_conds.put(gpa, inst, new_local.new_local);
+ break :cond new_local;
+ } else init_condition;
+
+ defer if (is_dispatch_loop) {
+ assert(f.loop_switch_conds.remove(inst));
+ };
+
try writer.writeAll("switch (");
const lowered_condition_ty = if (condition_ty.toIntern() == .bool_type)
@@ -5013,23 +5092,29 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {
try writer.writeAll(") {");
f.object.indent_writer.pushIndent();
- const gpa = f.object.dg.gpa;
const liveness = try f.liveness.getSwitchBr(gpa, inst, switch_br.cases_len + 1);
defer gpa.free(liveness.deaths);
- // On the final iteration we do not need to fix any state. This is because, like in the `else`
- // branch of a `cond_br`, our parent has to do it for this entire body anyway.
- const last_case_i = switch_br.cases_len - @intFromBool(switch_br.else_body_len == 0);
-
+ var any_range_cases = false;
var it = switch_br.iterateCases();
while (it.next()) |case| {
+ if (case.ranges.len > 0) {
+ any_range_cases = true;
+ continue;
+ }
for (case.items) |item| {
try f.object.indent_writer.insertNewline();
try writer.writeAll("case ");
const item_value = try f.air.value(item, pt);
- if (item_value.?.getUnsignedInt(zcu)) |item_int| try writer.print("{}\n", .{
- try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int)),
- }) else {
+ // If `item_value` is a pointer with a known integer address, print the address
+ // with no cast to avoid a warning.
+ write_val: {
+ if (condition_ty.isPtrAtRuntime(zcu)) {
+ if (item_value.?.getUnsignedInt(zcu)) |item_int| {
+ try writer.print("{}", .{try f.fmtIntLiteral(try pt.intValue(lowered_condition_ty, item_int))});
+ break :write_val;
+ }
+ }
if (condition_ty.isPtrAtRuntime(zcu)) {
try writer.writeByte('(');
try f.renderType(writer, Type.usize);
@@ -5039,37 +5124,76 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue {
}
try writer.writeByte(':');
}
- try writer.writeByte(' ');
-
- if (case.idx != last_case_i) {
- try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, false);
- } else {
- for (liveness.deaths[case.idx]) |death| {
- try die(f, inst, death.toRef());
- }
- try genBody(f, case.body);
+ try writer.writeAll(" {\n");
+ f.object.indent_writer.pushIndent();
+ if (is_dispatch_loop) {
+ try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
}
+ try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
+ f.object.indent_writer.popIndent();
+ try writer.writeByte('}');
// The case body must be noreturn so we don't need to insert a break.
}
const else_body = it.elseBody();
try f.object.indent_writer.insertNewline();
+
+ try writer.writeAll("default: ");
+ if (any_range_cases) {
+ // We will iterate the cases again to handle those with ranges, and generate
+ // code using conditions rather than switch cases for such cases.
+ it = switch_br.iterateCases();
+ while (it.next()) |case| {
+ if (case.ranges.len == 0) continue; // handled above
+
+ try writer.writeAll("if (");
+ for (case.items, 0..) |item, item_i| {
+ if (item_i != 0) try writer.writeAll(" || ");
+ try f.writeCValue(writer, condition, .Other);
+ try writer.writeAll(" == ");
+ try f.object.dg.renderValue(writer, (try f.air.value(item, pt)).?, .Other);
+ }
+ for (case.ranges, 0..) |range, range_i| {
+ if (case.items.len != 0 or range_i != 0) try writer.writeAll(" || ");
+ // "(x >= lower && x <= upper)"
+ try writer.writeByte('(');
+ try f.writeCValue(writer, condition, .Other);
+ try writer.writeAll(" >= ");
+ try f.object.dg.renderValue(writer, (try f.air.value(range[0], pt)).?, .Other);
+ try writer.writeAll(" && ");
+ try f.writeCValue(writer, condition, .Other);
+ try writer.writeAll(" <= ");
+ try f.object.dg.renderValue(writer, (try f.air.value(range[1], pt)).?, .Other);
+ try writer.writeByte(')');
+ }
+ try writer.writeAll(") {\n");
+ f.object.indent_writer.pushIndent();
+ if (is_dispatch_loop) {
+ try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), case.idx });
+ }
+ try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
+ f.object.indent_writer.popIndent();
+ try writer.writeByte('}');
+ }
+ }
+ if (is_dispatch_loop) {
+ try writer.print("zig_switch_{d}_dispatch_{d}: ", .{ @intFromEnum(inst), switch_br.cases_len });
+ }
if (else_body.len > 0) {
- // Note that this must be the last case (i.e. the `last_case_i` case was not hit above)
+ // Note that this must be the last case, so we do not need to use `genBodyResolveState` since
+ // the parent block will do it (because the case body is noreturn).
for (liveness.deaths[liveness.deaths.len - 1]) |death| {
try die(f, inst, death.toRef());
}
- try writer.writeAll("default: ");
try genBody(f, else_body);
} else {
- try writer.writeAll("default: zig_unreachable();");
+ try writer.writeAll("zig_unreachable();");
}
try f.object.indent_writer.insertNewline();
f.object.indent_writer.popIndent();
try writer.writeAll("}\n");
- return .none;
}
fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool {
diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig
index 6915e9a2ac..a46d875b34 100644
--- a/src/codegen/llvm.zig
+++ b/src/codegen/llvm.zig
@@ -1739,6 +1739,8 @@ pub const Object = struct {
.arg_inline_index = 0,
.func_inst_table = .{},
.blocks = .{},
+ .loops = .{},
+ .switch_dispatch_info = .{},
.sync_scope = if (owner_mod.single_threaded) .singlethread else .system,
.file = file,
.scope = subprogram,
@@ -4860,6 +4862,13 @@ pub const FuncGen = struct {
breaks: *BreakList,
}),
+ /// Maps `loop` instructions to the bb to branch to to repeat the loop.
+ loops: std.AutoHashMapUnmanaged(Air.Inst.Index, Builder.Function.Block.Index),
+
+ /// Maps `loop_switch_br` instructions to the information required to lower
+ /// dispatches (`switch_dispatch` instructions).
+ switch_dispatch_info: std.AutoHashMapUnmanaged(Air.Inst.Index, SwitchDispatchInfo),
+
sync_scope: Builder.SyncScope,
const Fuzz = struct {
@@ -4872,6 +4881,33 @@ pub const FuncGen = struct {
}
};
+ const SwitchDispatchInfo = struct {
+ /// These are the blocks corresponding to each switch case.
+ /// The final element corresponds to the `else` case.
+ /// Slices allocated into `gpa`.
+ case_blocks: []Builder.Function.Block.Index,
+ /// This is `.none` if `jmp_table` is set, since we won't use a `switch` instruction to dispatch.
+ switch_weights: Builder.Function.Instruction.BrCond.Weights,
+ /// If not `null`, we have manually constructed a jump table to reach the desired block.
+ /// `table` can be used if the value is between `min` and `max` inclusive.
+ /// We perform this lowering manually to avoid some questionable behavior from LLVM.
+ /// See `airSwitchBr` for details.
+ jmp_table: ?JmpTable,
+
+ const JmpTable = struct {
+ min: Builder.Constant,
+ max: Builder.Constant,
+ in_bounds_hint: enum { none, unpredictable, likely, unlikely },
+ /// Pointer to the jump table itself, to be used with `indirectbr`.
+ /// The index into the jump table is the dispatch condition minus `min`.
+ /// The table values are `blockaddress` constants corresponding to blocks in `case_blocks`.
+ table: Builder.Constant,
+ /// `true` if `table` conatins a reference to the `else` block.
+ /// In this case, the `indirectbr` must include the `else` block in its target list.
+ table_includes_else: bool,
+ };
+ };
+
const BreakList = union {
list: std.MultiArrayList(struct {
bb: Builder.Function.Block.Index,
@@ -4886,6 +4922,12 @@ pub const FuncGen = struct {
self.wip.deinit();
self.func_inst_table.deinit(gpa);
self.blocks.deinit(gpa);
+ self.loops.deinit(gpa);
+ var it = self.switch_dispatch_info.valueIterator();
+ while (it.next()) |info| {
+ self.gpa.free(info.case_blocks);
+ }
+ self.switch_dispatch_info.deinit(gpa);
}
fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
@@ -5077,14 +5119,9 @@ pub const FuncGen = struct {
.arg => try self.airArg(inst),
.bitcast => try self.airBitCast(inst),
.int_from_bool => try self.airIntFromBool(inst),
- .block => try self.airBlock(inst),
- .br => try self.airBr(inst),
- .switch_br => try self.airSwitchBr(inst),
- .trap => try self.airTrap(inst),
.breakpoint => try self.airBreakpoint(inst),
.ret_addr => try self.airRetAddr(inst),
.frame_addr => try self.airFrameAddress(inst),
- .cond_br => try self.airCondBr(inst),
.@"try" => try self.airTry(body[i..], false),
.try_cold => try self.airTry(body[i..], true),
.try_ptr => try self.airTryPtr(inst, false),
@@ -5095,22 +5132,13 @@ pub const FuncGen = struct {
.fpext => try self.airFpext(inst),
.int_from_ptr => try self.airIntFromPtr(inst),
.load => try self.airLoad(body[i..]),
- .loop => try self.airLoop(inst),
.not => try self.airNot(inst),
- .ret => try self.airRet(inst, false),
- .ret_safe => try self.airRet(inst, true),
- .ret_load => try self.airRetLoad(inst),
.store => try self.airStore(inst, false),
.store_safe => try self.airStore(inst, true),
.assembly => try self.airAssembly(inst),
.slice_ptr => try self.airSliceField(inst, 0),
.slice_len => try self.airSliceField(inst, 1),
- .call => try self.airCall(inst, .auto),
- .call_always_tail => try self.airCall(inst, .always_tail),
- .call_never_tail => try self.airCall(inst, .never_tail),
- .call_never_inline => try self.airCall(inst, .never_inline),
-
.ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0),
.ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1),
@@ -5195,9 +5223,7 @@ pub const FuncGen = struct {
.inferred_alloc, .inferred_alloc_comptime => unreachable,
- .unreach => try self.airUnreach(inst),
.dbg_stmt => try self.airDbgStmt(inst),
- .dbg_inline_block => try self.airDbgInlineBlock(inst),
.dbg_var_ptr => try self.airDbgVarPtr(inst),
.dbg_var_val => try self.airDbgVarVal(inst, false),
.dbg_arg_inline => try self.airDbgVarVal(inst, true),
@@ -5210,10 +5236,52 @@ pub const FuncGen = struct {
.work_item_id => try self.airWorkItemId(inst),
.work_group_size => try self.airWorkGroupSize(inst),
.work_group_id => try self.airWorkGroupId(inst),
+
+ // Instructions that are known to always be `noreturn` based on their tag.
+ .br => return self.airBr(inst),
+ .repeat => return self.airRepeat(inst),
+ .switch_dispatch => return self.airSwitchDispatch(inst),
+ .cond_br => return self.airCondBr(inst),
+ .switch_br => return self.airSwitchBr(inst, false),
+ .loop_switch_br => return self.airSwitchBr(inst, true),
+ .loop => return self.airLoop(inst),
+ .ret => return self.airRet(inst, false),
+ .ret_safe => return self.airRet(inst, true),
+ .ret_load => return self.airRetLoad(inst),
+ .trap => return self.airTrap(inst),
+ .unreach => return self.airUnreach(inst),
+
+ // Instructions which may be `noreturn`.
+ .block => res: {
+ const res = try self.airBlock(inst);
+ if (self.typeOfIndex(inst).isNoReturn(zcu)) return;
+ break :res res;
+ },
+ .dbg_inline_block => res: {
+ const res = try self.airDbgInlineBlock(inst);
+ if (self.typeOfIndex(inst).isNoReturn(zcu)) return;
+ break :res res;
+ },
+ .call, .call_always_tail, .call_never_tail, .call_never_inline => |tag| res: {
+ const res = try self.airCall(inst, switch (tag) {
+ .call => .auto,
+ .call_always_tail => .always_tail,
+ .call_never_tail => .never_tail,
+ .call_never_inline => .never_inline,
+ else => unreachable,
+ });
+ // TODO: the AIR we emit for calls is a bit weird - the instruction has
+ // type `noreturn`, but there are instructions (and maybe a safety check) following
+ // nonetheless. The `unreachable` or safety check should be emitted by backends instead.
+ //if (self.typeOfIndex(inst).isNoReturn(mod)) return;
+ break :res res;
+ },
+
// zig fmt: on
};
if (val != .none) try self.func_inst_table.putNoClobber(self.gpa, inst.toRef(), val);
}
+ unreachable;
}
fn genBodyDebugScope(
@@ -5659,7 +5727,7 @@ pub const FuncGen = struct {
_ = try fg.wip.@"unreachable"();
}
- fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) !Builder.Value {
+ fn airRet(self: *FuncGen, inst: Air.Inst.Index, safety: bool) !void {
const o = self.ng.object;
const pt = o.pt;
const zcu = pt.zcu;
@@ -5694,7 +5762,7 @@ pub const FuncGen = struct {
try self.valgrindMarkUndef(self.ret_ptr, len);
}
_ = try self.wip.retVoid();
- return .none;
+ return;
}
const unwrapped_operand = operand.unwrap();
@@ -5703,12 +5771,12 @@ pub const FuncGen = struct {
// Return value was stored previously
if (unwrapped_operand == .instruction and unwrapped_ret == .instruction and unwrapped_operand.instruction == unwrapped_ret.instruction) {
_ = try self.wip.retVoid();
- return .none;
+ return;
}
try self.store(self.ret_ptr, ptr_ty, operand, .none);
_ = try self.wip.retVoid();
- return .none;
+ return;
}
const fn_info = zcu.typeToFunc(Type.fromInterned(ip.getNav(self.ng.nav_index).typeOf(ip))).?;
if (!ret_ty.hasRuntimeBitsIgnoreComptime(zcu)) {
@@ -5720,7 +5788,7 @@ pub const FuncGen = struct {
} else {
_ = try self.wip.retVoid();
}
- return .none;
+ return;
}
const abi_ret_ty = try lowerFnRetTy(o, fn_info);
@@ -5744,29 +5812,29 @@ pub const FuncGen = struct {
try self.valgrindMarkUndef(rp, len);
}
_ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, rp, alignment, ""));
- return .none;
+ return;
}
if (isByRef(ret_ty, zcu)) {
// operand is a pointer however self.ret_ptr is null so that means
// we need to return a value.
_ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, operand, alignment, ""));
- return .none;
+ return;
}
const llvm_ret_ty = operand.typeOfWip(&self.wip);
if (abi_ret_ty == llvm_ret_ty) {
_ = try self.wip.ret(operand);
- return .none;
+ return;
}
const rp = try self.buildAlloca(llvm_ret_ty, alignment);
_ = try self.wip.store(.normal, operand, rp, alignment);
_ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, rp, alignment, ""));
- return .none;
+ return;
}
- fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+ fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !void {
const o = self.ng.object;
const pt = o.pt;
const zcu = pt.zcu;
@@ -5784,17 +5852,17 @@ pub const FuncGen = struct {
} else {
_ = try self.wip.retVoid();
}
- return .none;
+ return;
}
if (self.ret_ptr != .none) {
_ = try self.wip.retVoid();
- return .none;
+ return;
}
const ptr = try self.resolveInst(un_op);
const abi_ret_ty = try lowerFnRetTy(o, fn_info);
const alignment = ret_ty.abiAlignment(zcu).toLlvm();
_ = try self.wip.ret(try self.wip.load(.normal, abi_ret_ty, ptr, alignment, ""));
- return .none;
+ return;
}
fn airCVaArg(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
@@ -6058,7 +6126,7 @@ pub const FuncGen = struct {
}
}
- fn airBr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+ fn airBr(self: *FuncGen, inst: Air.Inst.Index) !void {
const o = self.ng.object;
const zcu = o.pt.zcu;
const branch = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
@@ -6074,10 +6142,212 @@ pub const FuncGen = struct {
try block.breaks.list.append(self.gpa, .{ .bb = self.wip.cursor.block, .val = val });
} else block.breaks.len += 1;
_ = try self.wip.br(block.parent_bb);
- return .none;
}
- fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+ fn airRepeat(self: *FuncGen, inst: Air.Inst.Index) !void {
+ const repeat = self.air.instructions.items(.data)[@intFromEnum(inst)].repeat;
+ const loop_bb = self.loops.get(repeat.loop_inst).?;
+ loop_bb.ptr(&self.wip).incoming += 1;
+ _ = try self.wip.br(loop_bb);
+ }
+
+ fn lowerSwitchDispatch(
+ self: *FuncGen,
+ switch_inst: Air.Inst.Index,
+ cond_ref: Air.Inst.Ref,
+ dispatch_info: SwitchDispatchInfo,
+ ) !void {
+ const o = self.ng.object;
+ const pt = o.pt;
+ const zcu = pt.zcu;
+ const cond_ty = self.typeOf(cond_ref);
+ const switch_br = self.air.unwrapSwitch(switch_inst);
+
+ if (try self.air.value(cond_ref, pt)) |cond_val| {
+ // Comptime-known dispatch. Iterate the cases to find the correct
+ // one, and branch to the corresponding element of `case_blocks`.
+ var it = switch_br.iterateCases();
+ const target_case_idx = target: while (it.next()) |case| {
+ for (case.items) |item| {
+ const val = Value.fromInterned(item.toInterned().?);
+ if (cond_val.compareHetero(.eq, val, zcu)) break :target case.idx;
+ }
+ for (case.ranges) |range| {
+ const low = Value.fromInterned(range[0].toInterned().?);
+ const high = Value.fromInterned(range[1].toInterned().?);
+ if (cond_val.compareHetero(.gte, low, zcu) and
+ cond_val.compareHetero(.lte, high, zcu))
+ {
+ break :target case.idx;
+ }
+ }
+ } else dispatch_info.case_blocks.len - 1;
+ const target_block = dispatch_info.case_blocks[target_case_idx];
+ target_block.ptr(&self.wip).incoming += 1;
+ _ = try self.wip.br(target_block);
+ return;
+ }
+
+ // Runtime-known dispatch.
+ const cond = try self.resolveInst(cond_ref);
+
+ if (dispatch_info.jmp_table) |jmp_table| {
+ // We should use the constructed jump table.
+ // First, check the bounds to branch to the `else` case if needed.
+ const inbounds = try self.wip.bin(
+ .@"and",
+ try self.cmp(.normal, .gte, cond_ty, cond, jmp_table.min.toValue()),
+ try self.cmp(.normal, .lte, cond_ty, cond, jmp_table.max.toValue()),
+ "",
+ );
+ const jmp_table_block = try self.wip.block(1, "Then");
+ const else_block = dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1];
+ else_block.ptr(&self.wip).incoming += 1;
+ _ = try self.wip.brCond(inbounds, jmp_table_block, else_block, switch (jmp_table.in_bounds_hint) {
+ .none => .none,
+ .unpredictable => .unpredictable,
+ .likely => .then_likely,
+ .unlikely => .else_likely,
+ });
+
+ self.wip.cursor = .{ .block = jmp_table_block };
+
+ // Figure out the list of blocks we might branch to.
+ // This includes all case blocks, but it might not include the `else` block if
+ // the table is dense.
+ const target_blocks_len = dispatch_info.case_blocks.len - @intFromBool(!jmp_table.table_includes_else);
+ const target_blocks = dispatch_info.case_blocks[0..target_blocks_len];
+
+ // Make sure to cast the index to a usize so it's not treated as negative!
+ const table_index = try self.wip.cast(
+ .zext,
+ try self.wip.bin(.@"sub nuw", cond, jmp_table.min.toValue(), ""),
+ try o.lowerType(Type.usize),
+ "",
+ );
+ const target_ptr_ptr = try self.wip.gep(
+ .inbounds,
+ .ptr,
+ jmp_table.table.toValue(),
+ &.{table_index},
+ "",
+ );
+ const target_ptr = try self.wip.load(.normal, .ptr, target_ptr_ptr, .default, "");
+
+ // Do the branch!
+ _ = try self.wip.indirectbr(target_ptr, target_blocks);
+
+ // Mark all target blocks as having one more incoming branch.
+ for (target_blocks) |case_block| {
+ case_block.ptr(&self.wip).incoming += 1;
+ }
+
+ return;
+ }
+
+ // We must lower to an actual LLVM `switch` instruction.
+ // The switch prongs will correspond to our scalar cases. Ranges will
+ // be handled by conditional branches in the `else` prong.
+
+ const llvm_usize = try o.lowerType(Type.usize);
+ const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder))
+ try self.wip.cast(.ptrtoint, cond, llvm_usize, "")
+ else
+ cond;
+
+ const llvm_cases_len, const last_range_case = info: {
+ var llvm_cases_len: u32 = 0;
+ var last_range_case: ?u32 = null;
+ var it = switch_br.iterateCases();
+ while (it.next()) |case| {
+ if (case.ranges.len > 0) last_range_case = case.idx;
+ llvm_cases_len += @intCast(case.items.len);
+ }
+ break :info .{ llvm_cases_len, last_range_case };
+ };
+
+ // The `else` of the LLVM `switch` is the actual `else` prong only
+ // if there are no ranges. Otherwise, the `else` will have a
+ // conditional chain before the "true" `else` prong.
+ const llvm_else_block = if (last_range_case == null)
+ dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1]
+ else
+ try self.wip.block(0, "RangeTest");
+
+ llvm_else_block.ptr(&self.wip).incoming += 1;
+
+ var wip_switch = try self.wip.@"switch"(cond_int, llvm_else_block, llvm_cases_len, dispatch_info.switch_weights);
+ defer wip_switch.finish(&self.wip);
+
+ // Construct the actual cases. Set the cursor to the `else` block so
+ // we can construct ranges at the same time as scalar cases.
+ self.wip.cursor = .{ .block = llvm_else_block };
+
+ var it = switch_br.iterateCases();
+ while (it.next()) |case| {
+ const case_block = dispatch_info.case_blocks[case.idx];
+
+ for (case.items) |item| {
+ const llvm_item = (try self.resolveInst(item)).toConst().?;
+ const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder))
+ try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize)
+ else
+ llvm_item;
+ try wip_switch.addCase(llvm_int_item, case_block, &self.wip);
+ }
+ case_block.ptr(&self.wip).incoming += @intCast(case.items.len);
+
+ if (case.ranges.len == 0) continue;
+
+ // Add a conditional for the ranges, directing to the relevant bb.
+ // We don't need to consider `cold` branch hints since that information is stored
+ // in the target bb body, but we do care about likely/unlikely/unpredictable.
+
+ const hint = switch_br.getHint(case.idx);
+
+ var range_cond: ?Builder.Value = null;
+ for (case.ranges) |range| {
+ const llvm_min = try self.resolveInst(range[0]);
+ const llvm_max = try self.resolveInst(range[1]);
+ const cond_part = try self.wip.bin(
+ .@"and",
+ try self.cmp(.normal, .gte, cond_ty, cond, llvm_min),
+ try self.cmp(.normal, .lte, cond_ty, cond, llvm_max),
+ "",
+ );
+ if (range_cond) |prev| {
+ range_cond = try self.wip.bin(.@"or", prev, cond_part, "");
+ } else range_cond = cond_part;
+ }
+
+ // If the check fails, we either branch to the "true" `else` case,
+ // or to the next range condition.
+ const range_else_block = if (case.idx == last_range_case.?)
+ dispatch_info.case_blocks[dispatch_info.case_blocks.len - 1]
+ else
+ try self.wip.block(0, "RangeTest");
+
+ _ = try self.wip.brCond(range_cond.?, case_block, range_else_block, switch (hint) {
+ .none, .cold => .none,
+ .unpredictable => .unpredictable,
+ .likely => .then_likely,
+ .unlikely => .else_likely,
+ });
+ case_block.ptr(&self.wip).incoming += 1;
+ range_else_block.ptr(&self.wip).incoming += 1;
+
+ // Construct the next range conditional (if any) in the false branch.
+ self.wip.cursor = .{ .block = range_else_block };
+ }
+ }
+
+ fn airSwitchDispatch(self: *FuncGen, inst: Air.Inst.Index) !void {
+ const br = self.air.instructions.items(.data)[@intFromEnum(inst)].br;
+ const dispatch_info = self.switch_dispatch_info.get(br.block_inst).?;
+ return self.lowerSwitchDispatch(br.block_inst, br.operand, dispatch_info);
+ }
+
+ fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void {
const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op;
const cond = try self.resolveInst(pl_op.operand);
const extra = self.air.extraData(Air.CondBr, pl_op.payload);
@@ -6136,7 +6406,6 @@ pub const FuncGen = struct {
try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov);
// No need to reset the insert cursor since this instruction is noreturn.
- return .none;
}
fn airTry(self: *FuncGen, body_tail: []const Air.Inst.Index, err_cold: bool) !Builder.Value {
@@ -6242,28 +6511,123 @@ pub const FuncGen = struct {
return fg.wip.extractValue(err_union, &.{offset}, "");
}
- fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+ fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index, is_dispatch_loop: bool) !void {
const o = self.ng.object;
+ const zcu = o.pt.zcu;
const switch_br = self.air.unwrapSwitch(inst);
- const cond = try self.resolveInst(switch_br.operand);
+ // For `loop_switch_br`, we need these BBs prepared ahead of time to generate dispatches.
+ // For `switch_br`, they allow us to sometimes generate better IR by sharing a BB between
+ // scalar and range cases in the same prong.
+ // +1 for `else` case. This is not the same as the LLVM `else` prong, as that may first contain
+ // conditionals to handle ranges.
+ const case_blocks = try self.gpa.alloc(Builder.Function.Block.Index, switch_br.cases_len + 1);
+ defer self.gpa.free(case_blocks);
+ // We set incoming as 0 for now, and increment it as we construct dispatches.
+ for (case_blocks[0 .. case_blocks.len - 1]) |*b| b.* = try self.wip.block(0, "Case");
+ case_blocks[case_blocks.len - 1] = try self.wip.block(0, "Default");
+
+ // There's a special case here to manually generate a jump table in some cases.
+ //
+ // Labeled switch in Zig is intended to follow the "direct threading" pattern. We would ideally use a jump
+ // table, and each `continue` has its own indirect `jmp`, to allow the branch predictor to more accurately
+ // use data patterns to predict future dispatches. The problem, however, is that LLVM emits fascinatingly
+ // bad asm for this. Not only does it not share the jump table -- which we really need it to do to prevent
+ // destroying the cache -- but it also actually generates slightly different jump tables for each case,
+ // and *a separate conditional branch beforehand* to handle dispatching back to the case we're currently
+ // within(!!).
+ //
+ // This asm is really, really, not what we want. As such, we will construct the jump table manually where
+ // appropriate (the values are dense and relatively few), and use it when lowering dispatches.
+
+ const jmp_table: ?SwitchDispatchInfo.JmpTable = jmp_table: {
+ if (!is_dispatch_loop) break :jmp_table null;
+ // On a 64-bit target, 1024 pointers in our jump table is about 8K of pointers. This seems just
+ // about acceptable - it won't fill L1d cache on most CPUs.
+ const max_table_len = 1024;
+
+ const cond_ty = self.typeOf(switch_br.operand);
+ switch (cond_ty.zigTypeTag(zcu)) {
+ .bool, .pointer => break :jmp_table null,
+ .@"enum", .int, .error_set => {},
+ else => unreachable,
+ }
- const else_block = try self.wip.block(1, "Default");
- const llvm_usize = try o.lowerType(Type.usize);
- const cond_int = if (cond.typeOfWip(&self.wip).isPointer(&o.builder))
- try self.wip.cast(.ptrtoint, cond, llvm_usize, "")
- else
- cond;
+ if (cond_ty.intInfo(zcu).signedness == .signed) break :jmp_table null;
+
+ // Don't worry about the size of the type -- it's irrelevant, because the prong values could be fairly dense.
+ // If they are, then we will construct a jump table.
+ const min, const max = self.switchCaseItemRange(switch_br);
+ const min_int = min.getUnsignedInt(zcu) orelse break :jmp_table null;
+ const max_int = max.getUnsignedInt(zcu) orelse break :jmp_table null;
+ const table_len = max_int - min_int + 1;
+ if (table_len > max_table_len) break :jmp_table null;
+
+ const table_elems = try self.gpa.alloc(Builder.Constant, @intCast(table_len));
+ defer self.gpa.free(table_elems);
- const llvm_cases_len = llvm_cases_len: {
- var len: u32 = 0;
+ // Set them all to the `else` branch, then iterate over the AIR switch
+ // and replace all values which correspond to other prongs.
+ @memset(table_elems, try o.builder.blockAddrConst(
+ self.wip.function,
+ case_blocks[case_blocks.len - 1],
+ ));
+ var item_count: u32 = 0;
var it = switch_br.iterateCases();
- while (it.next()) |case| len += @intCast(case.items.len);
- break :llvm_cases_len len;
+ while (it.next()) |case| {
+ const case_block = case_blocks[case.idx];
+ const case_block_addr = try o.builder.blockAddrConst(
+ self.wip.function,
+ case_block,
+ );
+ for (case.items) |item| {
+ const val = Value.fromInterned(item.toInterned().?);
+ const table_idx = val.toUnsignedInt(zcu) - min_int;
+ table_elems[@intCast(table_idx)] = case_block_addr;
+ item_count += 1;
+ }
+ for (case.ranges) |range| {
+ const low = Value.fromInterned(range[0].toInterned().?);
+ const high = Value.fromInterned(range[1].toInterned().?);
+ const low_idx = low.toUnsignedInt(zcu) - min_int;
+ const high_idx = high.toUnsignedInt(zcu) - min_int;
+ @memset(table_elems[@intCast(low_idx)..@intCast(high_idx + 1)], case_block_addr);
+ item_count += @intCast(high_idx + 1 - low_idx);
+ }
+ }
+
+ const table_llvm_ty = try o.builder.arrayType(table_elems.len, .ptr);
+ const table_val = try o.builder.arrayConst(table_llvm_ty, table_elems);
+
+ const table_variable = try o.builder.addVariable(
+ try o.builder.strtabStringFmt("__jmptab_{d}", .{@intFromEnum(inst)}),
+ table_llvm_ty,
+ .default,
+ );
+ try table_variable.setInitializer(table_val, &o.builder);
+ table_variable.setLinkage(.internal, &o.builder);
+ table_variable.setUnnamedAddr(.unnamed_addr, &o.builder);
+
+ const table_includes_else = item_count != table_len;
+
+ break :jmp_table .{
+ .min = try o.lowerValue(min.toIntern()),
+ .max = try o.lowerValue(max.toIntern()),
+ .in_bounds_hint = if (table_includes_else) .none else switch (switch_br.getElseHint()) {
+ .none, .cold => .none,
+ .unpredictable => .unpredictable,
+ .likely => .likely,
+ .unlikely => .unlikely,
+ },
+ .table = table_variable.toConst(&o.builder),
+ .table_includes_else = table_includes_else,
+ };
};
const weights: Builder.Function.Instruction.BrCond.Weights = weights: {
+ if (jmp_table != null) break :weights .none; // not used
+
// First pass. If any weights are `.unpredictable`, unpredictable.
// If all are `.none` or `.cold`, none.
var any_likely = false;
@@ -6281,6 +6645,13 @@ pub const FuncGen = struct {
}
if (!any_likely) break :weights .none;
+ const llvm_cases_len = llvm_cases_len: {
+ var len: u32 = 0;
+ var it = switch_br.iterateCases();
+ while (it.next()) |case| len += @intCast(case.items.len);
+ break :llvm_cases_len len;
+ };
+
var weights = try self.gpa.alloc(Builder.Metadata, llvm_cases_len + 1);
defer self.gpa.free(weights);
@@ -6313,60 +6684,80 @@ pub const FuncGen = struct {
break :weights @enumFromInt(@intFromEnum(tuple));
};
- var wip_switch = try self.wip.@"switch"(cond_int, else_block, llvm_cases_len, weights);
- defer wip_switch.finish(&self.wip);
+ const dispatch_info: SwitchDispatchInfo = .{
+ .case_blocks = case_blocks,
+ .switch_weights = weights,
+ .jmp_table = jmp_table,
+ };
+
+ if (is_dispatch_loop) {
+ try self.switch_dispatch_info.putNoClobber(self.gpa, inst, dispatch_info);
+ }
+ defer if (is_dispatch_loop) {
+ assert(self.switch_dispatch_info.remove(inst));
+ };
+
+ // Generate the initial dispatch.
+ // If this is a simple `switch_br`, this is the only dispatch.
+ try self.lowerSwitchDispatch(inst, switch_br.operand, dispatch_info);
+ // Iterate the cases and generate their bodies.
var it = switch_br.iterateCases();
while (it.next()) |case| {
- const case_block = try self.wip.block(@intCast(case.items.len), "Case");
- for (case.items) |item| {
- const llvm_item = (try self.resolveInst(item)).toConst().?;
- const llvm_int_item = if (llvm_item.typeOf(&o.builder).isPointer(&o.builder))
- try o.builder.castConst(.ptrtoint, llvm_item, llvm_usize)
- else
- llvm_item;
- try wip_switch.addCase(llvm_int_item, case_block, &self.wip);
- }
+ const case_block = case_blocks[case.idx];
self.wip.cursor = .{ .block = case_block };
if (switch_br.getHint(case.idx) == .cold) _ = try self.wip.callIntrinsicAssumeCold();
- try self.genBodyDebugScope(null, case.body, .poi);
+ try self.genBodyDebugScope(null, case.body, .none);
}
-
+ self.wip.cursor = .{ .block = case_blocks[case_blocks.len - 1] };
const else_body = it.elseBody();
- self.wip.cursor = .{ .block = else_block };
if (switch_br.getElseHint() == .cold) _ = try self.wip.callIntrinsicAssumeCold();
- if (else_body.len != 0) {
- try self.genBodyDebugScope(null, else_body, .poi);
+ if (else_body.len > 0) {
+ try self.genBodyDebugScope(null, it.elseBody(), .none);
} else {
_ = try self.wip.@"unreachable"();
}
+ }
- // No need to reset the insert cursor since this instruction is noreturn.
- return .none;
+ fn switchCaseItemRange(self: *FuncGen, switch_br: Air.UnwrappedSwitch) [2]Value {
+ const zcu = self.ng.object.pt.zcu;
+ var it = switch_br.iterateCases();
+ var min: ?Value = null;
+ var max: ?Value = null;
+ while (it.next()) |case| {
+ for (case.items) |item| {
+ const val = Value.fromInterned(item.toInterned().?);
+ const low = if (min) |m| val.compareHetero(.lt, m, zcu) else true;
+ const high = if (max) |m| val.compareHetero(.gt, m, zcu) else true;
+ if (low) min = val;
+ if (high) max = val;
+ }
+ for (case.ranges) |range| {
+ const vals: [2]Value = .{
+ Value.fromInterned(range[0].toInterned().?),
+ Value.fromInterned(range[1].toInterned().?),
+ };
+ const low = if (min) |m| vals[0].compareHetero(.lt, m, zcu) else true;
+ const high = if (max) |m| vals[1].compareHetero(.gt, m, zcu) else true;
+ if (low) min = vals[0];
+ if (high) max = vals[1];
+ }
+ }
+ return .{ min.?, max.? };
}
- fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
- const o = self.ng.object;
- const zcu = o.pt.zcu;
+ fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void {
const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl;
const loop = self.air.extraData(Air.Block, ty_pl.payload);
const body: []const Air.Inst.Index = @ptrCast(self.air.extra[loop.end..][0..loop.data.body_len]);
- const loop_block = try self.wip.block(2, "Loop");
+ const loop_block = try self.wip.block(1, "Loop"); // `airRepeat` will increment incoming each time
_ = try self.wip.br(loop_block);
+ try self.loops.putNoClobber(self.gpa, inst, loop_block);
+ defer assert(self.loops.remove(inst));
+
self.wip.cursor = .{ .block = loop_block };
try self.genBodyDebugScope(null, body, .none);
-
- // TODO instead of this logic, change AIR to have the property that
- // every block is guaranteed to end with a noreturn instruction.
- // Then we can simply rely on the fact that a repeat or break instruction
- // would have been emitted already. Also the main loop in genBody can
- // be while(true) instead of for(body), which will eliminate 1 branch on
- // a hot path.
- if (body.len == 0 or !self.typeOfIndex(body[body.len - 1]).isNoReturn(zcu)) {
- _ = try self.wip.br(loop_block);
- }
- return .none;
}
fn airArrayToSlice(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
@@ -6861,10 +7252,9 @@ pub const FuncGen = struct {
return self.wip.not(operand, "");
}
- fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+ fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) !void {
_ = inst;
_ = try self.wip.@"unreachable"();
- return .none;
}
fn airDbgStmt(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
@@ -9267,11 +9657,10 @@ pub const FuncGen = struct {
return fg.load(ptr, ptr_ty);
}
- fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
+ fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !void {
_ = inst;
_ = try self.wip.callIntrinsic(.normal, .none, .trap, &.{}, &.{}, "");
_ = try self.wip.@"unreachable"();
- return .none;
}
fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value {
diff --git a/src/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig
index 9b28126604..f6bfcab1ad 100644
--- a/src/codegen/llvm/Builder.zig
+++ b/src/codegen/llvm/Builder.zig
@@ -4157,6 +4157,7 @@ pub const Function = struct {
@"icmp ugt",
@"icmp ule",
@"icmp ult",
+ indirectbr,
insertelement,
insertvalue,
inttoptr,
@@ -4367,6 +4368,7 @@ pub const Function = struct {
return switch (wip.instructions.items(.tag)[@intFromEnum(self)]) {
.br,
.br_cond,
+ .indirectbr,
.ret,
.@"ret void",
.@"switch",
@@ -4381,6 +4383,7 @@ pub const Function = struct {
.br,
.br_cond,
.fence,
+ .indirectbr,
.ret,
.@"ret void",
.store,
@@ -4471,6 +4474,7 @@ pub const Function = struct {
.br,
.br_cond,
.fence,
+ .indirectbr,
.ret,
.@"ret void",
.store,
@@ -4657,6 +4661,7 @@ pub const Function = struct {
.br,
.br_cond,
.fence,
+ .indirectbr,
.ret,
.@"ret void",
.store,
@@ -4837,6 +4842,12 @@ pub const Function = struct {
//case_blocks: [cases_len]Block.Index,
};
+ pub const IndirectBr = struct {
+ addr: Value,
+ targets_len: u32,
+ //targets: [targets_len]Block.Index,
+ };
+
pub const Binary = struct {
lhs: Value,
rhs: Value,
@@ -5294,10 +5305,27 @@ pub const WipFunction = struct {
return .{ .index = 0, .instruction = instruction };
}
+ pub fn indirectbr(
+ self: *WipFunction,
+ addr: Value,
+ targets: []const Block.Index,
+ ) Allocator.Error!Instruction.Index {
+ try self.ensureUnusedExtraCapacity(1, Instruction.IndirectBr, targets.len);
+ const instruction = try self.addInst(null, .{
+ .tag = .indirectbr,
+ .data = self.addExtraAssumeCapacity(Instruction.IndirectBr{
+ .addr = addr,
+ .targets_len = @intCast(targets.len),
+ }),
+ });
+ _ = self.extra.appendSliceAssumeCapacity(@ptrCast(targets));
+ for (targets) |target| target.ptr(self).branches += 1;
+ return instruction;
+ }
+
pub fn @"unreachable"(self: *WipFunction) Allocator.Error!Instruction.Index {
try self.ensureUnusedExtraCapacity(1, NoExtra, 0);
- const instruction = try self.addInst(null, .{ .tag = .@"unreachable", .data = undefined });
- return instruction;
+ return try self.addInst(null, .{ .tag = .@"unreachable", .data = undefined });
}
pub fn un(
@@ -6299,8 +6327,7 @@ pub const WipFunction = struct {
});
names[@intFromEnum(new_block_index)] = try wip_name.map(current_block.name, "");
for (current_block.instructions.items) |old_instruction_index| {
- const new_instruction_index: Instruction.Index =
- @enumFromInt(function.instructions.len);
+ const new_instruction_index: Instruction.Index = @enumFromInt(function.instructions.len);
var instruction = self.instructions.get(@intFromEnum(old_instruction_index));
switch (instruction.tag) {
.add,
@@ -6509,6 +6536,15 @@ pub const WipFunction = struct {
});
wip_extra.appendMappedValues(indices, instructions);
},
+ .indirectbr => {
+ var extra = self.extraDataTrail(Instruction.IndirectBr, instruction.data);
+ const targets = extra.trail.next(extra.data.targets_len, Block.Index, self);
+ instruction.data = wip_extra.addExtra(Instruction.IndirectBr{
+ .addr = instructions.map(extra.data.addr),
+ .targets_len = extra.data.targets_len,
+ });
+ wip_extra.appendSlice(targets);
+ },
.insertelement => {
const extra = self.extraData(Instruction.InsertElement, instruction.data);
instruction.data = wip_extra.addExtra(Instruction.InsertElement{
@@ -7555,10 +7591,10 @@ pub const Constant = enum(u32) {
.blockaddress => |tag| {
const extra = data.builder.constantExtraData(BlockAddress, item.data);
const function = extra.function.ptrConst(data.builder);
- try writer.print("{s}({}, %{d})", .{
+ try writer.print("{s}({}, {})", .{
@tagName(tag),
function.global.fmt(data.builder),
- @intFromEnum(extra.block), // TODO
+ extra.block.toInst(function).fmt(extra.function, data.builder),
});
},
.dso_local_equivalent,
@@ -9902,6 +9938,23 @@ pub fn printUnbuffered(
index.fmt(function_index, self),
});
},
+ .indirectbr => |tag| {
+ var extra =
+ function.extraDataTrail(Function.Instruction.IndirectBr, instruction.data);
+ const targets =
+ extra.trail.next(extra.data.targets_len, Function.Block.Index, &function);
+ try writer.print(" {s} {%}, [", .{
+ @tagName(tag),
+ extra.data.addr.fmt(function_index, self),
+ });
+ for (0.., targets) |target_index, target| {
+ if (target_index > 0) try writer.writeAll(", ");
+ try writer.print("{%}", .{
+ target.toInst(&function).fmt(function_index, self),
+ });
+ }
+ try writer.writeByte(']');
+ },
.insertelement => |tag| {
const extra =
function.extraData(Function.Instruction.InsertElement, instruction.data);
@@ -14775,15 +14828,6 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
.indices = indices,
});
},
- .insertvalue => {
- var extra = func.extraDataTrail(Function.Instruction.InsertValue, data);
- const indices = extra.trail.next(extra.data.indices_len, u32, &func);
- try function_block.writeAbbrev(FunctionBlock.InsertValue{
- .val = adapter.getOffsetValueIndex(extra.data.val),
- .elem = adapter.getOffsetValueIndex(extra.data.elem),
- .indices = indices,
- });
- },
.extractelement => {
const extra = func.extraData(Function.Instruction.ExtractElement, data);
try function_block.writeAbbrev(FunctionBlock.ExtractElement{
@@ -14791,6 +14835,20 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
.index = adapter.getOffsetValueIndex(extra.index),
});
},
+ .indirectbr => {
+ var extra =
+ func.extraDataTrail(Function.Instruction.IndirectBr, datas[instr_index]);
+ const targets =
+ extra.trail.next(extra.data.targets_len, Function.Block.Index, &func);
+ try function_block.writeAbbrevAdapted(
+ FunctionBlock.IndirectBr{
+ .ty = extra.data.addr.typeOf(@enumFromInt(func_index), self),
+ .addr = extra.data.addr,
+ .targets = targets,
+ },
+ adapter,
+ );
+ },
.insertelement => {
const extra = func.extraData(Function.Instruction.InsertElement, data);
try function_block.writeAbbrev(FunctionBlock.InsertElement{
@@ -14799,6 +14857,15 @@ pub fn toBitcode(self: *Builder, allocator: Allocator) bitcode_writer.Error![]co
.index = adapter.getOffsetValueIndex(extra.index),
});
},
+ .insertvalue => {
+ var extra = func.extraDataTrail(Function.Instruction.InsertValue, datas[instr_index]);
+ const indices = extra.trail.next(extra.data.indices_len, u32, &func);
+ try function_block.writeAbbrev(FunctionBlock.InsertValue{
+ .val = adapter.getOffsetValueIndex(extra.data.val),
+ .elem = adapter.getOffsetValueIndex(extra.data.elem),
+ .indices = indices,
+ });
+ },
.select => {
const extra = func.extraData(Function.Instruction.Select, data);
try function_block.writeAbbrev(FunctionBlock.Select{
diff --git a/src/codegen/llvm/ir.zig b/src/codegen/llvm/ir.zig
index 4d7effdaaf..271e87c995 100644
--- a/src/codegen/llvm/ir.zig
+++ b/src/codegen/llvm/ir.zig
@@ -19,6 +19,7 @@ const LineAbbrev = AbbrevOp{ .vbr = 8 };
const ColumnAbbrev = AbbrevOp{ .vbr = 8 };
const BlockAbbrev = AbbrevOp{ .vbr = 6 };
+const BlockArrayAbbrev = AbbrevOp{ .array_vbr = 6 };
/// Unused tags are commented out so that they are omitted in the generated
/// bitcode, which scans over this enum using reflection.
@@ -1294,6 +1295,7 @@ pub const FunctionBlock = struct {
DebugLoc,
DebugLocAgain,
ColdOperandBundle,
+ IndirectBr,
};
pub const DeclareBlocks = struct {
@@ -1813,6 +1815,18 @@ pub const FunctionBlock = struct {
.{ .literal = 0 },
};
};
+
+ pub const IndirectBr = struct {
+ pub const ops = [_]AbbrevOp{
+ .{ .literal = 31 },
+ .{ .fixed_runtime = Builder.Type },
+ ValueAbbrev,
+ BlockArrayAbbrev,
+ };
+ ty: Builder.Type,
+ addr: Builder.Value,
+ targets: []const Builder.Function.Block.Index,
+ };
};
pub const FunctionValueSymbolTable = struct {
diff --git a/src/codegen/spirv.zig b/src/codegen/spirv.zig
index 345e80a23c..afc7641072 100644
--- a/src/codegen/spirv.zig
+++ b/src/codegen/spirv.zig
@@ -3340,6 +3340,7 @@ const NavGen = struct {
.store, .store_safe => return self.airStore(inst),
.br => return self.airBr(inst),
+ .repeat => return self.fail("TODO implement `repeat`", .{}),
.breakpoint => return,
.cond_br => return self.airCondBr(inst),
.loop => return self.airLoop(inst),
@@ -6211,6 +6212,7 @@ const NavGen = struct {
var num_conditions: u32 = 0;
var it = switch_br.iterateCases();
while (it.next()) |case| {
+ if (case.ranges.len > 0) return self.todo("switch with ranges", .{});
num_conditions += @intCast(case.items.len);
}
break :blk num_conditions;