mirror of
https://github.com/ziglang/zig.git
synced 2025-12-27 16:43:07 +00:00
After this change, the frontend and backend cooperate to keep track of which Decls are actually emitted into the machine code. When any backend sees a `decl_ref` Value, it must mark the corresponding Decl `alive` field to true. This prevents unused comptime data from spilling into the output object files. For example, if you do an `inline for` loop, previously, any intermediate value calculations would have gone into the object file. Now they are garbage collected immediately after the owner Decl has its machine code generated. In the frontend, when it is time to send a Decl to the linker, if it has not been marked "alive" then it is deleted instead. Additional improvements: * Resolve type ABI layouts after successful semantic analysis of a Decl. This is needed so that the backend has access to struct fields. * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return "not comptime known" instead of a compile error for global variables. * `Value.pointerDeref` now returns `null` in the case that the pointer deref cannot happen at compile-time. This is true for global variables, for example. Another example is if a comptime known pointer has a hard coded address value. * Binary arithmetic sets the requireRuntimeBlock source location to the lhs_src or rhs_src as appropriate instead of on the operator node. * Fix LLVM codegen for slice_elem_val which had the wrong logic for when the operand was not a pointer. As noted in the comment in the implementation of deleteUnusedDecl, a future improvement will be to rework the frontend/linker interface to remove the frontend's responsibility of calling allocateDeclIndexes. I discovered some issues with the plan9 linker backend that are related to this, and worked around them for now.
1863 lines
68 KiB
Zig
1863 lines
68 KiB
Zig
const std = @import("std");
|
|
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 };
|
|
|
|
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,
|
|
};
|
|
|
|
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.
|
|
/// It is not available when generating .h file.
|
|
pub const Object = struct {
|
|
dg: DeclGen,
|
|
air: Air,
|
|
liveness: Liveness,
|
|
gpa: *mem.Allocator,
|
|
code: std.ArrayList(u8),
|
|
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,
|
|
indent_writer: IndentWriter(std.ArrayList(u8).Writer),
|
|
|
|
fn resolveInst(o: *Object, inst: Air.Inst.Ref) !CValue {
|
|
if (o.air.value(inst)) |_| {
|
|
return CValue{ .constant = inst };
|
|
}
|
|
const index = Air.refToIndex(inst).?;
|
|
return o.value_map.get(index).?; // Assertion means instruction does not dominate usage.
|
|
}
|
|
|
|
fn allocLocalValue(o: *Object) CValue {
|
|
const result = o.next_local_index;
|
|
o.next_local_index += 1;
|
|
return .{ .local = result };
|
|
}
|
|
|
|
fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
|
|
const local_value = o.allocLocalValue();
|
|
try o.renderTypeAndName(o.writer(), ty, local_value, mutability);
|
|
return local_value;
|
|
}
|
|
|
|
fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
|
|
return o.indent_writer.writer();
|
|
}
|
|
|
|
fn writeCValue(o: *Object, 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 => |inst| {
|
|
const ty = o.air.typeOf(inst);
|
|
const val = o.air.value(inst).?;
|
|
return o.dg.renderValue(w, ty, val);
|
|
},
|
|
.arg => |i| return w.print("a{d}", .{i}),
|
|
.decl => |decl| return w.writeAll(mem.span(decl.name)),
|
|
.decl_ref => |decl| return w.print("&{s}", .{decl.name}),
|
|
}
|
|
}
|
|
|
|
fn renderTypeAndName(
|
|
o: *Object,
|
|
w: anytype,
|
|
ty: Type,
|
|
name: CValue,
|
|
mutability: Mutability,
|
|
) error{ OutOfMemory, AnalysisFail }!void {
|
|
var suffix = std.ArrayList(u8).init(o.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();
|
|
}
|
|
|
|
if (render_ty.zigTypeTag() == .Fn) {
|
|
const ret_ty = render_ty.fnReturnType();
|
|
if (ret_ty.zigTypeTag() == .NoReturn) {
|
|
// noreturn attribute is not allowed here.
|
|
try w.writeAll("void");
|
|
} else {
|
|
try o.dg.renderType(w, ret_ty);
|
|
}
|
|
try w.writeAll(" (*");
|
|
switch (mutability) {
|
|
.Const => try w.writeAll("const "),
|
|
.Mut => {},
|
|
}
|
|
try o.writeCValue(w, name);
|
|
try w.writeAll(")(");
|
|
const param_len = render_ty.fnParamLen();
|
|
const is_var_args = render_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 o.dg.renderType(w, render_ty.fnParamType(index));
|
|
}
|
|
}
|
|
if (is_var_args) {
|
|
if (param_len != 0) try w.writeAll(", ");
|
|
try w.writeAll("...");
|
|
}
|
|
try w.writeByte(')');
|
|
} else {
|
|
try o.dg.renderType(w, render_ty);
|
|
|
|
const const_prefix = switch (mutability) {
|
|
.Const => "const ",
|
|
.Mut => "",
|
|
};
|
|
try w.print(" {s}", .{const_prefix});
|
|
try o.writeCValue(w, name);
|
|
}
|
|
try w.writeAll(suffix.items);
|
|
}
|
|
};
|
|
|
|
/// This data is available both when outputting .c code and when outputting an .h file.
|
|
pub const DeclGen = struct {
|
|
module: *Module,
|
|
decl: *Decl,
|
|
fwd_decl: std.ArrayList(u8),
|
|
error_msg: ?*Module.ErrorMsg,
|
|
typedefs: TypedefMap,
|
|
|
|
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.toSrcLocWithDecl(dg.decl);
|
|
dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, src_loc, format, args);
|
|
return error.AnalysisFail;
|
|
}
|
|
|
|
fn renderValue(
|
|
dg: *DeclGen,
|
|
writer: anytype,
|
|
t: Type,
|
|
val: Value,
|
|
) error{ OutOfMemory, AnalysisFail }!void {
|
|
if (val.isUndef()) {
|
|
// 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("{}");
|
|
//return dg.fail("TODO: C backend: implement renderValue undef", .{});
|
|
}
|
|
switch (t.zigTypeTag()) {
|
|
.Int => {
|
|
if (t.isSignedInt())
|
|
return writer.print("{d}", .{val.toSignedInt()});
|
|
return writer.print("{d}", .{val.toUnsignedInt()});
|
|
},
|
|
.Pointer => switch (t.ptrSize()) {
|
|
.Slice => {
|
|
try writer.writeByte('(');
|
|
try dg.renderType(writer, t);
|
|
try writer.writeAll("){");
|
|
var buf: Type.Payload.ElemType = undefined;
|
|
try dg.renderValue(writer, t.slicePtrFieldType(&buf), val);
|
|
try writer.writeAll(", ");
|
|
try writer.print("{d}", .{val.sliceLen()});
|
|
try writer.writeAll("}");
|
|
},
|
|
else => switch (val.tag()) {
|
|
.null_value, .zero => try writer.writeAll("NULL"),
|
|
.one => try writer.writeAll("1"),
|
|
.decl_ref => {
|
|
const decl = val.castTag(.decl_ref).?.data;
|
|
decl.alive = true;
|
|
|
|
// Determine if we must pointer cast.
|
|
assert(decl.has_tv);
|
|
if (t.eql(decl.ty)) {
|
|
try writer.print("&{s}", .{decl.name});
|
|
} else {
|
|
try writer.writeAll("(");
|
|
try dg.renderType(writer, t);
|
|
try writer.print(")&{s}", .{decl.name});
|
|
}
|
|
},
|
|
.function => {
|
|
const func = val.castTag(.function).?.data;
|
|
try writer.print("{s}", .{func.owner_decl.name});
|
|
},
|
|
.extern_fn => {
|
|
const decl = val.castTag(.extern_fn).?.data;
|
|
try writer.print("{s}", .{decl.name});
|
|
},
|
|
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 = t.arrayLen();
|
|
const elem_ty = t.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 (t.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 child_type = t.optionalChild(&opt_buf);
|
|
if (t.isPtrLikeOptional()) {
|
|
return dg.renderValue(writer, child_type, val);
|
|
}
|
|
try writer.writeByte('(');
|
|
try dg.renderType(writer, t);
|
|
if (val.tag() == .null_value) {
|
|
try writer.writeAll("){ .is_null = true }");
|
|
} else {
|
|
try writer.writeAll("){ .is_null = false, .payload = ");
|
|
try dg.renderValue(writer, child_type, val);
|
|
try writer.writeAll(" }");
|
|
}
|
|
},
|
|
.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 = t.errorUnionSet();
|
|
const payload_type = t.errorUnionPayload();
|
|
const sub_val = val.castTag(.error_union).?.data;
|
|
|
|
if (!payload_type.hasCodeGenBits()) {
|
|
// We use the error type directly as the type.
|
|
return dg.renderValue(writer, error_type, sub_val);
|
|
}
|
|
|
|
try writer.writeByte('(');
|
|
try dg.renderType(writer, t);
|
|
try writer.writeAll("){");
|
|
if (val.getError()) |_| {
|
|
try writer.writeAll(" .error = ");
|
|
try dg.renderValue(
|
|
writer,
|
|
error_type,
|
|
sub_val,
|
|
);
|
|
try writer.writeAll(" }");
|
|
} else {
|
|
try writer.writeAll(" .payload = ");
|
|
try dg.renderValue(
|
|
writer,
|
|
payload_type,
|
|
sub_val,
|
|
);
|
|
try writer.writeAll(", .error = 0 }");
|
|
}
|
|
},
|
|
.Enum => {
|
|
switch (val.tag()) {
|
|
.enum_field_index => {
|
|
const field_index = val.castTag(.enum_field_index).?.data;
|
|
switch (t.tag()) {
|
|
.enum_simple => return writer.print("{d}", .{field_index}),
|
|
.enum_full, .enum_nonexhaustive => {
|
|
const enum_full = t.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 = t.intTagType(&int_tag_ty_buffer);
|
|
return dg.renderValue(writer, int_tag_ty, val);
|
|
},
|
|
}
|
|
},
|
|
.Fn => switch (val.tag()) {
|
|
.null_value, .zero => try writer.writeAll("NULL"),
|
|
.one => try writer.writeAll("1"),
|
|
.decl_ref => {
|
|
const decl = val.castTag(.decl_ref).?.data;
|
|
decl.alive = true;
|
|
|
|
// Determine if we must pointer cast.
|
|
assert(decl.has_tv);
|
|
if (t.eql(decl.ty)) {
|
|
try writer.print("&{s}", .{decl.name});
|
|
} else {
|
|
try writer.writeAll("(");
|
|
try dg.renderType(writer, t);
|
|
try writer.print(")&{s}", .{decl.name});
|
|
}
|
|
},
|
|
.function => {
|
|
const decl = val.castTag(.function).?.data.owner_decl;
|
|
decl.alive = true;
|
|
try writer.print("{s}", .{decl.name});
|
|
},
|
|
.extern_fn => {
|
|
const decl = val.castTag(.extern_fn).?.data;
|
|
decl.alive = true;
|
|
try writer.print("{s}", .{decl.name});
|
|
},
|
|
else => unreachable,
|
|
},
|
|
else => |e| return dg.fail("TODO: C backend: implement value {s}", .{
|
|
@tagName(e),
|
|
}),
|
|
}
|
|
}
|
|
|
|
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 ");
|
|
}
|
|
}
|
|
try dg.renderType(w, dg.decl.ty.fnReturnType());
|
|
const decl_name = mem.span(dg.decl.name);
|
|
try w.print(" {s}(", .{decl_name});
|
|
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 renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void {
|
|
switch (t.zigTypeTag()) {
|
|
.NoReturn => {
|
|
try w.writeAll("zig_noreturn void");
|
|
},
|
|
.Void => try w.writeAll("void"),
|
|
.Bool => try w.writeAll("bool"),
|
|
.Int => {
|
|
switch (t.tag()) {
|
|
.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"),
|
|
.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(dg.module.getTarget());
|
|
const sign_prefix = switch (info.signedness) {
|
|
.signed => "",
|
|
.unsigned => "u",
|
|
};
|
|
inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
|
|
if (info.bits <= nbits) {
|
|
try w.print("{s}int{d}_t", .{ sign_prefix, nbits });
|
|
break;
|
|
}
|
|
} else {
|
|
return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{});
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
|
|
.Float => return dg.fail("TODO: C backend: implement type Float", .{}),
|
|
|
|
.Pointer => {
|
|
if (t.isSlice()) {
|
|
if (dg.typedefs.get(t)) |some| {
|
|
return w.writeAll(some.name);
|
|
}
|
|
|
|
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);
|
|
try bw.writeAll(" *");
|
|
if (t.isConstPtr()) {
|
|
try bw.writeAll("const ");
|
|
}
|
|
if (t.isVolatilePtr()) {
|
|
try bw.writeAll("volatile ");
|
|
}
|
|
try bw.writeAll("ptr; size_t len; } ");
|
|
const name_index = buffer.items.len;
|
|
try bw.print("zig_L_{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);
|
|
try w.writeAll(name);
|
|
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
|
} else {
|
|
try dg.renderType(w, t.elemType());
|
|
try w.writeAll(" *");
|
|
if (t.isConstPtr()) {
|
|
try w.writeAll("const ");
|
|
}
|
|
if (t.isVolatilePtr()) {
|
|
try w.writeAll("volatile ");
|
|
}
|
|
}
|
|
},
|
|
.Array => {
|
|
try dg.renderType(w, t.elemType());
|
|
try w.writeAll(" *");
|
|
},
|
|
.Optional => {
|
|
var opt_buf: Type.Payload.ElemType = undefined;
|
|
const child_type = t.optionalChild(&opt_buf);
|
|
if (t.isPtrLikeOptional()) {
|
|
return dg.renderType(w, child_type);
|
|
} else if (dg.typedefs.get(t)) |some| {
|
|
return w.writeAll(some.name);
|
|
}
|
|
|
|
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);
|
|
try w.writeAll(name);
|
|
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
|
},
|
|
.ErrorSet => {
|
|
comptime std.debug.assert(Type.initTag(.anyerror).abiSize(std.Target.current) == 2);
|
|
try w.writeAll("uint16_t");
|
|
},
|
|
.ErrorUnion => {
|
|
if (dg.typedefs.get(t)) |some| {
|
|
return w.writeAll(some.name);
|
|
}
|
|
const child_type = t.errorUnionPayload();
|
|
const err_set_type = t.errorUnionSet();
|
|
|
|
if (!child_type.hasCodeGenBits()) {
|
|
return dg.renderType(w, err_set_type);
|
|
}
|
|
|
|
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.print("zig_E_{s};\n", .{func.owner_decl.name});
|
|
} 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);
|
|
try w.writeAll(name);
|
|
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
|
},
|
|
.Struct => {
|
|
if (dg.typedefs.get(t)) |some| {
|
|
return w.writeAll(some.name);
|
|
}
|
|
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| {
|
|
try buffer.append(' ');
|
|
try dg.renderType(buffer.writer(), entry.value_ptr.ty);
|
|
try buffer.writer().print(" {s};\n", .{fmtIdent(entry.key_ptr.*)});
|
|
}
|
|
}
|
|
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);
|
|
try w.writeAll(name);
|
|
dg.typedefs.putAssumeCapacityNoClobber(t, .{ .name = name, .rendered = rendered });
|
|
},
|
|
.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 => return dg.fail("TODO: C backend: implement type Union", .{}),
|
|
.Fn => {
|
|
try dg.renderType(w, t.fnReturnType());
|
|
try w.writeAll(" (*)(");
|
|
const param_len = t.fnParamLen();
|
|
const is_var_args = t.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, t.fnParamType(index));
|
|
}
|
|
}
|
|
if (is_var_args) {
|
|
if (param_len != 0) try w.writeAll(", ");
|
|
try w.writeAll("...");
|
|
}
|
|
try w.writeByte(')');
|
|
},
|
|
.Opaque => return dg.fail("TODO: C backend: implement type Opaque", .{}),
|
|
.Frame => return dg.fail("TODO: C backend: implement type Frame", .{}),
|
|
.AnyFrame => return dg.fail("TODO: C backend: implement type AnyFrame", .{}),
|
|
.Vector => return dg.fail("TODO: C backend: implement type Vector", .{}),
|
|
|
|
.Null,
|
|
.Undefined,
|
|
.EnumLiteral,
|
|
.ComptimeFloat,
|
|
.ComptimeInt,
|
|
.Type,
|
|
=> unreachable, // must be const or comptime
|
|
|
|
.BoundFn => unreachable, // this type will be deleted from the language
|
|
}
|
|
}
|
|
|
|
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,
|
|
}
|
|
}
|
|
};
|
|
|
|
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.castTag(.function)) |func_payload| {
|
|
const func: *Module.Fn = func_payload.data;
|
|
if (func.owner_decl == o.dg.decl) {
|
|
const is_global = o.dg.declIsGlobal(tv);
|
|
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 = o.air.getMainBody();
|
|
try genBody(o, main_body);
|
|
|
|
try o.indent_writer.insertNewline();
|
|
return;
|
|
}
|
|
}
|
|
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);
|
|
const fwd_decl_writer = o.dg.fwd_decl.writer();
|
|
if (is_global or variable.is_extern) {
|
|
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);
|
|
const decl_name = mem.span(o.dg.decl.name);
|
|
try fwd_decl_writer.print(" {s};\n", .{decl_name});
|
|
|
|
try o.indent_writer.insertNewline();
|
|
const w = o.writer();
|
|
try o.dg.renderType(w, o.dg.decl.ty);
|
|
try w.print(" {s} = ", .{decl_name});
|
|
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.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(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfMemory }!void {
|
|
const writer = o.writer();
|
|
if (body.len == 0) {
|
|
try writer.writeAll("{}");
|
|
return;
|
|
}
|
|
|
|
try writer.writeAll("{\n");
|
|
o.indent_writer.pushIndent();
|
|
|
|
const air_tags = o.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(o),
|
|
|
|
.breakpoint => try airBreakpoint(o),
|
|
.unreach => try airUnreach(o),
|
|
|
|
// TODO use a different strategy for add that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.add => try airBinOp( o, inst, " + "),
|
|
.addwrap => try airWrapOp(o, inst, " + ", "addw_"),
|
|
// TODO use a different strategy for sub that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.sub => try airBinOp( o, inst, " - "),
|
|
.subwrap => try airWrapOp(o, inst, " - ", "subw_"),
|
|
// TODO use a different strategy for mul that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.mul => try airBinOp( o, inst, " * "),
|
|
.mulwrap => try airWrapOp(o, inst, " * ", "mulw_"),
|
|
// TODO use a different strategy for div that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.div => try airBinOp( o, inst, " / "),
|
|
|
|
.cmp_eq => try airBinOp(o, inst, " == "),
|
|
.cmp_gt => try airBinOp(o, inst, " > "),
|
|
.cmp_gte => try airBinOp(o, inst, " >= "),
|
|
.cmp_lt => try airBinOp(o, inst, " < "),
|
|
.cmp_lte => try airBinOp(o, inst, " <= "),
|
|
.cmp_neq => try airBinOp(o, inst, " != "),
|
|
|
|
// bool_and and bool_or are non-short-circuit operations
|
|
.bool_and => try airBinOp(o, inst, " & "),
|
|
.bool_or => try airBinOp(o, inst, " | "),
|
|
.bit_and => try airBinOp(o, inst, " & "),
|
|
.bit_or => try airBinOp(o, inst, " | "),
|
|
.xor => try airBinOp(o, inst, " ^ "),
|
|
|
|
.not => try airNot( o, inst),
|
|
|
|
.optional_payload => try airOptionalPayload(o, inst),
|
|
.optional_payload_ptr => try airOptionalPayload(o, inst),
|
|
|
|
.is_err => try airIsErr(o, inst, "", ".", "!="),
|
|
.is_non_err => try airIsErr(o, inst, "", ".", "=="),
|
|
.is_err_ptr => try airIsErr(o, inst, "*", "->", "!="),
|
|
.is_non_err_ptr => try airIsErr(o, inst, "*", "->", "=="),
|
|
|
|
.is_null => try airIsNull(o, inst, "==", ""),
|
|
.is_non_null => try airIsNull(o, inst, "!=", ""),
|
|
.is_null_ptr => try airIsNull(o, inst, "==", "[0]"),
|
|
.is_non_null_ptr => try airIsNull(o, inst, "!=", "[0]"),
|
|
|
|
.alloc => try airAlloc(o, inst),
|
|
.assembly => try airAsm(o, inst),
|
|
.block => try airBlock(o, inst),
|
|
.bitcast => try airBitcast(o, inst),
|
|
.call => try airCall(o, inst),
|
|
.dbg_stmt => try airDbgStmt(o, inst),
|
|
.intcast => try airIntCast(o, inst),
|
|
.bool_to_int => try airBoolToInt(o, inst),
|
|
.load => try airLoad(o, inst),
|
|
.ret => try airRet(o, inst),
|
|
.store => try airStore(o, inst),
|
|
.loop => try airLoop(o, inst),
|
|
.cond_br => try airCondBr(o, inst),
|
|
.br => try airBr(o, inst),
|
|
.switch_br => try airSwitchBr(o, inst),
|
|
.wrap_optional => try airWrapOptional(o, inst),
|
|
.struct_field_ptr => try airStructFieldPtr(o, inst),
|
|
.struct_field_val => try airStructFieldVal(o, inst),
|
|
.slice_ptr => try airSliceField(o, inst, ".ptr;\n"),
|
|
.slice_len => try airSliceField(o, inst, ".len;\n"),
|
|
|
|
.slice_elem_val => try airSliceElemVal(o, inst, "["),
|
|
.ptr_slice_elem_val => try airSliceElemVal(o, inst, "[0]["),
|
|
|
|
.unwrap_errunion_payload => try airUnwrapErrUnionPay(o, inst),
|
|
.unwrap_errunion_err => try airUnwrapErrUnionErr(o, inst),
|
|
.unwrap_errunion_payload_ptr => try airUnwrapErrUnionPay(o, inst),
|
|
.unwrap_errunion_err_ptr => try airUnwrapErrUnionErr(o, inst),
|
|
.wrap_errunion_payload => try airWrapErrUnionPay(o, inst),
|
|
.wrap_errunion_err => try airWrapErrUnionErr(o, inst),
|
|
|
|
.ptrtoint => return o.dg.fail("TODO: C backend: implement codegen for ptrtoint", .{}),
|
|
.floatcast => return o.dg.fail("TODO: C backend: implement codegen for floatcast", .{}),
|
|
// zig fmt: on
|
|
};
|
|
switch (result_value) {
|
|
.none => {},
|
|
else => try o.value_map.putNoClobber(inst, result_value),
|
|
}
|
|
}
|
|
|
|
o.indent_writer.popIndent();
|
|
try writer.writeAll("}");
|
|
}
|
|
|
|
fn airSliceField(o: *Object, inst: Air.Inst.Index, suffix: []const u8) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(Type.initTag(.usize), .Const);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(suffix);
|
|
return local;
|
|
}
|
|
|
|
fn airSliceElemVal(o: *Object, inst: Air.Inst.Index, prefix: []const u8) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const bin_op = o.air.instructions.items(.data)[inst].bin_op;
|
|
const slice = try o.resolveInst(bin_op.lhs);
|
|
const index = try o.resolveInst(bin_op.rhs);
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(o.air.typeOfIndex(inst), .Const);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, slice);
|
|
try writer.writeAll(prefix);
|
|
try o.writeCValue(writer, index);
|
|
try writer.writeAll("];\n");
|
|
return local;
|
|
}
|
|
|
|
fn airAlloc(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const writer = o.writer();
|
|
const inst_ty = o.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 o.allocLocal(elem_type, mutability);
|
|
try writer.writeAll(";\n");
|
|
|
|
return CValue{ .local_ref = local.local };
|
|
}
|
|
|
|
fn airArg(o: *Object) CValue {
|
|
const i = o.next_arg_index;
|
|
o.next_arg_index += 1;
|
|
return .{ .arg = i };
|
|
}
|
|
|
|
fn airLoad(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const is_volatile = o.air.typeOf(ty_op.operand).isVolatilePtr();
|
|
if (!is_volatile and o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
switch (operand) {
|
|
.local_ref => |i| {
|
|
const wrapped: CValue = .{ .local = i };
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, wrapped);
|
|
try writer.writeAll(";\n");
|
|
},
|
|
.decl_ref => |decl| {
|
|
const wrapped: CValue = .{ .decl = decl };
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, wrapped);
|
|
try writer.writeAll(";\n");
|
|
},
|
|
else => {
|
|
try writer.writeAll(" = *");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
},
|
|
}
|
|
return local;
|
|
}
|
|
|
|
fn airRet(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const un_op = o.air.instructions.items(.data)[inst].un_op;
|
|
const writer = o.writer();
|
|
if (o.air.typeOf(un_op).hasCodeGenBits()) {
|
|
const operand = try o.resolveInst(un_op);
|
|
try writer.writeAll("return ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
} else {
|
|
try writer.writeAll("return;\n");
|
|
}
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airIntCast(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const from = try o.resolveInst(ty_op.operand);
|
|
|
|
const writer = o.writer();
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.dg.renderType(writer, inst_ty);
|
|
try writer.writeAll(")");
|
|
try o.writeCValue(writer, from);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
}
|
|
|
|
fn airBoolToInt(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
const un_op = o.air.instructions.items(.data)[inst].un_op;
|
|
const writer = o.writer();
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const operand = try o.resolveInst(un_op);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
}
|
|
|
|
fn airStore(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
// *a = b;
|
|
const bin_op = o.air.instructions.items(.data)[inst].bin_op;
|
|
const dest_ptr = try o.resolveInst(bin_op.lhs);
|
|
const src_val = try o.resolveInst(bin_op.rhs);
|
|
|
|
const writer = o.writer();
|
|
switch (dest_ptr) {
|
|
.local_ref => |i| {
|
|
const dest: CValue = .{ .local = i };
|
|
try o.writeCValue(writer, dest);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, src_val);
|
|
try writer.writeAll(";\n");
|
|
},
|
|
.decl_ref => |decl| {
|
|
const dest: CValue = .{ .decl = decl };
|
|
try o.writeCValue(writer, dest);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, src_val);
|
|
try writer.writeAll(";\n");
|
|
},
|
|
else => {
|
|
try writer.writeAll("*");
|
|
try o.writeCValue(writer, dest_ptr);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, src_val);
|
|
try writer.writeAll(";\n");
|
|
},
|
|
}
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airWrapOp(
|
|
o: *Object,
|
|
inst: Air.Inst.Index,
|
|
str_op: [*:0]const u8,
|
|
fn_op: [*:0]const u8,
|
|
) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const bin_op = o.air.instructions.items(.data)[inst].bin_op;
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const int_info = inst_ty.intInfo(o.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(o, inst, str_op);
|
|
}
|
|
}
|
|
|
|
if (bits > 64) {
|
|
return o.dg.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 o.resolveInst(bin_op.lhs);
|
|
const rhs = try o.resolveInst(bin_op.rhs);
|
|
const w = o.writer();
|
|
|
|
const ret = try o.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 o.writeCValue(w, lhs);
|
|
try w.writeAll(", ");
|
|
try o.writeCValue(w, rhs);
|
|
|
|
if (int_info.signedness == .signed) {
|
|
try w.print(", {s}", .{min});
|
|
}
|
|
|
|
try w.print(", {s});", .{max});
|
|
try o.indent_writer.insertNewline();
|
|
|
|
return ret;
|
|
}
|
|
|
|
fn airNot(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const op = try o.resolveInst(ty_op.operand);
|
|
|
|
const writer = o.writer();
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
|
|
try writer.writeAll(" = ");
|
|
if (inst_ty.zigTypeTag() == .Bool)
|
|
try writer.writeAll("!")
|
|
else
|
|
try writer.writeAll("~");
|
|
try o.writeCValue(writer, op);
|
|
try writer.writeAll(";\n");
|
|
|
|
return local;
|
|
}
|
|
|
|
fn airBinOp(o: *Object, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const bin_op = o.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try o.resolveInst(bin_op.lhs);
|
|
const rhs = try o.resolveInst(bin_op.rhs);
|
|
|
|
const writer = o.writer();
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, lhs);
|
|
try writer.print("{s}", .{operator});
|
|
try o.writeCValue(writer, rhs);
|
|
try writer.writeAll(";\n");
|
|
|
|
return local;
|
|
}
|
|
|
|
fn airCall(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const pl_op = o.air.instructions.items(.data)[inst].pl_op;
|
|
const extra = o.air.extraData(Air.Call, pl_op.payload);
|
|
const args = @bitCast([]const Air.Inst.Ref, o.air.extra[extra.end..][0..extra.data.args_len]);
|
|
const fn_ty = o.air.typeOf(pl_op.operand);
|
|
const ret_ty = fn_ty.fnReturnType();
|
|
const unused_result = o.liveness.isUnused(inst);
|
|
const writer = o.writer();
|
|
|
|
var result_local: CValue = .none;
|
|
if (unused_result) {
|
|
if (ret_ty.hasCodeGenBits()) {
|
|
try writer.print("(void)", .{});
|
|
}
|
|
} else {
|
|
result_local = try o.allocLocal(ret_ty, .Const);
|
|
try writer.writeAll(" = ");
|
|
}
|
|
|
|
if (o.air.value(pl_op.operand)) |func_val| {
|
|
const fn_decl = if (func_val.castTag(.extern_fn)) |extern_fn|
|
|
extern_fn.data
|
|
else if (func_val.castTag(.function)) |func_payload|
|
|
func_payload.data.owner_decl
|
|
else
|
|
unreachable;
|
|
|
|
try writer.writeAll(mem.spanZ(fn_decl.name));
|
|
} else {
|
|
const callee = try o.resolveInst(pl_op.operand);
|
|
try o.writeCValue(writer, callee);
|
|
}
|
|
|
|
try writer.writeAll("(");
|
|
for (args) |arg, i| {
|
|
if (i != 0) {
|
|
try writer.writeAll(", ");
|
|
}
|
|
if (o.air.value(arg)) |val| {
|
|
try o.dg.renderValue(writer, o.air.typeOf(arg), val);
|
|
} else {
|
|
const val = try o.resolveInst(arg);
|
|
try o.writeCValue(writer, val);
|
|
}
|
|
}
|
|
try writer.writeAll(");\n");
|
|
return result_local;
|
|
}
|
|
|
|
fn airDbgStmt(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const dbg_stmt = o.air.instructions.items(.data)[inst].dbg_stmt;
|
|
const writer = o.writer();
|
|
try writer.print("#line {d}\n", .{dbg_stmt.line + 1});
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airBlock(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = o.air.extraData(Air.Block, ty_pl.payload);
|
|
const body = o.air.extra[extra.end..][0..extra.data.body_len];
|
|
|
|
const block_id: usize = o.next_block_index;
|
|
o.next_block_index += 1;
|
|
const writer = o.writer();
|
|
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const result = if (inst_ty.tag() != .void and !o.liveness.isUnused(inst)) blk: {
|
|
// allocate a location for the result
|
|
const local = try o.allocLocal(inst_ty, .Mut);
|
|
try writer.writeAll(";\n");
|
|
break :blk local;
|
|
} else CValue{ .none = {} };
|
|
|
|
try o.blocks.putNoClobber(o.gpa, inst, .{
|
|
.block_id = block_id,
|
|
.result = result,
|
|
});
|
|
|
|
try genBody(o, body);
|
|
try o.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(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const branch = o.air.instructions.items(.data)[inst].br;
|
|
const block = o.blocks.get(branch.block_inst).?;
|
|
const result = block.result;
|
|
const writer = o.writer();
|
|
|
|
// If result is .none then the value of the block is unused.
|
|
if (result != .none) {
|
|
const operand = try o.resolveInst(branch.operand);
|
|
try o.writeCValue(writer, result);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
}
|
|
|
|
try o.writer().print("goto zig_block_{d};\n", .{block.block_id});
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airBitcast(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
|
|
const writer = o.writer();
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
if (inst_ty.zigTypeTag() == .Pointer and
|
|
o.air.typeOf(ty_op.operand).zigTypeTag() == .Pointer)
|
|
{
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.dg.renderType(writer, inst_ty);
|
|
|
|
try writer.writeAll(")");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
}
|
|
|
|
const local = try o.allocLocal(inst_ty, .Mut);
|
|
try writer.writeAll(";\n");
|
|
|
|
try writer.writeAll("memcpy(&");
|
|
try o.writeCValue(writer, local);
|
|
try writer.writeAll(", &");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(", sizeof ");
|
|
try o.writeCValue(writer, local);
|
|
try writer.writeAll(");\n");
|
|
|
|
return local;
|
|
}
|
|
|
|
fn airBreakpoint(o: *Object) !CValue {
|
|
try o.writer().writeAll("zig_breakpoint();\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airUnreach(o: *Object) !CValue {
|
|
try o.writer().writeAll("zig_unreachable();\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airLoop(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
|
|
const loop = o.air.extraData(Air.Block, ty_pl.payload);
|
|
const body = o.air.extra[loop.end..][0..loop.data.body_len];
|
|
try o.writer().writeAll("while (true) ");
|
|
try genBody(o, body);
|
|
try o.indent_writer.insertNewline();
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airCondBr(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const pl_op = o.air.instructions.items(.data)[inst].pl_op;
|
|
const cond = try o.resolveInst(pl_op.operand);
|
|
const extra = o.air.extraData(Air.CondBr, pl_op.payload);
|
|
const then_body = o.air.extra[extra.end..][0..extra.data.then_body_len];
|
|
const else_body = o.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
|
|
const writer = o.writer();
|
|
|
|
try writer.writeAll("if (");
|
|
try o.writeCValue(writer, cond);
|
|
try writer.writeAll(") ");
|
|
try genBody(o, then_body);
|
|
try writer.writeAll(" else ");
|
|
try genBody(o, else_body);
|
|
try o.indent_writer.insertNewline();
|
|
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airSwitchBr(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const pl_op = o.air.instructions.items(.data)[inst].pl_op;
|
|
const condition = try o.resolveInst(pl_op.operand);
|
|
const condition_ty = o.air.typeOf(pl_op.operand);
|
|
const switch_br = o.air.extraData(Air.SwitchBr, pl_op.payload);
|
|
const writer = o.writer();
|
|
|
|
try writer.writeAll("switch (");
|
|
try o.writeCValue(writer, condition);
|
|
try writer.writeAll(") {");
|
|
o.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 = o.air.extraData(Air.SwitchBr.Case, extra_index);
|
|
const items = @bitCast([]const Air.Inst.Ref, o.air.extra[case.end..][0..case.data.items_len]);
|
|
const case_body = o.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 o.indent_writer.insertNewline();
|
|
try writer.writeAll("case ");
|
|
try o.dg.renderValue(writer, condition_ty, o.air.value(item).?);
|
|
try writer.writeAll(": ");
|
|
}
|
|
// The case body must be noreturn so we don't need to insert a break.
|
|
try genBody(o, case_body);
|
|
}
|
|
|
|
const else_body = o.air.extra[extra_index..][0..switch_br.data.else_body_len];
|
|
try o.indent_writer.insertNewline();
|
|
try writer.writeAll("default: ");
|
|
try genBody(o, else_body);
|
|
try o.indent_writer.insertNewline();
|
|
|
|
o.indent_writer.popIndent();
|
|
try writer.writeAll("}\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn airAsm(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const air_datas = o.air.instructions.items(.data);
|
|
const air_extra = o.air.extraData(Air.Asm, air_datas[inst].ty_pl.payload);
|
|
const zir = o.dg.decl.namespace.file_scope.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, o.air.extra[air_extra.end..][0..outputs_len]);
|
|
const args = @bitCast([]const Air.Inst.Ref, o.air.extra[air_extra.end + outputs.len ..][0..args_len]);
|
|
|
|
if (outputs_len > 1) {
|
|
return o.dg.fail("TODO implement codegen for asm with more than 1 output", .{});
|
|
}
|
|
|
|
if (o.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 = o.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 o.resolveInst(arg);
|
|
try writer.writeAll("register ");
|
|
try o.dg.renderType(writer, o.air.typeOf(arg));
|
|
|
|
try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg });
|
|
try o.writeCValue(writer, arg_c_value);
|
|
try writer.writeAll(";\n");
|
|
} else {
|
|
return o.dg.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 o.dg.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 (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
return o.dg.fail("TODO: C backend: inline asm expression result used", .{});
|
|
}
|
|
|
|
fn airIsNull(
|
|
o: *Object,
|
|
inst: Air.Inst.Index,
|
|
operator: [*:0]const u8,
|
|
deref_suffix: [*:0]const u8,
|
|
) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const un_op = o.air.instructions.items(.data)[inst].un_op;
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(un_op);
|
|
|
|
const local = try o.allocLocal(Type.initTag(.bool), .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.writeCValue(writer, operand);
|
|
|
|
if (o.air.typeOf(un_op).isPtrLikeOptional()) {
|
|
// operand is a regular pointer, test `operand !=/== NULL`
|
|
try writer.print("){s} {s} NULL;\n", .{ deref_suffix, operator });
|
|
} else {
|
|
try writer.print("){s}.is_null {s} true;\n", .{ deref_suffix, operator });
|
|
}
|
|
return local;
|
|
}
|
|
|
|
fn airOptionalPayload(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
const operand_ty = o.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 = o.air.typeOfIndex(inst);
|
|
const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
|
|
const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else "";
|
|
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.print(" = {s}(", .{maybe_addrof});
|
|
try o.writeCValue(writer, operand);
|
|
|
|
try writer.print("){s}payload;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
|
|
fn airStructFieldPtr(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = o.air.extraData(Air.StructField, ty_pl.payload).data;
|
|
const writer = o.writer();
|
|
const struct_ptr = try o.resolveInst(extra.struct_operand);
|
|
const struct_ptr_ty = o.air.typeOf(extra.struct_operand);
|
|
const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data;
|
|
const field_name = struct_obj.fields.keys()[extra.field_index];
|
|
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
switch (struct_ptr) {
|
|
.local_ref => |i| {
|
|
try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) });
|
|
},
|
|
else => {
|
|
try writer.writeAll(" = &");
|
|
try o.writeCValue(writer, struct_ptr);
|
|
try writer.print("->{};\n", .{fmtIdent(field_name)});
|
|
},
|
|
}
|
|
return local;
|
|
}
|
|
|
|
fn airStructFieldVal(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = o.air.extraData(Air.StructField, ty_pl.payload).data;
|
|
const writer = o.writer();
|
|
const struct_byval = try o.resolveInst(extra.struct_operand);
|
|
const struct_ty = o.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 = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, struct_byval);
|
|
try writer.print(".{};\n", .{fmtIdent(field_name)});
|
|
return local;
|
|
}
|
|
|
|
// *(E!T) -> E NOT *E
|
|
fn airUnwrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
const operand_ty = o.air.typeOf(ty_op.operand);
|
|
|
|
const payload_ty = operand_ty.errorUnionPayload();
|
|
if (!payload_ty.hasCodeGenBits()) {
|
|
if (operand_ty.zigTypeTag() == .Pointer) {
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = *");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
} else {
|
|
return operand;
|
|
}
|
|
}
|
|
|
|
const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
|
|
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.writeCValue(writer, operand);
|
|
|
|
try writer.print("){s}error;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
|
|
fn airUnwrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
const operand_ty = o.air.typeOf(ty_op.operand);
|
|
|
|
const payload_ty = operand_ty.errorUnionPayload();
|
|
if (!payload_ty.hasCodeGenBits()) {
|
|
return CValue.none;
|
|
}
|
|
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const maybe_deref = if (operand_ty.zigTypeTag() == .Pointer) "->" else ".";
|
|
const maybe_addrof = if (inst_ty.zigTypeTag() == .Pointer) "&" else "";
|
|
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.print(" = {s}(", .{maybe_addrof});
|
|
try o.writeCValue(writer, operand);
|
|
|
|
try writer.print("){s}payload;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
|
|
fn airWrapOptional(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
|
|
const inst_ty = o.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 o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = { .is_null = false, .payload =");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll("};\n");
|
|
return local;
|
|
}
|
|
fn airWrapErrUnionErr(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const writer = o.writer();
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = { .error = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(" };\n");
|
|
return local;
|
|
}
|
|
|
|
fn airWrapErrUnionPay(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const ty_op = o.air.instructions.items(.data)[inst].ty_op;
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(ty_op.operand);
|
|
|
|
const inst_ty = o.air.typeOfIndex(inst);
|
|
const local = try o.allocLocal(inst_ty, .Const);
|
|
try writer.writeAll(" = { .error = 0, .payload = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(" };\n");
|
|
return local;
|
|
}
|
|
|
|
fn airIsErr(
|
|
o: *Object,
|
|
inst: Air.Inst.Index,
|
|
deref_prefix: [*:0]const u8,
|
|
deref_suffix: [*:0]const u8,
|
|
op_str: [*:0]const u8,
|
|
) !CValue {
|
|
if (o.liveness.isUnused(inst))
|
|
return CValue.none;
|
|
|
|
const un_op = o.air.instructions.items(.data)[inst].un_op;
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(un_op);
|
|
const operand_ty = o.air.typeOf(un_op);
|
|
const local = try o.allocLocal(Type.initTag(.bool), .Const);
|
|
const payload_ty = operand_ty.errorUnionPayload();
|
|
if (!payload_ty.hasCodeGenBits()) {
|
|
try writer.print(" = {s}", .{deref_prefix});
|
|
try o.writeCValue(writer, operand);
|
|
try writer.print(" {s} 0;\n", .{op_str});
|
|
} else {
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.print("{s}error {s} 0;\n", .{ deref_suffix, op_str });
|
|
}
|
|
return local;
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
}
|