aboutsummaryrefslogtreecommitdiff
path: root/src/codegen/c.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-02-25 21:04:23 -0700
committerAndrew Kelley <andrew@ziglang.org>2021-02-25 21:04:23 -0700
commit0b58b617998b79a765b54f88fbe90ca2798b3d3e (patch)
treeca6cc4b6bcc2b93166d196049ee49416afe781ad /src/codegen/c.zig
parentdc325669e360f7a9dfa24f85a62fa386529dade6 (diff)
parentfd208d9d5913a0929e444deb97b91092c427bb14 (diff)
downloadzig-0b58b617998b79a765b54f88fbe90ca2798b3d3e.tar.gz
zig-0b58b617998b79a765b54f88fbe90ca2798b3d3e.zip
Merge remote-tracking branch 'origin/master' into llvm12
Conflicts: * src/clang.zig * src/llvm.zig - this file got moved to src/llvm/bindings.zig in master branch so I had to put the new LLVM arch/os enum tags into it. * lib/std/target.zig, src/stage1/target.cpp - haiku had an inconsistency with its default target ABI, gnu vs eabi. In this commit we make it gnu in both places to match the latest changes by @hoanga. * src/translate_c.zig
Diffstat (limited to 'src/codegen/c.zig')
-rw-r--r--src/codegen/c.zig995
1 files changed, 757 insertions, 238 deletions
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index e562ce320e..a885b984ac 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1,312 +1,774 @@
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 Inst = @import("../ir.zig").Inst;
+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 mem = std.mem;
+const trace = @import("../tracy.zig").trace;
-const indentation = " ";
+const Mutability = enum { Const, Mut };
-/// Maps a name from Zig source to C. Currently, this will always give the same
-/// output for any given input, sometimes resulting in broken identifiers.
-fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
- return allocator.dupe(u8, name);
-}
+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,
+};
-fn renderType(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type) !void {
- switch (T.zigTypeTag()) {
- .NoReturn => {
- try writer.writeAll("zig_noreturn void");
- },
- .Void => try writer.writeAll("void"),
- .Int => {
- if (T.tag() == .u8) {
- ctx.file.need_stdint = true;
- try writer.writeAll("uint8_t");
- } else if (T.tag() == .usize) {
- ctx.file.need_stddef = true;
- try writer.writeAll("size_t");
- } else {
- return ctx.file.fail(ctx.decl.src(), "TODO implement int types", .{});
- }
- },
- else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement type {}", .{e}),
+pub const CValueMap = std.AutoHashMap(*Inst, CValue);
+
+/// 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 renderValue(ctx: *Context, writer: std.ArrayList(u8).Writer, T: Type, val: Value) !void {
- switch (T.zigTypeTag()) {
- .Int => {
- if (T.isSignedInt())
- return writer.print("{}", .{val.toSignedInt()});
- return writer.print("{}", .{val.toUnsignedInt()});
- },
- else => |e| return ctx.file.fail(ctx.decl.src(), "TODO implement value {}", .{e}),
+ fn allocLocalValue(o: *Object) CValue {
+ const result = o.next_local_index;
+ o.next_local_index += 1;
+ return .{ .local = result };
}
-}
-fn renderFunctionSignature(ctx: *Context, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
- const tv = decl.typed_value.most_recent.typed_value;
- try renderType(ctx, writer, tv.ty.fnReturnType());
- // Use the child allocator directly, as we know the name can be freed before
- // the rest of the arena.
- const name = try map(ctx.arena.child_allocator, mem.spanZ(decl.name));
- defer ctx.arena.child_allocator.free(name);
- try writer.print(" {}(", .{name});
- var param_len = tv.ty.fnParamLen();
- if (param_len == 0)
- try writer.writeAll("void")
- else {
- var index: usize = 0;
- while (index < param_len) : (index += 1) {
- if (index > 0) {
- try writer.writeAll(", ");
- }
- try renderType(ctx, writer, tv.ty.fnParamType(index));
- try writer.print(" arg{}", .{index});
- }
+ 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;
}
- try writer.writeByte(')');
-}
-pub fn generate(file: *C, decl: *Decl) !void {
- switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
- .Fn => try genFn(file, decl),
- .Array => try genArray(file, decl),
- else => |e| return file.fail(decl.src(), "TODO {}", .{e}),
+ fn writer(o: *Object) IndentWriter(std.ArrayList(u8).Writer).Writer {
+ return o.indent_writer.writer();
}
-}
-fn genArray(file: *C, decl: *Decl) !void {
- const tv = decl.typed_value.most_recent.typed_value;
- // TODO: prevent inline asm constants from being emitted
- const name = try map(file.base.allocator, mem.span(decl.name));
- defer file.base.allocator.free(name);
- if (tv.val.cast(Value.Payload.Bytes)) |payload|
- if (tv.ty.sentinel()) |sentinel|
- if (sentinel.toUnsignedInt() == 0)
- // TODO: static by default
- try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data })
- else
- return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{})
- else
- return file.fail(decl.src(), "TODO byte arrays without sentinels", .{})
- else
- return file.fail(decl.src(), "TODO non-byte arrays", .{});
-}
+ 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)),
+ }
+ }
+
+ 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 Context = struct {
- file: *C,
+ 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,
- inst_map: *std.AutoHashMap(*Inst, []u8),
- arena: *std.heap.ArenaAllocator,
- argdex: usize = 0,
- unnamed_index: usize = 0,
-
- fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
- if (inst.cast(Inst.Constant)) |const_inst| {
- var out = std.ArrayList(u8).init(&self.arena.allocator);
- try renderValue(self, out.writer(), inst.ty, const_inst.val);
- return out.toOwnedSlice();
+ fwd_decl: std.ArrayList(u8),
+ error_msg: ?*Module.ErrorMsg,
+
+ fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
+ dg.error_msg = try Module.ErrorMsg.create(dg.module.gpa, .{
+ .file_scope = dg.decl.getFileScope(),
+ .byte_offset = src,
+ }, format, args);
+ return error.AnalysisFail;
+ }
+
+ fn renderValue(
+ dg: *DeclGen,
+ writer: anytype,
+ t: Type,
+ val: Value,
+ ) error{ OutOfMemory, AnalysisFail }!void {
+ if (val.isUndef()) {
+ return dg.fail(dg.decl.src(), "TODO: C backend: properly handle undefined in all cases (with debug safety?)", .{});
}
- if (self.inst_map.get(inst)) |val| {
- return val;
+ switch (t.zigTypeTag()) {
+ .Int => {
+ if (t.isSignedInt())
+ return writer.print("{d}", .{val.toSignedInt()});
+ return writer.print("{d}", .{val.toUnsignedInt()});
+ },
+ .Pointer => switch (val.tag()) {
+ .undef, .zero => try writer.writeAll("0"),
+ .one => try writer.writeAll("1"),
+ .decl_ref => {
+ const decl = val.castTag(.decl_ref).?.data;
+
+ // Determine if we must pointer cast.
+ const decl_tv = decl.typed_value.most_recent.typed_value;
+ if (t.eql(decl_tv.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(
+ dg.decl.src(),
+ "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()}),
+ else => |e| return dg.fail(dg.decl.src(), "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 ");
+ }
+ const tv = dg.decl.typed_value.most_recent.typed_value;
+ try dg.renderType(w, tv.ty.fnReturnType());
+ const decl_name = mem.span(dg.decl.name);
+ try w.print(" {s}(", .{decl_name});
+ var param_len = tv.ty.fnParamLen();
+ if (param_len == 0)
+ 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, tv.ty.fnParamType(index));
+ try w.print(" a{d}", .{index});
+ }
}
- unreachable;
+ try w.writeByte(')');
}
- fn name(self: *Context) ![]u8 {
- const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{}", .{self.unnamed_index});
- self.unnamed_index += 1;
- return val;
+ 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(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
+ }
+ },
+ else => unreachable,
+ }
+ },
+ .Pointer => {
+ if (t.isSlice()) {
+ return dg.fail(dg.decl.src(), "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(" *");
+ },
+ .Null, .Undefined => unreachable, // must be const or comptime
+ else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
+ @tagName(e),
+ }),
+ }
}
- fn deinit(self: *Context) void {
- self.* = undefined;
+ fn functionIsGlobal(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);
+ },
+ else => unreachable,
+ }
}
};
-fn genFn(file: *C, decl: *Decl) !void {
- const writer = file.main.writer();
- const tv = decl.typed_value.most_recent.typed_value;
-
- var arena = std.heap.ArenaAllocator.init(file.base.allocator);
- defer arena.deinit();
- var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
- defer inst_map.deinit();
- var ctx = Context{
- .file = file,
- .decl = decl,
- .arena = &arena,
- .inst_map = &inst_map,
- };
- defer ctx.deinit();
-
- try renderFunctionSignature(&ctx, writer, decl);
-
- try writer.writeAll(" {");
-
- const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
- const instructions = func.analysis.success.instructions;
- if (instructions.len > 0) {
- try writer.writeAll("\n");
- for (instructions) |inst| {
- if (switch (inst.tag) {
- .assembly => try genAsm(&ctx, inst.castTag(.assembly).?),
- .call => try genCall(&ctx, inst.castTag(.call).?),
- .add => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "+"),
- .sub => try genBinOp(&ctx, inst.cast(Inst.BinOp).?, "-"),
- .ret => try genRet(&ctx, inst.castTag(.ret).?),
- .retvoid => try genRetVoid(&ctx),
- .arg => try genArg(&ctx),
- .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
- .breakpoint => try genBreak(&ctx, inst.castTag(.breakpoint).?),
- .unreach => try genUnreach(&ctx, inst.castTag(.unreach).?),
- .intcast => try genIntCast(&ctx, inst.castTag(.intcast).?),
- else => |e| return file.fail(decl.src(), "TODO implement C codegen for {}", .{e}),
- }) |name| {
- try ctx.inst_map.putNoClobber(inst, name);
+pub fn genDecl(o: *Object) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const tv = o.dg.decl.typed_value.most_recent.typed_value;
+
+ if (tv.val.castTag(.function)) |func_payload| {
+ const is_global = o.dg.functionIsGlobal(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 {
+ 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 = dg.decl.typed_value.most_recent.typed_value;
+ const writer = dg.fwd_decl.writer();
+
+ switch (tv.ty.zigTypeTag()) {
+ .Fn => {
+ const is_global = dg.functionIsGlobal(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) {
+ .constant => unreachable, // excluded from function bodies
+ .add => try genBinOp(o, inst.castTag(.add).?, " + "),
+ .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).?),
+ .sub => try genBinOp(o, inst.castTag(.sub).?, " - "),
+ .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).?, "!"),
+ else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
+ };
+ switch (result_value) {
+ .none => {},
+ else => try o.value_map.putNoClobber(inst, result_value),
}
}
- try writer.writeAll("}\n\n");
+ o.indent_writer.popIndent();
+ try writer.writeAll("}");
+}
+
+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 genArg(ctx: *Context) !?[]u8 {
- const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{}", .{ctx.argdex});
- ctx.argdex += 1;
- return name;
+fn genRetVoid(o: *Object) !CValue {
+ try o.writer().print("return;\n", .{});
+ return CValue.none;
}
-fn genRetVoid(ctx: *Context) !?[]u8 {
- try ctx.file.main.writer().print(indentation ++ "return;\n", .{});
- return null;
+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");
+ },
+ else => {
+ try writer.writeAll(" = *");
+ try o.writeCValue(writer, operand);
+ try writer.writeAll(";\n");
+ },
+ }
+ return local;
}
-fn genRet(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
- return ctx.file.fail(ctx.decl.src(), "TODO return", .{});
+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(ctx: *Context, inst: *Inst.UnOp) !?[]u8 {
+fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
if (inst.base.isUnused())
- return null;
- const op = inst.operand;
- const writer = ctx.file.main.writer();
- const name = try ctx.name();
- const from = try ctx.resolveInst(inst.operand);
- try writer.writeAll(indentation ++ "const ");
- try renderType(ctx, writer, inst.base.ty);
- try writer.print(" {} = (", .{name});
- try renderType(ctx, writer, inst.base.ty);
- try writer.print("){};\n", .{from});
- return name;
+ 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 genBinOp(ctx: *Context, inst: *Inst.BinOp, comptime operator: []const u8) !?[]u8 {
+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");
+ },
+ 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 null;
- const lhs = ctx.resolveInst(inst.lhs);
- const rhs = ctx.resolveInst(inst.rhs);
- const writer = ctx.file.main.writer();
- const name = try ctx.name();
- try writer.writeAll(indentation ++ "const ");
- try renderType(ctx, writer, inst.base.ty);
- try writer.print(" {} = {} " ++ operator ++ " {};\n", .{ name, lhs, rhs });
- return name;
+ 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 genCall(ctx: *Context, inst: *Inst.Call) !?[]u8 {
- const writer = ctx.file.main.writer();
- const header = ctx.file.header.writer();
- try writer.writeAll(indentation);
+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| {
- if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
- const target = func_val.func.owner_decl;
- const target_ty = target.typed_value.most_recent.typed_value.ty;
- const ret_ty = target_ty.fnReturnType().tag();
- if (target_ty.fnReturnType().hasCodeGenBits() and inst.base.isUnused()) {
+ 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.typed_value.most_recent.typed_value.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)", .{});
}
- const tname = mem.spanZ(target.name);
- if (ctx.file.called.get(tname) == null) {
- try ctx.file.called.put(tname, void{});
- try renderFunctionSignature(ctx, header, target);
- try header.writeAll(";\n");
- }
- try writer.print("{}(", .{tname});
- if (inst.args.len != 0) {
- for (inst.args) |arg, i| {
- if (i > 0) {
- try writer.writeAll(", ");
- }
- if (arg.cast(Inst.Constant)) |con| {
- try renderValue(ctx, writer, arg.ty, con.val);
- } else {
- const val = try ctx.resolveInst(arg);
- try writer.print("{}", .{val});
- }
+ } 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");
- } else {
- return ctx.file.fail(ctx.decl.src(), "TODO non-function call target?", .{});
}
+ try writer.writeAll(");\n");
+ return result_local;
} else {
- return ctx.file.fail(ctx.decl.src(), "TODO non-constant call inst?", .{});
+ return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{});
}
- return null;
}
-fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
+fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {
// TODO emit #line directive here with line number and filename
- return null;
+ 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 genBreak(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
- // TODO ??
- return null;
+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 genUnreach(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
- try ctx.file.main.writer().writeAll(indentation ++ "zig_unreachable();\n");
- return null;
+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 genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
- const writer = ctx.file.main.writer();
- try writer.writeAll(indentation);
+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 renderType(ctx, writer, arg.ty);
- try writer.print(" {}_constant __asm__(\"{}\") = ", .{ reg, reg });
- // TODO merge constant handling into inst_map as well
- if (arg.castTag(.constant)) |c| {
- try renderValue(ctx, writer, arg.ty, c.val);
- try writer.writeAll(";\n ");
- } else {
- const gop = try ctx.inst_map.getOrPut(arg);
- if (!gop.found_existing) {
- return ctx.file.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
- }
- try writer.print("{};\n ", .{gop.entry.value});
- }
+ 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 ctx.file.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
+ return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});
}
}
- try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
- if (as.output) |o| {
- return ctx.file.fail(ctx.decl.src(), "TODO inline asm output", .{});
+ 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) |_| {
+ return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{});
}
if (as.inputs.len > 0) {
if (as.output == null) {
@@ -320,7 +782,7 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
if (index > 0) {
try writer.writeAll(", ");
}
- try writer.print("\"\"({}_constant)", .{reg});
+ try writer.print("\"r\"({s}_constant)", .{reg});
} else {
// This is blocked by the earlier test
unreachable;
@@ -328,5 +790,62 @@ fn genAsm(ctx: *Context, as: *Inst.Assembly) !?[]u8 {
}
}
try writer.writeAll(");\n");
- return null;
+
+ if (as.base.isUnused())
+ return CValue.none;
+
+ return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
+}
+
+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 = 4;
+
+ 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;
+ }
+ };
}