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, ConstArgument, Mut }; const BigIntLimb = std.math.big.Limb; const BigInt = std.math.big.int; 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, /// Index into a tuple's fields field: usize, /// By-value decl: Decl.Index, decl_ref: Decl.Index, /// An undefined value (cannot be dereferenced) undef: Type, /// 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, }; const TypedefKind = enum { Forward, Complete, }; 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, Initializer, Other, }; const BuiltinInfo = enum { None, Range, Bits, }; fn formatTypeAsCIdentifier( data: FormatTypeAsCIdentContext, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { var stack = std.heap.stackFallback(128, data.mod.gpa); const allocator = stack.get(); const str = std.fmt.allocPrint(allocator, "{}", .{data.ty.fmt(data.mod)}) catch ""; defer allocator.free(str); return formatIdent(str, fmt, options, 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", {} }, .{ "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 isReservedIdent(ident: []const u8) bool { if (ident.len >= 2 and ident[0] == '_') { switch (ident[1]) { 'A'...'Z', '_' => return true, else => return false, } } else return reserved_idents.has(ident); } 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 isReservedIdent(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); const result = if (lowersToArray(ty, f.object.dg.module.getTarget())) result: { const writer = f.object.code_header.writer(); const decl_c_value = f.allocLocalValue(); try writer.writeAll("static "); try f.object.dg.renderTypeAndName(writer, ty, decl_c_value, .Const, 0, .Complete); try writer.writeAll(" = "); try f.object.dg.renderValue(writer, ty, val, .Initializer); try writer.writeAll(";\n "); break :result decl_c_value; } else CValue{ .constant = inst }; gop.value_ptr.* = result; return result; } fn wantSafety(f: *Function) bool { return switch (f.object.dg.module.optimizeMode()) { .Debug, .ReleaseSafe => true, .ReleaseFast, .ReleaseSmall => false, }; } 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, .Complete, ); return local_value; } fn writeCValue(f: *Function, w: anytype, c_value: CValue, location: ValueRenderLocation) !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, location); }, .undef => |ty| return f.object.dg.renderValue(w, ty, Value.undef, location), 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 writeCValueMember(f: *Function, w: anytype, c_value: CValue, member: CValue) !void { switch (c_value) { .constant => |inst| { const ty = f.air.typeOf(inst); const val = f.air.value(inst).?; try f.object.dg.renderValue(w, ty, val, .Other); try w.writeByte('.'); return f.writeCValue(w, member, .Other); }, else => return f.object.dg.writeCValueMember(w, c_value, member), } } fn writeCValueDerefMember(f: *Function, w: anytype, c_value: CValue, member: CValue) !void { switch (c_value) { .constant => |inst| { const ty = f.air.typeOf(inst); const val = f.air.value(inst).?; try w.writeByte('('); try f.object.dg.renderValue(w, ty, val, .Other); try w.writeAll(")->"); return f.writeCValue(w, member, .Other); }, else => return f.object.dg.writeCValueDerefMember(w, c_value, member), } } 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, .Complete); } fn renderTypecast(f: *Function, w: anytype, t: Type) !void { return f.object.dg.renderTypecast(w, t); } fn fmtIntLiteral(f: *Function, ty: Type, val: Value) !std.fmt.Formatter(formatIntLiteral) { return f.object.dg.fmtIntLiteral(ty, val); } }; /// 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.gpa, src_loc, format, args); return error.AnalysisFail; } fn getTypedefName(dg: *DeclGen, t: Type) ?[]const u8 { if (dg.typedefs.get(t)) |typedef| { return typedef.name; } else { return null; } } fn renderDeclValue( dg: *DeclGen, writer: anytype, ty: Type, val: Value, decl_index: Decl.Index, ) error{ OutOfMemory, AnalysisFail }!void { const decl = dg.module.declPtr(decl_index); assert(decl.has_tv); // Render an undefined pointer if we have a pointer to a zero-bit or comptime type. if (ty.isPtrAtRuntime() and !decl.ty.isFnOrHasRuntimeBits()) { return dg.writeCValue(writer, CValue{ .undef = ty }); } // Chase function values in order to be able to reference the original function. inline for (.{ .function, .extern_fn }) |tag| if (decl.val.castTag(tag)) |func| if (func.data.owner_decl != decl_index) return dg.renderDeclValue(writer, ty, val, func.data.owner_decl); if (ty.isSlice()) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeAll("){ .ptr = "); var buf: Type.SlicePtrFieldTypeBuffer = undefined; try dg.renderValue(writer, ty.slicePtrFieldType(&buf), val.slicePtr(), .Initializer); var len_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = val.sliceLen(dg.module), }; const len_val = Value.initPayload(&len_pl.base); return writer.print(", .len = {} }}", .{try dg.fmtIntLiteral(Type.usize, len_val)}); } // 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. const need_typecast = if (ty.castPtrToFn()) |_| false else !ty.eql(decl.ty, dg.module); if (need_typecast) { try writer.writeAll("(("); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('&'); try dg.renderDeclName(writer, decl_index); if (need_typecast) try writer.writeByte(')'); } // 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 { if (!ptr_ty.isSlice()) { 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 ptr_info = ptr_ty.ptrInfo(); const field_ptr = ptr_val.castTag(.field_ptr).?.data; const container_ty = field_ptr.container_ty; const index = field_ptr.field_index; 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); const FieldInfo = struct { name: []const u8, ty: Type }; const field_info: FieldInfo = switch (container_ty.zigTypeTag()) { .Struct => switch (container_ty.containerLayout()) { .Auto, .Extern => FieldInfo{ .name = container_ty.structFields().keys()[index], .ty = container_ty.structFields().values()[index].ty, }, .Packed => if (ptr_info.data.host_size == 0) { const target = dg.module.getTarget(); const byte_offset = container_ty.packedStructFieldByteOffset(index, target); var byte_offset_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = byte_offset, }; const byte_offset_val = Value.initPayload(&byte_offset_pl.base); var u8_ptr_pl = ptr_info; u8_ptr_pl.data.pointee_type = Type.u8; const u8_ptr_ty = Type.initPayload(&u8_ptr_pl.base); try writer.writeAll("&(("); try dg.renderTypecast(writer, u8_ptr_ty); try writer.writeByte(')'); try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty); return writer.print(")[{}]", .{try dg.fmtIntLiteral(Type.usize, byte_offset_val)}); } else { var host_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = ptr_info.data.host_size * 8, }; const host_ty = Type.initPayload(&host_pl.base); try writer.writeByte('('); try dg.renderTypecast(writer, ptr_ty); try writer.writeByte(')'); return dg.renderParentPtr(writer, field_ptr.container_ptr, host_ty); }, }, .Union => FieldInfo{ .name = container_ty.unionFields().keys()[index], .ty = container_ty.unionFields().values()[index].ty, }, .Pointer => field_info: { assert(container_ty.isSlice()); break :field_info switch (index) { 0 => FieldInfo{ .name = "ptr", .ty = container_ty.childType() }, 1 => FieldInfo{ .name = "len", .ty = Type.usize }, else => unreachable, }; }, else => unreachable, }; if (field_info.ty.hasRuntimeBitsIgnoreComptime()) { try writer.writeAll("&("); try dg.renderParentPtr(writer, field_ptr.container_ptr, container_ptr_ty); try writer.writeAll(")->"); switch (field_ptr.container_ty.tag()) { .union_tagged, .union_safety_tagged => try writer.writeAll("payload."), 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, arg_val: Value, location: ValueRenderLocation, ) error{ OutOfMemory, AnalysisFail }!void { var val = arg_val; if (val.castTag(.runtime_value)) |rt| { val = rt.data; } const target = dg.module.getTarget(); if (val.isUndefDeep()) { switch (ty.zigTypeTag()) { // bool b = 0xaa; evals to true, but memcpy(&b, 0xaa, 1); evals to false. .Bool => return dg.renderValue(writer, ty, Value.@"false", location), .Int, .Enum, .ErrorSet => return writer.print("{x}", .{try dg.fmtIntLiteral(ty, val)}), .Float => { const bits = ty.floatBits(target); var int_pl = Type.Payload.Bits{ .base = .{ .tag = .int_signed }, .data = bits }; const int_ty = Type.initPayload(&int_pl.base); try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeAll(")zig_as_"); try dg.renderTypeForBuiltinFnName(writer, ty); try writer.writeByte('('); switch (bits) { 16 => try writer.print("{x}", .{@bitCast(f16, undefPattern(i16))}), 32 => try writer.print("{x}", .{@bitCast(f32, undefPattern(i32))}), 64 => try writer.print("{x}", .{@bitCast(f64, undefPattern(i64))}), 80 => try writer.print("{x}", .{@bitCast(f80, undefPattern(i80))}), 128 => try writer.print("{x}", .{@bitCast(f128, undefPattern(i128))}), else => unreachable, } try writer.writeAll(", "); try dg.renderValue(writer, int_ty, Value.undef, .FunctionArgument); return writer.writeByte(')'); }, .Pointer => if (ty.isSlice()) { if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeAll("{("); var buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_ty = ty.slicePtrFieldType(&buf); try dg.renderTypecast(writer, ptr_ty); return writer.print("){x}, {0x}}}", .{try dg.fmtIntLiteral(Type.usize, val)}); } else { try writer.writeAll("(("); try dg.renderTypecast(writer, ty); return writer.print("){x})", .{try dg.fmtIntLiteral(Type.usize, val)}); }, .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const payload_ty = ty.optionalChild(&opt_buf); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) { return dg.renderValue(writer, Type.bool, val, location); } if (ty.optionalReprIsPayload()) { return dg.renderValue(writer, payload_ty, val, location); } if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeAll("{ .payload = "); try dg.renderValue(writer, payload_ty, val, .Initializer); try writer.writeAll(", .is_null = "); try dg.renderValue(writer, Type.bool, val, .Initializer); return writer.writeAll(" }"); }, .Struct => switch (ty.containerLayout()) { .Auto, .Extern => { if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('{'); var empty = true; for (ty.structFields().values()) |field| { if (!field.ty.hasRuntimeBits()) continue; if (!empty) try writer.writeByte(','); try dg.renderValue(writer, field.ty, val, .Initializer); empty = false; } if (empty) try writer.print("{x}", .{try dg.fmtIntLiteral(Type.u8, Value.undef)}); return writer.writeByte('}'); }, .Packed => return writer.print("{x}", .{try dg.fmtIntLiteral(ty, Value.undef)}), }, .Union => { if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('{'); if (ty.unionTagTypeSafety()) |tag_ty| { const layout = ty.unionGetLayout(target); if (layout.tag_size != 0) { try writer.writeAll(" .tag = "); try dg.renderValue(writer, tag_ty, val, .Initializer); try writer.writeByte(','); } try writer.writeAll(" .payload = {"); } for (ty.unionFields().values()) |field| { if (!field.ty.hasRuntimeBits()) continue; try dg.renderValue(writer, field.ty, val, .Initializer); break; } else try writer.print("{x}", .{try dg.fmtIntLiteral(Type.u8, Value.undef)}); if (ty.unionTagTypeSafety()) |_| try writer.writeByte('}'); return writer.writeByte('}'); }, .ErrorUnion => { if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeAll("{ .payload = "); try dg.renderValue(writer, ty.errorUnionPayload(), val, .Initializer); return writer.print(", .error = {x} }}", .{ try dg.fmtIntLiteral(ty.errorUnionSet(), val), }); }, .Array => { if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('{'); const c_len = ty.arrayLenIncludingSentinel(); var index: usize = 0; while (index < c_len) : (index += 1) { if (index > 0) try writer.writeAll(", "); try dg.renderValue(writer, ty.childType(), val, .Initializer); } return writer.writeByte('}'); }, .ComptimeInt, .ComptimeFloat, .Type, .EnumLiteral, .Void, .NoReturn, .Undefined, .Null, .BoundFn, .Opaque, => unreachable, .Fn, .Frame, .AnyFrame, .Vector, => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{ @tagName(tag), }), } unreachable; } switch (ty.zigTypeTag()) { .Int => switch (val.tag()) { .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr, .decl_ref_mut, .decl_ref, => try dg.renderParentPtr(writer, val, ty), else => try writer.print("{}", .{try dg.fmtIntLiteral(ty, val)}), }, .Float => { const bits = ty.floatBits(target); const f128_val = val.toFloat(f128); var int_ty_pl = Type.Payload.Bits{ .base = .{ .tag = .int_signed }, .data = bits }; const int_ty = Type.initPayload(&int_ty_pl.base); assert(bits <= 128); var int_val_limbs: [BigInt.calcTwosCompLimbCount(128)]BigIntLimb = undefined; var int_val_big = BigInt.Mutable{ .limbs = &int_val_limbs, .len = undefined, .positive = undefined, }; switch (bits) { 16 => int_val_big.set(@bitCast(i16, val.toFloat(f16))), 32 => int_val_big.set(@bitCast(i32, val.toFloat(f32))), 64 => int_val_big.set(@bitCast(i64, val.toFloat(f64))), 80 => int_val_big.set(@bitCast(i80, val.toFloat(f80))), 128 => int_val_big.set(@bitCast(i128, f128_val)), else => unreachable, } var int_val_pl = Value.Payload.BigInt{ .base = .{ .tag = if (int_val_big.positive) .int_big_positive else .int_big_negative }, .data = int_val_big.limbs[0..int_val_big.len], }; const int_val = Value.initPayload(&int_val_pl.base); try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); if (std.math.isFinite(f128_val)) { try writer.writeAll("zig_as_"); try dg.renderTypeForBuiltinFnName(writer, ty); try writer.writeByte('('); switch (bits) { 16 => try writer.print("{x}", .{val.toFloat(f16)}), 32 => try writer.print("{x}", .{val.toFloat(f32)}), 64 => try writer.print("{x}", .{val.toFloat(f64)}), 80 => try writer.print("{x}", .{val.toFloat(f80)}), 128 => try writer.print("{x}", .{f128_val}), else => unreachable, } } else { const operation = if (std.math.isSignalNan(f128_val)) "nans" else if (std.math.isNan(f128_val)) "nan" else if (std.math.isInf(f128_val)) "inf" else unreachable; try writer.writeAll("zig_as_special_"); try dg.renderTypeForBuiltinFnName(writer, ty); try writer.writeByte('('); if (std.math.signbit(f128_val)) try writer.writeByte('-'); try writer.writeAll(", "); try writer.writeAll(operation); try writer.writeAll(", "); if (std.math.isNan(f128_val)) switch (bits) { // We only actually need to pass the significand, but it will get // properly masked anyway, so just pass the whole value. 16 => try writer.print("\"0x{x}\"", .{@bitCast(u16, val.toFloat(f16))}), 32 => try writer.print("\"0x{x}\"", .{@bitCast(u32, val.toFloat(f32))}), 64 => try writer.print("\"0x{x}\"", .{@bitCast(u64, val.toFloat(f64))}), 80 => try writer.print("\"0x{x}\"", .{@bitCast(u80, val.toFloat(f80))}), 128 => try writer.print("\"0x{x}\"", .{@bitCast(u128, f128_val)}), else => unreachable, }; } return writer.print(", {x})", .{try dg.fmtIntLiteral(int_ty, int_val)}); }, .Pointer => switch (val.tag()) { .null_value, .zero => if (ty.isSlice()) { var slice_pl = Value.Payload.Slice{ .base = .{ .tag = .slice }, .data = .{ .ptr = val, .len = Value.undef }, }; const slice_val = Value.initPayload(&slice_pl.base); return dg.renderValue(writer, ty, slice_val, location); } else { try writer.writeAll("(("); try dg.renderTypecast(writer, ty); try writer.writeAll(")NULL)"); }, .variable => { const decl = val.castTag(.variable).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); }, .slice => { if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } const slice = val.castTag(.slice).?.data; var buf: Type.SlicePtrFieldTypeBuffer = undefined; try writer.writeByte('{'); try dg.renderValue(writer, ty.slicePtrFieldType(&buf), slice.ptr, .Initializer); try writer.writeAll(", "); try dg.renderValue(writer, Type.usize, slice.len, .Initializer); try writer.writeByte('}'); }, .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); return writer.print("){x})", .{try dg.fmtIntLiteral(Type.usize, val)}); }, .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr, .decl_ref_mut, .decl_ref, => try dg.renderParentPtr(writer, val, ty), else => unreachable, }, .Array => { if (location == .FunctionArgument) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } // 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, .Initializer); } else { try writer.writeByte('0'); } try writer.writeByte('}'); }, else => { // Fall back to generic implementation. var arena = std.heap.ArenaAllocator.init(dg.gpa); defer arena.deinit(); const arena_allocator = arena.allocator(); try writer.writeByte('{'); const ai = ty.arrayInfo(); var index: usize = 0; while (index < ai.len) : (index += 1) { if (index != 0) try writer.writeByte(','); const elem_val = try val.elemValue(dg.module, arena_allocator, index); try dg.renderValue(writer, ai.elem_type, elem_val, .Initializer); } if (ai.sentinel) |s| { if (index != 0) try writer.writeByte(','); try dg.renderValue(writer, ai.elem_type, s, .Initializer); } try writer.writeByte('}'); }, } }, .Bool => return writer.print("zig_{}", .{val.toBool()}), .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const payload_ty = ty.optionalChild(&opt_buf); const is_null_val = Value.makeBool(val.tag() == .null_value); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) return dg.renderValue(writer, Type.bool, is_null_val, location); if (ty.optionalReprIsPayload()) { const payload_val = if (val.castTag(.opt_payload)) |pl| pl.data else val; return dg.renderValue(writer, payload_ty, payload_val, location); } if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } const payload_val = if (val.castTag(.opt_payload)) |pl| pl.data else Value.undef; try writer.writeAll("{ .payload = "); try dg.renderValue(writer, payload_ty, payload_val, .Initializer); try writer.writeAll(", .is_null = "); try dg.renderValue(writer, Type.bool, is_null_val, .Initializer); try writer.writeAll(" }"); }, .ErrorSet => { const error_name = if (val.castTag(.@"error")) |error_pl| error_pl.data.name else dg.module.error_name_list.items[0]; // Error values are already defined by genErrDecls. try writer.print("zig_error_{}", .{fmtIdent(error_name)}); }, .ErrorUnion => { const error_ty = ty.errorUnionSet(); const payload_ty = ty.errorUnionPayload(); if (!payload_ty.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_ty, err_val, location); } if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } const payload_val = if (val.castTag(.eu_payload)) |pl| pl.data else Value.undef; const error_val = if (val.errorUnionIsPayload()) Value.zero else val; try writer.writeAll("{ .payload = "); try dg.renderValue(writer, payload_ty, payload_val, .Initializer); try writer.writeAll(", .error = "); try dg.renderValue(writer, error_ty, error_val, .Initializer); 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 => switch (ty.containerLayout()) { .Auto, .Extern => { const field_vals = val.castTag(.aggregate).?.data; if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('{'); var empty = true; for (field_vals) |field_val, field_index| { const field_ty = ty.structFieldType(field_index); if (!field_ty.hasRuntimeBits()) continue; if (!empty) try writer.writeByte(','); try dg.renderValue(writer, field_ty, field_val, .Initializer); empty = false; } if (empty) try writer.print("{}", .{try dg.fmtIntLiteral(Type.u8, Value.zero)}); try writer.writeByte('}'); }, .Packed => { const field_vals = val.castTag(.aggregate).?.data; const int_info = ty.intInfo(target); var bit_offset_ty_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = Type.smallestUnsignedBits(int_info.bits - 1), }; const bit_offset_ty = Type.initPayload(&bit_offset_ty_pl.base); var bit_offset_val_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = 0 }; const bit_offset_val = Value.initPayload(&bit_offset_val_pl.base); try writer.writeByte('('); var empty = true; for (field_vals) |field_val, index| { const field_ty = ty.structFieldType(index); if (!field_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!empty) try writer.writeAll(" | "); try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); try dg.renderValue(writer, field_ty, field_val, .Other); try writer.writeAll(" << "); try dg.renderValue(writer, bit_offset_ty, bit_offset_val, .FunctionArgument); bit_offset_val_pl.data += field_ty.bitSize(target); empty = false; } if (empty) try dg.renderValue(writer, ty, Value.undef, .Initializer); try writer.writeByte(')'); }, }, .Union => { const union_obj = val.castTag(.@"union").?.data; if (location != .Initializer) { try writer.writeByte('('); try dg.renderTypecast(writer, ty); try writer.writeByte(')'); } try writer.writeByte('{'); if (ty.unionTagTypeSafety()) |tag_ty| { const layout = ty.unionGetLayout(target); if (layout.tag_size != 0) { try writer.writeAll(".tag = "); try dg.renderValue(writer, tag_ty, union_obj.tag, .Initializer); 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, .Initializer); } else try writer.writeByte('0'); if (ty.unionTagTypeSafety()) |_| try writer.writeByte('}'); try writer.writeByte('}'); }, .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, kind: TypedefKind) !void { const fn_info = dg.decl.ty.fnInfo(); if (fn_info.cc == .Naked) try w.writeAll("zig_naked "); if (dg.decl.val.castTag(.function)) |func_payload| if (func_payload.data.is_cold) try w.writeAll("zig_cold "); const target = dg.module.getTarget(); var ret_buf: LowerFnRetTyBuffer = undefined; const ret_ty = lowerFnRetTy(fn_info.return_type, &ret_buf, target); try dg.renderType(w, ret_ty, kind); try w.writeByte(' '); try dg.renderDeclName(w, dg.decl_index); try w.writeByte('('); var index: usize = 0; for (fn_info.param_types) |param_type| { if (!param_type.hasRuntimeBitsIgnoreComptime()) continue; if (index > 0) try w.writeAll(", "); const name = CValue{ .arg = index }; try dg.renderTypeAndName(w, param_type, name, .ConstArgument, 0, kind); index += 1; } if (fn_info.is_var_args) { if (index > 0) try w.writeAll(", "); try w.writeAll("..."); } else if (index == 0) { try dg.renderType(w, Type.void, kind); } try w.writeByte(')'); } fn renderPtrToFnTypedef(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 fn_info = t.fnInfo(); const target = dg.module.getTarget(); var ret_buf: LowerFnRetTyBuffer = undefined; const ret_ty = lowerFnRetTy(fn_info.return_type, &ret_buf, target); try bw.writeAll("typedef "); try dg.renderType(bw, ret_ty, .Forward); try bw.writeAll(" (*"); const name_begin = buffer.items.len; try bw.print("zig_F_{}", .{typeToCIdentifier(t, dg.module)}); const name_end = buffer.items.len; try bw.writeAll(")("); const param_len = fn_info.param_types.len; var params_written: usize = 0; var index: usize = 0; while (index < param_len) : (index += 1) { const param_ty = fn_info.param_types[index]; if (!param_ty.hasRuntimeBitsIgnoreComptime()) continue; if (params_written > 0) { try bw.writeAll(", "); } try dg.renderTypeAndName(bw, param_ty, .{ .bytes = "" }, .Mut, 0, .Forward); 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 dg.renderType(bw, Type.void, .Forward); } try bw.writeAll(");\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..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 { std.debug.assert(t.sentinel() == null); // expected canonical type var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); var ptr_ty_buf: Type.SlicePtrFieldTypeBuffer = undefined; const ptr_ty = t.slicePtrFieldType(&ptr_ty_buf); const ptr_name = CValue{ .identifier = "ptr" }; const len_ty = Type.usize; const len_name = CValue{ .identifier = "len" }; try bw.writeAll("typedef struct {\n "); try dg.renderTypeAndName(bw, ptr_ty, ptr_name, .Mut, 0, .Complete); try bw.writeAll(";\n "); try dg.renderTypeAndName(bw, len_ty, len_name, .Mut, 0, .Complete); try bw.writeAll(";\n} "); const name_begin = buffer.items.len; try bw.print("zig_{c}_{}", .{ @as(u8, if (t.isConstPtr()) 'L' else 'M'), typeToCIdentifier(t.childType(), dg.module), }); const name_end = buffer.items.len; try bw.writeAll(";\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderFwdTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { // The forward declaration for T is stored with a key of *const T. const child_ty = t.childType(); var fqn_buf = std.ArrayList(u8).init(dg.typedefs.allocator); defer fqn_buf.deinit(); const owner_decl_index = child_ty.getOwnerDecl(); const owner_decl = dg.module.declPtr(owner_decl_index); try owner_decl.renderFullyQualifiedName(dg.module, fqn_buf.writer()); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const tag = switch (child_ty.zigTypeTag()) { .Struct => "struct ", .Union => if (child_ty.unionTagTypeSafety()) |_| "struct " else "union ", else => unreachable, }; const name_begin = buffer.items.len + "typedef ".len + tag.len; try buffer.writer().print("typedef {s}zig_S_{}__{d} ", .{ tag, fmtIdent(fqn_buf.items), @enumToInt(owner_decl_index), }); const name_end = buffer.items.len - " ".len; try buffer.ensureUnusedCapacity((name_end - name_begin) + ";\n".len); buffer.appendSliceAssumeCapacity(buffer.items[name_begin..name_end]); buffer.appendSliceAssumeCapacity(";\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; 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 { var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t }; const ptr_ty = Type.initPayload(&ptr_pl.base); const name = dg.getTypedefName(ptr_ty) orelse try dg.renderFwdTypedef(ptr_ty); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.appendSlice("struct "); try buffer.appendSlice(name); try buffer.appendSlice(" {\n"); { var it = t.structFields().iterator(); var empty = true; while (it.next()) |field| { const field_ty = field.value_ptr.ty; if (!field_ty.hasRuntimeBits()) continue; const alignment = field.value_ptr.abi_align; const field_name = CValue{ .identifier = field.key_ptr.* }; try buffer.append(' '); try dg.renderTypeAndName(buffer.writer(), field_ty, field_name, .Mut, alignment, .Complete); try buffer.appendSlice(";\n"); empty = false; } if (empty) try buffer.appendSlice(" char empty_struct;\n"); } try buffer.appendSlice("};\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); 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 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.appendSlice("typedef struct {\n"); { const fields = t.tupleFields(); var field_id: usize = 0; for (fields.types) |field_ty, i| { if (!field_ty.hasRuntimeBits() or fields.values[i].tag() != .unreachable_value) continue; try buffer.append(' '); try dg.renderTypeAndName(buffer.writer(), field_ty, .{ .field = field_id }, .Mut, 0, .Complete); try buffer.appendSlice(";\n"); field_id += 1; } if (field_id == 0) try buffer.appendSlice(" char empty_tuple;\n"); } const name_begin = buffer.items.len + "} ".len; try buffer.writer().print("}} zig_T_{};\n", .{typeToCIdentifier(t, dg.module)}); const name_end = buffer.items.len - ";\n".len; const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; 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 { var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t }; const ptr_ty = Type.initPayload(&ptr_pl.base); const name = dg.getTypedefName(ptr_ty) orelse try dg.renderFwdTypedef(ptr_ty); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.appendSlice(if (t.unionTagTypeSafety()) |_| "struct " else "union "); try buffer.appendSlice(name); try buffer.appendSlice(" {\n"); const indent = if (t.unionTagTypeSafety()) |tag_ty| indent: { const target = dg.module.getTarget(); const layout = t.unionGetLayout(target); if (layout.tag_size != 0) { try buffer.append(' '); try dg.renderTypeAndName(buffer.writer(), tag_ty, .{ .identifier = "tag" }, .Mut, 0, .Complete); try buffer.appendSlice(";\n"); } try buffer.appendSlice(" union {\n"); break :indent " "; } else " "; { var it = t.unionFields().iterator(); var empty = true; while (it.next()) |field| { const field_ty = field.value_ptr.ty; if (!field_ty.hasRuntimeBits()) continue; const alignment = field.value_ptr.abi_align; const field_name = CValue{ .identifier = field.key_ptr.* }; try buffer.appendSlice(indent); try dg.renderTypeAndName(buffer.writer(), field_ty, field_name, .Mut, alignment, .Complete); try buffer.appendSlice(";\n"); empty = false; } if (empty) { try buffer.appendSlice(indent); try buffer.appendSlice("char empty_union;\n"); } } if (t.unionTagTypeSafety()) |_| try buffer.appendSlice(" } payload;\n"); try buffer.appendSlice("};\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); 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 { assert(t.errorUnionSet().tag() == .anyerror); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); const payload_ty = t.errorUnionPayload(); const payload_name = CValue{ .identifier = "payload" }; const error_ty = t.errorUnionSet(); const error_name = CValue{ .identifier = "error" }; const target = dg.module.getTarget(); const payload_align = payload_ty.abiAlignment(target); const error_align = error_ty.abiAlignment(target); try bw.writeAll("typedef struct {\n "); if (error_align > payload_align) { try dg.renderTypeAndName(bw, payload_ty, payload_name, .Mut, 0, .Complete); try bw.writeAll(";\n "); try dg.renderTypeAndName(bw, error_ty, error_name, .Mut, 0, .Complete); } else { try dg.renderTypeAndName(bw, error_ty, error_name, .Mut, 0, .Complete); try bw.writeAll(";\n "); try dg.renderTypeAndName(bw, payload_ty, payload_name, .Mut, 0, .Complete); } try bw.writeAll(";\n} "); const name_begin = buffer.items.len; try bw.print("zig_E_{}", .{typeToCIdentifier(payload_ty, dg.module)}); const name_end = buffer.items.len; try bw.writeAll(";\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; 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 { const info = t.arrayInfo(); std.debug.assert(info.sentinel == null); // expected canonical type var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); try bw.writeAll("typedef "); try dg.renderType(bw, info.elem_type, .Complete); const name_begin = buffer.items.len + " ".len; try bw.print(" zig_A_{}_{d}", .{ typeToCIdentifier(info.elem_type, dg.module), info.len }); const name_end = buffer.items.len; const c_len = if (info.len > 0) info.len else 1; var c_len_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = c_len }; const c_len_val = Value.initPayload(&c_len_pl.base); try bw.print("[{}];\n", .{try dg.fmtIntLiteral(Type.usize, c_len_val)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..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 {\n "); try dg.renderTypeAndName(bw, child_type, .{ .identifier = "payload" }, .Mut, 0, .Complete); try bw.writeAll(";\n "); try dg.renderTypeAndName(bw, Type.bool, .{ .identifier = "is_null" }, .Mut, 0, .Complete); try bw.writeAll(";\n} "); const name_begin = buffer.items.len; try bw.print("zig_Q_{}", .{typeToCIdentifier(child_type, dg.module)}); const name_end = buffer.items.len; try bw.writeAll(";\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderOpaqueTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const opaque_ty = t.cast(Type.Payload.Opaque).?.data; const unqualified_name = dg.module.declPtr(opaque_ty.owner_decl).name; const fqn = try opaque_ty.getFullyQualifiedName(dg.module); defer dg.typedefs.allocator.free(fqn); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.writer().print("typedef struct { } ", .{fmtIdent(std.mem.span(unqualified_name))}); const name_begin = buffer.items.len; try buffer.writer().print("zig_O_{}", .{fmtIdent(fqn)}); const name_end = buffer.items.len; try buffer.appendSlice(";\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; 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, kind: TypedefKind, ) error{ OutOfMemory, AnalysisFail }!void { const target = dg.module.getTarget(); switch (t.zigTypeTag()) { .NoReturn, .Void, .Bool, .Int, .Float, .ErrorSet => |tag| { const is_named = switch (tag) { .Int => t.isNamedInt(), .ErrorSet => false, else => true, }; if (is_named) { try w.writeAll("zig_"); try t.print(w, dg.module); } else { const int_info = t.intInfo(target); if (toCIntBits(int_info.bits)) |c_bits| return w.print("zig_{c}{d}", .{ signAbbrev(int_info.signedness), c_bits }) else if (loweredArrayInfo(t, target)) |array_info| { assert(array_info.sentinel == null); var array_pl = Type.Payload.Array{ .base = .{ .tag = .array }, .data = .{ .len = array_info.len, .elem_type = array_info.elem_type }, }; const array_ty = Type.initPayload(&array_pl.base); return dg.renderType(w, array_ty, kind); } else return dg.fail("C backend: Unable to lower unnamed integer type {}", .{ t.fmt(dg.module), }); } }, .Pointer => { const ptr_info = t.ptrInfo().data; if (ptr_info.size == .Slice) { var slice_pl = Type.Payload.ElemType{ .base = .{ .tag = if (t.ptrIsMutable()) .mut_slice else .const_slice }, .data = ptr_info.pointee_type, }; const slice_ty = Type.initPayload(&slice_pl.base); const name = dg.getTypedefName(slice_ty) orelse try dg.renderSliceTypedef(slice_ty); return w.writeAll(name); } if (ptr_info.pointee_type.zigTypeTag() == .Fn) { const name = dg.getTypedefName(ptr_info.pointee_type) orelse try dg.renderPtrToFnTypedef(ptr_info.pointee_type); return w.writeAll(name); } if (ptr_info.host_size != 0) { var host_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = ptr_info.host_size * 8, }; const host_ty = Type.initPayload(&host_pl.base); try dg.renderType(w, host_ty, .Forward); } else if (t.isCPtr() and ptr_info.pointee_type.eql(Type.u8, dg.module) and (dg.decl.val.tag() == .extern_fn or std.mem.eql(u8, std.mem.span(dg.decl.name), "main"))) { // This is a hack, since the c compiler expects a lot of external // library functions to have char pointers in their signatures, but // u8 and i8 produce unsigned char and signed char respectively, // which in C are (not very usefully) different than char. try w.writeAll("char"); } else try dg.renderType(w, switch (ptr_info.pointee_type.tag()) { .anyopaque => Type.void, else => ptr_info.pointee_type, }, .Forward); if (t.isConstPtr()) try w.writeAll(" const"); if (t.isVolatilePtr()) try w.writeAll(" volatile"); return w.writeAll(" *"); }, .Array => { var array_pl = Type.Payload.Array{ .base = .{ .tag = .array }, .data = .{ .len = t.arrayLenIncludingSentinel(), .elem_type = t.childType(), } }; const array_ty = Type.initPayload(&array_pl.base); const name = dg.getTypedefName(array_ty) orelse try dg.renderArrayTypedef(array_ty); return w.writeAll(name); }, .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const child_type = t.optionalChild(&opt_buf); if (!child_type.hasRuntimeBitsIgnoreComptime()) return dg.renderType(w, Type.bool, kind); if (t.optionalReprIsPayload()) return dg.renderType(w, child_type, kind); const name = dg.getTypedefName(t) orelse try dg.renderOptionalTypedef(t, child_type); return w.writeAll(name); }, .ErrorUnion => { const payload_ty = t.errorUnionPayload(); if (!payload_ty.hasRuntimeBitsIgnoreComptime()) return dg.renderType(w, Type.anyerror, kind); var error_union_pl = Type.Payload.ErrorUnion{ .data = .{ .error_set = Type.anyerror, .payload = payload_ty }, }; const error_union_ty = Type.initPayload(&error_union_pl.base); const name = dg.getTypedefName(error_union_ty) orelse try dg.renderErrorUnionTypedef(error_union_ty); return w.writeAll(name); }, .Struct, .Union => |tag| if (tag == .Struct and t.containerLayout() == .Packed) try dg.renderType(w, t.castTag(.@"struct").?.data.backing_int_ty, kind) else if (t.isTupleOrAnonStruct()) { const ExpectedContents = struct { types: [8]Type, values: [8]Value }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), dg.gpa); const allocator = stack.get(); var tuple_storage = std.MultiArrayList(struct { type: Type, value: Value }){}; defer tuple_storage.deinit(allocator); try tuple_storage.ensureTotalCapacity(allocator, t.structFieldCount()); const fields = t.tupleFields(); for (fields.values) |value, index| if (value.tag() == .unreachable_value) tuple_storage.appendAssumeCapacity(.{ .type = fields.types[index], .value = value, }); const tuple_slice = tuple_storage.slice(); var tuple_pl = Type.Payload.Tuple{ .data = .{ .types = tuple_slice.items(.type), .values = tuple_slice.items(.value), } }; const tuple_ty = Type.initPayload(&tuple_pl.base); const name = dg.getTypedefName(tuple_ty) orelse try dg.renderTupleTypedef(tuple_ty); try w.writeAll(name); } else if (kind == .Complete) { const name = dg.getTypedefName(t) orelse switch (tag) { .Struct => try dg.renderStructTypedef(t), .Union => try dg.renderUnionTypedef(t), else => unreachable, }; try w.writeAll(name); } else { var ptr_pl = Type.Payload.ElemType{ .base = .{ .tag = .single_const_pointer }, .data = t, }; const ptr_ty = Type.initPayload(&ptr_pl.base); const name = dg.getTypedefName(ptr_ty) orelse try dg.renderFwdTypedef(ptr_ty); try w.writeAll(name); }, .Enum => { // For enums, we simply use the integer tag type. var int_tag_buf: Type.Payload.Bits = undefined; const int_tag_ty = t.intTagType(&int_tag_buf); try dg.renderType(w, int_tag_ty, kind); }, .Opaque => switch (t.tag()) { .@"opaque" => { const name = dg.getTypedefName(t) orelse try dg.renderOpaqueTypedef(t); try w.writeAll(name); }, else => unreachable, }, .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 { return renderTypeAndName(dg, w, ty, .{ .bytes = "" }, .Mut, 0, .Complete); } /// 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, kind: TypedefKind, ) error{ OutOfMemory, AnalysisFail }!void { var suffix = std.ArrayList(u8).init(dg.gpa); defer suffix.deinit(); const suffix_writer = suffix.writer(); // Any top-level array types are rendered here as a suffix, which // avoids creating typedefs for every array type const target = dg.module.getTarget(); var render_ty = ty; var depth: u32 = 0; while (loweredArrayInfo(render_ty, target)) |array_info| { const c_len = array_info.len + @boolToInt(array_info.sentinel != null); var c_len_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = c_len }; const c_len_val = Value.initPayload(&c_len_pl.base); try suffix_writer.writeByte('['); if (mutability == .ConstArgument and depth == 0) try suffix_writer.writeAll("static const "); try suffix.writer().print("{}]", .{try dg.fmtIntLiteral(Type.usize, c_len_val)}); render_ty = array_info.elem_type; depth += 1; } if (alignment != 0 and alignment > ty.abiAlignment(target)) { try w.print("zig_align({}) ", .{alignment}); } try dg.renderType(w, render_ty, kind); const const_prefix = switch (mutability) { .Const, .ConstArgument => "const ", .Mut => "", }; try w.print(" {s}", .{const_prefix}); try dg.writeCValue(w, name); try w.writeAll(suffix.items); } fn renderTagNameFn(dg: *DeclGen, enum_ty: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); const name_slice_ty = Type.initTag(.const_slice_u8_sentinel_0); try buffer.appendSlice("static "); try dg.renderType(bw, name_slice_ty, .Complete); const name_begin = buffer.items.len + " ".len; try bw.print(" zig_tagName_{}(", .{typeToCIdentifier(enum_ty, dg.module)}); const name_end = buffer.items.len - "(".len; try dg.renderTypeAndName(bw, enum_ty, .{ .identifier = "tag" }, .Const, 0, .Complete); try buffer.appendSlice(") {\n switch (tag) {\n"); for (enum_ty.enumFields().keys()) |name, index| { const name_z = try dg.typedefs.allocator.dupeZ(u8, name); defer dg.typedefs.allocator.free(name_z); const name_bytes = name_z[0 .. name_z.len + 1]; var tag_pl: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, .data = @intCast(u32, index), }; const tag_val = Value.initPayload(&tag_pl.base); var int_pl: Value.Payload.U64 = undefined; const int_val = tag_val.enumToInt(enum_ty, &int_pl); var name_ty_pl = Type.Payload.Len{ .base = .{ .tag = .array_u8_sentinel_0 }, .data = name.len }; const name_ty = Type.initPayload(&name_ty_pl.base); var name_pl = Value.Payload.Bytes{ .base = .{ .tag = .bytes }, .data = name_bytes }; const name_val = Value.initPayload(&name_pl.base); var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = name.len }; const len_val = Value.initPayload(&len_pl.base); try bw.print(" case {}: {{\n static ", .{try dg.fmtIntLiteral(enum_ty, int_val)}); try dg.renderTypeAndName(bw, name_ty, .{ .identifier = "name" }, .Const, 0, .Complete); try buffer.appendSlice(" = "); try dg.renderValue(bw, name_ty, name_val, .Initializer); try buffer.appendSlice(";\n return ("); try dg.renderTypecast(bw, name_slice_ty); try bw.print("){{{}, {}}};\n", .{ fmtIdent("name"), try dg.fmtIntLiteral(Type.usize, len_val), }); try buffer.appendSlice(" }\n"); } try buffer.appendSlice(" }\n while ("); try dg.renderValue(bw, Type.bool, Value.@"true", .Other); try buffer.appendSlice(") "); _ = try airBreakpoint(bw); try buffer.appendSlice("}\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_begin..name_end]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try enum_ty.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn getTagNameFn(dg: *DeclGen, enum_ty: Type) ![]const u8 { return dg.getTypedefName(enum_ty) orelse try dg.renderTagNameFn(enum_ty); } 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}), .field => |i| return w.print("f{d}", .{i}), .decl => |decl| return dg.renderDeclName(w, decl), .decl_ref => |decl| { try w.writeByte('&'); return dg.renderDeclName(w, decl); }, .undef => |ty| return dg.renderValue(w, ty, Value.undef, .Other), .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}), .field => |i| return w.print("f{d}", .{i}), .decl => |decl| { try w.writeAll("(*"); try dg.renderDeclName(w, decl); return w.writeByte(')'); }, .decl_ref => |decl| return dg.renderDeclName(w, decl), .undef => unreachable, .identifier => |ident| return w.print("(*{ })", .{fmtIdent(ident)}), .bytes => |bytes| { try w.writeAll("(*"); try w.writeAll(bytes); return w.writeByte(')'); }, } } fn writeCValueMember(dg: *DeclGen, writer: anytype, c_value: CValue, member: CValue) !void { try dg.writeCValue(writer, c_value); try writer.writeByte('.'); try dg.writeCValue(writer, member); } fn writeCValueDerefMember(dg: *DeclGen, writer: anytype, c_value: CValue, member: CValue) !void { switch (c_value) { .none, .constant, .field, .undef => unreachable, .local, .arg, .decl, .identifier, .bytes => { try dg.writeCValue(writer, c_value); try writer.writeAll("->"); }, .local_ref, .decl_ref => { try dg.writeCValueDeref(writer, c_value); try writer.writeByte('.'); }, } try dg.writeCValue(writer, member); } 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.isExtern()) { return writer.writeAll(mem.sliceTo(decl.name, 0)); } else { const gpa = dg.gpa; const name = try decl.getFullyQualifiedName(dg.module); defer gpa.free(name); return writer.print("{ }", .{fmtIdent(name)}); } } fn renderTypeForBuiltinFnName(dg: *DeclGen, writer: anytype, ty: Type) !void { const target = dg.module.getTarget(); if (ty.isAbiInt()) { const int_info = ty.intInfo(target); const c_bits = toCIntBits(int_info.bits) orelse return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); try writer.print("{c}{d}", .{ signAbbrev(int_info.signedness), c_bits }); } else if (ty.isRuntimeFloat()) { try ty.print(writer, dg.module); } else return dg.fail("TODO: CBE: implement renderTypeForBuiltinFnName for type {}", .{ ty.fmt(dg.module), }); } fn renderBuiltinInfo(dg: *DeclGen, writer: anytype, ty: Type, info: BuiltinInfo) !void { const target = dg.module.getTarget(); switch (info) { .None => {}, .Range => { var arena = std.heap.ArenaAllocator.init(dg.gpa); defer arena.deinit(); const ExpectedContents = union { u: Value.Payload.U64, i: Value.Payload.I64 }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), arena.allocator()); const int_info = ty.intInfo(target); if (int_info.signedness == .signed) { const min_val = try ty.minInt(stack.get(), target); try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, min_val)}); } const max_val = try ty.maxInt(stack.get(), target); try writer.print(", {x}", .{try dg.fmtIntLiteral(ty, max_val)}); }, .Bits => { var bits_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = ty.bitSize(target), }; const bits_val = Value.initPayload(&bits_pl.base); try writer.print(", {}", .{try dg.fmtIntLiteral(Type.u8, bits_val)}); }, } } fn fmtIntLiteral( dg: *DeclGen, ty: Type, val: Value, ) !std.fmt.Formatter(formatIntLiteral) { const int_info = ty.intInfo(dg.module.getTarget()); const c_bits = toCIntBits(int_info.bits); if (c_bits == null or c_bits.? > 128) return dg.fail("TODO implement integer constants larger than 128 bits", .{}); return std.fmt.Formatter(formatIntLiteral){ .data = .{ .ty = ty, .val = val, .mod = dg.module, } }; } }; pub fn genGlobalAsm(mod: *Module, code: *std.ArrayList(u8)) !void { var it = mod.global_assembly.valueIterator(); while (it.next()) |asm_source| { try code.writer().print("__asm({s});\n", .{fmtStringLiteral(asm_source.*)}); } } pub fn genErrDecls(o: *Object) !void { const writer = o.writer(); try writer.writeAll("enum {\n"); o.indent_writer.pushIndent(); var max_name_len: usize = 0; for (o.dg.module.error_name_list.items) |name, value| { max_name_len = std.math.max(name.len, max_name_len); var err_pl = Value.Payload.Error{ .data = .{ .name = name } }; try o.dg.renderValue(writer, Type.anyerror, Value.initPayload(&err_pl.base), .Other); try writer.print(" = {d}u,\n", .{value}); } o.indent_writer.popIndent(); try writer.writeAll("};\n"); const name_prefix = "zig_errorName"; const name_buf = try o.dg.gpa.alloc(u8, name_prefix.len + "_".len + max_name_len + 1); defer o.dg.gpa.free(name_buf); std.mem.copy(u8, name_buf, name_prefix ++ "_"); for (o.dg.module.error_name_list.items) |name| { std.mem.copy(u8, name_buf[name_prefix.len + "_".len ..], name); name_buf[name_prefix.len + "_".len + name.len] = 0; const identifier = name_buf[0 .. name_prefix.len + "_".len + name.len :0]; const name_z = identifier[name_prefix.len + "_".len ..]; var name_ty_pl = Type.Payload.Len{ .base = .{ .tag = .array_u8_sentinel_0 }, .data = name.len }; const name_ty = Type.initPayload(&name_ty_pl.base); var name_pl = Value.Payload.Bytes{ .base = .{ .tag = .bytes }, .data = name_z }; const name_val = Value.initPayload(&name_pl.base); try writer.writeAll("static "); try o.dg.renderTypeAndName(writer, name_ty, .{ .identifier = identifier }, .Const, 0, .Complete); try writer.writeAll(" = "); try o.dg.renderValue(writer, name_ty, name_val, .Initializer); try writer.writeAll(";\n"); } var name_array_ty_pl = Type.Payload.Array{ .base = .{ .tag = .array }, .data = .{ .len = o.dg.module.error_name_list.items.len, .elem_type = Type.initTag(.const_slice_u8_sentinel_0), } }; const name_array_ty = Type.initPayload(&name_array_ty_pl.base); try writer.writeAll("static "); try o.dg.renderTypeAndName(writer, name_array_ty, .{ .identifier = name_prefix }, .Const, 0, .Complete); try writer.writeAll(" = {"); for (o.dg.module.error_name_list.items) |name, value| { if (value != 0) try writer.writeByte(','); var len_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = name.len }; const len_val = Value.initPayload(&len_pl.base); try writer.print("{{" ++ name_prefix ++ "_{}, {}}}", .{ fmtIdent(name), try o.dg.fmtIntLiteral(Type.usize, len_val), }); } try writer.writeAll("};\n"); } 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(); try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static "); try o.dg.renderFunctionSignature(fwd_decl_writer, .Forward); try fwd_decl_writer.writeAll(";\n"); try o.indent_writer.insertNewline(); if (!is_global) try o.writer().writeAll("static "); try o.dg.renderFunctionSignature(o.writer(), .Complete); 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.ty.isFnOrHasRuntimeBitsIgnoreComptime()) return; if (tv.val.tag() == .extern_fn) { const fwd_decl_writer = o.dg.fwd_decl.writer(); try fwd_decl_writer.writeAll("zig_extern "); try o.dg.renderFunctionSignature(fwd_decl_writer, .Forward); try fwd_decl_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(); const decl_c_value: CValue = if (is_global) .{ .bytes = mem.span(o.dg.decl.name), } else .{ .decl = o.dg.decl_index, }; try fwd_decl_writer.writeAll(if (is_global) "zig_extern " else "static "); if (variable.is_threadlocal) try fwd_decl_writer.writeAll("zig_threadlocal "); try o.dg.renderTypeAndName(fwd_decl_writer, o.dg.decl.ty, decl_c_value, .Mut, o.dg.decl.@"align", .Complete); try fwd_decl_writer.writeAll(";\n"); if (variable.is_extern) return; const w = o.writer(); if (!is_global) try w.writeAll("static "); if (variable.is_threadlocal) try w.writeAll("zig_threadlocal "); try o.dg.renderTypeAndName(w, o.dg.decl.ty, decl_c_value, .Mut, o.dg.decl.@"align", .Complete); try w.writeAll(" = "); try o.dg.renderValue(w, tv.ty, variable.init, .Initializer); try w.writeByte(';'); try o.indent_writer.insertNewline(); } else { const decl_c_value: CValue = .{ .decl = o.dg.decl_index }; const fwd_decl_writer = o.dg.fwd_decl.writer(); try fwd_decl_writer.writeAll("static "); try o.dg.renderTypeAndName(fwd_decl_writer, tv.ty, decl_c_value, .Mut, o.dg.decl.@"align", .Complete); try fwd_decl_writer.writeAll(";\n"); const writer = o.writer(); try writer.writeAll("static "); // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 try o.dg.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut, o.dg.decl.@"align", .Complete); try writer.writeAll(" = "); try o.dg.renderValue(writer, tv.ty, tv.val, .Initializer); 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 "); try dg.renderFunctionSignature(writer, .Complete); 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.object.writer()), .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, "+", "add", .None), .sub => try airBinOp(f, inst, "-", "sub", .None), .mul => try airBinOp(f, inst, "*", "mul", .None), .neg => try airFloatNeg(f, inst), .div_float => try airBinBuiltinCall(f, inst, "div", .None), .div_trunc, .div_exact => try airBinOp(f, inst, "/", "div_trunc", .None), .rem => 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, "%", "rem", .None) else try airBinFloatOp(f, inst, "fmod"); }, .div_floor => try airBinBuiltinCall(f, inst, "div_floor", .None), .mod => try airBinBuiltinCall(f, inst, "mod", .None), .addwrap => try airBinBuiltinCall(f, inst, "addw", .Bits), .subwrap => try airBinBuiltinCall(f, inst, "subw", .Bits), .mulwrap => try airBinBuiltinCall(f, inst, "mulw", .Bits), .add_sat => try airBinBuiltinCall(f, inst, "adds", .Bits), .sub_sat => try airBinBuiltinCall(f, inst, "subs", .Bits), .mul_sat => try airBinBuiltinCall(f, inst, "muls", .Bits), .shl_sat => try airBinBuiltinCall(f, inst, "shls", .Bits), .sqrt, .sin, .cos, .tan, .exp, .exp2, .log, .log2, .log10, .fabs, .floor, .ceil, .round, => |tag| try airUnFloatOp(f, inst, @tagName(tag)), .trunc_float => try airUnFloatOp(f, inst, "trunc"), .mul_add => try airMulAdd(f, inst), .add_with_overflow => try airOverflow(f, inst, "add", .Bits), .sub_with_overflow => try airOverflow(f, inst, "sub", .Bits), .mul_with_overflow => try airOverflow(f, inst, "mul", .Bits), .shl_with_overflow => try airOverflow(f, inst, "shl", .Bits), .min => try airMinMax(f, inst, '<', "fmin"), .max => try airMinMax(f, inst, '>', "fmax"), .slice => try airSlice(f, inst), .cmp_gt => try airCmpOp(f, inst, ">", "gt"), .cmp_gte => try airCmpOp(f, inst, ">=", "ge"), .cmp_lt => try airCmpOp(f, inst, "<", "lt"), .cmp_lte => try airCmpOp(f, inst, "<=", "le"), .cmp_eq => try airEquality(f, inst, "((", "==", "eq"), .cmp_neq => try airEquality(f, inst, "!((", "!=", "ne"), .cmp_vector => return f.fail("TODO: C backend: implement cmp_vector", .{}), .cmp_lt_errors_len => try airCmpLtErrorsLen(f, inst), // bool_and and bool_or are non-short-circuit operations .bool_and, .bit_and => try airBinOp(f, inst, "&", "and", .None), .bool_or, .bit_or => try airBinOp(f, inst, "|", "or", .None), .xor => try airBinOp(f, inst, "^", "xor", .None), .shr, .shr_exact => try airBinBuiltinCall(f, inst, "shr", .None), .shl, => try airBinBuiltinCall(f, inst, "shlw", .Bits), .shl_exact => try airBinOp(f, inst, "<<", "shl", .None), .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, "==", false), .is_non_null => try airIsNull(f, inst, "!=", false), .is_null_ptr => try airIsNull(f, inst, "==", true), .is_non_null_ptr => try airIsNull(f, inst, "!=", true), .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, false), .ret_load => try airRet(f, inst, true), .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 airUnBuiltinCall(f, inst, "clz", .Bits), .ctz => try airUnBuiltinCall(f, inst, "ctz", .Bits), .popcount => try airUnBuiltinCall(f, inst, "popcount", .Bits), .byte_swap => try airUnBuiltinCall(f, inst, "byte_swap", .Bits), .bit_reverse => try airUnBuiltinCall(f, inst, "bit_reverse", .Bits), .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), .addrspace_cast => return f.fail("TODO: C backend: implement addrspace_cast", .{}), .@"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 airFloatCast(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, false, "ptr"), .slice_len => try airSliceField(f, inst, false, "len"), .ptr_slice_len_ptr => try airSliceField(f, inst, true, "len"), .ptr_slice_ptr_ptr => try airSliceField(f, inst, true, "ptr"), .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, false), .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst, true), .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), .save_err_return_trace_index => try airSaveErrReturnTraceIndex(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.writeByte('}'); } fn airSliceField(f: *Function, inst: Air.Inst.Index, is_ptr: bool, field_name: []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(" = "); if (is_ptr) { try writer.writeByte('&'); try f.writeCValueDerefMember(writer, operand, .{ .identifier = field_name }); } else try f.writeCValueMember(writer, operand, .{ .identifier = field_name }); try writer.writeAll(";\n"); return local; } fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const inst_ty = f.air.typeOfIndex(inst); 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)) or !inst_ty.hasRuntimeBitsIgnoreComptime()) return CValue.none; const ptr = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const target = f.object.dg.module.getTarget(); const is_array = lowersToArray(inst_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); const writer = f.object.writer(); if (is_array) { try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); } else try writer.writeAll(" = "); try f.writeCValue(writer, ptr, .Other); try writer.writeByte('['); try f.writeCValue(writer, index, .Other); try writer.writeByte(']'); if (is_array) { try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("))"); } 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, .Other); } try writer.writeAll(")["); try f.writeCValue(writer, index, .Other); try writer.writeAll("];\n"); return local; } fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const inst_ty = f.air.typeOfIndex(inst); 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)) or !inst_ty.hasRuntimeBitsIgnoreComptime()) return CValue.none; const slice = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const target = f.object.dg.module.getTarget(); const is_array = lowersToArray(inst_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); const writer = f.object.writer(); if (is_array) { try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); } else try writer.writeAll(" = "); try f.writeCValue(writer, slice, .Other); try writer.writeAll(".ptr["); try f.writeCValue(writer, index, .Other); try writer.writeByte(']'); if (is_array) { try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("))"); } 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, .Other); try writer.writeAll(".ptr["); try f.writeCValue(writer, index, .Other); try writer.writeAll("];\n"); return local; } fn airArrayElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const inst_ty = f.air.typeOfIndex(inst); if (f.liveness.isUnused(inst) or !inst_ty.hasRuntimeBitsIgnoreComptime()) 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 target = f.object.dg.module.getTarget(); const is_array = lowersToArray(inst_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); const writer = f.object.writer(); if (is_array) { try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); } else try writer.writeAll(" = "); try f.writeCValue(writer, array, .Other); try writer.writeByte('['); try f.writeCValue(writer, index, .Other); try writer.writeByte(']'); if (is_array) { try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("))"); } 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{ .undef = inst_ty }; } 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); const elem_ty = inst_ty.elemType(); if (!elem_ty.hasRuntimeBitsIgnoreComptime()) { return CValue{ .undef = inst_ty }; } // First line: the variable used as data storage. const local = try f.allocLocal(elem_ty, .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 ptr_info = f.air.typeOf(ty_op.operand).ptrInfo().data; const inst_ty = f.air.typeOfIndex(inst); if (!inst_ty.hasRuntimeBitsIgnoreComptime() or !ptr_info.@"volatile" and f.liveness.isUnused(inst)) return CValue.none; const target = f.object.dg.module.getTarget(); const is_array = lowersToArray(inst_ty, target); 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(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, operand, .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("))"); } else if (ptr_info.host_size != 0) { var host_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = ptr_info.host_size * 8, }; const host_ty = Type.initPayload(&host_pl.base); var bit_offset_ty_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = Type.smallestUnsignedBits(host_pl.data - 1), }; const bit_offset_ty = Type.initPayload(&bit_offset_ty_pl.base); var bit_offset_val_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = ptr_info.bit_offset, }; const bit_offset_val = Value.initPayload(&bit_offset_val_pl.base); var field_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = @intCast(u16, inst_ty.bitSize(target)), }; const field_ty = Type.initPayload(&field_pl.base); try writer.writeAll(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeAll(")zig_wrap_"); try f.object.dg.renderTypeForBuiltinFnName(writer, field_ty); try writer.writeAll("(("); try f.renderTypecast(writer, field_ty); try writer.writeAll(")zig_shr_"); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeByte('('); try f.writeCValueDeref(writer, operand); try writer.print(", {})", .{try f.fmtIntLiteral(bit_offset_ty, bit_offset_val)}); try f.object.dg.renderBuiltinInfo(writer, field_ty, .Bits); try writer.writeByte(')'); } else { try writer.writeAll(" = "); try f.writeCValueDeref(writer, operand); } try writer.writeAll(";\n"); return local; } fn airRet(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !CValue { const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const target = f.object.dg.module.getTarget(); const op_ty = f.air.typeOf(un_op); const ret_ty = if (is_ptr) op_ty.childType() else op_ty; var lowered_ret_buf: LowerFnRetTyBuffer = undefined; const lowered_ret_ty = lowerFnRetTy(ret_ty, &lowered_ret_buf, target); if (lowered_ret_ty.hasRuntimeBitsIgnoreComptime()) { var deref = is_ptr; const operand = try f.resolveInst(un_op); const ret_val = if (lowersToArray(ret_ty, target)) ret_val: { const array_local = try f.allocLocal(lowered_ret_ty, .Mut); try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValueMember(writer, array_local, .{ .field = 0 }); try writer.writeAll(", "); if (deref) try f.writeCValueDeref(writer, operand) else try f.writeCValue(writer, operand, .FunctionArgument); deref = false; try writer.writeAll(", sizeof("); try f.renderTypecast(writer, ret_ty); try writer.writeAll("));\n"); break :ret_val array_local; } else operand; try writer.writeAll("return "); if (deref) try f.writeCValueDeref(writer, ret_val) else try f.writeCValue(writer, ret_val, .Other); try writer.writeAll(";\n"); } else if (f.object.dg.decl.ty.fnCallingConvention() != .Naked) { // Not even allowed to return void in a naked function. 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.writeByte(')'); try f.writeCValue(writer, operand, .Other); 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(" = ("); try f.renderTypecast(writer, inst_ty); try writer.writeByte(')'); if (dest_bits >= 8 and std.math.isPowerOfTwo(dest_bits)) { try f.writeCValue(writer, operand, .Other); try writer.writeAll(";\n"); } else switch (dest_int_info.signedness) { .unsigned => { var arena = std.heap.ArenaAllocator.init(f.object.dg.gpa); defer arena.deinit(); const ExpectedContents = union { u: Value.Payload.U64, i: Value.Payload.I64 }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), arena.allocator()); const mask_val = try inst_ty.maxInt(stack.get(), target); try writer.writeByte('('); try f.writeCValue(writer, operand, .Other); try writer.print(" & {x});\n", .{try f.fmtIntLiteral(inst_ty, mask_val)}); }, .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", .{}); var shift_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = c_bits - dest_bits, }; const shift_val = Value.initPayload(&shift_pl.base); try writer.print("((int{d}_t)((uint{0d}_t)", .{c_bits}); try f.writeCValue(writer, operand, .Other); try writer.print(" << {}) >> {0});\n", .{try f.fmtIntLiteral(Type.u8, shift_val)}); }, } 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, .Other); try writer.writeAll(";\n"); return local; } fn airStoreUndefined(f: *Function, lhs_child_ty: Type, dest_ptr: CValue) !CValue { if (f.wantSafety()) { const writer = f.object.writer(); try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr, .FunctionArgument); try writer.print(", {x}, sizeof(", .{try f.fmtIntLiteral(Type.u8, Value.undef)}); try f.renderTypecast(writer, lhs_child_ty); 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 ptr_info = f.air.typeOf(bin_op.lhs).ptrInfo().data; if (!ptr_info.pointee_type.hasRuntimeBitsIgnoreComptime()) return CValue.none; const ptr_val = try f.resolveInst(bin_op.lhs); const src_ty = f.air.typeOf(bin_op.rhs); const src_val = try f.resolveInst(bin_op.rhs); // 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, ptr_info.pointee_type, ptr_val); const target = f.object.dg.module.getTarget(); const writer = f.object.writer(); if (lowersToArray(ptr_info.pointee_type, target)) { // 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). assert(src_ty.eql(ptr_info.pointee_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(src_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, src_val, .Initializer); try writer.writeAll(";\n"); break :blk new_local; } else src_val; try writer.writeAll("memcpy("); try f.writeCValue(writer, ptr_val, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, array_src, .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, src_ty); try writer.writeAll("))"); } else if (ptr_info.host_size != 0) { const host_bits = ptr_info.host_size * 8; var host_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = host_bits }; const host_ty = Type.initPayload(&host_pl.base); var bit_offset_ty_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = Type.smallestUnsignedBits(host_bits - 1), }; const bit_offset_ty = Type.initPayload(&bit_offset_ty_pl.base); var bit_offset_val_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = ptr_info.bit_offset, }; const bit_offset_val = Value.initPayload(&bit_offset_val_pl.base); const src_bits = src_ty.bitSize(target); const ExpectedContents = [BigInt.Managed.default_capacity]BigIntLimb; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), f.object.dg.gpa); var mask = try BigInt.Managed.initCapacity(stack.get(), BigInt.calcTwosCompLimbCount(host_bits)); defer mask.deinit(); try mask.setTwosCompIntLimit(.max, .unsigned, @intCast(usize, src_bits)); try mask.shiftLeft(&mask, ptr_info.bit_offset); try mask.bitNotWrap(&mask, .unsigned, host_bits); var mask_pl = Value.Payload.BigInt{ .base = .{ .tag = .int_big_positive }, .data = mask.limbs[0..mask.len()], }; const mask_val = Value.initPayload(&mask_pl.base); try f.writeCValueDeref(writer, ptr_val); try writer.writeAll(" = zig_or_"); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeAll("(zig_and_"); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeByte('('); try f.writeCValueDeref(writer, ptr_val); try writer.print(", {x}), zig_shl_", .{try f.fmtIntLiteral(host_ty, mask_val)}); try f.object.dg.renderTypeForBuiltinFnName(writer, host_ty); try writer.writeAll("(("); try f.renderTypecast(writer, host_ty); try writer.writeByte(')'); if (src_ty.isPtrAtRuntime()) { try writer.writeByte('('); try f.renderTypecast(writer, Type.usize); try writer.writeByte(')'); } try f.writeCValue(writer, src_val, .Other); try writer.print(", {}))", .{try f.fmtIntLiteral(bit_offset_ty, bit_offset_val)}); } else { try f.writeCValueDeref(writer, ptr_val); try writer.writeAll(" = "); try f.writeCValue(writer, src_val, .Other); } try writer.writeAll(";\n"); return CValue.none; } fn airOverflow(f: *Function, inst: Air.Inst.Index, operation: []const u8, info: BuiltinInfo) !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 w = f.object.writer(); const local = try f.allocLocal(inst_ty, .Mut); try w.writeAll(";\n"); try f.writeCValueMember(w, local, .{ .field = 1 }); try w.writeAll(" = zig_"); try w.writeAll(operation); try w.writeAll("o_"); try f.object.dg.renderTypeForBuiltinFnName(w, scalar_ty); try w.writeAll("(&"); try f.writeCValueMember(w, local, .{ .field = 0 }); try w.writeAll(", "); try f.writeCValue(w, lhs, .FunctionArgument); try w.writeAll(", "); try f.writeCValue(w, rhs, .FunctionArgument); try f.object.dg.renderBuiltinInfo(w, scalar_ty, info); try w.writeAll(");\n"); return local; } 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); const target = f.object.dg.module.getTarget(); if (inst_ty.bitSize(target) > 64) {} try writer.writeAll(" = "); try writer.writeByte(if (inst_ty.tag() == .bool) '!' else '~'); try f.writeCValue(writer, op, .Other); try writer.writeAll(";\n"); return local; } fn airBinOp( f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: []const u8, info: BuiltinInfo, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const operand_ty = f.air.typeOf(bin_op.lhs); const target = f.object.dg.module.getTarget(); if ((operand_ty.isInt() and operand_ty.bitSize(target) > 64) or operand_ty.isRuntimeFloat()) return try airBinBuiltinCall(f, inst, operation, info); const inst_ty = f.air.typeOfIndex(inst); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, lhs, .Other); try writer.writeByte(' '); try writer.writeAll(operator); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(";\n"); return local; } fn airCmpOp(f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const operand_ty = f.air.typeOf(bin_op.lhs); const target = f.object.dg.module.getTarget(); if (operand_ty.isInt() and operand_ty.bitSize(target) > 64) return try airCmpBuiltinCall(f, inst, operator, "cmp"); if (operand_ty.isRuntimeFloat()) return try airCmpBuiltinCall(f, inst, operator, operation); const inst_ty = f.air.typeOfIndex(inst); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, lhs, .Other); try writer.writeByte(' '); try writer.writeAll(operator); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(";\n"); return local; } fn airEquality( f: *Function, inst: Air.Inst.Index, negate_prefix: []const u8, operator: []const u8, operation: []const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const operand_ty = f.air.typeOf(bin_op.lhs); const target = f.object.dg.module.getTarget(); if (operand_ty.isInt() and operand_ty.bitSize(target) > 64) return try airCmpBuiltinCall(f, inst, operator, "cmp"); if (operand_ty.isRuntimeFloat()) return try airCmpBuiltinCall(f, inst, operator, operation); 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(" = "); if (operand_ty.zigTypeTag() == .Optional and !operand_ty.isPtrLikeOptional()) { // (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, .Other); try writer.writeAll(".is_null && "); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(".is_null) || ("); try f.writeCValue(writer, lhs, .Other); try writer.writeAll(".payload == "); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(".payload && "); try f.writeCValue(writer, lhs, .Other); try writer.writeAll(".is_null == "); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(".is_null));\n"); return local; } try f.writeCValue(writer, lhs, .Other); try writer.writeByte(' '); try writer.writeAll(operator); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(";\n"); return local; } fn airCmpLtErrorsLen(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 inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, operand, .Other); try writer.print(" < sizeof({ }) / sizeof(*{0 });\n", .{fmtIdent("zig_errorName")}); return local; } fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: 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, .Other); try writer.writeAll(") "); try writer.writeByte(operator); try writer.writeAll(" ("); try f.writeCValue(writer, rhs, .Other); try writer.writeAll("*sizeof("); try f.renderTypecast(writer, elem_ty); try writer.writeAll(")));\n"); return local; } fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: u8, operation: []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(); if (inst_ty.isInt() and inst_ty.bitSize(target) > 64) return try airBinBuiltinCall(f, inst, operation[1..], .None); if (inst_ty.isRuntimeFloat()) return try airBinFloatOp(f, inst, operation); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); // (lhs <> rhs) ? lhs : rhs try writer.writeAll(" = ("); try f.writeCValue(writer, lhs, .Other); try writer.writeByte(' '); try writer.writeByte(operator); try writer.writeByte(' '); try f.writeCValue(writer, rhs, .Other); try writer.writeAll(") ? "); try f.writeCValue(writer, lhs, .Other); try writer.writeAll(" : "); try f.writeCValue(writer, rhs, .Other); 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(" = {("); var buf: Type.SlicePtrFieldTypeBuffer = undefined; try f.renderTypecast(writer, inst_ty.slicePtrFieldType(&buf)); try writer.writeByte(')'); try f.writeCValue(writer, ptr, .Other); try writer.writeAll(", "); try f.writeCValue(writer, len, .Initializer); try writer.writeAll("};\n"); return local; } fn airCall( f: *Function, inst: Air.Inst.Index, modifier: std.builtin.CallOptions.Modifier, ) !CValue { // Not even allowed to call panic in a naked function. if (f.object.dg.decl.ty.fnCallingConvention() == .Naked) return .none; 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 target = f.object.dg.module.getTarget(); const ret_ty = fn_ty.fnReturnType(); var lowered_ret_buf: LowerFnRetTyBuffer = undefined; const lowered_ret_ty = lowerFnRetTy(ret_ty, &lowered_ret_buf, target); const result_local: CValue = if (!lowered_ret_ty.hasRuntimeBitsIgnoreComptime()) .none else if (f.liveness.isUnused(inst)) r: { try writer.writeByte('('); try f.renderTypecast(writer, Type.void); try writer.writeByte(')'); break :r .none; } else r: { const local = try f.allocLocal(lowered_ret_ty, .Const); try writer.writeAll(" = "); break :r local; }; var is_extern = false; var name: [*:0]const u8 = ""; 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 => blk: { is_extern = true; break :blk 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, }; }; name = f.object.dg.module.declPtr(fn_decl).name; 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, .Other); } try writer.writeByte('('); 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 ((is_extern or std.mem.eql(u8, std.mem.span(name), "main")) and ty.isCPtr() and ty.childType().tag() == .u8) { // Corresponds with hack in renderType .Pointer case. try writer.writeAll("(char"); if (ty.isConstPtr()) try writer.writeAll(" const"); if (ty.isVolatilePtr()) try writer.writeAll(" volatile"); try writer.writeAll(" *)"); } try f.writeCValue(writer, try f.resolveInst(arg), .FunctionArgument); args_written += 1; } try writer.writeAll(");\n"); if (result_local == .none or !lowersToArray(ret_ty, target)) return result_local; const array_local = try f.allocLocal(ret_ty, .Mut); try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, array_local, .FunctionArgument); try writer.writeAll(", "); try f.writeCValueMember(writer, result_local, .{ .field = 0 }); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, ret_ty); try writer.writeAll("));\n"); return array_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()) { try writer.writeAll("if ("); if (!payload_has_bits) { if (operand_is_ptr) try f.writeCValueDeref(writer, err_union) else try f.writeCValue(writer, err_union, .Other); } else { if (operand_is_ptr or isByRef(err_union_ty)) try f.writeCValueDerefMember(writer, err_union, .{ .identifier = "error" }) else try f.writeCValueMember(writer, err_union, .{ .identifier = "error" }); } try writer.writeByte(')'); 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 target = f.object.dg.module.getTarget(); const is_array = lowersToArray(payload_ty, target); const local = try f.allocLocal(result_ty, if (is_array) .Mut else .Const); if (is_array) { try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); try f.writeCValueMember(writer, err_union, .{ .identifier = "payload" }); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, payload_ty); try writer.writeAll("));\n"); } else { try writer.writeAll(" = "); if (operand_is_ptr or isByRef(payload_ty)) { try writer.writeByte('&'); try f.writeCValueDerefMember(writer, err_union, .{ .identifier = "payload" }); } else try f.writeCValueMember(writer, err_union, .{ .identifier = "payload" }); try writer.writeAll(";\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); const operand_ty = f.air.typeOf(branch.operand); const target = f.object.dg.module.getTarget(); if (lowersToArray(operand_ty, target)) { try writer.writeAll("memcpy("); try f.writeCValue(writer, result, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, operand, .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, operand_ty); try writer.writeAll("))"); } else { try f.writeCValue(writer, result, .Other); try writer.writeAll(" = "); try f.writeCValue(writer, operand, .Other); } try writer.writeAll(";\n"); } try writer.print("goto zig_block_{d};\n", .{block.block_id}); return CValue.none; } fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { const inst_ty = f.air.typeOfIndex(inst); // No IgnoreComptime until Sema stops giving us garbage Air. // https://github.com/ziglang/zig/issues/13410 if (f.liveness.isUnused(inst) or !inst_ty.hasRuntimeBits()) 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(); 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.writeByte(')'); try f.writeCValue(writer, operand, .Other); 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, .Other); try writer.writeAll(", &"); try f.writeCValue(writer, operand, .Other); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("));\n"); return local; } fn airBreakpoint(writer: anytype) !CValue { try 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 writer = f.object.writer(); const local = try f.allocLocal(Type.usize, .Const); try writer.writeAll(" = ("); try f.renderTypecast(writer, Type.usize); try 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 writer = f.object.writer(); const local = try f.allocLocal(Type.usize, .Const); try writer.writeAll(" = ("); try f.renderTypecast(writer, Type.usize); try 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 { // Not even allowed to call unreachable in a naked function. if (f.object.dg.decl.ty.fnCallingConvention() == .Naked) return .none; 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]; const writer = f.object.writer(); try writer.writeAll("while ("); try f.object.dg.renderValue(writer, Type.bool, Value.@"true", .Other); try writer.writeAll(") "); try genBody(f, body); try writer.writeByte('\n'); 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, .Other); 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 ("); if (condition_ty.tag() == .bool) { try writer.writeByte('('); try f.renderTypecast(writer, Type.u1); try writer.writeByte(')'); } try f.writeCValue(writer, condition, .Other); 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 asmInputNeedsLocal(constraint: []const u8, value: CValue) bool { return switch (constraint[0]) { '{' => true, 'i', 'r' => false, else => value == .constant, }; } 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; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = if (inst_ty.hasRuntimeBitsIgnoreComptime()) local: { const local = try f.allocLocal(inst_ty, .Mut); if (f.wantSafety()) { try writer.writeAll(" = "); try f.writeCValue(writer, .{ .undef = inst_ty }, .Initializer); } try writer.writeAll(";\n"); break :local local; } else .none; const locals_begin = f.next_local_index; const constraints_extra_begin = extra_i; for (outputs) |output| { const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(extra_bytes, 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; if (constraint.len < 2 or constraint[0] != '=' or (constraint[1] == '{' and constraint[constraint.len - 1] != '}')) { return f.fail("CBE: constraint not supported: '{s}'", .{constraint}); } const is_reg = constraint[1] == '{'; if (is_reg) { const output_ty = if (output == .none) inst_ty else f.air.typeOf(output).childType(); try writer.writeAll("register "); _ = try f.allocLocal(output_ty, .Mut); try writer.writeAll(" __asm(\""); try writer.writeAll(constraint["={".len .. constraint.len - "}".len]); try writer.writeAll("\")"); if (f.wantSafety()) { try writer.writeAll(" = "); try f.writeCValue(writer, .{ .undef = output_ty }, .Initializer); } try writer.writeAll(";\n"); } } for (inputs) |input| { const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(extra_bytes, 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; if (constraint.len < 1 or std.mem.indexOfScalar(u8, "=+&%", constraint[0]) != null or (constraint[0] == '{' and constraint[constraint.len - 1] != '}')) { return f.fail("CBE: constraint not supported: '{s}'", .{constraint}); } const is_reg = constraint[0] == '{'; const input_val = try f.resolveInst(input); if (asmInputNeedsLocal(constraint, input_val)) { const input_ty = f.air.typeOf(input); if (is_reg) try writer.writeAll("register "); _ = try f.allocLocal(input_ty, .Const); if (is_reg) { try writer.writeAll(" __asm(\""); try writer.writeAll(constraint["{".len .. constraint.len - "}".len]); try writer.writeAll("\")"); } try writer.writeAll(" = "); try f.writeCValue(writer, input_val, .Initializer); 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; } } const asm_source = std.mem.sliceAsBytes(f.air.extra[extra_i..])[0..extra.data.source_len]; try writer.writeAll("__asm"); if (is_volatile) try writer.writeAll(" volatile"); try writer.print("({s}", .{fmtStringLiteral(asm_source)}); extra_i = constraints_extra_begin; var locals_index = locals_begin; try writer.writeByte(':'); for (outputs) |output, index| { const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(extra_bytes, 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; if (index > 0) try writer.writeByte(','); try writer.writeByte(' '); if (!std.mem.eql(u8, name, "_")) try writer.print("[{s}]", .{name}); const is_reg = constraint[1] == '{'; try writer.print("{s}(", .{fmtStringLiteral(if (is_reg) "=r" else constraint)}); if (is_reg) { try f.writeCValue(writer, .{ .local = locals_index }, .Other); locals_index += 1; } else { try f.writeCValueDeref(writer, try f.resolveInst(output)); } try writer.writeByte(')'); } try writer.writeByte(':'); for (inputs) |input, index| { const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(extra_bytes, 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; if (index > 0) try writer.writeByte(','); try writer.writeByte(' '); if (!std.mem.eql(u8, name, "_")) try writer.print("[{s}]", .{name}); const is_reg = constraint[0] == '{'; const input_val = try f.resolveInst(input); try writer.print("{s}(", .{fmtStringLiteral(if (is_reg) "r" else constraint)}); try f.writeCValue(writer, if (asmInputNeedsLocal(constraint, input_val)) local: { const input_local = CValue{ .local = locals_index }; locals_index += 1; break :local input_local; } else input_val, .Other); try writer.writeByte(')'); } try writer.writeByte(':'); { 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; if (clobber.len == 0) continue; if (clobber_i > 0) try writer.writeByte(','); try writer.print(" {s}", .{fmtStringLiteral(clobber)}); } } try writer.writeAll(");\n"); extra_i = constraints_extra_begin; locals_index = locals_begin; for (outputs) |output| { const extra_bytes = std.mem.sliceAsBytes(f.air.extra[extra_i..]); const constraint = std.mem.sliceTo(extra_bytes, 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; const is_reg = constraint[1] == '{'; if (is_reg) { try f.writeCValueDeref(writer, if (output == .none) CValue{ .local_ref = local.local } else try f.resolveInst(output)); try writer.writeAll(" = "); try f.writeCValue(writer, .{ .local = locals_index }, .Other); locals_index += 1; try writer.writeAll(";\n"); } } return local; } fn airIsNull( f: *Function, inst: Air.Inst.Index, operator: []const u8, is_ptr: bool, ) !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 if (is_ptr) f.writeCValueDeref(writer, operand) else f.writeCValue(writer, operand, .Other); const operand_ty = f.air.typeOf(un_op); const optional_ty = if (is_ptr) operand_ty.childType() else operand_ty; var payload_buf: Type.Payload.ElemType = undefined; const payload_ty = optional_ty.optionalChild(&payload_buf); var slice_ptr_buf: Type.SlicePtrFieldTypeBuffer = undefined; const rhs = if (!payload_ty.hasRuntimeBitsIgnoreComptime()) TypedValue{ .ty = Type.bool, .val = Value.@"true" } else if (optional_ty.isPtrLikeOptional()) // operand is a regular pointer, test `operand !=/== NULL` TypedValue{ .ty = optional_ty, .val = Value.@"null" } else if (payload_ty.zigTypeTag() == .ErrorSet) TypedValue{ .ty = payload_ty, .val = Value.zero } else if (payload_ty.isSlice() and optional_ty.optionalReprIsPayload()) rhs: { try writer.writeAll(".ptr"); const slice_ptr_ty = payload_ty.slicePtrFieldType(&slice_ptr_buf); break :rhs TypedValue{ .ty = slice_ptr_ty, .val = Value.@"null" }; } else rhs: { try writer.writeAll(".is_null"); break :rhs TypedValue{ .ty = Type.bool, .val = Value.@"true" }; }; try writer.writeByte(' '); try writer.writeAll(operator); try writer.writeByte(' '); try f.object.dg.renderValue(writer, rhs.ty, rhs.val, .Other); try writer.writeAll(";\n"); 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 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 target = f.object.dg.module.getTarget(); const is_array = lowersToArray(inst_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); const writer = f.object.writer(); if (is_array) { try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); } else try writer.writeAll(" = "); try f.writeCValueMember(writer, operand, .{ .identifier = "payload" }); if (is_array) { try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("))"); } try writer.writeAll(";\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(); const inst_ty = f.air.typeOfIndex(inst); if (!inst_ty.childType().hasRuntimeBitsIgnoreComptime()) { return CValue{ .undef = inst_ty }; } 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 local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = &"); try f.writeCValueDerefMember(writer, operand, .{ .identifier = "payload" }); try writer.writeAll(";\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 = "); try f.object.dg.renderValue(writer, Type.bool, Value.@"false", .Initializer); try writer.writeAll(";\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 { 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.FieldParentPtr, ty_pl.payload).data; const struct_ptr_ty = f.air.typeOfIndex(inst); const field_ptr_ty = f.air.typeOf(extra.field_ptr); const field_ptr_val = try f.resolveInst(extra.field_ptr); const target = f.object.dg.module.getTarget(); const struct_ty = struct_ptr_ty.childType(); const field_offset = struct_ty.structFieldOffset(extra.field_index, target); var field_offset_pl = Value.Payload.I64{ .base = .{ .tag = .int_i64 }, .data = -@intCast(i64, field_offset), }; const field_offset_val = Value.initPayload(&field_offset_pl.base); var u8_ptr_pl = field_ptr_ty.ptrInfo(); u8_ptr_pl.data.pointee_type = Type.u8; const u8_ptr_ty = Type.initPayload(&u8_ptr_pl.base); const writer = f.object.writer(); const local = try f.allocLocal(struct_ptr_ty, .Const); try writer.writeAll(" = ("); try f.renderTypecast(writer, struct_ptr_ty); try writer.writeAll(")&(("); try f.renderTypecast(writer, u8_ptr_ty); try writer.writeByte(')'); try f.writeCValue(writer, field_ptr_val, .Other); try writer.print(")[{}];\n", .{try f.fmtIntLiteral(Type.isize, field_offset_val)}); return local; } fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue { const writer = f.object.writer(); const field_ptr_ty = f.air.typeOfIndex(inst); const field_ptr_info = field_ptr_ty.ptrInfo(); const struct_ty = struct_ptr_ty.elemType(); const field_ty = struct_ty.structFieldType(index); // Ensure complete type definition is visible before accessing fields. try f.renderType(std.io.null_writer, struct_ty); const local = try f.allocLocal(field_ptr_ty, .Const); try writer.writeAll(" = ("); try f.renderTypecast(writer, field_ptr_ty); try writer.writeByte(')'); const extra_name: CValue = switch (struct_ty.tag()) { .union_tagged, .union_safety_tagged => .{ .identifier = "payload" }, else => .none, }; const field_name: CValue = switch (struct_ty.tag()) { .@"struct" => switch (struct_ty.containerLayout()) { .Auto, .Extern => CValue{ .identifier = struct_ty.structFieldName(index) }, .Packed => if (field_ptr_info.data.host_size == 0) { const target = f.object.dg.module.getTarget(); const byte_offset = struct_ty.packedStructFieldByteOffset(index, target); var byte_offset_pl = Value.Payload.U64{ .base = .{ .tag = .int_u64 }, .data = byte_offset, }; const byte_offset_val = Value.initPayload(&byte_offset_pl.base); var u8_ptr_pl = field_ptr_info; u8_ptr_pl.data.pointee_type = Type.u8; const u8_ptr_ty = Type.initPayload(&u8_ptr_pl.base); try writer.writeAll("&(("); try f.renderTypecast(writer, u8_ptr_ty); try writer.writeByte(')'); try f.writeCValue(writer, struct_ptr, .Other); try writer.print(")[{}];\n", .{try f.fmtIntLiteral(Type.usize, byte_offset_val)}); return local; } else @as(CValue, CValue.none), // this @as is needed because of a stage1 bug }, .@"union", .union_safety_tagged, .union_tagged => .{ .identifier = struct_ty.unionFields().keys()[index], }, .tuple, .anon_struct => field_name: { const tuple = struct_ty.tupleFields(); if (tuple.values[index].tag() != .unreachable_value) return CValue.none; var id: usize = 0; for (tuple.values[0..index]) |value| id += @boolToInt(value.tag() == .unreachable_value); break :field_name .{ .field = id }; }, else => unreachable, }; if (field_ty.hasRuntimeBitsIgnoreComptime()) { try writer.writeByte('&'); if (extra_name != .none) { try f.writeCValueDerefMember(writer, struct_ptr, extra_name); if (field_name != .none) { try writer.writeByte('.'); try f.writeCValue(writer, field_name, .Other); } } else if (field_name != .none) try f.writeCValueDerefMember(writer, struct_ptr, field_name) else try f.writeCValueDeref(writer, struct_ptr); } else try f.writeCValue(writer, struct_ptr, .Other); try writer.writeAll(";\n"); return local; } fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); if (!inst_ty.hasRuntimeBitsIgnoreComptime()) 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 target = f.object.dg.module.getTarget(); const struct_byval = try f.resolveInst(extra.struct_operand); const struct_ty = f.air.typeOf(extra.struct_operand); const writer = f.object.writer(); // Ensure complete type definition is visible before accessing fields. try f.renderType(std.io.null_writer, struct_ty); const extra_name: CValue = switch (struct_ty.tag()) { .union_tagged, .union_safety_tagged => .{ .identifier = "payload" }, else => .none, }; const field_name: CValue = switch (struct_ty.tag()) { .@"struct" => switch (struct_ty.containerLayout()) { .Auto, .Extern => .{ .identifier = struct_ty.structFieldName(extra.field_index) }, .Packed => { const struct_obj = struct_ty.castTag(.@"struct").?.data; const int_info = struct_ty.intInfo(target); var bit_offset_ty_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = Type.smallestUnsignedBits(int_info.bits - 1), }; const bit_offset_ty = Type.initPayload(&bit_offset_ty_pl.base); var bit_offset_val_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = struct_obj.packedFieldBitOffset(target, extra.field_index), }; const bit_offset_val = Value.initPayload(&bit_offset_val_pl.base); const field_int_signedness = if (inst_ty.isAbiInt()) inst_ty.intInfo(target).signedness else .unsigned; var field_int_pl = Type.Payload.Bits{ .base = .{ .tag = switch (field_int_signedness) { .unsigned => .int_unsigned, .signed => .int_signed, } }, .data = @intCast(u16, inst_ty.bitSize(target)), }; const field_int_ty = Type.initPayload(&field_int_pl.base); const temp_local = try f.allocLocal(field_int_ty, .Const); try writer.writeAll(" = zig_wrap_"); try f.object.dg.renderTypeForBuiltinFnName(writer, field_int_ty); try writer.writeAll("(("); try f.renderTypecast(writer, field_int_ty); try writer.writeAll(")zig_shr_"); try f.object.dg.renderTypeForBuiltinFnName(writer, struct_ty); try writer.writeByte('('); try f.writeCValue(writer, struct_byval, .Other); try writer.writeAll(", "); try f.object.dg.renderValue(writer, bit_offset_ty, bit_offset_val, .FunctionArgument); try writer.writeByte(')'); try f.object.dg.renderBuiltinInfo(writer, field_int_ty, .Bits); try writer.writeAll(");\n"); if (inst_ty.eql(field_int_ty, f.object.dg.module)) return temp_local; const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, .{ .local_ref = local.local }, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, .{ .local_ref = temp_local.local }, .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("));\n"); return local; }, }, .@"union", .union_safety_tagged, .union_tagged => .{ .identifier = 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; var id: usize = 0; for (tuple.values[0..extra.field_index]) |value| id += @boolToInt(value.tag() == .unreachable_value); break :blk .{ .field = id }; }, else => unreachable, }; const is_array = lowersToArray(inst_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); if (is_array) { try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); } else try writer.writeAll(" = "); if (extra_name != .none) { try f.writeCValueMember(writer, struct_byval, extra_name); try writer.writeByte('.'); try f.writeCValue(writer, field_name, .Other); } else try f.writeCValueMember(writer, struct_byval, field_name); if (is_array) { try writer.writeAll(", sizeof("); try f.renderTypecast(writer, inst_ty); try writer.writeAll("))"); } try writer.writeAll(";\n"); 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 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; const error_ty = error_union_ty.errorUnionSet(); const payload_ty = error_union_ty.errorUnionPayload(); if (!payload_ty.hasRuntimeBits()) return operand; const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); if (!error_ty.errorSetIsEmpty()) if (operand_is_ptr) try f.writeCValueDerefMember(writer, operand, .{ .identifier = "error" }) else try f.writeCValueMember(writer, operand, .{ .identifier = "error" }) else try f.object.dg.renderValue(writer, error_ty, Value.zero, .Initializer); try writer.writeAll(";\n"); return local; } fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, is_ptr: bool) !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 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 writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); if (is_ptr) try writer.writeByte('&'); if (operand_is_ptr) try f.writeCValueDerefMember(writer, operand, .{ .identifier = "payload" }) else try f.writeCValueMember(writer, operand, .{ .identifier = "payload" }); try writer.writeAll(";\n"); return local; } fn airWrapOptional(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 payload = try f.resolveInst(ty_op.operand); if (inst_ty.optionalReprIsPayload()) return payload; const payload_ty = f.air.typeOf(ty_op.operand); const target = f.object.dg.module.getTarget(); const is_array = lowersToArray(payload_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); const writer = f.object.writer(); try writer.writeAll(" = { .payload = "); try f.writeCValue(writer, if (is_array) CValue{ .undef = payload_ty } else payload, .Initializer); try writer.writeAll(", .is_null = "); try f.object.dg.renderValue(writer, Type.bool, Value.@"false", .Initializer); try writer.writeAll(" };\n"); if (is_array) { try writer.writeAll("memcpy("); try f.writeCValueMember(writer, local, .{ .identifier = "payload" }); try writer.writeAll(", "); try f.writeCValue(writer, payload, .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, payload_ty); 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 error_union_ty = f.air.typeOfIndex(inst); const payload_ty = error_union_ty.errorUnionPayload(); if (!payload_ty.hasRuntimeBits()) return operand; const local = try f.allocLocal(error_union_ty, .Const); try writer.writeAll(" = { .payload = "); try f.writeCValue(writer, .{ .undef = payload_ty }, .Initializer); try writer.writeAll(", .error = "); try f.writeCValue(writer, operand, .Initializer); 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 airSaveErrReturnTraceIndex(f: *Function, inst: Air.Inst.Index) !CValue { _ = inst; return f.fail("TODO: C backend: implement airSaveErrReturnTraceIndex", .{}); } fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const error_ty = inst_ty.errorUnionSet(); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const payload_ty = inst_ty.errorUnionPayload(); const payload = try f.resolveInst(ty_op.operand); const target = f.object.dg.module.getTarget(); const is_array = lowersToArray(payload_ty, target); const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const); const writer = f.object.writer(); try writer.writeAll(" = { .payload = "); try f.writeCValue(writer, if (is_array) CValue{ .undef = payload_ty } else payload, .Initializer); try writer.writeAll(", .error = "); try f.object.dg.renderValue(writer, error_ty, Value.zero, .Initializer); try writer.writeAll(" };\n"); if (is_array) { try writer.writeAll("memcpy("); try f.writeCValueMember(writer, local, .{ .identifier = "payload" }); try writer.writeAll(", "); try f.writeCValue(writer, payload, .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, payload_ty); try writer.writeAll("));\n"); } return local; } fn airIsErr(f: *Function, inst: Air.Inst.Index, is_ptr: bool, operator: []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()) if (payload_ty.hasRuntimeBits()) if (is_ptr) try f.writeCValueDerefMember(writer, operand, .{ .identifier = "error" }) else try f.writeCValueMember(writer, operand, .{ .identifier = "error" }) else try f.writeCValue(writer, operand, .Other) else try f.object.dg.renderValue(writer, error_ty, Value.zero, .Other); try writer.writeByte(' '); try writer.writeAll(operator); try writer.writeByte(' '); try f.object.dg.renderValue(writer, error_ty, Value.zero, .Other); try writer.writeAll(";\n"); 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 == .undef) { // Unfortunately, C does not support any equivalent to // &(*(void *)p)[0], although LLVM does via GetElementPtr var buf: Type.SlicePtrFieldTypeBuffer = undefined; try f.writeCValue(writer, CValue{ .undef = inst_ty.slicePtrFieldType(&buf) }, .Initializer); } else { try writer.writeAll("&("); try f.writeCValueDeref(writer, operand); try writer.print(")[{}]", .{try f.fmtIntLiteral(Type.usize, Value.zero)}); } var len_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = array_len }; const len_val = Value.initPayload(&len_pl.base); try writer.print(", .len = {} }};\n", .{try f.fmtIntLiteral(Type.usize, len_val)}); return local; } fn airFloatCast(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 operand_ty = f.air.typeOf(ty_op.operand); const target = f.object.dg.module.getTarget(); const operation = if (inst_ty.isRuntimeFloat() and operand_ty.isRuntimeFloat()) if (inst_ty.floatBits(target) < operand_ty.floatBits(target)) "trunc" else "extend" else if (inst_ty.isInt() and operand_ty.isRuntimeFloat()) if (inst_ty.isSignedInt()) "fix" else "fixuns" else if (inst_ty.isRuntimeFloat() and operand_ty.isInt()) if (operand_ty.isSignedInt()) "float" else "floatun" else unreachable; const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = "); if (inst_ty.isInt() and operand_ty.isRuntimeFloat()) { try writer.writeAll("zig_wrap_"); try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); try writer.writeByte('('); } try writer.writeAll("__"); try writer.writeAll(operation); try writer.writeAll(compilerRtAbbrev(operand_ty, target)); try writer.writeAll(compilerRtAbbrev(inst_ty, target)); if (inst_ty.isRuntimeFloat() and operand_ty.isRuntimeFloat()) try writer.writeByte('2'); try writer.writeByte('('); try f.writeCValue(writer, operand, .FunctionArgument); try writer.writeByte(')'); if (inst_ty.isInt() and operand_ty.isRuntimeFloat()) { try f.object.dg.renderBuiltinInfo(writer, inst_ty, .Bits); try writer.writeByte(')'); } 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.writeByte(')'); try f.writeCValue(writer, operand, .Other); try writer.writeAll(";\n"); return local; } fn airUnBuiltinCall( f: *Function, inst: Air.Inst.Index, operation: []const u8, info: BuiltinInfo, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const operand = f.air.instructions.items(.data)[inst].ty_op.operand; const operand_ty = f.air.typeOf(operand); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_"); try writer.writeAll(operation); try writer.writeByte('_'); try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); try writer.writeByte('('); try f.writeCValue(writer, try f.resolveInst(operand), .FunctionArgument); try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); try writer.writeAll(");\n"); return local; } fn airBinBuiltinCall( f: *Function, inst: Air.Inst.Index, operation: []const u8, info: BuiltinInfo, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const bin_op = f.air.instructions.items(.data)[inst].bin_op; const operand_ty = f.air.typeOf(bin_op.lhs); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_"); try writer.writeAll(operation); try writer.writeByte('_'); try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); try writer.writeByte('('); try f.writeCValue(writer, try f.resolveInst(bin_op.lhs), .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, try f.resolveInst(bin_op.rhs), .FunctionArgument); try f.object.dg.renderBuiltinInfo(writer, operand_ty, info); try writer.writeAll(");\n"); return local; } fn airCmpBuiltinCall( f: *Function, inst: Air.Inst.Index, operator: []const u8, operation: []const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const bin_op = f.air.instructions.items(.data)[inst].bin_op; const operand_ty = f.air.typeOf(bin_op.lhs); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_"); try writer.writeAll(operation); try writer.writeByte('_'); try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); try writer.writeByte('('); try f.writeCValue(writer, try f.resolveInst(bin_op.lhs), .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, try f.resolveInst(bin_op.rhs), .FunctionArgument); try writer.print(") {s} {};\n", .{ operator, try f.fmtIntLiteral(Type.initTag(.i8), Value.zero) }); 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 is_struct = !inst_ty.isPtrLikeOptional(); const ptr_ty = f.air.typeOf(extra.ptr); 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 writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(" = "); if (is_struct) try writer.writeAll("{ .payload = "); try f.writeCValue(writer, expected_value, .Initializer); if (is_struct) { try writer.writeAll(", .is_null = "); try f.object.dg.renderValue(writer, Type.bool, Value.@"false", .Initializer); try writer.writeAll(" }"); } try writer.writeAll(";\n"); if (is_struct) { try f.writeCValue(writer, local, .Other); try writer.writeAll(".is_null = "); } else { try writer.writeAll("if ("); } try writer.print("zig_cmpxchg_{s}((zig_atomic(", .{flavor}); try f.renderTypecast(writer, ptr_ty.elemType()); try writer.writeByte(')'); if (ptr_ty.isVolatilePtr()) try writer.writeAll(" volatile"); try writer.writeAll(" *)"); try f.writeCValue(writer, ptr, .Other); try writer.writeAll(", "); if (is_struct) try f.writeCValueMember(writer, local, .{ .identifier = "payload" }) else try f.writeCValue(writer, local, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, new_value, .FunctionArgument); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.successOrder()); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.failureOrder()); try writer.writeByte(')'); if (is_struct) { try writer.writeAll(";\n"); } else { try writer.writeAll(") {\n"); f.object.indent_writer.pushIndent(); try f.writeCValue(writer, local, .Other); try writer.writeAll(" = NULL;\n"); f.object.indent_writer.popIndent(); 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_ty = f.air.typeOf(pl_op.operand); 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())}); switch (extra.op()) { else => { try writer.writeAll("zig_atomic("); try f.renderTypecast(writer, ptr_ty.elemType()); try writer.writeByte(')'); }, .Nand, .Min, .Max => { // These are missing from stdatomic.h, so no atomic types for now. try f.renderTypecast(writer, ptr_ty.elemType()); }, } if (ptr_ty.isVolatilePtr()) try writer.writeAll(" volatile"); try writer.writeAll(" *)"); try f.writeCValue(writer, ptr, .Other); try writer.writeAll(", "); try f.writeCValue(writer, operand, .FunctionArgument); 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((zig_atomic("); try f.renderTypecast(writer, ptr_ty.elemType()); try writer.writeByte(')'); if (ptr_ty.isVolatilePtr()) try writer.writeAll(" volatile"); try writer.writeAll(" *)"); try f.writeCValue(writer, ptr, .Other); 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_ty = f.air.typeOf(bin_op.lhs); const ptr = try f.resolveInst(bin_op.lhs); const element = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); try writer.writeAll("zig_atomic_store((zig_atomic("); try f.renderTypecast(writer, ptr_ty.elemType()); try writer.writeByte(')'); if (ptr_ty.isVolatilePtr()) try writer.writeAll(" volatile"); try writer.writeAll(" *)"); try f.writeCValue(writer, ptr, .Other); try writer.writeAll(", "); try f.writeCValue(writer, element, .FunctionArgument); try writer.print(", {s});\n", .{order}); return CValue.none; } 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_ty = f.air.typeOf(pl_op.operand); 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(); if (dest_ty.isVolatilePtr()) { var u8_ptr_pl = dest_ty.ptrInfo(); u8_ptr_pl.data.pointee_type = Type.u8; const u8_ptr_ty = Type.initPayload(&u8_ptr_pl.base); try writer.writeAll("for ("); const index = try f.allocLocal(Type.usize, .Mut); try writer.writeAll(" = "); try f.object.dg.renderValue(writer, Type.usize, Value.zero, .Initializer); try writer.writeAll("; "); try f.writeCValue(writer, index, .Other); try writer.writeAll(" != "); try f.writeCValue(writer, len, .Other); try writer.writeAll("; "); try f.writeCValue(writer, index, .Other); try writer.writeAll(" += "); try f.object.dg.renderValue(writer, Type.usize, Value.one, .Other); try writer.writeAll(") (("); try f.renderTypecast(writer, u8_ptr_ty); try writer.writeByte(')'); try f.writeCValue(writer, dest_ptr, .FunctionArgument); try writer.writeAll(")["); try f.writeCValue(writer, index, .Other); try writer.writeAll("] = "); try f.writeCValue(writer, value, .FunctionArgument); try writer.writeAll(";\n"); return CValue.none; } try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, value, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, len, .FunctionArgument); 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, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, src_ptr, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, len, .FunctionArgument); 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 writer.writeByte('('); try f.writeCValue(writer, union_ptr, .Other); try writer.writeAll(")->tag = "); try f.writeCValue(writer, new_tag, .Other); 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, .Other); 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 inst_ty = f.air.typeOfIndex(inst); const enum_ty = f.air.typeOf(un_op); const operand = try f.resolveInst(un_op); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{try f.object.dg.getTagNameFn(enum_ty)}); try f.writeCValue(writer, operand, .Other); 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(" = zig_errorName["); try f.writeCValue(writer, operand, .Other); try writer.writeAll("];\n"); return local; } 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 len = @intCast(usize, inst_ty.arrayLen()); const elements = @ptrCast([]const Air.Inst.Ref, f.air.extra[ty_pl.payload..][0..len]); const target = f.object.dg.module.getTarget(); const mutability: Mutability = for (elements) |element| { if (lowersToArray(f.air.typeOf(element), target)) break .Mut; } else .Const; const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, mutability); try writer.writeAll(" = "); switch (inst_ty.zigTypeTag()) { .Array => { const elem_ty = inst_ty.childType(); try writer.writeByte('{'); var empty = true; for (elements) |element| { if (!empty) try writer.writeAll(", "); try f.writeCValue(writer, try f.resolveInst(element), .Initializer); empty = false; } if (inst_ty.sentinel()) |sentinel| { if (!empty) try writer.writeAll(", "); try f.object.dg.renderValue(writer, elem_ty, sentinel, .Initializer); empty = false; } if (empty) try writer.print("{}", .{try f.fmtIntLiteral(Type.u8, Value.zero)}); try writer.writeAll("};\n"); }, .Struct => switch (inst_ty.containerLayout()) { .Auto, .Extern => { try writer.writeByte('{'); var empty = true; for (elements) |element, index| { if (inst_ty.structFieldValueComptime(index)) |_| continue; if (!empty) try writer.writeAll(", "); if (!inst_ty.isTupleOrAnonStruct()) { try writer.print(".{ } = ", .{fmtIdent(inst_ty.structFieldName(index))}); } const element_ty = f.air.typeOf(element); try f.writeCValue(writer, switch (element_ty.zigTypeTag()) { .Array => CValue{ .undef = element_ty }, else => try f.resolveInst(element), }, .Initializer); empty = false; } if (empty) try writer.print("{}", .{try f.fmtIntLiteral(Type.u8, Value.zero)}); try writer.writeAll("};\n"); var field_id: usize = 0; for (elements) |element, index| { if (inst_ty.structFieldValueComptime(index)) |_| continue; const element_ty = f.air.typeOf(element); if (element_ty.zigTypeTag() != .Array) continue; const field_name = if (inst_ty.isTupleOrAnonStruct()) CValue{ .field = field_id } else CValue{ .identifier = inst_ty.structFieldName(index) }; try writer.writeAll(";\n"); try writer.writeAll("memcpy("); try f.writeCValueMember(writer, local, field_name); try writer.writeAll(", "); try f.writeCValue(writer, try f.resolveInst(element), .FunctionArgument); try writer.writeAll(", sizeof("); try f.renderTypecast(writer, element_ty); try writer.writeAll("));\n"); field_id += 1; } }, .Packed => { const int_info = inst_ty.intInfo(target); var bit_offset_ty_pl = Type.Payload.Bits{ .base = .{ .tag = .int_unsigned }, .data = Type.smallestUnsignedBits(int_info.bits - 1), }; const bit_offset_ty = Type.initPayload(&bit_offset_ty_pl.base); var bit_offset_val_pl: Value.Payload.U64 = .{ .base = .{ .tag = .int_u64 }, .data = 0 }; const bit_offset_val = Value.initPayload(&bit_offset_val_pl.base); var empty = true; for (elements) |_, index| { const field_ty = inst_ty.structFieldType(index); if (!field_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!empty) { try writer.writeAll("zig_or_"); try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); try writer.writeByte('('); } empty = false; } empty = true; for (elements) |element, index| { const field_ty = inst_ty.structFieldType(index); if (!field_ty.hasRuntimeBitsIgnoreComptime()) continue; if (!empty) try writer.writeAll(", "); try writer.writeAll("zig_shlw_"); try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); try writer.writeAll("(("); try f.renderTypecast(writer, inst_ty); try writer.writeByte(')'); if (field_ty.isPtrAtRuntime()) { try writer.writeByte('('); try f.renderTypecast(writer, switch (int_info.signedness) { .unsigned => Type.usize, .signed => Type.isize, }); try writer.writeByte(')'); } try f.writeCValue(writer, try f.resolveInst(element), .Other); try writer.writeAll(", "); try f.object.dg.renderValue(writer, bit_offset_ty, bit_offset_val, .FunctionArgument); try f.object.dg.renderBuiltinInfo(writer, inst_ty, .Bits); try writer.writeByte(')'); if (!empty) try writer.writeByte(')'); bit_offset_val_pl.data += field_ty.bitSize(target); empty = false; } if (empty) try f.writeCValue(writer, .{ .undef = inst_ty }, .Initializer); try writer.writeAll(";\n"); }, }, .Vector => return f.fail("TODO: C backend: implement airAggregateInit for vectors", .{}), else => unreachable, } return local; } fn airUnionInit(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.UnionInit, ty_pl.payload).data; const union_ty = f.air.typeOfIndex(inst); const target = f.object.dg.module.getTarget(); const union_obj = union_ty.cast(Type.Payload.Union).?.data; const field_name = union_obj.fields.keys()[extra.field_index]; const payload = try f.resolveInst(extra.init); const writer = f.object.writer(); const local = try f.allocLocal(union_ty, .Const); try writer.writeAll(" = {"); if (union_ty.unionTagTypeSafety()) |tag_ty| { const layout = union_ty.unionGetLayout(target); if (layout.tag_size != 0) { const field_index = tag_ty.enumFieldIndex(field_name).?; var tag_pl: Value.Payload.U32 = .{ .base = .{ .tag = .enum_field_index }, .data = @intCast(u32, field_index), }; const tag_val = Value.initPayload(&tag_pl.base); var int_pl: Value.Payload.U64 = undefined; const int_val = tag_val.enumToInt(tag_ty, &int_pl); try writer.print(".tag = {}, ", .{try f.fmtIntLiteral(tag_ty, int_val)}); } try writer.writeAll(".payload = {"); } try writer.print(".{ } = ", .{fmtIdent(field_name)}); try f.writeCValue(writer, payload, .Initializer); if (union_ty.unionTagTypeSafety()) |_| try writer.writeByte('}'); try writer.writeAll("};\n"); return local; } 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, .FunctionArgument); 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, .FunctionArgument); try writer.writeAll(");\n"); return local; } fn airFloatNeg(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const un_op = f.air.instructions.items(.data)[inst].un_op; const operand = try f.resolveInst(un_op); const operand_ty = f.air.typeOf(un_op); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_neg_"); try f.object.dg.renderTypeForBuiltinFnName(writer, operand_ty); try writer.writeByte('('); try f.writeCValue(writer, operand, .FunctionArgument); try writer.writeAll(");\n"); return local; } fn airUnFloatOp(f: *Function, inst: Air.Inst.Index, operation: []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 inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = zig_libc_name_"); try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); try writer.writeByte('('); try writer.writeAll(operation); try writer.writeAll(")("); try f.writeCValue(writer, operand, .FunctionArgument); try writer.writeAll(");\n"); return local; } fn airBinFloatOp(f: *Function, inst: Air.Inst.Index, operation: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = zig_libc_name_"); try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); try writer.writeByte('('); try writer.writeAll(operation); try writer.writeAll(")("); try f.writeCValue(writer, lhs, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, rhs, .FunctionArgument); 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 local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = zig_libc_name_"); try f.object.dg.renderTypeForBuiltinFnName(writer, inst_ty); try writer.writeAll("(fma)("); try f.writeCValue(writer, mulend1, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, mulend2, .FunctionArgument); try writer.writeAll(", "); try f.writeCValue(writer, addend, .FunctionArgument); try writer.writeAll(");\n"); return local; } fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { // Note: unordered is actually even less atomic than relaxed .Unordered, .Monotonic => "memory_order_relaxed", .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 compilerRtAbbrev(ty: Type, target: std.Target) []const u8 { return if (ty.isInt()) switch (ty.intInfo(target).bits) { 1...32 => "si", 33...64 => "di", 65...128 => "ti", else => unreachable, } else if (ty.isRuntimeFloat()) switch (ty.floatBits(target)) { 16 => "hf", 32 => "sf", 64 => "df", 80 => "xf", 128 => "tf", else => unreachable, } else unreachable; } fn formatStringLiteral( str: []const u8, comptime fmt: []const u8, _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { if (fmt.len != 1 or fmt[0] != 's') @compileError("Invalid fmt: " ++ fmt); try writer.writeByte('\"'); for (str) |c| switch (c) { 7 => try writer.writeAll("\\a"), 8 => try writer.writeAll("\\b"), '\t' => try writer.writeAll("\\t"), '\n' => try writer.writeAll("\\n"), 11 => try writer.writeAll("\\v"), 12 => try writer.writeAll("\\f"), '\r' => try writer.writeAll("\\r"), '"', '\'', '?', '\\' => try writer.print("\\{c}", .{c}), else => switch (c) { ' '...'~' => try writer.writeByte(c), else => try writer.print("\\{o:0>3}", .{c}), }, }; try writer.writeByte('\"'); } fn fmtStringLiteral(str: []const u8) std.fmt.Formatter(formatStringLiteral) { return .{ .data = str }; } fn undefPattern(comptime IntType: type) IntType { const int_info = @typeInfo(IntType).Int; const UnsignedType = std.meta.Int(.unsigned, int_info.bits); return @bitCast(IntType, @as(UnsignedType, (1 << (int_info.bits | 1)) / 3)); } const FormatIntLiteralContext = struct { ty: Type, val: Value, mod: *Module, }; fn formatIntLiteral( data: FormatIntLiteralContext, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { const target = data.mod.getTarget(); const int_info = data.ty.intInfo(target); const ExpectedContents = struct { const base = 10; const limbs_count_128 = BigInt.calcTwosCompLimbCount(128); const expected_needed_limbs_count = BigInt.calcToStringLimbsBufferLen(limbs_count_128, base); const worst_case_int = BigInt.Const{ .limbs = &([1]BigIntLimb{std.math.maxInt(BigIntLimb)} ** expected_needed_limbs_count), .positive = false, }; undef_limbs: [limbs_count_128]BigIntLimb, wrap_limbs: [limbs_count_128]BigIntLimb, }; var stack align(@alignOf(ExpectedContents)) = std.heap.stackFallback(@sizeOf(ExpectedContents), data.mod.gpa); const allocator = stack.get(); var undef_limbs: []BigIntLimb = &.{}; defer allocator.free(undef_limbs); var int_buf: Value.BigIntSpace = undefined; const int = if (data.val.isUndefDeep()) blk: { undef_limbs = try allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(int_info.bits)); std.mem.set(BigIntLimb, undef_limbs, undefPattern(BigIntLimb)); var undef_int = BigInt.Mutable{ .limbs = undef_limbs, .len = undef_limbs.len, .positive = true, }; undef_int.truncate(undef_int.toConst(), int_info.signedness, int_info.bits); break :blk undef_int.toConst(); } else data.val.toBigInt(&int_buf, target); assert(int.fitsInTwosComp(int_info.signedness, int_info.bits)); const c_bits = toCIntBits(int_info.bits) orelse unreachable; var one_limbs: [BigInt.calcLimbLen(1)]BigIntLimb = undefined; const one = BigInt.Mutable.init(&one_limbs, 1).toConst(); const wrap_limbs = try allocator.alloc(BigIntLimb, BigInt.calcTwosCompLimbCount(c_bits)); defer allocator.free(wrap_limbs); var wrap = BigInt.Mutable{ .limbs = wrap_limbs, .len = undefined, .positive = undefined }; if (wrap.addWrap(int, one, int_info.signedness, c_bits) or int_info.signedness == .signed and wrap.subWrap(int, one, int_info.signedness, c_bits)) { const abbrev = switch (data.ty.tag()) { .c_short, .c_ushort => "SHRT", .c_int, .c_uint => "INT", .c_long, .c_ulong => "LONG", .c_longlong, .c_ulonglong => "LLONG", .isize, .usize => "INTPTR", else => return writer.print("zig_{s}Int_{c}{d}", .{ if (int.positive) "max" else "min", signAbbrev(int_info.signedness), c_bits, }), }; if (int_info.signedness == .unsigned) try writer.writeByte('U'); return writer.print("{s}_{s}", .{ abbrev, if (int.positive) "MAX" else "MIN" }); } if (!int.positive) try writer.writeByte('-'); switch (data.ty.tag()) { .c_short, .c_ushort, .c_int, .c_uint, .c_long, .c_ulong, .c_longlong, .c_ulonglong => {}, else => try writer.print("zig_as_{c}{d}(", .{ signAbbrev(int_info.signedness), c_bits }), } const limbs_count_64 = @divExact(64, @bitSizeOf(BigIntLimb)); if (c_bits <= 64) { var base: u8 = undefined; var case: std.fmt.Case = undefined; switch (fmt.len) { 0 => base = 10, 1 => switch (fmt[0]) { 'b' => { base = 2; try writer.writeAll("0b"); }, 'o' => { base = 8; try writer.writeByte('0'); }, 'd' => base = 10, 'x' => { base = 16; case = .lower; try writer.writeAll("0x"); }, 'X' => { base = 16; case = .upper; try writer.writeAll("0x"); }, else => @compileError("Invalid fmt: " ++ fmt), }, else => @compileError("Invalid fmt: " ++ fmt), } var str: [64]u8 = undefined; var limbs_buf: [BigInt.calcToStringLimbsBufferLen(limbs_count_64, 10)]BigIntLimb = undefined; try writer.writeAll(str[0..int.abs().toString(&str, base, case, &limbs_buf)]); } else { assert(c_bits == 128); const split = std.math.min(int.limbs.len, limbs_count_64); var upper_pl = Value.Payload.BigInt{ .base = .{ .tag = .int_big_positive }, .data = int.limbs[split..], }; const upper_val = Value.initPayload(&upper_pl.base); try formatIntLiteral(.{ .ty = switch (int_info.signedness) { .unsigned => Type.u64, .signed => Type.i64, }, .val = upper_val, .mod = data.mod, }, fmt, options, writer); try writer.writeAll(", "); var lower_pl = Value.Payload.BigInt{ .base = .{ .tag = .int_big_positive }, .data = int.limbs[0..split], }; const lower_val = Value.initPayload(&lower_pl.base); try formatIntLiteral(.{ .ty = Type.u64, .val = lower_val, .mod = data.mod, }, fmt, options, writer); return writer.writeByte(')'); } switch (data.ty.tag()) { .c_short, .c_ushort, .c_int => {}, .c_uint => try writer.writeAll("u"), .c_long => try writer.writeAll("l"), .c_ulong => try writer.writeAll("ul"), .c_longlong => try writer.writeAll("ll"), .c_ulonglong => try writer.writeAll("ull"), else => try writer.writeByte(')'), } } fn isByRef(ty: Type) bool { _ = ty; return false; } const LowerFnRetTyBuffer = struct { types: [1]Type, values: [1]Value, payload: Type.Payload.Tuple, }; fn lowerFnRetTy(ret_ty: Type, buffer: *LowerFnRetTyBuffer, target: std.Target) Type { if (ret_ty.zigTypeTag() == .NoReturn) return Type.initTag(.noreturn); if (lowersToArray(ret_ty, target)) { buffer.types = [1]Type{ret_ty}; buffer.values = [1]Value{Value.initTag(.unreachable_value)}; buffer.payload = .{ .data = .{ .types = &buffer.types, .values = &buffer.values, } }; return Type.initPayload(&buffer.payload.base); } return if (ret_ty.hasRuntimeBitsIgnoreComptime()) ret_ty else Type.void; } fn lowersToArray(ty: Type, target: std.Target) bool { return switch (ty.zigTypeTag()) { .Array => return true, else => return ty.isAbiInt() and toCIntBits(@intCast(u32, ty.bitSize(target))) == null, }; } fn loweredArrayInfo(ty: Type, target: std.Target) ?Type.ArrayInfo { if (!lowersToArray(ty, target)) return null; switch (ty.zigTypeTag()) { .Array => return ty.arrayInfo(), else => { const abi_size = ty.abiSize(target); const abi_align = ty.abiAlignment(target); return Type.ArrayInfo{ .elem_type = switch (abi_align) { 1 => Type.u8, 2 => Type.u16, 4 => Type.u32, 8 => Type.u64, 16 => Type.initTag(.u128), else => unreachable, }, .len = @divExact(abi_size, abi_align), }; }, } }