diff --git a/BRANCH_TODO b/BRANCH_TODO index 95030f4966e292b661d23c51c82b90bac4746925..729c411bed1a548f5079018ff4aea7fa0241fa8a 100644 --- a/BRANCH_TODO +++ b/BRANCH_TODO @@ -1,3 +1,9 @@ + * calling getFuncAsyncStatus is triggering machine code lowering too early, + because the function which does not yet know its own async status may get called + recursively by one of the callees. + - don't do any lowering to machine code until async status has been resolved for + the local call graph sub-tree. + * detect when a called function is async and make the caller async too * generate the async frame type *after* lowering the function to LLVM IR * calculate frame size after llvm lowering, ability to inspect with `@sizeOf` @@ -11,3 +17,4 @@ * use function pointers instead of resume index to... - reduce the number of runtime branches from 2 to 1 - pass function arguments as normal arguments to the first segment + diff --git a/src/Module.zig b/src/Module.zig index 22ccbf382b805241f22bdcbc70c9e64794e569e8..60beecce17fd77cbce11b7815f13716862fce0f5 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -5692,10 +5692,9 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE inner_block.error_return_trace_index = error_return_trace_index; sema.analyzeBody(&inner_block, fn_info.body) catch |err| switch (err) { - // TODO make these unreachable instead of @panic - error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), - error.GenericPoison => @panic("zig compiler bug: GenericPoison"), - error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"), + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, else => |e| return e, }; @@ -5717,11 +5716,10 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE !sema.fn_ret_ty.isError(mod)) { sema.setupErrorReturnTrace(&inner_block, last_arg_index) catch |err| switch (err) { - // TODO make these unreachable instead of @panic - error.NeededSourceLocation => @panic("zig compiler bug: NeededSourceLocation"), - error.GenericPoison => @panic("zig compiler bug: GenericPoison"), - error.ComptimeReturn => @panic("zig compiler bug: ComptimeReturn"), - error.ComptimeBreak => @panic("zig compiler bug: ComptimeBreak"), + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, else => |e| return e, }; } @@ -5742,6 +5740,59 @@ pub fn analyzeFnBody(mod: *Module, func_index: Fn.Index, arena: Allocator) SemaE sema.air_extra.items[@intFromEnum(Air.ExtraIndex.main_block)] = main_block_index; func.state = .success; + + // Next we must look at all the function calls and determine two pieces of information: + // * for each call, whether the called function is async + // * whether this function making the calls is async + // This happens *after* setting func.state to success above so that any + // recursive check on this function will not cause an infinite loop. + // Both of these pieces of information are needed by backends for machine code lowering. + { + const air = sema.getTmpAir(); + const air_tags = air.instructions.items(.tag); + const air_datas = air.instructions.items(.data); + for (air_tags, 0..) |air_tag, inst| { + var is_suspend_point = false; + const callee = switch (air_tag) { + .call, .call_always_tail, .call_never_tail, .call_never_inline => c: { + is_suspend_point = true; + const pl_op = air_datas[inst].pl_op; + break :c pl_op.operand; + }, + .call_async => c: { + const pl_op = air_datas[inst].pl_op; + break :c pl_op.operand; + }, + .call_async_alloc => c: { + const ty_pl = air.instructions.items(.data)[inst].ty_pl; + const extra = air.extraData(Air.AsyncCallAlloc, ty_pl.payload); + break :c extra.data.callee; + }, + else => continue, + }; + const callee_val = (try air.value(callee, mod)) orelse continue; + const callee_decl_index = switch (mod.intern_pool.indexToKey(callee_val.toIntern())) { + .extern_func => continue, // extern functions cannot be async + .func => |f| mod.funcPtr(f.index).owner_decl, + else => unreachable, + }; + const callee_decl = mod.declPtr(callee_decl_index); + const callee_func_index = callee_decl.getOwnedFunctionIndex(mod).unwrap() orelse continue; + const callee_status = sema.getFuncAsyncStatus(callee_func_index) catch |err| switch (err) { + error.NeededSourceLocation => unreachable, + error.GenericPoison => unreachable, + error.ComptimeReturn => unreachable, + error.ComptimeBreak => unreachable, + error.AnalysisFail => continue, // treat this callee as non-async + else => |e| return e, + }; + if (is_suspend_point) switch (callee_status) { + .unknown => unreachable, + .not_async => continue, + .yes_async => func.async_status = .yes_async, + }; + } + } if (func.async_status == .unknown) { func.async_status = .not_async; } diff --git a/src/Sema.zig b/src/Sema.zig index 32eba6d500a5842e1226211d4ea112ab04f1a001..a60c24f442dbff612c5acddf9056d253bd7a3222 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9322,7 +9322,8 @@ fn funcCommon( return sema.addType(fn_ty); } - const is_inline = fn_ty.fnCallingConvention(mod) == .Inline; + const init_cc = fn_ty.fnCallingConvention(mod); + const is_inline = init_cc == .Inline; const anal_state: Module.Fn.Analysis = if (is_inline) .inline_only else .none; const comptime_args: ?[*]TypedValue = if (sema.comptime_args_fn_inst == func_inst) blk: { @@ -9334,7 +9335,7 @@ fn funcCommon( const generic_owner_decl = if (comptime_args == null) .none else new_func.generic_owner_decl; new_func.* = .{ .state = anal_state, - .async_status = .unknown, + .async_status = initAsyncSatus(init_cc), .zir_body_inst = func_inst, .owner_decl = sema.owner_decl_index, .generic_owner_decl = generic_owner_decl, @@ -9353,6 +9354,14 @@ fn funcCommon( } })).toValue()); } +fn initAsyncSatus(cc: std.builtin.CallingConvention) Module.Fn.AsyncStatus { + return switch (cc) { + .Unspecified => .unknown, + .Async => .yes_async, + else => .not_async, + }; +} + fn analyzeParameter( sema: *Sema, block: *Block, @@ -30557,6 +30566,22 @@ fn ensureFuncBodyAnalyzed(sema: *Sema, func: Module.Fn.Index) CompileError!void }; } +pub fn getFuncAsyncStatus(sema: *Sema, func_index: Module.Fn.Index) CompileError!Module.Fn.AsyncStatus { + const mod = sema.mod; + const func = mod.funcPtr(func_index); + switch (func.async_status) { + .yes_async => return .yes_async, + .not_async => return .not_async, + .unknown => { + try ensureFuncBodyAnalyzed(sema, func_index); + switch (func.async_status) { + .yes_async => return .yes_async, + .not_async, .unknown => return .not_async, + } + }, + } +} + fn refValue(sema: *Sema, block: *Block, ty: Type, val: Value) !Value { const mod = sema.mod; var anon_decl = try block.startAnonDecl(); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 46bf7349c89d649dc50131517930d7b1c916f2d0..f07875aec2e3b99d9e0539e7588b408126eb4785 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -961,7 +961,11 @@ pub const Object = struct { defer args.deinit(); { - var llvm_arg_i = @as(c_uint, @intFromBool(ret_ptr != null)) + @intFromBool(err_return_tracing); + var llvm_arg_i = + @as(c_uint, @intFromBool(ret_ptr != null)) + + @intFromBool(err_return_tracing) + + @intFromBool(func.isAsync()); + var it = iterateParamTypes(o, fn_info); while (it.next()) |lowering| switch (lowering) { .no_bits => continue, @@ -1215,25 +1219,27 @@ pub const Object = struct { }; defer fg.deinit(); - if (func.isAsync()) { - const frame_ty = try mod.asyncFrameType(func_index); - const frame_size = frame_ty.abiSize(mod); - const llvm_usize = dg.context.intType(target.ptrBitWidth()); - const size_val = llvm_usize.constInt(frame_size, .False); - llvm_func.functionSetPrefixData(size_val); + const llvm_usize = o.context.intType(target.ptrBitWidth()); - const async_preamble_bb = dg.context.appendBasicBlock(llvm_func, "AsyncSwitch"); - const bad_resume_bb = dg.context.appendBasicBlock(llvm_func, "BadResume"); + if (func.isAsync()) { + const bad_resume_bb = o.context.appendBasicBlock(llvm_func, "BadResume"); builder.positionBuilderAtEnd(bad_resume_bb); _ = builder.buildUnreachable(); // TODO make this a safety panic - builder.positionBuilderAtEnd(async_preamble_bb); + builder.positionBuilderAtEnd(entry_block); const l = asyncFrameLayout(); - const frame_llvm_ty = try dg.lowerType(frame_ty); + const frame_llvm_ty = try o.lowerAsyncFrameHeader(fn_info.return_type.toType()); const frame_ptr = llvm_func.getParam(0); fg.resume_index_ptr = builder.buildStructGEP(frame_llvm_ty, frame_ptr, l.resume_index, ""); const resume_index = builder.buildLoad(llvm_usize, fg.resume_index_ptr, ""); fg.async_switch = builder.buildSwitch(resume_index, bad_resume_bb, 4); + + const init_bb = o.context.appendBasicBlock(llvm_func, "Init"); + const new_block_index = fg.resume_block_index; + fg.resume_block_index += 1; + const new_block_index_llvm_val = llvm_usize.constInt(new_block_index, .False); + fg.async_switch.addCase(new_block_index_llvm_val, init_bb); + builder.positionBuilderAtEnd(init_bb); } fg.genBody(air.getMainBody()) catch |err| switch (err) { @@ -1246,6 +1252,12 @@ pub const Object = struct { else => |e| return e, }; + if (func.isAsync()) { + const frame_size = 3 * (target.ptrBitWidth() / 8); + const size_val = llvm_usize.constInt(frame_size, .False); + llvm_func.functionSetPrefixData(size_val); + } + try o.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index)); } @@ -2499,16 +2511,20 @@ pub const Object = struct { const mod = o.module; const gpa = o.gpa; const decl = mod.declPtr(decl_index); - const zig_fn_type = decl.ty; const gop = try o.decl_map.getOrPut(gpa, decl_index); if (gop.found_existing) return gop.value_ptr.*; assert(decl.has_tv); - const fn_info = mod.typeToFunc(zig_fn_type).?; + const func = decl.getOwnedFunction(mod).?; + const zig_fn_type = decl.ty; + const fn_info = info: { + var info = mod.typeToFunc(zig_fn_type).?; + if (func.isAsync()) info.cc = .Async; + break :info info; + }; const target = mod.getTarget(); const sret = firstParamSRet(fn_info, mod); - - const fn_type = try o.lowerType(zig_fn_type); + const fn_type = try o.lowerTypeFn(fn_info); const fqn = try decl.getFullyQualifiedName(mod); @@ -2531,32 +2547,33 @@ pub const Object = struct { } } + var llvm_param_i: u32 = 0; + if (sret) { - o.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0 - o.addArgAttr(llvm_fn, 0, "noalias"); + o.addArgAttr(llvm_fn, llvm_param_i, "nonnull"); // Sret pointers must not be address 0 + o.addArgAttr(llvm_fn, llvm_param_i, "noalias"); const raw_llvm_ret_ty = try o.lowerType(fn_info.return_type.toType()); llvm_fn.addSretAttr(raw_llvm_ret_ty); + + llvm_param_i += 1; } const err_return_tracing = fn_info.return_type.toType().isError(mod) and mod.comp.bin_file.options.error_return_tracing; if (err_return_tracing) { - o.addArgAttr(llvm_fn, @intFromBool(sret), "nonnull"); + o.addArgAttr(llvm_fn, llvm_param_i, "nonnull"); + llvm_param_i += 1; } switch (fn_info.cc) { - .Unspecified, .Inline => { + .Unspecified, .Inline, .Async => { llvm_fn.setFunctionCallConv(.Fast); }, .Naked => { o.addFnAttr(llvm_fn, "naked"); }, - .Async => { - llvm_fn.setFunctionCallConv(.Fast); - @panic("TODO: LLVM backend lower async function"); - }, else => { llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target)); }, @@ -2577,8 +2594,7 @@ pub const Object = struct { // because functions with bodies are handled in `updateFunc`. if (is_extern) { var it = iterateParamTypes(o, fn_info); - it.llvm_index += @intFromBool(sret); - it.llvm_index += @intFromBool(err_return_tracing); + it.llvm_index += llvm_param_i; while (it.next()) |lowering| switch (lowering) { .byval => { const param_index = it.zig_index - 1; @@ -3052,7 +3068,7 @@ pub const Object = struct { llvm_union_ty.structSetBody(&llvm_fields, llvm_fields_len, .False); return llvm_union_ty; }, - .Fn => return lowerTypeFn(o, t), + .Fn => return lowerTypeFn(o, mod.typeToFunc(t).?), .ComptimeInt => unreachable, .ComptimeFloat => unreachable, .Type => unreachable, @@ -3089,12 +3105,16 @@ pub const Object = struct { } fn lowerAsyncFrameHeader(o: *Object, ret_ty: Type) !*llvm.Type { + const mod = o.module; const opaque_ptr_ty = o.context.pointerType(0); const l = asyncFrameLayout(); var fields: [4]*llvm.Type = undefined; fields[l.fn_ptr] = opaque_ptr_ty; fields[l.resume_index] = try o.lowerType(Type.usize); fields[l.awaiter] = opaque_ptr_ty; + if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod)) { + return o.context.structType(&fields, 3, .False); + } fields[l.ret_val] = try o.lowerType(ret_ty); return o.context.structType(&fields, fields.len, .False); } @@ -3122,23 +3142,29 @@ pub const Object = struct { return llvm_struct_ty; } - fn lowerTypeFn(o: *Object, fn_ty: Type) Allocator.Error!*llvm.Type { + fn lowerTypeFn(o: *Object, fn_info: InternPool.Key.FuncType) Allocator.Error!*llvm.Type { const mod = o.module; - const fn_info = mod.typeToFunc(fn_ty).?; const llvm_ret_ty = try lowerFnRetTy(o, fn_info); var llvm_params = std.ArrayList(*llvm.Type).init(o.gpa); defer llvm_params.deinit(); + try llvm_params.ensureUnusedCapacity(3); + if (firstParamSRet(fn_info, mod)) { - try llvm_params.append(o.context.pointerType(0)); + llvm_params.appendAssumeCapacity(o.context.pointerType(0)); + } + + if (fn_info.cc == .Async) { + // frame_ptr + llvm_params.appendAssumeCapacity(o.context.pointerType(0)); } if (fn_info.return_type.toType().isError(mod) and mod.comp.bin_file.options.error_return_tracing) { const ptr_ty = try mod.singleMutPtrType(try o.getStackTraceType()); - try llvm_params.append(try o.lowerType(ptr_ty)); + llvm_params.appendAssumeCapacity(try o.lowerType(ptr_ty)); } var it = iterateParamTypes(o, fn_info); @@ -4860,9 +4886,11 @@ pub const FuncGen = struct { } fn genSuspendBegin(fg: *FuncGen, name_hint: [*:0]const u8) *llvm.BasicBlock { - const target = fg.getTarget(); - const llvm_usize = fg.dg.context.intType(target.ptrBitWidth()); - const resume_bb = fg.context.appendBasicBlock(fg.llvm_func, name_hint); + const o = fg.dg.object; + const mod = o.module; + const target = mod.getTarget(); + const llvm_usize = o.context.intType(target.ptrBitWidth()); + const resume_bb = o.context.appendBasicBlock(fg.llvm_func, name_hint); const new_block_index = fg.resume_block_index; fg.resume_block_index += 1; const new_block_index_llvm_val = llvm_usize.constInt(new_block_index, .False);