mirror of
https://github.com/ziglang/zig.git
synced 2025-12-27 00:23:22 +00:00
* There is now a main_pkg in addition to root_pkg. They are usually the
same. When using `zig test`, main_pkg is the user's source file and
root_pkg has the test runner.
* scanDecl no longer looks for test decls outside the package being
tested. honoring `--test-filter` is still TODO.
* test runner main function has a void return value rather than
`anyerror!void`
* Sema is improved to generate better AIR for for loops on slices.
* Sema: fix incorrect capacity calculation in zirBoolBr
* Sema: add compile errors for trying to use slice fields as an lvalue.
* Sema: fix type coercion for error unions
* Sema: fix analyzeVarRef generating garbage AIR
* C codegen: fix renderValue for error unions with 0 bit payload
* C codegen: implement function pointer calls
* CLI: fix usage text
Adds 4 new AIR instructions:
* slice_len, slice_ptr: to get the ptr and len fields of a slice.
* slice_elem_val, ptr_slice_elem_val: to get the element value of
a slice, and a pointer to a slice.
AstGen gains a new functionality:
* One of the unused flags of struct decls is now used to indicate
structs that are known to have non-zero size based on the AST alone.
1876 lines
68 KiB
Zig
1876 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;
|
|
|
|
// 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 => switch (t.ptrSize()) {
|
|
.Slice => unreachable,
|
|
.Many => {
|
|
if (val.castTag(.ref_val)) |ref_val_payload| {
|
|
const sub_val = ref_val_payload.data;
|
|
if (sub_val.castTag(.bytes)) |bytes_payload| {
|
|
const bytes = bytes_payload.data;
|
|
try writer.writeByte('(');
|
|
try dg.renderType(writer, t);
|
|
// TODO: make our own C string escape instead of using std.zig.fmtEscapes
|
|
try writer.print(")\"{}\"", .{std.zig.fmtEscapes(bytes)});
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
unreachable;
|
|
}
|
|
},
|
|
.One => {
|
|
var arena = std.heap.ArenaAllocator.init(dg.module.gpa);
|
|
defer arena.deinit();
|
|
|
|
const elem_ty = t.elemType();
|
|
const elem_val = try val.pointerDeref(&arena.allocator);
|
|
|
|
try writer.writeAll("&");
|
|
try dg.renderValue(writer, elem_ty, elem_val);
|
|
},
|
|
.C => 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;
|
|
|
|
// 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,
|
|
},
|
|
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),
|
|
.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),
|
|
.ref => try airRef(o, inst),
|
|
.struct_field_ptr => try airStructFieldPtr(o, inst),
|
|
.varptr => try airVarPtr(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 airVarPtr(o: *Object, inst: Air.Inst.Index) !CValue {
|
|
const ty_pl = o.air.instructions.items(.data)[inst].ty_pl;
|
|
const variable = o.air.variables[ty_pl.payload];
|
|
return CValue{ .decl_ref = variable.owner_decl };
|
|
}
|
|
|
|
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 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 airRef(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(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
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_ptr);
|
|
const struct_ptr_ty = o.air.typeOf(extra.struct_ptr);
|
|
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;
|
|
}
|
|
|
|
// *(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;
|
|
}
|
|
};
|
|
}
|