const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const mem = std.mem; const log = std.log.scoped(.c); const link = @import("../link.zig"); const Module = @import("../Module.zig"); const Compilation = @import("../Compilation.zig"); const Value = @import("../value.zig").Value; const Type = @import("../type.zig").Type; const TypedValue = @import("../TypedValue.zig"); const C = link.File.C; const Decl = Module.Decl; const trace = @import("../tracy.zig").trace; const LazySrcLoc = Module.LazySrcLoc; const Air = @import("../Air.zig"); const Liveness = @import("../Liveness.zig"); const CType = @import("../type.zig").CType; const Mutability = enum { Const, Mut }; const BigIntConst = std.math.big.int.Const; pub const CValue = union(enum) { none: void, /// Index into local_names local: usize, /// Index into local_names, but take the address. local_ref: usize, /// A constant instruction, to be rendered inline. constant: Air.Inst.Ref, /// Index into the parameters arg: usize, /// By-value decl: Decl.Index, decl_ref: Decl.Index, /// An undefined (void *) pointer (cannot be dereferenced) undefined_ptr: void, /// Render the slice as an identifier (using fmtIdent) identifier: []const u8, /// Render these bytes literally. /// TODO make this a [*:0]const u8 to save memory bytes: []const u8, }; const BlockData = struct { block_id: usize, result: CValue, }; pub const CValueMap = std.AutoHashMap(Air.Inst.Ref, CValue); pub const TypedefMap = std.ArrayHashMap( Type, struct { name: []const u8, rendered: []u8 }, Type.HashContext32, true, ); const FormatTypeAsCIdentContext = struct { ty: Type, mod: *Module, }; const ValueRenderLocation = enum { FunctionArgument, Other, }; /// TODO make this not cut off at 128 bytes fn formatTypeAsCIdentifier( data: FormatTypeAsCIdentContext, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; var buffer = [1]u8{0} ** 128; var buf = std.fmt.bufPrint(&buffer, "{}", .{data.ty.fmt(data.mod)}) catch &buffer; return formatIdent(buf, "", .{}, writer); } pub fn typeToCIdentifier(ty: Type, mod: *Module) std.fmt.Formatter(formatTypeAsCIdentifier) { return .{ .data = .{ .ty = ty, .mod = mod, } }; } const reserved_idents = std.ComptimeStringMap(void, .{ .{ "_Alignas", { @setEvalBranchQuota(4000); } }, .{ "_Alignof", {} }, .{ "_Atomic", {} }, .{ "_Bool", {} }, .{ "_Complex", {} }, .{ "_Decimal128", {} }, .{ "_Decimal32", {} }, .{ "_Decimal64", {} }, .{ "_Generic", {} }, .{ "_Imaginary", {} }, .{ "_Noreturn", {} }, .{ "_Pragma", {} }, .{ "_Static_assert", {} }, .{ "_Thread_local", {} }, .{ "alignas", {} }, .{ "alignof", {} }, .{ "asm", {} }, .{ "atomic_bool", {} }, .{ "atomic_char", {} }, .{ "atomic_char16_t", {} }, .{ "atomic_char32_t", {} }, .{ "atomic_int", {} }, .{ "atomic_int_fast16_t", {} }, .{ "atomic_int_fast32_t", {} }, .{ "atomic_int_fast64_t", {} }, .{ "atomic_int_fast8_t", {} }, .{ "atomic_int_least16_t", {} }, .{ "atomic_int_least32_t", {} }, .{ "atomic_int_least64_t", {} }, .{ "atomic_int_least8_t", {} }, .{ "atomic_intmax_t", {} }, .{ "atomic_intptr_t", {} }, .{ "atomic_llong", {} }, .{ "atomic_long", {} }, .{ "atomic_ptrdiff_t", {} }, .{ "atomic_schar", {} }, .{ "atomic_short", {} }, .{ "atomic_size_t", {} }, .{ "atomic_uchar", {} }, .{ "atomic_uint", {} }, .{ "atomic_uint_fast16_t", {} }, .{ "atomic_uint_fast32_t", {} }, .{ "atomic_uint_fast64_t", {} }, .{ "atomic_uint_fast8_t", {} }, .{ "atomic_uint_least16_t", {} }, .{ "atomic_uint_least32_t", {} }, .{ "atomic_uint_least64_t", {} }, .{ "atomic_uint_least8_t", {} }, .{ "atomic_uintmax_t", {} }, .{ "atomic_uintptr_t", {} }, .{ "atomic_ullong", {} }, .{ "atomic_ulong", {} }, .{ "atomic_ushort", {} }, .{ "atomic_wchar_t", {} }, .{ "auto", {} }, .{ "bool", {} }, .{ "break", {} }, .{ "case", {} }, .{ "char", {} }, .{ "complex", {} }, .{ "const", {} }, .{ "continue", {} }, .{ "default", {} }, .{ "do", {} }, .{ "double", {} }, .{ "else", {} }, .{ "enum", {} }, .{ "extern ", {} }, .{ "float", {} }, .{ "for", {} }, .{ "fortran", {} }, .{ "goto", {} }, .{ "if", {} }, .{ "imaginary", {} }, .{ "inline", {} }, .{ "int", {} }, .{ "int16_t", {} }, .{ "int32_t", {} }, .{ "int64_t", {} }, .{ "int8_t", {} }, .{ "intptr_t", {} }, .{ "long", {} }, .{ "noreturn", {} }, .{ "register", {} }, .{ "restrict", {} }, .{ "return", {} }, .{ "short ", {} }, .{ "signed", {} }, .{ "size_t", {} }, .{ "sizeof", {} }, .{ "ssize_t", {} }, .{ "static", {} }, .{ "static_assert", {} }, .{ "struct", {} }, .{ "switch", {} }, .{ "thread_local", {} }, .{ "typedef", {} }, .{ "uint16_t", {} }, .{ "uint32_t", {} }, .{ "uint64_t", {} }, .{ "uint8_t", {} }, .{ "uintptr_t", {} }, .{ "union", {} }, .{ "unsigned", {} }, .{ "void", {} }, .{ "volatile", {} }, .{ "while ", {} }, }); fn formatIdent( ident: []const u8, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = options; const solo = fmt.len != 0 and fmt[0] == ' '; // space means solo; not part of a bigger ident. if (solo and reserved_idents.has(ident)) { try writer.writeAll("zig_e_"); } for (ident) |c, i| { switch (c) { 'a'...'z', 'A'...'Z', '_' => try writer.writeByte(c), '.' => try writer.writeByte('_'), '0'...'9' => if (i == 0) { try writer.print("_{x:2}", .{c}); } else { try writer.writeByte(c); }, else => try writer.print("_{x:2}", .{c}), } } } pub fn fmtIdent(ident: []const u8) std.fmt.Formatter(formatIdent) { return .{ .data = ident }; } /// This data is available when outputting .c code for a `*Module.Fn`. /// It is not available when generating .h file. pub const Function = struct { air: Air, liveness: Liveness, value_map: CValueMap, blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .{}, next_arg_index: usize = 0, next_local_index: usize = 0, next_block_index: usize = 0, object: Object, func: *Module.Fn, fn resolveInst(f: *Function, inst: Air.Inst.Ref) !CValue { const gop = try f.value_map.getOrPut(inst); if (gop.found_existing) return gop.value_ptr.*; const val = f.air.value(inst).?; const ty = f.air.typeOf(inst); switch (ty.zigTypeTag()) { .Array => { const writer = f.object.code_header.writer(); const decl_c_value = f.allocLocalValue(); gop.value_ptr.* = decl_c_value; try writer.writeAll("static "); try f.object.dg.renderTypeAndName( writer, ty, decl_c_value, .Const, 0, ); try writer.writeAll(" = "); try f.object.dg.renderValue(writer, ty, val, .Other); try writer.writeAll(";\n "); return decl_c_value; }, else => { const result = CValue{ .constant = inst }; gop.value_ptr.* = result; return result; }, } } fn allocLocalValue(f: *Function) CValue { const result = f.next_local_index; f.next_local_index += 1; return .{ .local = result }; } fn allocLocal(f: *Function, ty: Type, mutability: Mutability) !CValue { return f.allocAlignedLocal(ty, mutability, 0); } fn allocAlignedLocal(f: *Function, ty: Type, mutability: Mutability, alignment: u32) !CValue { const local_value = f.allocLocalValue(); try f.object.dg.renderTypeAndName( f.object.writer(), ty, local_value, mutability, alignment, ); return local_value; } fn writeCValue(f: *Function, w: anytype, c_value: CValue) !void { switch (c_value) { .constant => |inst| { const ty = f.air.typeOf(inst); const val = f.air.value(inst).?; return f.object.dg.renderValue(w, ty, val, .Other); }, else => return f.object.dg.writeCValue(w, c_value), } } fn writeCValueDeref(f: *Function, w: anytype, c_value: CValue) !void { switch (c_value) { .constant => |inst| { const ty = f.air.typeOf(inst); const val = f.air.value(inst).?; try w.writeAll("(*"); try f.object.dg.renderValue(w, ty, val, .Other); return w.writeByte(')'); }, else => return f.object.dg.writeCValueDeref(w, c_value), } } fn fail(f: *Function, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { return f.object.dg.fail(format, args); } fn renderType(f: *Function, w: anytype, t: Type) !void { return f.object.dg.renderType(w, t); } fn renderTypecast(f: *Function, w: anytype, t: Type) !void { return f.object.dg.renderTypecast(w, t); } }; /// This data is available when outputting .c code for a `Module`. /// It is not available when generating .h file. pub const Object = struct { dg: DeclGen, code: std.ArrayList(u8), /// Goes before code. Initialized and deinitialized in `genFunc`. code_header: std.ArrayList(u8) = undefined, indent_writer: IndentWriter(std.ArrayList(u8).Writer), fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer { return o.indent_writer.writer(); } }; /// This data is available both when outputting .c code and when outputting an .h file. pub const DeclGen = struct { gpa: std.mem.Allocator, module: *Module, decl: *Decl, decl_index: Decl.Index, fwd_decl: std.ArrayList(u8), error_msg: ?*Module.ErrorMsg, /// The key of this map is Type which has references to typedefs_arena. typedefs: TypedefMap, typedefs_arena: std.mem.Allocator, fn fail(dg: *DeclGen, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { @setCold(true); const src = LazySrcLoc.nodeOffset(0); const src_loc = src.toSrcLoc(dg.decl); dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, src_loc, format, args); return error.AnalysisFail; } fn getTypedefName(dg: *DeclGen, t: Type) ?[]const u8 { if (dg.typedefs.get(t)) |some| { return some.name; } else { return null; } } fn renderDeclValue( dg: *DeclGen, writer: anytype, ty: Type, val: Value, decl_index: Decl.Index, ) error{ OutOfMemory, AnalysisFail }!void { if (ty.isSlice()) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeAll("){"); var buf: Type.SlicePtrFieldTypeBuffer = undefined; try dg.renderValue(writer, ty.slicePtrFieldType(&buf), val.slicePtr(), .Other); try writer.writeAll(", "); try writer.print("{d}", .{val.sliceLen(dg.module)}); try writer.writeAll("}"); return; } const decl = dg.module.declPtr(decl_index); assert(decl.has_tv); // We shouldn't cast C function pointers as this is UB (when you call // them). The analysis until now should ensure that the C function // pointers are compatible. If they are not, then there is a bug // somewhere and we should let the C compiler tell us about it. if (ty.castPtrToFn() == null) { // Determine if we must pointer cast. if (ty.eql(decl.ty, dg.module)) { try writer.writeByte('&'); try dg.renderDeclName(writer, decl_index); return; } try writer.writeAll("(("); try dg.renderTypecast(writer, ty); try writer.writeAll(")&"); try dg.renderDeclName(writer, decl_index); try writer.writeByte(')'); return; } try dg.renderDeclName(writer, decl_index); } fn renderInt128( writer: anytype, int_val: anytype, ) error{ OutOfMemory, AnalysisFail }!void { const int_info = @typeInfo(@TypeOf(int_val)).Int; const is_signed = int_info.signedness == .signed; const is_neg = int_val < 0; comptime assert(int_info.bits > 64 and int_info.bits <= 128); // Clang and GCC don't support 128-bit integer constants but will hopefully unfold them // if we construct one manually. const magnitude = std.math.absCast(int_val); const high = @truncate(u64, magnitude >> 64); const low = @truncate(u64, magnitude); // (int128_t)/<->( ( (uint128_t)( val_high << 64 )u ) + (uint128_t)val_low/u ) if (is_signed) try writer.writeAll("(int128_t)"); if (is_neg) try writer.writeByte('-'); try writer.print("(((uint128_t)0x{x}u<<64)", .{high}); if (low > 0) try writer.print("+(uint128_t)0x{x}u", .{low}); return writer.writeByte(')'); } fn renderBigIntConst( dg: *DeclGen, writer: anytype, val: BigIntConst, signed: bool, ) error{ OutOfMemory, AnalysisFail }!void { if (signed) { try renderInt128(writer, val.to(i128) catch { return dg.fail("TODO implement integer constants larger than 128 bits", .{}); }); } else { try renderInt128(writer, val.to(u128) catch { return dg.fail("TODO implement integer constants larger than 128 bits", .{}); }); } } // Renders a "parent" pointer by recursing to the root decl/variable // that its contents are defined with respect to. // // Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr fn renderParentPtr(dg: *DeclGen, writer: anytype, ptr_val: Value, ptr_ty: Type) error{ OutOfMemory, AnalysisFail }!void { try writer.writeByte('('); try dg.renderTypecast(writer, ptr_ty); try writer.writeByte(')'); switch (ptr_val.tag()) { .decl_ref_mut, .decl_ref, .variable => { const decl_index = switch (ptr_val.tag()) { .decl_ref => ptr_val.castTag(.decl_ref).?.data, .decl_ref_mut => ptr_val.castTag(.decl_ref_mut).?.data.decl_index, .variable => ptr_val.castTag(.variable).?.data.owner_decl, else => unreachable, }; try dg.renderDeclValue(writer, ptr_ty, ptr_val, decl_index); }, .field_ptr => { const field_ptr = ptr_val.castTag(.field_ptr).?.data; const container_ty = field_ptr.container_ty; const index = field_ptr.field_index; const FieldInfo = struct { name: []const u8, ty: Type }; const field_info: FieldInfo = switch (container_ty.zigTypeTag()) { .Struct => .{ .name = container_ty.structFields().keys()[index], .ty = container_ty.structFields().values()[index].ty, }, .Union => .{ .name = container_ty.unionFields().keys()[index], .ty = container_ty.unionFields().values()[index].ty, }, .Pointer => switch (container_ty.ptrSize()) { .Slice => switch (index) { 0 => FieldInfo{ .name = "ptr", .ty = container_ty.childType() }, 1 => FieldInfo{ .name = "len", .ty = Type.usize }, else => unreachable, }, else => unreachable, }, else => unreachable, }; var container_ptr_ty_pl: Type.Payload.ElemType = .{ .base = .{ .tag = .c_mut_pointer }, .data = field_ptr.container_ty, }; const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base); if (field_info.ty.hasRuntimeBitsIgnoreComptime()) { try writer.writeAll("&("); try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty); if (field_ptr.container_ty.tag() == .union_tagged or field_ptr.container_ty.tag() == .union_safety_tagged) { try writer.print(")->payload.{ }", .{fmtIdent(field_info.name)}); } else { try writer.print(")->{ }", .{fmtIdent(field_info.name)}); } } else { try dg.renderParentPtr(writer, field_ptr.container_ptr, field_info.ty); } }, .elem_ptr => { const elem_ptr = ptr_val.castTag(.elem_ptr).?.data; var elem_ptr_ty_pl: Type.Payload.ElemType = .{ .base = .{ .tag = .c_mut_pointer }, .data = elem_ptr.elem_ty, }; const elem_ptr_ty = Type.initPayload(&elem_ptr_ty_pl.base); try writer.writeAll("&("); try dg.renderParentPtr(writer, elem_ptr.array_ptr, elem_ptr_ty); try writer.print(")[{d}]", .{elem_ptr.index}); }, .opt_payload_ptr, .eu_payload_ptr => { const payload_ptr = ptr_val.cast(Value.Payload.PayloadPtr).?.data; var container_ptr_ty_pl: Type.Payload.ElemType = .{ .base = .{ .tag = .c_mut_pointer }, .data = payload_ptr.container_ty, }; const container_ptr_ty = Type.initPayload(&container_ptr_ty_pl.base); try writer.writeAll("&("); try dg.renderParentPtr(writer, payload_ptr.container_ptr, container_ptr_ty); try writer.writeAll(")->payload"); }, else => unreachable, } } fn renderValue( dg: *DeclGen, writer: anytype, ty: Type, val: Value, location: ValueRenderLocation, ) error{ OutOfMemory, AnalysisFail }!void { const target = dg.module.getTarget(); if (val.isUndefDeep()) { switch (ty.zigTypeTag()) { // Using '{}' for integer and floats seemed to error C compilers (both GCC and Clang) // with 'error: expected expression' (including when built with 'zig cc') .Int => { const c_bits = toCIntBits(ty.intInfo(dg.module.getTarget()).bits) orelse return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); switch (c_bits) { 8 => return writer.writeAll("0xaau"), 16 => return writer.writeAll("0xaaaau"), 32 => return writer.writeAll("0xaaaaaaaau"), 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaau"), 128 => return renderInt128(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), else => unreachable, } }, .Float => { switch (ty.floatBits(dg.module.getTarget())) { 32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaau)"), 64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaau)"), else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}), } }, .Pointer => switch (dg.module.getTarget().cpu.arch.ptrBitWidth()) { 32 => return writer.writeAll("(void *)0xaaaaaaaa"), 64 => return writer.writeAll("(void *)0xaaaaaaaaaaaaaaaa"), else => unreachable, }, .Struct, .ErrorUnion => { try writer.writeByte('('); try dg.renderTypecast(writer, ty); return writer.writeAll("){0xaa}"); }, else => { // This should lower to 0xaa bytes in safe modes, and for unsafe modes should // lower to leaving variables uninitialized (that might need to be implemented // outside of this function). return writer.writeAll("{}"); }, } } switch (ty.zigTypeTag()) { .Int => switch (val.tag()) { .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()), .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true), .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr, .decl_ref_mut, .decl_ref, => try dg.renderParentPtr(writer, val, ty), else => { if (ty.isSignedInt()) return writer.print("{d}", .{val.toSignedInt()}); return writer.print("{d}u", .{val.toUnsignedInt(target)}); }, }, .Float => { if (ty.floatBits(dg.module.getTarget()) <= 64) { if (std.math.isNan(val.toFloat(f64)) or std.math.isInf(val.toFloat(f64))) { // just generate a bit cast (exactly like we do in airBitcast) switch (ty.tag()) { .f32 => return writer.print("zig_bitcast_f32_u32(0x{x})", .{@bitCast(u32, val.toFloat(f32))}), .f64 => return writer.print("zig_bitcast_f64_u64(0x{x})", .{@bitCast(u64, val.toFloat(f64))}), else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}), } } else { return writer.print("{x}", .{val.toFloat(f64)}); } } return dg.fail("TODO: C backend: implement lowering large float values", .{}); }, .Pointer => switch (val.tag()) { .null_value => try writer.writeAll("NULL"), // Technically this should produce NULL but the integer literal 0 will always coerce // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc) // between pointers and 0, which is an extension to begin with. .zero => try writer.writeByte('0'), .variable => { const decl = val.castTag(.variable).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); }, .slice => { const slice = val.castTag(.slice).?.data; var buf: Type.SlicePtrFieldTypeBuffer = undefined; try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeAll("){"); try dg.renderValue(writer, ty.slicePtrFieldType(&buf), slice.ptr, location); try writer.writeAll(", "); try dg.renderValue(writer, Type.usize, slice.len, location); try writer.writeAll("}"); }, .function => { const func = val.castTag(.function).?.data; try dg.renderDeclName(writer, func.owner_decl); }, .extern_fn => { const extern_fn = val.castTag(.extern_fn).?.data; try dg.renderDeclName(writer, extern_fn.owner_decl); }, .int_u64, .one => { try writer.writeAll("(("); try dg.renderTypecast(writer, ty); try writer.print(")0x{x}u)", .{val.toUnsignedInt(target)}); }, .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr, .decl_ref_mut, .decl_ref, => try dg.renderParentPtr(writer, val, ty), else => unreachable, }, .Array => { // First try specific tag representations for more efficiency. switch (val.tag()) { .undef, .empty_struct_value, .empty_array => { try writer.writeByte('{'); const ai = ty.arrayInfo(); if (ai.sentinel) |s| { try dg.renderValue(writer, ai.elem_type, s, location); } try writer.writeByte('}'); }, else => { // Fall back to generic implementation. var arena = std.heap.ArenaAllocator.init(dg.module.gpa); defer arena.deinit(); const arena_allocator = arena.allocator(); if (location == .FunctionArgument) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('{'); const ai = ty.arrayInfo(); var index: usize = 0; while (index < ai.len) : (index += 1) { if (index != 0) try writer.writeAll(","); const elem_val = try val.elemValue(dg.module, arena_allocator, index); try dg.renderValue(writer, ai.elem_type, elem_val, .Other); } if (ai.sentinel) |s| { if (index != 0) try writer.writeAll(","); try dg.renderValue(writer, ai.elem_type, s, .Other); } try writer.writeByte('}'); }, } }, .Bool => return writer.print("{}", .{val.toBool()}), .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const payload_ty = ty.optionalChild(&opt_buf); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { const is_null = val.castTag(.opt_payload) == null; return writer.print("{}", .{is_null}); } if (ty.optionalReprIsPayload()) { return dg.renderValue(writer, payload_ty, val, location); } try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeAll("){"); if (val.castTag(.opt_payload)) |pl| { const payload_val = pl.data; try writer.writeAll(" .is_null = false, .payload = "); try dg.renderValue(writer, payload_ty, payload_val, location); try writer.writeAll(" }"); } else { try writer.writeAll(" .is_null = true }"); } }, .ErrorSet => { switch (val.tag()) { .@"error" => { const payload = val.castTag(.@"error").?; // error values will be #defined at the top of the file return writer.print("zig_error_{s}", .{payload.data.name}); }, else => { // In this case we are rendering an error union which has a // 0 bits payload. return writer.writeAll("0"); }, } }, .ErrorUnion => { const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); if (!payload_type.hasRuntimeBits()) { // We use the error type directly as the type. const err_val = if (val.errorUnionIsPayload()) Value.initTag(.zero) else val; return dg.renderValue(writer, error_type, err_val, location); } try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeAll("){"); if (val.castTag(.eu_payload)) |pl| { const payload_val = pl.data; try writer.writeAll(" .payload = "); try dg.renderValue(writer, payload_type, payload_val, location); try writer.writeAll(", .error = 0 }"); } else { try writer.writeAll(" .error = "); try dg.renderValue(writer, error_type, val, location); try writer.writeAll(" }"); } }, .Enum => { switch (val.tag()) { .enum_field_index => { const field_index = val.castTag(.enum_field_index).?.data; switch (ty.tag()) { .enum_simple => return writer.print("{d}", .{field_index}), .enum_full, .enum_nonexhaustive => { const enum_full = ty.cast(Type.Payload.EnumFull).?.data; if (enum_full.values.count() != 0) { const tag_val = enum_full.values.keys()[field_index]; return dg.renderValue(writer, enum_full.tag_ty, tag_val, location); } else { return writer.print("{d}", .{field_index}); } }, .enum_numbered => { const enum_obj = ty.castTag(.enum_numbered).?.data; if (enum_obj.values.count() != 0) { const tag_val = enum_obj.values.keys()[field_index]; return dg.renderValue(writer, enum_obj.tag_ty, tag_val, location); } else { return writer.print("{d}", .{field_index}); } }, else => unreachable, } }, else => { var int_tag_ty_buffer: Type.Payload.Bits = undefined; const int_tag_ty = ty.intTagType(&int_tag_ty_buffer); return dg.renderValue(writer, int_tag_ty, val, location); }, } }, .Fn => switch (val.tag()) { .function => { const decl = val.castTag(.function).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); }, .extern_fn => { const decl = val.castTag(.extern_fn).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); }, else => unreachable, }, .Struct => { const field_vals = val.castTag(.aggregate).?.data; try writer.writeAll("("); try dg.renderTypecast(writer, ty); try writer.writeAll("){"); var i: usize = 0; for (field_vals) |field_val, field_index| { const field_ty = ty.structFieldType(field_index); if (!field_ty.hasRuntimeBits()) continue; if (i != 0) try writer.writeAll(","); try dg.renderValue(writer, field_ty, field_val, location); i += 1; } try writer.writeAll("}"); }, .Union => { const union_obj = val.castTag(.@"union").?.data; const layout = ty.unionGetLayout(target); try writer.writeAll("("); try dg.renderTypecast(writer, ty); try writer.writeAll("){"); if (ty.unionTagTypeSafety()) |tag_ty| { if (layout.tag_size != 0) { try writer.writeAll(".tag = "); try dg.renderValue(writer, tag_ty, union_obj.tag, location); try writer.writeAll(", "); } try writer.writeAll(".payload = {"); } const index = ty.unionTagFieldIndex(union_obj.tag, dg.module).?; const field_ty = ty.unionFields().values()[index].ty; const field_name = ty.unionFields().keys()[index]; if (field_ty.hasRuntimeBits()) { try writer.print(".{ } = ", .{fmtIdent(field_name)}); try dg.renderValue(writer, field_ty, union_obj.val, location); } if (ty.unionTagTypeSafety()) |_| { try writer.writeAll("}"); } try writer.writeAll("}"); }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, .Type => unreachable, .EnumLiteral => unreachable, .Void => unreachable, .NoReturn => unreachable, .Undefined => unreachable, .Null => unreachable, .BoundFn => unreachable, .Opaque => unreachable, .Frame, .AnyFrame, .Vector, => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{ @tagName(tag), }), } } fn renderFunctionSignature(dg: *DeclGen, w: anytype, is_global: bool) !void { if (!is_global) { try w.writeAll("static "); } if (dg.decl.val.castTag(.function)) |func_payload| { const func: *Module.Fn = func_payload.data; if (func.is_cold) { try w.writeAll("ZIG_COLD "); } } const fn_info = dg.decl.ty.fnInfo(); if (fn_info.return_type.hasRuntimeBits()) { try dg.renderType(w, fn_info.return_type); } else if (fn_info.return_type.isError()) { try dg.renderType(w, Type.anyerror); } else if (fn_info.return_type.zigTypeTag() == .NoReturn) { try w.writeAll("zig_noreturn void"); } else { try w.writeAll("void"); } try w.writeAll(" "); try dg.renderDeclName(w, dg.decl_index); try w.writeAll("("); var params_written: usize = 0; for (fn_info.param_types) |param_type, index| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; if (params_written > 0) { try w.writeAll(", "); } const name = CValue{ .arg = index }; try dg.renderTypeAndName(w, param_type, name, .Mut, 0); params_written += 1; } if (fn_info.is_var_args) { if (params_written != 0) try w.writeAll(", "); try w.writeAll("..."); } else if (params_written == 0) { try w.writeAll("void"); } try w.writeByte(')'); } fn renderPtrToFnTypedef(dg: *DeclGen, t: Type, fn_ty: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); const fn_info = fn_ty.fnInfo(); try bw.writeAll("typedef "); try dg.renderType(bw, fn_info.return_type); try bw.writeAll(" (*"); const name_start = buffer.items.len; try bw.print("zig_F_{s})(", .{typeToCIdentifier(t, dg.module)}); const name_end = buffer.items.len - 2; const param_len = fn_info.param_types.len; var params_written: usize = 0; var index: usize = 0; while (index < param_len) : (index += 1) { if (!fn_info.param_types[index].hasRuntimeBitsIgnoreComptime()) continue; if (params_written > 0) { try bw.writeAll(", "); } try dg.renderTypecast(bw, fn_info.param_types[index]); params_written += 1; } if (fn_info.is_var_args) { if (params_written != 0) try bw.writeAll(", "); try bw.writeAll("..."); } else if (params_written == 0) { try bw.writeAll("void"); } try bw.writeAll(");\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start..name_end]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderSliceTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); try bw.writeAll("typedef struct { "); var ptr_type_buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_type = t.slicePtrFieldType(&ptr_type_buf); const ptr_name = CValue{ .bytes = "ptr" }; try dg.renderTypeAndName(bw, ptr_type, ptr_name, .Mut, 0); const ptr_sentinel = ptr_type.ptrInfo().data.sentinel; const child_type = t.childType(); try bw.writeAll("; size_t len; } "); const name_index = buffer.items.len; if (t.isConstPtr()) { try bw.print("zig_L_{s}", .{typeToCIdentifier(child_type, dg.module)}); } else { try bw.print("zig_M_{s}", .{typeToCIdentifier(child_type, dg.module)}); } if (ptr_sentinel) |s| { try bw.writeAll("_s_"); try dg.renderValue(bw, child_type, s, .Other); } try bw.writeAll(";\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_index .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderStructTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const struct_obj = t.castTag(.@"struct").?.data; // Handle 0 bit types elsewhere. const fqn = try struct_obj.getFullyQualifiedName(dg.module); defer dg.typedefs.allocator.free(fqn); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.appendSlice("typedef struct {\n"); { var it = struct_obj.fields.iterator(); while (it.next()) |entry| { const field_ty = entry.value_ptr.ty; if (!field_ty.hasRuntimeBits()) continue; const alignment = entry.value_ptr.abi_align; const name: CValue = .{ .identifier = entry.key_ptr.* }; try buffer.append(' '); try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut, alignment); try buffer.appendSlice(";\n"); } } try buffer.appendSlice("} "); const name_start = buffer.items.len; try buffer.writer().print("zig_S_{};\n", .{fmtIdent(fqn)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderTupleTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const tuple = t.tupleFields(); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const writer = buffer.writer(); try buffer.appendSlice("typedef struct {\n"); { for (tuple.types) |field_ty, i| { const val = tuple.values[i]; if (val.tag() != .unreachable_value) continue; var name = std.ArrayList(u8).init(dg.gpa); defer name.deinit(); try name.writer().print("field_{d}", .{i}); try buffer.append(' '); try dg.renderTypeAndName(writer, field_ty, .{ .bytes = name.items }, .Mut, 0); try buffer.appendSlice(";\n"); } } try buffer.appendSlice("} "); const name_start = buffer.items.len; try writer.print("zig_T_{};\n", .{typeToCIdentifier(t, dg.module)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const union_ty = t.cast(Type.Payload.Union).?.data; const fqn = try union_ty.getFullyQualifiedName(dg.module); defer dg.typedefs.allocator.free(fqn); const target = dg.module.getTarget(); const layout = t.unionGetLayout(target); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.appendSlice("typedef "); if (t.unionTagTypeSafety()) |tag_ty| { const name: CValue = .{ .bytes = "tag" }; try buffer.appendSlice("struct {\n "); if (layout.tag_size != 0) { try dg.renderTypeAndName(buffer.writer(), tag_ty, name, .Mut, 0); try buffer.appendSlice(";\n"); } } try buffer.appendSlice("union {\n"); { var it = t.unionFields().iterator(); while (it.next()) |entry| { const field_ty = entry.value_ptr.ty; if (!field_ty.hasRuntimeBits()) continue; const alignment = entry.value_ptr.abi_align; const name: CValue = .{ .identifier = entry.key_ptr.* }; try buffer.append(' '); try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut, alignment); try buffer.appendSlice(";\n"); } } try buffer.appendSlice("} "); if (t.unionTagTypeSafety()) |_| { try buffer.appendSlice("payload;\n} "); } const name_start = buffer.items.len; try buffer.writer().print("zig_U_{};\n", .{fmtIdent(fqn)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const payload_ty = t.errorUnionPayload(); const error_ty = t.errorUnionSet(); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); const payload_name = CValue{ .bytes = "payload" }; const target = dg.module.getTarget(); const payload_align = payload_ty.abiAlignment(target); const error_align = Type.anyerror.abiAlignment(target); if (error_align > payload_align) { try bw.writeAll("typedef struct { "); try dg.renderTypeAndName(bw, payload_ty, payload_name, .Mut, 0); try bw.writeAll("; uint16_t error; } "); } else { try bw.writeAll("typedef struct { uint16_t error; "); try dg.renderTypeAndName(bw, payload_ty, payload_name, .Mut, 0); try bw.writeAll("; } "); } const name_index = buffer.items.len; if (error_ty.castTag(.error_set_inferred)) |inf_err_set_payload| { const func = inf_err_set_payload.data.func; try bw.writeAll("zig_E_"); try dg.renderDeclName(bw, func.owner_decl); try bw.writeAll(";\n"); } else { try bw.print("zig_E_{s}_{s};\n", .{ typeToCIdentifier(error_ty, dg.module), typeToCIdentifier(payload_ty, dg.module), }); } const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_index .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderArrayTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); const elem_type = t.elemType(); const sentinel_bit = @boolToInt(t.sentinel() != null); const c_len = t.arrayLen() + sentinel_bit; try bw.writeAll("typedef "); try dg.renderType(bw, elem_type); const name_start = buffer.items.len + 1; try bw.print(" zig_A_{s}_{d}", .{ typeToCIdentifier(elem_type, dg.module), c_len }); const name_end = buffer.items.len; try bw.print("[{d}];\n", .{c_len}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start..name_end]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderOptionalTypedef(dg: *DeclGen, t: Type, child_type: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); try bw.writeAll("typedef struct { "); const payload_name = CValue{ .bytes = "payload" }; try dg.renderTypeAndName(bw, child_type, payload_name, .Mut, 0); try bw.writeAll("; bool is_null; } "); const name_index = buffer.items.len; try bw.print("zig_Q_{s};\n", .{typeToCIdentifier(child_type, dg.module)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_index .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } /// Renders a type as a single identifier, generating intermediate typedefs /// if necessary. /// /// This is guaranteed to be valid in both typedefs and declarations/definitions. /// /// There are three type formats in total that we support rendering: /// | Function | Example 1 (*u8) | Example 2 ([10]*u8) | /// |---------------------|-----------------|---------------------| /// | `renderTypecast` | "uint8_t *" | "uint8_t *[10]" | /// | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" | /// | `renderType` | "uint8_t *" | "zig_A_uint8_t_10" | /// fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void { const target = dg.module.getTarget(); switch (t.zigTypeTag()) { .NoReturn, .Void => try w.writeAll("void"), .Bool => try w.writeAll("bool"), .Int => { switch (t.tag()) { .u1, .u8 => try w.writeAll("uint8_t"), .i8 => try w.writeAll("int8_t"), .u16 => try w.writeAll("uint16_t"), .i16 => try w.writeAll("int16_t"), .u32 => try w.writeAll("uint32_t"), .i32 => try w.writeAll("int32_t"), .u64 => try w.writeAll("uint64_t"), .i64 => try w.writeAll("int64_t"), .u128 => try w.writeAll("uint128_t"), .i128 => try w.writeAll("int128_t"), .usize => try w.writeAll("uintptr_t"), .isize => try w.writeAll("intptr_t"), .c_short => try w.writeAll("short"), .c_ushort => try w.writeAll("unsigned short"), .c_int => try w.writeAll("int"), .c_uint => try w.writeAll("unsigned int"), .c_long => try w.writeAll("long"), .c_ulong => try w.writeAll("unsigned long"), .c_longlong => try w.writeAll("long long"), .c_ulonglong => try w.writeAll("unsigned long long"), .int_signed, .int_unsigned => { const info = t.intInfo(target); const sign_prefix = switch (info.signedness) { .signed => "", .unsigned => "u", }; const c_bits = toCIntBits(info.bits) orelse return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); try w.print("{s}int{d}_t", .{ sign_prefix, c_bits }); }, else => unreachable, } }, .Float => { switch (t.tag()) { .f32 => try w.writeAll("float"), .f64 => try w.writeAll("double"), .c_longdouble => try w.writeAll("long double"), .f16 => return dg.fail("TODO: C backend: implement float type f16", .{}), .f128 => return dg.fail("TODO: C backend: implement float type f128", .{}), else => unreachable, } }, .Pointer => { if (t.isSlice()) { const name = dg.getTypedefName(t) orelse try dg.renderSliceTypedef(t); return w.writeAll(name); } if (t.castPtrToFn()) |fn_ty| { const name = dg.getTypedefName(t) orelse try dg.renderPtrToFnTypedef(t, fn_ty); return w.writeAll(name); } try dg.renderType(w, t.elemType()); if (t.isConstPtr()) { try w.writeAll(" const"); } if (t.isVolatilePtr()) { try w.writeAll(" volatile"); } return w.writeAll(" *"); }, .Array => { const name = dg.getTypedefName(t) orelse try dg.renderArrayTypedef(t); return w.writeAll(name); }, .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const child_type = t.optionalChild(&opt_buf); if (!child_type.hasRuntimeBitsIgnoreComptime()) { return w.writeAll("bool"); } if (t.optionalReprIsPayload()) { return dg.renderType(w, child_type); } const name = dg.getTypedefName(t) orelse try dg.renderOptionalTypedef(t, child_type); return w.writeAll(name); }, .ErrorSet => { comptime assert(Type.anyerror.abiSize(builtin.target) == 2); return w.writeAll("uint16_t"); }, .ErrorUnion => { const payload_ty = t.errorUnionPayload(); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { return dg.renderType(w, Type.anyerror); } const name = dg.getTypedefName(t) orelse try dg.renderErrorUnionTypedef(t); return w.writeAll(name); }, .Struct => { const name = dg.getTypedefName(t) orelse if (t.isTuple() or t.tag() == .anon_struct) try dg.renderTupleTypedef(t) else try dg.renderStructTypedef(t); return w.writeAll(name); }, .Union => { const name = dg.getTypedefName(t) orelse try dg.renderUnionTypedef(t); return w.writeAll(name); }, .Enum => { // For enums, we simply use the integer tag type. var int_tag_ty_buffer: Type.Payload.Bits = undefined; const int_tag_ty = t.intTagType(&int_tag_ty_buffer); try dg.renderType(w, int_tag_ty); }, .Opaque => return w.writeAll("void"), .Frame, .AnyFrame, .Vector, => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{ @tagName(tag), }), .Fn => unreachable, // This is a function body, not a function pointer. .Null, .Undefined, .EnumLiteral, .ComptimeFloat, .ComptimeInt, .Type, => unreachable, // must be const or comptime .BoundFn => unreachable, // this type will be deleted from the language } } /// Renders a type in C typecast format. /// /// This is guaranteed to be valid in a typecast expression, but not /// necessarily in a variable/field declaration. /// /// There are three type formats in total that we support rendering: /// | Function | Example 1 (*u8) | Example 2 ([10]*u8) | /// |---------------------|-----------------|---------------------| /// | `renderTypecast` | "uint8_t *" | "uint8_t *[10]" | /// | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" | /// | `renderType` | "uint8_t *" | "zig_A_uint8_t_10" | /// fn renderTypecast( dg: *DeclGen, w: anytype, ty: Type, ) error{ OutOfMemory, AnalysisFail }!void { const name = CValue{ .bytes = "" }; return renderTypeAndName(dg, w, ty, name, .Mut, 0); } /// Renders a type and name in field declaration/definition format. /// /// There are three type formats in total that we support rendering: /// | Function | Example 1 (*u8) | Example 2 ([10]*u8) | /// |---------------------|-----------------|---------------------| /// | `renderTypecast` | "uint8_t *" | "uint8_t *[10]" | /// | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" | /// | `renderType` | "uint8_t *" | "zig_A_uint8_t_10" | /// fn renderTypeAndName( dg: *DeclGen, w: anytype, ty: Type, name: CValue, mutability: Mutability, alignment: u32, ) error{ OutOfMemory, AnalysisFail }!void { var suffix = std.ArrayList(u8).init(dg.gpa); defer suffix.deinit(); // Any top-level array types are rendered here as a suffix, which // avoids creating typedefs for every array type var render_ty = ty; while (render_ty.zigTypeTag() == .Array) { const sentinel_bit = @boolToInt(render_ty.sentinel() != null); const c_len = render_ty.arrayLen() + sentinel_bit; try suffix.writer().print("[{d}]", .{c_len}); render_ty = render_ty.elemType(); } if (alignment != 0) try w.print("ZIG_ALIGN({}) ", .{alignment}); try dg.renderType(w, render_ty); const const_prefix = switch (mutability) { .Const => "const ", .Mut => "", }; try w.print(" {s}", .{const_prefix}); try dg.writeCValue(w, name); try w.writeAll(suffix.items); } fn declIsGlobal(dg: *DeclGen, tv: TypedValue) bool { switch (tv.val.tag()) { .extern_fn => return true, .function => { const func = tv.val.castTag(.function).?.data; return dg.module.decl_exports.contains(func.owner_decl); }, .variable => { const variable = tv.val.castTag(.variable).?.data; return dg.module.decl_exports.contains(variable.owner_decl); }, else => unreachable, } } fn writeCValue(dg: DeclGen, w: anytype, c_value: CValue) !void { switch (c_value) { .none => unreachable, .local => |i| return w.print("t{d}", .{i}), .local_ref => |i| return w.print("&t{d}", .{i}), .constant => unreachable, .arg => |i| return w.print("a{d}", .{i}), .decl => |decl| return dg.renderDeclName(w, decl), .decl_ref => |decl| { try w.writeByte('&'); return dg.renderDeclName(w, decl); }, .undefined_ptr => { const target = dg.module.getTarget(); switch (target.cpu.arch.ptrBitWidth()) { 32 => try w.writeAll("(void *)0xaaaaaaaa"), 64 => try w.writeAll("(void *)0xaaaaaaaaaaaaaaaa"), else => unreachable, } }, .identifier => |ident| return w.print("{ }", .{fmtIdent(ident)}), .bytes => |bytes| return w.writeAll(bytes), } } fn writeCValueDeref(dg: DeclGen, w: anytype, c_value: CValue) !void { switch (c_value) { .none => unreachable, .local => |i| return w.print("(*t{d})", .{i}), .local_ref => |i| return w.print("t{d}", .{i}), .constant => unreachable, .arg => |i| return w.print("(*a{d})", .{i}), .decl => |decl| { try w.writeAll("(*"); try dg.renderDeclName(w, decl); return w.writeByte(')'); }, .decl_ref => |decl| return dg.renderDeclName(w, decl), .undefined_ptr => unreachable, .identifier => |ident| return w.print("(*{ })", .{fmtIdent(ident)}), .bytes => |bytes| { try w.writeAll("(*"); try w.writeAll(bytes); return w.writeByte(')'); }, } } fn renderDeclName(dg: DeclGen, writer: anytype, decl_index: Decl.Index) !void { const decl = dg.module.declPtr(decl_index); dg.module.markDeclAlive(decl); if (dg.module.decl_exports.get(decl_index)) |exports| { return writer.writeAll(exports[0].options.name); } else if (decl.val.tag() == .extern_fn) { return writer.writeAll(mem.sliceTo(decl.name, 0)); } else { const gpa = dg.module.gpa; const name = try decl.getFullyQualifiedName(dg.module); defer gpa.free(name); return writer.print("{ }", .{fmtIdent(name)}); } } }; pub fn genFunc(f: *Function) !void { const tracy = trace(@src()); defer tracy.end(); const o = &f.object; o.code_header = std.ArrayList(u8).init(f.object.dg.gpa); defer o.code_header.deinit(); const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl); const fwd_decl_writer = o.dg.fwd_decl.writer(); if (is_global) { try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); } try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); try fwd_decl_writer.writeAll(";\n"); try o.indent_writer.insertNewline(); try o.dg.renderFunctionSignature(o.writer(), is_global); try o.writer().writeByte(' '); // In case we need to use the header, populate it with a copy of the function // signature here. We anticipate a brace, newline, and space. try o.code_header.ensureUnusedCapacity(o.code.items.len + 3); o.code_header.appendSliceAssumeCapacity(o.code.items); o.code_header.appendSliceAssumeCapacity("{\n "); const empty_header_len = o.code_header.items.len; const main_body = f.air.getMainBody(); try genBody(f, main_body); try o.indent_writer.insertNewline(); // If we have a header to insert, append the body to the header // and then return the result, freeing the body. if (o.code_header.items.len > empty_header_len) { try o.code_header.appendSlice(o.code.items[empty_header_len..]); mem.swap(std.ArrayList(u8), &o.code, &o.code_header); } } pub fn genDecl(o: *Object) !void { const tracy = trace(@src()); defer tracy.end(); const tv: TypedValue = .{ .ty = o.dg.decl.ty, .val = o.dg.decl.val, }; if (tv.val.tag() == .extern_fn) { const writer = o.writer(); try writer.writeAll("ZIG_EXTERN_C "); try o.dg.renderFunctionSignature(writer, true); try writer.writeAll(";\n"); } else if (tv.val.castTag(.variable)) |var_payload| { const variable: *Module.Var = var_payload.data; const is_global = o.dg.declIsGlobal(tv) or variable.is_extern; const fwd_decl_writer = o.dg.fwd_decl.writer(); if (is_global) { try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); } if (variable.is_threadlocal) { try fwd_decl_writer.writeAll("zig_threadlocal "); } const decl_c_value: CValue = if (is_global) .{ .bytes = mem.span(o.dg.decl.name), } else .{ .decl = o.dg.decl_index, }; try o.dg.renderTypeAndName(fwd_decl_writer, o.dg.decl.ty, decl_c_value, .Mut, o.dg.decl.@"align"); try fwd_decl_writer.writeAll(";\n"); if (variable.init.isUndefDeep()) { return; } try o.indent_writer.insertNewline(); const w = o.writer(); try o.dg.renderTypeAndName(w, o.dg.decl.ty, decl_c_value, .Mut, o.dg.decl.@"align"); try w.writeAll(" = "); if (variable.init.tag() != .unreachable_value) { try o.dg.renderValue(w, tv.ty, variable.init, .Other); } try w.writeAll(";"); try o.indent_writer.insertNewline(); } else { const writer = o.writer(); try writer.writeAll("static "); // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 const decl_c_value: CValue = .{ .decl = o.dg.decl_index }; try o.dg.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut, o.dg.decl.@"align"); try writer.writeAll(" = "); try o.dg.renderValue(writer, tv.ty, tv.val, .Other); try writer.writeAll(";\n"); } } pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); const tv: TypedValue = .{ .ty = dg.decl.ty, .val = dg.decl.val, }; const writer = dg.fwd_decl.writer(); switch (tv.ty.zigTypeTag()) { .Fn => { const is_global = dg.declIsGlobal(tv); if (is_global) { try writer.writeAll("ZIG_EXTERN_C "); try dg.renderFunctionSignature(writer, is_global); try dg.fwd_decl.appendSlice(";\n"); } }, else => {}, } } fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void { const writer = f.object.writer(); if (body.len == 0) { try writer.writeAll("{}"); return; } try writer.writeAll("{\n"); f.object.indent_writer.pushIndent(); const air_tags = f.air.instructions.items(.tag); for (body) |inst| { const result_value = switch (air_tags[inst]) { // zig fmt: off .constant => unreachable, // excluded from function bodies .const_ty => unreachable, // excluded from function bodies .arg => airArg(f), .breakpoint => try airBreakpoint(f), .ret_addr => try airRetAddr(f, inst), .frame_addr => try airFrameAddress(f, inst), .unreach => try airUnreach(f), .fence => try airFence(f, inst), .ptr_add => try airPtrAddSub(f, inst, " + "), .ptr_sub => try airPtrAddSub(f, inst, " - "), // TODO use a different strategy for add, sub, mul, div // that communicates to the optimizer that wrapping is UB. .add => try airBinOp (f, inst, " + "), .sub => try airBinOp (f, inst, " - "), .mul => try airBinOp (f, inst, " * "), .div_float, .div_exact => try airBinOp( f, inst, " / "), .rem => try airBinOp( f, inst, " % "), .div_trunc => blk: { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs_ty = f.air.typeOf(bin_op.lhs); // For binary operations @TypeOf(lhs)==@TypeOf(rhs), // so we only check one. break :blk if (lhs_ty.isInt()) try airBinOp(f, inst, " / ") else try airBinOpBuiltinCall(f, inst, "div_trunc"); }, .div_floor => try airBinOpBuiltinCall(f, inst, "div_floor"), .mod => try airBinOpBuiltinCall(f, inst, "mod"), .addwrap => try airWrapOp(f, inst, " + ", "addw_"), .subwrap => try airWrapOp(f, inst, " - ", "subw_"), .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), .add_sat => try airSatOp(f, inst, "adds_"), .sub_sat => try airSatOp(f, inst, "subs_"), .mul_sat => try airSatOp(f, inst, "muls_"), .shl_sat => try airSatOp(f, inst, "shls_"), .neg => try airNeg(f, inst), .sqrt, .sin, .cos, .tan, .exp, .exp2, .log, .log2, .log10, .fabs, .floor, .ceil, .round, .trunc_float, => |tag| return f.fail("TODO: C backend: implement unary op for tag '{s}'", .{@tagName(tag)}), .mul_add => try airMulAdd(f, inst), .add_with_overflow => try airOverflow(f, inst, "addo_"), .sub_with_overflow => try airOverflow(f, inst, "subo_"), .mul_with_overflow => try airOverflow(f, inst, "mulo_"), .shl_with_overflow => try airOverflow(f, inst, "shlo_"), .min => try airMinMax(f, inst, "<"), .max => try airMinMax(f, inst, ">"), .slice => try airSlice(f, inst), .cmp_gt => try airBinOp(f, inst, " > "), .cmp_gte => try airBinOp(f, inst, " >= "), .cmp_lt => try airBinOp(f, inst, " < "), .cmp_lte => try airBinOp(f, inst, " <= "), .cmp_eq => try airEquality(f, inst, "((", "=="), .cmp_neq => try airEquality(f, inst, "!((", "!="), .cmp_vector => return f.fail("TODO: C backend: implement cmp_vector", .{}), .cmp_lt_errors_len => return f.fail("TODO: C backend: implement cmp_lt_errors_len", .{}), // bool_and and bool_or are non-short-circuit operations .bool_and => try airBinOp(f, inst, " & "), .bool_or => try airBinOp(f, inst, " | "), .bit_and => try airBinOp(f, inst, " & "), .bit_or => try airBinOp(f, inst, " | "), .xor => try airBinOp(f, inst, " ^ "), .shr, .shr_exact => try airBinOp(f, inst, " >> "), .shl, .shl_exact => try airBinOp(f, inst, " << "), .not => try airNot (f, inst), .optional_payload => try airOptionalPayload(f, inst), .optional_payload_ptr => try airOptionalPayloadPtr(f, inst), .optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst), .wrap_optional => try airWrapOptional(f, inst), .is_err => try airIsErr(f, inst, false, "!="), .is_non_err => try airIsErr(f, inst, false, "=="), .is_err_ptr => try airIsErr(f, inst, true, "!="), .is_non_err_ptr => try airIsErr(f, inst, true, "=="), .is_null => try airIsNull(f, inst, "==", ""), .is_non_null => try airIsNull(f, inst, "!=", ""), .is_null_ptr => try airIsNull(f, inst, "==", "[0]"), .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), .dbg_stmt => try airDbgStmt(f, inst), .intcast => try airIntCast(f, inst), .trunc => try airTrunc(f, inst), .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), .br => try airBr(f, inst), .switch_br => try airSwitchBr(f, inst), .struct_field_ptr => try airStructFieldPtr(f, inst), .array_to_slice => try airArrayToSlice(f, inst), .cmpxchg_weak => try airCmpxchg(f, inst, "weak"), .cmpxchg_strong => try airCmpxchg(f, inst, "strong"), .atomic_rmw => try airAtomicRmw(f, inst), .atomic_load => try airAtomicLoad(f, inst), .memset => try airMemset(f, inst), .memcpy => try airMemcpy(f, inst), .set_union_tag => try airSetUnionTag(f, inst), .get_union_tag => try airGetUnionTag(f, inst), .clz => try airBuiltinCall(f, inst, "clz"), .ctz => try airBuiltinCall(f, inst, "ctz"), .popcount => try airBuiltinCall(f, inst, "popcount"), .byte_swap => try airBuiltinCall(f, inst, "byte_swap"), .bit_reverse => try airBuiltinCall(f, inst, "bit_reverse"), .tag_name => try airTagName(f, inst), .error_name => try airErrorName(f, inst), .splat => try airSplat(f, inst), .select => try airSelect(f, inst), .shuffle => try airShuffle(f, inst), .reduce => try airReduce(f, inst), .aggregate_init => try airAggregateInit(f, inst), .union_init => try airUnionInit(f, inst), .prefetch => try airPrefetch(f, inst), .@"try" => try airTry(f, inst), .try_ptr => try airTryPtr(f, inst), .dbg_var_ptr, .dbg_var_val, => try airDbgVar(f, inst), .dbg_inline_begin, .dbg_inline_end, => try airDbgInline(f, inst), .dbg_block_begin, .dbg_block_end, => CValue{ .none = {} }, .call => try airCall(f, inst, .auto), .call_always_tail => try airCall(f, inst, .always_tail), .call_never_tail => try airCall(f, inst, .never_tail), .call_never_inline => try airCall(f, inst, .never_inline), .int_to_float, .float_to_int, .fptrunc, .fpext, => try airSimpleCast(f, inst), .ptrtoint => try airPtrToInt(f, inst), .atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)), .atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)), .atomic_store_release => try airAtomicStore(f, inst, toMemoryOrder(.Release)), .atomic_store_seq_cst => try airAtomicStore(f, inst, toMemoryOrder(.SeqCst)), .struct_field_ptr_index_0 => try airStructFieldPtrIndex(f, inst, 0), .struct_field_ptr_index_1 => try airStructFieldPtrIndex(f, inst, 1), .struct_field_ptr_index_2 => try airStructFieldPtrIndex(f, inst, 2), .struct_field_ptr_index_3 => try airStructFieldPtrIndex(f, inst, 3), .field_parent_ptr => try airFieldParentPtr(f, inst), .struct_field_val => try airStructFieldVal(f, inst), .slice_ptr => try airSliceField(f, inst, ".ptr;\n"), .slice_len => try airSliceField(f, inst, ".len;\n"), .ptr_slice_len_ptr => try airPtrSliceFieldPtr(f, inst, ".len;\n"), .ptr_slice_ptr_ptr => try airPtrSliceFieldPtr(f, inst, ".ptr;\n"), .ptr_elem_val => try airPtrElemVal(f, inst), .ptr_elem_ptr => try airPtrElemPtr(f, inst), .slice_elem_val => try airSliceElemVal(f, inst), .slice_elem_ptr => try airSliceElemPtr(f, inst), .array_elem_val => try airArrayElemVal(f, inst), .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst, ""), .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst, "&"), .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst), .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst), .wrap_errunion_payload => try airWrapErrUnionPay(f, inst), .wrap_errunion_err => try airWrapErrUnionErr(f, inst), .errunion_payload_ptr_set => try airErrUnionPayloadPtrSet(f, inst), .err_return_trace => try airErrReturnTrace(f, inst), .set_err_return_trace => try airSetErrReturnTrace(f, inst), .wasm_memory_size => try airWasmMemorySize(f, inst), .wasm_memory_grow => try airWasmMemoryGrow(f, inst), .add_optimized, .addwrap_optimized, .sub_optimized, .subwrap_optimized, .mul_optimized, .mulwrap_optimized, .div_float_optimized, .div_trunc_optimized, .div_floor_optimized, .div_exact_optimized, .rem_optimized, .mod_optimized, .neg_optimized, .cmp_lt_optimized, .cmp_lte_optimized, .cmp_eq_optimized, .cmp_gte_optimized, .cmp_gt_optimized, .cmp_neq_optimized, .cmp_vector_optimized, .reduce_optimized, .float_to_int_optimized, => return f.fail("TODO implement optimized float mode", .{}), .is_named_enum_value => return f.fail("TODO: C backend: implement is_named_enum_value", .{}), .error_set_has_value => return f.fail("TODO: C backend: implement error_set_has_value", .{}), // zig fmt: on }; switch (result_value) { .none => {}, else => try f.value_map.putNoClobber(Air.indexToRef(inst), result_value), } } f.object.indent_writer.popIndent(); try writer.writeAll("}"); } fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(suffix); return local; } fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); _ = writer; _ = operand; _ = suffix; return f.fail("TODO: C backend: airPtrSliceFieldPtr", .{}); } fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const ptr_ty = f.air.typeOf(bin_op.lhs); if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; const ptr = 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, ptr); try writer.writeByte('['); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const ptr_ty = f.air.typeOf(bin_op.lhs); const ptr = 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(" = &("); if (ptr_ty.ptrSize() == .One) { // It's a pointer to an array, so we need to de-reference. try f.writeCValueDeref(writer, ptr); } else { try f.writeCValue(writer, ptr); } try writer.writeAll(")["); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const slice_ty = f.air.typeOf(bin_op.lhs); if (!slice_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; const slice = 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, slice); try writer.writeAll(".ptr["); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airSliceElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const slice = 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, slice); try writer.writeAll(".ptr["); try f.writeCValue(writer, index); try writer.writeAll("];\n"); 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); const elem_type = inst_ty.elemType(); const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut; if (!elem_type.isFnOrHasRuntimeBitsIgnoreComptime()) { return CValue.undefined_ptr; } const target = f.object.dg.module.getTarget(); // First line: the variable used as data storage. const local = try f.allocAlignedLocal(elem_type, mutability, inst_ty.ptrAlignment(target)); try writer.writeAll(";\n"); 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; return .{ .arg = i }; } fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { const ty_op = f.air.instructions.items(.data)[inst].ty_op; const is_volatile = f.air.typeOf(ty_op.operand).isVolatilePtr(); if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const is_array = inst_ty.zigTypeTag() == .Array; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); // We need to separately initialize arrays with a memcpy so they must be mutable. const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); if (is_array) { // Insert a memcpy to initialize this array. The source operand is always a pointer // and thus we only need to know size/type information from the local type/dest. try writer.writeAll(";"); try f.object.indent_writer.insertNewline(); try writer.writeAll("memcpy("); try f.writeCValue(writer, local); try writer.writeAll(", "); try f.writeCValue(writer, operand); try writer.writeAll(", sizeof("); try f.writeCValue(writer, local); try writer.writeAll("));\n"); } else { try writer.writeAll(" = "); try f.writeCValueDeref(writer, operand); try writer.writeAll(";\n"); } return local; } fn airRet(f: *Function, inst: Air.Inst.Index) !CValue { const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const ret_ty = f.air.typeOf(un_op); if (ret_ty.isFnOrHasRuntimeBitsIgnoreComptime()) { const operand = try f.resolveInst(un_op); try writer.writeAll("return "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); } else if (ret_ty.isError()) { try writer.writeAll("return 0;"); } else { try writer.writeAll("return;\n"); } 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.isFnOrHasRuntimeBitsIgnoreComptime()) { const ptr = try f.resolveInst(un_op); try writer.writeAll("return *"); try f.writeCValue(writer, ptr); try writer.writeAll(";\n"); } else if (ret_ty.isError()) { try writer.writeAll("return 0;\n"); } else { try writer.writeAll("return;\n"); } return CValue.none; } fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const target = f.object.dg.module.getTarget(); const dest_int_info = inst_ty.intInfo(target); const dest_bits = dest_int_info.bits; try writer.writeAll(" = "); if (dest_bits >= 8 and std.math.isPowerOfTwo(dest_bits)) { try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } switch (dest_int_info.signedness) { .unsigned => { try f.writeCValue(writer, operand); const mask = (@as(u65, 1) << @intCast(u7, dest_bits)) - 1; try writer.print(" & {d}ULL;\n", .{mask}); return local; }, .signed => { const operand_ty = f.air.typeOf(ty_op.operand); const c_bits = toCIntBits(operand_ty.intInfo(target).bits) orelse return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); const shift_rhs = c_bits - dest_bits; try writer.print("(int{d}_t)((uint{d}_t)", .{ c_bits, c_bits }); try f.writeCValue(writer, operand); try writer.print(" << {d}) >> {d};\n", .{ shift_rhs, shift_rhs }); return local; }, } } fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { const is_debug_build = f.object.dg.module.optimizeMode() == .Debug; if (!is_debug_build) return CValue.none; const writer = f.object.writer(); try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); try writer.writeAll(", 0xaa, sizeof("); try f.writeCValueDeref(writer, dest_ptr); try writer.writeAll("));\n"); return CValue.none; } fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { // *a = b; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try f.resolveInst(bin_op.lhs); const src_val = try f.resolveInst(bin_op.rhs); const lhs_child_type = f.air.typeOf(bin_op.lhs).childType(); // TODO Sema should emit a different instruction when the store should // possibly do the safety 0xaa bytes for undefined. const src_val_is_undefined = if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false; if (src_val_is_undefined) return try airStoreUndefined(f, dest_ptr); const writer = f.object.writer(); if (lhs_child_type.zigTypeTag() == .Array) { // For this memcpy to safely work we need the rhs to have the same // underlying type as the lhs (i.e. they must both be arrays of the same underlying type). const rhs_type = f.air.typeOf(bin_op.rhs); assert(rhs_type.eql(lhs_child_type, f.object.dg.module)); // If the source is a constant, writeCValue will emit a brace initialization // so work around this by initializing into new local. // TODO this should be done by manually initializing elements of the dest array const array_src = if (src_val == .constant) blk: { const new_local = try f.allocLocal(rhs_type, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, src_val); try writer.writeAll(";"); try f.object.indent_writer.insertNewline(); break :blk new_local; } else src_val; try writer.writeAll("memcpy("); try f.writeCValue(writer, dest_ptr); try writer.writeAll(", "); try f.writeCValue(writer, array_src); try writer.writeAll(", sizeof("); try f.writeCValue(writer, array_src); try writer.writeAll("));\n"); } else { try f.writeCValueDeref(writer, dest_ptr); try writer.writeAll(" = "); try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); } return CValue.none; } fn airWrapOp( f: *Function, inst: Air.Inst.Index, str_op: [*:0]const u8, fn_op: [*:0]const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const inst_ty = f.air.typeOfIndex(inst); const target = f.object.dg.module.getTarget(); const int_info = inst_ty.intInfo(target); const bits = int_info.bits; // if it's an unsigned int with non-arbitrary bit size then we can just add if (int_info.signedness == .unsigned) { const ok_bits = switch (bits) { 8, 16, 32, 64, 128 => true, else => false, }; if (ok_bits or inst_ty.tag() != .int_unsigned) { return try airBinOp(f, inst, str_op); } } if (bits > 64) { return f.fail("TODO: C backend: airWrapOp for large integers", .{}); } var max_buf: [80]u8 = undefined; const max = intMax(inst_ty, target, &max_buf); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const w = f.object.writer(); const ret = try f.allocLocal(inst_ty, .Mut); try w.print(" = zig_{s}", .{fn_op}); switch (inst_ty.tag()) { .isize => try w.writeAll("isize"), .c_short => try w.writeAll("short"), .c_int => try w.writeAll("int"), .c_long => try w.writeAll("long"), .c_longlong => try w.writeAll("longlong"), else => { const prefix_byte: u8 = signAbbrev(int_info.signedness); for ([_]u8{ 8, 16, 32, 64 }) |nbits| { if (bits <= nbits) { try w.print("{c}{d}", .{ prefix_byte, nbits }); break; } } else { unreachable; } }, } try w.writeByte('('); try f.writeCValue(w, lhs); try w.writeAll(", "); try f.writeCValue(w, rhs); if (int_info.signedness == .signed) { var min_buf: [80]u8 = undefined; const min = intMin(inst_ty, target, &min_buf); try w.print(", {s}", .{min}); } try w.print(", {s});", .{max}); try f.object.indent_writer.insertNewline(); return ret; } fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const inst_ty = f.air.typeOfIndex(inst); const int_info = inst_ty.intInfo(f.object.dg.module.getTarget()); const bits = int_info.bits; switch (bits) { 8, 16, 32, 64, 128 => {}, else => return f.object.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}), } // if it's an unsigned int with non-arbitrary bit size then we can just add if (bits > 64) { return f.object.dg.fail("TODO: C backend: airSatOp for large integers", .{}); } var min_buf: [80]u8 = undefined; const min = switch (int_info.signedness) { .unsigned => "0", else => switch (inst_ty.tag()) { .c_short => "SHRT_MIN", .c_int => "INT_MIN", .c_long => "LONG_MIN", .c_longlong => "LLONG_MIN", .isize => "INTPTR_MIN", else => blk: { // compute the type minimum based on the bitcount (bits) const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1)); break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, }; }, }, }; var max_buf: [80]u8 = undefined; const max = switch (inst_ty.tag()) { .c_short => "SHRT_MAX", .c_ushort => "USHRT_MAX", .c_int => "INT_MAX", .c_uint => "UINT_MAX", .c_long => "LONG_MAX", .c_ulong => "ULONG_MAX", .c_longlong => "LLONG_MAX", .c_ulonglong => "ULLONG_MAX", .isize => "INTPTR_MAX", .usize => "UINTPTR_MAX", else => blk: { const pow_bits = switch (int_info.signedness) { .signed => bits - 1, .unsigned => bits, }; const val = std.math.pow(u65, 2, pow_bits) - 1; break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, }; }, }; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const w = f.object.writer(); const ret = try f.allocLocal(inst_ty, .Mut); try w.print(" = zig_{s}", .{fn_op}); switch (inst_ty.tag()) { .isize => try w.writeAll("isize"), .c_short => try w.writeAll("short"), .c_int => try w.writeAll("int"), .c_long => try w.writeAll("long"), .c_longlong => try w.writeAll("longlong"), else => { const prefix_byte: u8 = signAbbrev(int_info.signedness); for ([_]u8{ 8, 16, 32, 64 }) |nbits| { if (bits <= nbits) { try w.print("{c}{d}", .{ prefix_byte, nbits }); break; } } else { unreachable; } }, } try w.writeByte('('); try f.writeCValue(w, lhs); try w.writeAll(", "); try f.writeCValue(w, rhs); if (int_info.signedness == .signed) { try w.print(", {s}", .{min}); } try w.print(", {s});", .{max}); try f.object.indent_writer.insertNewline(); return ret; } fn airOverflow(f: *Function, inst: Air.Inst.Index, op_abbrev: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const inst_ty = f.air.typeOfIndex(inst); const scalar_ty = f.air.typeOf(bin_op.lhs).scalarType(); const target = f.object.dg.module.getTarget(); const int_info = scalar_ty.intInfo(target); const w = f.object.writer(); const c_bits = toCIntBits(int_info.bits) orelse return f.fail("TODO: C backend: implement integer arithmetic larger than 128 bits", .{}); var max_buf: [80]u8 = undefined; const max = intMax(scalar_ty, target, &max_buf); const ret = try f.allocLocal(inst_ty, .Mut); try w.writeAll(";"); try f.object.indent_writer.insertNewline(); try f.writeCValue(w, ret); switch (int_info.signedness) { .unsigned => { try w.print(".field_1 = zig_{s}u{d}(", .{ op_abbrev, c_bits, }); try f.writeCValue(w, lhs); try w.writeAll(", "); try f.writeCValue(w, rhs); try w.writeAll(", &"); try f.writeCValue(w, ret); try w.print(".field_0, {s}", .{max}); }, .signed => { var min_buf: [80]u8 = undefined; const min = intMin(scalar_ty, target, &min_buf); try w.print(".field_1 = zig_{s}i{d}(", .{ op_abbrev, c_bits, }); try f.writeCValue(w, lhs); try w.writeAll(", "); try f.writeCValue(w, rhs); try w.writeAll(", &"); try f.writeCValue(w, ret); try w.print(".field_0, {s}, {s}", .{ min, max }); }, } try w.writeAll(");"); try f.object.indent_writer.insertNewline(); return ret; } fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const op = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); if (inst_ty.zigTypeTag() == .Bool) try writer.writeAll("!") else try writer.writeAll("~"); try f.writeCValue(writer, op); try writer.writeAll(";\n"); return local; } fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, lhs); try writer.print("{s}", .{operator}); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } fn airEquality( f: *Function, inst: Air.Inst.Index, negate_prefix: []const u8, eq_op_str: []const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); const lhs_ty = f.air.typeOf(bin_op.lhs); if (lhs_ty.tag() == .optional) { // (A && B) || (C && (A == B)) // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload try writer.writeAll(negate_prefix); try f.writeCValue(writer, lhs); try writer.writeAll(".is_null && "); try f.writeCValue(writer, rhs); try writer.writeAll(".is_null) || ("); try f.writeCValue(writer, lhs); try writer.writeAll(".payload == "); try f.writeCValue(writer, rhs); try writer.writeAll(".payload && "); try f.writeCValue(writer, lhs); try writer.writeAll(".is_null == "); try f.writeCValue(writer, rhs); try writer.writeAll(".is_null));\n"); return local; } try f.writeCValue(writer, lhs); try writer.writeAll(eq_op_str); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const elem_ty = switch (inst_ty.ptrSize()) { .One => blk: { const array_ty = inst_ty.childType(); break :blk array_ty.childType(); }, else => inst_ty.childType(), }; // We must convert to and from integer types to prevent UB if the operation results in a NULL pointer, // or if LHS is NULL. The operation is only UB if the result is NULL and then dereferenced. try writer.writeAll(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeAll(")(((uintptr_t)"); try f.writeCValue(writer, lhs); try writer.print("){s}(", .{operator}); try f.writeCValue(writer, rhs); try writer.writeAll("*sizeof("); try f.renderTypecast(writer, elem_ty); try writer.print(")));\n", .{}); return local; } fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); // (lhs <> rhs) ? lhs : rhs try writer.writeAll(" = ("); try f.writeCValue(writer, lhs); try writer.print("{s}", .{operator}); try f.writeCValue(writer, rhs); try writer.writeAll(") ? "); try f.writeCValue(writer, lhs); try writer.writeAll(" : "); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const ptr = try f.resolveInst(bin_op.lhs); const len = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = {"); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, len); try writer.writeAll("};\n"); return local; } fn airCall( f: *Function, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier, ) !CValue { switch (modifier) { .auto => {}, .always_tail => return f.fail("TODO: C backend: call with always_tail attribute", .{}), .never_tail => return f.fail("TODO: C backend: call with never_tail attribute", .{}), .never_inline => return f.fail("TODO: C backend: call with never_inline attribute", .{}), else => unreachable, } const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Call, pl_op.payload); const args = @ptrCast([]const Air.Inst.Ref, f.air.extra[extra.end..][0..extra.data.args_len]); 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 writer = f.object.writer(); const result_local: CValue = r: { if (f.liveness.isUnused(inst)) { if (loweredFnRetTyHasBits(fn_ty)) { try writer.print("(void)", .{}); } break :r .none; } else { const local = try f.allocLocal(fn_ty.fnReturnType(), .Const); try writer.writeAll(" = "); break :r local; } }; 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.owner_decl, .function => callee_val.castTag(.function).?.data.owner_decl, .decl_ref => callee_val.castTag(.decl_ref).?.data, else => break :known, }; }; try f.object.dg.renderDeclName(writer, fn_decl); break :callee; } // Fall back to function pointer call. const callee = try f.resolveInst(pl_op.operand); try f.writeCValue(writer, callee); } try writer.writeAll("("); var args_written: usize = 0; for (args) |arg| { const ty = f.air.typeOf(arg); if (!ty.hasRuntimeBitsIgnoreComptime()) continue; if (args_written != 0) { try writer.writeAll(", "); } if (f.air.value(arg)) |val| { try f.object.dg.renderValue(writer, f.air.typeOf(arg), val, .FunctionArgument); } else { const val = try f.resolveInst(arg); try f.writeCValue(writer, val); } args_written += 1; } try writer.writeAll(");\n"); return result_local; } fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue { const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt; const writer = f.object.writer(); // TODO re-evaluate whether to emit these or not. If we naively emit // these directives, the output file will report bogus line numbers because // every newline after the #line directive adds one to the line. // We also don't print the filename yet, so the output is strictly unhelpful. // If we wanted to go this route, we would need to go all the way and not output // newlines until the next dbg_stmt occurs. // Perhaps an additional compilation option is in order? //try writer.print("#line {d}\n", .{dbg_stmt.line + 1}); try writer.print("/* file:{d}:{d} */\n", .{ dbg_stmt.line + 1, dbg_stmt.column + 1 }); return CValue.none; } fn airDbgInline(f: *Function, inst: Air.Inst.Index) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const writer = f.object.writer(); const function = f.air.values[ty_pl.payload].castTag(.function).?.data; const mod = f.object.dg.module; try writer.print("/* dbg func:{s} */\n", .{mod.declPtr(function.owner_decl).name}); return CValue.none; } fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const name = f.air.nullTerminatedString(pl_op.payload); const operand = try f.resolveInst(pl_op.operand); _ = operand; const writer = f.object.writer(); try writer.print("/* var:{s} */\n", .{name}); return CValue.none; } fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.Block, ty_pl.payload); const body = f.air.extra[extra.end..][0..extra.data.body_len]; const block_id: usize = f.next_block_index; f.next_block_index += 1; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const result = if (inst_ty.tag() != .void and !f.liveness.isUnused(inst)) blk: { // allocate a location for the result const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(";\n"); break :blk local; } else CValue{ .none = {} }; try f.blocks.putNoClobber(f.object.dg.gpa, inst, .{ .block_id = block_id, .result = result, }); try genBody(f, body); try f.object.indent_writer.insertNewline(); // label must be followed by an expression, add an empty one. try writer.print("zig_block_{d}:;\n", .{block_id}); return result; } fn airTry(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const err_union = try f.resolveInst(pl_op.operand); const extra = f.air.extraData(Air.Try, pl_op.payload); const body = f.air.extra[extra.end..][0..extra.data.body_len]; const err_union_ty = f.air.typeOf(pl_op.operand); const result_ty = f.air.typeOfIndex(inst); return lowerTry(f, err_union, body, err_union_ty, false, result_ty); } fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.TryPtr, ty_pl.payload); const err_union_ptr = try f.resolveInst(extra.data.ptr); const body = f.air.extra[extra.end..][0..extra.data.body_len]; const err_union_ty = f.air.typeOf(extra.data.ptr).childType(); const result_ty = f.air.typeOfIndex(inst); return lowerTry(f, err_union_ptr, body, err_union_ty, true, result_ty); } fn lowerTry( f: *Function, err_union: CValue, body: []const Air.Inst.Index, err_union_ty: Type, operand_is_ptr: bool, result_ty: Type, ) !CValue { const writer = f.object.writer(); const payload_ty = err_union_ty.errorUnionPayload(); const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(); if (!err_union_ty.errorUnionSet().errorSetIsEmpty()) { err: { if (!payload_has_bits) { if (operand_is_ptr) { try writer.writeAll("if(*"); } else { try writer.writeAll("if("); } try f.writeCValue(writer, err_union); try writer.writeAll(")"); break :err; } if (operand_is_ptr or isByRef(err_union_ty)) { try writer.writeAll("if("); try f.writeCValue(writer, err_union); try writer.writeAll("->error)"); break :err; } try writer.writeAll("if("); try f.writeCValue(writer, err_union); try writer.writeAll(".error)"); } try genBody(f, body); try f.object.indent_writer.insertNewline(); } if (!payload_has_bits) { if (!operand_is_ptr) { return CValue.none; } else { return err_union; } } const local = try f.allocLocal(result_ty, .Const); if (operand_is_ptr or isByRef(payload_ty)) { try writer.writeAll(" = &"); try f.writeCValue(writer, err_union); try writer.writeAll("->payload;\n"); } else { try writer.writeAll(" = "); try f.writeCValue(writer, err_union); try writer.writeAll(".payload;\n"); } return local; } fn airBr(f: *Function, inst: Air.Inst.Index) !CValue { const branch = f.air.instructions.items(.data)[inst].br; const block = f.blocks.get(branch.block_inst).?; const result = block.result; const writer = f.object.writer(); // If result is .none then the value of the block is unused. if (result != .none) { const operand = try f.resolveInst(branch.operand); try f.writeCValue(writer, result); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); } try f.object.writer().print("goto zig_block_{d};\n", .{block.block_id}); return CValue.none; } fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.isPtrAtRuntime() and f.air.typeOf(ty_op.operand).isPtrAtRuntime()) { const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(";\n"); try writer.writeAll("memcpy(&"); try f.writeCValue(writer, local); try writer.writeAll(", &"); try f.writeCValue(writer, operand); try writer.writeAll(", sizeof("); try f.writeCValue(writer, local); try writer.writeAll("));\n"); return local; } fn airBreakpoint(f: *Function) !CValue { try f.object.writer().writeAll("zig_breakpoint();\n"); return CValue.none; } fn airRetAddr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const local = try f.allocLocal(Type.usize, .Const); try f.object.writer().writeAll(" = zig_return_address();\n"); return local; } fn airFrameAddress(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const local = try f.allocLocal(Type.usize, .Const); try f.object.writer().writeAll(" = zig_frame_address();\n"); return local; } fn airFence(f: *Function, inst: Air.Inst.Index) !CValue { const atomic_order = f.air.instructions.items(.data)[inst].fence; const writer = f.object.writer(); try writer.writeAll("zig_fence("); try writeMemoryOrder(writer, atomic_order); try writer.writeAll(");\n"); return CValue.none; } fn airUnreach(f: *Function) !CValue { try f.object.writer().writeAll("zig_unreachable();\n"); return CValue.none; } fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const loop = f.air.extraData(Air.Block, ty_pl.payload); const body = f.air.extra[loop.end..][0..loop.data.body_len]; try f.object.writer().writeAll("while (true) "); try genBody(f, body); try f.object.indent_writer.insertNewline(); return CValue.none; } fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const cond = try f.resolveInst(pl_op.operand); const extra = f.air.extraData(Air.CondBr, pl_op.payload); const then_body = f.air.extra[extra.end..][0..extra.data.then_body_len]; const else_body = f.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; const writer = f.object.writer(); try writer.writeAll("if ("); try f.writeCValue(writer, cond); try writer.writeAll(") "); try genBody(f, then_body); try writer.writeAll(" else "); try genBody(f, else_body); try f.object.indent_writer.insertNewline(); return CValue.none; } fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const condition = try f.resolveInst(pl_op.operand); const condition_ty = f.air.typeOf(pl_op.operand); const switch_br = f.air.extraData(Air.SwitchBr, pl_op.payload); const writer = f.object.writer(); try writer.writeAll("switch ("); try f.writeCValue(writer, condition); try writer.writeAll(") {"); f.object.indent_writer.pushIndent(); var extra_index: usize = switch_br.end; var case_i: u32 = 0; while (case_i < switch_br.data.cases_len) : (case_i += 1) { const case = f.air.extraData(Air.SwitchBr.Case, extra_index); const items = @ptrCast([]const Air.Inst.Ref, f.air.extra[case.end..][0..case.data.items_len]); const case_body = f.air.extra[case.end + items.len ..][0..case.data.body_len]; extra_index = case.end + case.data.items_len + case_body.len; for (items) |item| { try f.object.indent_writer.insertNewline(); try writer.writeAll("case "); try f.object.dg.renderValue(writer, condition_ty, f.air.value(item).?, .Other); try writer.writeAll(": "); } // The case body must be noreturn so we don't need to insert a break. try genBody(f, case_body); } const else_body = f.air.extra[extra_index..][0..switch_br.data.else_body_len]; try f.object.indent_writer.insertNewline(); try writer.writeAll("default: "); try genBody(f, else_body); try f.object.indent_writer.insertNewline(); f.object.indent_writer.popIndent(); try writer.writeAll("}\n"); return CValue.none; } fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.Asm, ty_pl.payload); const is_volatile = @truncate(u1, extra.data.flags >> 31) != 0; const clobbers_len = @truncate(u31, extra.data.flags); var extra_i: usize = extra.end; const outputs = @ptrCast([]const Air.Inst.Ref, f.air.extra[extra_i..][0..extra.data.outputs_len]); extra_i += outputs.len; const inputs = @ptrCast([]const Air.Inst.Ref, f.air.extra[extra_i..][0..extra.data.inputs_len]); extra_i += inputs.len; if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; if (outputs.len > 1) { return f.fail("TODO implement codegen for asm with more than 1 output", .{}); } const output_constraint: ?[]const u8 = for (outputs) |output| { if (output != .none) { return f.fail("TODO implement codegen for non-expr asm", .{}); } const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); // This equation accounts for the fact that even if we have exactly 4 bytes // for the string, we still use the next u32 for the null terminator. extra_i += (constraint.len + name.len + (2 + 3)) / 4; break constraint; } else null; const writer = f.object.writer(); try writer.writeAll("{\n"); const inputs_extra_begin = extra_i; for (inputs) |input, i| { const input_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(input_bytes, 0); const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0); // This equation accounts for the fact that even if we have exactly 4 bytes // for the string, we still use the next u32 for the null terminator. extra_i += (constraint.len + name.len + (2 + 3)) / 4; if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; const arg_c_value = try f.resolveInst(input); try writer.writeAll("register "); try f.renderType(writer, f.air.typeOf(input)); try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); try f.writeCValue(writer, arg_c_value); try writer.writeAll(";\n"); } else { try writer.writeAll("register "); try f.renderType(writer, f.air.typeOf(input)); try writer.print(" input_{d} = ", .{i}); try f.writeCValue(writer, try f.resolveInst(input)); try writer.writeAll(";\n"); } } { var clobber_i: u32 = 0; while (clobber_i < clobbers_len) : (clobber_i += 1) { const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); // This equation accounts for the fact that even if we have exactly 4 bytes // for the string, we still use the next u32 for the null terminator. extra_i += clobber.len / 4 + 1; // TODO honor these } } const asm_source = std.mem.sliceAsBytes(f.air.extra[extra_i..])[0..extra.data.source_len]; const volatile_string: []const u8 = if (is_volatile) "volatile " else ""; try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, asm_source }); if (output_constraint) |_| { return f.fail("TODO: CBE inline asm output", .{}); } if (inputs.len > 0) { if (output_constraint == null) { try writer.writeAll(" :"); } try writer.writeAll(": "); extra_i = inputs_extra_begin; for (inputs) |_, index| { const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(f.air.extra[extra_i..]), 0); // This equation accounts for the fact that even if we have exactly 4 bytes // for the string, we still use the next u32 for the null terminator. extra_i += constraint.len / 4 + 1; if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; if (index > 0) { try writer.writeAll(", "); } try writer.print("\"r\"({s}_constant)", .{reg}); } else { if (index > 0) { try writer.writeAll(", "); } try writer.print("\"r\"(input_{d})", .{index}); } } } try writer.writeAll(");\n"); try writer.writeAll("}\n"); if (f.liveness.isUnused(inst)) return CValue.none; return f.fail("TODO: C backend: inline asm expression result used", .{}); } fn airIsNull( f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8, deref_suffix: [*:0]const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(Type.initTag(.bool), .Const); try writer.writeAll(" = ("); try f.writeCValue(writer, operand); const ty = f.air.typeOf(un_op); var opt_buf: Type.Payload.ElemType = undefined; const payload_ty = if (deref_suffix[0] != 0) ty.childType().optionalChild(&opt_buf) else ty.optionalChild(&opt_buf); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { try writer.print("){s} {s} true;\n", .{ deref_suffix, operator }); } else if (ty.isPtrLikeOptional()) { // operand is a regular pointer, test `operand !=/== NULL` try writer.print("){s} {s} NULL;\n", .{ deref_suffix, operator }); } else if (payload_ty.zigTypeTag() == .ErrorSet) { try writer.print("){s} {s} 0;\n", .{ deref_suffix, operator }); } else { try writer.print("){s}.is_null {s} true;\n", .{ deref_suffix, operator }); } return local; } fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const opt_ty = f.air.typeOf(ty_op.operand); var buf: Type.Payload.ElemType = undefined; const payload_ty = opt_ty.optionalChild(&buf); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { return CValue.none; } if (opt_ty.optionalReprIsPayload()) { return operand; } const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); try f.writeCValue(writer, operand); try writer.writeAll(").payload;\n"); return local; } fn airOptionalPayloadPtr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const ptr_ty = f.air.typeOf(ty_op.operand); const opt_ty = ptr_ty.childType(); var buf: Type.Payload.ElemType = undefined; const payload_ty = opt_ty.optionalChild(&buf); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { return operand; } if (opt_ty.optionalReprIsPayload()) { // the operand is just a regular pointer, no need to do anything special. // *?*T -> **T and ?*T -> *T are **T -> **T and *T -> *T in C return operand; } const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = &("); try f.writeCValue(writer, operand); try writer.writeAll(")->payload;\n"); return local; } fn airOptionalPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue { const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const operand_ty = f.air.typeOf(ty_op.operand); const opt_ty = operand_ty.elemType(); if (opt_ty.optionalReprIsPayload()) { // The payload and the optional are the same value. // Setting to non-null will be done when the payload is set. return operand; } try f.writeCValueDeref(writer, operand); try writer.writeAll(".is_null = false;\n"); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = &"); try f.writeCValueDeref(writer, operand); try writer.writeAll(".payload;\n"); return local; } fn airStructFieldPtr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) // TODO this @as is needed because of a stage1 bug return @as(CValue, CValue.none); const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.StructField, ty_pl.payload).data; const struct_ptr = try f.resolveInst(extra.struct_operand); const struct_ptr_ty = f.air.typeOf(extra.struct_operand); return structFieldPtr(f, inst, struct_ptr_ty, struct_ptr, extra.field_index); } fn airStructFieldPtrIndex(f: *Function, inst: Air.Inst.Index, index: u8) !CValue { if (f.liveness.isUnused(inst)) // TODO this @as is needed because of a stage1 bug return @as(CValue, CValue.none); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const struct_ptr = try f.resolveInst(ty_op.operand); const struct_ptr_ty = f.air.typeOf(ty_op.operand); return structFieldPtr(f, inst, struct_ptr_ty, struct_ptr, index); } fn airFieldParentPtr(f: *Function, inst: Air.Inst.Index) !CValue { _ = inst; return f.fail("TODO: C backend: implement airFieldParentPtr", .{}); } fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue { const writer = f.object.writer(); const struct_ty = struct_ptr_ty.elemType(); var field_name: []const u8 = undefined; var field_val_ty: Type = undefined; var buf = std.ArrayList(u8).init(f.object.dg.gpa); defer buf.deinit(); switch (struct_ty.tag()) { .@"struct" => { const fields = struct_ty.structFields(); field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; }, .@"union", .union_safety_tagged, .union_tagged => { const fields = struct_ty.unionFields(); field_name = fields.keys()[index]; field_val_ty = fields.values()[index].ty; }, .tuple, .anon_struct => { const tuple = struct_ty.tupleFields(); if (tuple.values[index].tag() != .unreachable_value) return CValue.none; try buf.writer().print("field_{d}", .{index}); field_name = buf.items; field_val_ty = tuple.types[index]; }, else => unreachable, } const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else ""; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); if (field_val_ty.hasRuntimeBitsIgnoreComptime()) { try writer.writeAll(" = &"); try f.writeCValueDeref(writer, struct_ptr); try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) }); } else { try writer.writeAll(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeByte(')'); try f.writeCValue(writer, struct_ptr); try writer.writeAll(";\n"); } return local; } fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.StructField, ty_pl.payload).data; const writer = f.object.writer(); const struct_byval = try f.resolveInst(extra.struct_operand); const struct_ty = f.air.typeOf(extra.struct_operand); var buf = std.ArrayList(u8).init(f.object.dg.gpa); defer buf.deinit(); const field_name = switch (struct_ty.tag()) { .@"struct" => struct_ty.structFields().keys()[extra.field_index], .@"union", .union_safety_tagged, .union_tagged => struct_ty.unionFields().keys()[extra.field_index], .tuple, .anon_struct => blk: { const tuple = struct_ty.tupleFields(); if (tuple.values[extra.field_index].tag() != .unreachable_value) return CValue.none; try buf.writer().print("field_{d}", .{extra.field_index}); break :blk buf.items; }, else => unreachable, }; const payload = if (struct_ty.tag() == .union_tagged or struct_ty.tag() == .union_safety_tagged) "payload." else ""; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, struct_byval); try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) }); return local; } /// *(E!T) -> E /// Note that the result is never a pointer. fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const inst_ty = f.air.typeOfIndex(inst); const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const operand_ty = f.air.typeOf(ty_op.operand); if (operand_ty.zigTypeTag() == .Pointer) { const err_union_ty = operand_ty.childType(); if (err_union_ty.errorUnionSet().errorSetIsEmpty()) { return CValue{ .bytes = "0" }; } if (!err_union_ty.errorUnionPayload().hasRuntimeBits()) { return operand; } const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = *"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } if (operand_ty.errorUnionSet().errorSetIsEmpty()) { return CValue{ .bytes = "0" }; } if (!operand_ty.errorUnionPayload().hasRuntimeBits()) { return operand; } const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); if (operand_ty.zigTypeTag() == .Pointer) { try f.writeCValueDeref(writer, operand); } else { try f.writeCValue(writer, operand); } try writer.writeAll(".error;\n"); return local; } fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, maybe_addrof: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const operand_ty = f.air.typeOf(ty_op.operand); const operand_is_ptr = operand_ty.zigTypeTag() == .Pointer; const error_union_ty = if (operand_is_ptr) operand_ty.childType() else operand_ty; if (!error_union_ty.errorUnionPayload().hasRuntimeBits()) { return CValue.none; } const inst_ty = f.air.typeOfIndex(inst); const maybe_deref = if (operand_is_ptr) "->" else "."; const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{maybe_addrof}); try f.writeCValue(writer, operand); try writer.print("){s}payload;\n", .{maybe_deref}); return local; } fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.optionalReprIsPayload()) { return operand; } // .wrap_optional is used to convert non-optionals into optionals so it can never be null. const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .is_null = false, .payload ="); try f.writeCValue(writer, operand); try writer.writeAll("};\n"); return local; } fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const writer = f.object.writer(); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const err_un_ty = f.air.typeOfIndex(inst); const payload_ty = err_un_ty.errorUnionPayload(); if (!payload_ty.hasRuntimeBits()) { return operand; } const local = try f.allocLocal(err_un_ty, .Const); try writer.writeAll(" = { .error = "); try f.writeCValue(writer, operand); try writer.writeAll(" };\n"); return local; } fn airErrUnionPayloadPtrSet(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const error_union_ty = f.air.typeOf(ty_op.operand).childType(); const error_ty = error_union_ty.errorUnionSet(); const payload_ty = error_union_ty.errorUnionPayload(); // First, set the non-error value. if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { try f.writeCValueDeref(writer, operand); try writer.writeAll(" = "); try f.object.dg.renderValue(writer, error_ty, Value.zero, .Other); try writer.writeAll(";\n "); return operand; } try f.writeCValueDeref(writer, operand); try writer.writeAll(".error = "); try f.object.dg.renderValue(writer, error_ty, Value.zero, .Other); try writer.writeAll(";\n"); // Then return the payload pointer (only if it is used) if (f.liveness.isUnused(inst)) return CValue.none; const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = &("); try f.writeCValueDeref(writer, operand); try writer.writeAll(").payload;\n"); return local; } fn airErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; return f.fail("TODO: C backend: implement airErrReturnTrace", .{}); } fn airSetErrReturnTrace(f: *Function, inst: Air.Inst.Index) !CValue { _ = inst; return f.fail("TODO: C backend: implement airSetErrReturnTrace", .{}); } fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .error = 0, .payload = "); try f.writeCValue(writer, operand); try writer.writeAll(" };\n"); return local; } fn airIsErr( f: *Function, inst: Air.Inst.Index, is_ptr: bool, op_str: [*:0]const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const operand = try f.resolveInst(un_op); const operand_ty = f.air.typeOf(un_op); const local = try f.allocLocal(Type.initTag(.bool), .Const); const err_union_ty = if (is_ptr) operand_ty.childType() else operand_ty; const payload_ty = err_union_ty.errorUnionPayload(); const error_ty = err_union_ty.errorUnionSet(); try writer.writeAll(" = "); if (error_ty.errorSetIsEmpty()) { try writer.print("0 {s} 0;\n", .{op_str}); } else { if (is_ptr) { try f.writeCValueDeref(writer, operand); } else { try f.writeCValue(writer, operand); } if (payload_ty.hasRuntimeBits()) { try writer.writeAll(".error"); } try writer.print(" {s} 0;\n", .{op_str}); } return local; } fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const array_len = f.air.typeOf(ty_op.operand).elemType().arrayLen(); try writer.writeAll(" = { .ptr = "); if (operand == .undefined_ptr) { // Unfortunately, C does not support any equivalent to // &(*(void *)p)[0], although LLVM does via GetElementPtr try f.writeCValue(writer, CValue.undefined_ptr); } else { try writer.writeAll("&("); try f.writeCValueDeref(writer, operand); try writer.writeAll(")[0]"); } try writer.print(", .len = {d} }};\n", .{array_len}); return local; } /// Emits a local variable with the result type and initializes it /// with the operand. fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airPtrToInt(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const operand = try f.resolveInst(un_op); try writer.writeAll(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airBuiltinCall(f: *Function, inst: Air.Inst.Index, fn_name: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const operand = f.air.instructions.items(.data)[inst].ty_op.operand; const operand_ty = f.air.typeOf(operand); const target = f.object.dg.module.getTarget(); const writer = f.object.writer(); const int_info = operand_ty.intInfo(target); const c_bits = toCIntBits(int_info.bits) orelse return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); try writer.print(" = zig_{s}_", .{fn_name}); try writer.print("{c}{d}(", .{ signAbbrev(int_info.signedness), c_bits }); try f.writeCValue(writer, try f.resolveInst(operand)); try writer.print(", {d});\n", .{int_info.bits}); return local; } fn airBinOpBuiltinCall(f: *Function, inst: Air.Inst.Index, fn_name: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs_ty = f.air.typeOf(bin_op.lhs); const target = f.object.dg.module.getTarget(); const writer = f.object.writer(); // For binary operations @TypeOf(lhs)==@TypeOf(rhs), so we only check one. if (lhs_ty.isInt()) { const int_info = lhs_ty.intInfo(target); const c_bits = toCIntBits(int_info.bits) orelse return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); try writer.print(" = zig_{s}_{c}{d}", .{ fn_name, signAbbrev(int_info.signedness), c_bits }); } else if (lhs_ty.isRuntimeFloat()) { const c_bits = lhs_ty.floatBits(target); try writer.print(" = zig_{s}_f{d}", .{ fn_name, c_bits }); } else { return f.fail("TODO: C backend: implement airBinOpBuiltinCall for type {s}", .{@tagName(lhs_ty.tag())}); } try writer.writeByte('('); try f.writeCValue(writer, try f.resolveInst(bin_op.lhs)); try writer.writeAll(", "); try f.writeCValue(writer, try f.resolveInst(bin_op.rhs)); try writer.writeAll(");\n"); return local; } fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data; const inst_ty = f.air.typeOfIndex(inst); const ptr = try f.resolveInst(extra.ptr); const expected_value = try f.resolveInst(extra.expected_value); const new_value = try f.resolveInst(extra.new_value); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.print(" = zig_cmpxchg_{s}(", .{flavor}); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, expected_value); try writer.writeAll(", "); try f.writeCValue(writer, new_value); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.successOrder()); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.failureOrder()); try writer.writeAll(");\n"); return local; } fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.AtomicRmw, pl_op.payload).data; const inst_ty = f.air.typeOfIndex(inst); const ptr = try f.resolveInst(pl_op.operand); const operand = try f.resolveInst(extra.operand); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.print(" = zig_atomicrmw_{s}(", .{toAtomicRmwSuffix(extra.op())}); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, operand); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.ordering()); try writer.writeAll(");\n"); return local; } fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue { const atomic_load = f.air.instructions.items(.data)[inst].atomic_load; const ptr = try f.resolveInst(atomic_load.ptr); const ptr_ty = f.air.typeOf(atomic_load.ptr); if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_atomic_load("); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try writeMemoryOrder(writer, atomic_load.order); try writer.writeAll(");\n"); return local; } fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const ptr = try f.resolveInst(bin_op.lhs); const element = try f.resolveInst(bin_op.rhs); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_atomic_store("); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, element); try writer.print(", {s});\n", .{order}); return local; } fn airMemset(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Bin, pl_op.payload).data; const dest_ptr = try f.resolveInst(pl_op.operand); const value = try f.resolveInst(extra.lhs); const len = try f.resolveInst(extra.rhs); const writer = f.object.writer(); try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); try writer.writeAll(", "); try f.writeCValue(writer, value); try writer.writeAll(", "); try f.writeCValue(writer, len); try writer.writeAll(");\n"); return CValue.none; } fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Bin, pl_op.payload).data; const dest_ptr = try f.resolveInst(pl_op.operand); const src_ptr = try f.resolveInst(extra.lhs); const len = try f.resolveInst(extra.rhs); const writer = f.object.writer(); try writer.writeAll("memcpy("); try f.writeCValue(writer, dest_ptr); try writer.writeAll(", "); try f.writeCValue(writer, src_ptr); try writer.writeAll(", "); try f.writeCValue(writer, len); try writer.writeAll(");\n"); return CValue.none; } fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const union_ptr = try f.resolveInst(bin_op.lhs); const new_tag = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const union_ty = f.air.typeOf(bin_op.lhs).childType(); const target = f.object.dg.module.getTarget(); const layout = union_ty.unionGetLayout(target); if (layout.tag_size == 0) return CValue.none; try f.writeCValue(writer, union_ptr); try writer.writeAll("->tag = "); try f.writeCValue(writer, new_tag); try writer.writeAll(";\n"); return CValue.none; } fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const un_ty = f.air.typeOf(ty_op.operand); const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const target = f.object.dg.module.getTarget(); const layout = un_ty.unionGetLayout(target); if (layout.tag_size == 0) return CValue.none; try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(".tag;\n"); return local; } fn airTagName(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = operand; _ = local; return f.fail("TODO: C backend: implement airTagName", .{}); //try writer.writeAll(";\n"); //return local; } fn airErrorName(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = operand; _ = local; return f.fail("TODO: C backend: implement airErrorName", .{}); } fn airSplat(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = operand; _ = local; return f.fail("TODO: C backend: implement airSplat", .{}); } fn airSelect(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = local; _ = ty_pl; return f.fail("TODO: C backend: implement airSelect", .{}); } fn airShuffle(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = operand; _ = local; return f.fail("TODO: C backend: implement airShuffle", .{}); } fn airReduce(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const reduce = f.air.instructions.items(.data)[inst].reduce; const operand = try f.resolveInst(reduce.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = operand; _ = local; return f.fail("TODO: C backend: implement airReduce", .{}); } fn airAggregateInit(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const vector_ty = f.air.getRefType(ty_pl.ty); const len = vector_ty.vectorLen(); const elements = @ptrCast([]const Air.Inst.Ref, f.air.extra[ty_pl.payload..][0..len]); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = {"); switch (vector_ty.zigTypeTag()) { .Struct => { const tuple = vector_ty.tupleFields(); var i: usize = 0; for (elements) |elem, elem_index| { if (tuple.values[elem_index].tag() != .unreachable_value) continue; const value = try f.resolveInst(elem); if (i != 0) try writer.writeAll(", "); try f.writeCValue(writer, value); i += 1; } }, else => |tag| return f.fail("TODO: C backend: implement airAggregateInit for type {s}", .{@tagName(tag)}), } try writer.writeAll("};\n"); return local; } fn airUnionInit(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); _ = local; _ = ty_pl; return f.fail("TODO: C backend: implement airUnionInit", .{}); } fn airPrefetch(f: *Function, inst: Air.Inst.Index) !CValue { const prefetch = f.air.instructions.items(.data)[inst].prefetch; switch (prefetch.cache) { .data => {}, // The available prefetch intrinsics do not accept a cache argument; only // address, rw, and locality. So unless the cache is data, we do not lower // this instruction. .instruction => return CValue.none, } const ptr = try f.resolveInst(prefetch.ptr); const writer = f.object.writer(); try writer.writeAll("zig_prefetch("); try f.writeCValue(writer, ptr); try writer.print(", {d}, {d});\n", .{ @enumToInt(prefetch.rw), prefetch.locality, }); return CValue.none; } fn airWasmMemorySize(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const pl_op = f.air.instructions.items(.data)[inst].pl_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try writer.print("zig_wasm_memory_size({d});\n", .{pl_op.payload}); return local; } fn airWasmMemoryGrow(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(pl_op.operand); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try writer.print("zig_wasm_memory_grow({d}, ", .{pl_op.payload}); try f.writeCValue(writer, operand); try writer.writeAll(");\n"); return local; } fn airNeg(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll("-"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airMulAdd(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Bin, pl_op.payload).data; const inst_ty = f.air.typeOfIndex(inst); const mulend1 = try f.resolveInst(extra.lhs); const mulend2 = try f.resolveInst(extra.rhs); const addend = try f.resolveInst(pl_op.operand); const writer = f.object.writer(); const target = f.object.dg.module.getTarget(); const fn_name = switch (inst_ty.floatBits(target)) { 16, 32 => "fmaf", 64 => "fma", 80 => if (CType.longdouble.sizeInBits(target) == 80) "fmal" else "__fmax", 128 => if (CType.longdouble.sizeInBits(target) == 128) "fmal" else "fmaq", else => unreachable, }; const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try writer.print("{s}(", .{fn_name}); try f.writeCValue(writer, mulend1); try writer.writeAll(", "); try f.writeCValue(writer, mulend2); try writer.writeAll(", "); try f.writeCValue(writer, addend); try writer.writeAll(");\n"); return local; } fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", .Monotonic => "memory_order_consume", .Acquire => "memory_order_acquire", .Release => "memory_order_release", .AcqRel => "memory_order_acq_rel", .SeqCst => "memory_order_seq_cst", }; } fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { return w.writeAll(toMemoryOrder(order)); } fn toAtomicRmwSuffix(order: std.builtin.AtomicRmwOp) []const u8 { return switch (order) { .Xchg => "xchg", .Add => "add", .Sub => "sub", .And => "and", .Nand => "nand", .Or => "or", .Xor => "xor", .Max => "max", .Min => "min", }; } fn IndentWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); pub const Error = UnderlyingWriter.Error; pub const Writer = std.io.Writer(*Self, Error, write); pub const indent_delta = 1; underlying_writer: UnderlyingWriter, indent_count: usize = 0, current_line_empty: bool = true, pub fn writer(self: *Self) Writer { return .{ .context = self }; } pub fn write(self: *Self, bytes: []const u8) Error!usize { if (bytes.len == 0) return @as(usize, 0); const current_indent = self.indent_count * Self.indent_delta; if (self.current_line_empty and current_indent > 0) { try self.underlying_writer.writeByteNTimes(' ', current_indent); } self.current_line_empty = false; return self.writeNoIndent(bytes); } pub fn insertNewline(self: *Self) Error!void { _ = try self.writeNoIndent("\n"); } pub fn pushIndent(self: *Self) void { self.indent_count += 1; } pub fn popIndent(self: *Self) void { assert(self.indent_count != 0); self.indent_count -= 1; } fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { if (bytes.len == 0) return @as(usize, 0); try self.underlying_writer.writeAll(bytes); if (bytes[bytes.len - 1] == '\n') { self.current_line_empty = true; } return bytes.len; } }; } fn toCIntBits(zig_bits: u32) ?u32 { for (&[_]u8{ 8, 16, 32, 64, 128 }) |c_bits| { if (zig_bits <= c_bits) { return c_bits; } } return null; } fn signAbbrev(signedness: std.builtin.Signedness) u8 { return switch (signedness) { .signed => 'i', .unsigned => 'u', }; } fn intMax(ty: Type, target: std.Target, buf: []u8) []const u8 { switch (ty.tag()) { .c_short => return "SHRT_MAX", .c_ushort => return "USHRT_MAX", .c_int => return "INT_MAX", .c_uint => return "UINT_MAX", .c_long => return "LONG_MAX", .c_ulong => return "ULONG_MAX", .c_longlong => return "LLONG_MAX", .c_ulonglong => return "ULLONG_MAX", else => { const int_info = ty.intInfo(target); const rhs = @intCast(u7, int_info.bits - @boolToInt(int_info.signedness == .signed)); const val = (@as(u128, 1) << rhs) - 1; // TODO make this integer literal have a suffix if necessary (such as "ull") return std.fmt.bufPrint(buf, "{}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, }; }, } } fn intMin(ty: Type, target: std.Target, buf: []u8) []const u8 { switch (ty.tag()) { .c_short => return "SHRT_MIN", .c_int => return "INT_MIN", .c_long => return "LONG_MIN", .c_longlong => return "LLONG_MIN", else => { const int_info = ty.intInfo(target); assert(int_info.signedness == .signed); const val = v: { if (int_info.bits == 0) break :v 0; const rhs = @intCast(u7, (int_info.bits - 1)); break :v -(@as(i128, 1) << rhs); }; return std.fmt.bufPrint(buf, "{d}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, }; }, } } fn loweredFnRetTyHasBits(fn_ty: Type) bool { const ret_ty = fn_ty.fnReturnType(); if (ret_ty.hasRuntimeBitsIgnoreComptime()) { return true; } if (ret_ty.isError()) { return true; } return false; } fn isByRef(ty: Type) bool { _ = ty; return false; }