Commit de4f3f11f7
Changed files (6)
src-self-hosted
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).?;