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 Zir = @import("../Zir.zig"); const Liveness = @import("../Liveness.zig"); const Mutability = enum { Const, Mut }; const BigIntConst = std.math.big.int.Const; pub const CValue = union(enum) { none: void, /// Index into local_names local: usize, /// Index into local_names, but take the address. local_ref: usize, /// A constant instruction, to be rendered inline. constant: Air.Inst.Ref, /// Index into the parameters arg: usize, /// By-value decl: *Decl, decl_ref: *Decl, /// Render these bytes literally. /// TODO make this a [*:0]const u8 to save memory bytes: []const u8, }; const BlockData = struct { block_id: usize, result: CValue, }; pub const CValueMap = std.AutoHashMap(Air.Inst.Index, CValue); pub const TypedefMap = std.ArrayHashMap( Type, struct { name: []const u8, rendered: []u8 }, Type.HashContext32, true, ); fn formatTypeAsCIdentifier( data: Type, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; var buffer = [1]u8{0} ** 128; // We don't care if it gets cut off, it's still more unique than a number var buf = std.fmt.bufPrint(&buffer, "{}", .{data}) catch &buffer; return formatIdent(buf, "", .{}, writer); } pub fn typeToCIdentifier(t: Type) std.fmt.Formatter(formatTypeAsCIdentifier) { return .{ .data = t }; } fn formatIdent( ident: []const u8, comptime fmt: []const u8, options: std.fmt.FormatOptions, writer: anytype, ) !void { _ = fmt; _ = options; 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 { if (f.air.value(inst)) |_| { return CValue{ .constant = inst }; } const index = Air.refToIndex(inst).?; return f.value_map.get(index).?; // Assertion means instruction does not dominate usage. } 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 { const local_value = f.allocLocalValue(); try f.object.dg.renderTypeAndName(f.object.writer(), ty, local_value, mutability); return local_value; } fn writeCValue(f: *Function, w: anytype, c_value: CValue) !void { switch (c_value) { .constant => |inst| { const ty = f.air.typeOf(inst); const val = f.air.value(inst).?; return f.object.dg.renderValue(w, ty, val); }, else => return f.object.dg.writeCValue(w, c_value), } } fn fail(f: *Function, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { return f.object.dg.fail(format, args); } fn renderType(f: *Function, w: anytype, t: Type) !void { return f.object.dg.renderType(w, t); } }; /// 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), 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, 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 = .{ .node_offset = 0 }; const src_loc = src.toSrcLoc(dg.decl); dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, src_loc, format, args); return error.AnalysisFail; } fn getTypedefName(dg: *DeclGen, t: Type) ?[]const u8 { if (dg.typedefs.get(t)) |some| { return some.name; } else { return null; } } fn renderDeclValue( dg: *DeclGen, writer: anytype, ty: Type, val: Value, decl: *Decl, ) error{ OutOfMemory, AnalysisFail }!void { decl.alive = true; if (ty.isSlice()) { try writer.writeByte('('); try dg.renderType(writer, ty); try writer.writeAll("){"); var buf: Type.SlicePtrFieldTypeBuffer = undefined; try dg.renderValue(writer, ty.slicePtrFieldType(&buf), val); try writer.writeAll(", "); try writer.print("{d}", .{val.sliceLen()}); try writer.writeAll("}"); return; } assert(decl.has_tv); // We shouldn't cast C function pointers as this is UB (when you call // them). The analysis until now should ensure that the C function // pointers are compatible. If they are not, then there is a bug // somewhere and we should let the C compiler tell us about it. if (ty.castPtrToFn() == null) { // Determine if we must pointer cast. if (ty.eql(decl.ty)) { try writer.writeByte('&'); } else { try writer.writeAll("("); try dg.renderType(writer, ty); try writer.writeAll(")&"); } } try dg.renderDeclName(decl, writer); } fn renderInt128( writer: anytype, int_val: anytype, ) error{ OutOfMemory, AnalysisFail }!void { const int_info = @typeInfo(@TypeOf(int_val)).Int; const is_signed = int_info.signedness == .signed; const is_neg = int_val < 0; comptime assert(int_info.bits > 64 and int_info.bits <= 128); // Clang and GCC don't support 128-bit integer constants but will hopefully unfold them // if we construct one manually. const magnitude = std.math.absCast(int_val); const high = @truncate(u64, magnitude >> 64); const low = @truncate(u64, magnitude); // (int128_t)/<->( ( (uint128_t)( val_high << 64 )u ) + (uint128_t)val_low/u ) if (is_signed) try writer.writeAll("(int128_t)"); if (is_neg) try writer.writeByte('-'); assert(high > 0); try writer.print("(((uint128_t)0x{x}u<<64)", .{high}); if (low > 0) try writer.print("+(uint128_t)0x{x}u", .{low}); return writer.writeByte(')'); } fn renderBigIntConst( dg: *DeclGen, writer: anytype, val: BigIntConst, signed: bool, ) error{ OutOfMemory, AnalysisFail }!void { if (signed) { try renderInt128(writer, val.to(i128) catch { return dg.fail("TODO implement integer constants larger than 128 bits", .{}); }); } else { try renderInt128(writer, val.to(u128) catch { return dg.fail("TODO implement integer constants larger than 128 bits", .{}); }); } } fn renderValue( dg: *DeclGen, writer: anytype, ty: Type, val: Value, ) error{ OutOfMemory, AnalysisFail }!void { if (val.isUndefDeep()) { switch (ty.zigTypeTag()) { // Using '{}' for integer and floats seemed to error C compilers (both GCC and Clang) // with 'error: expected expression' (including when built with 'zig cc') .Int => { const c_bits = toCIntBits(ty.intInfo(dg.module.getTarget()).bits) orelse return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); switch (c_bits) { 8 => return writer.writeAll("0xaau"), 16 => return writer.writeAll("0xaaaau"), 32 => return writer.writeAll("0xaaaaaaaau"), 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaau"), 128 => return renderInt128(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), else => unreachable, } }, .Float => { switch (ty.floatBits(dg.module.getTarget())) { 32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaau)"), 64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaau)"), else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}), } }, else => { // This should lower to 0xaa bytes in safe modes, and for unsafe modes should // lower to leaving variables uninitialized (that might need to be implemented // outside of this function). return writer.writeAll("{}"); }, } } switch (ty.zigTypeTag()) { .Int => switch (val.tag()) { .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()), .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true), else => { if (ty.isSignedInt()) return writer.print("{d}", .{val.toSignedInt()}); return writer.print("{d}u", .{val.toUnsignedInt()}); }, }, .Float => { if (ty.floatBits(dg.module.getTarget()) <= 64) { if (std.math.isNan(val.toFloat(f64)) or std.math.isInf(val.toFloat(f64))) { // just generate a bit cast (exactly like we do in airBitcast) switch (ty.tag()) { .f32 => return writer.print("zig_bitcast_f32_u32(0x{x})", .{@bitCast(u32, val.toFloat(f32))}), .f64 => return writer.print("zig_bitcast_f64_u64(0x{x})", .{@bitCast(u64, val.toFloat(f64))}), else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}), } } else { return writer.print("{x}", .{val.toFloat(f64)}); } } return dg.fail("TODO: C backend: implement lowering large float values", .{}); }, .Pointer => switch (val.tag()) { .null_value => try writer.writeAll("NULL"), // Technically this should produce NULL but the integer literal 0 will always coerce // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc) // between pointers and 0, which is an extension to begin with. .zero => try writer.writeByte('0'), .decl_ref => { const decl = val.castTag(.decl_ref).?.data; return dg.renderDeclValue(writer, ty, val, decl); }, .variable => { const decl = val.castTag(.variable).?.data.owner_decl; return dg.renderDeclValue(writer, ty, val, decl); }, .slice => { const slice = val.castTag(.slice).?.data; var buf: Type.SlicePtrFieldTypeBuffer = undefined; try writer.writeByte('('); try dg.renderType(writer, ty); try writer.writeAll("){"); try dg.renderValue(writer, ty.slicePtrFieldType(&buf), slice.ptr); try writer.writeAll(", "); try dg.renderValue(writer, Type.usize, slice.len); try writer.writeAll("}"); }, .function => { const func = val.castTag(.function).?.data; try dg.renderDeclName(func.owner_decl, writer); }, .extern_fn => { const decl = val.castTag(.extern_fn).?.data; try dg.renderDeclName(decl, writer); }, .int_u64, .one => { try writer.writeAll("(("); try dg.renderType(writer, ty); try writer.print(")0x{x}u)", .{val.toUnsignedInt()}); }, else => unreachable, }, .Array => { // First try specific tag representations for more efficiency. switch (val.tag()) { .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), .bytes => { const bytes = val.castTag(.bytes).?.data; // TODO: make our own C string escape instead of using std.zig.fmtEscapes try writer.print("\"{}\"", .{std.zig.fmtEscapes(bytes)}); }, else => { // Fall back to generic implementation. var arena = std.heap.ArenaAllocator.init(dg.module.gpa); defer arena.deinit(); try writer.writeAll("{"); var index: usize = 0; const len = ty.arrayLen(); const elem_ty = ty.elemType(); while (index < len) : (index += 1) { if (index != 0) try writer.writeAll(","); const elem_val = try val.elemValue(&arena.allocator, index); try dg.renderValue(writer, elem_ty, elem_val); } if (ty.sentinel()) |sentinel_val| { if (index != 0) try writer.writeAll(","); try dg.renderValue(writer, elem_ty, sentinel_val); } try writer.writeAll("}"); }, } }, .Bool => return writer.print("{}", .{val.toBool()}), .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const payload_type = ty.optionalChild(&opt_buf); if (ty.isPtrLikeOptional()) { return dg.renderValue(writer, payload_type, val); } const target = dg.module.getTarget(); if (payload_type.abiSize(target) == 0) { const is_null = val.castTag(.opt_payload) == null; return writer.print("{}", .{is_null}); } try writer.writeByte('('); try dg.renderType(writer, ty); try writer.writeAll("){"); if (val.castTag(.opt_payload)) |pl| { const payload_val = pl.data; try writer.writeAll(" .is_null = false, .payload = "); try dg.renderValue(writer, payload_type, payload_val); try writer.writeAll(" }"); } else { try writer.writeAll(" .is_null = true }"); } }, .ErrorSet => { switch (val.tag()) { .@"error" => { const payload = val.castTag(.@"error").?; // error values will be #defined at the top of the file return writer.print("zig_error_{s}", .{payload.data.name}); }, else => { // In this case we are rendering an error union which has a // 0 bits payload. return writer.writeAll("0"); }, } }, .ErrorUnion => { const error_type = ty.errorUnionSet(); const payload_type = ty.errorUnionPayload(); if (!payload_type.hasCodeGenBits()) { // We use the error type directly as the type. const err_val = if (val.errorUnionIsPayload()) Value.initTag(.zero) else val; return dg.renderValue(writer, error_type, err_val); } try writer.writeByte('('); try dg.renderType(writer, ty); try writer.writeAll("){"); if (val.castTag(.eu_payload)) |pl| { const payload_val = pl.data; try writer.writeAll(" .payload = "); try dg.renderValue(writer, payload_type, payload_val); try writer.writeAll(", .error = 0 }"); } else { try writer.writeAll(" .error = "); try dg.renderValue(writer, error_type, val); 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); } 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); }, } }, .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; return dg.renderDeclValue(writer, ty, val, decl); }, else => unreachable, }, .Struct => { const field_vals = val.castTag(.@"struct").?.data; try writer.writeAll("("); try dg.renderType(writer, ty); try writer.writeAll("){"); for (field_vals) |field_val, i| { const field_ty = ty.structFieldType(i); if (!field_ty.hasCodeGenBits()) continue; if (i != 0) try writer.writeAll(","); try dg.renderValue(writer, field_ty, field_val); } try writer.writeAll("}"); }, .ComptimeInt => unreachable, .ComptimeFloat => unreachable, .Type => unreachable, .EnumLiteral => unreachable, .Void => unreachable, .NoReturn => unreachable, .Undefined => unreachable, .Null => unreachable, .BoundFn => unreachable, .Opaque => unreachable, .Union, .Frame, .AnyFrame, .Vector, => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{ @tagName(tag), }), } } fn renderFunctionSignature(dg: *DeclGen, w: anytype, is_global: bool) !void { if (!is_global) { try w.writeAll("static "); } if (dg.decl.val.castTag(.function)) |func_payload| { const func: *Module.Fn = func_payload.data; if (func.is_cold) { try w.writeAll("ZIG_COLD "); } } const return_ty = dg.decl.ty.fnReturnType(); if (return_ty.hasCodeGenBits()) { try dg.renderType(w, return_ty); } else if (return_ty.zigTypeTag() == .NoReturn) { try w.writeAll("zig_noreturn void"); } else { try w.writeAll("void"); } try w.writeAll(" "); try dg.renderDeclName(dg.decl, w); try w.writeAll("("); const param_len = dg.decl.ty.fnParamLen(); const is_var_args = dg.decl.ty.fnIsVarArgs(); if (param_len == 0 and !is_var_args) try w.writeAll("void") else { var index: usize = 0; while (index < param_len) : (index += 1) { if (index > 0) { try w.writeAll(", "); } try dg.renderType(w, dg.decl.ty.fnParamType(index)); try w.print(" a{d}", .{index}); } } if (is_var_args) { if (param_len != 0) try w.writeAll(", "); try w.writeAll("..."); } try w.writeByte(')'); } fn renderPtrToFnTypedef(dg: *DeclGen, t: Type, fn_ty: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); const fn_info = fn_ty.fnInfo(); try bw.writeAll("typedef "); try dg.renderType(bw, fn_info.return_type); try bw.writeAll(" (*"); const name_start = buffer.items.len; // TODO: typeToCIdentifier truncates to 128 bytes, we probably don't want to do this try bw.print("zig_F_{s})(", .{typeToCIdentifier(t)}); const name_end = buffer.items.len - 2; const param_len = fn_info.param_types.len; const is_var_args = fn_info.is_var_args; if (param_len == 0 and !is_var_args) try bw.writeAll("void") else { var index: usize = 0; while (index < param_len) : (index += 1) { if (index > 0) { try bw.writeAll(", "); } try dg.renderType(bw, fn_info.param_types[index]); } } if (is_var_args) { if (param_len != 0) try bw.writeAll(", "); try bw.writeAll("..."); } try bw.writeAll(");\n"); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start..name_end]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderSliceTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); try bw.writeAll("typedef struct { "); const elem_type = t.elemType(); try dg.renderType(bw, elem_type); if (t.isConstPtr()) { try bw.writeAll(" const"); } if (t.isVolatilePtr()) { try bw.writeAll(" volatile"); } try bw.writeAll(" *"); try bw.writeAll("ptr; size_t len; } "); const name_index = buffer.items.len; if (t.isConstPtr()) { try bw.print("zig_L_{s};\n", .{typeToCIdentifier(elem_type)}); } else { try bw.print("zig_M_{s};\n", .{typeToCIdentifier(elem_type)}); } const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_index .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderStructTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const struct_obj = t.castTag(.@"struct").?.data; // Handle 0 bit types elsewhere. const fqn = try struct_obj.getFullyQualifiedName(dg.typedefs.allocator); defer dg.typedefs.allocator.free(fqn); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); try buffer.appendSlice("typedef struct {\n"); { var it = struct_obj.fields.iterator(); while (it.next()) |entry| { const field_ty = entry.value_ptr.ty; const name: CValue = .{ .bytes = entry.key_ptr.* }; try buffer.append(' '); try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut); try buffer.appendSlice(";\n"); } } try buffer.appendSlice("} "); const name_start = buffer.items.len; try buffer.writer().print("zig_S_{s};\n", .{fmtIdent(fqn)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_start .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 { const child_type = t.errorUnionPayload(); const err_set_type = t.errorUnionSet(); var buffer = std.ArrayList(u8).init(dg.typedefs.allocator); defer buffer.deinit(); const bw = buffer.writer(); try bw.writeAll("typedef struct { "); try dg.renderType(bw, child_type); try bw.writeAll(" payload; uint16_t error; } "); const name_index = buffer.items.len; if (err_set_type.castTag(.error_set_inferred)) |inf_err_set_payload| { const func = inf_err_set_payload.data.func; try bw.writeAll("zig_E_"); try dg.renderDeclName(func.owner_decl, bw); try bw.writeAll(";\n"); } else { try bw.print("zig_E_{s}_{s};\n", .{ typeToCIdentifier(err_set_type), typeToCIdentifier(child_type), }); } const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_index .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn 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 { "); try dg.renderType(bw, child_type); try bw.writeAll(" payload; bool is_null; } "); const name_index = buffer.items.len; try bw.print("zig_Q_{s};\n", .{typeToCIdentifier(child_type)}); const rendered = buffer.toOwnedSlice(); errdefer dg.typedefs.allocator.free(rendered); const name = rendered[name_index .. rendered.len - 2]; try dg.typedefs.ensureUnusedCapacity(1); dg.typedefs.putAssumeCapacityNoClobber( try t.copy(dg.typedefs_arena), .{ .name = name, .rendered = rendered }, ); return name; } fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void { const target = dg.module.getTarget(); switch (t.zigTypeTag()) { .NoReturn, .Void => try w.writeAll("void"), .Bool => try w.writeAll("bool"), .Int => { switch (t.tag()) { .u1, .u8 => try w.writeAll("uint8_t"), .i8 => try w.writeAll("int8_t"), .u16 => try w.writeAll("uint16_t"), .i16 => try w.writeAll("int16_t"), .u32 => try w.writeAll("uint32_t"), .i32 => try w.writeAll("int32_t"), .u64 => try w.writeAll("uint64_t"), .i64 => try w.writeAll("int64_t"), .u128 => try w.writeAll("uint128_t"), .i128 => try w.writeAll("int128_t"), .usize => try w.writeAll("uintptr_t"), .isize => try w.writeAll("intptr_t"), .c_short => try w.writeAll("short"), .c_ushort => try w.writeAll("unsigned short"), .c_int => try w.writeAll("int"), .c_uint => try w.writeAll("unsigned int"), .c_long => try w.writeAll("long"), .c_ulong => try w.writeAll("unsigned long"), .c_longlong => try w.writeAll("long long"), .c_ulonglong => try w.writeAll("unsigned long long"), .int_signed, .int_unsigned => { const info = t.intInfo(target); const sign_prefix = switch (info.signedness) { .signed => "", .unsigned => "u", }; const c_bits = toCIntBits(info.bits) orelse return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); try w.print("{s}int{d}_t", .{ sign_prefix, c_bits }); }, else => unreachable, } }, .Float => { switch (t.tag()) { .f32 => try w.writeAll("float"), .f64 => try w.writeAll("double"), .c_longdouble => try w.writeAll("long double"), .f16 => return dg.fail("TODO: C backend: implement float type f16", .{}), .f128 => return dg.fail("TODO: C backend: implement float type f128", .{}), else => unreachable, } }, .Pointer => { if (t.isSlice()) { const name = dg.getTypedefName(t) orelse try dg.renderSliceTypedef(t); return w.writeAll(name); } if (t.castPtrToFn()) |fn_ty| { const name = dg.getTypedefName(t) orelse try dg.renderPtrToFnTypedef(t, fn_ty); return w.writeAll(name); } try dg.renderType(w, t.elemType()); if (t.isConstPtr()) { try w.writeAll(" const"); } if (t.isVolatilePtr()) { try w.writeAll(" volatile"); } return w.writeAll(" *"); }, .Array => { // We are referencing the array so it will decay to a C pointer. // NB: arrays are not really types in C so they are either specified in the declaration // or are already pointed to; our only job is to render the element type. return dg.renderType(w, t.elemType()); }, .Optional => { var opt_buf: Type.Payload.ElemType = undefined; const child_type = t.optionalChild(&opt_buf); if (t.isPtrLikeOptional()) { return dg.renderType(w, child_type); } if (child_type.abiSize(target) == 0) { return w.writeAll("bool"); } const name = dg.getTypedefName(t) orelse try dg.renderOptionalTypedef(t, child_type); return w.writeAll(name); }, .ErrorSet => { comptime std.debug.assert(Type.initTag(.anyerror).abiSize(builtin.target) == 2); return w.writeAll("uint16_t"); }, .ErrorUnion => { if (t.errorUnionPayload().abiSize(target) == 0) { return dg.renderType(w, t.errorUnionSet()); } const name = dg.getTypedefName(t) orelse try dg.renderErrorUnionTypedef(t); return w.writeAll(name); }, .Struct => { const name = dg.getTypedefName(t) orelse try dg.renderStructTypedef(t); return w.writeAll(name); }, .Enum => { // For enums, we simply use the integer tag type. var int_tag_ty_buffer: Type.Payload.Bits = undefined; const int_tag_ty = t.intTagType(&int_tag_ty_buffer); try dg.renderType(w, int_tag_ty); }, .Union, .Frame, .AnyFrame, .Vector, .Opaque, => |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 } } fn renderTypeAndName( dg: *DeclGen, w: anytype, ty: Type, name: CValue, mutability: Mutability, ) error{ OutOfMemory, AnalysisFail }!void { var suffix = std.ArrayList(u8).init(dg.gpa); defer suffix.deinit(); var render_ty = ty; while (render_ty.zigTypeTag() == .Array) { const sentinel_bit = @boolToInt(render_ty.sentinel() != null); const c_len = render_ty.arrayLen() + sentinel_bit; try suffix.writer().print("[{d}]", .{c_len}); render_ty = render_ty.elemType(); } try dg.renderType(w, render_ty); const const_prefix = switch (mutability) { .Const => "const ", .Mut => "", }; try w.print(" {s}", .{const_prefix}); try dg.writeCValue(w, name); try w.writeAll(suffix.items); } fn declIsGlobal(dg: *DeclGen, tv: TypedValue) bool { switch (tv.val.tag()) { .extern_fn => return true, .function => { const func = tv.val.castTag(.function).?.data; return dg.module.decl_exports.contains(func.owner_decl); }, .variable => { const variable = tv.val.castTag(.variable).?.data; return dg.module.decl_exports.contains(variable.owner_decl); }, else => unreachable, } } fn writeCValue(dg: DeclGen, w: anytype, c_value: CValue) !void { switch (c_value) { .none => unreachable, .local => |i| return w.print("t{d}", .{i}), .local_ref => |i| return w.print("&t{d}", .{i}), .constant => unreachable, .arg => |i| return w.print("a{d}", .{i}), .decl => |decl| return dg.renderDeclName(decl, w), .decl_ref => |decl| { try w.writeByte('&'); return dg.renderDeclName(decl, w); }, .bytes => |bytes| return w.writeAll(bytes), } } fn renderDeclName(dg: DeclGen, decl: *Decl, writer: anytype) !void { if (dg.module.decl_exports.get(decl)) |exports| { return writer.writeAll(exports[0].options.name); } else if (decl.val.tag() == .extern_fn) { return writer.writeAll(mem.sliceTo(decl.name, 0)); } else { const gpa = dg.module.gpa; const name = try decl.getFullyQualifiedName(gpa); defer gpa.free(name); return writer.print("{}", .{fmtIdent(name)}); } } }; pub fn genFunc(f: *Function) !void { const tracy = trace(@src()); defer tracy.end(); const o = &f.object; const is_global = o.dg.module.decl_exports.contains(f.func.owner_decl); const fwd_decl_writer = o.dg.fwd_decl.writer(); if (is_global) { try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); } try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); try fwd_decl_writer.writeAll(";\n"); try o.indent_writer.insertNewline(); try o.dg.renderFunctionSignature(o.writer(), is_global); try o.writer().writeByte(' '); const main_body = f.air.getMainBody(); try genBody(f, main_body); try o.indent_writer.insertNewline(); } pub fn genDecl(o: *Object) !void { const tracy = trace(@src()); defer tracy.end(); const tv: TypedValue = .{ .ty = o.dg.decl.ty, .val = o.dg.decl.val, }; if (tv.val.tag() == .extern_fn) { const writer = o.writer(); try writer.writeAll("ZIG_EXTERN_C "); try o.dg.renderFunctionSignature(writer, true); try writer.writeAll(";\n"); } else if (tv.val.castTag(.variable)) |var_payload| { const variable: *Module.Var = var_payload.data; const is_global = o.dg.declIsGlobal(tv) or variable.is_extern; const fwd_decl_writer = o.dg.fwd_decl.writer(); if (is_global) { try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); } if (variable.is_threadlocal) { try fwd_decl_writer.writeAll("zig_threadlocal "); } try o.dg.renderType(fwd_decl_writer, o.dg.decl.ty); try fwd_decl_writer.writeAll(" "); if (is_global) { try fwd_decl_writer.writeAll(mem.span(o.dg.decl.name)); } else { try o.dg.renderDeclName(o.dg.decl, fwd_decl_writer); } try fwd_decl_writer.writeAll(";\n"); if (variable.init.isUndefDeep()) { return; } try o.indent_writer.insertNewline(); const w = o.writer(); try o.dg.renderType(w, o.dg.decl.ty); try w.writeAll(" "); if (is_global) { try w.writeAll(mem.span(o.dg.decl.name)); } else { try o.dg.renderDeclName(o.dg.decl, w); } try w.writeAll(" = "); if (variable.init.tag() != .unreachable_value) { try o.dg.renderValue(w, tv.ty, variable.init); } try w.writeAll(";"); try o.indent_writer.insertNewline(); } else { const writer = o.writer(); try writer.writeAll("static "); // TODO ask the Decl if it is const // https://github.com/ziglang/zig/issues/7582 const decl_c_value: CValue = .{ .decl = o.dg.decl }; try o.dg.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut); try writer.writeAll(" = "); try o.dg.renderValue(writer, tv.ty, tv.val); try writer.writeAll(";\n"); } } pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { const tracy = trace(@src()); defer tracy.end(); const tv: TypedValue = .{ .ty = dg.decl.ty, .val = dg.decl.val, }; const writer = dg.fwd_decl.writer(); switch (tv.ty.zigTypeTag()) { .Fn => { const is_global = dg.declIsGlobal(tv); if (is_global) { try writer.writeAll("ZIG_EXTERN_C "); try dg.renderFunctionSignature(writer, is_global); try dg.fwd_decl.appendSlice(";\n"); } }, else => {}, } } fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void { const writer = f.object.writer(); if (body.len == 0) { try writer.writeAll("{}"); return; } try writer.writeAll("{\n"); f.object.indent_writer.pushIndent(); const air_tags = f.air.instructions.items(.tag); for (body) |inst| { const result_value = switch (air_tags[inst]) { // zig fmt: off .constant => unreachable, // excluded from function bodies .const_ty => unreachable, // excluded from function bodies .arg => airArg(f), .breakpoint => try airBreakpoint(f), .unreach => try airUnreach(f), .fence => try airFence(f, inst), // TODO use a different strategy for add that communicates to the optimizer // that wrapping is UB. .add => try airBinOp (f, inst, " + "), .ptr_add => try airPtrAddSub (f, inst, " + "), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. .sub => try airBinOp (f, inst, " - "), .ptr_sub => try airPtrAddSub (f, inst, " - "), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. .mul => try airBinOp (f, inst, " * "), // TODO use a different strategy for div that communicates to the optimizer // that wrapping is UB. .div_float, .div_exact, .div_trunc => try airBinOp( f, inst, " / "), .div_floor => try airBinOp( f, inst, " divfloor "), .rem => try airBinOp( f, inst, " % "), .mod => try airBinOp( f, inst, " mod "), // TODO implement modulus division .addwrap => try airWrapOp(f, inst, " + ", "addw_"), .subwrap => try airWrapOp(f, inst, " - ", "subw_"), .mulwrap => try airWrapOp(f, inst, " * ", "mulw_"), .add_sat => try airSatOp(f, inst, "adds_"), .sub_sat => try airSatOp(f, inst, "subs_"), .mul_sat => try airSatOp(f, inst, "muls_"), .shl_sat => try airSatOp(f, inst, "shls_"), .min => try airMinMax(f, inst, "<"), .max => try airMinMax(f, inst, ">"), .slice => try airSlice(f, inst), .cmp_gt => try airBinOp(f, inst, " > "), .cmp_gte => try airBinOp(f, inst, " >= "), .cmp_lt => try airBinOp(f, inst, " < "), .cmp_lte => try airBinOp(f, inst, " <= "), .cmp_eq => try airEquality(f, inst, "((", "=="), .cmp_neq => try airEquality(f, inst, "!((", "!="), // bool_and and bool_or are non-short-circuit operations .bool_and => try airBinOp(f, inst, " & "), .bool_or => try airBinOp(f, inst, " | "), .bit_and => try airBinOp(f, inst, " & "), .bit_or => try airBinOp(f, inst, " | "), .xor => try airBinOp(f, inst, " ^ "), .shr => try airBinOp(f, inst, " >> "), .shl, .shl_exact => try airBinOp(f, inst, " << "), .not => try airNot (f, inst), .optional_payload => try airOptionalPayload(f, inst), .optional_payload_ptr => try airOptionalPayload(f, inst), .optional_payload_ptr_set => try airOptionalPayloadPtrSet(f, inst), .is_err => try airIsErr(f, inst, "", ".", "!="), .is_non_err => try airIsErr(f, inst, "", ".", "=="), .is_err_ptr => try airIsErr(f, inst, "*", "->", "!="), .is_non_err_ptr => try airIsErr(f, inst, "*", "->", "=="), .is_null => try airIsNull(f, inst, "==", ""), .is_non_null => try airIsNull(f, inst, "!=", ""), .is_null_ptr => try airIsNull(f, inst, "==", "[0]"), .is_non_null_ptr => try airIsNull(f, inst, "!=", "[0]"), .alloc => try airAlloc(f, inst), .ret_ptr => try airRetPtr(f, inst), .assembly => try airAsm(f, inst), .block => try airBlock(f, inst), .bitcast => try airBitcast(f, inst), .call => try airCall(f, inst), .dbg_stmt => try airDbgStmt(f, inst), .intcast => try airIntCast(f, inst), .trunc => try airTrunc(f, inst), .bool_to_int => try airBoolToInt(f, inst), .load => try airLoad(f, inst), .ret => try airRet(f, inst), .ret_load => try airRetLoad(f, inst), .store => try airStore(f, inst), .loop => try airLoop(f, inst), .cond_br => try airCondBr(f, inst), .br => try airBr(f, inst), .switch_br => try airSwitchBr(f, inst), .wrap_optional => try airWrapOptional(f, inst), .struct_field_ptr => try airStructFieldPtr(f, inst), .array_to_slice => try airArrayToSlice(f, inst), .cmpxchg_weak => try airCmpxchg(f, inst, "weak"), .cmpxchg_strong => try airCmpxchg(f, inst, "strong"), .atomic_rmw => try airAtomicRmw(f, inst), .atomic_load => try airAtomicLoad(f, inst), .memset => try airMemset(f, inst), .memcpy => try airMemcpy(f, inst), .set_union_tag => try airSetUnionTag(f, inst), .get_union_tag => try airGetUnionTag(f, inst), .clz => try airBuiltinCall(f, inst, "clz"), .ctz => try airBuiltinCall(f, inst, "ctz"), .popcount => try airBuiltinCall(f, inst, "popcount"), .int_to_float, .float_to_int, .fptrunc, .fpext, => try airSimpleCast(f, inst), .ptrtoint => try airPtrToInt(f, inst), .atomic_store_unordered => try airAtomicStore(f, inst, toMemoryOrder(.Unordered)), .atomic_store_monotonic => try airAtomicStore(f, inst, toMemoryOrder(.Monotonic)), .atomic_store_release => try airAtomicStore(f, inst, toMemoryOrder(.Release)), .atomic_store_seq_cst => try airAtomicStore(f, inst, toMemoryOrder(.SeqCst)), .struct_field_ptr_index_0 => try airStructFieldPtrIndex(f, inst, 0), .struct_field_ptr_index_1 => try airStructFieldPtrIndex(f, inst, 1), .struct_field_ptr_index_2 => try airStructFieldPtrIndex(f, inst, 2), .struct_field_ptr_index_3 => try airStructFieldPtrIndex(f, inst, 3), .struct_field_val => try airStructFieldVal(f, inst), .slice_ptr => try airSliceField(f, inst, ".ptr;\n"), .slice_len => try airSliceField(f, inst, ".len;\n"), .ptr_slice_len_ptr => try airPtrSliceFieldPtr(f, inst, ".len;\n"), .ptr_slice_ptr_ptr => try airPtrSliceFieldPtr(f, inst, ".ptr;\n"), .ptr_elem_val => try airPtrElemVal(f, inst), .ptr_elem_ptr => try airPtrElemPtr(f, inst), .slice_elem_val => try airSliceElemVal(f, inst), .slice_elem_ptr => try airSliceElemPtr(f, inst), .array_elem_val => try airArrayElemVal(f, inst), .unwrap_errunion_payload => try airUnwrapErrUnionPay(f, inst, ""), .unwrap_errunion_err => try airUnwrapErrUnionErr(f, inst), .unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(f, inst, "&"), .unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(f, inst), .wrap_errunion_payload => try airWrapErrUnionPay(f, inst), .wrap_errunion_err => try airWrapErrUnionErr(f, inst), // zig fmt: on }; switch (result_value) { .none => {}, else => try f.value_map.putNoClobber(inst, result_value), } } f.object.indent_writer.popIndent(); try writer.writeAll("}"); } fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(suffix); return local; } fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); _ = writer; _ = operand; _ = suffix; return f.fail("TODO: C backend: airPtrSliceFieldPtr", .{}); } fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const ptr_ty = f.air.typeOf(bin_op.lhs); if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; const ptr = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = "); try f.writeCValue(writer, ptr); try writer.writeByte('['); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const ptr = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = &"); try f.writeCValue(writer, ptr); try writer.writeByte('['); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const slice_ty = f.air.typeOf(bin_op.lhs); if (!slice_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; const slice = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = "); try f.writeCValue(writer, slice); try writer.writeAll(".ptr["); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airSliceElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const slice = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = &"); try f.writeCValue(writer, slice); try writer.writeAll(".ptr["); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airArrayElemVal(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const array = try f.resolveInst(bin_op.lhs); const index = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const); try writer.writeAll(" = "); try f.writeCValue(writer, array); try writer.writeAll("["); try f.writeCValue(writer, index); try writer.writeAll("];\n"); return local; } fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); // First line: the variable used as data storage. const elem_type = inst_ty.elemType(); const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut; const local = try f.allocLocal(elem_type, mutability); try writer.writeAll(";\n"); // Arrays are already pointers so they don't need to be referenced. if (elem_type.zigTypeTag() == .Array) return CValue{ .local = local.local }; return CValue{ .local_ref = local.local }; } fn airRetPtr(f: *Function, inst: Air.Inst.Index) !CValue { const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); // First line: the variable used as data storage. const elem_type = inst_ty.elemType(); const local = try f.allocLocal(elem_type, .Mut); try writer.writeAll(";\n"); return CValue{ .local_ref = local.local }; } fn airArg(f: *Function) CValue { const i = f.next_arg_index; f.next_arg_index += 1; return .{ .arg = i }; } fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { const ty_op = f.air.instructions.items(.data)[inst].ty_op; const is_volatile = f.air.typeOf(ty_op.operand).isVolatilePtr(); if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.zigTypeTag() == .Array) return f.fail("TODO: C backend: implement airLoad for arrays", .{}); const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); switch (operand) { .local_ref => |i| { const wrapped: CValue = .{ .local = i }; try writer.writeAll(" = "); try f.writeCValue(writer, wrapped); try writer.writeAll(";\n"); }, .decl_ref => |decl| { const wrapped: CValue = .{ .decl = decl }; try writer.writeAll(" = "); try f.writeCValue(writer, wrapped); try writer.writeAll(";\n"); }, else => { try writer.writeAll(" = *"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); }, } return local; } fn airRet(f: *Function, inst: Air.Inst.Index) !CValue { const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); if (f.air.typeOf(un_op).hasCodeGenBits()) { const operand = try f.resolveInst(un_op); try writer.writeAll("return "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); } else { try writer.writeAll("return;\n"); } return CValue.none; } fn airRetLoad(f: *Function, inst: Air.Inst.Index) !CValue { const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const ptr_ty = f.air.typeOf(un_op); const ret_ty = ptr_ty.childType(); if (!ret_ty.hasCodeGenBits()) { try writer.writeAll("return;\n"); } const ptr = try f.resolveInst(un_op); try writer.writeAll("return *"); try f.writeCValue(writer, ptr); try writer.writeAll(";\n"); return CValue.none; } fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; 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.renderType(writer, inst_ty); try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const target = f.object.dg.module.getTarget(); const dest_int_info = inst_ty.intInfo(target); const dest_bits = dest_int_info.bits; try writer.writeAll(" = "); if (dest_bits >= 8 and std.math.isPowerOfTwo(dest_bits)) { try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } switch (dest_int_info.signedness) { .unsigned => { try f.writeCValue(writer, operand); const mask = (@as(u65, 1) << @intCast(u7, dest_bits)) - 1; try writer.print(" & {d}ULL;\n", .{mask}); return local; }, .signed => { const operand_ty = f.air.typeOf(ty_op.operand); const c_bits = toCIntBits(operand_ty.intInfo(target).bits) orelse return f.fail("TODO: C backend: implement integer types larger than 128 bits", .{}); const shift_rhs = c_bits - dest_bits; try writer.print("(int{d}_t)((uint{d}_t)", .{ c_bits, c_bits }); try f.writeCValue(writer, operand); try writer.print(" << {d}) >> {d};\n", .{ shift_rhs, shift_rhs }); return local; }, } } fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const operand = try f.resolveInst(un_op); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { const is_debug_build = f.object.dg.module.optimizeMode() == .Debug; if (!is_debug_build) return CValue.none; const writer = f.object.writer(); switch (dest_ptr) { .local_ref => |i| { const dest: CValue = .{ .local = i }; try writer.writeAll("memset(&"); try f.writeCValue(writer, dest); try writer.writeAll(", 0xaa, sizeof("); try f.writeCValue(writer, dest); try writer.writeAll("));\n"); }, .decl_ref => |decl| { const dest: CValue = .{ .decl = decl }; try writer.writeAll("memset(&"); try f.writeCValue(writer, dest); try writer.writeAll(", 0xaa, sizeof("); try f.writeCValue(writer, dest); try writer.writeAll("));\n"); }, else => { const indirection = if (dest_type.childType().zigTypeTag() == .Array) "" else "*"; try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); try writer.print(", 0xaa, sizeof({s}", .{indirection}); try f.writeCValue(writer, dest_ptr); try writer.writeAll("));\n"); }, } return CValue.none; } fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { // *a = b; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try f.resolveInst(bin_op.lhs); const src_val = try f.resolveInst(bin_op.rhs); const lhs_type = f.air.typeOf(bin_op.lhs); // TODO Sema should emit a different instruction when the store should // possibly do the safety 0xaa bytes for undefined. const src_val_is_undefined = if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false; if (src_val_is_undefined) return try airStoreUndefined(f, dest_ptr, lhs_type); // Don't check this for airStoreUndefined as that will work for arrays already if (lhs_type.childType().zigTypeTag() == .Array) return f.fail("TODO: C backend: implement airStore for arrays", .{}); const writer = f.object.writer(); switch (dest_ptr) { .local_ref => |i| { const dest: CValue = .{ .local = i }; try f.writeCValue(writer, dest); try writer.writeAll(" = "); try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); }, .decl_ref => |decl| { const dest: CValue = .{ .decl = decl }; try f.writeCValue(writer, dest); try writer.writeAll(" = "); try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); }, else => { try writer.writeAll("*"); try f.writeCValue(writer, dest_ptr); try writer.writeAll(" = "); try f.writeCValue(writer, src_val); try writer.writeAll(";\n"); }, } return CValue.none; } fn airWrapOp( f: *Function, inst: Air.Inst.Index, str_op: [*:0]const u8, fn_op: [*:0]const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const inst_ty = f.air.typeOfIndex(inst); const int_info = inst_ty.intInfo(f.object.dg.module.getTarget()); const bits = int_info.bits; // if it's an unsigned int with non-arbitrary bit size then we can just add if (int_info.signedness == .unsigned) { const ok_bits = switch (bits) { 8, 16, 32, 64, 128 => true, else => false, }; if (ok_bits or inst_ty.tag() != .int_unsigned) { return try airBinOp(f, inst, str_op); } } if (bits > 64) { return f.fail("TODO: C backend: airWrapOp for large integers", .{}); } var min_buf: [80]u8 = undefined; const min = switch (int_info.signedness) { .unsigned => "0", else => switch (inst_ty.tag()) { .c_short => "SHRT_MIN", .c_int => "INT_MIN", .c_long => "LONG_MIN", .c_longlong => "LLONG_MIN", .isize => "INTPTR_MIN", else => blk: { const val = -1 * std.math.pow(i64, 2, @intCast(i64, bits - 1)); break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, else => |e| return e, }; }, }, }; var max_buf: [80]u8 = undefined; const max = switch (inst_ty.tag()) { .c_short => "SHRT_MAX", .c_ushort => "USHRT_MAX", .c_int => "INT_MAX", .c_uint => "UINT_MAX", .c_long => "LONG_MAX", .c_ulong => "ULONG_MAX", .c_longlong => "LLONG_MAX", .c_ulonglong => "ULLONG_MAX", .isize => "INTPTR_MAX", .usize => "UINTPTR_MAX", else => blk: { const pow_bits = switch (int_info.signedness) { .signed => bits - 1, .unsigned => bits, }; const val = std.math.pow(u64, 2, pow_bits) - 1; break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, else => |e| return e, }; }, }; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const w = f.object.writer(); const ret = try f.allocLocal(inst_ty, .Mut); try w.print(" = zig_{s}", .{fn_op}); switch (inst_ty.tag()) { .isize => try w.writeAll("isize"), .c_short => try w.writeAll("short"), .c_int => try w.writeAll("int"), .c_long => try w.writeAll("long"), .c_longlong => try w.writeAll("longlong"), else => { const prefix_byte: u8 = switch (int_info.signedness) { .signed => 'i', .unsigned => 'u', }; for ([_]u8{ 8, 16, 32, 64 }) |nbits| { if (bits <= nbits) { try w.print("{c}{d}", .{ prefix_byte, nbits }); break; } } else { unreachable; } }, } try w.writeByte('('); try f.writeCValue(w, lhs); try w.writeAll(", "); try f.writeCValue(w, rhs); if (int_info.signedness == .signed) { try w.print(", {s}", .{min}); } try w.print(", {s});", .{max}); try f.object.indent_writer.insertNewline(); return ret; } fn airSatOp(f: *Function, inst: Air.Inst.Index, fn_op: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const inst_ty = f.air.typeOfIndex(inst); const int_info = inst_ty.intInfo(f.object.dg.module.getTarget()); const bits = int_info.bits; switch (bits) { 8, 16, 32, 64, 128 => {}, else => return f.object.dg.fail("TODO: C backend: airSatOp for non power of 2 integers", .{}), } // if it's an unsigned int with non-arbitrary bit size then we can just add if (bits > 64) { return f.object.dg.fail("TODO: C backend: airSatOp for large integers", .{}); } var min_buf: [80]u8 = undefined; const min = switch (int_info.signedness) { .unsigned => "0", else => switch (inst_ty.tag()) { .c_short => "SHRT_MIN", .c_int => "INT_MIN", .c_long => "LONG_MIN", .c_longlong => "LLONG_MIN", .isize => "INTPTR_MIN", else => blk: { // compute the type minimum based on the bitcount (bits) const val = -1 * std.math.pow(i65, 2, @intCast(i65, bits - 1)); break :blk std.fmt.bufPrint(&min_buf, "{d}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, else => |e| return e, }; }, }, }; var max_buf: [80]u8 = undefined; const max = switch (inst_ty.tag()) { .c_short => "SHRT_MAX", .c_ushort => "USHRT_MAX", .c_int => "INT_MAX", .c_uint => "UINT_MAX", .c_long => "LONG_MAX", .c_ulong => "ULONG_MAX", .c_longlong => "LLONG_MAX", .c_ulonglong => "ULLONG_MAX", .isize => "INTPTR_MAX", .usize => "UINTPTR_MAX", else => blk: { const pow_bits = switch (int_info.signedness) { .signed => bits - 1, .unsigned => bits, }; const val = std.math.pow(u65, 2, pow_bits) - 1; break :blk std.fmt.bufPrint(&max_buf, "{}", .{val}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, else => |e| return e, }; }, }; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const w = f.object.writer(); const ret = try f.allocLocal(inst_ty, .Mut); try w.print(" = zig_{s}", .{fn_op}); switch (inst_ty.tag()) { .isize => try w.writeAll("isize"), .c_short => try w.writeAll("short"), .c_int => try w.writeAll("int"), .c_long => try w.writeAll("long"), .c_longlong => try w.writeAll("longlong"), else => { const prefix_byte: u8 = switch (int_info.signedness) { .signed => 'i', .unsigned => 'u', }; for ([_]u8{ 8, 16, 32, 64 }) |nbits| { if (bits <= nbits) { try w.print("{c}{d}", .{ prefix_byte, nbits }); break; } } else { unreachable; } }, } try w.writeByte('('); try f.writeCValue(w, lhs); try w.writeAll(", "); try f.writeCValue(w, rhs); if (int_info.signedness == .signed) { try w.print(", {s}", .{min}); } try w.print(", {s});", .{max}); try f.object.indent_writer.insertNewline(); return ret; } fn airNot(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const op = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); if (inst_ty.zigTypeTag() == .Bool) try writer.writeAll("!") else try writer.writeAll("~"); try f.writeCValue(writer, op); try writer.writeAll(";\n"); return local; } fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, lhs); try writer.print("{s}", .{operator}); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } fn airEquality( f: *Function, inst: Air.Inst.Index, negate_prefix: []const u8, eq_op_str: []const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); const lhs_ty = f.air.typeOf(bin_op.lhs); if (lhs_ty.tag() == .optional) { // (A && B) || (C && (A == B)) // A = lhs.is_null ; B = rhs.is_null ; C = rhs.payload == lhs.payload try writer.writeAll(negate_prefix); try f.writeCValue(writer, lhs); try writer.writeAll(".is_null && "); try f.writeCValue(writer, rhs); try writer.writeAll(".is_null) || ("); try f.writeCValue(writer, lhs); try writer.writeAll(".payload == "); try f.writeCValue(writer, rhs); try writer.writeAll(".payload && "); try f.writeCValue(writer, lhs); try writer.writeAll(".is_null == "); try f.writeCValue(writer, rhs); try writer.writeAll(".is_null));\n"); return local; } try f.writeCValue(writer, lhs); try writer.writeAll(eq_op_str); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); // 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.renderType(writer, inst_ty); try writer.writeAll(")(((uintptr_t)"); try f.writeCValue(writer, lhs); try writer.print("){s}(", .{operator}); try f.writeCValue(writer, rhs); try writer.writeAll("*sizeof("); try f.renderType(writer, inst_ty.childType()); try writer.print(")));\n", .{}); return local; } fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const bin_op = f.air.instructions.items(.data)[inst].bin_op; const lhs = try f.resolveInst(bin_op.lhs); const rhs = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); // (lhs <> rhs) ? lhs : rhs try writer.writeAll(" = ("); try f.writeCValue(writer, lhs); try writer.print("{s}", .{operator}); try f.writeCValue(writer, rhs); try writer.writeAll(") "); try f.writeCValue(writer, lhs); try writer.writeAll(" : "); try f.writeCValue(writer, rhs); try writer.writeAll(";\n"); return local; } fn airSlice(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data; const ptr = try f.resolveInst(bin_op.lhs); const len = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = {"); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, len); try writer.writeAll("};\n"); return local; } fn airCall(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Call, pl_op.payload); const args = @bitCast([]const Air.Inst.Ref, f.air.extra[extra.end..][0..extra.data.args_len]); const callee_ty = f.air.typeOf(pl_op.operand); const fn_ty = switch (callee_ty.zigTypeTag()) { .Fn => callee_ty, .Pointer => callee_ty.childType(), else => unreachable, }; const ret_ty = fn_ty.fnReturnType(); const unused_result = f.liveness.isUnused(inst); const writer = f.object.writer(); var result_local: CValue = .none; if (unused_result) { if (ret_ty.hasCodeGenBits()) { try writer.print("(void)", .{}); } } else { result_local = try f.allocLocal(ret_ty, .Const); try writer.writeAll(" = "); } callee: { known: { const fn_decl = fn_decl: { const callee_val = f.air.value(pl_op.operand) orelse break :known; break :fn_decl switch (callee_val.tag()) { .extern_fn => callee_val.castTag(.extern_fn).?.data, .function => callee_val.castTag(.function).?.data.owner_decl, .decl_ref => callee_val.castTag(.decl_ref).?.data, else => break :known, }; }; try f.object.dg.renderDeclName(fn_decl, writer); break :callee; } // Fall back to function pointer call. const callee = try f.resolveInst(pl_op.operand); try f.writeCValue(writer, callee); } try writer.writeAll("("); for (args) |arg, i| { if (i != 0) { try writer.writeAll(", "); } if (f.air.value(arg)) |val| { try f.object.dg.renderValue(writer, f.air.typeOf(arg), val); } else { const val = try f.resolveInst(arg); try f.writeCValue(writer, val); } } try writer.writeAll(");\n"); return result_local; } fn airDbgStmt(f: *Function, inst: Air.Inst.Index) !CValue { const dbg_stmt = f.air.instructions.items(.data)[inst].dbg_stmt; const writer = f.object.writer(); try writer.print("#line {d}\n", .{dbg_stmt.line + 1}); 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 airBr(f: *Function, inst: Air.Inst.Index) !CValue { const branch = f.air.instructions.items(.data)[inst].br; const block = f.blocks.get(branch.block_inst).?; const result = block.result; const writer = f.object.writer(); // If result is .none then the value of the block is unused. if (result != .none) { const operand = try f.resolveInst(branch.operand); try f.writeCValue(writer, result); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); } try f.object.writer().print("goto zig_block_{d};\n", .{block.block_id}); return CValue.none; } fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.isPtrAtRuntime() and f.air.typeOf(ty_op.operand).isPtrAtRuntime()) { const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); try f.renderType(writer, inst_ty); try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } const local = try f.allocLocal(inst_ty, .Mut); try writer.writeAll(";\n"); try writer.writeAll("memcpy(&"); try f.writeCValue(writer, local); try writer.writeAll(", &"); try f.writeCValue(writer, operand); try writer.writeAll(", sizeof "); try f.writeCValue(writer, local); try writer.writeAll(");\n"); return local; } fn airBreakpoint(f: *Function) !CValue { try f.object.writer().writeAll("zig_breakpoint();\n"); return CValue.none; } fn airFence(f: *Function, inst: Air.Inst.Index) !CValue { const atomic_order = f.air.instructions.items(.data)[inst].fence; const writer = f.object.writer(); try writer.writeAll("zig_fence("); try writeMemoryOrder(writer, atomic_order); try writer.writeAll(");\n"); return CValue.none; } fn airUnreach(f: *Function) !CValue { try f.object.writer().writeAll("zig_unreachable();\n"); return CValue.none; } fn airLoop(f: *Function, inst: Air.Inst.Index) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const loop = f.air.extraData(Air.Block, ty_pl.payload); const body = f.air.extra[loop.end..][0..loop.data.body_len]; try f.object.writer().writeAll("while (true) "); try genBody(f, body); try f.object.indent_writer.insertNewline(); return CValue.none; } fn airCondBr(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const cond = try f.resolveInst(pl_op.operand); const extra = f.air.extraData(Air.CondBr, pl_op.payload); const then_body = f.air.extra[extra.end..][0..extra.data.then_body_len]; const else_body = f.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len]; const writer = f.object.writer(); try writer.writeAll("if ("); try f.writeCValue(writer, cond); try writer.writeAll(") "); try genBody(f, then_body); try writer.writeAll(" else "); try genBody(f, else_body); try f.object.indent_writer.insertNewline(); return CValue.none; } fn airSwitchBr(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const condition = try f.resolveInst(pl_op.operand); const condition_ty = f.air.typeOf(pl_op.operand); const switch_br = f.air.extraData(Air.SwitchBr, pl_op.payload); const writer = f.object.writer(); try writer.writeAll("switch ("); try f.writeCValue(writer, condition); try writer.writeAll(") {"); f.object.indent_writer.pushIndent(); var extra_index: usize = switch_br.end; var case_i: u32 = 0; while (case_i < switch_br.data.cases_len) : (case_i += 1) { const case = f.air.extraData(Air.SwitchBr.Case, extra_index); const items = @bitCast([]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).?); try writer.writeAll(": "); } // The case body must be noreturn so we don't need to insert a break. try genBody(f, case_body); } const else_body = f.air.extra[extra_index..][0..switch_br.data.else_body_len]; try f.object.indent_writer.insertNewline(); try writer.writeAll("default: "); try genBody(f, else_body); try f.object.indent_writer.insertNewline(); f.object.indent_writer.popIndent(); try writer.writeAll("}\n"); return CValue.none; } fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { const air_datas = f.air.instructions.items(.data); const air_extra = f.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload); const zir = f.object.dg.decl.getFileScope().zir; const extended = zir.instructions.items(.data)[air_extra.data.zir_index].extended; const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand); const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source); const outputs_len = @truncate(u5, extended.small); const args_len = @truncate(u5, extended.small >> 5); const clobbers_len = @truncate(u5, extended.small >> 10); _ = clobbers_len; // TODO honor these const is_volatile = @truncate(u1, extended.small >> 15) != 0; const outputs = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end..][0..outputs_len]); const args = @bitCast([]const Air.Inst.Ref, f.air.extra[air_extra.end + outputs.len ..][0..args_len]); if (outputs_len > 1) { return f.fail("TODO implement codegen for asm with more than 1 output", .{}); } if (f.liveness.isUnused(inst) and !is_volatile) return CValue.none; var extra_i: usize = zir_extra.end; const output_constraint: ?[]const u8 = out: { var i: usize = 0; while (i < outputs_len) : (i += 1) { const output = zir.extraData(Zir.Inst.Asm.Output, extra_i); extra_i = output.end; break :out zir.nullTerminatedString(output.data.constraint); } break :out null; }; const args_extra_begin = extra_i; const writer = f.object.writer(); for (args) |arg| { const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); extra_i = input.end; const constraint = zir.nullTerminatedString(input.data.constraint); if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; const arg_c_value = try f.resolveInst(arg); try writer.writeAll("register "); try f.renderType(writer, f.air.typeOf(arg)); try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); try f.writeCValue(writer, arg_c_value); try writer.writeAll(";\n"); } else { return f.fail("TODO non-explicit inline asm regs", .{}); } } const volatile_string: []const u8 = if (is_volatile) "volatile " else ""; try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, asm_source }); if (output_constraint) |_| { return f.fail("TODO: CBE inline asm output", .{}); } if (args.len > 0) { if (output_constraint == null) { try writer.writeAll(" :"); } try writer.writeAll(": "); extra_i = args_extra_begin; for (args) |_, index| { const input = zir.extraData(Zir.Inst.Asm.Input, extra_i); extra_i = input.end; const constraint = zir.nullTerminatedString(input.data.constraint); if (constraint[0] == '{' and constraint[constraint.len - 1] == '}') { const reg = constraint[1 .. constraint.len - 1]; if (index > 0) { try writer.writeAll(", "); } try writer.print("\"r\"({s}_constant)", .{reg}); } else { // This is blocked by the earlier test unreachable; } } } try writer.writeAll(");\n"); if (f.liveness.isUnused(inst)) return CValue.none; return f.fail("TODO: C backend: inline asm expression result used", .{}); } fn airIsNull( f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8, deref_suffix: [*:0]const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const operand = try f.resolveInst(un_op); const target = f.object.dg.module.getTarget(); const local = try f.allocLocal(Type.initTag(.bool), .Const); try writer.writeAll(" = ("); try f.writeCValue(writer, operand); const ty = f.air.typeOf(un_op); var opt_buf: Type.Payload.ElemType = undefined; const payload_type = if (ty.zigTypeTag() == .Pointer) ty.childType().optionalChild(&opt_buf) else ty.optionalChild(&opt_buf); if (ty.isPtrLikeOptional()) { // operand is a regular pointer, test `operand !=/== NULL` try writer.print("){s} {s} NULL;\n", .{ deref_suffix, operator }); } else if (payload_type.abiSize(target) == 0) { try writer.print("){s} {s} true;\n", .{ deref_suffix, operator }); } else { try writer.print("){s}.is_null {s} true;\n", .{ deref_suffix, operator }); } return local; } fn airOptionalPayload(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const operand_ty = f.air.typeOf(ty_op.operand); const opt_ty = if (operand_ty.zigTypeTag() == .Pointer) operand_ty.elemType() else operand_ty; if (opt_ty.isPtrLikeOptional()) { // the operand is just a regular pointer, no need to do anything special. // *?*T -> **T and ?*T -> *T are **T -> **T and *T -> *T in C return operand; } const inst_ty = f.air.typeOfIndex(inst); const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else ""; const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{maybe_addrof}); try f.writeCValue(writer, operand); try writer.print("){s}payload;\n", .{maybe_deref}); return local; } fn 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.isPtrLikeOptional()) { // The payload and the optional are the same value. // Setting to non-null will be done when the payload is set. return operand; } try writer.writeAll("("); try f.writeCValue(writer, operand); try writer.writeAll(")->is_null = false;\n"); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = &("); try f.writeCValue(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 structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struct_ptr: CValue, index: u32) !CValue { const writer = f.object.writer(); const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data; const field_name = struct_obj.fields.keys()[index]; const field_val = struct_obj.fields.values()[index]; const addrof = if (field_val.ty.zigTypeTag() == .Array) "" else "&"; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); switch (struct_ptr) { .local_ref => |i| { try writer.print(" = {s}t{d}.{};\n", .{ addrof, i, fmtIdent(field_name) }); }, else => { try writer.print(" = {s}", .{addrof}); try f.writeCValue(writer, struct_ptr); try writer.print("->{};\n", .{fmtIdent(field_name)}); }, } return local; } fn airStructFieldVal(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.StructField, ty_pl.payload).data; const writer = f.object.writer(); const struct_byval = try f.resolveInst(extra.struct_operand); const struct_ty = f.air.typeOf(extra.struct_operand); const struct_obj = struct_ty.castTag(.@"struct").?.data; const field_name = struct_obj.fields.keys()[extra.field_index]; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = "); try f.writeCValue(writer, struct_byval); try writer.print(".{};\n", .{fmtIdent(field_name)}); return local; } // *(E!T) -> E NOT *E fn airUnwrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const inst_ty = f.air.typeOfIndex(inst); const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const operand_ty = f.air.typeOf(ty_op.operand); const payload_ty = operand_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { if (operand_ty.zigTypeTag() == .Pointer) { const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = *"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } else { return operand; } } const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = ("); try f.writeCValue(writer, operand); try writer.print("){s}error;\n", .{maybe_deref}); return local; } fn airUnwrapErrUnionPay(f: *Function, inst: Air.Inst.Index, maybe_addrof: []const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const operand_ty = f.air.typeOf(ty_op.operand); const payload_ty = operand_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { return CValue.none; } const inst_ty = f.air.typeOfIndex(inst); const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else "."; const local = try f.allocLocal(inst_ty, .Const); try writer.print(" = {s}(", .{maybe_addrof}); try f.writeCValue(writer, operand); try writer.print("){s}payload;\n", .{maybe_deref}); return local; } fn airWrapOptional(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const inst_ty = f.air.typeOfIndex(inst); if (inst_ty.isPtrLikeOptional()) { // the operand is just a regular pointer, no need to do anything special. return operand; } // .wrap_optional is used to convert non-optionals into optionals so it can never be null. const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .is_null = false, .payload ="); try f.writeCValue(writer, operand); try writer.writeAll("};\n"); return local; } fn airWrapErrUnionErr(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const writer = f.object.writer(); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const operand = try f.resolveInst(ty_op.operand); const err_un_ty = f.air.typeOfIndex(inst); const payload_ty = err_un_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { return operand; } const local = try f.allocLocal(err_un_ty, .Const); try writer.writeAll(" = { .error = "); try f.writeCValue(writer, operand); try writer.writeAll(" };\n"); return local; } fn airWrapErrUnionPay(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); try writer.writeAll(" = { .error = 0, .payload = "); try f.writeCValue(writer, operand); try writer.writeAll(" };\n"); return local; } fn airIsErr( f: *Function, inst: Air.Inst.Index, deref_prefix: [*:0]const u8, deref_suffix: [*:0]const u8, op_str: [*:0]const u8, ) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const operand = try f.resolveInst(un_op); const operand_ty = f.air.typeOf(un_op); const local = try f.allocLocal(Type.initTag(.bool), .Const); const payload_ty = operand_ty.errorUnionPayload(); if (!payload_ty.hasCodeGenBits()) { try writer.print(" = {s}", .{deref_prefix}); try f.writeCValue(writer, operand); try writer.print(" {s} 0;\n", .{op_str}); } else { try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.print("{s}error {s} 0;\n", .{ deref_suffix, op_str }); } return local; } fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); const array_len = f.air.typeOf(ty_op.operand).elemType().arrayLen(); try writer.writeAll(" = { .ptr = "); try f.writeCValue(writer, operand); try writer.print(", .len = {d} }};\n", .{array_len}); return local; } /// Emits a local variable with the result type and initializes it /// with the operand. fn airSimpleCast(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); try writer.writeAll(" = "); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airPtrToInt(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const un_op = f.air.instructions.items(.data)[inst].un_op; const writer = f.object.writer(); const operand = try f.resolveInst(un_op); try writer.writeAll(" = ("); try f.renderType(writer, inst_ty); try writer.writeAll(")"); try f.writeCValue(writer, operand); try writer.writeAll(";\n"); return local; } fn airBuiltinCall(f: *Function, inst: Air.Inst.Index, fn_name: [*:0]const u8) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); // TODO implement the function in zig.h and call it here try writer.print(" = {s}(", .{fn_name}); try f.writeCValue(writer, operand); try writer.writeAll(");\n"); return local; } fn airCmpxchg(f: *Function, inst: Air.Inst.Index, flavor: [*:0]const u8) !CValue { const ty_pl = f.air.instructions.items(.data)[inst].ty_pl; const extra = f.air.extraData(Air.Cmpxchg, ty_pl.payload).data; const inst_ty = f.air.typeOfIndex(inst); const ptr = try f.resolveInst(extra.ptr); const expected_value = try f.resolveInst(extra.expected_value); const new_value = try f.resolveInst(extra.new_value); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.print(" = zig_cmpxchg_{s}(", .{flavor}); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, expected_value); try writer.writeAll(", "); try f.writeCValue(writer, new_value); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.successOrder()); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.failureOrder()); try writer.writeAll(");\n"); return local; } fn airAtomicRmw(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.AtomicRmw, pl_op.payload).data; const inst_ty = f.air.typeOfIndex(inst); const ptr = try f.resolveInst(pl_op.operand); const operand = try f.resolveInst(extra.operand); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.print(" = zig_atomicrmw_{s}(", .{toAtomicRmwSuffix(extra.op())}); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, operand); try writer.writeAll(", "); try writeMemoryOrder(writer, extra.ordering()); try writer.writeAll(");\n"); return local; } fn airAtomicLoad(f: *Function, inst: Air.Inst.Index) !CValue { const atomic_load = f.air.instructions.items(.data)[inst].atomic_load; const ptr = try f.resolveInst(atomic_load.ptr); const ptr_ty = f.air.typeOf(atomic_load.ptr); if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_atomic_load("); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try writeMemoryOrder(writer, atomic_load.order); try writer.writeAll(");\n"); return local; } fn airAtomicStore(f: *Function, inst: Air.Inst.Index, order: [*:0]const u8) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const ptr = try f.resolveInst(bin_op.lhs); const element = try f.resolveInst(bin_op.rhs); const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const writer = f.object.writer(); try writer.writeAll(" = zig_atomic_store("); try f.writeCValue(writer, ptr); try writer.writeAll(", "); try f.writeCValue(writer, element); try writer.print(", {s});\n", .{order}); return local; } fn airMemset(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Bin, pl_op.payload).data; const dest_ptr = try f.resolveInst(pl_op.operand); const value = try f.resolveInst(extra.lhs); const len = try f.resolveInst(extra.rhs); const writer = f.object.writer(); try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); try writer.writeAll(", "); try f.writeCValue(writer, value); try writer.writeAll(", "); try f.writeCValue(writer, len); try writer.writeAll(");\n"); return CValue.none; } fn airMemcpy(f: *Function, inst: Air.Inst.Index) !CValue { const pl_op = f.air.instructions.items(.data)[inst].pl_op; const extra = f.air.extraData(Air.Bin, pl_op.payload).data; const dest_ptr = try f.resolveInst(pl_op.operand); const src_ptr = try f.resolveInst(extra.lhs); const len = try f.resolveInst(extra.rhs); const writer = f.object.writer(); try writer.writeAll("memcpy("); try f.writeCValue(writer, dest_ptr); try writer.writeAll(", "); try f.writeCValue(writer, src_ptr); try writer.writeAll(", "); try f.writeCValue(writer, len); try writer.writeAll(");\n"); return CValue.none; } fn airSetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const union_ptr = try f.resolveInst(bin_op.lhs); const new_tag = try f.resolveInst(bin_op.rhs); const writer = f.object.writer(); try writer.writeAll("*"); try f.writeCValue(writer, union_ptr); try writer.writeAll(" = "); try f.writeCValue(writer, new_tag); try writer.writeAll(";\n"); return CValue.none; } fn airGetUnionTag(f: *Function, inst: Air.Inst.Index) !CValue { if (f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); const ty_op = f.air.instructions.items(.data)[inst].ty_op; const writer = f.object.writer(); const operand = try f.resolveInst(ty_op.operand); try writer.writeAll("get_union_tag("); try f.writeCValue(writer, operand); try writer.writeAll(");\n"); return local; } fn toMemoryOrder(order: std.builtin.AtomicOrder) [:0]const u8 { return switch (order) { .Unordered => "memory_order_relaxed", .Monotonic => "memory_order_consume", .Acquire => "memory_order_acquire", .Release => "memory_order_release", .AcqRel => "memory_order_acq_rel", .SeqCst => "memory_order_seq_cst", }; } fn writeMemoryOrder(w: anytype, order: std.builtin.AtomicOrder) !void { return w.writeAll(toMemoryOrder(order)); } fn toAtomicRmwSuffix(order: std.builtin.AtomicRmwOp) []const u8 { return switch (order) { .Xchg => "xchg", .Add => "add", .Sub => "sub", .And => "and", .Nand => "nand", .Or => "or", .Xor => "xor", .Max => "max", .Min => "min", }; } fn IndentWriter(comptime UnderlyingWriter: type) type { return struct { const Self = @This(); pub const Error = UnderlyingWriter.Error; pub const Writer = std.io.Writer(*Self, Error, write); pub const indent_delta = 1; underlying_writer: UnderlyingWriter, indent_count: usize = 0, current_line_empty: bool = true, pub fn writer(self: *Self) Writer { return .{ .context = self }; } pub fn write(self: *Self, bytes: []const u8) Error!usize { if (bytes.len == 0) return @as(usize, 0); const current_indent = self.indent_count * Self.indent_delta; if (self.current_line_empty and current_indent > 0) { try self.underlying_writer.writeByteNTimes(' ', current_indent); } self.current_line_empty = false; return self.writeNoIndent(bytes); } pub fn insertNewline(self: *Self) Error!void { _ = try self.writeNoIndent("\n"); } pub fn pushIndent(self: *Self) void { self.indent_count += 1; } pub fn popIndent(self: *Self) void { assert(self.indent_count != 0); self.indent_count -= 1; } fn writeNoIndent(self: *Self, bytes: []const u8) Error!usize { if (bytes.len == 0) return @as(usize, 0); try self.underlying_writer.writeAll(bytes); if (bytes[bytes.len - 1] == '\n') { self.current_line_empty = true; } return bytes.len; } }; } fn toCIntBits(zig_bits: u32) ?u32 { for (&[_]u8{ 8, 16, 32, 64, 128 }) |c_bits| { if (zig_bits <= c_bits) { return c_bits; } } return null; }