authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-09-20 22:15:48-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-06-26 15:57:06-07:00
loge70c34cdb7ea7d9b7b6feddb2d8e6194ed1188a4
treead94bd45b4ca65ec72820bed6d5bd7c35f60690f
parent5a8c445779b3b87ba38fbcac0efcc7ebb3787161

stage2: implement async_call

* AstGen emits async_call instruction for variable declarations of async calls. * Sema analyzes async_call instruction. * Liveness handles call_async AIR instruction. * Fix Type.eql for async frame types.

6 files changed, 187 insertions(+), 70 deletions(-)

src/Air.zig+1
......@@ -1075,6 +1075,7 @@ pub const Call = struct {
10751075
10761076/// Trailing is a list of `Inst.Ref` for every `args_len`.
10771077pub const AsyncCall = struct {
1078 frame_ptr: Inst.Ref,
10781079 callee: Inst.Ref,
10791080 args_len: u32,
10801081};
src/AstGen.zig+99-22
......@@ -2541,6 +2541,8 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
25412541 .alloc_inferred_mut,
25422542 .alloc_inferred_comptime,
25432543 .alloc_inferred_comptime_mut,
2544 .async_call,
2545 .async_field_call,
25442546 .make_ptr_const,
25452547 .array_cat,
25462548 .array_mul,
......@@ -2788,7 +2790,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As
27882790 .validate_deref,
27892791 .save_err_ret_index,
27902792 .restore_err_ret_index,
2791 .async_call,
27922793 => break :b true,
27932794
27942795 .@"defer" => unreachable,
......@@ -3301,11 +3302,17 @@ fn varDecl(
33013302 } else a: {
33023303 const alloc = alloc: {
33033304 if (align_inst == .none) {
3304 const tag: Zir.Inst.Tag = if (is_comptime)
3305 .alloc_inferred_comptime_mut
3306 else
3307 .alloc_inferred_mut;
3308 break :alloc try gz.addNode(tag, node);
3305 var params: [1]Ast.Node.Index = undefined;
3306 if (is_comptime) {
3307 break :alloc try gz.addNode(.alloc_inferred_comptime_mut, node);
3308 } else {
3309 if (nodeGetCall(tree, var_decl.ast.init_node, &params)) |call| {
3310 if (call.async_token != null) {
3311 return asyncCallExpr(gz, scope, node, call, ident_name, block_arena, name_token);
3312 }
3313 }
3314 break :alloc try gz.addNode(.alloc_inferred_mut, node);
3315 }
33093316 } else {
33103317 break :alloc try gz.addAllocExtended(.{
33113318 .node = node,
......@@ -3346,6 +3353,34 @@ fn varDecl(
33463353 }
33473354}
33483355
3356fn asyncCallExpr(
3357 gz: *GenZir,
3358 scope: *Scope,
3359 node: Ast.Node.Index,
3360 call: Ast.full.Call,
3361 ident_name: u32,
3362 block_arena: Allocator,
3363 name_token: Ast.TokenIndex,
3364) InnerError!*Scope {
3365 gz.rl_ty_inst = .none;
3366
3367 const call_inst = try callExprInner(gz, scope, node, call, .async_kw, false, true);
3368
3369 try gz.addDbgVar(.dbg_var_ptr, ident_name, call_inst);
3370
3371 const sub_scope = try block_arena.create(Scope.LocalPtr);
3372 sub_scope.* = .{
3373 .parent = scope,
3374 .gen_zir = gz,
3375 .name = ident_name,
3376 .ptr = call_inst,
3377 .token_src = name_token,
3378 .maybe_comptime = false,
3379 .id_cat = .@"local variable",
3380 };
3381 return &sub_scope.base;
3382}
3383
33493384fn emitDbgNode(gz: *GenZir, node: Ast.Node.Index) !void {
33503385 // The instruction emitted here is for debugging runtime code.
33513386 // If the current block will be evaluated only during semantic analysis
......@@ -9076,9 +9111,6 @@ fn callExpr(
90769111 node: Ast.Node.Index,
90779112 call: Ast.full.Call,
90789113) InnerError!Zir.Inst.Ref {
9079 const astgen = gz.astgen;
9080
9081 const callee = try calleeExpr(gz, scope, call.ast.fn_expr);
90829114 const modifier: std.builtin.CallModifier = blk: {
90839115 if (gz.is_comptime) {
90849116 break :blk .compile_time;
......@@ -9092,6 +9124,29 @@ fn callExpr(
90929124 break :blk .auto;
90939125 };
90949126
9127 // If our result location is a try/catch/error-union-if/return, a function argument,
9128 // or an initializer for a `const` variable, the error trace propagates.
9129 // Otherwise, it should always be popped (handled in Sema).
9130 const propagate_error_trace = switch (ri.ctx) {
9131 .error_handling_expr, .@"return", .fn_arg, .const_init => true,
9132 else => false,
9133 };
9134 const call_inst = try callExprInner(gz, scope, node, call, modifier, propagate_error_trace, false);
9135 return rvalue(gz, ri, call_inst, node); // TODO function call with result location
9136}
9137
9138fn callExprInner(
9139 gz: *GenZir,
9140 scope: *Scope,
9141 node: Ast.Node.Index,
9142 call: Ast.full.Call,
9143 modifier: std.builtin.CallModifier,
9144 propagate_error_trace: bool,
9145 is_async: bool,
9146) InnerError!Zir.Inst.Ref {
9147 const astgen = gz.astgen;
9148 const callee = try calleeExpr(gz, scope, call.ast.fn_expr);
9149
90959150 {
90969151 astgen.advanceSourceCursor(astgen.tree.tokens.items(.start)[call.ast.lparen]);
90979152 const line = astgen.source_line - gz.decl_line;
......@@ -9139,17 +9194,12 @@ fn callExpr(
91399194 scratch_index += 1;
91409195 }
91419196
9142 // If our result location is a try/catch/error-union-if/return, a function argument,
9143 // or an initializer for a `const` variable, the error trace propagates.
9144 // Otherwise, it should always be popped (handled in Sema).
9145 const propagate_error_trace = switch (ri.ctx) {
9146 .error_handling_expr, .@"return", .fn_arg, .const_init => true,
9147 else => false,
9148 };
9149
91509197 switch (callee) {
91519198 .direct => |callee_obj| {
9152 const payload_index = try addExtra(astgen, Zir.Inst.Call{
9199 const payload_index = if (is_async) try addExtra(astgen, Zir.Inst.AsyncCall{
9200 .callee = callee_obj,
9201 .args_len = @intCast(call.ast.params.len),
9202 }) else try addExtra(astgen, Zir.Inst.Call{
91539203 .callee = callee_obj,
91549204 .flags = .{
91559205 .pop_error_return_trace = !propagate_error_trace,
......@@ -9161,7 +9211,7 @@ fn callExpr(
91619211 try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
91629212 }
91639213 gz.astgen.instructions.set(call_index, .{
9164 .tag = .call,
9214 .tag = if (is_async) .async_call else .call,
91659215 .data = .{ .pl_node = .{
91669216 .src_node = gz.nodeIndexToRelative(node),
91679217 .payload_index = payload_index,
......@@ -9169,7 +9219,11 @@ fn callExpr(
91699219 });
91709220 },
91719221 .field => |callee_field| {
9172 const payload_index = try addExtra(astgen, Zir.Inst.FieldCall{
9222 const payload_index = if (is_async) try addExtra(astgen, Zir.Inst.AsyncFieldCall{
9223 .obj_ptr = callee_field.obj_ptr,
9224 .field_name_start = callee_field.field_name_start,
9225 .args_len = @intCast(call.ast.params.len),
9226 }) else try addExtra(astgen, Zir.Inst.FieldCall{
91739227 .obj_ptr = callee_field.obj_ptr,
91749228 .field_name_start = callee_field.field_name_start,
91759229 .flags = .{
......@@ -9182,7 +9236,7 @@ fn callExpr(
91829236 try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]);
91839237 }
91849238 gz.astgen.instructions.set(call_index, .{
9185 .tag = .field_call,
9239 .tag = if (is_async) .async_field_call else .field_call,
91869240 .data = .{ .pl_node = .{
91879241 .src_node = gz.nodeIndexToRelative(node),
91889242 .payload_index = payload_index,
......@@ -9190,7 +9244,7 @@ fn callExpr(
91909244 });
91919245 },
91929246 }
9193 return rvalue(gz, ri, call_inst, node); // TODO function call with result location
9247 return call_inst;
91949248}
91959249
91969250const Callee = union(enum) {
......@@ -10321,6 +10375,29 @@ fn nodeUsesAnonNameStrategy(tree: *const Ast, node: Ast.Node.Index) bool {
1032110375 }
1032210376}
1032310377
10378fn nodeGetCall(
10379 tree: *const Ast,
10380 start_node: Ast.Node.Index,
10381 params: *[1]Ast.Node.Index,
10382) ?Ast.full.Call {
10383 const node_tags = tree.nodes.items(.tag);
10384 const node_datas = tree.nodes.items(.data);
10385
10386 var node = start_node;
10387 while (true) {
10388 switch (node_tags[node]) {
10389 .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => {
10390 return tree.callOne(params, node);
10391 },
10392 .call, .call_comma, .async_call, .async_call_comma => {
10393 return tree.callFull(node);
10394 },
10395 .grouped_expression => node = node_datas[node].lhs,
10396 else => return null,
10397 }
10398 }
10399}
10400
1032410401/// Applies `rl` semantics to `result`. Expressions which do not do their own handling of
1032510402/// result locations must call this function on their result.
1032610403/// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer.
src/Sema.zig+28-14
......@@ -409,11 +409,7 @@ pub const Block = struct {
409409 return mod.namespacePtr(block.namespace).file_scope;
410410 }
411411
412 fn addTy(
413 block: *Block,
414 tag: Air.Inst.Tag,
415 ty: Type,
416 ) error{OutOfMemory}!Air.Inst.Ref {
412 fn addTy(block: *Block, tag: Air.Inst.Tag, ty: Type) error{OutOfMemory}!Air.Inst.Ref {
417413 return block.addInst(.{
418414 .tag = tag,
419415 .data = .{ .ty = ty },
......@@ -943,7 +939,8 @@ fn analyzeBodyInner(
943939 .c_import => try sema.zirCImport(block, inst),
944940 .call => try sema.zirCall(block, inst, Zir.Inst.Call),
945941 .field_call => try sema.zirCall(block, inst, Zir.Inst.FieldCall),
946 .async_call => try sema.zirAsyncCall(block, inst),
942 .async_call => try sema.zirAsyncCall(block, inst, Zir.Inst.AsyncCall),
943 .async_field_call => try sema.zirAsyncCall(block, inst, Zir.Inst.AsyncFieldCall),
947944 .closure_get => try sema.zirClosureGet(block, inst),
948945 .cmp_lt => try sema.zirCmp(block, inst, .lt),
949946 .cmp_lte => try sema.zirCmp(block, inst, .lte),
......@@ -6477,18 +6474,33 @@ fn zirCall(
64776474 );
64786475}
64796476
6480fn zirAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
6477fn zirAsyncCall(
6478 sema: *Sema,
6479 block: *Block,
6480 inst: Zir.Inst.Index,
6481 comptime ExtraType: type,
6482) CompileError!Air.Inst.Ref {
6483 const mod = sema.mod;
64816484 const inst_data = sema.code.instructions.items(.data)[inst].pl_node;
6482 const func_src: LazySrcLoc = .{ .node_offset_async_call_func = inst_data.src_node };
6485 const callee_src: LazySrcLoc = .{ .node_offset_async_call_func = inst_data.src_node };
64836486 const call_src: LazySrcLoc = .{ .node_offset_var_decl_init = inst_data.src_node };
6484 const extra = sema.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index);
6487 const extra = sema.code.extraData(ExtraType, inst_data.payload_index);
64856488 const args_len = extra.data.args_len;
6486 const callee: ResolvedFieldCallee = .{ .direct = try sema.resolveInst(extra.data.callee) };
6489 const callee: ResolvedFieldCallee = switch (ExtraType) {
6490 Zir.Inst.AsyncCall => .{ .direct = try sema.resolveInst(extra.data.callee) },
6491 Zir.Inst.AsyncFieldCall => blk: {
6492 const object_ptr = try sema.resolveInst(extra.data.obj_ptr);
6493 const field_name = try mod.intern_pool.getOrPutString(sema.gpa, sema.code.nullTerminatedString(extra.data.field_name_start));
6494 const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node };
6495 break :blk try sema.fieldCallBind(block, callee_src, object_ptr, field_name, field_name_src);
6496 },
6497 else => @compileError("unreachable"),
6498 };
64876499 return callCommon(
64886500 sema,
64896501 block,
64906502 inst,
6491 func_src,
6503 callee_src,
64926504 call_src,
64936505 callee,
64946506 args_len,
......@@ -7286,8 +7298,9 @@ fn addAsyncCallInst(
72867298 args: []const Air.Inst.Ref,
72877299) Allocator.Error!Air.Inst.Ref {
72887300 const mod = sema.mod;
7289 const frame_ty = try mod.asyncFrameType(callee_fn);
7290 const frame_ty_ref = try sema.addType(frame_ty);
7301 const ptr_frame_ty = try mod.singleMutPtrType(try mod.asyncFrameType(callee_fn));
7302 const frame_ptr = try block.addTy(.alloc, ptr_frame_ty);
7303 const ptr_frame_ty_ref = try sema.addType(ptr_frame_ty);
72917304 try sema.air_extra.ensureUnusedCapacity(
72927305 sema.gpa,
72937306 @typeInfo(Air.AsyncCall).Struct.fields.len + args.len,
......@@ -7295,8 +7308,9 @@ fn addAsyncCallInst(
72957308 const call_inst = try block.addInst(.{
72967309 .tag = .call_async,
72977310 .data = .{ .ty_pl = .{
7298 .ty = frame_ty_ref,
7311 .ty = ptr_frame_ty_ref,
72997312 .payload = sema.addExtraAssumeCapacity(Air.AsyncCall{
7313 .frame_ptr = frame_ptr,
73007314 .callee = callee,
73017315 .args_len = @intCast(args.len),
73027316 }),
src/Zir.zig+17-1
......@@ -318,6 +318,11 @@ pub const Inst = struct {
318318 /// Uses the `pl_node` union field with payload `AsyncCall`
319319 /// AST node is the entire variable declaration, with the init node being a call.
320320 async_call,
321 /// Combination of `field_call` and `async_call`.
322 /// Corresponds with the syntax `var foo = async bar.baz();`.
323 /// Uses the `pl_node` union field with payload `AsyncFieldCall`
324 /// AST node is the entire variable declaration, with the init node being a field call.
325 async_field_call,
321326 /// `<`
322327 /// Uses the `pl_node` union field. Payload is `Bin`.
323328 cmp_lt,
......@@ -1032,6 +1037,7 @@ pub const Inst = struct {
10321037 .call,
10331038 .field_call,
10341039 .async_call,
1040 .async_field_call,
10351041 .cmp_lt,
10361042 .cmp_lte,
10371043 .cmp_eq,
......@@ -1310,6 +1316,8 @@ pub const Inst = struct {
13101316 .alloc_inferred_mut,
13111317 .alloc_inferred_comptime,
13121318 .alloc_inferred_comptime_mut,
1319 .async_call,
1320 .async_field_call,
13131321 .make_ptr_const,
13141322 .array_cat,
13151323 .array_mul,
......@@ -1336,7 +1344,6 @@ pub const Inst = struct {
13361344 .bool_not,
13371345 .call,
13381346 .field_call,
1339 .async_call,
13401347 .cmp_lt,
13411348 .cmp_lte,
13421349 .cmp_eq,
......@@ -1572,6 +1579,7 @@ pub const Inst = struct {
15721579 .call = .pl_node,
15731580 .field_call = .pl_node,
15741581 .async_call = .pl_node,
1582 .async_field_call = .pl_node,
15751583 .cmp_lt = .pl_node,
15761584 .cmp_lte = .pl_node,
15771585 .cmp_eq = .pl_node,
......@@ -2549,6 +2557,14 @@ pub const Inst = struct {
25492557 args_len: u32,
25502558 };
25512559
2560 /// Same trailing data as `AsyncCall`, `FieldCall`, and `Call`.
2561 pub const AsyncFieldCall = struct {
2562 args_len: u32,
2563 obj_ptr: Ref,
2564 /// Offset into `string_bytes`.
2565 field_name_start: u32,
2566 };
2567
25522568 pub const TypeOfPeer = struct {
25532569 src_node: i32,
25542570 body_len: u32,
src/print_air.zig+14-4
......@@ -701,7 +701,7 @@ const Writer = struct {
701701 const pl_op = w.air.instructions.items(.data)[inst].pl_op;
702702 const extra = w.air.extraData(Air.Call, pl_op.payload);
703703 const args: []const Air.Inst.Ref = @ptrCast(w.air.extra[extra.end..][0..extra.data.args_len]);
704 return finishWriteCall(w, s, inst, pl_op.operand, args);
704 return finishWriteCall(w, s, inst, .none, pl_op.operand, args);
705705 }
706706
707707 fn writeCallAsync(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
......@@ -709,21 +709,31 @@ const Writer = struct {
709709 const extra = w.air.extraData(Air.AsyncCall, ty_pl.payload);
710710 const callee = extra.data.callee;
711711 const args: []const Air.Inst.Ref = @ptrCast(w.air.extra[extra.end..][0..extra.data.args_len]);
712 return finishWriteCall(w, s, inst, callee, args);
712 const frame_ptr = extra.data.frame_ptr;
713 return finishWriteCall(w, s, inst, frame_ptr, callee, args);
713714 }
714715
715716 fn finishWriteCall(
716717 w: *Writer,
717718 s: anytype,
718719 inst: Air.Inst.Index,
720 frame_ptr: Air.Inst.Ref,
719721 callee: Air.Inst.Ref,
720722 args: []const Air.Inst.Ref,
721723 ) @TypeOf(s).Error!void {
722 try w.writeOperand(s, inst, 0, callee);
724 var op_index: usize = 0;
725 if (frame_ptr != .none) {
726 try w.writeOperand(s, inst, op_index, frame_ptr);
727 op_index += 1;
728 try s.writeAll(", ");
729 }
730 try w.writeOperand(s, inst, op_index, callee);
731 op_index += 1;
723732 try s.writeAll(", [");
724733 for (args, 0..) |arg, i| {
725734 if (i != 0) try s.writeAll(", ");
726 try w.writeOperand(s, inst, 1 + i, arg);
735 try w.writeOperand(s, inst, op_index, arg);
736 op_index += 1;
727737 }
728738 try s.writeAll("]");
729739 }
src/print_zir.zig+28-29
......@@ -360,9 +360,10 @@ const Writer = struct {
360360 .@"export" => try self.writePlNodeExport(stream, inst),
361361 .export_value => try self.writePlNodeExportValue(stream, inst),
362362
363 .call => try self.writeCall(stream, inst, .direct),
364 .field_call => try self.writeCall(stream, inst, .field),
365 .async_call => try self.writeAsyncCall(stream, inst),
363 .call => try self.writeCall(stream, inst, Zir.Inst.Call),
364 .field_call => try self.writeCall(stream, inst, Zir.Inst.FieldCall),
365 .async_call => try self.writeCall(stream, inst, Zir.Inst.AsyncCall),
366 .async_field_call => try self.writeCall(stream, inst, Zir.Inst.AsyncFieldCall),
366367
367368 .block,
368369 .block_comptime,
......@@ -1188,50 +1189,48 @@ const Writer = struct {
11881189 try self.writeSrc(stream, src);
11891190 }
11901191
1191 const CallKind = enum { direct, field };
1192
11931192 fn writeCall(
11941193 self: *Writer,
11951194 stream: anytype,
11961195 inst: Zir.Inst.Index,
1197 comptime kind: CallKind,
1196 comptime ExtraType: type,
11981197 ) !void {
11991198 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
1200 const ExtraType = switch (kind) {
1201 .direct => Zir.Inst.Call,
1202 .field => Zir.Inst.FieldCall,
1203 };
12041199 const extra = self.code.extraData(ExtraType, inst_data.payload_index);
1205 const args_len = extra.data.flags.args_len;
1200 const args_len = switch (ExtraType) {
1201 Zir.Inst.Call, Zir.Inst.FieldCall => extra.data.flags.args_len,
1202 Zir.Inst.AsyncCall, Zir.Inst.AsyncFieldCall => extra.data.args_len,
1203 else => @compileError("unreachable"),
1204 };
12061205 const body = self.code.extra[extra.end..];
1207 const modifier: std.builtin.CallModifier = @enumFromInt(extra.data.flags.packed_modifier);
1208
1209 if (extra.data.flags.ensure_result_used) {
1210 try stream.writeAll("nodiscard ");
1206 switch (ExtraType) {
1207 Zir.Inst.Call, Zir.Inst.FieldCall => if (extra.data.flags.ensure_result_used) {
1208 try stream.writeAll("nodiscard ");
1209 },
1210 Zir.Inst.AsyncCall, Zir.Inst.AsyncFieldCall => {},
1211 else => @compileError("unreachable"),
12111212 }
1213
1214 const modifier: std.builtin.CallModifier = switch (ExtraType) {
1215 Zir.Inst.Call, Zir.Inst.FieldCall => @enumFromInt(extra.data.flags.packed_modifier),
1216 Zir.Inst.AsyncCall, Zir.Inst.AsyncFieldCall => std.builtin.CallModifier.async_kw,
1217 else => @compileError("unreachable"),
1218 };
1219
12121220 try stream.print(".{s}, ", .{@tagName(modifier)});
1213 switch (kind) {
1214 .direct => try self.writeInstRef(stream, extra.data.callee),
1215 .field => {
1221
1222 switch (ExtraType) {
1223 Zir.Inst.Call, Zir.Inst.AsyncCall => try self.writeInstRef(stream, extra.data.callee),
1224 Zir.Inst.FieldCall, Zir.Inst.AsyncFieldCall => {
12161225 const field_name = self.code.nullTerminatedString(extra.data.field_name_start);
12171226 try self.writeInstRef(stream, extra.data.obj_ptr);
12181227 try stream.print(", \"{}\"", .{std.zig.fmtEscapes(field_name)});
12191228 },
1229 else => @compileError("unreachable"),
12201230 }
12211231 return finishWriteCall(self, stream, body, args_len, extra.end, inst_data.src());
12221232 }
12231233
1224 fn writeAsyncCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void {
1225 const inst_data = self.code.instructions.items(.data)[inst].pl_node;
1226 const extra = self.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index);
1227 const args_len = extra.data.args_len;
1228 const body = self.code.extra[extra.end..];
1229 const callee = extra.data.callee;
1230 try stream.print(".{s}, ", .{@tagName(std.builtin.CallModifier.async_kw)});
1231 try self.writeInstRef(stream, callee);
1232 return finishWriteCall(self, stream, body, args_len, extra.end, inst_data.src());
1233 }
1234
12351234 fn finishWriteCall(
12361235 self: *Writer,
12371236 stream: anytype,