From 6d6cf598475ab8d2c3259002655ba04f1d056b2e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 11 Oct 2021 11:00:32 -0700 Subject: stage2: support nested structs and arrays and sret * Add AIR instructions: ret_ptr, ret_load - This allows Sema to be blissfully unaware of the backend's decision to implement by-val/by-ref semantics for struct/union/array types. Backends can lower these simply as alloc, load, ret instructions, or they can take advantage of them to use a result pointer. * Add AIR instruction: array_elem_val - Allows for better codegen for `Sema.elemVal`. * Implement calculation of ABI alignment and ABI size for unions. * Before appending the following AIR instructions to a block, resolveTypeLayout is called on the type: - call - return type - ret - return type - store_ptr - elem type * Sema: fix memory leak in `zirArrayInit` and other cleanups to this function. * x86_64: implement the full x86_64 C ABI according to the spec * Type: implement `intInfo` for error sets. * Type: implement `intTagType` for tagged unions. The Zig type tag `Fn` is now used exclusively for function bodies. Function pointers are modeled as `*const T` where `T` is a `Fn` type. * The `call` AIR instruction now allows a function pointer operand as well as a function operand. * Sema now has a coercion from function body to function pointer. * Function type syntax, e.g. `fn()void`, now returns zig tag type of Pointer with child Fn, rather than Fn directly. - I think this should probably be reverted. Will discuss the lang specs before doing this. Idea being that function pointers would need to be specified as `*const fn()void` rather than `fn() void`. LLVM backend: * Enable calling the panic handler (previously this just emitted `@breakpoint()` since the backend could not handle the panic function). * Implement sret * Introduce `isByRef` and implement it for structs and arrays. Types that are `isByRef` are now passed as pointers to functions, and e.g. `elem_val` will return a pointer instead of doing a load. * Move the function type creating code from `resolveLlvmFunction` to `llvmType` where it belongs; now there is only 1 instance of this logic instead of two. * Add the `nonnull` attribute to non-optional pointer parameters. * Fix `resolveGlobalDecl` not using fully-qualified names and not using the `decl_map`. * Implement `genTypedValue` for pointer-like optionals. * Fix memory leak when lowering `block` instruction and OOM occurs. * Implement volatile checks where relevant. --- src/codegen/c.zig | 84 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 67 insertions(+), 17 deletions(-) (limited to 'src/codegen/c.zig') diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 6fcde5eead..466322c0a3 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -384,12 +384,6 @@ pub const DeclGen = struct { } }, .Fn => switch (val.tag()) { - .null_value, .zero => try writer.writeAll("NULL"), - .one => try writer.writeAll("1"), - .decl_ref => { - const decl = val.castTag(.decl_ref).?.data; - return dg.renderDeclValue(writer, ty, val, decl); - }, .function => { const decl = val.castTag(.function).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); @@ -1026,6 +1020,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .is_non_null_ptr => try airIsNull(f, inst, "!=", "[0]"), .alloc => try airAlloc(f, inst), + .ret_ptr => try airRetPtr(f, inst), .assembly => try airAsm(f, inst), .block => try airBlock(f, inst), .bitcast => try airBitcast(f, inst), @@ -1036,6 +1031,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .bool_to_int => try airBoolToInt(f, inst), .load => try airLoad(f, inst), .ret => try airRet(f, inst), + .ret_load => try airRetLoad(f, inst), .store => try airStore(f, inst), .loop => try airLoop(f, inst), .cond_br => try airCondBr(f, inst), @@ -1081,6 +1077,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO .ptr_elem_ptr => try airPtrElemPtr(f, inst), .slice_elem_val => try airSliceElemVal(f, inst, "["), .ptr_slice_elem_val => try airSliceElemVal(f, inst, "[0]["), + .array_elem_val => try airArrayElemVal(f, inst), .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst), .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst), @@ -1148,6 +1145,22 @@ fn airSliceElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CVal return local; } +fn airArrayElemVal(f: *Function, inst: Air.Inst.Index) !CValue { + if (f.liveness.isUnused(inst)) return CValue.none; + + const bin_op = f.air.instructions.items(.data)[inst].bin_op; + const array = try f.resolveInst(bin_op.lhs); + const index = try f.resolveInst(bin_op.rhs); + const writer = f.object.writer(); + const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); + try writer.writeAll(" = "); + try f.writeCValue(writer, array); + try writer.writeAll("["); + try f.writeCValue(writer, index); + try writer.writeAll("];\n"); + return local; +} + fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); @@ -1161,6 +1174,18 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { return CValue{ .local_ref = local.local }; } +fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue { + const writer = f.object.writer(); + const inst_ty = f.air.typeOfIndex(inst); + + // First line: the variable used as data storage. + const elem_type = inst_ty.elemType(); + const local = try f.allocLocal(elem_type, .Mut); + try writer.writeAll(";\n"); + + return CValue{ .local_ref = local.local }; +} + fn airArg(f: *Function) CValue { const i = f.next_arg_index; f.next_arg_index += 1; @@ -1212,6 +1237,21 @@ fn airRet(f: *Function, inst: Air.Inst.Index) !CValue { return CValue.none; } +fn airRetLoad(f: *Function, inst: Air.Inst.Index) !CValue { + const un_op = f.air.instructions.items(.data)[inst].un_op; + const writer = f.object.writer(); + const ptr_ty = f.air.typeOf(un_op); + const ret_ty = ptr_ty.childType(); + if (!ret_ty.hasCodeGenBits()) { + try writer.writeAll("return;\n"); + } + const ptr = try f.resolveInst(un_op); + try writer.writeAll("return *"); + try f.writeCValue(writer, ptr); + try writer.writeAll(";\n"); + return CValue.none; +} + fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; @@ -1559,7 +1599,12 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Call, pl_op.payload); const args = @bitCast([]const Air.Inst.Ref, f.air.extra[extra.end..][0..extra.data.args_len]); - const fn_ty = f.air.typeOf(pl_op.operand); + const callee_ty = f.air.typeOf(pl_op.operand); + const fn_ty = switch (callee_ty.zigTypeTag()) { + .Fn => callee_ty, + .Pointer => callee_ty.childType(), + else => unreachable, + }; const ret_ty = fn_ty.fnReturnType(); const unused_result = f.liveness.isUnused(inst); const writer = f.object.writer(); @@ -1574,16 +1619,21 @@ fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { try writer.writeAll(" = "); } - if (f.air.value(pl_op.operand)) |func_val| { - const fn_decl = if (func_val.castTag(.extern_fn)) |extern_fn| - extern_fn.data - else if (func_val.castTag(.function)) |func_payload| - func_payload.data.owner_decl - else - unreachable; - - try f.object.dg.renderDeclName(fn_decl, writer); - } else { + callee: { + known: { + const fn_decl = fn_decl: { + const callee_val = f.air.value(pl_op.operand) orelse break :known; + break :fn_decl switch (callee_val.tag()) { + .extern_fn => callee_val.castTag(.extern_fn).?.data, + .function => callee_val.castTag(.function).?.data.owner_decl, + .decl_ref => callee_val.castTag(.decl_ref).?.data, + else => break :known, + }; + }; + try f.object.dg.renderDeclName(fn_decl, writer); + break :callee; + } + // Fall back to function pointer call. const callee = try f.resolveInst(pl_op.operand); try f.writeCValue(writer, callee); } -- cgit v1.2.3