Skip to content

introduce a "try" ZIR and AIR instruction #11783

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 6, 2022
33 changes: 33 additions & 0 deletions src/Air.zig
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,20 @@ pub const Inst = struct {
/// Result type is always noreturn; no instructions in a block follow this one.
/// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`.
switch_br,
/// Given an operand which is an error union, splits control flow. In
/// case of error, control flow goes into the block that is part of this
/// instruction, which is guaranteed to end with a return instruction
/// and never breaks out of the block.
/// In the case of non-error, control flow proceeds to the next instruction
/// after the `try`, with the result of this instruction being the unwrapped
/// payload value, as if `unwrap_errunion_payload` was executed on the operand.
/// Uses the `pl_op` field. Payload is `Try`.
@"try",
/// Same as `try` except the operand is a pointer to an error union, and the
/// result is a pointer to the payload. Result is as if `unwrap_errunion_payload_ptr`
/// was executed on the operand.
/// Uses the `ty_pl` field. Payload is `TryPtr`.
try_ptr,
/// A comptime-known value. Uses the `ty_pl` field, payload is index of
/// `values` array.
constant,
Expand Down Expand Up @@ -780,6 +794,19 @@ pub const SwitchBr = struct {
};
};

/// This data is stored inside extra. Trailing:
/// 0. body: Inst.Index // for each body_len
pub const Try = struct {
body_len: u32,
};

/// This data is stored inside extra. Trailing:
/// 0. body: Inst.Index // for each body_len
pub const TryPtr = struct {
ptr: Inst.Ref,
body_len: u32,
};

pub const StructField = struct {
/// Whether this is a pointer or byval is determined by the AIR tag.
struct_operand: Inst.Ref,
Expand Down Expand Up @@ -991,6 +1018,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
.shl_with_overflow,
.ptr_add,
.ptr_sub,
.try_ptr,
=> return air.getRefType(datas[inst].ty_pl.ty),

.not,
Expand Down Expand Up @@ -1102,6 +1130,11 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
const extra = air.extraData(Air.Bin, datas[inst].pl_op.payload).data;
return air.typeOf(extra.lhs);
},

.@"try" => {
const err_union_ty = air.typeOf(datas[inst].pl_op.operand);
return err_union_ty.errorUnionPayload();
},
}
}

Expand Down
99 changes: 47 additions & 52 deletions src/AstGen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2425,6 +2425,10 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
.param_type,
.ret_ptr,
.ret_type,
.@"try",
.try_ptr,
//.try_inline,
//.try_ptr_inline,
=> break :b false,

.extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) {
Expand Down Expand Up @@ -4871,68 +4875,43 @@ fn tryExpr(

if (parent_gz.in_defer) return astgen.failNode(node, "'try' not allowed inside defer expression", .{});

var block_scope = parent_gz.makeSubBlock(scope);
block_scope.setBreakResultLoc(rl);
defer block_scope.unstack();

const operand_rl: ResultLoc = switch (block_scope.break_result_loc) {
const operand_rl: ResultLoc = switch (rl) {
.ref => .ref,
else => .none,
};
const err_ops = switch (operand_rl) {
// zig fmt: off
.ref => [3]Zir.Inst.Tag{ .is_non_err_ptr, .err_union_code_ptr, .err_union_payload_unsafe_ptr },
else => [3]Zir.Inst.Tag{ .is_non_err, .err_union_code, .err_union_payload_unsafe },
// zig fmt: on
};
// This could be a pointer or value depending on the `operand_rl` parameter.
// We cannot use `block_scope.break_result_loc` because that has the bare
// type, whereas this expression has the optional type. Later we make
// up for this fact by calling rvalue on the else branch.
const operand = try expr(&block_scope, &block_scope.base, operand_rl, operand_node);
const cond = try block_scope.addUnNode(err_ops[0], operand, node);
const condbr = try block_scope.addCondBr(.condbr, node);

const block = try parent_gz.makeBlockInst(.block, node);
try block_scope.setBlockBody(block);
// block_scope unstacked now, can add new instructions to parent_gz
try parent_gz.instructions.append(astgen.gpa, block);

var then_scope = parent_gz.makeSubBlock(scope);
defer then_scope.unstack();

block_scope.break_count += 1;
// This could be a pointer or value depending on `err_ops[2]`.
const unwrapped_payload = try then_scope.addUnNode(err_ops[2], operand, node);
const then_result = switch (rl) {
.ref => unwrapped_payload,
else => try rvalue(&then_scope, block_scope.break_result_loc, unwrapped_payload, node),
// This could be a pointer or value depending on the `rl` parameter.
const operand = try expr(parent_gz, scope, operand_rl, operand_node);
const is_inline = parent_gz.force_comptime;
const is_inline_bit = @as(u2, @boolToInt(is_inline));
const is_ptr_bit = @as(u2, @boolToInt(operand_rl == .ref)) << 1;
const block_tag: Zir.Inst.Tag = switch (is_inline_bit | is_ptr_bit) {
0b00 => .@"try",
0b01 => .@"try",
//0b01 => .try_inline,
0b10 => .try_ptr,
0b11 => .try_ptr,
//0b11 => .try_ptr_inline,
};
const try_inst = try parent_gz.makeBlockInst(block_tag, node);
try parent_gz.instructions.append(astgen.gpa, try_inst);

// else_scope will be stacked on then_scope as both are stacked on parent_gz
var else_scope = parent_gz.makeSubBlock(scope);
defer else_scope.unstack();

const err_code = try else_scope.addUnNode(err_ops[1], operand, node);
const err_tag = switch (rl) {
.ref => Zir.Inst.Tag.err_union_code_ptr,
else => Zir.Inst.Tag.err_union_code,
};
const err_code = try else_scope.addUnNode(err_tag, operand, node);
try genDefers(&else_scope, &fn_block.base, scope, .{ .both = err_code });
const else_result = try else_scope.addUnNode(.ret_node, err_code, node);
_ = try else_scope.addUnNode(.ret_node, err_code, node);

const break_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .break_inline else .@"break";
return finishThenElseBlock(
parent_gz,
rl,
node,
&block_scope,
&then_scope,
&else_scope,
condbr,
cond,
then_result,
else_result,
block,
block,
break_tag,
);
try else_scope.setTryBody(try_inst, operand);
const result = indexToRef(try_inst);
switch (rl) {
.ref => return result,
else => return rvalue(parent_gz, rl, result, node),
}
}

fn orelseCatchExpr(
Expand Down Expand Up @@ -10011,6 +9990,22 @@ const GenZir = struct {
gz.unstack();
}

/// Assumes nothing stacked on `gz`. Unstacks `gz`.
fn setTryBody(gz: *GenZir, inst: Zir.Inst.Index, operand: Zir.Inst.Ref) !void {
const gpa = gz.astgen.gpa;
const body = gz.instructionsSlice();
try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Try).Struct.fields.len + body.len);
const zir_datas = gz.astgen.instructions.items(.data);
zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity(
Zir.Inst.Try{
.operand = operand,
.body_len = @intCast(u32, body.len),
},
);
gz.astgen.extra.appendSliceAssumeCapacity(body);
gz.unstack();
}

/// Must be called with the following stack set up:
/// * gz (bottom)
/// * align_gz
Expand Down
19 changes: 19 additions & 0 deletions src/Liveness.zig
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,12 @@ pub fn categorizeOperand(
.block => {
return .complex;
},
.@"try" => {
return .complex;
},
.try_ptr => {
return .complex;
},
.loop => {
return .complex;
},
Expand Down Expand Up @@ -1019,6 +1025,19 @@ fn analyzeInst(
try analyzeWithContext(a, new_set, body);
return; // Loop has no operands and it is always unreferenced.
},
.@"try" => {
const pl_op = inst_datas[inst].pl_op;
const extra = a.air.extraData(Air.Try, pl_op.payload);
const body = a.air.extra[extra.end..][0..extra.data.body_len];
try analyzeWithContext(a, new_set, body);
return trackOperands(a, new_set, inst, main_tomb, .{ pl_op.operand, .none, .none });
},
.try_ptr => {
const extra = a.air.extraData(Air.TryPtr, inst_datas[inst].ty_pl.payload);
const body = a.air.extra[extra.end..][0..extra.data.body_len];
try analyzeWithContext(a, new_set, body);
return trackOperands(a, new_set, inst, main_tomb, .{ extra.data.ptr, .none, .none });
},
.cond_br => {
// Each death that occurs inside one branch, but not the other, needs
// to be added as a death immediately upon entering the other branch.
Expand Down
Loading