Commit de4f3f11f7

Andrew Kelley <andrew@ziglang.org>
2020-08-13 06:13:07
stage2: astgen for while loops
See #6021
1 parent 30db5b1
src-self-hosted/astgen.zig
@@ -48,20 +48,21 @@ pub fn typeExpr(mod: *Module, scope: *Scope, type_node: *ast.Node) InnerError!*z
 pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerError!*zir.Inst {
     switch (node.tag) {
         .VarDecl => unreachable, // Handled in `blockExpr`.
-        .Assign => unreachable, // Handled in `blockExpr`.
-        .AssignBitAnd => unreachable, // Handled in `blockExpr`.
-        .AssignBitOr => unreachable, // Handled in `blockExpr`.
-        .AssignBitShiftLeft => unreachable, // Handled in `blockExpr`.
-        .AssignBitShiftRight => unreachable, // Handled in `blockExpr`.
-        .AssignBitXor => unreachable, // Handled in `blockExpr`.
-        .AssignDiv => unreachable, // Handled in `blockExpr`.
-        .AssignSub => unreachable, // Handled in `blockExpr`.
-        .AssignSubWrap => unreachable, // Handled in `blockExpr`.
-        .AssignMod => unreachable, // Handled in `blockExpr`.
-        .AssignAdd => unreachable, // Handled in `blockExpr`.
-        .AssignAddWrap => unreachable, // Handled in `blockExpr`.
-        .AssignMul => unreachable, // Handled in `blockExpr`.
-        .AssignMulWrap => unreachable, // Handled in `blockExpr`.
+
+        .Assign => return rlWrapVoid(mod, scope, rl, node, try assign(mod, scope, node.castTag(.Assign).?)),
+        .AssignBitAnd => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitAnd).?, .bitand)),
+        .AssignBitOr => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitOr).?, .bitor)),
+        .AssignBitShiftLeft => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitShiftLeft).?, .shl)),
+        .AssignBitShiftRight => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitShiftRight).?, .shr)),
+        .AssignBitXor => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignBitXor).?, .xor)),
+        .AssignDiv => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignDiv).?, .div)),
+        .AssignSub => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignSub).?, .sub)),
+        .AssignSubWrap => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignSubWrap).?, .subwrap)),
+        .AssignMod => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignMod).?, .mod_rem)),
+        .AssignAdd => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignAdd).?, .add)),
+        .AssignAddWrap => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignAddWrap).?, .addwrap)),
+        .AssignMul => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignMul).?, .mul)),
+        .AssignMulWrap => return rlWrapVoid(mod, scope, rl, node, try assignOp(mod, scope, node.castTag(.AssignMulWrap).?, .mulwrap)),
 
         .Add => return simpleBinOp(mod, scope, rl, node.castTag(.Add).?, .add),
         .AddWrap => return simpleBinOp(mod, scope, rl, node.castTag(.AddWrap).?, .addwrap),
@@ -96,6 +97,7 @@ pub fn expr(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node) InnerEr
         .Unreachable => return unreach(mod, scope, node.castTag(.Unreachable).?),
         .Return => return ret(mod, scope, node.castTag(.Return).?),
         .If => return ifExpr(mod, scope, rl, node.castTag(.If).?),
+        .While => return whileExpr(mod, scope, rl, node.castTag(.While).?),
         .Period => return rlWrap(mod, scope, rl, try field(mod, scope, node.castTag(.Period).?)),
         .Deref => return rlWrap(mod, scope, rl, try deref(mod, scope, node.castTag(.Deref).?)),
         .BoolNot => return rlWrap(mod, scope, rl, try boolNot(mod, scope, node.castTag(.BoolNot).?)),
@@ -127,10 +129,7 @@ pub fn blockExpr(mod: *Module, parent_scope: *Scope, block_node: *ast.Node.Block
                 const var_decl_node = statement.castTag(.VarDecl).?;
                 scope = try varDecl(mod, scope, var_decl_node, &block_arena.allocator);
             },
