authorgravatar for sinon@vortan.devDavid Rubin <sinon@vortan.dev> 2026-06-20 16:08:28-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-06-26 23:29:08+02:00
logc150a66bfe2010be86e38f40573ffc8bdf5c2326
tree69e4e861bfedd6d2b41e1ebfc51ecccd00fd09d3
parent7c832740e027f8f656e4d2d945b985196367c826

Sema: fix inline for loop breaks


3 files changed, 55 insertions(+), 24 deletions(-)

src/Air/print.zig+8-12
...@@ -73,23 +73,19 @@ pub fn writeInst(...@@ -73,23 +73,19 @@ pub fn writeInst(
73}73}
7474
75pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {75pub fn dump(air: Air, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
76 const comp = pt.zcu.comp;76 var buffer: [4096]u8 = undefined;
77 const io = comp.io;77 const stderr = std.debug.lockStderr(&buffer);
78 var buffer: [512]u8 = undefined;78 defer std.debug.unlockStderr();
79 const stderr = try io.lockStderr(&buffer, null);
80 defer io.unlockStderr();
81 const w = &stderr.file_writer.interface;79 const w = &stderr.file_writer.interface;
82 air.write(w, pt, liveness);80 air.write(w, pt, liveness) catch return;
83}81}
8482
85pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {83pub fn dumpInst(air: Air, inst: Air.Inst.Index, pt: Zcu.PerThread, liveness: ?Air.Liveness) void {
86 const comp = pt.zcu.comp;84 var buffer: [4096]u8 = undefined;
87 const io = comp.io;85 const stderr = std.debug.lockStderr(&buffer);
88 var buffer: [512]u8 = undefined;86 defer std.debug.unlockStderr();
89 const stderr = try io.lockStderr(&buffer, null);
90 defer io.unlockStderr();
91 const w = &stderr.file_writer.interface;87 const w = &stderr.file_writer.interface;
92 air.writeInst(w, inst, pt, liveness);88 air.writeInst(w, inst, pt, liveness) catch return;
93}89}
9490
95const Writer = struct {91const Writer = struct {
src/Sema.zig+30-12
...@@ -5287,33 +5287,51 @@ fn resolveBlockBody(...@@ -5287,33 +5287,51 @@ fn resolveBlockBody(
5287 return sema.resolveAnalyzedBlock(parent_block, src, child_block, merges, need_debug_scope);5287 return sema.resolveAnalyzedBlock(parent_block, src, child_block, merges, need_debug_scope);
5288 } else |err| switch (err) {5288 } else |err| switch (err) {
5289 error.ComptimeBreak => {5289 error.ComptimeBreak => {
5290 const break_inst = sema.comptime_break_inst;
5291 const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break";
5292 const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;
5293 const breaks_to_body = extra.block_inst == body_inst;
5294
5290 // Comptime control flow is happening, however child_block may still contain5295 // Comptime control flow is happening, however child_block may still contain
5291 // runtime instructions which need to be copied to the parent block.5296 // runtime instructions which need to be copied to the parent block.
5292 if (need_debug_scope and child_block.instructions.items.len > 0) {5297 if (need_debug_scope and child_block.instructions.items.len > 0) {
5293 // We need a runtime block for scoping reasons.5298 // We need a runtime block for scoping reasons. The break
5294 _ = try child_block.addBr(merges.block_inst, .void_value);5299 // operand may have been produced by a runtime instruction
5300 // inside `child_blocks`.
5301 const operand = sema.resolveInst(break_data.operand);
5302 const operand_ty = sema.typeOf(operand);
5303 _ = try child_block.addBr(merges.block_inst, operand);
5295 try parent_block.instructions.append(sema.gpa, merges.block_inst);5304 try parent_block.instructions.append(sema.gpa, merges.block_inst);
5296 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).@"struct".field_names.len +5305 try sema.air_extra.ensureUnusedCapacity(sema.gpa, @typeInfo(Air.Block).@"struct".field_names.len +
5297 child_block.instructions.items.len);5306 child_block.instructions.items.len);
5298 sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{5307 sema.air_instructions.items(.data)[@intFromEnum(merges.block_inst)] = .{ .ty_pl = .{
5299 .ty = .void_type,5308 .ty = Air.internedToRef(operand_ty.toIntern()),
5300 .payload = sema.addExtraAssumeCapacity(Air.Block{5309 .payload = sema.addExtraAssumeCapacity(Air.Block{
5301 .body_len = @intCast(child_block.instructions.items.len),5310 .body_len = @intCast(child_block.instructions.items.len),
5302 }),5311 }),
5303 } };5312 } };
5304 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));5313 sema.air_extra.appendSliceAssumeCapacity(@ptrCast(child_block.instructions.items));
5314
5315 // The block result now holds the operand value, so we remap
5316 // the operand such that an enclosing scope which resolves
5317 // it picks up the block result rather than the internal
5318 // block instruction.
5319 if (break_data.operand.toIndex()) |operand_zir| {
5320 sema.inst_map.putAssumeCapacity(operand_zir, merges.block_inst.toRef());
5321 }
5322 if (breaks_to_body) {
5323 return merges.block_inst.toRef();
5324 } else {
5325 return error.ComptimeBreak;
5326 }
5305 } else {5327 } else {
5306 // We can copy instructions directly to the parent block.5328 // We can copy instructions directly to the parent block.
5307 try parent_block.instructions.appendSlice(sema.gpa, child_block.instructions.items);5329 try parent_block.instructions.appendSlice(sema.gpa, child_block.instructions.items);
5308 }5330 if (breaks_to_body) {
53095331 return sema.resolveInst(break_data.operand);
5310 const break_inst = sema.comptime_break_inst;5332 } else {
5311 const break_data = sema.code.instructions.items(.data)[@intFromEnum(break_inst)].@"break";5333 return error.ComptimeBreak;
5312 const extra = sema.code.extraData(Zir.Inst.Break, break_data.payload_index).data;5334 }
5313 if (extra.block_inst == body_inst) {
5314 return sema.resolveInst(break_data.operand);
5315 } else {
5316 return error.ComptimeBreak;
5317 }5335 }
5318 },5336 },
5319 else => |e| return e,5337 else => |e| return e,
test/behavior/for.zig+17
...@@ -547,3 +547,20 @@ test "labeled break from else" {...@@ -547,3 +547,20 @@ test "labeled break from else" {
547 try S.doTheTest(5);547 try S.doTheTest(5);
548 try comptime S.doTheTest(5);548 try comptime S.doTheTest(5);
549}549}
550
551test "value break from inline for" {
552 const S = struct {
553 fn doTheTest() !void {
554 const x = inline for (0..2) |_| {
555 if (true) {
556 var idx: u32 = 0;
557 idx += 1;
558 break idx;
559 }
560 };
561 try expect(x == 1);
562 }
563 };
564 try S.doTheTest();
565 try comptime S.doTheTest();
566}