authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-05 11:08:34-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-01-05 17:41:14-07:00
log7b8cede61fc20c137aca4e02425536bfc9a5a400
tree9533e4e3afb63e23ab5a42ecddb18b9998ec1237
parent9360e5887ce0bf0ce204eb49f0d0b253348ef557

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.

10 files changed, 703 insertions(+), 655 deletions(-)

lib/std/array_list.zig+12-10
......@@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
100100
101101 /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
102102 /// of this ArrayList. This ArrayList retains ownership of underlying memory.
103 /// Deprecated: use `moveToUnmanaged` which has different semantics.
103104 pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) {
104105 return .{ .items = self.items, .capacity = self.capacity };
105106 }
106107
108 /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
109 /// of this ArrayList. Empties this ArrayList.
110 pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) {
111 const allocator = self.allocator;
112 const result = .{ .items = self.items, .capacity = self.capacity };
113 self.* = init(allocator);
114 return result;
115 }
116
107117 /// The caller owns the returned memory. Empties this ArrayList.
108118 pub fn toOwnedSlice(self: *Self) Slice {
109119 const allocator = self.allocator;
......@@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
551561 mem.copy(T, self.items[oldlen..], items);
552562 }
553563
554 /// Same as `append` except it returns the number of bytes written, which is always the same
555 /// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API.
556 /// This function may be called only when `T` is `u8`.
557 fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize {
558 try self.appendSlice(allocator, m);
559 return m.len;
560 }
561
562564 /// Append a value to the list `n` times.
563565 /// Allocates more memory as necessary.
564566 pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void {
......@@ -1129,13 +1131,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" {
11291131 }
11301132}
11311133
1132test "std.ArrayList(u8) implements outStream" {
1134test "std.ArrayList(u8) implements writer" {
11331135 var buffer = ArrayList(u8).init(std.testing.allocator);
11341136 defer buffer.deinit();
11351137
11361138 const x: i32 = 42;
11371139 const y: i32 = 1234;
1138 try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y });
1140 try buffer.writer().print("x: {}\ny: {}\n", .{ x, y });
11391141
11401142 testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items);
11411143}
src/Compilation.zig+11-47
......@@ -138,8 +138,6 @@ emit_llvm_ir: ?EmitLoc,
138138emit_analysis: ?EmitLoc,
139139emit_docs: ?EmitLoc,
140140
141c_header: ?c_link.Header,
142
143141work_queue_wait_group: WaitGroup,
144142
145143pub const InnerError = Module.InnerError;
......@@ -866,9 +864,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
866864 .root_pkg = root_pkg,
867865 .root_scope = root_scope,
868866 .zig_cache_artifact_directory = zig_cache_artifact_directory,
867 .emit_h = options.emit_h,
869868 };
870869 break :blk module;
871 } else null;
870 } else blk: {
871 if (options.emit_h != null) return error.NoZigModuleForCHeader;
872 break :blk null;
873 };
872874 errdefer if (module) |zm| zm.deinit();
873875
874876 const error_return_tracing = !strip and switch (options.optimize_mode) {
......@@ -996,7 +998,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
996998 .local_cache_directory = options.local_cache_directory,
997999 .global_cache_directory = options.global_cache_directory,
9981000 .bin_file = bin_file,
999 .c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null,
10001001 .emit_asm = options.emit_asm,
10011002 .emit_llvm_ir = options.emit_llvm_ir,
10021003 .emit_analysis = options.emit_analysis,
......@@ -1218,10 +1219,6 @@ pub fn destroy(self: *Compilation) void {
12181219 }
12191220 self.failed_c_objects.deinit(gpa);
12201221
1221 if (self.c_header) |*header| {
1222 header.deinit();
1223 }
1224
12251222 self.cache_parent.manifest_dir.close();
12261223 if (self.owned_link_dir) |*dir| dir.close();
12271224
......@@ -1325,20 +1322,6 @@ pub fn update(self: *Compilation) !void {
13251322 module.root_scope.unload(self.gpa);
13261323 }
13271324 }
1328
1329 // If we've chosen to emit a C header, flush the header to the disk.
1330 if (self.c_header) |header| {
1331 const header_path = header.emit_loc.?;
1332 // If a directory has been provided, write the header there. Otherwise, just write it to the
1333 // cache directory.
1334 const header_dir = if (header_path.directory) |dir|
1335 dir.handle
1336 else
1337 self.local_cache_directory.handle;
1338 const header_file = try header_dir.createFile(header_path.basename, .{});
1339 defer header_file.close();
1340 try header.flush(header_file.writer());
1341 }
13421325}
13431326
13441327/// 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
14971480 switch (err) {
14981481 error.OutOfMemory => return error.OutOfMemory,
14991482 error.AnalysisFail => {
1500 decl.analysis = .dependency_failure;
1483 decl.analysis = .codegen_failure;
15011484 },
15021485 else => {
15031486 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
15121495 }
15131496 return;
15141497 };
1515
1516 if (self.c_header) |*header| {
1517 c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) {
1518 error.OutOfMemory => return error.OutOfMemory,
1519 error.AnalysisFail => {
1520 decl.analysis = .dependency_failure;
1521 },
1522 else => {
1523 try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
1524 module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
1525 module.gpa,
1526 decl.src(),
1527 "unable to generate C header: {s}",
1528 .{@errorName(err)},
1529 ));
1530 decl.analysis = .codegen_failure_retryable;
1531 },
1532 };
1533 }
15341498 },
15351499 },
15361500 .analyze_decl => |decl| {
......@@ -2998,9 +2962,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
29982962 man.hash.add(comp.bin_file.options.function_sections);
29992963 man.hash.add(comp.bin_file.options.is_test);
30002964 man.hash.add(comp.bin_file.options.emit != null);
3001 man.hash.add(comp.c_header != null);
3002 if (comp.c_header) |header| {
3003 man.hash.addEmitLoc(header.emit_loc.?);
2965 man.hash.add(mod.emit_h != null);
2966 if (mod.emit_h) |emit_h| {
2967 man.hash.addEmitLoc(emit_h);
30042968 }
30052969 man.hash.addOptionalEmitLoc(comp.emit_asm);
30062970 man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
......@@ -3105,10 +3069,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
31053069 });
31063070 break :blk try directory.join(arena, &[_][]const u8{bin_basename});
31073071 } else "";
3108 if (comp.c_header != null) {
3072 if (comp.emit_h != null) {
31093073 log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
31103074 }
3111 const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory);
3075 const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory);
31123076 const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
31133077 const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
31143078 const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
src/Module.zig+4-2
......@@ -94,6 +94,8 @@ stage1_flags: packed struct {
9494 reserved: u2 = 0,
9595} = .{},
9696
97emit_h: ?Compilation.EmitLoc,
98
9799pub const Export = struct {
98100 options: std.builtin.ExportOptions,
99101 /// Byte offset into the file that contains the export directive.
......@@ -1943,14 +1945,14 @@ fn allocateNewDecl(
19431945 .coff => .{ .coff = link.File.Coff.TextBlock.empty },
19441946 .elf => .{ .elf = link.File.Elf.TextBlock.empty },
19451947 .macho => .{ .macho = link.File.MachO.TextBlock.empty },
1946 .c => .{ .c = {} },
1948 .c => .{ .c = link.File.C.DeclBlock.empty },
19471949 .wasm => .{ .wasm = {} },
19481950 },
19491951 .fn_link = switch (self.comp.bin_file.tag) {
19501952 .coff => .{ .coff = {} },
19511953 .elf => .{ .elf = link.File.Elf.SrcFn.empty },
19521954 .macho => .{ .macho = link.File.MachO.SrcFn.empty },
1953 .c => .{ .c = {} },
1955 .c => .{ .c = link.File.C.FnBlock.empty },
19541956 .wasm => .{ .wasm = null },
19551957 },
19561958 .generation = 0,
src/codegen/c.zig+491-459
......@@ -1,495 +1,526 @@
11const std = @import("std");
2const mem = std.mem;
3const log = std.log.scoped(.c);
4const Writer = std.ArrayList(u8).Writer;
25
36const link = @import("../link.zig");
47const Module = @import("../Module.zig");
58const Compilation = @import("../Compilation.zig");
6
79const Inst = @import("../ir.zig").Inst;
810const Value = @import("../value.zig").Value;
911const Type = @import("../type.zig").Type;
10
1112const C = link.File.C;
1213const Decl = Module.Decl;
13const mem = std.mem;
14const log = std.log.scoped(.c);
14const trace = @import("../tracy.zig").trace;
1515
16const Writer = std.ArrayList(u8).Writer;
16const Mutability = enum { Const, Mut };
1717
18/// Maps a name from Zig source to C. Currently, this will always give the same
19/// output for any given input, sometimes resulting in broken identifiers.
20fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
21 return allocator.dupe(u8, name);
22}
18pub const CValue = union(enum) {
19 none: void,
20 /// Index into local_names
21 local: usize,
22 /// Index into local_names, but take the address.
23 local_ref: usize,
24 /// A constant instruction, to be rendered inline.
25 constant: *Inst,
26 /// Index into the parameters
27 arg: usize,
28 /// By-value
29 decl: *Decl,
2330
24const Mutability = enum { Const, Mut };
31 pub fn printed(value: CValue, object: *Object) Printed {
32 return .{
33 .value = value,
34 .object = object,
35 };
36 }
37
38 pub const Printed = struct {
39 value: CValue,
40 object: *Object,
41
42 /// TODO this got unwieldly, I want to remove the ability to print this way
43 pub fn format(
44 self: Printed,
45 comptime fmt: []const u8,
46 options: std.fmt.FormatOptions,
47 writer: anytype,
48 ) error{OutOfMemory}!void {
49 if (fmt.len != 0) @compileError("Unknown format string: '" ++ fmt ++ "'");
50 switch (self.value) {
51 .none => unreachable,
52 .local => |i| return std.fmt.format(writer, "t{d}", .{i}),
53 .local_ref => |i| return std.fmt.format(writer, "&t{d}", .{i}),
54 .constant => |inst| {
55 const o = self.object;
56 o.dg.renderValue(writer, inst.ty, inst.value().?) catch |err| switch (err) {
57 error.OutOfMemory => return error.OutOfMemory,
58 error.AnalysisFail => return,
59 };
60 },
61 .arg => |i| return std.fmt.format(writer, "a{d}", .{i}),
62 .decl => |decl| return writer.writeAll(mem.span(decl.name)),
63 }
64 }
65 };
66};
2567
26fn renderTypeAndName(
27 ctx: *Context,
28 writer: Writer,
29 ty: Type,
30 name: []const u8,
31 mutability: Mutability,
32) error{ OutOfMemory, AnalysisFail }!void {
33 var suffix = std.ArrayList(u8).init(&ctx.arena.allocator);
34
35 var render_ty = ty;
36 while (render_ty.zigTypeTag() == .Array) {
37 const sentinel_bit = @boolToInt(render_ty.sentinel() != null);
38 const c_len = render_ty.arrayLen() + sentinel_bit;
39 try suffix.writer().print("[{d}]", .{c_len});
40 render_ty = render_ty.elemType();
68pub const CValueMap = std.AutoHashMap(*Inst, CValue);
69
70/// This data is available when outputting .c code for a Module.
71/// It is not available when generating .h file.
72pub const Object = struct {
73 dg: DeclGen,
74 gpa: *mem.Allocator,
75 code: std.ArrayList(u8),
76 value_map: CValueMap,
77 next_arg_index: usize = 0,
78 next_local_index: usize = 0,
79
80 fn resolveInst(o: *Object, inst: *Inst) !CValue {
81 if (inst.value()) |_| {
82 return CValue{ .constant = inst };
83 }
84 return o.value_map.get(inst).?; // Instruction does not dominate all uses!
4185 }
4286
43 try renderType(ctx, writer, render_ty);
87 fn allocLocalValue(o: *Object) CValue {
88 const result = o.next_local_index;
89 o.next_local_index += 1;
90 return .{ .local = result };
91 }
4492
45 const const_prefix = switch (mutability) {
46 .Const => "const ",
47 .Mut => "",
48 };
49 try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items });
50}
93 fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue {
94 const local_value = o.allocLocalValue();
95 try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability);
96 return local_value;
97 }
5198
52fn renderType(
53 ctx: *Context,
54 writer: Writer,
55 t: Type,
56) error{ OutOfMemory, AnalysisFail }!void {
57 switch (t.zigTypeTag()) {
58 .NoReturn => {
59 try writer.writeAll("zig_noreturn void");
60 },
61 .Void => try writer.writeAll("void"),
62 .Bool => try writer.writeAll("bool"),
63 .Int => {
64 switch (t.tag()) {
65 .u8 => try writer.writeAll("uint8_t"),
66 .i8 => try writer.writeAll("int8_t"),
67 .u16 => try writer.writeAll("uint16_t"),
68 .i16 => try writer.writeAll("int16_t"),
69 .u32 => try writer.writeAll("uint32_t"),
70 .i32 => try writer.writeAll("int32_t"),
71 .u64 => try writer.writeAll("uint64_t"),
72 .i64 => try writer.writeAll("int64_t"),
73 .usize => try writer.writeAll("uintptr_t"),
74 .isize => try writer.writeAll("intptr_t"),
75 .c_short => try writer.writeAll("short"),
76 .c_ushort => try writer.writeAll("unsigned short"),
77 .c_int => try writer.writeAll("int"),
78 .c_uint => try writer.writeAll("unsigned int"),
79 .c_long => try writer.writeAll("long"),
80 .c_ulong => try writer.writeAll("unsigned long"),
81 .c_longlong => try writer.writeAll("long long"),
82 .c_ulonglong => try writer.writeAll("unsigned long long"),
83 .int_signed, .int_unsigned => {
84 const info = t.intInfo(ctx.target);
85 const sign_prefix = switch (info.signedness) {
86 .signed => "i",
87 .unsigned => "",
88 };
89 inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
90 if (info.bits <= nbits) {
91 try writer.print("{s}int{d}_t", .{ sign_prefix, nbits });
92 break;
93 }
99 fn indent(o: *Object) !void {
100 const indent_size = 4;
101 const indent_level = 1;
102 const indent_amt = indent_size * indent_level;
103 try o.code.writer().writeByteNTimes(' ', indent_amt);
104 }
105
106 fn renderTypeAndName(
107 o: *Object,
108 writer: Writer,
109 ty: Type,
110 name: CValue,
111 mutability: Mutability,
112 ) error{ OutOfMemory, AnalysisFail }!void {
113 var suffix = std.ArrayList(u8).init(o.gpa);
114 defer suffix.deinit();
115
116 var render_ty = ty;
117 while (render_ty.zigTypeTag() == .Array) {
118 const sentinel_bit = @boolToInt(render_ty.sentinel() != null);
119 const c_len = render_ty.arrayLen() + sentinel_bit;
120 try suffix.writer().print("[{d}]", .{c_len});
121 render_ty = render_ty.elemType();
122 }
123
124 try o.dg.renderType(writer, render_ty);
125
126 const const_prefix = switch (mutability) {
127 .Const => "const ",
128 .Mut => "",
129 };
130 try writer.print(" {s}{}{s}", .{ const_prefix, name.printed(o), suffix.items });
131 }
132};
133
134/// This data is available both when outputting .c code and when outputting an .h file.
135const DeclGen = struct {
136 module: *Module,
137 decl: *Decl,
138 fwd_decl: std.ArrayList(u8),
139 error_msg: ?*Compilation.ErrorMsg,
140
141 fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
142 dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args);
143 return error.AnalysisFail;
144 }
145
146 fn renderValue(
147 dg: *DeclGen,
148 writer: Writer,
149 t: Type,
150 val: Value,
151 ) error{ OutOfMemory, AnalysisFail }!void {
152 switch (t.zigTypeTag()) {
153 .Int => {
154 if (t.isSignedInt())
155 return writer.print("{d}", .{val.toSignedInt()});
156 return writer.print("{d}", .{val.toUnsignedInt()});
157 },
158 .Pointer => switch (val.tag()) {
159 .undef, .zero => try writer.writeAll("0"),
160 .one => try writer.writeAll("1"),
161 .decl_ref => {
162 const decl = val.castTag(.decl_ref).?.data;
163
164 // Determine if we must pointer cast.
165 const decl_tv = decl.typed_value.most_recent.typed_value;
166 if (t.eql(decl_tv.ty)) {
167 try writer.print("&{s}", .{decl.name});
94168 } else {
95 return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
169 try writer.writeAll("(");
170 try dg.renderType(writer, t);
171 try writer.print(")&{s}", .{decl.name});
96172 }
97173 },
174 .function => {
175 const func = val.castTag(.function).?.data;
176 try writer.print("{s}", .{func.owner_decl.name});
177 },
178 .extern_fn => {
179 const decl = val.castTag(.extern_fn).?.data;
180 try writer.print("{s}", .{decl.name});
181 },
182 else => |e| return dg.fail(
183 dg.decl.src(),
184 "TODO: C backend: implement Pointer value {s}",
185 .{@tagName(e)},
186 ),
187 },
188 .Array => {
189 // First try specific tag representations for more efficiency.
190 switch (val.tag()) {
191 .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"),
192 .bytes => {
193 const bytes = val.castTag(.bytes).?.data;
194 // TODO: make our own C string escape instead of using {Z}
195 try writer.print("\"{Z}\"", .{bytes});
196 },
197 else => {
198 // Fall back to generic implementation.
199 var arena = std.heap.ArenaAllocator.init(dg.module.gpa);
200 defer arena.deinit();
201
202 try writer.writeAll("{");
203 var index: usize = 0;
204 const len = t.arrayLen();
205 const elem_ty = t.elemType();
206 while (index < len) : (index += 1) {
207 if (index != 0) try writer.writeAll(",");
208 const elem_val = try val.elemValue(&arena.allocator, index);
209 try dg.renderValue(writer, elem_ty, elem_val);
210 }
211 if (t.sentinel()) |sentinel_val| {
212 if (index != 0) try writer.writeAll(",");
213 try dg.renderValue(writer, elem_ty, sentinel_val);
214 }
215 try writer.writeAll("}");
216 },
217 }
218 },
219 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{
220 @tagName(e),
221 }),
222 }
223 }
224
225 fn renderFunctionSignature(dg: *DeclGen, w: Writer) !void {
226 const tv = dg.decl.typed_value.most_recent.typed_value;
227 // Determine whether the function is globally visible.
228 const is_global = blk: {
229 switch (tv.val.tag()) {
230 .extern_fn => break :blk true,
231 .function => {
232 const func = tv.val.castTag(.function).?.data;
233 break :blk dg.module.decl_exports.contains(func.owner_decl);
234 },
98235 else => unreachable,
99236 }
100 },
101 .Pointer => {
102 if (t.isSlice()) {
103 return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{});
104 } else {
105 try renderType(ctx, writer, t.elemType());
106 try writer.writeAll(" *");
107 if (t.isConstPtr()) {
108 try writer.writeAll("const ");
109 }
110 if (t.isVolatilePtr()) {
111 try writer.writeAll("volatile ");
237 };
238 if (!is_global) {
239 try w.writeAll("static ");
240 }
241 try dg.renderType(w, tv.ty.fnReturnType());
242 const decl_name = mem.span(dg.decl.name);
243 try w.print(" {s}(", .{decl_name});
244 var param_len = tv.ty.fnParamLen();
245 if (param_len == 0)
246 try w.writeAll("void")
247 else {
248 var index: usize = 0;
249 while (index < param_len) : (index += 1) {
250 if (index > 0) {
251 try w.writeAll(", ");
112252 }
253 try dg.renderType(w, tv.ty.fnParamType(index));
254 try w.print(" a{d}", .{index});
113255 }
114 },
115 .Array => {
116 try renderType(ctx, writer, t.elemType());
117 try writer.writeAll(" *");
118 },
119 else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{
120 @tagName(e),
121 }),
256 }
257 try w.writeByte(')');
122258 }
123}
124259
125fn renderValue(
126 ctx: *Context,
127 writer: Writer,
128 t: Type,
129 val: Value,
130) error{ OutOfMemory, AnalysisFail }!void {
131 switch (t.zigTypeTag()) {
132 .Int => {
133 if (t.isSignedInt())
134 return writer.print("{d}", .{val.toSignedInt()});
135 return writer.print("{d}", .{val.toUnsignedInt()});
136 },
137 .Pointer => switch (val.tag()) {
138 .undef, .zero => try writer.writeAll("0"),
139 .one => try writer.writeAll("1"),
140 .decl_ref => {
141 const decl = val.castTag(.decl_ref).?.data;
142
143 // Determine if we must pointer cast.
144 const decl_tv = decl.typed_value.most_recent.typed_value;
145 if (t.eql(decl_tv.ty)) {
146 try writer.print("&{s}", .{decl.name});
147 } else {
148 try writer.writeAll("(");
149 try renderType(ctx, writer, t);
150 try writer.print(")&{s}", .{decl.name});
151 }
260 fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void {
261 switch (t.zigTypeTag()) {
262 .NoReturn => {
263 try w.writeAll("zig_noreturn void");
152264 },
153 .function => {
154 const func = val.castTag(.function).?.data;
155 try writer.print("{s}", .{func.owner_decl.name});
156 },
157 .extern_fn => {
158 const decl = val.castTag(.extern_fn).?.data;
159 try writer.print("{s}", .{decl.name});
265 .Void => try w.writeAll("void"),
266 .Bool => try w.writeAll("bool"),
267 .Int => {
268 switch (t.tag()) {
269 .u8 => try w.writeAll("uint8_t"),
270 .i8 => try w.writeAll("int8_t"),
271 .u16 => try w.writeAll("uint16_t"),
272 .i16 => try w.writeAll("int16_t"),
273 .u32 => try w.writeAll("uint32_t"),
274 .i32 => try w.writeAll("int32_t"),
275 .u64 => try w.writeAll("uint64_t"),
276 .i64 => try w.writeAll("int64_t"),
277 .usize => try w.writeAll("uintptr_t"),
278 .isize => try w.writeAll("intptr_t"),
279 .c_short => try w.writeAll("short"),
280 .c_ushort => try w.writeAll("unsigned short"),
281 .c_int => try w.writeAll("int"),
282 .c_uint => try w.writeAll("unsigned int"),
283 .c_long => try w.writeAll("long"),
284 .c_ulong => try w.writeAll("unsigned long"),
285 .c_longlong => try w.writeAll("long long"),
286 .c_ulonglong => try w.writeAll("unsigned long long"),
287 .int_signed, .int_unsigned => {
288 const info = t.intInfo(dg.module.getTarget());
289 const sign_prefix = switch (info.signedness) {
290 .signed => "i",
291 .unsigned => "",
292 };
293 inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
294 if (info.bits <= nbits) {
295 try w.print("{s}int{d}_t", .{ sign_prefix, nbits });
296 break;
297 }
298 } else {
299 return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{});
300 }
301 },
302 else => unreachable,
303 }
160304 },
161 else => |e| return ctx.fail(
162 ctx.decl.src(),
163 "TODO: C backend: implement Pointer value {s}",
164 .{@tagName(e)},
165 ),
166 },
167 .Array => {
168 // First try specific tag representations for more efficiency.
169 switch (val.tag()) {
170 .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"),
171 .bytes => {
172 const bytes = val.castTag(.bytes).?.data;
173 // TODO: make our own C string escape instead of using {Z}
174 try writer.print("\"{Z}\"", .{bytes});
175 },
176 else => {
177 // Fall back to generic implementation.
178 try writer.writeAll("{");
179 var index: usize = 0;
180 const len = t.arrayLen();
181 const elem_ty = t.elemType();
182 while (index < len) : (index += 1) {
183 if (index != 0) try writer.writeAll(",");
184 const elem_val = try val.elemValue(&ctx.arena.allocator, index);
185 try renderValue(ctx, writer, elem_ty, elem_val);
305 .Pointer => {
306 if (t.isSlice()) {
307 return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{});
308 } else {
309 try dg.renderType(w, t.elemType());
310 try w.writeAll(" *");
311 if (t.isConstPtr()) {
312 try w.writeAll("const ");
186313 }
187 if (t.sentinel()) |sentinel_val| {
188 if (index != 0) try writer.writeAll(",");
189 try renderValue(ctx, writer, elem_ty, sentinel_val);
314 if (t.isVolatilePtr()) {
315 try w.writeAll("volatile ");
190316 }
191 try writer.writeAll("}");
192 },
193 }
194 },
195 else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{
196 @tagName(e),
197 }),
198 }
199}
200
201fn renderFunctionSignature(
202 ctx: *Context,
203 writer: Writer,
204 decl: *Decl,
205) !void {
206 const tv = decl.typed_value.most_recent.typed_value;
207 // Determine whether the function is globally visible.
208 const is_global = blk: {
209 switch (tv.val.tag()) {
210 .extern_fn => break :blk true,
211 .function => {
212 const func = tv.val.castTag(.function).?.data;
213 break :blk ctx.module.decl_exports.contains(func.owner_decl);
317 }
214318 },
215 else => unreachable,
216 }
217 };
218 if (!is_global) {
219 try writer.writeAll("static ");
220 }
221 try renderType(ctx, writer, tv.ty.fnReturnType());
222 // Use the child allocator directly, as we know the name can be freed before
223 // the rest of the arena.
224 const decl_name = mem.span(decl.name);
225 const name = try map(ctx.arena.child_allocator, decl_name);
226 defer ctx.arena.child_allocator.free(name);
227 try writer.print(" {s}(", .{name});
228 var param_len = tv.ty.fnParamLen();
229 if (param_len == 0)
230 try writer.writeAll("void")
231 else {
232 var index: usize = 0;
233 while (index < param_len) : (index += 1) {
234 if (index > 0) {
235 try writer.writeAll(", ");
236 }
237 try renderType(ctx, writer, tv.ty.fnParamType(index));
238 try writer.print(" arg{d}", .{index});
319 .Array => {
320 try dg.renderType(w, t.elemType());
321 try w.writeAll(" *");
322 },
323 else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{
324 @tagName(e),
325 }),
239326 }
240327 }
241 try writer.writeByte(')');
242}
328};
243329
244fn indent(file: *C) !void {
245 const indent_size = 4;
246 const indent_level = 1;
247 const indent_amt = indent_size * indent_level;
248 try file.main.writer().writeByteNTimes(' ', indent_amt);
249}
330pub fn genDecl(o: *Object) !void {
331 const tracy = trace(@src());
332 defer tracy.end();
250333
251pub fn generate(file: *C, module: *Module, decl: *Decl) !void {
252 const tv = decl.typed_value.most_recent.typed_value;
253
254 var arena = std.heap.ArenaAllocator.init(file.base.allocator);
255 defer arena.deinit();
256 var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator);
257 defer inst_map.deinit();
258 var ctx = Context{
259 .decl = decl,
260 .arena = &arena,
261 .inst_map = &inst_map,
262 .target = file.base.options.target,
263 .header = &file.header,
264 .module = module,
265 };
266 defer {
267 file.error_msg = ctx.error_msg;
268 ctx.deinit();
269 }
334 const tv = o.dg.decl.typed_value.most_recent.typed_value;
270335
271336 if (tv.val.castTag(.function)) |func_payload| {
272 const writer = file.main.writer();
273 try renderFunctionSignature(&ctx, writer, decl);
274
275 try writer.writeAll(" {");
337 const fwd_decl_writer = o.dg.fwd_decl.writer();
338 try o.dg.renderFunctionSignature(fwd_decl_writer);
339 try fwd_decl_writer.writeAll(";\n");
276340
277341 const func: *Module.Fn = func_payload.data;
278342 const instructions = func.body.instructions;
279 if (instructions.len > 0) {
280 try writer.writeAll("\n");
281 for (instructions) |inst| {
282 if (switch (inst.tag) {
283 .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"),
284 .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?),
285 .arg => try genArg(&ctx),
286 .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?),
287 .block => try genBlock(&ctx, file, inst.castTag(.block).?),
288 .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?),
289 .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?),
290 .call => try genCall(&ctx, file, inst.castTag(.call).?),
291 .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="),
292 .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"),
293 .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="),
294 .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"),
295 .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="),
296 .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="),
297 .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?),
298 .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?),
299 .load => try genLoad(&ctx, file, inst.castTag(.load).?),
300 .ret => try genRet(&ctx, file, inst.castTag(.ret).?),
301 .retvoid => try genRetVoid(file),
302 .store => try genStore(&ctx, file, inst.castTag(.store).?),
303 .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"),
304 .unreach => try genUnreach(file, inst.castTag(.unreach).?),
305 else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
306 }) |name| {
307 try ctx.inst_map.putNoClobber(inst, name);
308 }
343 const writer = o.code.writer();
344 try o.dg.renderFunctionSignature(writer);
345 if (instructions.len == 0) {
346 try writer.writeAll(" {}\n\n");
347 return;
348 }
349
350 try writer.writeAll(" {");
351
352 try writer.writeAll("\n");
353 for (instructions) |inst| {
354 const result_value = switch (inst.tag) {
355 .add => try genBinOp(o, inst.castTag(.add).?, "+"),
356 .alloc => try genAlloc(o, inst.castTag(.alloc).?),
357 .arg => genArg(o),
358 .assembly => try genAsm(o, inst.castTag(.assembly).?),
359 .block => try genBlock(o, inst.castTag(.block).?),
360 .bitcast => try genBitcast(o, inst.castTag(.bitcast).?),
361 .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?),
362 .call => try genCall(o, inst.castTag(.call).?),
363 .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, "=="),
364 .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, ">"),
365 .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, ">="),
366 .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, "<"),
367 .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, "<="),
368 .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, "!="),
369 .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?),
370 .intcast => try genIntCast(o, inst.castTag(.intcast).?),
371 .load => try genLoad(o, inst.castTag(.load).?),
372 .ret => try genRet(o, inst.castTag(.ret).?),
373 .retvoid => try genRetVoid(o),
374 .store => try genStore(o, inst.castTag(.store).?),
375 .sub => try genBinOp(o, inst.castTag(.sub).?, "-"),
376 .unreach => try genUnreach(o, inst.castTag(.unreach).?),
377 else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}),
378 };
379 switch (result_value) {
380 .none => {},
381 else => try o.value_map.putNoClobber(inst, result_value),
309382 }
310383 }
311384
312385 try writer.writeAll("}\n\n");
313386 } else if (tv.val.tag() == .extern_fn) {
314 return; // handled when referenced
387 const writer = o.code.writer();
388 try o.dg.renderFunctionSignature(writer);
389 try writer.writeAll(";\n");
315390 } else {
316 const writer = file.constants.writer();
391 const writer = o.code.writer();
317392 try writer.writeAll("static ");
318393
319394 // TODO ask the Decl if it is const
320395 // https://github.com/ziglang/zig/issues/7582
321396
322 try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut);
397 const decl_c_value: CValue = .{ .decl = o.dg.decl };
398 try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut);
323399
324400 try writer.writeAll(" = ");
325 try renderValue(&ctx, writer, tv.ty, tv.val);
401 try o.dg.renderValue(writer, tv.ty, tv.val);
326402 try writer.writeAll(";\n");
327403 }
328404}
329405
330pub fn generateHeader(
331 comp: *Compilation,
332 module: *Module,
333 header: *C.Header,
334 decl: *Decl,
335) error{ AnalysisFail, OutOfMemory }!void {
406pub fn genHeader(comp: *Compilation, dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void {
407 const tracy = trace(@src());
408 defer tracy.end();
409
336410 switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) {
337411 .Fn => {
338 var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa);
339 defer inst_map.deinit();
340
341 var arena = std.heap.ArenaAllocator.init(comp.gpa);
342 defer arena.deinit();
343
344 var ctx = Context{
345 .decl = decl,
346 .arena = &arena,
347 .inst_map = &inst_map,
348 .target = comp.getTarget(),
349 .header = header,
350 .module = module,
351 };
352 const writer = header.buf.writer();
353 renderFunctionSignature(&ctx, writer, decl) catch |err| {
354 if (err == error.AnalysisFail) {
355 try module.failed_decls.put(module.gpa, decl, ctx.error_msg);
356 }
357 return err;
412 dg.renderFunctionSignature() catch |err| switch (err) {
413 error.AnalysisFail => {
414 try dg.module.failed_decls.put(dg.module.gpa, decl, dg.error_msg.?);
415 dg.error_msg = null;
416 return error.AnalysisFail;
417 },
418 else => |e| return e,
358419 };
359 try writer.writeAll(";\n");
420 try dg.fwd_decl.appendSlice(";\n");
360421 },
361422 else => {},
362423 }
363424}
364425
365const Context = struct {
366 decl: *Decl,
367 inst_map: *std.AutoHashMap(*Inst, []u8),
368 arena: *std.heap.ArenaAllocator,
369 argdex: usize = 0,
370 unnamed_index: usize = 0,
371 error_msg: *Compilation.ErrorMsg = undefined,
372 target: std.Target,
373 header: *C.Header,
374 module: *Module,
375
376 fn resolveInst(self: *Context, inst: *Inst) ![]u8 {
377 if (inst.value()) |val| {
378 var out = std.ArrayList(u8).init(&self.arena.allocator);
379 try renderValue(self, out.writer(), inst.ty, val);
380 return out.toOwnedSlice();
381 }
382 return self.inst_map.get(inst).?; // Instruction does not dominate all uses!
383 }
384
385 fn name(self: *Context) ![]u8 {
386 const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index});
387 self.unnamed_index += 1;
388 return val;
389 }
390
391 fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
392 self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args);
393 return error.AnalysisFail;
394 }
395
396 fn deinit(self: *Context) void {
397 self.* = undefined;
398 }
399};
400
401fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 {
402 const writer = file.main.writer();
426fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue {
427 const writer = o.code.writer();
403428
404429 // First line: the variable used as data storage.
405 try indent(file);
406 const local_name = try ctx.name();
430 try o.indent();
407431 const elem_type = alloc.base.ty.elemType();
408432 const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut;
409 try renderTypeAndName(ctx, writer, elem_type, local_name, mutability);
433 const local = try o.allocLocal(elem_type, mutability);
410434 try writer.writeAll(";\n");
411435
412 // Second line: a pointer to it so that we can refer to it as the allocation.
413 // One line for the variable, one line for the pointer to the variable, which we return.
414 try indent(file);
415 const ptr_local_name = try ctx.name();
416 try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const);
417 try writer.print(" = &{s};\n", .{local_name});
418
419 return ptr_local_name;
436 return CValue{ .local_ref = local.local };
420437}
421438
422fn genArg(ctx: *Context) !?[]u8 {
423 const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex});
424 ctx.argdex += 1;
425 return name;
439fn genArg(o: *Object) CValue {
440 const i = o.next_arg_index;
441 o.next_arg_index += 1;
442 return .{ .arg = i };
426443}
427444
428fn genRetVoid(file: *C) !?[]u8 {
429 try indent(file);
430 try file.main.writer().print("return;\n", .{});
431 return null;
445fn genRetVoid(o: *Object) !CValue {
446 try o.indent();
447 try o.code.writer().print("return;\n", .{});
448 return CValue.none;
432449}
433450
434fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
435 const operand = try ctx.resolveInst(inst.operand);
436 const writer = file.main.writer();
437 try indent(file);
438 const local_name = try ctx.name();
439 try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const);
440 try writer.print(" = *{s};\n", .{operand});
441 return local_name;
451fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue {
452 const operand = try o.resolveInst(inst.operand);
453 const writer = o.code.writer();
454 try o.indent();
455 const local = try o.allocLocal(inst.base.ty, .Const);
456 switch (operand) {
457 .local_ref => |i| {
458 const wrapped: CValue = .{ .local = i };
459 try writer.print(" = {};\n", .{wrapped.printed(o)});
460 },
461 else => {
462 try writer.print(" = *{};\n", .{operand.printed(o)});
463 },
464 }
465 return local;
442466}
443467
444fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
445 try indent(file);
446 const writer = file.main.writer();
447 try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)});
448 return null;
468fn genRet(o: *Object, inst: *Inst.UnOp) !CValue {
469 const operand = try o.resolveInst(inst.operand);
470 try o.indent();
471 try o.code.writer().print("return {};\n", .{operand.printed(o)});
472 return CValue.none;
449473}
450474
451fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
475fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue {
452476 if (inst.base.isUnused())
453 return null;
454 try indent(file);
455 const writer = file.main.writer();
456 const name = try ctx.name();
457 const from = try ctx.resolveInst(inst.operand);
477 return CValue.none;
458478
459 try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const);
479 const from = try o.resolveInst(inst.operand);
480
481 try o.indent();
482 const writer = o.code.writer();
483 const local = try o.allocLocal(inst.base.ty, .Const);
460484 try writer.writeAll(" = (");
461 try renderType(ctx, writer, inst.base.ty);
462 try writer.print("){s};\n", .{from});
463 return name;
485 try o.dg.renderType(writer, inst.base.ty);
486 try writer.print("){};\n", .{from.printed(o)});
487 return local;
464488}
465489
466fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 {
490fn genStore(o: *Object, inst: *Inst.BinOp) !CValue {
467491 // *a = b;
468 try indent(file);
469 const writer = file.main.writer();
470 const dest_ptr_name = try ctx.resolveInst(inst.lhs);
471 const src_val_name = try ctx.resolveInst(inst.rhs);
472 try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name });
473 return null;
492 const dest_ptr = try o.resolveInst(inst.lhs);
493 const src_val = try o.resolveInst(inst.rhs);
494
495 try o.indent();
496 const writer = o.code.writer();
497 switch (dest_ptr) {
498 .local_ref => |i| {
499 const dest: CValue = .{ .local = i };
500 try writer.print("{} = {};\n", .{ dest.printed(o), src_val.printed(o) });
501 },
502 else => {
503 try writer.print("*{} = {};\n", .{ dest_ptr.printed(o), src_val.printed(o) });
504 },
505 }
506 return CValue.none;
474507}
475508
476fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 {
509fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue {
477510 if (inst.base.isUnused())
478 return null;
479 try indent(file);
480 const lhs = try ctx.resolveInst(inst.lhs);
481 const rhs = try ctx.resolveInst(inst.rhs);
482 const writer = file.main.writer();
483 const name = try ctx.name();
484 try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const);
485 try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs });
486 return name;
511 return CValue.none;
512
513 const lhs = try o.resolveInst(inst.lhs);
514 const rhs = try o.resolveInst(inst.rhs);
515
516 try o.indent();
517 const writer = o.code.writer();
518 const local = try o.allocLocal(inst.base.ty, .Const);
519 try writer.print(" = {} {s} {};\n", .{ lhs.printed(o), operator, rhs.printed(o) });
520 return local;
487521}
488522
489fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
490 try indent(file);
491 const writer = file.main.writer();
492 const header = file.header.buf.writer();
523fn genCall(o: *Object, inst: *Inst.Call) !CValue {
493524 if (inst.func.castTag(.constant)) |func_inst| {
494525 const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn|
495526 extern_fn.data
......@@ -501,23 +532,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
501532 const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty;
502533 const ret_ty = fn_ty.fnReturnType();
503534 const unused_result = inst.base.isUnused();
504 var result_name: ?[]u8 = null;
535 var result_local: CValue = .none;
536
537 try o.indent();
538 const writer = o.code.writer();
505539 if (unused_result) {
506540 if (ret_ty.hasCodeGenBits()) {
507541 try writer.print("(void)", .{});
508542 }
509543 } else {
510 const local_name = try ctx.name();
511 try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const);
544 result_local = try o.allocLocal(ret_ty, .Const);
512545 try writer.writeAll(" = ");
513 result_name = local_name;
514546 }
515547 const fn_name = mem.spanZ(fn_decl.name);
516 if (file.called.get(fn_name) == null) {
517 try file.called.put(fn_name, {});
518 try renderFunctionSignature(ctx, header, fn_decl);
519 try header.writeAll(";\n");
520 }
521548 try writer.print("{s}(", .{fn_name});
522549 if (inst.args.len != 0) {
523550 for (inst.args) |arg, i| {
......@@ -525,87 +552,88 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 {
525552 try writer.writeAll(", ");
526553 }
527554 if (arg.value()) |val| {
528 try renderValue(ctx, writer, arg.ty, val);
555 try o.dg.renderValue(writer, arg.ty, val);
529556 } else {
530 const val = try ctx.resolveInst(arg);
531 try writer.print("{s}", .{val});
557 const val = try o.resolveInst(arg);
558 try writer.print("{}", .{val.printed(o)});
532559 }
533560 }
534561 }
535562 try writer.writeAll(");\n");
536 return result_name;
563 return result_local;
537564 } else {
538 return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{});
565 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{});
539566 }
540567}
541568
542fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 {
569fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue {
543570 // TODO emit #line directive here with line number and filename
544 return null;
571 return CValue.none;
545572}
546573
547fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 {
548 return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{});
574fn genBlock(o: *Object, inst: *Inst.Block) !CValue {
575 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{});
549576}
550577
551fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 {
552 const writer = file.main.writer();
553 try indent(file);
554 const local_name = try ctx.name();
555 const operand = try ctx.resolveInst(inst.operand);
556 try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const);
578fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue {
579 const operand = try o.resolveInst(inst.operand);
580
581 const writer = o.code.writer();
582 try o.indent();
557583 if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) {
584 const local = try o.allocLocal(inst.base.ty, .Const);
558585 try writer.writeAll(" = (");
559 try renderType(ctx, writer, inst.base.ty);
560 try writer.print("){s};\n", .{operand});
561 } else {
562 try writer.writeAll(";\n");
563 try indent(file);
564 try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name });
586 try o.dg.renderType(writer, inst.base.ty);
587 try writer.print("){};\n", .{operand.printed(o)});
588 return local;
565589 }
566 return local_name;
590
591 const local = try o.allocLocal(inst.base.ty, .Mut);
592 try writer.writeAll(";\n");
593 try o.indent();
594 try writer.print("memcpy(&{}, &{}, sizeof {});\n", .{
595 local.printed(o), operand.printed(o), local.printed(o),
596 });
597 return local;
567598}
568599
569fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 {
570 try indent(file);
571 try file.main.writer().writeAll("zig_breakpoint();\n");
572 return null;
600fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue {
601 try o.indent();
602 try o.code.writer().writeAll("zig_breakpoint();\n");
603 return CValue.none;
573604}
574605
575fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 {
576 try indent(file);
577 try file.main.writer().writeAll("zig_unreachable();\n");
578 return null;
606fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue {
607 try o.indent();
608 try o.code.writer().writeAll("zig_unreachable();\n");
609 return CValue.none;
579610}
580611
581fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
582 try indent(file);
583 const writer = file.main.writer();
612fn genAsm(o: *Object, as: *Inst.Assembly) !CValue {
613 if (as.base.isUnused() and !as.is_volatile)
614 return CValue.none;
615
616 const writer = o.code.writer();
584617 for (as.inputs) |i, index| {
585618 if (i[0] == '{' and i[i.len - 1] == '}') {
586619 const reg = i[1 .. i.len - 1];
587620 const arg = as.args[index];
621 const arg_c_value = try o.resolveInst(arg);
622 try o.indent();
588623 try writer.writeAll("register ");
589 try renderType(ctx, writer, arg.ty);
590 try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg });
591 // TODO merge constant handling into inst_map as well
592 if (arg.castTag(.constant)) |c| {
593 try renderValue(ctx, writer, arg.ty, c.val);
594 try writer.writeAll(";\n ");
595 } else {
596 const gop = try ctx.inst_map.getOrPut(arg);
597 if (!gop.found_existing) {
598 return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{});
599 }
600 try writer.print("{s};\n ", .{gop.entry.value});
601 }
624 try o.dg.renderType(writer, arg.ty);
625 try writer.print(" {s}_constant __asm__(\"{s}\") = {};\n", .{
626 reg, reg, arg_c_value.printed(o),
627 });
602628 } else {
603 return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{});
629 return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{});
604630 }
605631 }
606 try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
607 if (as.output) |o| {
608 return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{});
632 try o.indent();
633 const volatile_string: []const u8 = if (as.is_volatile) "volatile " else "";
634 try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source });
635 if (as.output) |_| {
636 return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{});
609637 }
610638 if (as.inputs.len > 0) {
611639 if (as.output == null) {
......@@ -627,5 +655,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 {
627655 }
628656 }
629657 try writer.writeAll(");\n");
630 return null;
658
659 if (as.base.isUnused())
660 return CValue.none;
661
662 return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{});
631663}
src/link.zig+11-8
......@@ -130,7 +130,7 @@ pub const File = struct {
130130 elf: Elf.TextBlock,
131131 coff: Coff.TextBlock,
132132 macho: MachO.TextBlock,
133 c: void,
133 c: C.DeclBlock,
134134 wasm: void,
135135 };
136136
......@@ -138,7 +138,7 @@ pub const File = struct {
138138 elf: Elf.SrcFn,
139139 coff: Coff.SrcFn,
140140 macho: MachO.SrcFn,
141 c: void,
141 c: C.FnBlock,
142142 wasm: ?Wasm.FnData,
143143 };
144144
......@@ -291,7 +291,7 @@ pub const File = struct {
291291 .coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl),
292292 .elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
293293 .macho => return @fieldParentPtr(MachO, "base", base).updateDecl(module, decl),
294 .c => {},
294 .c => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
295295 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDecl(module, decl),
296296 }
297297 }
......@@ -301,7 +301,8 @@ pub const File = struct {
301301 .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
302302 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
303303 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
304 .c, .wasm => {},
304 .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl),
305 .wasm => {},
305306 }
306307 }
307308
......@@ -312,7 +313,8 @@ pub const File = struct {
312313 .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl),
313314 .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
314315 .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
315 .c, .wasm => {},
316 .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
317 .wasm => {},
316318 }
317319 }
318320
......@@ -407,12 +409,13 @@ pub const File = struct {
407409 }
408410 }
409411
412 /// Called when a Decl is deleted from the Module.
410413 pub fn freeDecl(base: *File, decl: *Module.Decl) void {
411414 switch (base.tag) {
412415 .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
413416 .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
414417 .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
415 .c => {},
418 .c => @fieldParentPtr(C, "base", base).freeDecl(decl),
416419 .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
417420 }
418421 }
......@@ -432,14 +435,14 @@ pub const File = struct {
432435 pub fn updateDeclExports(
433436 base: *File,
434437 module: *Module,
435 decl: *const Module.Decl,
438 decl: *Module.Decl,
436439 exports: []const *Module.Export,
437440 ) !void {
438441 switch (base.tag) {
439442 .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports),
440443 .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
441444 .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
442 .c => return {},
445 .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports),
443446 .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
444447 }
445448 }
src/link/C.zig+120-75
......@@ -11,45 +11,28 @@ const trace = @import("../tracy.zig").trace;
1111const C = @This();
1212
1313pub const base_tag: link.File.Tag = .c;
14pub const zig_h = @embedFile("C/zig.h");
1415
15pub const Header = struct {
16 buf: std.ArrayList(u8),
17 emit_loc: ?Compilation.EmitLoc,
18
19 pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header {
20 return .{
21 .buf = std.ArrayList(u8).init(allocator),
22 .emit_loc = emit_loc,
23 };
24 }
25
26 pub fn flush(self: *const Header, writer: anytype) !void {
27 const tracy = trace(@src());
28 defer tracy.end();
16base: link.File,
2917
30 try writer.writeAll(@embedFile("cbe.h"));
31 if (self.buf.items.len > 0) {
32 try writer.print("{s}", .{self.buf.items});
33 }
34 }
18/// Per-declaration data. For functions this is the body, and
19/// the forward declaration is stored in the FnBlock.
20pub const DeclBlock = struct {
21 code: std.ArrayListUnmanaged(u8),
3522
36 pub fn deinit(self: *Header) void {
37 self.buf.deinit();
38 self.* = undefined;
39 }
23 pub const empty: DeclBlock = .{
24 .code = .{},
25 };
4026};
4127
42base: link.File,
43
44path: []const u8,
28/// Per-function data.
29pub const FnBlock = struct {
30 fwd_decl: std.ArrayListUnmanaged(u8),
4531
46// These are only valid during a flush()!
47header: Header,
48constants: std.ArrayList(u8),
49main: std.ArrayList(u8),
50called: std.StringHashMap(void),
51
52error_msg: *Compilation.ErrorMsg = undefined,
32 pub const empty: FnBlock = .{
33 .fwd_decl = .{},
34 };
35};
5336
5437pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {
5538 assert(options.object_format == .c);
......@@ -57,6 +40,14 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
5740 if (options.use_llvm) return error.LLVMHasNoCBackend;
5841 if (options.use_lld) return error.LLDHasNoCBackend;
5942
43 const file = try options.emit.?.directory.handle.createFile(sub_path, .{
44 .truncate = true,
45 .mode = link.determineMode(options),
46 });
47 errdefer file.close();
48
49 try file.writeAll(zig_h);
50
6051 var c_file = try allocator.create(C);
6152 errdefer allocator.destroy(c_file);
6253
......@@ -64,25 +55,75 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
6455 .base = .{
6556 .tag = .c,
6657 .options = options,
67 .file = null,
58 .file = file,
6859 .allocator = allocator,
6960 },
70 .main = undefined,
71 .header = undefined,
72 .constants = undefined,
73 .called = undefined,
74 .path = sub_path,
7561 };
7662
7763 return c_file;
7864}
7965
80pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
81 self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args);
82 return error.AnalysisFail;
66pub fn deinit(self: *C) void {
67 const module = self.base.options.module orelse return;
68 for (module.decl_table.items()) |entry| {
69 self.freeDecl(entry.value);
70 }
71}
72
73pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
74
75pub fn freeDecl(self: *C, decl: *Module.Decl) void {
76 decl.link.c.code.deinit(self.base.allocator);
77 decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
78}
79
80pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
81 const tracy = trace(@src());
82 defer tracy.end();
83
84 const fwd_decl = &decl.fn_link.c.fwd_decl;
85 const code = &decl.link.c.code;
86 fwd_decl.shrinkRetainingCapacity(0);
87 code.shrinkRetainingCapacity(0);
88
89 var object: codegen.Object = .{
90 .dg = .{
91 .module = module,
92 .error_msg = null,
93 .decl = decl,
94 .fwd_decl = fwd_decl.toManaged(module.gpa),
95 },
96 .gpa = module.gpa,
97 .code = code.toManaged(module.gpa),
98 .value_map = codegen.CValueMap.init(module.gpa),
99 };
100 defer object.value_map.deinit();
101 defer object.code.deinit();
102 defer object.dg.fwd_decl.deinit();
103
104 codegen.genDecl(&object) catch |err| switch (err) {
105 error.AnalysisFail => {},
106 else => |e| return e,
107 };
108 // The code may populate this error without returning error.AnalysisFail.
109 if (object.dg.error_msg) |msg| {
110 try module.failed_decls.put(module.gpa, decl, msg);
111 return;
112 }
113
114 fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
115 code.* = object.code.moveToUnmanaged();
116
117 // Free excess allocated memory for this Decl.
118 fwd_decl.shrink(module.gpa, fwd_decl.items.len);
119 code.shrink(module.gpa, code.items.len);
83120}
84121
85pub fn deinit(self: *C) void {}
122pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void {
123 // The C backend does not have the ability to fix line numbers without re-generating
124 // the entire Decl.
125 return self.updateDecl(module, decl);
126}
86127
87128pub fn flush(self: *C, comp: *Compilation) !void {
88129 return self.flushModule(comp);
......@@ -92,41 +133,45 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
92133 const tracy = trace(@src());
93134 defer tracy.end();
94135
95 self.main = std.ArrayList(u8).init(self.base.allocator);
96 self.header = Header.init(self.base.allocator, null);
97 self.constants = std.ArrayList(u8).init(self.base.allocator);
98 self.called = std.StringHashMap(void).init(self.base.allocator);
99 defer self.main.deinit();
100 defer self.header.deinit();
101 defer self.constants.deinit();
102 defer self.called.deinit();
103
104 const module = self.base.options.module.?;
105 for (self.base.options.module.?.decl_table.entries.items) |kv| {
106 codegen.generate(self, module, kv.value) catch |err| {
107 if (err == error.AnalysisFail) {
108 try module.failed_decls.put(module.gpa, kv.value, self.error_msg);
109 }
110 return err;
111 };
112 }
136 const file = self.base.file.?;
113137
114 const file = try self.base.options.emit.?.directory.handle.createFile(self.path, .{ .truncate = true, .read = true, .mode = link.determineMode(self.base.options) });
115 defer file.close();
138 // The header is written upon opening; here we truncate and seek to after the header.
139 // TODO: use writev
140 try file.seekTo(zig_h.len);
141 try file.setEndPos(zig_h.len);
116142
117 const writer = file.writer();
118 try self.header.flush(writer);
119 if (self.header.buf.items.len > 0) {
120 try writer.writeByte('\n');
121 }
122 if (self.constants.items.len > 0) {
123 try writer.print("{s}\n", .{self.constants.items});
143 var buffered_writer = std.io.bufferedWriter(file.writer());
144 const writer = buffered_writer.writer();
145
146 const module = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
147
148 // Forward decls and non-functions first.
149 // TODO: use writev
150 for (module.decl_table.items()) |kv| {
151 const decl = kv.value;
152 const decl_tv = decl.typed_value.most_recent.typed_value;
153 if (decl_tv.val.castTag(.function)) |_| {
154 try writer.writeAll(decl.fn_link.c.fwd_decl.items);
155 } else {
156 try writer.writeAll(decl.link.c.code.items);
157 }
124158 }
125 if (self.main.items.len > 1) {
126 const last_two = self.main.items[self.main.items.len - 2 ..];
127 if (std.mem.eql(u8, last_two, "\n\n")) {
128 self.main.items.len -= 1;
159
160 // Now the function bodies.
161 for (module.decl_table.items()) |kv| {
162 const decl = kv.value;
163 const decl_tv = decl.typed_value.most_recent.typed_value;
164 if (decl_tv.val.castTag(.function)) |_| {
165 try writer.writeAll(decl.link.c.code.items);
129166 }
130167 }
131 try writer.writeAll(self.main.items);
168
169 try buffered_writer.flush();
132170}
171
172pub fn updateDeclExports(
173 self: *C,
174 module: *Module,
175 decl: *Module.Decl,
176 exports: []const *Module.Export,
177) !void {}
src/link/C/zig.h created+45
......@@ -0,0 +1,45 @@
1#if __STDC_VERSION__ >= 199901L
2#include <stdbool.h>
3#else
4#define bool unsigned char
5#define true 1
6#define false 0
7#endif
8
9#if __STDC_VERSION__ >= 201112L
10#define zig_noreturn _Noreturn
11#elif __GNUC__
12#define zig_noreturn __attribute__ ((noreturn))
13#elif _MSC_VER
14#define zig_noreturn __declspec(noreturn)
15#else
16#define zig_noreturn
17#endif
18
19#if defined(__GNUC__)
20#define zig_unreachable() __builtin_unreachable()
21#else
22#define zig_unreachable()
23#endif
24
25#if defined(_MSC_VER)
26#define zig_breakpoint __debugbreak()
27#else
28#if defined(__MINGW32__) || defined(__MINGW64__)
29#define zig_breakpoint __debugbreak()
30#elif defined(__clang__)
31#define zig_breakpoint __builtin_debugtrap()
32#elif defined(__GNUC__)
33#define zig_breakpoint __builtin_trap()
34#elif defined(__i386__) || defined(__x86_64__)
35#define zig_breakpoint __asm__ volatile("int $0x03");
36#else
37#define zig_breakpoint raise(SIGTRAP)
38#endif
39#endif
40
41#include <stdint.h>
42#define int128_t __int128
43#define uint128_t unsigned __int128
44#include <string.h>
45
src/link/cbe.h deleted-44
......@@ -1,44 +0,0 @@
1#if __STDC_VERSION__ >= 199901L
2#include <stdbool.h>
3#else
4#define bool unsigned char
5#define true 1
6#define false 0
7#endif
8
9#if __STDC_VERSION__ >= 201112L
10#define zig_noreturn _Noreturn
11#elif __GNUC__
12#define zig_noreturn __attribute__ ((noreturn))
13#elif _MSC_VER
14#define zig_noreturn __declspec(noreturn)
15#else
16#define zig_noreturn
17#endif
18
19#if defined(__GNUC__)
20#define zig_unreachable() __builtin_unreachable()
21#else
22#define zig_unreachable()
23#endif
24
25#if defined(_MSC_VER)
26#define zig_breakpoint __debugbreak()
27#else
28#if defined(__MINGW32__) || defined(__MINGW64__)
29#define zig_breakpoint __debugbreak()
30#elif defined(__clang__)
31#define zig_breakpoint __builtin_debugtrap()
32#elif defined(__GNUC__)
33#define zig_breakpoint __builtin_trap()
34#elif defined(__i386__) || defined(__x86_64__)
35#define zig_breakpoint __asm__ volatile("int $0x03");
36#else
37#define zig_breakpoint raise(SIGTRAP)
38#endif
39#endif
40
41#include <stdint.h>
42#define int128_t __int128
43#define uint128_t unsigned __int128
44#include <string.h>
src/test.zig+9-8
......@@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d
1313const ThreadPool = @import("ThreadPool.zig");
1414const CrossTarget = std.zig.CrossTarget;
1515
16const c_header = @embedFile("link/cbe.h");
16const zig_h = link.File.C.zig_h;
1717
1818test "self-hosted" {
1919 var ctx = TestContext.init();
......@@ -324,11 +324,11 @@ pub const TestContext = struct {
324324 }
325325
326326 pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
327 ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out);
327 ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out);
328328 }
329329
330330 pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
331 ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out);
331 ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out);
332332 }
333333
334334 pub fn addCompareOutput(
......@@ -700,11 +700,12 @@ pub const TestContext = struct {
700700 },
701701 }
702702 }
703 if (comp.bin_file.cast(link.File.C)) |c_file| {
704 std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
705 c_file.main.items,
706 });
707 }
703 // TODO print generated C code
704 //if (comp.bin_file.cast(link.File.C)) |c_file| {
705 // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
706 // c_file.main.items,
707 // });
708 //}
708709 std.debug.print("Test failed.\n", .{});
709710 std.process.exit(1);
710711 }
test/stage2/cbe.zig-2
......@@ -22,8 +22,6 @@ pub fn addCases(ctx: *TestContext) !void {
2222 , "hello world!" ++ std.cstr.line_sep);
2323
2424 // Now change the message only
25 // TODO fix C backend not supporting updates
26 // https://github.com/ziglang/zig/issues/7589
2725 case.addCompareOutput(
2826 \\extern fn puts(s: [*:0]const u8) c_int;
2927 \\export fn main() c_int {