-            .Assign => {
-                const ass = statement.castTag(.Assign).?;
-                try assign(mod, scope, ass);
-            },
+            .Assign => try assign(mod, scope, statement.castTag(.Assign).?),
             .AssignBitAnd => try assignOp(mod, scope, statement.castTag(.AssignBitAnd).?, .bitand),
             .AssignBitOr => try assignOp(mod, scope, statement.castTag(.AssignBitOr).?, .bitor),
             .AssignBitShiftLeft => try assignOp(mod, scope, statement.castTag(.AssignBitShiftLeft).?, .shl),
@@ -454,6 +453,132 @@ fn ifExpr(mod: *Module, scope: *Scope, rl: ResultLoc, if_node: *ast.Node.If) Inn
     return &block.base;
 }
 
+fn whileExpr(mod: *Module, scope: *Scope, rl: ResultLoc, while_node: *ast.Node.While) InnerError!*zir.Inst {
+    if (while_node.payload) |payload| {
+        return mod.failNode(scope, payload, "TODO implement astgen.whileExpr for optionals", .{});
+    }
+    if (while_node.@"else") |else_node| {
+        if (else_node.payload) |payload| {
+            return mod.failNode(scope, payload, "TODO implement astgen.whileExpr for error unions", .{});
+        }
+    }
+
+    var expr_scope: Scope.GenZIR = .{
+        .parent = scope,
+        .decl = scope.decl().?,
+        .arena = scope.arena(),
+        .instructions = .{},
+    };
+    defer expr_scope.instructions.deinit(mod.gpa);
+
+    var loop_scope: Scope.GenZIR = .{
+        .parent = &expr_scope.base,
+        .decl = expr_scope.decl,
+        .arena = expr_scope.arena,
+        .instructions = .{},
+    };
+    defer loop_scope.instructions.deinit(mod.gpa);
+
+    var continue_scope: Scope.GenZIR = .{
+        .parent = &loop_scope.base,
+        .decl = loop_scope.decl,
+        .arena = loop_scope.arena,
+        .instructions = .{},
+    };
+    defer continue_scope.instructions.deinit(mod.gpa);
+
+    const tree = scope.tree();
+    const while_src = tree.token_locs[while_node.while_token].start;
+    const bool_type = try addZIRInstConst(mod, scope, while_src, .{
+        .ty = Type.initTag(.type),
+        .val = Value.initTag(.bool_type),
+    });
+    const void_type = try addZIRInstConst(mod, scope, while_src, .{
+        .ty = Type.initTag(.type),
+        .val = Value.initTag(.void_type),
+    });
+    const cond = try expr(mod, &continue_scope.base, .{ .ty = bool_type }, while_node.condition);
+
+    const condbr = try addZIRInstSpecial(mod, &continue_scope.base, while_src, zir.Inst.CondBr, .{
+        .condition = cond,
+        .then_body = undefined, // populated below
+        .else_body = undefined, // populated below
+    }, .{});
+    const cond_block = try addZIRInstBlock(mod, &loop_scope.base, while_src, .{
+        .instructions = try loop_scope.arena.dupe(*zir.Inst, continue_scope.instructions.items),
+    });
+    if (while_node.continue_expr) |cont_expr| {
+        const cont_expr_result = try expr(mod, &loop_scope.base, .{ .ty = void_type }, cont_expr);
+        if (!cont_expr_result.tag.isNoReturn()) {
+            _ = try addZIRNoOp(mod, &loop_scope.base, while_src, .repeat);
+        }
+    } else {
+        _ = try addZIRNoOp(mod, &loop_scope.base, while_src, .repeat);
+    }
+    const loop = try addZIRInstLoop(mod, &expr_scope.base, while_src, .{
+        .instructions = try expr_scope.arena.dupe(*zir.Inst, loop_scope.instructions.items),
+    });
+    const while_block = try addZIRInstBlock(mod, scope, while_src, .{
+        .instructions = try expr_scope.arena.dupe(*zir.Inst, expr_scope.instructions.items),
+    });
+    var then_scope: Scope.GenZIR = .{
+        .parent = &continue_scope.base,
+        .decl = continue_scope.decl,
+        .arena = continue_scope.arena,
+        .instructions = .{},
+    };
+    defer then_scope.instructions.deinit(mod.gpa);
+
+    // Most result location types can be forwarded directly; however
+    // if we need to write to a pointer which has an inferred type,
+    // proper type inference requires peer type resolution on the while's
+    // branches.
+    const branch_rl: ResultLoc = switch (rl) {
+        .discard, .none, .ty, .ptr, .lvalue => rl,
+        .inferred_ptr, .bitcasted_ptr, .block_ptr => .{ .block_ptr = while_block },
+    };
+
+    const then_result = try expr(mod, &then_scope.base, branch_rl, while_node.body);
+    if (!then_result.tag.isNoReturn()) {
+        const then_src = tree.token_locs[while_node.body.lastToken()].start;
+        _ = try addZIRInst(mod, &then_scope.base, then_src, zir.Inst.Break, .{
+            .block = cond_block,
+            .operand = then_result,
+        }, .{});
+    }
+    condbr.positionals.then_body = .{
+        .instructions = try then_scope.arena.dupe(*zir.Inst, then_scope.instructions.items),
+    };
+
+    var else_scope: Scope.GenZIR = .{
+        .parent = &continue_scope.base,
+        .decl = continue_scope.decl,
+        .arena = continue_scope.arena,
+        .instructions = .{},
+    };
+    defer else_scope.instructions.deinit(mod.gpa);
+
+    if (while_node.@"else") |else_node| {
+        const else_result = try expr(mod, &else_scope.base, branch_rl, else_node.body);
+        if (!else_result.tag.isNoReturn()) {
+            const else_src = tree.token_locs[else_node.body.lastToken()].start;
+            _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.Break, .{
+                .block = while_block,
+                .operand = else_result,
+            }, .{});
+        }
+    } else {
+        const else_src = tree.token_locs[while_node.lastToken()].start;
+        _ = try addZIRInst(mod, &else_scope.base, else_src, zir.Inst.BreakVoid, .{
+            .block = while_block,
+        }, .{});
+    }
+    condbr.positionals.else_body = .{
+        .instructions = try else_scope.arena.dupe(*zir.Inst, else_scope.instructions.items),
+    };
+    return &while_block.base;
+}
+
 fn ret(mod: *Module, scope: *Scope, cfe: *ast.Node.ControlFlowExpression) InnerError!*zir.Inst {
     const tree = scope.tree();
     const src = tree.token_locs[cfe.ltoken].start;
@@ -1094,6 +1219,15 @@ fn rlWrap(mod: *Module, scope: *Scope, rl: ResultLoc, result: *zir.Inst) InnerEr
     }
 }
 
