Skip to content

Commit 33bc328

Browse files
committed
AstGen: introduce try instruction
This introduces two ZIR instructions: * `try` * `try_inline` This is part of an effort to implement #11772.
1 parent e498fb1 commit 33bc328

File tree

4 files changed

+150
-67
lines changed

4 files changed

+150
-67
lines changed

src/AstGen.zig

Lines changed: 33 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -2425,6 +2425,8 @@ fn unusedResultExpr(gz: *GenZir, scope: *Scope, statement: Ast.Node.Index) Inner
24252425
.param_type,
24262426
.ret_ptr,
24272427
.ret_type,
2428+
.@"try",
2429+
.try_inline,
24282430
=> break :b false,
24292431

24302432
.extended => switch (gz.astgen.instructions.items(.data)[inst].extended.opcode) {
@@ -4871,68 +4873,30 @@ fn tryExpr(
48714873

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

4874-
var block_scope = parent_gz.makeSubBlock(scope);
4875-
block_scope.setBreakResultLoc(rl);
4876-
defer block_scope.unstack();
4877-
4878-
const operand_rl: ResultLoc = switch (block_scope.break_result_loc) {
4876+
const operand_rl: ResultLoc = switch (rl) {
48794877
.ref => .ref,
48804878
else => .none,
48814879
};
4882-
const err_ops = switch (operand_rl) {
4883-
// zig fmt: off
4884-
.ref => [3]Zir.Inst.Tag{ .is_non_err_ptr, .err_union_code_ptr, .err_union_payload_unsafe_ptr },
4885-
else => [3]Zir.Inst.Tag{ .is_non_err, .err_union_code, .err_union_payload_unsafe },
4886-
// zig fmt: on
4887-
};
4888-
// This could be a pointer or value depending on the `operand_rl` parameter.
4889-
// We cannot use `block_scope.break_result_loc` because that has the bare
4890-
// type, whereas this expression has the optional type. Later we make
4891-
// up for this fact by calling rvalue on the else branch.
4892-
const operand = try expr(&block_scope, &block_scope.base, operand_rl, operand_node);
4893-
const cond = try block_scope.addUnNode(err_ops[0], operand, node);
4894-
const condbr = try block_scope.addCondBr(.condbr, node);
4880+
// This could be a pointer or value depending on the `rl` parameter.
4881+
const operand = try expr(parent_gz, scope, operand_rl, operand_node);
4882+
const is_inline = parent_gz.force_comptime;
4883+
const block_tag: Zir.Inst.Tag = if (is_inline) .try_inline else .@"try";
4884+
const try_inst = try parent_gz.makeBlockInst(block_tag, node);
4885+
try parent_gz.instructions.append(astgen.gpa, try_inst);
48954886

4896-
const block = try parent_gz.makeBlockInst(.block, node);
4897-
try block_scope.setBlockBody(block);
4898-
// block_scope unstacked now, can add new instructions to parent_gz
4899-
try parent_gz.instructions.append(astgen.gpa, block);
4900-
4901-
var then_scope = parent_gz.makeSubBlock(scope);
4902-
defer then_scope.unstack();
4903-
4904-
block_scope.break_count += 1;
4905-
// This could be a pointer or value depending on `err_ops[2]`.
4906-
const unwrapped_payload = try then_scope.addUnNode(err_ops[2], operand, node);
4907-
const then_result = switch (rl) {
4908-
.ref => unwrapped_payload,
4909-
else => try rvalue(&then_scope, block_scope.break_result_loc, unwrapped_payload, node),
4910-
};
4911-
4912-
// else_scope will be stacked on then_scope as both are stacked on parent_gz
49134887
var else_scope = parent_gz.makeSubBlock(scope);
49144888
defer else_scope.unstack();
49154889

4916-
const err_code = try else_scope.addUnNode(err_ops[1], operand, node);
4890+
const err_tag = switch (rl) {
4891+
.ref => Zir.Inst.Tag.err_union_code_ptr,
4892+
else => Zir.Inst.Tag.err_union_code,
4893+
};
4894+
const err_code = try else_scope.addUnNode(err_tag, operand, node);
49174895
try genDefers(&else_scope, &fn_block.base, scope, .{ .both = err_code });
4918-
const else_result = try else_scope.addUnNode(.ret_node, err_code, node);
4896+
_ = try else_scope.addUnNode(.ret_node, err_code, node);
49194897

4920-
const break_tag: Zir.Inst.Tag = if (parent_gz.force_comptime) .break_inline else .@"break";
4921-
return finishThenElseBlock(
4922-
parent_gz,
4923-
rl,
4924-
node,
4925-
&block_scope,
4926-
&then_scope,
4927-
&else_scope,
4928-
condbr,
4929-
cond,
4930-
then_result,
4931-
else_result,
4932-
block,
4933-
block,
4934-
break_tag,
4935-
);
4898+
try else_scope.setTryBody(try_inst, operand);
4899+
return indexToRef(try_inst);
49364900
}
49374901

49384902
fn orelseCatchExpr(
@@ -10011,6 +9975,22 @@ const GenZir = struct {
100119975
gz.unstack();
100129976
}
100139977

9978+
/// Assumes nothing stacked on `gz`. Unstacks `gz`.
9979+
fn setTryBody(gz: *GenZir, inst: Zir.Inst.Index, operand: Zir.Inst.Ref) !void {
9980+
const gpa = gz.astgen.gpa;
9981+
const body = gz.instructionsSlice();
9982+
try gz.astgen.extra.ensureUnusedCapacity(gpa, @typeInfo(Zir.Inst.Try).Struct.fields.len + body.len);
9983+
const zir_datas = gz.astgen.instructions.items(.data);
9984+
zir_datas[inst].pl_node.payload_index = gz.astgen.addExtraAssumeCapacity(
9985+
Zir.Inst.Try{
9986+
.operand = operand,
9987+
.body_len = @intCast(u32, body.len),
9988+
},
9989+
);
9990+
gz.astgen.extra.appendSliceAssumeCapacity(body);
9991+
gz.unstack();
9992+
}
9993+
100149994
/// Must be called with the following stack set up:
100159995
/// * gz (bottom)
100169996
/// * align_gz

src/Sema.zig

Lines changed: 63 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,13 @@ fn analyzeBodyInner(
13221322
break break_data.inst;
13231323
}
13241324
},
1325+
.@"try" => blk: {
1326+
if (!block.is_comptime) break :blk try sema.zirTry(block, inst);
1327+
@panic("TODO");
1328+
},
1329+
.try_inline => {
1330+
@panic("TODO");
1331+
},
13251332
};
13261333
if (sema.typeOf(air_inst).isNoReturn())
13271334
break always_noreturn;
@@ -6415,32 +6422,43 @@ fn zirErrUnionPayload(
64156422
const src = inst_data.src();
64166423
const operand = try sema.resolveInst(inst_data.operand);
64176424
const operand_src = src;
6418-
const operand_ty = sema.typeOf(operand);
6419-
if (operand_ty.zigTypeTag() != .ErrorUnion) {
6425+
const err_union_ty = sema.typeOf(operand);
6426+
if (err_union_ty.zigTypeTag() != .ErrorUnion) {
64206427
return sema.fail(block, operand_src, "expected error union type, found '{}'", .{
6421-
operand_ty.fmt(sema.mod),
6428+
err_union_ty.fmt(sema.mod),
64226429
});
64236430
}
6431+
return sema.analyzeErrUnionPayload(block, src, err_union_ty, operand, operand_src, safety_check);
6432+
}
64246433

6425-
const result_ty = operand_ty.errorUnionPayload();
6426-
if (try sema.resolveDefinedValue(block, src, operand)) |val| {
6434+
fn analyzeErrUnionPayload(
6435+
sema: *Sema,
6436+
block: *Block,
6437+
src: LazySrcLoc,
6438+
err_union_ty: Type,
6439+
operand: Zir.Inst.Ref,
6440+
operand_src: LazySrcLoc,
6441+
safety_check: bool,
6442+
) CompileError!Air.Inst.Ref {
6443+
const payload_ty = err_union_ty.errorUnionPayload();
6444+
if (try sema.resolveDefinedValue(block, operand_src, operand)) |val| {
64276445
if (val.getError()) |name| {
64286446
return sema.fail(block, src, "caught unexpected error '{s}'", .{name});
64296447
}
64306448
const data = val.castTag(.eu_payload).?.data;
6431-
return sema.addConstant(result_ty, data);
6449+
return sema.addConstant(payload_ty, data);
64326450
}
64336451

64346452
try sema.requireRuntimeBlock(block, src);
64356453

64366454
// If the error set has no fields then no safety check is needed.
64376455
if (safety_check and block.wantSafety() and
6438-
operand_ty.errorUnionSet().errorSetCardinality() != .zero)
6456+
err_union_ty.errorUnionSet().errorSetCardinality() != .zero)
64396457
{
64406458
try sema.panicUnwrapError(block, src, operand, .unwrap_errunion_err, .is_non_err);
64416459
}
64426460

6443-
return block.addTyOp(.unwrap_errunion_payload, result_ty, operand);
6461+
return block.addTyOp(.unwrap_errunion_payload, payload_ty, operand);
64446462
}
64456463

64466464
/// Pointer in, pointer out.
@@ -12954,6 +12972,43 @@ fn zirCondbr(
1295412972
return always_noreturn;
1295512973
}
1295612974

12975+
fn zirTry(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Ref {
12976+
const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
12977+
const src = inst_data.src();
12978+
const operand_src: LazySrcLoc = .{ .node_offset_bin_lhs = inst_data.src_node };
12979+
const extra = sema.code.extraData(Zir.Inst.Try, inst_data.payload_index);
12980+
const body = sema.code.extra[extra.end..][0..extra.data.body_len];
12981+
const operand = try sema.resolveInst(extra.data.operand);
12982+
const is_ptr = sema.typeOf(operand).zigTypeTag() == .Pointer;
12983+
const err_union = if (is_ptr)
12984+
try sema.analyzeLoad(parent_block, src, operand, operand_src)
12985+
else
12986+
operand;
12987+
const err_union_ty = sema.typeOf(err_union);
12988+
if (err_union_ty.zigTypeTag() != .ErrorUnion) {
12989+
return sema.fail(parent_block, operand_src, "expected error union type, found '{}'", .{
12990+
err_union_ty.fmt(sema.mod),
12991+
});
12992+
}
12993+
const is_non_err = try sema.analyzeIsNonErr(parent_block, operand_src, err_union);
12994+
12995+
if (try sema.resolveDefinedValue(parent_block, operand_src, is_non_err)) |is_non_err_val| {
12996+
if (is_non_err_val.toBool()) {
12997+
if (is_ptr) {
12998+
return sema.analyzeErrUnionPayloadPtr(parent_block, src, operand, false, false);
12999+
} else {
13000+
return sema.analyzeErrUnionPayload(parent_block, src, err_union_ty, operand, operand_src, false);
13001+
}
13002+
}
13003+
// We can analyze the body directly in the parent block because we know there are
13004+
// no breaks from the body possible, and that the body is noreturn.
13005+
return sema.resolveBody(parent_block, body, inst);
13006+
}
13007+
_ = body;
13008+
_ = is_non_err;
13009+
@panic("TODO");
13010+
}
13011+
1295713012
// A `break` statement is inside a runtime condition, but trying to
1295813013
// break from an inline loop. In such case we must convert it to
1295913014
// a runtime break.

src/Zir.zig

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,19 @@ pub const Inst = struct {
319319
/// only the taken branch is analyzed. The then block and else block must
320320
/// terminate with an "inline" variant of a noreturn instruction.
321321
condbr_inline,
322+
/// Given an operand which is an error union, splits control flow. In
323+
/// case of error, control flow goes into the block that is part of this
324+
/// instruction, which is guaranteed to end with a return instruction
325+
/// and never breaks out of the block.
326+
/// In the case of non-error, control flow proceeds to the next instruction
327+
/// after the `try`, with the result of this instruction being the unwrapped
328+
/// payload value, as if `err_union_payload_unsafe` was executed on the operand.
329+
/// Uses the `pl_node` union field. Payload is `Try`.
330+
@"try",
331+
/// Same as `try` except the operand is coerced to a comptime value, and
332+
/// only the taken branch is analyzed. The block must terminate with an "inline"
333+
/// variant of a noreturn instruction.
334+
try_inline,
322335
/// An error set type definition. Contains a list of field names.
323336
/// Uses the `pl_node` union field. Payload is `ErrorSetDecl`.
324337
error_set_decl,
@@ -1231,6 +1244,8 @@ pub const Inst = struct {
12311244
.closure_capture,
12321245
.ret_ptr,
12331246
.ret_type,
1247+
.@"try",
1248+
.try_inline,
12341249
=> false,
12351250

12361251
.@"break",
@@ -1509,6 +1524,8 @@ pub const Inst = struct {
15091524
.repeat,
15101525
.repeat_inline,
15111526
.panic,
1527+
.@"try",
1528+
.try_inline,
15121529
=> false,
15131530

15141531
.extended => switch (data.extended.opcode) {
@@ -1569,6 +1586,8 @@ pub const Inst = struct {
15691586
.coerce_result_ptr = .bin,
15701587
.condbr = .pl_node,
15711588
.condbr_inline = .pl_node,
1589+
.@"try" = .pl_node,
1590+
.try_inline = .pl_node,
15721591
.error_set_decl = .pl_node,
15731592
.error_set_decl_anon = .pl_node,
15741593
.error_set_decl_func = .pl_node,
@@ -2803,6 +2822,14 @@ pub const Inst = struct {
28032822
else_body_len: u32,
28042823
};
28052824

2825+
/// This data is stored inside extra, trailed by:
2826+
/// * 0. body: Index // for each `body_len`.
2827+
pub const Try = struct {
2828+
/// The error union to unwrap.
2829+
operand: Ref,
2830+
body_len: u32,
2831+
};
2832+
28062833
/// Stored in extra. Depending on the flags in Data, there will be up to 5
28072834
/// trailing Ref fields:
28082835
/// 0. sentinel: Ref // if `has_sentinel` flag is set
@@ -3739,6 +3766,12 @@ fn findDeclsInner(
37393766
try zir.findDeclsBody(list, then_body);
37403767
try zir.findDeclsBody(list, else_body);
37413768
},
3769+
.@"try", .try_inline => {
3770+
const inst_data = datas[inst].pl_node;
3771+
const extra = zir.extraData(Inst.Try, inst_data.payload_index);
3772+
const body = zir.extra[extra.end..][0..extra.data.body_len];
3773+
try zir.findDeclsBody(list, body);
3774+
},
37423775
.switch_block => return findDeclsSwitch(zir, list, inst),
37433776

37443777
.suspend_block => @panic("TODO iterate suspend block"),

src/print_zir.zig

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,17 +374,21 @@ const Writer = struct {
374374
.validate_array_init_comptime,
375375
.c_import,
376376
.typeof_builtin,
377-
=> try self.writePlNodeBlock(stream, inst),
377+
=> try self.writeBlock(stream, inst),
378378

379379
.condbr,
380380
.condbr_inline,
381-
=> try self.writePlNodeCondBr(stream, inst),
381+
=> try self.writeCondBr(stream, inst),
382+
383+
.@"try",
384+
.try_inline,
385+
=> try self.writeTry(stream, inst),
382386

383387
.error_set_decl => try self.writeErrorSetDecl(stream, inst, .parent),
384388
.error_set_decl_anon => try self.writeErrorSetDecl(stream, inst, .anon),
385389
.error_set_decl_func => try self.writeErrorSetDecl(stream, inst, .func),
386390

387-
.switch_block => try self.writePlNodeSwitchBlock(stream, inst),
391+
.switch_block => try self.writeSwitchBlock(stream, inst),
388392

389393
.field_ptr,
390394
.field_val,
@@ -1171,7 +1175,7 @@ const Writer = struct {
11711175
try self.writeSrc(stream, inst_data.src());
11721176
}
11731177

1174-
fn writePlNodeBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
1178+
fn writeBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
11751179
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
11761180
try self.writePlNodeBlockWithoutSrc(stream, inst);
11771181
try self.writeSrc(stream, inst_data.src());
@@ -1185,7 +1189,7 @@ const Writer = struct {
11851189
try stream.writeAll(") ");
11861190
}
11871191

1188-
fn writePlNodeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
1192+
fn writeCondBr(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
11891193
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
11901194
const extra = self.code.extraData(Zir.Inst.CondBr, inst_data.payload_index);
11911195
const then_body = self.code.extra[extra.end..][0..extra.data.then_body_len];
@@ -1199,6 +1203,17 @@ const Writer = struct {
11991203
try self.writeSrc(stream, inst_data.src());
12001204
}
12011205

1206+
fn writeTry(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
1207+
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
1208+
const extra = self.code.extraData(Zir.Inst.Try, inst_data.payload_index);
1209+
const body = self.code.extra[extra.end..][0..extra.data.body_len];
1210+
try self.writeInstRef(stream, extra.data.operand);
1211+
try stream.writeAll(", ");
1212+
try self.writeBracedBody(stream, body);
1213+
try stream.writeAll(") ");
1214+
try self.writeSrc(stream, inst_data.src());
1215+
}
1216+
12021217
fn writeStructDecl(self: *Writer, stream: anytype, extended: Zir.Inst.Extended.InstData) !void {
12031218
const small = @bitCast(Zir.Inst.StructDecl.Small, extended.small);
12041219

@@ -1746,7 +1761,7 @@ const Writer = struct {
17461761
try self.writeSrc(stream, inst_data.src());
17471762
}
17481763

1749-
fn writePlNodeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
1764+
fn writeSwitchBlock(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
17501765
const inst_data = self.code.instructions.items(.data)[inst].pl_node;
17511766
const extra = self.code.extraData(Zir.Inst.SwitchBlock, inst_data.payload_index);
17521767

0 commit comments

Comments
 (0)