whileExpr
Function parameters
Parameters
- parent_gz:*GenZir
- scope:*Scope
- node:Ast.Node.Index
- while_full:Ast.full.While
- is_statement:bool
Functions in this namespace
Functions
Source
Implementation
fn whileExpr(
parent_gz: *GenZir,
scope: *Scope,
ri: ResultInfo,
node: Ast.Node.Index,
while_full: Ast.full.While,
is_statement: bool,
) InnerError!Zir.Inst.Ref {
const astgen = parent_gz.astgen;
const tree = astgen.tree;
const need_rl = astgen.nodes_need_rl.contains(node);
const block_ri: ResultInfo = if (need_rl) ri else .{
.rl = switch (ri.rl) {
.ptr => .{ .ty = (try ri.rl.resultType(parent_gz, node)).? },
.inferred_ptr => .none,
else => ri.rl,
},
.ctx = ri.ctx,
};
// We need to call `rvalue` to write through to the pointer only if we had a
// result pointer and aren't forwarding it.
const LocTag = @typeInfo(ResultInfo.Loc).@"union".tag_type.?;
const need_result_rvalue = @as(LocTag, block_ri.rl) != @as(LocTag, ri.rl);
if (while_full.label_token) |label_token| {
try astgen.checkLabelRedefinition(scope, label_token);
}
const is_inline = while_full.inline_token != null;
if (parent_gz.is_comptime and is_inline) {
try astgen.appendErrorTok(while_full.inline_token.?, "redundant inline keyword in comptime scope", .{});
}
const loop_tag: Zir.Inst.Tag = if (is_inline) .block_inline else .loop;
const loop_block = try parent_gz.makeBlockInst(loop_tag, node);
try parent_gz.instructions.append(astgen.gpa, loop_block);
var loop_scope = parent_gz.makeSubBlock(scope);
loop_scope.is_inline = is_inline;
loop_scope.setBreakResultInfo(block_ri);
defer loop_scope.unstack();
var cond_scope = parent_gz.makeSubBlock(&loop_scope.base);
defer cond_scope.unstack();
const payload_is_ref = if (while_full.payload_token) |payload_token|
tree.tokenTag(payload_token) == .asterisk
else
false;
try emitDbgNode(parent_gz, while_full.ast.cond_expr);
const cond: struct {
inst: Zir.Inst.Ref,
bool_bit: Zir.Inst.Ref,
} = c: {
if (while_full.error_token) |_| {
const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none };
const err_union = try fullBodyExpr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr, .normal);
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_err_ptr else .is_non_err;
break :c .{
.inst = err_union,
.bool_bit = try cond_scope.addUnNode(tag, err_union, while_full.ast.cond_expr),
};
} else if (while_full.payload_token) |_| {
const cond_ri: ResultInfo = .{ .rl = if (payload_is_ref) .ref else .none };
const optional = try fullBodyExpr(&cond_scope, &cond_scope.base, cond_ri, while_full.ast.cond_expr, .normal);
const tag: Zir.Inst.Tag = if (payload_is_ref) .is_non_null_ptr else .is_non_null;
break :c .{
.inst = optional,
.bool_bit = try cond_scope.addUnNode(tag, optional, while_full.ast.cond_expr),
};
} else {
const cond = try fullBodyExpr(&cond_scope, &cond_scope.base, coerced_bool_ri, while_full.ast.cond_expr, .normal);
break :c .{
.inst = cond,
.bool_bit = cond,
};
}
};
const condbr_tag: Zir.Inst.Tag = if (is_inline) .condbr_inline else .condbr;
const condbr = try cond_scope.addCondBr(condbr_tag, node);
const block_tag: Zir.Inst.Tag = if (is_inline) .block_inline else .block;
const cond_block = try loop_scope.makeBlockInst(block_tag, node);
try cond_scope.setBlockBody(cond_block);
// cond_scope unstacked now, can add new instructions to loop_scope
try loop_scope.instructions.append(astgen.gpa, cond_block);
// make scope now but don't stack on parent_gz until loop_scope
// gets unstacked after cont_expr is emitted and added below
var then_scope = parent_gz.makeSubBlock(&cond_scope.base);
then_scope.instructions_top = GenZir.unstacked_top;
defer then_scope.unstack();
var dbg_var_name: Zir.NullTerminatedString = .empty;
var dbg_var_inst: Zir.Inst.Ref = undefined;
var opt_payload_inst: Zir.Inst.OptionalIndex = .none;
var payload_val_scope: Scope.LocalVal = undefined;
const then_sub_scope = s: {
if (while_full.error_token != null) {
if (while_full.payload_token) |payload_token| {
const tag: Zir.Inst.Tag = if (payload_is_ref)
.err_union_payload_unsafe_ptr
else
.err_union_payload_unsafe;
// will add this instruction to then_scope.instructions below
const payload_inst = try then_scope.makeUnNode(tag, cond.inst, while_full.ast.cond_expr);
opt_payload_inst = payload_inst.toOptional();
const ident_token = payload_token + @intFromBool(payload_is_ref);
const ident_bytes = tree.tokenSlice(ident_token);
if (mem.eql(u8, "_", ident_bytes)) {
if (payload_is_ref) return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
break :s &then_scope.base;
}
const ident_name = try astgen.identAsString(ident_token);
try astgen.detectLocalShadowing(&then_scope.base, ident_name, ident_token, ident_bytes, .capture);
payload_val_scope = .{
.parent = &then_scope.base,
.gen_zir = &then_scope,
.name = ident_name,
.inst = payload_inst.toRef(),
.token_src = ident_token,
.id_cat = .capture,
};
dbg_var_name = ident_name;
dbg_var_inst = payload_inst.toRef();
break :s &payload_val_scope.base;
} else {
_ = try then_scope.addUnNode(.ensure_err_union_payload_void, cond.inst, node);
break :s &then_scope.base;
}
} else if (while_full.payload_token) |payload_token| {
const tag: Zir.Inst.Tag = if (payload_is_ref)
.optional_payload_unsafe_ptr
else
.optional_payload_unsafe;
// will add this instruction to then_scope.instructions below
const payload_inst = try then_scope.makeUnNode(tag, cond.inst, while_full.ast.cond_expr);
opt_payload_inst = payload_inst.toOptional();
const ident_token = payload_token + @intFromBool(payload_is_ref);
const ident_name = try astgen.identAsString(ident_token);
const ident_bytes = tree.tokenSlice(ident_token);
if (mem.eql(u8, "_", ident_bytes)) {
if (payload_is_ref) return astgen.failTok(payload_token, "pointer modifier invalid on discard", .{});
break :s &then_scope.base;
}
try astgen.detectLocalShadowing(&then_scope.base, ident_name, ident_token, ident_bytes, .capture);
payload_val_scope = .{
.parent = &then_scope.base,
.gen_zir = &then_scope,
.name = ident_name,
.inst = payload_inst.toRef(),
.token_src = ident_token,
.id_cat = .capture,
};
dbg_var_name = ident_name;
dbg_var_inst = payload_inst.toRef();
break :s &payload_val_scope.base;
} else {
break :s &then_scope.base;
}
};
var continue_scope = parent_gz.makeSubBlock(then_sub_scope);
continue_scope.instructions_top = GenZir.unstacked_top;
defer continue_scope.unstack();
const continue_block = try then_scope.makeBlockInst(block_tag, node);
const repeat_tag: Zir.Inst.Tag = if (is_inline) .repeat_inline else .repeat;
_ = try loop_scope.addNode(repeat_tag, node);
try loop_scope.setBlockBody(loop_block);
loop_scope.break_block = loop_block.toOptional();
loop_scope.continue_block = continue_block.toOptional();
if (while_full.label_token) |label_token| {
loop_scope.label = .{
.token = label_token,
.block_inst = loop_block,
};
}
// done adding instructions to loop_scope, can now stack then_scope
then_scope.instructions_top = then_scope.instructions.items.len;
const then_node = while_full.ast.then_expr;
if (opt_payload_inst.unwrap()) |payload_inst| {
try then_scope.instructions.append(astgen.gpa, payload_inst);
}
if (dbg_var_name != .empty) try then_scope.addDbgVar(.dbg_var_val, dbg_var_name, dbg_var_inst);
try then_scope.instructions.append(astgen.gpa, continue_block);
// This code could be improved to avoid emitting the continue expr when there
// are no jumps to it. This happens when the last statement of a while body is noreturn
// and there are no `continue` statements.
// Tracking issue: https://github.com/ziglang/zig/issues/9185
if (while_full.ast.cont_expr.unwrap()) |cont_expr| {
_ = try unusedResultExpr(&then_scope, then_sub_scope, cont_expr);
}
continue_scope.instructions_top = continue_scope.instructions.items.len;
{
try emitDbgNode(&continue_scope, then_node);
const unused_result = try fullBodyExpr(&continue_scope, &continue_scope.base, .{ .rl = .none }, then_node, .allow_branch_hint);
_ = try addEnsureResult(&continue_scope, unused_result, then_node);
}
try checkUsed(parent_gz, &then_scope.base, then_sub_scope);
const break_tag: Zir.Inst.Tag = if (is_inline) .break_inline else .@"break";
if (!continue_scope.endsWithNoReturn()) {
astgen.advanceSourceCursor(tree.tokenStart(tree.lastToken(then_node)));
try emitDbgStmt(parent_gz, .{ astgen.source_line - parent_gz.decl_line, astgen.source_column });
_ = try parent_gz.add(.{
.tag = .extended,
.data = .{ .extended = .{
.opcode = .dbg_empty_stmt,
.small = undefined,
.operand = undefined,
} },
});
_ = try continue_scope.addBreak(break_tag, continue_block, .void_value);
}
try continue_scope.setBlockBody(continue_block);
_ = try then_scope.addBreak(break_tag, cond_block, .void_value);
var else_scope = parent_gz.makeSubBlock(&cond_scope.base);
defer else_scope.unstack();
if (while_full.ast.else_expr.unwrap()) |else_node| {
const sub_scope = s: {
if (while_full.error_token) |error_token| {
const tag: Zir.Inst.Tag = if (payload_is_ref)
.err_union_code_ptr
else
.err_union_code;
const else_payload_inst = try else_scope.addUnNode(tag, cond.inst, while_full.ast.cond_expr);
const ident_name = try astgen.identAsString(error_token);
const ident_bytes = tree.tokenSlice(error_token);
if (mem.eql(u8, ident_bytes, "_"))
break :s &else_scope.base;
try astgen.detectLocalShadowing(&else_scope.base, ident_name, error_token, ident_bytes, .capture);
payload_val_scope = .{
.parent = &else_scope.base,
.gen_zir = &else_scope,
.name = ident_name,
.inst = else_payload_inst,
.token_src = error_token,
.id_cat = .capture,
};
try else_scope.addDbgVar(.dbg_var_val, ident_name, else_payload_inst);
break :s &payload_val_scope.base;
} else {
break :s &else_scope.base;
}
};
// Remove the continue block and break block so that `continue` and `break`
// control flow apply to outer loops; not this one.
loop_scope.continue_block = .none;
loop_scope.break_block = .none;
const else_result = try fullBodyExpr(&else_scope, sub_scope, loop_scope.break_result_info, else_node, .allow_branch_hint);
if (is_statement) {
_ = try addEnsureResult(&else_scope, else_result, else_node);
}
try checkUsed(parent_gz, &else_scope.base, sub_scope);
if (!else_scope.endsWithNoReturn()) {
_ = try else_scope.addBreakWithSrcNode(break_tag, loop_block, else_result, else_node);
}
} else {
const result = try rvalue(&else_scope, ri, .void_value, node);
_ = try else_scope.addBreak(break_tag, loop_block, result);
}
if (loop_scope.label) |some| {
if (!some.used) {
try astgen.appendErrorTok(some.token, "unused while loop label", .{});
}
}
try setCondBrPayload(condbr, cond.bool_bit, &then_scope, &else_scope);
const result = if (need_result_rvalue)
try rvalue(parent_gz, ri, loop_block.toRef(), node)
else
loop_block.toRef();
if (is_statement) {
_ = try parent_gz.addUnNode(.ensure_result_used, result, node);
}
return result;
}