+fn rlWrapVoid(mod: *Module, scope: *Scope, rl: ResultLoc, node: *ast.Node, result: void) InnerError!*zir.Inst {
+    const src = scope.tree().token_locs[node.firstToken()].start;
+    const void_inst = try addZIRInstConst(mod, scope, src, .{
+        .ty = Type.initTag(.void),
+        .val = Value.initTag(.void_value),
+    });
+    return rlWrap(mod, scope, rl, void_inst);
+}
+
 pub fn addZIRInstSpecial(
     mod: *Module,
     scope: *Scope,
@@ -1211,3 +1345,9 @@ pub fn addZIRInstBlock(mod: *Module, scope: *Scope, src: usize, body: zir.Module
     const P = std.meta.fieldInfo(zir.Inst.Block, "positionals").field_type;
     return addZIRInstSpecial(mod, scope, src, zir.Inst.Block, P{ .body = body }, .{});
 }
+
+/// TODO The existence of this function is a workaround for a bug in stage1.
+pub fn addZIRInstLoop(mod: *Module, scope: *Scope, src: usize, body: zir.Module.Body) !*zir.Inst.Loop {
+    const P = std.meta.fieldInfo(zir.Inst.Loop, "positionals").field_type;
+    return addZIRInstSpecial(mod, scope, src, zir.Inst.Loop, P{ .body = body }, .{});
+}
src-self-hosted/codegen.zig
@@ -23,6 +23,8 @@ pub const BlockData = struct {
     relocs: std.ArrayListUnmanaged(Reloc) = .{},
 };
 
+pub const LoopData = struct { };
+
 pub const Reloc = union(enum) {
     /// The value is an offset into the `Function` `code` from the beginning.
     /// To perform the reloc, write 32-bit signed little-endian integer
@@ -657,6 +659,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                 .isnonnull => return self.genIsNonNull(inst.castTag(.isnonnull).?),
                 .isnull => return self.genIsNull(inst.castTag(.isnull).?),
                 .load => return self.genLoad(inst.castTag(.load).?),
+                .loop => return self.genLoop(inst.castTag(.loop).?),
                 .not => return self.genNot(inst.castTag(.not).?),
                 .ptrtoint => return self.genPtrToInt(inst.castTag(.ptrtoint).?),
                 .ref => return self.genRef(inst.castTag(.ref).?),
@@ -1346,6 +1349,10 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             }
         }
 
