diff --git a/src/Air.zig b/src/Air.zig index dc08e4de269356582eac8a536ed89a493c68e7fe..37001e002ceb9c7d3e2bb71c0d2508b745bd4f71 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -1075,6 +1075,7 @@ pub const Call = struct { /// Trailing is a list of `Inst.Ref` for every `args_len`. pub const AsyncCall = struct { + frame_ptr: Inst.Ref, callee: Inst.Ref, args_len: u32, }; diff --git a/src/AstGen.zig b/src/AstGen.zig index b17f3b3a970742c2d8353fc4e80545d7c56f5bde..d2caef1b95b8448b36c99b933dacebd10440f9a6 100644 --- a/src/AstGen.zig +++ b/src/AstGen.zig @@ -2541,6 +2541,8 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .alloc_inferred_mut, .alloc_inferred_comptime, .alloc_inferred_comptime_mut, + .async_call, + .async_field_call, .make_ptr_const, .array_cat, .array_mul, @@ -2788,7 +2790,6 @@ fn addEnsureResult(gz: *GenZir, maybe_unused_result: Zir.Inst.Ref, statement: As .validate_deref, .save_err_ret_index, .restore_err_ret_index, - .async_call, => break :b true, .@"defer" => unreachable, @@ -3301,11 +3302,17 @@ fn varDecl( } else a: { const alloc = alloc: { if (align_inst == .none) { - const tag: Zir.Inst.Tag = if (is_comptime) - .alloc_inferred_comptime_mut - else - .alloc_inferred_mut; - break :alloc try gz.addNode(tag, node); + var params: [1]Ast.Node.Index = undefined; + if (is_comptime) { + break :alloc try gz.addNode(.alloc_inferred_comptime_mut, node); + } else { + if (nodeGetCall(tree, var_decl.ast.init_node, ¶ms)) |call| { + if (call.async_token != null) { + return asyncCallExpr(gz, scope, node, call, ident_name, block_arena, name_token); + } + } + break :alloc try gz.addNode(.alloc_inferred_mut, node); + } } else { break :alloc try gz.addAllocExtended(.{ .node = node, @@ -3346,6 +3353,34 @@ fn varDecl( } } +fn asyncCallExpr( + gz: *GenZir, + scope: *Scope, + node: Ast.Node.Index, + call: Ast.full.Call, + ident_name: u32, + block_arena: Allocator, + name_token: Ast.TokenIndex, +) InnerError!*Scope { + gz.rl_ty_inst = .none; + + const call_inst = try callExprInner(gz, scope, node, call, .async_kw, false, true); + + try gz.addDbgVar(.dbg_var_ptr, ident_name, call_inst); + + const sub_scope = try block_arena.create(Scope.LocalPtr); + sub_scope.* = .{ + .parent = scope, + .gen_zir = gz, + .name = ident_name, + .ptr = call_inst, + .token_src = name_token, + .maybe_comptime = false, + .id_cat = .@"local variable", + }; + return &sub_scope.base; +} + fn emitDbgNode(gz: *GenZir, node: Ast.Node.Index) !void { // The instruction emitted here is for debugging runtime code. // If the current block will be evaluated only during semantic analysis @@ -9076,9 +9111,6 @@ fn callExpr( node: Ast.Node.Index, call: Ast.full.Call, ) InnerError!Zir.Inst.Ref { - const astgen = gz.astgen; - - const callee = try calleeExpr(gz, scope, call.ast.fn_expr); const modifier: std.builtin.CallModifier = blk: { if (gz.is_comptime) { break :blk .compile_time; @@ -9092,6 +9124,29 @@ fn callExpr( break :blk .auto; }; + // If our result location is a try/catch/error-union-if/return, a function argument, + // or an initializer for a `const` variable, the error trace propagates. + // Otherwise, it should always be popped (handled in Sema). + const propagate_error_trace = switch (ri.ctx) { + .error_handling_expr, .@"return", .fn_arg, .const_init => true, + else => false, + }; + const call_inst = try callExprInner(gz, scope, node, call, modifier, propagate_error_trace, false); + return rvalue(gz, ri, call_inst, node); // TODO function call with result location +} + +fn callExprInner( + gz: *GenZir, + scope: *Scope, + node: Ast.Node.Index, + call: Ast.full.Call, + modifier: std.builtin.CallModifier, + propagate_error_trace: bool, + is_async: bool, +) InnerError!Zir.Inst.Ref { + const astgen = gz.astgen; + const callee = try calleeExpr(gz, scope, call.ast.fn_expr); + { astgen.advanceSourceCursor(astgen.tree.tokens.items(.start)[call.ast.lparen]); const line = astgen.source_line - gz.decl_line; @@ -9139,17 +9194,12 @@ fn callExpr( scratch_index += 1; } - // If our result location is a try/catch/error-union-if/return, a function argument, - // or an initializer for a `const` variable, the error trace propagates. - // Otherwise, it should always be popped (handled in Sema). - const propagate_error_trace = switch (ri.ctx) { - .error_handling_expr, .@"return", .fn_arg, .const_init => true, - else => false, - }; - switch (callee) { .direct => |callee_obj| { - const payload_index = try addExtra(astgen, Zir.Inst.Call{ + const payload_index = if (is_async) try addExtra(astgen, Zir.Inst.AsyncCall{ + .callee = callee_obj, + .args_len = @intCast(call.ast.params.len), + }) else try addExtra(astgen, Zir.Inst.Call{ .callee = callee_obj, .flags = .{ .pop_error_return_trace = !propagate_error_trace, @@ -9161,7 +9211,7 @@ fn callExpr( try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]); } gz.astgen.instructions.set(call_index, .{ - .tag = .call, + .tag = if (is_async) .async_call else .call, .data = .{ .pl_node = .{ .src_node = gz.nodeIndexToRelative(node), .payload_index = payload_index, @@ -9169,7 +9219,11 @@ fn callExpr( }); }, .field => |callee_field| { - const payload_index = try addExtra(astgen, Zir.Inst.FieldCall{ + const payload_index = if (is_async) try addExtra(astgen, Zir.Inst.AsyncFieldCall{ + .obj_ptr = callee_field.obj_ptr, + .field_name_start = callee_field.field_name_start, + .args_len = @intCast(call.ast.params.len), + }) else try addExtra(astgen, Zir.Inst.FieldCall{ .obj_ptr = callee_field.obj_ptr, .field_name_start = callee_field.field_name_start, .flags = .{ @@ -9182,7 +9236,7 @@ fn callExpr( try astgen.extra.appendSlice(astgen.gpa, astgen.scratch.items[scratch_top..]); } gz.astgen.instructions.set(call_index, .{ - .tag = .field_call, + .tag = if (is_async) .async_field_call else .field_call, .data = .{ .pl_node = .{ .src_node = gz.nodeIndexToRelative(node), .payload_index = payload_index, @@ -9190,7 +9244,7 @@ fn callExpr( }); }, } - return rvalue(gz, ri, call_inst, node); // TODO function call with result location + return call_inst; } const Callee = union(enum) { @@ -10321,6 +10375,29 @@ fn nodeUsesAnonNameStrategy(tree: *const Ast, node: Ast.Node.Index) bool { } } +fn nodeGetCall( + tree: *const Ast, + start_node: Ast.Node.Index, + params: *[1]Ast.Node.Index, +) ?Ast.full.Call { + const node_tags = tree.nodes.items(.tag); + const node_datas = tree.nodes.items(.data); + + var node = start_node; + while (true) { + switch (node_tags[node]) { + .call_one, .call_one_comma, .async_call_one, .async_call_one_comma => { + return tree.callOne(params, node); + }, + .call, .call_comma, .async_call, .async_call_comma => { + return tree.callFull(node); + }, + .grouped_expression => node = node_datas[node].lhs, + else => return null, + } + } +} + /// Applies `rl` semantics to `result`. Expressions which do not do their own handling of /// result locations must call this function on their result. /// As an example, if the `ResultLoc` is `ptr`, it will write the result to the pointer. diff --git a/src/Sema.zig b/src/Sema.zig index cbb0fbdc43db4dabee2f9a0ccdeddf7c2661f5f3..2f60982dd2c54b0c577b0fbe71f9dbc928cf0f3b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -409,11 +409,7 @@ pub const Block = struct { return mod.namespacePtr(block.namespace).file_scope; } - fn addTy( - block: *Block, - tag: Air.Inst.Tag, - ty: Type, - ) error{OutOfMemory}!Air.Inst.Ref { + fn addTy(block: *Block, tag: Air.Inst.Tag, ty: Type) error{OutOfMemory}!Air.Inst.Ref { return block.addInst(.{ .tag = tag, .data = .{ .ty = ty }, @@ -943,7 +939,8 @@ fn analyzeBodyInner( .c_import => try sema.zirCImport(block, inst), .call => try sema.zirCall(block, inst, Zir.Inst.Call), .field_call => try sema.zirCall(block, inst, Zir.Inst.FieldCall), - .async_call => try sema.zirAsyncCall(block, inst), + .async_call => try sema.zirAsyncCall(block, inst, Zir.Inst.AsyncCall), + .async_field_call => try sema.zirAsyncCall(block, inst, Zir.Inst.AsyncFieldCall), .closure_get => try sema.zirClosureGet(block, inst), .cmp_lt => try sema.zirCmp(block, inst, .lt), .cmp_lte => try sema.zirCmp(block, inst, .lte), @@ -6477,18 +6474,33 @@ fn zirCall( ); } -fn zirAsyncCall(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref { +fn zirAsyncCall( + sema: *Sema, + block: *Block, + inst: Zir.Inst.Index, + comptime ExtraType: type, +) CompileError!Air.Inst.Ref { + const mod = sema.mod; const inst_data = sema.code.instructions.items(.data)[inst].pl_node; - const func_src: LazySrcLoc = .{ .node_offset_async_call_func = inst_data.src_node }; + const callee_src: LazySrcLoc = .{ .node_offset_async_call_func = inst_data.src_node }; const call_src: LazySrcLoc = .{ .node_offset_var_decl_init = inst_data.src_node }; - const extra = sema.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index); + const extra = sema.code.extraData(ExtraType, inst_data.payload_index); const args_len = extra.data.args_len; - const callee: ResolvedFieldCallee = .{ .direct = try sema.resolveInst(extra.data.callee) }; + const callee: ResolvedFieldCallee = switch (ExtraType) { + Zir.Inst.AsyncCall => .{ .direct = try sema.resolveInst(extra.data.callee) }, + Zir.Inst.AsyncFieldCall => blk: { + const object_ptr = try sema.resolveInst(extra.data.obj_ptr); + const field_name = try mod.intern_pool.getOrPutString(sema.gpa, sema.code.nullTerminatedString(extra.data.field_name_start)); + const field_name_src: LazySrcLoc = .{ .node_offset_field_name = inst_data.src_node }; + break :blk try sema.fieldCallBind(block, callee_src, object_ptr, field_name, field_name_src); + }, + else => @compileError("unreachable"), + }; return callCommon( sema, block, inst, - func_src, + callee_src, call_src, callee, args_len, @@ -7286,8 +7298,9 @@ fn addAsyncCallInst( args: []const Air.Inst.Ref, ) Allocator.Error!Air.Inst.Ref { const mod = sema.mod; - const frame_ty = try mod.asyncFrameType(callee_fn); - const frame_ty_ref = try sema.addType(frame_ty); + const ptr_frame_ty = try mod.singleMutPtrType(try mod.asyncFrameType(callee_fn)); + const frame_ptr = try block.addTy(.alloc, ptr_frame_ty); + const ptr_frame_ty_ref = try sema.addType(ptr_frame_ty); try sema.air_extra.ensureUnusedCapacity( sema.gpa, @typeInfo(Air.AsyncCall).Struct.fields.len + args.len, @@ -7295,8 +7308,9 @@ fn addAsyncCallInst( const call_inst = try block.addInst(.{ .tag = .call_async, .data = .{ .ty_pl = .{ - .ty = frame_ty_ref, + .ty = ptr_frame_ty_ref, .payload = sema.addExtraAssumeCapacity(Air.AsyncCall{ + .frame_ptr = frame_ptr, .callee = callee, .args_len = @intCast(args.len), }), diff --git a/src/Zir.zig b/src/Zir.zig index 3d685554bf52c54894e27d0b1015ebcf10da3342..eb2e9bdb33565a54dab3307065966f1978a3ba19 100644 --- a/src/Zir.zig +++ b/src/Zir.zig @@ -318,6 +318,11 @@ pub const Inst = struct { /// Uses the `pl_node` union field with payload `AsyncCall` /// AST node is the entire variable declaration, with the init node being a call. async_call, + /// Combination of `field_call` and `async_call`. + /// Corresponds with the syntax `var foo = async bar.baz();`. + /// Uses the `pl_node` union field with payload `AsyncFieldCall` + /// AST node is the entire variable declaration, with the init node being a field call. + async_field_call, /// `<` /// Uses the `pl_node` union field. Payload is `Bin`. cmp_lt, @@ -1032,6 +1037,7 @@ pub const Inst = struct { .call, .field_call, .async_call, + .async_field_call, .cmp_lt, .cmp_lte, .cmp_eq, @@ -1310,6 +1316,8 @@ pub const Inst = struct { .alloc_inferred_mut, .alloc_inferred_comptime, .alloc_inferred_comptime_mut, + .async_call, + .async_field_call, .make_ptr_const, .array_cat, .array_mul, @@ -1336,7 +1344,6 @@ pub const Inst = struct { .bool_not, .call, .field_call, - .async_call, .cmp_lt, .cmp_lte, .cmp_eq, @@ -1572,6 +1579,7 @@ pub const Inst = struct { .call = .pl_node, .field_call = .pl_node, .async_call = .pl_node, + .async_field_call = .pl_node, .cmp_lt = .pl_node, .cmp_lte = .pl_node, .cmp_eq = .pl_node, @@ -2549,6 +2557,14 @@ pub const Inst = struct { args_len: u32, }; + /// Same trailing data as `AsyncCall`, `FieldCall`, and `Call`. + pub const AsyncFieldCall = struct { + args_len: u32, + obj_ptr: Ref, + /// Offset into `string_bytes`. + field_name_start: u32, + }; + pub const TypeOfPeer = struct { src_node: i32, body_len: u32, diff --git a/src/print_air.zig b/src/print_air.zig index e1ac169340c302a293bb9067e276f37f5c021ffc..9fb488282afd1fc3617e7e6e6b96b7915eb80e14 100644 --- a/src/print_air.zig +++ b/src/print_air.zig @@ -701,7 +701,7 @@ const Writer = struct { const pl_op = w.air.instructions.items(.data)[inst].pl_op; const extra = w.air.extraData(Air.Call, pl_op.payload); const args: []const Air.Inst.Ref = @ptrCast(w.air.extra[extra.end..][0..extra.data.args_len]); - return finishWriteCall(w, s, inst, pl_op.operand, args); + return finishWriteCall(w, s, inst, .none, pl_op.operand, args); } fn writeCallAsync(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void { @@ -709,21 +709,31 @@ const Writer = struct { const extra = w.air.extraData(Air.AsyncCall, ty_pl.payload); const callee = extra.data.callee; const args: []const Air.Inst.Ref = @ptrCast(w.air.extra[extra.end..][0..extra.data.args_len]); - return finishWriteCall(w, s, inst, callee, args); + const frame_ptr = extra.data.frame_ptr; + return finishWriteCall(w, s, inst, frame_ptr, callee, args); } fn finishWriteCall( w: *Writer, s: anytype, inst: Air.Inst.Index, + frame_ptr: Air.Inst.Ref, callee: Air.Inst.Ref, args: []const Air.Inst.Ref, ) @TypeOf(s).Error!void { - try w.writeOperand(s, inst, 0, callee); + var op_index: usize = 0; + if (frame_ptr != .none) { + try w.writeOperand(s, inst, op_index, frame_ptr); + op_index += 1; + try s.writeAll(", "); + } + try w.writeOperand(s, inst, op_index, callee); + op_index += 1; try s.writeAll(", ["); for (args, 0..) |arg, i| { if (i != 0) try s.writeAll(", "); - try w.writeOperand(s, inst, 1 + i, arg); + try w.writeOperand(s, inst, op_index, arg); + op_index += 1; } try s.writeAll("]"); } diff --git a/src/print_zir.zig b/src/print_zir.zig index 8633c174232df353c0c5b6236858e26e5a7f48bd..070a91ca156726e2bfb768313eedf9db88b05f55 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -360,9 +360,10 @@ const Writer = struct { .@"export" => try self.writePlNodeExport(stream, inst), .export_value => try self.writePlNodeExportValue(stream, inst), - .call => try self.writeCall(stream, inst, .direct), - .field_call => try self.writeCall(stream, inst, .field), - .async_call => try self.writeAsyncCall(stream, inst), + .call => try self.writeCall(stream, inst, Zir.Inst.Call), + .field_call => try self.writeCall(stream, inst, Zir.Inst.FieldCall), + .async_call => try self.writeCall(stream, inst, Zir.Inst.AsyncCall), + .async_field_call => try self.writeCall(stream, inst, Zir.Inst.AsyncFieldCall), .block, .block_comptime, @@ -1188,50 +1189,48 @@ const Writer = struct { try self.writeSrc(stream, src); } - const CallKind = enum { direct, field }; - fn writeCall( self: *Writer, stream: anytype, inst: Zir.Inst.Index, - comptime kind: CallKind, + comptime ExtraType: type, ) !void { const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const ExtraType = switch (kind) { - .direct => Zir.Inst.Call, - .field => Zir.Inst.FieldCall, - }; const extra = self.code.extraData(ExtraType, inst_data.payload_index); - const args_len = extra.data.flags.args_len; + const args_len = switch (ExtraType) { + Zir.Inst.Call, Zir.Inst.FieldCall => extra.data.flags.args_len, + Zir.Inst.AsyncCall, Zir.Inst.AsyncFieldCall => extra.data.args_len, + else => @compileError("unreachable"), + }; const body = self.code.extra[extra.end..]; - const modifier: std.builtin.CallModifier = @enumFromInt(extra.data.flags.packed_modifier); - - if (extra.data.flags.ensure_result_used) { - try stream.writeAll("nodiscard "); + switch (ExtraType) { + Zir.Inst.Call, Zir.Inst.FieldCall => if (extra.data.flags.ensure_result_used) { + try stream.writeAll("nodiscard "); + }, + Zir.Inst.AsyncCall, Zir.Inst.AsyncFieldCall => {}, + else => @compileError("unreachable"), } + + const modifier: std.builtin.CallModifier = switch (ExtraType) { + Zir.Inst.Call, Zir.Inst.FieldCall => @enumFromInt(extra.data.flags.packed_modifier), + Zir.Inst.AsyncCall, Zir.Inst.AsyncFieldCall => std.builtin.CallModifier.async_kw, + else => @compileError("unreachable"), + }; + try stream.print(".{s}, ", .{@tagName(modifier)}); - switch (kind) { - .direct => try self.writeInstRef(stream, extra.data.callee), - .field => { + + switch (ExtraType) { + Zir.Inst.Call, Zir.Inst.AsyncCall => try self.writeInstRef(stream, extra.data.callee), + Zir.Inst.FieldCall, Zir.Inst.AsyncFieldCall => { const field_name = self.code.nullTerminatedString(extra.data.field_name_start); try self.writeInstRef(stream, extra.data.obj_ptr); try stream.print(", \"{}\"", .{std.zig.fmtEscapes(field_name)}); }, + else => @compileError("unreachable"), } return finishWriteCall(self, stream, body, args_len, extra.end, inst_data.src()); } - fn writeAsyncCall(self: *Writer, stream: anytype, inst: Zir.Inst.Index) !void { - const inst_data = self.code.instructions.items(.data)[inst].pl_node; - const extra = self.code.extraData(Zir.Inst.AsyncCall, inst_data.payload_index); - const args_len = extra.data.args_len; - const body = self.code.extra[extra.end..]; - const callee = extra.data.callee; - try stream.print(".{s}, ", .{@tagName(std.builtin.CallModifier.async_kw)}); - try self.writeInstRef(stream, callee); - return finishWriteCall(self, stream, body, args_len, extra.end, inst_data.src()); - } - fn finishWriteCall( self: *Writer, stream: anytype,