mirror of
https://github.com/ziglang/zig.git
synced 2025-12-27 08:33:15 +00:00
Decl objects need to know whether they are the owner of the Type/Value associated with them, in order to decide whether to destroy the associated Namespace, Fn, or Var when cleaning up.
1335 lines
50 KiB
Zig
1335 lines
50 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 ir = @import("../ir.zig");
|
|
const Inst = ir.Inst;
|
|
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 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: *Inst,
|
|
/// Index into the parameters
|
|
arg: usize,
|
|
/// By-value
|
|
decl: *Decl,
|
|
decl_ref: *Decl,
|
|
};
|
|
|
|
pub const CValueMap = std.AutoHashMap(*Inst, CValue);
|
|
pub const TypedefMap = std.HashMap(Type, struct { name: []const u8, rendered: []u8 }, Type.hash, Type.eql, std.hash_map.default_max_load_percentage);
|
|
|
|
fn formatTypeAsCIdentifier(
|
|
data: Type,
|
|
comptime fmt: []const u8,
|
|
options: std.fmt.FormatOptions,
|
|
writer: anytype,
|
|
) !void {
|
|
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 {
|
|
for (ident) |c, i| {
|
|
switch (c) {
|
|
'a'...'z', 'A'...'Z', '_' => try writer.writeByte(c),
|
|
'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,
|
|
gpa: *mem.Allocator,
|
|
code: std.ArrayList(u8),
|
|
value_map: CValueMap,
|
|
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: *Inst) !CValue {
|
|
if (inst.value()) |_| {
|
|
return CValue{ .constant = inst };
|
|
}
|
|
return o.value_map.get(inst).?; // Instruction does not dominate all uses!
|
|
}
|
|
|
|
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| return o.dg.renderValue(w, inst.ty, inst.value().?),
|
|
.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();
|
|
}
|
|
|
|
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, src: LazySrcLoc, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
|
|
@setCold(true);
|
|
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 dg.fail(.{ .node_offset = 0 }, "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 (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 => |e| return dg.fail(
|
|
.{ .node_offset = 0 },
|
|
"TODO: C backend: implement Pointer value {s}",
|
|
.{@tagName(e)},
|
|
),
|
|
},
|
|
.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 => {
|
|
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});
|
|
},
|
|
.ErrorUnion => {
|
|
const error_type = t.errorUnionSet();
|
|
const payload_type = t.errorUnionChild();
|
|
const data = val.castTag(.error_union).?.data;
|
|
try writer.writeByte('(');
|
|
try dg.renderType(writer, t);
|
|
try writer.writeAll("){");
|
|
if (val.getError()) |_| {
|
|
try writer.writeAll(" .error = ");
|
|
try dg.renderValue(
|
|
writer,
|
|
error_type,
|
|
data,
|
|
);
|
|
try writer.writeAll(" }");
|
|
} else {
|
|
try writer.writeAll(" .payload = ");
|
|
try dg.renderValue(
|
|
writer,
|
|
payload_type,
|
|
data,
|
|
);
|
|
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.entries.items[field_index].key;
|
|
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);
|
|
},
|
|
}
|
|
},
|
|
else => |e| return dg.fail(.{ .node_offset = 0 }, "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 ");
|
|
}
|
|
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(.{ .node_offset = 0 }, "TODO: C backend: implement integer types larger than 128 bits", .{});
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
|
|
.Float => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Float", .{}),
|
|
|
|
.Pointer => {
|
|
if (t.isSlice()) {
|
|
return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement slices", .{});
|
|
} 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_opt_{s}_t;\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.ensureCapacity(dg.typedefs.capacity() + 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.errorUnionChild();
|
|
const set_type = t.errorUnionSet();
|
|
|
|
var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
|
|
defer buffer.deinit();
|
|
const bw = buffer.writer();
|
|
|
|
try bw.writeAll("typedef struct { ");
|
|
try dg.renderType(bw, child_type);
|
|
try bw.writeAll(" payload; uint16_t error; } ");
|
|
const name_index = buffer.items.len;
|
|
try bw.print("zig_err_union_{s}_{s}_t;\n", .{ typeToCIdentifier(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.ensureCapacity(dg.typedefs.capacity() + 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");
|
|
for (struct_obj.fields.entries.items) |entry| {
|
|
try buffer.append(' ');
|
|
try dg.renderType(buffer.writer(), entry.value.ty);
|
|
try buffer.writer().print(" {s};\n", .{fmtIdent(entry.key)});
|
|
}
|
|
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.ensureCapacity(dg.typedefs.capacity() + 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(.{ .node_offset = 0 }, "TODO: C backend: implement type Union", .{}),
|
|
.Fn => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Fn", .{}),
|
|
.Opaque => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Opaque", .{}),
|
|
.Frame => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type Frame", .{}),
|
|
.AnyFrame => return dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement type AnyFrame", .{}),
|
|
.Vector => return dg.fail(.{ .node_offset = 0 }, "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 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");
|
|
|
|
const func: *Module.Fn = func_payload.data;
|
|
try o.indent_writer.insertNewline();
|
|
try o.dg.renderFunctionSignature(o.writer(), is_global);
|
|
|
|
try o.writer().writeByte(' ');
|
|
try genBody(o, func.body);
|
|
|
|
try o.indent_writer.insertNewline();
|
|
} else 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 => {},
|
|
}
|
|
}
|
|
|
|
pub fn genBody(o: *Object, body: ir.Body) error{ AnalysisFail, OutOfMemory }!void {
|
|
const writer = o.writer();
|
|
if (body.instructions.len == 0) {
|
|
try writer.writeAll("{}");
|
|
return;
|
|
}
|
|
|
|
try writer.writeAll("{\n");
|
|
o.indent_writer.pushIndent();
|
|
|
|
for (body.instructions) |inst| {
|
|
const result_value = switch (inst.tag) {
|
|
// TODO use a different strategy for add that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.add => try genBinOp(o, inst.castTag(.add).?, " + "),
|
|
// TODO make this do wrapping arithmetic for signed ints
|
|
.addwrap => try genBinOp(o, inst.castTag(.add).?, " + "),
|
|
// TODO use a different strategy for sub that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
|
// TODO make this do wrapping arithmetic for signed ints
|
|
.subwrap => try genBinOp(o, inst.castTag(.sub).?, " - "),
|
|
// TODO use a different strategy for mul that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.mul => try genBinOp(o, inst.castTag(.sub).?, " * "),
|
|
// TODO make this do wrapping multiplication for signed ints
|
|
.mulwrap => try genBinOp(o, inst.castTag(.sub).?, " * "),
|
|
// TODO use a different strategy for div that communicates to the optimizer
|
|
// that wrapping is UB.
|
|
.div => try genBinOp(o, inst.castTag(.div).?, " / "),
|
|
|
|
.constant => unreachable, // excluded from function bodies
|
|
.alloc => try genAlloc(o, inst.castTag(.alloc).?),
|
|
.arg => genArg(o),
|
|
.assembly => try genAsm(o, inst.castTag(.assembly).?),
|
|
.block => try genBlock(o, inst.castTag(.block).?),
|
|
.bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
|
|
.breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
|
|
.call => try genCall(o, inst.castTag(.call).?),
|
|
.cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "),
|
|
.cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "),
|
|
.cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "),
|
|
.cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "),
|
|
.cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "),
|
|
.cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "),
|
|
.dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
|
|
.intcast => try genIntCast(o, inst.castTag(.intcast).?),
|
|
.load => try genLoad(o, inst.castTag(.load).?),
|
|
.ret => try genRet(o, inst.castTag(.ret).?),
|
|
.retvoid => try genRetVoid(o),
|
|
.store => try genStore(o, inst.castTag(.store).?),
|
|
.unreach => try genUnreach(o, inst.castTag(.unreach).?),
|
|
.loop => try genLoop(o, inst.castTag(.loop).?),
|
|
.condbr => try genCondBr(o, inst.castTag(.condbr).?),
|
|
.br => try genBr(o, inst.castTag(.br).?),
|
|
.br_void => try genBrVoid(o, inst.castTag(.br_void).?.block),
|
|
.switchbr => try genSwitchBr(o, inst.castTag(.switchbr).?),
|
|
// bool_and and bool_or are non-short-circuit operations
|
|
.bool_and => try genBinOp(o, inst.castTag(.bool_and).?, " & "),
|
|
.bool_or => try genBinOp(o, inst.castTag(.bool_or).?, " | "),
|
|
.bit_and => try genBinOp(o, inst.castTag(.bit_and).?, " & "),
|
|
.bit_or => try genBinOp(o, inst.castTag(.bit_or).?, " | "),
|
|
.xor => try genBinOp(o, inst.castTag(.xor).?, " ^ "),
|
|
.not => try genUnOp(o, inst.castTag(.not).?, "!"),
|
|
.is_null => try genIsNull(o, inst.castTag(.is_null).?),
|
|
.is_non_null => try genIsNull(o, inst.castTag(.is_non_null).?),
|
|
.is_null_ptr => try genIsNull(o, inst.castTag(.is_null_ptr).?),
|
|
.is_non_null_ptr => try genIsNull(o, inst.castTag(.is_non_null_ptr).?),
|
|
.wrap_optional => try genWrapOptional(o, inst.castTag(.wrap_optional).?),
|
|
.optional_payload => try genOptionalPayload(o, inst.castTag(.optional_payload).?),
|
|
.optional_payload_ptr => try genOptionalPayload(o, inst.castTag(.optional_payload_ptr).?),
|
|
.ref => try genRef(o, inst.castTag(.ref).?),
|
|
.struct_field_ptr => try genStructFieldPtr(o, inst.castTag(.struct_field_ptr).?),
|
|
|
|
.is_err => try genIsErr(o, inst.castTag(.is_err).?),
|
|
.is_err_ptr => try genIsErr(o, inst.castTag(.is_err_ptr).?),
|
|
.error_to_int => try genErrorToInt(o, inst.castTag(.error_to_int).?),
|
|
.int_to_error => try genIntToError(o, inst.castTag(.int_to_error).?),
|
|
|
|
.unwrap_errunion_payload => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload).?),
|
|
.unwrap_errunion_err => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err).?),
|
|
.unwrap_errunion_payload_ptr => try genUnwrapErrUnionPay(o, inst.castTag(.unwrap_errunion_payload_ptr).?),
|
|
.unwrap_errunion_err_ptr => try genUnwrapErrUnionErr(o, inst.castTag(.unwrap_errunion_err_ptr).?),
|
|
.wrap_errunion_payload => try genWrapErrUnionPay(o, inst.castTag(.wrap_errunion_payload).?),
|
|
.wrap_errunion_err => try genWrapErrUnionErr(o, inst.castTag(.wrap_errunion_err).?),
|
|
.br_block_flat => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for br_block_flat", .{}),
|
|
.ptrtoint => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for ptrtoint", .{}),
|
|
.varptr => try genVarPtr(o, inst.castTag(.varptr).?),
|
|
.floatcast => return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement codegen for floatcast", .{}),
|
|
};
|
|
switch (result_value) {
|
|
.none => {},
|
|
else => try o.value_map.putNoClobber(inst, result_value),
|
|
}
|
|
}
|
|
|
|
o.indent_writer.popIndent();
|
|
try writer.writeAll("}");
|
|
}
|
|
|
|
fn genVarPtr(o: *Object, inst: *Inst.VarPtr) !CValue {
|
|
return CValue{ .decl_ref = inst.variable.owner_decl };
|
|
}
|
|
|
|
fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
|
|
const writer = o.writer();
|
|
|
|
// First line: the variable used as data storage.
|
|
const elem_type = alloc.base.ty.elemType();
|
|
const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;
|
|
const local = try o.allocLocal(elem_type, mutability);
|
|
try writer.writeAll(";\n");
|
|
|
|
return CValue{ .local_ref = local.local };
|
|
}
|
|
|
|
fn genArg(o: *Object) CValue {
|
|
const i = o.next_arg_index;
|
|
o.next_arg_index += 1;
|
|
return .{ .arg = i };
|
|
}
|
|
|
|
fn genRetVoid(o: *Object) !CValue {
|
|
try o.writer().print("return;\n", .{});
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const operand = try o.resolveInst(inst.operand);
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(inst.base.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 genRet(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const operand = try o.resolveInst(inst.operand);
|
|
const writer = o.writer();
|
|
try writer.writeAll("return ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
if (inst.base.isUnused())
|
|
return CValue.none;
|
|
|
|
const from = try o.resolveInst(inst.operand);
|
|
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.dg.renderType(writer, inst.base.ty);
|
|
try writer.writeAll(")");
|
|
try o.writeCValue(writer, from);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
}
|
|
|
|
fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
|
|
// *a = b;
|
|
const dest_ptr = try o.resolveInst(inst.lhs);
|
|
const src_val = try o.resolveInst(inst.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 genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
|
|
if (inst.base.isUnused())
|
|
return CValue.none;
|
|
|
|
const lhs = try o.resolveInst(inst.lhs);
|
|
const rhs = try o.resolveInst(inst.rhs);
|
|
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, lhs);
|
|
try writer.writeAll(operator);
|
|
try o.writeCValue(writer, rhs);
|
|
try writer.writeAll(";\n");
|
|
|
|
return local;
|
|
}
|
|
|
|
fn genUnOp(o: *Object, inst: *Inst.UnOp, operator: []const u8) !CValue {
|
|
if (inst.base.isUnused())
|
|
return CValue.none;
|
|
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const writer = o.writer();
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
|
|
try writer.print(" = {s}", .{operator});
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
|
|
return local;
|
|
}
|
|
|
|
fn genCall(o: *Object, inst: *Inst.Call) !CValue {
|
|
if (inst.func.castTag(.constant)) |func_inst| {
|
|
const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|
|
|
extern_fn.data
|
|
else if (func_inst.val.castTag(.function)) |func_payload|
|
|
func_payload.data.owner_decl
|
|
else
|
|
unreachable;
|
|
|
|
const fn_ty = fn_decl.ty;
|
|
const ret_ty = fn_ty.fnReturnType();
|
|
const unused_result = inst.base.isUnused();
|
|
var result_local: CValue = .none;
|
|
|
|
const writer = o.writer();
|
|
if (unused_result) {
|
|
if (ret_ty.hasCodeGenBits()) {
|
|
try writer.print("(void)", .{});
|
|
}
|
|
} else {
|
|
result_local = try o.allocLocal(ret_ty, .Const);
|
|
try writer.writeAll(" = ");
|
|
}
|
|
const fn_name = mem.spanZ(fn_decl.name);
|
|
try writer.print("{s}(", .{fn_name});
|
|
if (inst.args.len != 0) {
|
|
for (inst.args) |arg, i| {
|
|
if (i > 0) {
|
|
try writer.writeAll(", ");
|
|
}
|
|
if (arg.value()) |val| {
|
|
try o.dg.renderValue(writer, arg.ty, val);
|
|
} else {
|
|
const val = try o.resolveInst(arg);
|
|
try o.writeCValue(writer, val);
|
|
}
|
|
}
|
|
}
|
|
try writer.writeAll(");\n");
|
|
return result_local;
|
|
} else {
|
|
return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: implement function pointers", .{});
|
|
}
|
|
}
|
|
|
|
fn genDbgStmt(o: *Object, inst: *Inst.DbgStmt) !CValue {
|
|
// TODO emit #line directive here with line number and filename
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genBlock(o: *Object, inst: *Inst.Block) !CValue {
|
|
const block_id: usize = o.next_block_index;
|
|
o.next_block_index += 1;
|
|
const writer = o.writer();
|
|
|
|
// store the block id in relocs.capacity as it is not used for anything else in the C backend.
|
|
inst.codegen.relocs.capacity = block_id;
|
|
const result = if (inst.base.ty.tag() != .void and !inst.base.isUnused()) blk: {
|
|
// allocate a location for the result
|
|
const local = try o.allocLocal(inst.base.ty, .Mut);
|
|
try writer.writeAll(";\n");
|
|
break :blk local;
|
|
} else CValue{ .none = {} };
|
|
|
|
inst.codegen.mcv = @bitCast(@import("../codegen.zig").AnyMCValue, result);
|
|
try genBody(o, inst.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 genBr(o: *Object, inst: *Inst.Br) !CValue {
|
|
const result = @bitCast(CValue, inst.block.codegen.mcv);
|
|
const writer = o.writer();
|
|
|
|
// If result is .none then the value of the block is unused.
|
|
if (inst.operand.ty.tag() != .void and result != .none) {
|
|
const operand = try o.resolveInst(inst.operand);
|
|
try o.writeCValue(writer, result);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
}
|
|
|
|
return genBrVoid(o, inst.block);
|
|
}
|
|
|
|
fn genBrVoid(o: *Object, block: *Inst.Block) !CValue {
|
|
try o.writer().print("goto zig_block_{d};\n", .{block.codegen.relocs.capacity});
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const writer = o.writer();
|
|
if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) {
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.dg.renderType(writer, inst.base.ty);
|
|
|
|
try writer.writeAll(")");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
}
|
|
|
|
const local = try o.allocLocal(inst.base.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 genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {
|
|
try o.writer().writeAll("zig_breakpoint();\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {
|
|
try o.writer().writeAll("zig_unreachable();\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genLoop(o: *Object, inst: *Inst.Loop) !CValue {
|
|
try o.writer().writeAll("while (true) ");
|
|
try genBody(o, inst.body);
|
|
try o.indent_writer.insertNewline();
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genCondBr(o: *Object, inst: *Inst.CondBr) !CValue {
|
|
const cond = try o.resolveInst(inst.condition);
|
|
const writer = o.writer();
|
|
|
|
try writer.writeAll("if (");
|
|
try o.writeCValue(writer, cond);
|
|
try writer.writeAll(") ");
|
|
try genBody(o, inst.then_body);
|
|
try writer.writeAll(" else ");
|
|
try genBody(o, inst.else_body);
|
|
try o.indent_writer.insertNewline();
|
|
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genSwitchBr(o: *Object, inst: *Inst.SwitchBr) !CValue {
|
|
const target = try o.resolveInst(inst.target);
|
|
const writer = o.writer();
|
|
|
|
try writer.writeAll("switch (");
|
|
try o.writeCValue(writer, target);
|
|
try writer.writeAll(") {\n");
|
|
o.indent_writer.pushIndent();
|
|
|
|
for (inst.cases) |case| {
|
|
try writer.writeAll("case ");
|
|
try o.dg.renderValue(writer, inst.target.ty, case.item);
|
|
try writer.writeAll(": ");
|
|
// the case body must be noreturn so we don't need to insert a break
|
|
try genBody(o, case.body);
|
|
try o.indent_writer.insertNewline();
|
|
}
|
|
|
|
try writer.writeAll("default: ");
|
|
try genBody(o, inst.else_body);
|
|
try o.indent_writer.insertNewline();
|
|
|
|
o.indent_writer.popIndent();
|
|
try writer.writeAll("}\n");
|
|
return CValue.none;
|
|
}
|
|
|
|
fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
|
|
if (as.base.isUnused() and !as.is_volatile)
|
|
return CValue.none;
|
|
|
|
const writer = o.writer();
|
|
for (as.inputs) |i, index| {
|
|
if (i[0] == '{' and i[i.len - 1] == '}') {
|
|
const reg = i[1 .. i.len - 1];
|
|
const arg = as.args[index];
|
|
const arg_c_value = try o.resolveInst(arg);
|
|
try writer.writeAll("register ");
|
|
try o.dg.renderType(writer, arg.ty);
|
|
|
|
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(.{ .node_offset = 0 }, "TODO non-explicit inline asm regs", .{});
|
|
}
|
|
}
|
|
const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
|
|
try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
|
|
if (as.output_constraint) |_| {
|
|
return o.dg.fail(.{ .node_offset = 0 }, "TODO: CBE inline asm output", .{});
|
|
}
|
|
if (as.inputs.len > 0) {
|
|
if (as.output_constraint == null) {
|
|
try writer.writeAll(" :");
|
|
}
|
|
try writer.writeAll(": ");
|
|
for (as.inputs) |i, index| {
|
|
if (i[0] == '{' and i[i.len - 1] == '}') {
|
|
const reg = i[1 .. i.len - 1];
|
|
const arg = as.args[index];
|
|
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 (as.base.isUnused())
|
|
return CValue.none;
|
|
|
|
return o.dg.fail(.{ .node_offset = 0 }, "TODO: C backend: inline asm expression result used", .{});
|
|
}
|
|
|
|
fn genIsNull(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const invert_logic = inst.base.tag == .is_non_null or inst.base.tag == .is_non_null_ptr;
|
|
const operator = if (invert_logic) "!=" else "==";
|
|
const maybe_deref = if (inst.base.tag == .is_null_ptr or inst.base.tag == .is_non_null_ptr) "[0]" else "";
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const local = try o.allocLocal(Type.initTag(.bool), .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.writeCValue(writer, operand);
|
|
|
|
if (inst.operand.ty.isPtrLikeOptional()) {
|
|
// operand is a regular pointer, test `operand !=/== NULL`
|
|
try writer.print("){s} {s} NULL;\n", .{ maybe_deref, operator });
|
|
} else {
|
|
try writer.print("){s}.is_null {s} true;\n", .{ maybe_deref, operator });
|
|
}
|
|
return local;
|
|
}
|
|
|
|
fn genOptionalPayload(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const opt_ty = if (inst.operand.ty.zigTypeTag() == .Pointer)
|
|
inst.operand.ty.elemType()
|
|
else
|
|
inst.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 maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
|
|
const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else "";
|
|
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.print(" = {s}(", .{maybe_addrof});
|
|
try o.writeCValue(writer, operand);
|
|
|
|
try writer.print("){s}payload;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
|
|
fn genRef(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.writeAll(" = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(";\n");
|
|
return local;
|
|
}
|
|
|
|
fn genStructFieldPtr(o: *Object, inst: *Inst.StructFieldPtr) !CValue {
|
|
const writer = o.writer();
|
|
const struct_ptr = try o.resolveInst(inst.struct_ptr);
|
|
const struct_obj = inst.struct_ptr.ty.elemType().castTag(.@"struct").?.data;
|
|
const field_name = struct_obj.fields.entries.items[inst.field_index].key;
|
|
|
|
const local = try o.allocLocal(inst.base.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 genUnwrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
|
|
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.writeCValue(writer, operand);
|
|
|
|
try writer.print("){s}error;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
fn genUnwrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const maybe_deref = if (inst.operand.ty.zigTypeTag() == .Pointer) "->" else ".";
|
|
const maybe_addrof = if (inst.base.ty.zigTypeTag() == .Pointer) "&" else "";
|
|
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.print(" = {s}(", .{maybe_addrof});
|
|
try o.writeCValue(writer, operand);
|
|
|
|
try writer.print("){s}payload;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
|
|
fn genWrapOptional(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
if (inst.base.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.base.ty, .Const);
|
|
try writer.writeAll(" = { .is_null = false, .payload =");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll("};\n");
|
|
return local;
|
|
}
|
|
fn genWrapErrUnionErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.writeAll(" = { .error = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(" };\n");
|
|
return local;
|
|
}
|
|
fn genWrapErrUnionPay(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const local = try o.allocLocal(inst.base.ty, .Const);
|
|
try writer.writeAll(" = { .error = 0, .payload = ");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.writeAll(" };\n");
|
|
return local;
|
|
}
|
|
|
|
fn genIsErr(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
const writer = o.writer();
|
|
const maybe_deref = if (inst.base.tag == .is_err_ptr) "[0]" else "";
|
|
const operand = try o.resolveInst(inst.operand);
|
|
|
|
const local = try o.allocLocal(Type.initTag(.bool), .Const);
|
|
try writer.writeAll(" = (");
|
|
try o.writeCValue(writer, operand);
|
|
try writer.print("){s}.error != 0;\n", .{maybe_deref});
|
|
return local;
|
|
}
|
|
|
|
fn genIntToError(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
return o.resolveInst(inst.operand);
|
|
}
|
|
|
|
fn genErrorToInt(o: *Object, inst: *Inst.UnOp) !CValue {
|
|
return o.resolveInst(inst.operand);
|
|
}
|
|
|
|
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;
|
|
}
|
|
};
|
|
}
|