+        fn genLoop(self: *Self, inst: *ir.Inst.Loop) !MCValue {
+            return self.fail(inst.base.src, "TODO codegen loop", .{});
+        }
+
         fn genBlock(self: *Self, inst: *ir.Inst.Block) !MCValue {
             if (inst.base.ty.hasCodeGenBits()) {
                 return self.fail(inst.base.src, "TODO codegen Block with non-void type", .{});
src-self-hosted/ir.zig
@@ -70,6 +70,7 @@ pub const Inst = struct {
         isnull,
         /// Read a value from a pointer.
         load,
+        loop,
         ptrtoint,
         ref,
         ret,
@@ -122,6 +123,7 @@ pub const Inst = struct {
                 .call => Call,
                 .condbr => CondBr,
                 .constant => Constant,
+                .loop => Loop,
             };
         }
 
@@ -401,6 +403,23 @@ pub const Inst = struct {
             return null;
         }
     };
+
+    pub const Loop = struct {
+        pub const base_tag = Tag.loop;
+
+        base: Inst,
+        body: Body,
+        /// This memory is reserved for codegen code to do whatever it needs to here.
+        codegen: codegen.LoopData = .{},
+
+        pub fn operandCount(self: *const Loop) usize {
+            return 0;
+        }
+        pub fn getOperand(self: *const Loop, index: usize) ?*Inst {
+            return null;
+        }
+    };
+
 };
 
 pub const Body = struct {
src-self-hosted/link.zig
@@ -1484,9 +1484,6 @@ pub const File = struct {
             assert(!self.shdr_table_dirty);
             assert(!self.shstrtab_dirty);
             assert(!self.debug_strtab_dirty);
-            assert(!self.offset_table_count_dirty);
-            const syms_sect = &self.sections.items[self.symtab_section_index.?];
-            assert(syms_sect.sh_info == self.local_symbols.items.len);
         }
 
         fn writeDwarfAddrAssumeCapacity(self: *Elf, buf: *std.ArrayList(u8), addr: u64) void {
src-self-hosted/zir.zig
@@ -151,6 +151,8 @@ pub const Inst = struct {
         isnonnull,
         /// Return a boolean true if an optional is null. `x == null`
         isnull,
+        /// A labeled block of code that loops forever.
+        loop,
         /// Ambiguously remainder division or modulus. If the computation would possibly have
         /// a different value depending on whether the operation is remainder division or modulus,
         /// a compile error is emitted. Otherwise the computation is performed.
@@ -173,6 +175,8 @@ pub const Inst = struct {
         /// the memory location is in the stack frame, local to the scope containing the
         /// instruction.
         ref,
+        /// Sends control flow back to the loop block operand.
+        repeat,
         /// Obtains a pointer to the return value.
         ret_ptr,
         /// Obtains the return type of the in-scope function.
@@ -279,7 +283,9 @@ pub const Inst = struct {
                 .declval_in_module => DeclValInModule,
                 .coerce_result_block_ptr => CoerceResultBlockPtr,
                 .compileerror => CompileError,
+                .loop => Loop,
                 .@"const" => Const,
+                .repeat => Repeat,
                 .str => Str,
                 .int => Int,
                 .inttype => IntType,
@@ -372,10 +378,12 @@ pub const Inst = struct {
                 .breakvoid,
                 .condbr,
                 .compileerror,
+                .repeat,
                 .@"return",
                 .returnvoid,
                 .unreach_nocheck,
                 .@"unreachable",
+                .loop,
                 => true,
             };
         }
@@ -567,6 +575,16 @@ pub const Inst = struct {
         kw_args: struct {},
     };
 
+    pub const Repeat = struct {
+        pub const base_tag = Tag.repeat;
+        base: Inst,
+
+        positionals: struct {
+            loop: *Loop,
+        },
+        kw_args: struct {},
+    };
+
     pub const Str = struct {
         pub const base_tag = Tag.str;
         base: Inst,
@@ -587,6 +605,16 @@ pub const Inst = struct {
         kw_args: struct {},
     };
 
+    pub const Loop = struct {
+        pub const base_tag = Tag.loop;
+        base: Inst,
+
+        positionals: struct {
+            body: Module.Body,
+        },
+        kw_args: struct {},
+    };
+
     pub const FieldPtr = struct {
         pub const base_tag = Tag.fieldptr;
         base: Inst,
@@ -848,12 +876,14 @@ pub const Module = struct {
             .module = &self,
             .inst_table = InstPtrTable.init(allocator),
             .block_table = std.AutoHashMap(*Inst.Block, []const u8).init(allocator),
+            .loop_table = std.AutoHashMap(*Inst.Loop, []const u8).init(allocator),
             .arena = std.heap.ArenaAllocator.init(allocator),
             .indent = 2,
         };
         defer write.arena.deinit();
         defer write.inst_table.deinit();
         defer write.block_table.deinit();
+        defer write.loop_table.deinit();
 
         // First, build a map of *Inst to @ or % indexes
         try write.inst_table.ensureCapacity(self.decls.len);
@@ -882,6 +912,7 @@ const Writer = struct {
     module: *const Module,
     inst_table: InstPtrTable,
     block_table: std.AutoHashMap(*Inst.Block, []const u8),
+    loop_table: std.AutoHashMap(*Inst.Loop, []const u8),
     arena: std.heap.ArenaAllocator,
     indent: usize,
 
@@ -962,6 +993,9 @@ const Writer = struct {
                     if (inst.cast(Inst.Block)) |block| {
                         const name = try std.fmt.allocPrint(&self.arena.allocator, "label_{}", .{i});
                         try self.block_table.put(block, name);
+                    } else if (inst.cast(Inst.Loop)) |loop| {
+                        const name = try std.fmt.allocPrint(&self.arena.allocator, "loop_{}", .{i});
+                        try self.loop_table.put(loop, name);
                     }
                     self.indent += 2;
                     try self.writeInstToStream(stream, inst);
@@ -980,6 +1014,10 @@ const Writer = struct {
                 const name = self.block_table.get(param).?;
                 return std.zig.renderStringLiteral(name, stream);
             },
+            *Inst.Loop => {
+                const name = self.loop_table.get(param).?;
+                return std.zig.renderStringLiteral(name, stream);
+            },
             else => |T| @compileError("unimplemented: rendering parameter of type " ++ @typeName(T)),
         }
     }
@@ -1016,8 +1054,10 @@ pub fn parse(allocator: *Allocator, source: [:0]const u8) Allocator.Error!Module
         .decls = .{},
         .unnamed_index = 0,
         .block_table = std.StringHashMap(*Inst.Block).init(allocator),
+        .loop_table = std.StringHashMap(*Inst.Loop).init(allocator),
     };
     defer parser.block_table.deinit();
+    defer parser.loop_table.deinit();
     errdefer parser.arena.deinit();
 
     parser.parseRoot() catch |err| switch (err) {
@@ -1044,6 +1084,7 @@ const Parser = struct {
     error_msg: ?ErrorMsg = null,
     unnamed_index: usize,
     block_table: std.StringHashMap(*Inst.Block),
+    loop_table: std.StringHashMap(*Inst.Loop),
 
     const Body = struct {
         instructions: std.ArrayList(*Inst),
@@ -1255,6 +1296,8 @@ const Parser = struct {
 
         if (InstType == Inst.Block) {
             try self.block_table.put(inst_name, inst_specific);
+        } else if (InstType == Inst.Loop) {
+            try self.loop_table.put(inst_name, inst_specific);
         }
 
         if (@hasField(InstType, "ty")) {
@@ -1366,6 +1409,10 @@ const Parser = struct {
                 const name = try self.parseStringLiteral();
                 return self.block_table.get(name).?;
             },
+            *Inst.Loop => {
+                const name = try self.parseStringLiteral();
+                return self.loop_table.get(name).?;
+            },
             else => @compileError("Unimplemented: ir parseParameterGeneric for type " ++ @typeName(T)),
         }
         return self.fail("TODO parse parameter {}", .{@typeName(T)});
@@ -1431,8 +1478,10 @@ pub fn emit(allocator: *Allocator, old_module: IrModule) !Module {
         .primitive_table = std.AutoHashMap(Inst.Primitive.Builtin, *Decl).init(allocator),
         .indent = 0,
         .block_table = std.AutoHashMap(*ir.Inst.Block, *Inst.Block).init(allocator),
+        .loop_table = std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop).init(allocator),
     };
     defer ctx.block_table.deinit();
+    defer ctx.loop_table.deinit();
     defer ctx.decls.deinit(allocator);
     defer ctx.names.deinit();
     defer ctx.primitive_table.deinit();
@@ -1456,6 +1505,7 @@ const EmitZIR = struct {
     primitive_table: std.AutoHashMap(Inst.Primitive.Builtin, *Decl),
     indent: usize,
     block_table: std.AutoHashMap(*ir.Inst.Block, *Inst.Block),
+    loop_table: std.AutoHashMap(*ir.Inst.Loop, *Inst.Loop),
 
     fn emit(self: *EmitZIR) !void {
         // Put all the Decls in a list and sort them by name to avoid nondeterminism introduced
@@ -1936,6 +1986,31 @@ const EmitZIR = struct {
                     break :blk &new_inst.base;
                 },
 
+                .loop => blk: {
+                    const old_inst = inst.castTag(.loop).?;
+                    const new_inst = try self.arena.allocator.create(Inst.Loop);
+
+                    try self.loop_table.put(old_inst, new_inst);
+
+                    var loop_body = std.ArrayList(*Inst).init(self.allocator);
+                    defer loop_body.deinit();
+
+                    try self.emitBody(old_inst.body, inst_table, &loop_body);
+
+                    new_inst.* = .{
+                        .base = .{
+                            .src = inst.src,
+                            .tag = Inst.Loop.base_tag,
+                        },
+                        .positionals = .{
+                            .body = .{ .instructions = loop_body.toOwnedSlice() },
+                        },
+                        .kw_args = .{},
+                    };
+
+                    break :blk &new_inst.base;
+                },
+
                 .brvoid => blk: {
                     const old_inst = inst.cast(ir.Inst.BrVoid).?;
                     const new_block = self.block_table.get(old_inst.block).?;
src-self-hosted/zir_sema.zig
@@ -60,6 +60,8 @@ pub fn analyzeInst(mod: *Module, scope: *Scope, old_inst: *zir.Inst) InnerError!
             return mod.constIntBig(scope, old_inst.src, Type.initTag(.comptime_int), big_int);
         },
         .inttype => return analyzeInstIntType(mod, scope, old_inst.castTag(.inttype).?),
+        .loop => return analyzeInstLoop(mod, scope, old_inst.castTag(.loop).?),
+        .repeat => return analyzeInstRepeat(mod, scope, old_inst.castTag(.repeat).?),
         .param_type => return analyzeInstParamType(mod, scope, old_inst.castTag(.param_type).?),
         .ptrtoint => return analyzeInstPtrToInt(mod, scope, old_inst.castTag(.ptrtoint).?),
         .fieldptr => return analyzeInstFieldPtr(mod, scope, old_inst.castTag(.fieldptr).?),
@@ -424,6 +426,14 @@ fn analyzeInstArg(mod: *Module, scope: *Scope, inst: *zir.Inst.Arg) InnerError!*
     return mod.addArg(b, inst.base.src, param_type, name);
 }
 
+fn analyzeInstRepeat(mod: *Module, scope: *Scope, inst: *zir.Inst.Repeat) InnerError!*Inst {
+    return mod.fail(scope, inst.base.src, "TODO analyze .repeat ZIR", .{});
+}
+
+fn analyzeInstLoop(mod: *Module, scope: *Scope, inst: *zir.Inst.Loop) InnerError!*Inst {
+    return mod.fail(scope, inst.base.src, "TODO analyze .loop ZIR", .{});
+}
+
 fn analyzeInstBlock(mod: *Module, scope: *Scope, inst: *zir.Inst.Block) InnerError!*Inst {
     const parent_block = scope.cast(Scope.Block).?;