aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2021-01-05 11:08:34 -0700
committerAndrew Kelley <andrew@ziglang.org>2021-01-05 17:41:14 -0700
commit7b8cede61fc20c137aca4e02425536bfc9a5a400 (patch)
tree9533e4e3afb63e23ab5a42ecddb18b9998ec1237 /src
parent9360e5887ce0bf0ce204eb49f0d0b253348ef557 (diff)
downloadzig-7b8cede61fc20c137aca4e02425536bfc9a5a400.tar.gz
zig-7b8cede61fc20c137aca4e02425536bfc9a5a400.zip
stage2: rework the C backend
* std.ArrayList gains `moveToUnmanaged` and dead code `ArrayListUnmanaged.appendWrite` is deleted. * emit_h state is attached to Module rather than Compilation. * remove the implementation of emit-h because it did not properly integrate with incremental compilation. I will re-implement it in a follow-up commit. * Compilation: use the .codegen_failure tag rather than .dependency_failure tag for when `bin_file.updateDecl` fails. C backend: * Use a CValue tagged union instead of strings for C values. * Cleanly separate state into Object and DeclGen: - Object is present only when generating a .c file - DeclGen is present for both generating a .c and .h * Move some functions into their respective Object/DeclGen namespace. * Forward decls are managed by the incremental compilation frontend; C backend no longer renders function signatures based on callsites. For simplicity, all functions always get forward decls. * Constants are managed by the incremental compilation frontend. C backend no longer has a "constants" section. * Participate in incremental compilation. Each Decl gets an ArrayList for its generated C code and it is updated when the Decl is updated. During flush(), all these are joined together in the output file. * The new CValue tagged union is used to clean up using of assigning to locals without an additional pointer local. * Fix bug with bitcast of non-pointers making the memcpy destination immutable.
Diffstat (limited to 'src')
-rw-r--r--src/Compilation.zig58
-rw-r--r--src/Module.zig6
-rw-r--r--src/codegen/c.zig950
-rw-r--r--src/link.zig19
-rw-r--r--src/link/C.zig195
-rw-r--r--src/link/C/zig.h (renamed from src/link/cbe.h)1
-rw-r--r--src/test.zig17
7 files changed, 647 insertions, 599 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig
index 9912520437..c654833270 100644
--- a/src/Compilation.zig
+++ b/src/Compilation.zig
@@ -138,8 +138,6 @@ emit_llvm_ir: ?EmitLoc,
emit_analysis: ?EmitLoc,
emit_docs: ?EmitLoc,
-c_header: ?c_link.Header,
-
work_queue_wait_group: WaitGroup,
pub const InnerError = Module.InnerError;
@@ -866,9 +864,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.root_pkg = root_pkg,
.root_scope = root_scope,
.zig_cache_artifact_directory = zig_cache_artifact_directory,
+ .emit_h = options.emit_h,
};
break :blk module;
- } else null;
+ } else blk: {
+ if (options.emit_h != null) return error.NoZigModuleForCHeader;
+ break :blk null;
+ };
errdefer if (module) |zm| zm.deinit();
const error_return_tracing = !strip and switch (options.optimize_mode) {
@@ -996,7 +998,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
.local_cache_directory = options.local_cache_directory,
.global_cache_directory = options.global_cache_directory,
.bin_file = bin_file,
- .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null,
.emit_asm = options.emit_asm,
.emit_llvm_ir = options.emit_llvm_ir,
.emit_analysis = options.emit_analysis,
@@ -1218,10 +1219,6 @@ pub fn destroy(self: *Compilation) void {
}
self.failed_c_objects.deinit(gpa);
- if (self.c_header) |*header| {
- header.deinit();
- }
-
self.cache_parent.manifest_dir.close();
if (self.owned_link_dir) |*dir| dir.close();
@@ -1325,20 +1322,6 @@ pub fn update(self: *Compilation) !void {
module.root_scope.unload(self.gpa);
}
}
-
- // If we've chosen to emit a C header, flush the header to the disk.
- if (self.c_header) |header| {
- const header_path = header.emit_loc.?;
- // If a directory has been provided, write the header there. Otherwise, just write it to the
- // cache directory.
- const header_dir = if (header_path.directory) |dir|
- dir.handle
- else
- self.local_cache_directory.handle;
- const header_file = try header_dir.createFile(header_path.basename, .{});
- defer header_file.close();
- try header.flush(header_file.writer());
- }
}
/// Having the file open for writing is problematic as far as executing the
@@ -1497,7 +1480,7 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.AnalysisFail => {
- decl.analysis = .dependency_failure;
+ decl.analysis = .codegen_failure;
},
else => {
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
@@ -1512,25 +1495,6 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
}
return;
};
-
- if (self.c_header) |*header| {
- c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) {
- error.OutOfMemory => return error.OutOfMemory,
- error.AnalysisFail => {
- decl.analysis = .dependency_failure;
- },
- else => {
- try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
- module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
- module.gpa,
- decl.src(),
- "unable to generate C header: {s}",
- .{@errorName(err)},
- ));
- decl.analysis = .codegen_failure_retryable;
- },
- };
- }
},
},
.analyze_decl => |decl| {
@@ -2998,9 +2962,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
man.hash.add(comp.bin_file.options.function_sections);
man.hash.add(comp.bin_file.options.is_test);
man.hash.add(comp.bin_file.options.emit != null);
- man.hash.add(comp.c_header != null);
- if (comp.c_header) |header| {
- man.hash.addEmitLoc(header.emit_loc.?);
+ man.hash.add(mod.emit_h != null);
+ if (mod.emit_h) |emit_h| {
+ man.hash.addEmitLoc(emit_h);
}
man.hash.addOptionalEmitLoc(comp.emit_asm);
man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
@@ -3105,10 +3069,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
});
break :blk try directory.join(arena, &[_][]const u8{bin_basename});
} else "";
- if (comp.c_header != null) {
+ if (comp.emit_h != null) {
log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
}
- const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory);
+ const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory);
const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
diff --git a/src/Module.zig b/src/Module.zig
index 6a4575394a..f1cec82680 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -94,6 +94,8 @@ stage1_flags: packed struct {
reserved: u2 = 0,
} = .{},
+emit_h: ?Compilation.EmitLoc,
+
pub const Export = struct {
options: std.builtin.ExportOptions,
/// Byte offset into the file that contains the export directive.
@@ -1943,14 +1945,14 @@ fn allocateNewDecl(
.coff => .{ .coff = link.File.Coff.TextBlock.empty },
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
- .c => .{ .c = {} },
+ .c => .{ .c = link.File.C.DeclBlock.empty },
.wasm => .{ .wasm = {} },
},
.fn_link = switch (self.comp.bin_file.tag) {
.coff => .{ .coff = {} },
.elf => .{ .elf = link.File.Elf.SrcFn.empty },
.macho => .{ .macho = link.File.MachO.SrcFn.empty },
- .c => .{ .c = {} },
+ .c => .{ .c = link.File.C.FnBlock.empty },
.wasm => .{ .wasm = null },
},
.generation = 0,
diff --git a/src/codegen/c.zig b/src/codegen/c.zig
index 1a89e22d48..d87801ae2e 100644
--- a/src/codegen/c.zig
+++ b/src/codegen/c.zig
@@ -1,495 +1,526 @@
const std = @import("std");
+const mem = std.mem;
+const log = std.log.scoped(.c);
+const Writer = std.ArrayList(u8).Writer;
const link = @import("../link.zig");
const Module = @import("../Module.zig");
const Compilation = @import("../Compilation.zig");
-
const Inst = @import("../ir.zig").Inst;
const Value = @import("../value.zig").Value;
const Type = @import("../type.zig").Type;
-
const C = link.File.C;
const Decl = Module.Decl;
-const mem = std.mem;
-const log = std.log.scoped(.c);
+const trace = @import("../tracy.zig").trace;
-const Writer = std.ArrayList(u8).Writer;
+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,
-const Mutability = enum { Const, Mut };
+ pub fn printed(value: CValue, object: *Object) Printed {
+ return .{
+ .value = value,
+ .object = object,
+ };
+ }
+
+ pub const Printed = struct {
+ value: CValue,
+ object: *Object,
+
+ /// TODO this got unwieldly, I want to remove the ability to print this way
+ pub fn format(
+ self: Printed,
+ comptime fmt: []const u8,
+ options: std.fmt.FormatOptions,
+ writer: anytype,
+ ) error{OutOfMemory}!void {
+ if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'");
+ switch (self.value) {
+ .none => unreachable,
+ .local => |i| return std.fmt.format(writer, "t{d}", .{i}),
+ .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}),
+ .constant => |inst| {
+ const o = self.object;
+ o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.AnalysisFail => return,
+ };
+ },
+ .arg => |i| return std.fmt.format(writer, "a{d}", .{i}),
+ .decl => |decl| return writer.writeAll(mem.span(decl.name)),
+ }
+ }
+ };
+};
-fn renderTypeAndName(
- ctx: *Context,
- writer: Writer,
- ty: Type,
- name: []const u8,
- mutability: Mutability,
-) error{ OutOfMemory, AnalysisFail }!void {
- var suffix = std.ArrayList(u8).init(&ctx.arena.allocator);
-
- 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();
+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,
+
+ 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!
}
- try renderType(ctx, writer, render_ty);
+ fn allocLocalValue(o: *Object) CValue {
+ const result = o.next_local_index;
+ o.next_local_index += 1;
+ return .{ .local = result };
+ }
- const const_prefix = switch (mutability) {
- .Const => "const ",
- .Mut => "",
- };
- try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items });
-}
+ fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
+ const local_value = o.allocLocalValue();
+ try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability);
+ return local_value;
+ }
-fn renderType(
- ctx: *Context,
- writer: Writer,
- t: Type,
-) error{ OutOfMemory, AnalysisFail }!void {
- switch (t.zigTypeTag()) {
- .NoReturn => {
- try writer.writeAll("zig_noreturn void");
- },
- .Void => try writer.writeAll("void"),
- .Bool => try writer.writeAll("bool"),
- .Int => {
- switch (t.tag()) {
- .u8 => try writer.writeAll("uint8_t"),
- .i8 => try writer.writeAll("int8_t"),
- .u16 => try writer.writeAll("uint16_t"),
- .i16 => try writer.writeAll("int16_t"),
- .u32 => try writer.writeAll("uint32_t"),
- .i32 => try writer.writeAll("int32_t"),
- .u64 => try writer.writeAll("uint64_t"),
- .i64 => try writer.writeAll("int64_t"),
- .usize => try writer.writeAll("uintptr_t"),
- .isize => try writer.writeAll("intptr_t"),
- .c_short => try writer.writeAll("short"),
- .c_ushort => try writer.writeAll("unsigned short"),
- .c_int => try writer.writeAll("int"),
- .c_uint => try writer.writeAll("unsigned int"),
- .c_long => try writer.writeAll("long"),
- .c_ulong => try writer.writeAll("unsigned long"),
- .c_longlong => try writer.writeAll("long long"),
- .c_ulonglong => try writer.writeAll("unsigned long long"),
- .int_signed, .int_unsigned => {
- const info = t.intInfo(ctx.target);
- const sign_prefix = switch (info.signedness) {
- .signed => "i",
- .unsigned => "",
- };
- inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
- if (info.bits <= nbits) {
- try writer.print("{s}int{d}_t", .{ sign_prefix, nbits });
- break;
- }
+ fn indent(o: *Object) !void {
+ const indent_size = 4;
+ const indent_level = 1;
+ const indent_amt = indent_size * indent_level;
+ try o.code.writer().writeByteNTimes(' ', indent_amt);
+ }
+
+ fn renderTypeAndName(
+ o: *Object,
+ writer: Writer,
+ 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(writer, render_ty);
+
+ const const_prefix = switch (mutability) {
+ .Const => "const ",
+ .Mut => "",
+ };
+ try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items });
+ }
+};
+
+/// This data is available both when outputting .c code and when outputting an .h file.
+const DeclGen = struct {
+ module: *Module,
+ decl: *Decl,
+ fwd_decl: std.ArrayList(u8),
+ error_msg: ?*Compilation.ErrorMsg,
+
+ fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
+ dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args);
+ return error.AnalysisFail;
+ }
+
+ fn renderValue(
+ dg: *DeclGen,
+ writer: Writer,
+ t: Type,
+ val: Value,
+ ) error{ OutOfMemory, AnalysisFail }!void {
+ 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 {
- return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
+ 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 {Z}
+ try writer.print("\"{Z}\"", .{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("}");
+ },
+ }
+ },
+ else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
+ @tagName(e),
+ }),
+ }
+ }
+
+ fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void {
+ const tv = dg.decl.typed_value.most_recent.typed_value;
+ // Determine whether the function is globally visible.
+ const is_global = blk: {
+ switch (tv.val.tag()) {
+ .extern_fn => break :blk true,
+ .function => {
+ const func = tv.val.castTag(.function).?.data;
+ break :blk dg.module.decl_exports.contains(func.owner_decl);
+ },
else => unreachable,
}
- },
- .Pointer => {
- if (t.isSlice()) {
- return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{});
- } else {
- try renderType(ctx, writer, t.elemType());
- try writer.writeAll(" *");
- if (t.isConstPtr()) {
- try writer.writeAll("const ");
- }
- if (t.isVolatilePtr()) {
- try writer.writeAll("volatile ");
+ };
+ if (!is_global) {
+ try w.writeAll("static ");
+ }
+ 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});
}
- },
- .Array => {
- try renderType(ctx, writer, t.elemType());
- try writer.writeAll(" *");
- },
- else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{
- @tagName(e),
- }),
+ }
+ try w.writeByte(')');
}
-}
-fn renderValue(
- ctx: *Context,
- writer: Writer,
- t: Type,
- val: Value,
-) error{ OutOfMemory, AnalysisFail }!void {
- 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 renderType(ctx, writer, t);
- try writer.print(")&{s}", .{decl.name});
- }
+ fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void {
+ switch (t.zigTypeTag()) {
+ .NoReturn => {
+ try w.writeAll("zig_noreturn void");
},
- .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});
+ .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 => "i",
+ .unsigned => "",
+ };
+ 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,
+ }
},
- else => |e| return ctx.fail(
- ctx.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 {Z}
- try writer.print("\"{Z}\"", .{bytes});
- },
- else => {
- // Fall back to generic implementation.
- 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(&ctx.arena.allocator, index);
- try renderValue(ctx, writer, elem_ty, elem_val);
+ .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.sentinel()) |sentinel_val| {
- if (index != 0) try writer.writeAll(",");
- try renderValue(ctx, writer, elem_ty, sentinel_val);
+ if (t.isVolatilePtr()) {
+ try w.writeAll("volatile ");
}
- try writer.writeAll("}");
- },
- }
- },
- else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{
- @tagName(e),
- }),
- }
-}
-
-fn renderFunctionSignature(
- ctx: *Context,
- writer: Writer,
- decl: *Decl,
-) !void {
- const tv = decl.typed_value.most_recent.typed_value;
- // Determine whether the function is globally visible.
- const is_global = blk: {
- switch (tv.val.tag()) {
- .extern_fn => break :blk true,
- .function => {
- const func = tv.val.castTag(.function).?.data;
- break :blk ctx.module.decl_exports.contains(func.owner_decl);
+ }
},
- else => unreachable,
- }
- };
- if (!is_global) {
- try writer.writeAll("static ");
- }
- 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 decl_name = mem.span(decl.name);
- const name = try map(ctx.arena.child_allocator, decl_name);
- defer ctx.arena.child_allocator.free(name);
- try writer.print(" {s}(", .{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{d}", .{index});
+ .Array => {
+ try dg.renderType(w, t.elemType());
+ try w.writeAll(" *");
+ },
+ else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
+ @tagName(e),
+ }),
}
}
- try writer.writeByte(')');
-}
+};
-fn indent(file: *C) !void {
- const indent_size = 4;
- const indent_level = 1;
- const indent_amt = indent_size * indent_level;
- try file.main.writer().writeByteNTimes(' ', indent_amt);
-}
+pub fn genDecl(o: *Object) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
-pub fn generate(file: *C, module: *Module, decl: *Decl) !void {
- 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{
- .decl = decl,
- .arena = &arena,
- .inst_map = &inst_map,
- .target = file.base.options.target,
- .header = &file.header,
- .module = module,
- };
- defer {
- file.error_msg = ctx.error_msg;
- ctx.deinit();
- }
+ const tv = o.dg.decl.typed_value.most_recent.typed_value;
if (tv.val.castTag(.function)) |func_payload| {
- const writer = file.main.writer();
- try renderFunctionSignature(&ctx, writer, decl);
-
- try writer.writeAll(" {");
+ const fwd_decl_writer = o.dg.fwd_decl.writer();
+ try o.dg.renderFunctionSignature(fwd_decl_writer);
+ try fwd_decl_writer.writeAll(";\n");
const func: *Module.Fn = func_payload.data;
const instructions = func.body.instructions;
- if (instructions.len > 0) {
- try writer.writeAll("\n");
- for (instructions) |inst| {
- if (switch (inst.tag) {
- .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"),
- .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?),
- .arg => try genArg(&ctx),
- .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?),
- .block => try genBlock(&ctx, file, inst.castTag(.block).?),
- .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?),
- .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?),
- .call => try genCall(&ctx, file, inst.castTag(.call).?),
- .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="),
- .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"),
- .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="),
- .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"),
- .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="),
- .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="),
- .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
- .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?),
- .load => try genLoad(&ctx, file, inst.castTag(.load).?),
- .ret => try genRet(&ctx, file, inst.castTag(.ret).?),
- .retvoid => try genRetVoid(file),
- .store => try genStore(&ctx, file, inst.castTag(.store).?),
- .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"),
- .unreach => try genUnreach(file, inst.castTag(.unreach).?),
- else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
- }) |name| {
- try ctx.inst_map.putNoClobber(inst, name);
- }
+ const writer = o.code.writer();
+ try o.dg.renderFunctionSignature(writer);
+ if (instructions.len == 0) {
+ try writer.writeAll(" {}\n\n");
+ return;
+ }
+
+ try writer.writeAll(" {");
+
+ try writer.writeAll("\n");
+ for (instructions) |inst| {
+ const result_value = switch (inst.tag) {
+ .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).?),
+ 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");
} else if (tv.val.tag() == .extern_fn) {
- return; // handled when referenced
+ const writer = o.code.writer();
+ try o.dg.renderFunctionSignature(writer);
+ try writer.writeAll(";\n");
} else {
- const writer = file.constants.writer();
+ const writer = o.code.writer();
try writer.writeAll("static ");
// TODO ask the Decl if it is const
// https://github.com/ziglang/zig/issues/7582
- try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut);
+ const decl_c_value: CValue = .{ .decl = o.dg.decl };
+ try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut);
try writer.writeAll(" = ");
- try renderValue(&ctx, writer, tv.ty, tv.val);
+ try o.dg.renderValue(writer, tv.ty, tv.val);
try writer.writeAll(";\n");
}
}
-pub fn generateHeader(
- comp: *Compilation,
- module: *Module,
- header: *C.Header,
- decl: *Decl,
-) error{ AnalysisFail, OutOfMemory }!void {
+pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
.Fn => {
- var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa);
- defer inst_map.deinit();
-
- var arena = std.heap.ArenaAllocator.init(comp.gpa);
- defer arena.deinit();
-
- var ctx = Context{
- .decl = decl,
- .arena = &arena,
- .inst_map = &inst_map,
- .target = comp.getTarget(),
- .header = header,
- .module = module,
- };
- const writer = header.buf.writer();
- renderFunctionSignature(&ctx, writer, decl) catch |err| {
- if (err == error.AnalysisFail) {
- try module.failed_decls.put(module.gpa, decl, ctx.error_msg);
- }
- return err;
+ dg.renderFunctionSignature() catch |err| switch (err) {
+ error.AnalysisFail => {
+ try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?);
+ dg.error_msg = null;
+ return error.AnalysisFail;
+ },
+ else => |e| return e,
};
- try writer.writeAll(";\n");
+ try dg.fwd_decl.appendSlice(";\n");
},
else => {},
}
}
-const Context = struct {
- decl: *Decl,
- inst_map: *std.AutoHashMap(*Inst, []u8),
- arena: *std.heap.ArenaAllocator,
- argdex: usize = 0,
- unnamed_index: usize = 0,
- error_msg: *Compilation.ErrorMsg = undefined,
- target: std.Target,
- header: *C.Header,
- module: *Module,
-
- fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
- if (inst.value()) |val| {
- var out = std.ArrayList(u8).init(&self.arena.allocator);
- try renderValue(self, out.writer(), inst.ty, val);
- return out.toOwnedSlice();
- }
- return self.inst_map.get(inst).?; // Instruction does not dominate all uses!
- }
-
- fn name(self: *Context) ![]u8 {
- const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index});
- self.unnamed_index += 1;
- return val;
- }
-
- fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
- self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args);
- return error.AnalysisFail;
- }
-
- fn deinit(self: *Context) void {
- self.* = undefined;
- }
-};
-
-fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 {
- const writer = file.main.writer();
+fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
+ const writer = o.code.writer();
// First line: the variable used as data storage.
- try indent(file);
- const local_name = try ctx.name();
+ try o.indent();
const elem_type = alloc.base.ty.elemType();
const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;
- try renderTypeAndName(ctx, writer, elem_type, local_name, mutability);
+ const local = try o.allocLocal(elem_type, mutability);
try writer.writeAll(";\n");
- // Second line: a pointer to it so that we can refer to it as the allocation.
- // One line for the variable, one line for the pointer to the variable, which we return.
- try indent(file);
- const ptr_local_name = try ctx.name();
- try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const);
- try writer.print(" = &{s};\n", .{local_name});
-
- return ptr_local_name;
+ return CValue{ .local_ref = local.local };
}
-fn genArg(ctx: *Context) !?[]u8 {
- const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex});
- ctx.argdex += 1;
- return name;
+fn genArg(o: *Object) CValue {
+ const i = o.next_arg_index;
+ o.next_arg_index += 1;
+ return .{ .arg = i };
}
-fn genRetVoid(file: *C) !?[]u8 {
- try indent(file);
- try file.main.writer().print("return;\n", .{});
- return null;
+fn genRetVoid(o: *Object) !CValue {
+ try o.indent();
+ try o.code.writer().print("return;\n", .{});
+ return CValue.none;
}
-fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
- const operand = try ctx.resolveInst(inst.operand);
- const writer = file.main.writer();
- try indent(file);
- const local_name = try ctx.name();
- try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const);
- try writer.print(" = *{s};\n", .{operand});
- return local_name;
+fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
+ const operand = try o.resolveInst(inst.operand);
+ const writer = o.code.writer();
+ try o.indent();
+ const local = try o.allocLocal(inst.base.ty, .Const);
+ switch (operand) {
+ .local_ref => |i| {
+ const wrapped: CValue = .{ .local = i };
+ try writer.print(" = {};\n", .{wrapped.printed(o)});
+ },
+ else => {
+ try writer.print(" = *{};\n", .{operand.printed(o)});
+ },
+ }
+ return local;
}
-fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
- try indent(file);
- const writer = file.main.writer();
- try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)});
- return null;
+fn genRet(o: *Object, inst: *Inst.UnOp) !CValue {
+ const operand = try o.resolveInst(inst.operand);
+ try o.indent();
+ try o.code.writer().print("return {};\n", .{operand.printed(o)});
+ return CValue.none;
}
-fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
+fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
if (inst.base.isUnused())
- return null;
- try indent(file);
- const writer = file.main.writer();
- const name = try ctx.name();
- const from = try ctx.resolveInst(inst.operand);
+ return CValue.none;
- try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const);
+ const from = try o.resolveInst(inst.operand);
+
+ try o.indent();
+ const writer = o.code.writer();
+ const local = try o.allocLocal(inst.base.ty, .Const);
try writer.writeAll(" = (");
- try renderType(ctx, writer, inst.base.ty);
- try writer.print("){s};\n", .{from});
- return name;
+ try o.dg.renderType(writer, inst.base.ty);
+ try writer.print("){};\n", .{from.printed(o)});
+ return local;
}
-fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 {
+fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
// *a = b;
- try indent(file);
- const writer = file.main.writer();
- const dest_ptr_name = try ctx.resolveInst(inst.lhs);
- const src_val_name = try ctx.resolveInst(inst.rhs);
- try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name });
- return null;
+ const dest_ptr = try o.resolveInst(inst.lhs);
+ const src_val = try o.resolveInst(inst.rhs);
+
+ try o.indent();
+ const writer = o.code.writer();
+ switch (dest_ptr) {
+ .local_ref => |i| {
+ const dest: CValue = .{ .local = i };
+ try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) });
+ },
+ else => {
+ try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) });
+ },
+ }
+ return CValue.none;
}
-fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 {
+fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
if (inst.base.isUnused())
- return null;
- try indent(file);
- const lhs = try ctx.resolveInst(inst.lhs);
- const rhs = try ctx.resolveInst(inst.rhs);
- const writer = file.main.writer();
- const name = try ctx.name();
- try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const);
- try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs });
- return name;
+ return CValue.none;
+
+ const lhs = try o.resolveInst(inst.lhs);
+ const rhs = try o.resolveInst(inst.rhs);
+
+ try o.indent();
+ const writer = o.code.writer();
+ const local = try o.allocLocal(inst.base.ty, .Const);
+ try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) });
+ return local;
}
-fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
- try indent(file);
- const writer = file.main.writer();
- const header = file.header.buf.writer();
+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
@@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
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_name: ?[]u8 = null;
+ var result_local: CValue = .none;
+
+ try o.indent();
+ const writer = o.code.writer();
if (unused_result) {
if (ret_ty.hasCodeGenBits()) {
try writer.print("(void)", .{});
}
} else {
- const local_name = try ctx.name();
- try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const);
+ result_local = try o.allocLocal(ret_ty, .Const);
try writer.writeAll(" = ");
- result_name = local_name;
}
const fn_name = mem.spanZ(fn_decl.name);
- if (file.called.get(fn_name) == null) {
- try file.called.put(fn_name, {});
- try renderFunctionSignature(ctx, header, fn_decl);
- try header.writeAll(";\n");
- }
try writer.print("{s}(", .{fn_name});
if (inst.args.len != 0) {
for (inst.args) |arg, i| {
@@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
try writer.writeAll(", ");
}
if (arg.value()) |val| {
- try renderValue(ctx, writer, arg.ty, val);
+ try o.dg.renderValue(writer, arg.ty, val);
} else {
- const val = try ctx.resolveInst(arg);
- try writer.print("{s}", .{val});
+ const val = try o.resolveInst(arg);
+ try writer.print("{}", .{val.printed(o)});
}
}
}
try writer.writeAll(");\n");
- return result_name;
+ return result_local;
} else {
- return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{});
+ return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{});
}
}
-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(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 {
- return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{});
+fn genBlock(o: *Object, inst: *Inst.Block) !CValue {
+ return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{});
}
-fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
- const writer = file.main.writer();
- try indent(file);
- const local_name = try ctx.name();
- const operand = try ctx.resolveInst(inst.operand);
- try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const);
+fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
+ const operand = try o.resolveInst(inst.operand);
+
+ const writer = o.code.writer();
+ try o.indent();
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 renderType(ctx, writer, inst.base.ty);
- try writer.print("){s};\n", .{operand});
- } else {
- try writer.writeAll(";\n");
- try indent(file);
- try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name });
+ try o.dg.renderType(writer, inst.base.ty);
+ try writer.print("){};\n", .{operand.printed(o)});
+ return local;
}
- return local_name;
+
+ const local = try o.allocLocal(inst.base.ty, .Mut);
+ try writer.writeAll(";\n");
+ try o.indent();
+ try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{
+ local.printed(o), operand.printed(o), local.printed(o),
+ });
+ return local;
}
-fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 {
- try indent(file);
- try file.main.writer().writeAll("zig_breakpoint();\n");
- return null;
+fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {
+ try o.indent();
+ try o.code.writer().writeAll("zig_breakpoint();\n");
+ return CValue.none;
}
-fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 {
- try indent(file);
- try file.main.writer().writeAll("zig_unreachable();\n");
- return null;
+fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {
+ try o.indent();
+ try o.code.writer().writeAll("zig_unreachable();\n");
+ return CValue.none;
}
-fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
- try indent(file);
- const writer = file.main.writer();
+fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
+ if (as.base.isUnused() and !as.is_volatile)
+ return CValue.none;
+
+ const writer = o.code.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 o.indent();
try writer.writeAll("register ");
- try renderType(ctx, writer, arg.ty);
- try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ 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.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
- }
- try writer.print("{s};\n ", .{gop.entry.value});
- }
+ try o.dg.renderType(writer, arg.ty);
+ try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{
+ reg, reg, arg_c_value.printed(o),
+ });
} else {
- return ctx.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 {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
- if (as.output) |o| {
- return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{});
+ try o.indent();
+ 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) {
@@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, 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", .{});
}
diff --git a/src/link.zig b/src/link.zig
index 488f8bf69b..18b093a07a 100644
--- a/src/link.zig
+++ b/src/link.zig
@@ -130,7 +130,7 @@ pub const File = struct {
elf: Elf.TextBlock,
coff: Coff.TextBlock,
macho: MachO.TextBlock,
- c: void,
+ c: C.DeclBlock,
wasm: void,
};
@@ -138,7 +138,7 @@ pub const File = struct {
elf: Elf.SrcFn,
coff: Coff.SrcFn,
macho: MachO.SrcFn,
- c: void,
+ c: C.FnBlock,
wasm: ?Wasm.FnData,
};
@@ -291,7 +291,7 @@ pub const File = struct {
.coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
.macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
- .c => {},
+ .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
}
}
@@ -301,7 +301,8 @@ pub const File = struct {
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
- .c, .wasm => {},
+ .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl),
+ .wasm => {},
}
}
@@ -312,7 +313,8 @@ pub const File = struct {
.coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl),
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
.macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
- .c, .wasm => {},
+ .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
+ .wasm => {},
}
}
@@ -407,12 +409,13 @@ pub const File = struct {
}
}
+ /// Called when a Decl is deleted from the Module.
pub fn freeDecl(base: *File, decl: *Module.Decl) void {
switch (base.tag) {
.coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
.elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
.macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
- .c => {},
+ .c => @fieldParentPtr(C, "base", base).freeDecl(decl),
.wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
}
}
@@ -432,14 +435,14 @@ pub const File = struct {
pub fn updateDeclExports(
base: *File,
module: *Module,
- decl: *const Module.Decl,
+ decl: *Module.Decl,
exports: []const *Module.Export,
) !void {
switch (base.tag) {
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports),
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
- .c => return {},
+ .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports),
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
}
}
diff --git a/src/link/C.zig b/src/link/C.zig
index 5f38c9324f..10b98b854c 100644
--- a/src/link/C.zig
+++ b/src/link/C.zig
@@ -11,45 +11,28 @@ const trace = @import("../tracy.zig").trace;
const C = @This();
pub const base_tag: link.File.Tag = .c;
+pub const zig_h = @embedFile("C/zig.h");
-pub const Header = struct {
- buf: std.ArrayList(u8),
- emit_loc: ?Compilation.EmitLoc,
-
- pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header {
- return .{
- .buf = std.ArrayList(u8).init(allocator),
- .emit_loc = emit_loc,
- };
- }
-
- pub fn flush(self: *const Header, writer: anytype) !void {
- const tracy = trace(@src());
- defer tracy.end();
+base: link.File,
- try writer.writeAll(@embedFile("cbe.h"));
- if (self.buf.items.len > 0) {
- try writer.print("{s}", .{self.buf.items});
- }
- }
+/// Per-declaration data. For functions this is the body, and
+/// the forward declaration is stored in the FnBlock.
+pub const DeclBlock = struct {
+ code: std.ArrayListUnmanaged(u8),
- pub fn deinit(self: *Header) void {
- self.buf.deinit();
- self.* = undefined;
- }
+ pub const empty: DeclBlock = .{
+ .code = .{},
+ };
};
-base: link.File,
-
-path: []const u8,
+/// Per-function data.
+pub const FnBlock = struct {
+ fwd_decl: std.ArrayListUnmanaged(u8),
-// These are only valid during a flush()!
-header: Header,
-constants: std.ArrayList(u8),
-main: std.ArrayList(u8),
-called: std.StringHashMap(void),
-
-error_msg: *Compilation.ErrorMsg = undefined,
+ pub const empty: FnBlock = .{
+ .fwd_decl = .{},
+ };
+};
pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {
assert(options.object_format == .c);
@@ -57,6 +40,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
if (options.use_llvm) return error.LLVMHasNoCBackend;
if (options.use_lld) return error.LLDHasNoCBackend;
+ const file = try options.emit.?.directory.handle.createFile(sub_path, .{
+ .truncate = true,
+ .mode = link.determineMode(options),
+ });
+ errdefer file.close();
+
+ try file.writeAll(zig_h);
+
var c_file = try allocator.create(C);
errdefer allocator.destroy(c_file);
@@ -64,25 +55,75 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
.base = .{
.tag = .c,
.options = options,
- .file = null,
+ .file = file,
.allocator = allocator,
},
- .main = undefined,
- .header = undefined,
- .constants = undefined,
- .called = undefined,
- .path = sub_path,
};
return c_file;
}
-pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
- self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args);
- return error.AnalysisFail;
+pub fn deinit(self: *C) void {
+ const module = self.base.options.module orelse return;
+ for (module.decl_table.items()) |entry| {
+ self.freeDecl(entry.value);
+ }
+}
+
+pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
+
+pub fn freeDecl(self: *C, decl: *Module.Decl) void {
+ decl.link.c.code.deinit(self.base.allocator);
+ decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
+}
+
+pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
+ const tracy = trace(@src());
+ defer tracy.end();
+
+ const fwd_decl = &decl.fn_link.c.fwd_decl;
+ const code = &decl.link.c.code;
+ fwd_decl.shrinkRetainingCapacity(0);
+ code.shrinkRetainingCapacity(0);
+
+ var object: codegen.Object = .{
+ .dg = .{
+ .module = module,
+ .error_msg = null,
+ .decl = decl,
+ .fwd_decl = fwd_decl.toManaged(module.gpa),
+ },
+ .gpa = module.gpa,
+ .code = code.toManaged(module.gpa),
+ .value_map = codegen.CValueMap.init(module.gpa),
+ };
+ defer object.value_map.deinit();
+ defer object.code.deinit();
+ defer object.dg.fwd_decl.deinit();
+
+ codegen.genDecl(&object) catch |err| switch (err) {
+ error.AnalysisFail => {},
+ else => |e| return e,
+ };
+ // The code may populate this error without returning error.AnalysisFail.
+ if (object.dg.error_msg) |msg| {
+ try module.failed_decls.put(module.gpa, decl, msg);
+ return;
+ }
+
+ fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
+ code.* = object.code.moveToUnmanaged();
+
+ // Free excess allocated memory for this Decl.
+ fwd_decl.shrink(module.gpa, fwd_decl.items.len);
+ code.shrink(module.gpa, code.items.len);
}
-pub fn deinit(self: *C) void {}
+pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void {
+ // The C backend does not have the ability to fix line numbers without re-generating
+ // the entire Decl.
+ return self.updateDecl(module, decl);
+}
pub fn flush(self: *C, comp: *Compilation) !void {
return self.flushModule(comp);
@@ -92,41 +133,45 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
const tracy = trace(@src());
defer tracy.end();
- self.main = std.ArrayList(u8).init(self.base.allocator);
- self.header = Header.init(self.base.allocator, null);
- self.constants = std.ArrayList(u8).init(self.base.allocator);
- self.called = std.StringHashMap(void).init(self.base.allocator);
- defer self.main.deinit();
- defer self.header.deinit();
- defer self.constants.deinit();
- defer self.called.deinit();
-
- const module = self.base.options.module.?;
- for (self.base.options.module.?.decl_table.entries.items) |kv| {
- codegen.generate(self, module, kv.value) catch |err| {
- if (err == error.AnalysisFail) {
- try module.failed_decls.put(module.gpa, kv.value, self.error_msg);
- }
- return err;
- };
- }
+ const file = self.base.file.?;
- const file = try self.base.options.emit.?.directory.handle.createFile(self.path, .{ .truncate = true, .read = true, .mode = link.determineMode(self.base.options) });
- defer file.close();
+ // The header is written upon opening; here we truncate and seek to after the header.
+ // TODO: use writev
+ try file.seekTo(zig_h.len);
+ try file.setEndPos(zig_h.len);
- const writer = file.writer();
- try self.header.flush(writer);
- if (self.header.buf.items.len > 0) {
- try writer.writeByte('\n');
- }
- if (self.constants.items.len > 0) {
- try writer.print("{s}\n", .{self.constants.items});
+ var buffered_writer = std.io.bufferedWriter(file.writer());
+ const writer = buffered_writer.writer();
+
+ const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
+
+ // Forward decls and non-functions first.
+ // TODO: use writev
+ for (module.decl_table.items()) |kv| {
+ const decl = kv.value;
+ const decl_tv = decl.typed_value.most_recent.typed_value;
+ if (decl_tv.val.castTag(.function)) |_| {
+ try writer.writeAll(decl.fn_link.c.fwd_decl.items);
+ } else {
+ try writer.writeAll(decl.link.c.code.items);
+ }
}
- if (self.main.items.len > 1) {
- const last_two = self.main.items[self.main.items.len - 2 ..];
- if (std.mem.eql(u8, last_two, "\n\n")) {
- self.main.items.len -= 1;
+
+ // Now the function bodies.
+ for (module.decl_table.items()) |kv| {
+ const decl = kv.value;
+ const decl_tv = decl.typed_value.most_recent.typed_value;
+ if (decl_tv.val.castTag(.function)) |_| {
+ try writer.writeAll(decl.link.c.code.items);
}
}
- try writer.writeAll(self.main.items);
+
+ try buffered_writer.flush();
}
+
+pub fn updateDeclExports(
+ self: *C,
+ module: *Module,
+ decl: *Module.Decl,
+ exports: []const *Module.Export,
+) !void {}
diff --git a/src/link/cbe.h b/src/link/C/zig.h
index 8452af8fbc..49f97210eb 100644
--- a/src/link/cbe.h
+++ b/src/link/C/zig.h
@@ -42,3 +42,4 @@
#define int128_t __int128
#define uint128_t unsigned __int128
#include <string.h>
+
diff --git a/src/test.zig b/src/test.zig
index 67a30f1f32..f630898189 100644
--- a/src/test.zig
+++ b/src/test.zig
@@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d
const ThreadPool = @import("ThreadPool.zig");
const CrossTarget = std.zig.CrossTarget;
-const c_header = @embedFile("link/cbe.h");
+const zig_h = link.File.C.zig_h;
test "self-hosted" {
var ctx = TestContext.init();
@@ -324,11 +324,11 @@ pub const TestContext = struct {
}
pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
- ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out);
+ ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out);
}
pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
- ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out);
+ ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out);
}
pub fn addCompareOutput(
@@ -700,11 +700,12 @@ pub const TestContext = struct {
},
}
}
- if (comp.bin_file.cast(link.File.C)) |c_file| {
- std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
- c_file.main.items,
- });
- }
+ // TODO print generated C code
+ //if (comp.bin_file.cast(link.File.C)) |c_file| {
+ // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
+ // c_file.main.items,
+ // });
+ //}
std.debug.print("Test failed.\n", .{});
std.process.exit(1);
}