| ... | ... | @@ -68,6 +68,11 @@ preallocated_new_func: ?*Module.Fn = null, |
| 68 | 68 | /// TODO: after upgrading to use InternPool change the key here to be an |
| 69 | 69 | /// InternPool value index. |
| 70 | 70 | types_to_resolve: std.ArrayListUnmanaged(Air.Inst.Ref) = .{}, |
| 71 | /// These are lazily created runtime blocks from inline_block instructions. |
| 72 | /// They are created when an inline_break passes through a runtime condition, because |
| 73 | /// Sema must convert comptime control flow to runtime control flow, which means |
| 74 | /// breaking from a block. |
| 75 | post_hoc_blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, *LabeledBlock) = .{}, |
| 71 | 76 | |
| 72 | 77 | const std = @import("std"); |
| 73 | 78 | const mem = std.mem; |
| ... | ... | @@ -525,6 +530,18 @@ pub const Block = struct { |
| 525 | 530 | }; |
| 526 | 531 | }; |
| 527 | 532 | |
| 533 | const LabeledBlock = struct { |
| 534 | block: Block, |
| 535 | label: Block.Label, |
| 536 | |
| 537 | fn destroy(lb: *LabeledBlock, gpa: Allocator) void { |
| 538 | lb.block.instructions.deinit(gpa); |
| 539 | lb.label.merges.results.deinit(gpa); |
| 540 | lb.label.merges.br_list.deinit(gpa); |
| 541 | gpa.destroy(lb); |
| 542 | } |
| 543 | }; |
| 544 | |
| 528 | 545 | pub fn deinit(sema: *Sema) void { |
| 529 | 546 | const gpa = sema.gpa; |
| 530 | 547 | sema.air_instructions.deinit(gpa); |
| ... | ... | @@ -533,6 +550,14 @@ pub fn deinit(sema: *Sema) void { |
| 533 | 550 | sema.inst_map.deinit(gpa); |
| 534 | 551 | sema.decl_val_table.deinit(gpa); |
| 535 | 552 | sema.types_to_resolve.deinit(gpa); |
| 553 | { |
| 554 | var it = sema.post_hoc_blocks.iterator(); |
| 555 | while (it.next()) |entry| { |
| 556 | const labeled_block = entry.value_ptr.*; |
| 557 | labeled_block.destroy(gpa); |
| 558 | } |
| 559 | sema.post_hoc_blocks.deinit(gpa); |
| 560 | } |
| 536 | 561 | sema.* = undefined; |
| 537 | 562 | } |
| 538 | 563 | |
| ... | ... | @@ -573,8 +598,8 @@ pub fn analyzeBody( |
| 573 | 598 | |
| 574 | 599 | const BreakData = struct { |
| 575 | 600 | block_inst: Zir.Inst.Index, |
| 576 | | operand: Air.Inst.Ref, |
| 577 | | inst: Air.Inst.Index, |
| 601 | operand: Zir.Inst.Ref, |
| 602 | inst: Zir.Inst.Index, |
| 578 | 603 | }; |
| 579 | 604 | |
| 580 | 605 | pub fn analyzeBodyBreak( |
| ... | ... | @@ -1192,20 +1217,67 @@ fn analyzeBodyInner( |
| 1192 | 1217 | }, |
| 1193 | 1218 | .block_inline => blk: { |
| 1194 | 1219 | // Directly analyze the block body without introducing a new block. |
| 1220 | // However, in the case of a corresponding break_inline which reaches |
| 1221 | // through a runtime conditional branch, we must retroactively emit |
| 1222 | // a block, so we remember the block index here just in case. |
| 1223 | const block_index = block.instructions.items.len; |
| 1195 | 1224 | const inst_data = datas[inst].pl_node; |
| 1196 | 1225 | const extra = sema.code.extraData(Zir.Inst.Block, inst_data.payload_index); |
| 1197 | 1226 | const inline_body = sema.code.extra[extra.end..][0..extra.data.body_len]; |
| 1227 | const gpa = sema.gpa; |
| 1198 | 1228 | // If this block contains a function prototype, we need to reset the |
| 1199 | 1229 | // current list of parameters and restore it later. |
| 1200 | 1230 | // Note: this probably needs to be resolved in a more general manner. |
| 1201 | 1231 | const prev_params = block.params; |
| 1202 | 1232 | block.params = .{}; |
| 1203 | 1233 | defer { |
| 1204 | | block.params.deinit(sema.gpa); |
| 1234 | block.params.deinit(gpa); |
| 1205 | 1235 | block.params = prev_params; |
| 1206 | 1236 | } |
| 1207 | | const break_data = (try sema.analyzeBodyBreak(block, inline_body)) orelse |
| 1208 | | break always_noreturn; |
| 1237 | const opt_break_data = try sema.analyzeBodyBreak(block, inline_body); |
| 1238 | // A runtime conditional branch that needs a post-hoc block to be |
| 1239 | // emitted communicates this by mapping the block index into the inst map. |
| 1240 | if (map.get(inst)) |new_block_ref| ph: { |
| 1241 | // Comptime control flow populates the map, so we don't actually know |
| 1242 | // if this is a post-hoc runtime block until we check the |
| 1243 | // post_hoc_block map. |
| 1244 | const new_block_inst = Air.refToIndex(new_block_ref) orelse break :ph; |
| 1245 | const labeled_block = sema.post_hoc_blocks.get(new_block_inst) orelse |
| 1246 | break :ph; |
| 1247 | |
| 1248 | // In this case we need to move all the instructions starting at |
| 1249 | // block_index from the current block into this new one. |
| 1250 | |
| 1251 | if (opt_break_data) |break_data| { |
| 1252 | // This is a comptime break which we now change to a runtime break |
| 1253 | // since it crosses a runtime branch. |
| 1254 | // It may pass through our currently being analyzed block_inline or it |
| 1255 | // may point directly to it. In the latter case, this modifies the |
| 1256 | // block that we are about to look up in the post_hoc_blocks map below. |
| 1257 | try sema.addRuntimeBreak(block, break_data); |
| 1258 | } else { |
| 1259 | // Here the comptime control flow ends with noreturn; however |
| 1260 | // we have runtime control flow continuing after this block. |
| 1261 | // This branch is therefore handled by the `i += 1; continue;` |
| 1262 | // logic below. |
| 1263 | } |
| 1264 | |
| 1265 | try labeled_block.block.instructions.appendSlice(gpa, block.instructions.items[block_index..]); |
| 1266 | block.instructions.items.len = block_index; |
| 1267 | |
| 1268 | const block_result = try sema.analyzeBlockBody(block, inst_data.src(), &labeled_block.block, &labeled_block.label.merges); |
| 1269 | { |
| 1270 | // Destroy the ad-hoc block entry so that it does not interfere with |
| 1271 | // the next iteration of comptime control flow, if any. |
| 1272 | labeled_block.destroy(gpa); |
| 1273 | assert(sema.post_hoc_blocks.remove(new_block_inst)); |
| 1274 | } |
| 1275 | try map.put(gpa, inst, block_result); |
| 1276 | i += 1; |
| 1277 | continue; |
| 1278 | } |
| 1279 | |
| 1280 | const break_data = opt_break_data orelse break always_noreturn; |
| 1209 | 1281 | if (inst == break_data.block_inst) { |
| 1210 | 1282 | break :blk sema.resolveInst(break_data.operand); |
| 1211 | 1283 | } else { |
| ... | ... | @@ -3996,13 +4068,12 @@ fn zirBlock(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileErro |
| 3996 | 4068 | .inlining = parent_block.inlining, |
| 3997 | 4069 | .is_comptime = parent_block.is_comptime, |
| 3998 | 4070 | }; |
| 3999 | | const merges = &child_block.label.?.merges; |
| 4000 | 4071 | |
| 4001 | 4072 | defer child_block.instructions.deinit(gpa); |
| 4002 | | defer merges.results.deinit(gpa); |
| 4003 | | defer merges.br_list.deinit(gpa); |
| 4073 | defer label.merges.results.deinit(gpa); |
| 4074 | defer label.merges.br_list.deinit(gpa); |
| 4004 | 4075 | |
| 4005 | | return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, merges); |
| 4076 | return sema.resolveBlockBody(parent_block, src, &child_block, body, inst, &label.merges); |
| 4006 | 4077 | } |
| 4007 | 4078 | |
| 4008 | 4079 | fn resolveBlockBody( |
| ... | ... | @@ -7955,7 +8026,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 7955 | 8026 | const item = sema.resolveInst(item_ref); |
| 7956 | 8027 | // `item` is already guaranteed to be constant known. |
| 7957 | 8028 | |
| 7958 | | try sema.analyzeBody(&case_block, body); |
| 8029 | _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { |
| 8030 | error.ComptimeBreak => { |
| 8031 | const zir_datas = sema.code.instructions.items(.data); |
| 8032 | const break_data = zir_datas[sema.comptime_break_inst].@"break"; |
| 8033 | try sema.addRuntimeBreak(&case_block, .{ |
| 8034 | .block_inst = break_data.block_inst, |
| 8035 | .operand = break_data.operand, |
| 8036 | .inst = sema.comptime_break_inst, |
| 8037 | }); |
| 8038 | }, |
| 8039 | else => |e| return e, |
| 8040 | }; |
| 7959 | 8041 | |
| 7960 | 8042 | try wip_captures.finalize(); |
| 7961 | 8043 | |
| ... | ... | @@ -7998,7 +8080,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 7998 | 8080 | |
| 7999 | 8081 | const body = sema.code.extra[extra_index..][0..body_len]; |
| 8000 | 8082 | extra_index += body_len; |
| 8001 | | try sema.analyzeBody(&case_block, body); |
| 8083 | _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { |
| 8084 | error.ComptimeBreak => { |
| 8085 | const zir_datas = sema.code.instructions.items(.data); |
| 8086 | const break_data = zir_datas[sema.comptime_break_inst].@"break"; |
| 8087 | try sema.addRuntimeBreak(&case_block, .{ |
| 8088 | .block_inst = break_data.block_inst, |
| 8089 | .operand = break_data.operand, |
| 8090 | .inst = sema.comptime_break_inst, |
| 8091 | }); |
| 8092 | }, |
| 8093 | else => |e| return e, |
| 8094 | }; |
| 8002 | 8095 | |
| 8003 | 8096 | try cases_extra.ensureUnusedCapacity(gpa, 2 + items.len + |
| 8004 | 8097 | case_block.instructions.items.len); |
| ... | ... | @@ -8073,7 +8166,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8073 | 8166 | |
| 8074 | 8167 | const body = sema.code.extra[extra_index..][0..body_len]; |
| 8075 | 8168 | extra_index += body_len; |
| 8076 | | try sema.analyzeBody(&case_block, body); |
| 8169 | _ = sema.analyzeBodyInner(&case_block, body) catch |err| switch (err) { |
| 8170 | error.ComptimeBreak => { |
| 8171 | const zir_datas = sema.code.instructions.items(.data); |
| 8172 | const break_data = zir_datas[sema.comptime_break_inst].@"break"; |
| 8173 | try sema.addRuntimeBreak(&case_block, .{ |
| 8174 | .block_inst = break_data.block_inst, |
| 8175 | .operand = break_data.operand, |
| 8176 | .inst = sema.comptime_break_inst, |
| 8177 | }); |
| 8178 | }, |
| 8179 | else => |e| return e, |
| 8180 | }; |
| 8077 | 8181 | |
| 8078 | 8182 | try wip_captures.finalize(); |
| 8079 | 8183 | |
| ... | ... | @@ -8110,7 +8214,18 @@ fn zirSwitchBlock(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError |
| 8110 | 8214 | case_block.wip_capture_scope = wip_captures.scope; |
| 8111 | 8215 | |
| 8112 | 8216 | if (special.body.len != 0) { |
| 8113 | | try sema.analyzeBody(&case_block, special.body); |
| 8217 | _ = sema.analyzeBodyInner(&case_block, special.body) catch |err| switch (err) { |
| 8218 | error.ComptimeBreak => { |
| 8219 | const zir_datas = sema.code.instructions.items(.data); |
| 8220 | const break_data = zir_datas[sema.comptime_break_inst].@"break"; |
| 8221 | try sema.addRuntimeBreak(&case_block, .{ |
| 8222 | .block_inst = break_data.block_inst, |
| 8223 | .operand = break_data.operand, |
| 8224 | .inst = sema.comptime_break_inst, |
| 8225 | }); |
| 8226 | }, |
| 8227 | else => |e| return e, |
| 8228 | }; |
| 8114 | 8229 | } else { |
| 8115 | 8230 | // We still need a terminator in this block, but we have proven |
| 8116 | 8231 | // that it is unreachable. |
| ... | ... | @@ -11979,11 +12094,33 @@ fn zirCondbr( |
| 11979 | 12094 | sub_block.runtime_index += 1; |
| 11980 | 12095 | defer sub_block.instructions.deinit(gpa); |
| 11981 | 12096 | |
| 11982 | | try sema.analyzeBody(&sub_block, then_body); |
| 12097 | _ = sema.analyzeBodyInner(&sub_block, then_body) catch |err| switch (err) { |
| 12098 | error.ComptimeBreak => { |
| 12099 | const zir_datas = sema.code.instructions.items(.data); |
| 12100 | const break_data = zir_datas[sema.comptime_break_inst].@"break"; |
| 12101 | try sema.addRuntimeBreak(&sub_block, .{ |
| 12102 | .block_inst = break_data.block_inst, |
| 12103 | .operand = break_data.operand, |
| 12104 | .inst = sema.comptime_break_inst, |
| 12105 | }); |
| 12106 | }, |
| 12107 | else => |e| return e, |
| 12108 | }; |
| 11983 | 12109 | const true_instructions = sub_block.instructions.toOwnedSlice(gpa); |
| 11984 | 12110 | defer gpa.free(true_instructions); |
| 11985 | 12111 | |
| 11986 | | try sema.analyzeBody(&sub_block, else_body); |
| 12112 | _ = sema.analyzeBodyInner(&sub_block, else_body) catch |err| switch (err) { |
| 12113 | error.ComptimeBreak => { |
| 12114 | const zir_datas = sema.code.instructions.items(.data); |
| 12115 | const break_data = zir_datas[sema.comptime_break_inst].@"break"; |
| 12116 | try sema.addRuntimeBreak(&sub_block, .{ |
| 12117 | .block_inst = break_data.block_inst, |
| 12118 | .operand = break_data.operand, |
| 12119 | .inst = sema.comptime_break_inst, |
| 12120 | }); |
| 12121 | }, |
| 12122 | else => |e| return e, |
| 12123 | }; |
| 11987 | 12124 | try sema.air_extra.ensureUnusedCapacity(gpa, @typeInfo(Air.CondBr).Struct.fields.len + |
| 11988 | 12125 | true_instructions.len + sub_block.instructions.items.len); |
| 11989 | 12126 | _ = try parent_block.addInst(.{ |
| ... | ... | @@ -12001,6 +12138,61 @@ fn zirCondbr( |
| 12001 | 12138 | return always_noreturn; |
| 12002 | 12139 | } |
| 12003 | 12140 | |
| 12141 | // A `break` statement is inside a runtime condition, but trying to |
| 12142 | // break from an inline loop. In such case we must convert it to |
| 12143 | // a runtime break. |
| 12144 | fn addRuntimeBreak(sema: *Sema, child_block: *Block, break_data: BreakData) !void { |
| 12145 | const gop = try sema.inst_map.getOrPut(sema.gpa, break_data.block_inst); |
| 12146 | const labeled_block = if (!gop.found_existing) blk: { |
| 12147 | try sema.post_hoc_blocks.ensureUnusedCapacity(sema.gpa, 1); |
| 12148 | |
| 12149 | const new_block_inst = @intCast(Air.Inst.Index, sema.air_instructions.len); |
| 12150 | gop.value_ptr.* = Air.indexToRef(new_block_inst); |
| 12151 | try sema.air_instructions.append(sema.gpa, .{ |
| 12152 | .tag = .block, |
| 12153 | .data = undefined, |
| 12154 | }); |
| 12155 | const labeled_block = try sema.gpa.create(LabeledBlock); |
| 12156 | labeled_block.* = .{ |
| 12157 | .label = .{ |
| 12158 | .zir_block = break_data.block_inst, |
| 12159 | .merges = .{ |
| 12160 | .results = .{}, |
| 12161 | .br_list = .{}, |
| 12162 | .block_inst = new_block_inst, |
| 12163 | }, |
| 12164 | }, |
| 12165 | .block = .{ |
| 12166 | .parent = child_block, |
| 12167 | .sema = sema, |
| 12168 | .src_decl = child_block.src_decl, |
| 12169 | .namespace = child_block.namespace, |
| 12170 | .wip_capture_scope = child_block.wip_capture_scope, |
| 12171 | .instructions = .{}, |
| 12172 | .label = &labeled_block.label, |
| 12173 | .inlining = child_block.inlining, |
| 12174 | .is_comptime = child_block.is_comptime, |
| 12175 | }, |
| 12176 | }; |
| 12177 | sema.post_hoc_blocks.putAssumeCapacityNoClobber(new_block_inst, labeled_block); |
| 12178 | break :blk labeled_block; |
| 12179 | } else blk: { |
| 12180 | const new_block_inst = Air.refToIndex(gop.value_ptr.*).?; |
| 12181 | const labeled_block = sema.post_hoc_blocks.get(new_block_inst).?; |
| 12182 | break :blk labeled_block; |
| 12183 | }; |
| 12184 | |
| 12185 | const operand = sema.resolveInst(break_data.operand); |
| 12186 | const br_ref = try child_block.addBr(labeled_block.label.merges.block_inst, operand); |
| 12187 | try labeled_block.label.merges.results.append(sema.gpa, operand); |
| 12188 | try labeled_block.label.merges.br_list.append(sema.gpa, Air.refToIndex(br_ref).?); |
| 12189 | labeled_block.block.runtime_index += 1; |
| 12190 | if (labeled_block.block.runtime_cond == null and labeled_block.block.runtime_loop == null) { |
| 12191 | labeled_block.block.runtime_cond = child_block.runtime_cond orelse child_block.runtime_loop; |
| 12192 | labeled_block.block.runtime_loop = child_block.runtime_loop; |
| 12193 | } |
| 12194 | } |
| 12195 | |
| 12004 | 12196 | fn zirUnreachable(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index { |
| 12005 | 12197 | const tracy = trace(@src()); |
| 12006 | 12198 | defer tracy.end(); |