| author | |
| committer | |
| log | 38572ee89492d8499a5881d12c401d9bb0925167 |
| tree | 2ff6f00b6545697c386740032f9a32d124cfb438 |
| parent | 6c4924408b1957d493568271a0158b1190574dd0 |
| parent | 3e39d0c44fbf0cfb56ef0c00fc8ec7c0d5e1c1ec |
Reworks the C backend and -femit-h to properly participate in
incremental compilation.
closes #760212 files changed, 1044 insertions(+), 903 deletions(-)
CMakeLists.txt+1-1| ... | @@ -559,7 +559,7 @@ set(ZIG_STAGE2_SOURCES | ... | @@ -559,7 +559,7 @@ set(ZIG_STAGE2_SOURCES |
| 559 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" | 559 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" |
| 560 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" | 560 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" |
| 561 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" | 561 | "${CMAKE_SOURCE_DIR}/src/link/Wasm.zig" |
| 562 | "${CMAKE_SOURCE_DIR}/src/link/cbe.h" | 562 | "${CMAKE_SOURCE_DIR}/src/link/C/zig.h" |
| 563 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" | 563 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" |
| 564 | "${CMAKE_SOURCE_DIR}/src/liveness.zig" | 564 | "${CMAKE_SOURCE_DIR}/src/liveness.zig" |
| 565 | "${CMAKE_SOURCE_DIR}/src/llvm_backend.zig" | 565 | "${CMAKE_SOURCE_DIR}/src/llvm_backend.zig" |
lib/std/array_list.zig+12-10| ... | @@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { | ... | @@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type { |
| 100 | 100 | ||
| 101 | /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields | 101 | /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields |
| 102 | /// of this ArrayList. This ArrayList retains ownership of underlying memory. | 102 | /// of this ArrayList. This ArrayList retains ownership of underlying memory. |
| 103 | /// Deprecated: use `moveToUnmanaged` which has different semantics. | ||
| 103 | pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { | 104 | pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { |
| 104 | return .{ .items = self.items, .capacity = self.capacity }; | 105 | return .{ .items = self.items, .capacity = self.capacity }; |
| 105 | } | 106 | } |
| 106 | 107 | ||
| 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 | |||
| 107 | /// The caller owns the returned memory. Empties this ArrayList. | 117 | /// The caller owns the returned memory. Empties this ArrayList. |
| 108 | pub fn toOwnedSlice(self: *Self) Slice { | 118 | pub fn toOwnedSlice(self: *Self) Slice { |
| 109 | const allocator = self.allocator; | 119 | const allocator = self.allocator; |
| ... | @@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ | ... | @@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ |
| 551 | mem.copy(T, self.items[oldlen..], items); | 561 | mem.copy(T, self.items[oldlen..], items); |
| 552 | } | 562 | } |
| 553 | 563 | ||
| 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 | |||
| 562 | /// Append a value to the list `n` times. | 564 | /// Append a value to the list `n` times. |
| 563 | /// Allocates more memory as necessary. | 565 | /// Allocates more memory as necessary. |
| 564 | pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void { | 566 | 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" { | ... | @@ -1129,13 +1131,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" { |
| 1129 | } | 1131 | } |
| 1130 | } | 1132 | } |
| 1131 | 1133 | ||
| 1132 | test "std.ArrayList(u8) implements outStream" { | 1134 | test "std.ArrayList(u8) implements writer" { |
| 1133 | var buffer = ArrayList(u8).init(std.testing.allocator); | 1135 | var buffer = ArrayList(u8).init(std.testing.allocator); |
| 1134 | defer buffer.deinit(); | 1136 | defer buffer.deinit(); |
| 1135 | 1137 | ||
| 1136 | const x: i32 = 42; | 1138 | const x: i32 = 42; |
| 1137 | const y: i32 = 1234; | 1139 | const y: i32 = 1234; |
| 1138 | try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y }); | 1140 | try buffer.writer().print("x: {}\ny: {}\n", .{ x, y }); |
| 1139 | 1141 | ||
| 1140 | testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); | 1142 | testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items); |
| 1141 | } | 1143 | } |
lib/std/fs/file.zig+8| ... | @@ -459,6 +459,7 @@ pub const File = struct { | ... | @@ -459,6 +459,7 @@ pub const File = struct { |
| 459 | return index; | 459 | return index; |
| 460 | } | 460 | } |
| 461 | 461 | ||
| 462 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 462 | pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { | 463 | pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { |
| 463 | if (is_windows) { | 464 | if (is_windows) { |
| 464 | // TODO improve this to use ReadFileScatter | 465 | // TODO improve this to use ReadFileScatter |
| ... | @@ -479,6 +480,7 @@ pub const File = struct { | ... | @@ -479,6 +480,7 @@ pub const File = struct { |
| 479 | /// is not an error condition. | 480 | /// is not an error condition. |
| 480 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in | 481 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 481 | /// order to handle partial reads from the underlying OS layer. | 482 | /// order to handle partial reads from the underlying OS layer. |
| 483 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 482 | pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { | 484 | pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { |
| 483 | if (iovecs.len == 0) return; | 485 | if (iovecs.len == 0) return; |
| 484 | 486 | ||
| ... | @@ -500,6 +502,7 @@ pub const File = struct { | ... | @@ -500,6 +502,7 @@ pub const File = struct { |
| 500 | } | 502 | } |
| 501 | } | 503 | } |
| 502 | 504 | ||
| 505 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 503 | pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { | 506 | pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { |
| 504 | if (is_windows) { | 507 | if (is_windows) { |
| 505 | // TODO improve this to use ReadFileScatter | 508 | // TODO improve this to use ReadFileScatter |
| ... | @@ -520,6 +523,7 @@ pub const File = struct { | ... | @@ -520,6 +523,7 @@ pub const File = struct { |
| 520 | /// is not an error condition. | 523 | /// is not an error condition. |
| 521 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in | 524 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 522 | /// order to handle partial reads from the underlying OS layer. | 525 | /// order to handle partial reads from the underlying OS layer. |
| 526 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 523 | pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { | 527 | pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { |
| 524 | if (iovecs.len == 0) return; | 528 | if (iovecs.len == 0) return; |
| 525 | 529 | ||
| ... | @@ -582,6 +586,7 @@ pub const File = struct { | ... | @@ -582,6 +586,7 @@ pub const File = struct { |
| 582 | } | 586 | } |
| 583 | } | 587 | } |
| 584 | 588 | ||
| 589 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 585 | pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { | 590 | pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { |
| 586 | if (is_windows) { | 591 | if (is_windows) { |
| 587 | // TODO improve this to use WriteFileScatter | 592 | // TODO improve this to use WriteFileScatter |
| ... | @@ -599,6 +604,7 @@ pub const File = struct { | ... | @@ -599,6 +604,7 @@ pub const File = struct { |
| 599 | 604 | ||
| 600 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in | 605 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 601 | /// order to handle partial writes from the underlying OS layer. | 606 | /// order to handle partial writes from the underlying OS layer. |
| 607 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 602 | pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { | 608 | pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { |
| 603 | if (iovecs.len == 0) return; | 609 | if (iovecs.len == 0) return; |
| 604 | 610 | ||
| ... | @@ -615,6 +621,7 @@ pub const File = struct { | ... | @@ -615,6 +621,7 @@ pub const File = struct { |
| 615 | } | 621 | } |
| 616 | } | 622 | } |
| 617 | 623 | ||
| 624 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 618 | pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { | 625 | pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { |
| 619 | if (is_windows) { | 626 | if (is_windows) { |
| 620 | // TODO improve this to use WriteFileScatter | 627 | // TODO improve this to use WriteFileScatter |
| ... | @@ -632,6 +639,7 @@ pub const File = struct { | ... | @@ -632,6 +639,7 @@ pub const File = struct { |
| 632 | 639 | ||
| 633 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in | 640 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 634 | /// order to handle partial writes from the underlying OS layer. | 641 | /// order to handle partial writes from the underlying OS layer. |
| 642 | /// See https://github.com/ziglang/zig/issues/7699 | ||
| 635 | pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { | 643 | pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { |
| 636 | if (iovecs.len == 0) return; | 644 | if (iovecs.len == 0) return; |
| 637 | 645 |
src/Compilation.zig+84-66| ... | @@ -27,7 +27,6 @@ const Cache = @import("Cache.zig"); | ... | @@ -27,7 +27,6 @@ const Cache = @import("Cache.zig"); |
| 27 | const stage1 = @import("stage1.zig"); | 27 | const stage1 = @import("stage1.zig"); |
| 28 | const translate_c = @import("translate_c.zig"); | 28 | const translate_c = @import("translate_c.zig"); |
| 29 | const c_codegen = @import("codegen/c.zig"); | 29 | const c_codegen = @import("codegen/c.zig"); |
| 30 | const c_link = @import("link/C.zig"); | ||
| 31 | const ThreadPool = @import("ThreadPool.zig"); | 30 | const ThreadPool = @import("ThreadPool.zig"); |
| 32 | const WaitGroup = @import("WaitGroup.zig"); | 31 | const WaitGroup = @import("WaitGroup.zig"); |
| 33 | const libtsan = @import("libtsan.zig"); | 32 | const libtsan = @import("libtsan.zig"); |
| ... | @@ -138,8 +137,6 @@ emit_llvm_ir: ?EmitLoc, | ... | @@ -138,8 +137,6 @@ emit_llvm_ir: ?EmitLoc, |
| 138 | emit_analysis: ?EmitLoc, | 137 | emit_analysis: ?EmitLoc, |
| 139 | emit_docs: ?EmitLoc, | 138 | emit_docs: ?EmitLoc, |
| 140 | 139 | ||
| 141 | c_header: ?c_link.Header, | ||
| 142 | |||
| 143 | work_queue_wait_group: WaitGroup, | 140 | work_queue_wait_group: WaitGroup, |
| 144 | 141 | ||
| 145 | pub const InnerError = Module.InnerError; | 142 | pub const InnerError = Module.InnerError; |
| ... | @@ -164,6 +161,8 @@ pub const CSourceFile = struct { | ... | @@ -164,6 +161,8 @@ pub const CSourceFile = struct { |
| 164 | const Job = union(enum) { | 161 | const Job = union(enum) { |
| 165 | /// Write the machine code for a Decl to the output file. | 162 | /// Write the machine code for a Decl to the output file. |
| 166 | codegen_decl: *Module.Decl, | 163 | codegen_decl: *Module.Decl, |
| 164 | /// Render the .h file snippet for the Decl. | ||
| 165 | emit_h_decl: *Module.Decl, | ||
| 167 | /// The Decl needs to be analyzed and possibly export itself. | 166 | /// The Decl needs to be analyzed and possibly export itself. |
| 168 | /// It may have already be analyzed, or it may have been determined | 167 | /// It may have already be analyzed, or it may have been determined |
| 169 | /// to be outdated; in this case perform semantic analysis again. | 168 | /// to be outdated; in this case perform semantic analysis again. |
| ... | @@ -866,9 +865,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { | ... | @@ -866,9 +865,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 866 | .root_pkg = root_pkg, | 865 | .root_pkg = root_pkg, |
| 867 | .root_scope = root_scope, | 866 | .root_scope = root_scope, |
| 868 | .zig_cache_artifact_directory = zig_cache_artifact_directory, | 867 | .zig_cache_artifact_directory = zig_cache_artifact_directory, |
| 868 | .emit_h = options.emit_h, | ||
| 869 | }; | 869 | }; |
| 870 | break :blk module; | 870 | break :blk module; |
| 871 | } else null; | 871 | } else blk: { |
| 872 | if (options.emit_h != null) return error.NoZigModuleForCHeader; | ||
| 873 | break :blk null; | ||
| 874 | }; | ||
| 872 | errdefer if (module) |zm| zm.deinit(); | 875 | errdefer if (module) |zm| zm.deinit(); |
| 873 | 876 | ||
| 874 | const error_return_tracing = !strip and switch (options.optimize_mode) { | 877 | const error_return_tracing = !strip and switch (options.optimize_mode) { |
| ... | @@ -996,7 +999,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { | ... | @@ -996,7 +999,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 996 | .local_cache_directory = options.local_cache_directory, | 999 | .local_cache_directory = options.local_cache_directory, |
| 997 | .global_cache_directory = options.global_cache_directory, | 1000 | .global_cache_directory = options.global_cache_directory, |
| 998 | .bin_file = bin_file, | 1001 | .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, | ||
| 1000 | .emit_asm = options.emit_asm, | 1002 | .emit_asm = options.emit_asm, |
| 1001 | .emit_llvm_ir = options.emit_llvm_ir, | 1003 | .emit_llvm_ir = options.emit_llvm_ir, |
| 1002 | .emit_analysis = options.emit_analysis, | 1004 | .emit_analysis = options.emit_analysis, |
| ... | @@ -1218,10 +1220,6 @@ pub fn destroy(self: *Compilation) void { | ... | @@ -1218,10 +1220,6 @@ pub fn destroy(self: *Compilation) void { |
| 1218 | } | 1220 | } |
| 1219 | self.failed_c_objects.deinit(gpa); | 1221 | self.failed_c_objects.deinit(gpa); |
| 1220 | 1222 | ||
| 1221 | if (self.c_header) |*header| { | ||
| 1222 | header.deinit(); | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | self.cache_parent.manifest_dir.close(); | 1223 | self.cache_parent.manifest_dir.close(); |
| 1226 | if (self.owned_link_dir) |*dir| dir.close(); | 1224 | if (self.owned_link_dir) |*dir| dir.close(); |
| 1227 | 1225 | ||
| ... | @@ -1315,9 +1313,14 @@ pub fn update(self: *Compilation) !void { | ... | @@ -1315,9 +1313,14 @@ pub fn update(self: *Compilation) !void { |
| 1315 | 1313 | ||
| 1316 | // This is needed before reading the error flags. | 1314 | // This is needed before reading the error flags. |
| 1317 | try self.bin_file.flush(self); | 1315 | try self.bin_file.flush(self); |
| 1318 | |||
| 1319 | self.link_error_flags = self.bin_file.errorFlags(); | 1316 | self.link_error_flags = self.bin_file.errorFlags(); |
| 1320 | 1317 | ||
| 1318 | if (!use_stage1) { | ||
| 1319 | if (self.bin_file.options.module) |module| { | ||
| 1320 | try link.File.C.flushEmitH(module); | ||
| 1321 | } | ||
| 1322 | } | ||
| 1323 | |||
| 1321 | // If there are any errors, we anticipate the source files being loaded | 1324 | // If there are any errors, we anticipate the source files being loaded |
| 1322 | // to report error messages. Otherwise we unload all source files to save memory. | 1325 | // to report error messages. Otherwise we unload all source files to save memory. |
| 1323 | if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { | 1326 | if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { |
| ... | @@ -1325,20 +1328,6 @@ pub fn update(self: *Compilation) !void { | ... | @@ -1325,20 +1328,6 @@ pub fn update(self: *Compilation) !void { |
| 1325 | module.root_scope.unload(self.gpa); | 1328 | module.root_scope.unload(self.gpa); |
| 1326 | } | 1329 | } |
| 1327 | } | 1330 | } |
| 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 | } | ||
| 1342 | } | 1331 | } |
| 1343 | 1332 | ||
| 1344 | /// Having the file open for writing is problematic as far as executing the | 1333 | /// Having the file open for writing is problematic as far as executing the |
| ... | @@ -1357,7 +1346,8 @@ pub fn totalErrorCount(self: *Compilation) usize { | ... | @@ -1357,7 +1346,8 @@ pub fn totalErrorCount(self: *Compilation) usize { |
| 1357 | var total: usize = self.failed_c_objects.items().len; | 1346 | var total: usize = self.failed_c_objects.items().len; |
| 1358 | 1347 | ||
| 1359 | if (self.bin_file.options.module) |module| { | 1348 | if (self.bin_file.options.module) |module| { |
| 1360 | total += module.failed_decls.items().len + | 1349 | total += module.failed_decls.count() + |
| 1350 | module.emit_h_failed_decls.count() + | ||
| 1361 | module.failed_exports.items().len + | 1351 | module.failed_exports.items().len + |
| 1362 | module.failed_files.items().len + | 1352 | module.failed_files.items().len + |
| 1363 | @boolToInt(module.failed_root_src_file != null); | 1353 | @boolToInt(module.failed_root_src_file != null); |
| ... | @@ -1396,6 +1386,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { | ... | @@ -1396,6 +1386,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { |
| 1396 | const source = try decl.scope.getSource(module); | 1386 | const source = try decl.scope.getSource(module); |
| 1397 | try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); | 1387 | try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); |
| 1398 | } | 1388 | } |
| 1389 | for (module.emit_h_failed_decls.items()) |entry| { | ||
| 1390 | const decl = entry.key; | ||
| 1391 | const err_msg = entry.value; | ||
| 1392 | const source = try decl.scope.getSource(module); | ||
| 1393 | try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); | ||
| 1394 | } | ||
| 1399 | for (module.failed_exports.items()) |entry| { | 1395 | for (module.failed_exports.items()) |entry| { |
| 1400 | const decl = entry.key.owner_decl; | 1396 | const decl = entry.key.owner_decl; |
| 1401 | const err_msg = entry.value; | 1397 | const err_msg = entry.value; |
| ... | @@ -1493,44 +1489,66 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor | ... | @@ -1493,44 +1489,66 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor |
| 1493 | 1489 | ||
| 1494 | assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); | 1490 | assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); |
| 1495 | 1491 | ||
| 1496 | self.bin_file.updateDecl(module, decl) catch |err| { | 1492 | self.bin_file.updateDecl(module, decl) catch |err| switch (err) { |
| 1497 | switch (err) { | 1493 | error.OutOfMemory => return error.OutOfMemory, |
| 1498 | error.OutOfMemory => return error.OutOfMemory, | 1494 | error.AnalysisFail => { |
| 1499 | error.AnalysisFail => { | 1495 | decl.analysis = .codegen_failure; |
| 1500 | decl.analysis = .dependency_failure; | 1496 | continue; |
| 1501 | }, | 1497 | }, |
| 1502 | else => { | 1498 | else => { |
| 1503 | try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); | 1499 | try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); |
| 1504 | module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | 1500 | module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( |
| 1505 | module.gpa, | 1501 | module.gpa, |
| 1506 | decl.src(), | 1502 | decl.src(), |
| 1507 | "unable to codegen: {s}", | 1503 | "unable to codegen: {s}", |
| 1508 | .{@errorName(err)}, | 1504 | .{@errorName(err)}, |
| 1509 | )); | 1505 | )); |
| 1510 | decl.analysis = .codegen_failure_retryable; | 1506 | decl.analysis = .codegen_failure_retryable; |
| 1511 | }, | 1507 | continue; |
| 1512 | } | 1508 | }, |
| 1513 | return; | ||
| 1514 | }; | 1509 | }; |
| 1510 | }, | ||
| 1511 | }, | ||
| 1512 | .emit_h_decl => |decl| switch (decl.analysis) { | ||
| 1513 | .unreferenced => unreachable, | ||
| 1514 | .in_progress => unreachable, | ||
| 1515 | .outdated => unreachable, | ||
| 1515 | 1516 | ||
| 1516 | if (self.c_header) |*header| { | 1517 | .sema_failure, |
| 1517 | c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) { | 1518 | .dependency_failure, |
| 1518 | error.OutOfMemory => return error.OutOfMemory, | 1519 | .sema_failure_retryable, |
| 1519 | error.AnalysisFail => { | 1520 | => continue, |
| 1520 | decl.analysis = .dependency_failure; | 1521 | |
| 1521 | }, | 1522 | // emit-h only requires semantic analysis of the Decl to be complete, |
| 1522 | else => { | 1523 | // it does not depend on machine code generation to succeed. |
| 1523 | try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); | 1524 | .codegen_failure, .codegen_failure_retryable, .complete => { |
| 1524 | module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | 1525 | if (build_options.omit_stage2) |
| 1525 | module.gpa, | 1526 | @panic("sadly stage2 is omitted from this build to save memory on the CI server"); |
| 1526 | decl.src(), | 1527 | const module = self.bin_file.options.module.?; |
| 1527 | "unable to generate C header: {s}", | 1528 | const emit_loc = module.emit_h.?; |
| 1528 | .{@errorName(err)}, | 1529 | const tv = decl.typed_value.most_recent.typed_value; |
| 1529 | )); | 1530 | const emit_h = decl.getEmitH(module); |
| 1530 | decl.analysis = .codegen_failure_retryable; | 1531 | const fwd_decl = &emit_h.fwd_decl; |
| 1531 | }, | 1532 | fwd_decl.shrinkRetainingCapacity(0); |
| 1532 | }; | 1533 | |
| 1533 | } | 1534 | var dg: c_codegen.DeclGen = .{ |
| 1535 | .module = module, | ||
| 1536 | .error_msg = null, | ||
| 1537 | .decl = decl, | ||
| 1538 | .fwd_decl = fwd_decl.toManaged(module.gpa), | ||
| 1539 | }; | ||
| 1540 | defer dg.fwd_decl.deinit(); | ||
| 1541 | |||
| 1542 | c_codegen.genHeader(&dg) catch |err| switch (err) { | ||
| 1543 | error.AnalysisFail => { | ||
| 1544 | try module.emit_h_failed_decls.put(module.gpa, decl, dg.error_msg.?); | ||
| 1545 | continue; | ||
| 1546 | }, | ||
| 1547 | else => |e| return e, | ||
| 1548 | }; | ||
| 1549 | |||
| 1550 | fwd_decl.* = dg.fwd_decl.moveToUnmanaged(); | ||
| 1551 | fwd_decl.shrink(module.gpa, fwd_decl.items.len); | ||
| 1534 | }, | 1552 | }, |
| 1535 | }, | 1553 | }, |
| 1536 | .analyze_decl => |decl| { | 1554 | .analyze_decl => |decl| { |
| ... | @@ -2998,9 +3016,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node | ... | @@ -2998,9 +3016,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 2998 | man.hash.add(comp.bin_file.options.function_sections); | 3016 | man.hash.add(comp.bin_file.options.function_sections); |
| 2999 | man.hash.add(comp.bin_file.options.is_test); | 3017 | man.hash.add(comp.bin_file.options.is_test); |
| 3000 | man.hash.add(comp.bin_file.options.emit != null); | 3018 | man.hash.add(comp.bin_file.options.emit != null); |
| 3001 | man.hash.add(comp.c_header != null); | 3019 | man.hash.add(mod.emit_h != null); |
| 3002 | if (comp.c_header) |header| { | 3020 | if (mod.emit_h) |emit_h| { |
| 3003 | man.hash.addEmitLoc(header.emit_loc.?); | 3021 | man.hash.addEmitLoc(emit_h); |
| 3004 | } | 3022 | } |
| 3005 | man.hash.addOptionalEmitLoc(comp.emit_asm); | 3023 | man.hash.addOptionalEmitLoc(comp.emit_asm); |
| 3006 | man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); | 3024 | man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); |
| ... | @@ -3105,10 +3123,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node | ... | @@ -3105,10 +3123,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3105 | }); | 3123 | }); |
| 3106 | break :blk try directory.join(arena, &[_][]const u8{bin_basename}); | 3124 | break :blk try directory.join(arena, &[_][]const u8{bin_basename}); |
| 3107 | } else ""; | 3125 | } else ""; |
| 3108 | if (comp.c_header != null) { | 3126 | if (mod.emit_h != null) { |
| 3109 | log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); | 3127 | log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); |
| 3110 | } | 3128 | } |
| 3111 | const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory); | 3129 | const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory); |
| 3112 | const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); | 3130 | const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); |
| 3113 | const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); | 3131 | const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); |
| 3114 | const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); | 3132 | const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); |
src/Module.zig+69-10| ... | @@ -57,6 +57,10 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has | ... | @@ -57,6 +57,10 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has |
| 57 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, | 57 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, |
| 58 | /// a Decl can have a failed_decls entry but have analysis status of success. | 58 | /// a Decl can have a failed_decls entry but have analysis status of success. |
| 59 | failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, | 59 | failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, |
| 60 | /// When emit_h is non-null, each Decl gets one more compile error slot for | ||
| 61 | /// emit-h failing for that Decl. This table is also how we tell if a Decl has | ||
| 62 | /// failed emit-h or succeeded. | ||
| 63 | emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{}, | ||
| 60 | /// Using a map here for consistency with the other fields here. | 64 | /// Using a map here for consistency with the other fields here. |
| 61 | /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. | 65 | /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. |
| 62 | failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, | 66 | failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, |
| ... | @@ -94,6 +98,8 @@ stage1_flags: packed struct { | ... | @@ -94,6 +98,8 @@ stage1_flags: packed struct { |
| 94 | reserved: u2 = 0, | 98 | reserved: u2 = 0, |
| 95 | } = .{}, | 99 | } = .{}, |
| 96 | 100 | ||
| 101 | emit_h: ?Compilation.EmitLoc, | ||
| 102 | |||
| 97 | pub const Export = struct { | 103 | pub const Export = struct { |
| 98 | options: std.builtin.ExportOptions, | 104 | options: std.builtin.ExportOptions, |
| 99 | /// Byte offset into the file that contains the export directive. | 105 | /// Byte offset into the file that contains the export directive. |
| ... | @@ -114,6 +120,13 @@ pub const Export = struct { | ... | @@ -114,6 +120,13 @@ pub const Export = struct { |
| 114 | }, | 120 | }, |
| 115 | }; | 121 | }; |
| 116 | 122 | ||
| 123 | /// When Module emit_h field is non-null, each Decl is allocated via this struct, so that | ||
| 124 | /// there can be EmitH state attached to each Decl. | ||
| 125 | pub const DeclPlusEmitH = struct { | ||
| 126 | decl: Decl, | ||
| 127 | emit_h: EmitH, | ||
| 128 | }; | ||
| 129 | |||
| 117 | pub const Decl = struct { | 130 | pub const Decl = struct { |
| 118 | /// This name is relative to the containing namespace of the decl. It uses a null-termination | 131 | /// This name is relative to the containing namespace of the decl. It uses a null-termination |
| 119 | /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed | 132 | /// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed |
| ... | @@ -202,14 +215,21 @@ pub const Decl = struct { | ... | @@ -202,14 +215,21 @@ pub const Decl = struct { |
| 202 | /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` | 215 | /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` |
| 203 | pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false); | 216 | pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false); |
| 204 | 217 | ||
| 205 | pub fn destroy(self: *Decl, gpa: *Allocator) void { | 218 | pub fn destroy(self: *Decl, module: *Module) void { |
| 219 | const gpa = module.gpa; | ||
| 206 | gpa.free(mem.spanZ(self.name)); | 220 | gpa.free(mem.spanZ(self.name)); |
| 207 | if (self.typedValueManaged()) |tvm| { | 221 | if (self.typedValueManaged()) |tvm| { |
| 208 | tvm.deinit(gpa); | 222 | tvm.deinit(gpa); |
| 209 | } | 223 | } |
| 210 | self.dependants.deinit(gpa); | 224 | self.dependants.deinit(gpa); |
| 211 | self.dependencies.deinit(gpa); | 225 | self.dependencies.deinit(gpa); |
| 212 | gpa.destroy(self); | 226 | if (module.emit_h != null) { |
| 227 | const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", self); | ||
| 228 | decl_plus_emit_h.emit_h.fwd_decl.deinit(gpa); | ||
| 229 | gpa.destroy(decl_plus_emit_h); | ||
| 230 | } else { | ||
| 231 | gpa.destroy(self); | ||
| 232 | } | ||
| 213 | } | 233 | } |
| 214 | 234 | ||
| 215 | pub fn src(self: Decl) usize { | 235 | pub fn src(self: Decl) usize { |
| ... | @@ -275,6 +295,12 @@ pub const Decl = struct { | ... | @@ -275,6 +295,12 @@ pub const Decl = struct { |
| 275 | return self.scope.cast(Scope.Container).?.file_scope; | 295 | return self.scope.cast(Scope.Container).?.file_scope; |
| 276 | } | 296 | } |
| 277 | 297 | ||
| 298 | pub fn getEmitH(decl: *Decl, module: *Module) *EmitH { | ||
| 299 | assert(module.emit_h != null); | ||
| 300 | const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", decl); | ||
| 301 | return &decl_plus_emit_h.emit_h; | ||
| 302 | } | ||
| 303 | |||
| 278 | fn removeDependant(self: *Decl, other: *Decl) void { | 304 | fn removeDependant(self: *Decl, other: *Decl) void { |
| 279 | self.dependants.removeAssertDiscard(other); | 305 | self.dependants.removeAssertDiscard(other); |
| 280 | } | 306 | } |
| ... | @@ -284,6 +310,11 @@ pub const Decl = struct { | ... | @@ -284,6 +310,11 @@ pub const Decl = struct { |
| 284 | } | 310 | } |
| 285 | }; | 311 | }; |
| 286 | 312 | ||
| 313 | /// This state is attached to every Decl when Module emit_h is non-null. | ||
| 314 | pub const EmitH = struct { | ||
| 315 | fwd_decl: std.ArrayListUnmanaged(u8) = .{}, | ||
| 316 | }; | ||
| 317 | |||
| 287 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. | 318 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. |
| 288 | /// Extern functions do not have this data structure; they are represented by | 319 | /// Extern functions do not have this data structure; they are represented by |
| 289 | /// the `Decl` only, with a `Value` tag of `extern_fn`. | 320 | /// the `Decl` only, with a `Value` tag of `extern_fn`. |
| ... | @@ -881,7 +912,7 @@ pub fn deinit(self: *Module) void { | ... | @@ -881,7 +912,7 @@ pub fn deinit(self: *Module) void { |
| 881 | self.deletion_set.deinit(gpa); | 912 | self.deletion_set.deinit(gpa); |
| 882 | 913 | ||
| 883 | for (self.decl_table.items()) |entry| { | 914 | for (self.decl_table.items()) |entry| { |
| 884 | entry.value.destroy(gpa); | 915 | entry.value.destroy(self); |
| 885 | } | 916 | } |
| 886 | self.decl_table.deinit(gpa); | 917 | self.decl_table.deinit(gpa); |
| 887 | 918 | ||
| ... | @@ -890,6 +921,11 @@ pub fn deinit(self: *Module) void { | ... | @@ -890,6 +921,11 @@ pub fn deinit(self: *Module) void { |
| 890 | } | 921 | } |
| 891 | self.failed_decls.deinit(gpa); | 922 | self.failed_decls.deinit(gpa); |
| 892 | 923 | ||
| 924 | for (self.emit_h_failed_decls.items()) |entry| { | ||
| 925 | entry.value.destroy(gpa); | ||
| 926 | } | ||
| 927 | self.emit_h_failed_decls.deinit(gpa); | ||
| 928 | |||
| 893 | for (self.failed_files.items()) |entry| { | 929 | for (self.failed_files.items()) |entry| { |
| 894 | entry.value.destroy(gpa); | 930 | entry.value.destroy(gpa); |
| 895 | } | 931 | } |
| ... | @@ -1148,6 +1184,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1148,6 +1184,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1148 | try self.comp.bin_file.allocateDeclIndexes(decl); | 1184 | try self.comp.bin_file.allocateDeclIndexes(decl); |
| 1149 | try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); | 1185 | try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); |
| 1150 | 1186 | ||
| 1187 | if (type_changed and self.emit_h != null) { | ||
| 1188 | try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); | ||
| 1189 | } | ||
| 1190 | |||
| 1151 | return type_changed; | 1191 | return type_changed; |
| 1152 | }; | 1192 | }; |
| 1153 | 1193 | ||
| ... | @@ -1267,6 +1307,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { | ... | @@ -1267,6 +1307,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1267 | // increasing how many computations can be done in parallel. | 1307 | // increasing how many computations can be done in parallel. |
| 1268 | try self.comp.bin_file.allocateDeclIndexes(decl); | 1308 | try self.comp.bin_file.allocateDeclIndexes(decl); |
| 1269 | try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); | 1309 | try self.comp.work_queue.writeItem(.{ .codegen_decl = decl }); |
| 1310 | if (type_changed and self.emit_h != null) { | ||
| 1311 | try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl }); | ||
| 1312 | } | ||
| 1270 | } else if (!prev_is_inline and prev_type_has_bits) { | 1313 | } else if (!prev_is_inline and prev_type_has_bits) { |
| 1271 | self.comp.bin_file.freeDecl(decl); | 1314 | self.comp.bin_file.freeDecl(decl); |
| 1272 | } | 1315 | } |
| ... | @@ -1835,9 +1878,13 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { | ... | @@ -1835,9 +1878,13 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1835 | if (self.failed_decls.remove(decl)) |entry| { | 1878 | if (self.failed_decls.remove(decl)) |entry| { |
| 1836 | entry.value.destroy(self.gpa); | 1879 | entry.value.destroy(self.gpa); |
| 1837 | } | 1880 | } |
| 1881 | if (self.emit_h_failed_decls.remove(decl)) |entry| { | ||
| 1882 | entry.value.destroy(self.gpa); | ||
| 1883 | } | ||
| 1838 | self.deleteDeclExports(decl); | 1884 | self.deleteDeclExports(decl); |
| 1839 | self.comp.bin_file.freeDecl(decl); | 1885 | self.comp.bin_file.freeDecl(decl); |
| 1840 | decl.destroy(self.gpa); | 1886 | |
| 1887 | decl.destroy(self); | ||
| 1841 | } | 1888 | } |
| 1842 | 1889 | ||
| 1843 | /// Delete all the Export objects that are caused by this Decl. Re-analysis of | 1890 | /// Delete all the Export objects that are caused by this Decl. Re-analysis of |
| ... | @@ -1921,16 +1968,28 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { | ... | @@ -1921,16 +1968,28 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void { |
| 1921 | if (self.failed_decls.remove(decl)) |entry| { | 1968 | if (self.failed_decls.remove(decl)) |entry| { |
| 1922 | entry.value.destroy(self.gpa); | 1969 | entry.value.destroy(self.gpa); |
| 1923 | } | 1970 | } |
| 1971 | if (self.emit_h_failed_decls.remove(decl)) |entry| { | ||
| 1972 | entry.value.destroy(self.gpa); | ||
| 1973 | } | ||
| 1924 | decl.analysis = .outdated; | 1974 | decl.analysis = .outdated; |
| 1925 | } | 1975 | } |
| 1926 | 1976 | ||
| 1927 | fn allocateNewDecl( | 1977 | fn allocateNewDecl( |
| 1928 | self: *Module, | 1978 | mod: *Module, |
| 1929 | scope: *Scope, | 1979 | scope: *Scope, |
| 1930 | src_index: usize, | 1980 | src_index: usize, |
| 1931 | contents_hash: std.zig.SrcHash, | 1981 | contents_hash: std.zig.SrcHash, |
| 1932 | ) !*Decl { | 1982 | ) !*Decl { |
| 1933 | const new_decl = try self.gpa.create(Decl); | 1983 | // If we have emit-h then we must allocate a bigger structure to store the emit-h state. |
| 1984 | const new_decl: *Decl = if (mod.emit_h != null) blk: { | ||
| 1985 | const parent_struct = try mod.gpa.create(DeclPlusEmitH); | ||
| 1986 | parent_struct.* = .{ | ||
| 1987 | .emit_h = .{}, | ||
| 1988 | .decl = undefined, | ||
| 1989 | }; | ||
| 1990 | break :blk &parent_struct.decl; | ||
| 1991 | } else try mod.gpa.create(Decl); | ||
| 1992 | |||
| 1934 | new_decl.* = .{ | 1993 | new_decl.* = .{ |
| 1935 | .name = "", | 1994 | .name = "", |
| 1936 | .scope = scope.namespace(), | 1995 | .scope = scope.namespace(), |
| ... | @@ -1939,18 +1998,18 @@ fn allocateNewDecl( | ... | @@ -1939,18 +1998,18 @@ fn allocateNewDecl( |
| 1939 | .analysis = .unreferenced, | 1998 | .analysis = .unreferenced, |
| 1940 | .deletion_flag = false, | 1999 | .deletion_flag = false, |
| 1941 | .contents_hash = contents_hash, | 2000 | .contents_hash = contents_hash, |
| 1942 | .link = switch (self.comp.bin_file.tag) { | 2001 | .link = switch (mod.comp.bin_file.tag) { |
| 1943 | .coff => .{ .coff = link.File.Coff.TextBlock.empty }, | 2002 | .coff => .{ .coff = link.File.Coff.TextBlock.empty }, |
| 1944 | .elf => .{ .elf = link.File.Elf.TextBlock.empty }, | 2003 | .elf => .{ .elf = link.File.Elf.TextBlock.empty }, |
| 1945 | .macho => .{ .macho = link.File.MachO.TextBlock.empty }, | 2004 | .macho => .{ .macho = link.File.MachO.TextBlock.empty }, |
| 1946 | .c => .{ .c = {} }, | 2005 | .c => .{ .c = link.File.C.DeclBlock.empty }, |
| 1947 | .wasm => .{ .wasm = {} }, | 2006 | .wasm => .{ .wasm = {} }, |
| 1948 | }, | 2007 | }, |
| 1949 | .fn_link = switch (self.comp.bin_file.tag) { | 2008 | .fn_link = switch (mod.comp.bin_file.tag) { |
| 1950 | .coff => .{ .coff = {} }, | 2009 | .coff => .{ .coff = {} }, |
| 1951 | .elf => .{ .elf = link.File.Elf.SrcFn.empty }, | 2010 | .elf => .{ .elf = link.File.Elf.SrcFn.empty }, |
| 1952 | .macho => .{ .macho = link.File.MachO.SrcFn.empty }, | 2011 | .macho => .{ .macho = link.File.MachO.SrcFn.empty }, |
| 1953 | .c => .{ .c = {} }, | 2012 | .c => .{ .c = link.File.C.FnBlock.empty }, |
| 1954 | .wasm => .{ .wasm = null }, | 2013 | .wasm => .{ .wasm = null }, |
| 1955 | }, | 2014 | }, |
| 1956 | .generation = 0, | 2015 | .generation = 0, |
src/codegen/c.zig+504-456| ... | @@ -1,495 +1,532 @@ | ... | @@ -1,495 +1,532 @@ |
| 1 | const std = @import("std"); | 1 | const std = @import("std"); |
| 2 | const mem = std.mem; | ||
| 3 | const log = std.log.scoped(.c); | ||
| 4 | const Writer = std.ArrayList(u8).Writer; | ||
| 2 | 5 | ||
| 3 | const link = @import("../link.zig"); | 6 | const link = @import("../link.zig"); |
| 4 | const Module = @import("../Module.zig"); | 7 | const Module = @import("../Module.zig"); |
| 5 | const Compilation = @import("../Compilation.zig"); | 8 | const Compilation = @import("../Compilation.zig"); |
| 6 | |||
| 7 | const Inst = @import("../ir.zig").Inst; | 9 | const Inst = @import("../ir.zig").Inst; |
| 8 | const Value = @import("../value.zig").Value; | 10 | const Value = @import("../value.zig").Value; |
| 9 | const Type = @import("../type.zig").Type; | 11 | const Type = @import("../type.zig").Type; |
| 10 | 12 | const TypedValue = @import("../TypedValue.zig"); | |
| 11 | const C = link.File.C; | 13 | const C = link.File.C; |
| 12 | const Decl = Module.Decl; | 14 | const Decl = Module.Decl; |
| 13 | const mem = std.mem; | 15 | const trace = @import("../tracy.zig").trace; |
| 14 | const log = std.log.scoped(.c); | ||
| 15 | 16 | ||
| 16 | const Writer = std.ArrayList(u8).Writer; | 17 | const Mutability = enum { Const, Mut }; |
| 17 | 18 | ||
| 18 | /// Maps a name from Zig source to C. Currently, this will always give the same | 19 | pub const CValue = union(enum) { |
| 19 | /// output for any given input, sometimes resulting in broken identifiers. | 20 | none: void, |
| 20 | fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { | 21 | /// Index into local_names |
| 21 | return allocator.dupe(u8, name); | 22 | local: usize, |
| 22 | } | 23 | /// Index into local_names, but take the address. |
| 24 | local_ref: usize, | ||
| 25 | /// A constant instruction, to be rendered inline. | ||
| 26 | constant: *Inst, | ||
| 27 | /// Index into the parameters | ||
| 28 | arg: usize, | ||
| 29 | /// By-value | ||
| 30 | decl: *Decl, | ||
| 31 | }; | ||
| 23 | 32 | ||
| 24 | const Mutability = enum { Const, Mut }; | 33 | pub const CValueMap = std.AutoHashMap(*Inst, CValue); |
| 34 | |||
| 35 | /// This data is available when outputting .c code for a Module. | ||
| 36 | /// It is not available when generating .h file. | ||
| 37 | pub const Object = struct { | ||
| 38 | dg: DeclGen, | ||
| 39 | gpa: *mem.Allocator, | ||
| 40 | code: std.ArrayList(u8), | ||
| 41 | value_map: CValueMap, | ||
| 42 | next_arg_index: usize = 0, | ||
| 43 | next_local_index: usize = 0, | ||
| 44 | |||
| 45 | fn resolveInst(o: *Object, inst: *Inst) !CValue { | ||
| 46 | if (inst.value()) |_| { | ||
| 47 | return CValue{ .constant = inst }; | ||
| 48 | } | ||
| 49 | return o.value_map.get(inst).?; // Instruction does not dominate all uses! | ||
| 50 | } | ||
| 25 | 51 | ||
| 26 | fn renderTypeAndName( | 52 | fn allocLocalValue(o: *Object) CValue { |
| 27 | ctx: *Context, | 53 | const result = o.next_local_index; |
| 28 | writer: Writer, | 54 | o.next_local_index += 1; |
| 29 | ty: Type, | 55 | return .{ .local = result }; |
| 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(); | ||
| 41 | } | 56 | } |
| 42 | 57 | ||
| 43 | try renderType(ctx, writer, render_ty); | 58 | fn allocLocal(o: *Object, ty: Type, mutability: Mutability) !CValue { |
| 59 | const local_value = o.allocLocalValue(); | ||
| 60 | try o.renderTypeAndName(o.code.writer(), ty, local_value, mutability); | ||
| 61 | return local_value; | ||
| 62 | } | ||
| 44 | 63 | ||
| 45 | const const_prefix = switch (mutability) { | 64 | fn indent(o: *Object) !void { |
| 46 | .Const => "const ", | 65 | const indent_size = 4; |
| 47 | .Mut => "", | 66 | const indent_level = 1; |
| 48 | }; | 67 | const indent_amt = indent_size * indent_level; |
| 49 | try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); | 68 | try o.code.writer().writeByteNTimes(' ', indent_amt); |
| 50 | } | 69 | } |
| 51 | 70 | ||
| 52 | fn renderType( | 71 | fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void { |
| 53 | ctx: *Context, | 72 | switch (c_value) { |
| 54 | writer: Writer, | 73 | .none => unreachable, |
| 55 | t: Type, | 74 | .local => |i| return writer.print("t{d}", .{i}), |
| 56 | ) error{ OutOfMemory, AnalysisFail }!void { | 75 | .local_ref => |i| return writer.print("&t{d}", .{i}), |
| 57 | switch (t.zigTypeTag()) { | 76 | .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?), |
| 58 | .NoReturn => { | 77 | .arg => |i| return writer.print("a{d}", .{i}), |
| 59 | try writer.writeAll("zig_noreturn void"); | 78 | .decl => |decl| return writer.writeAll(mem.span(decl.name)), |
| 60 | }, | 79 | } |
| 61 | .Void => try writer.writeAll("void"), | 80 | } |
| 62 | .Bool => try writer.writeAll("bool"), | 81 | |
| 63 | .Int => { | 82 | fn renderTypeAndName( |
| 64 | switch (t.tag()) { | 83 | o: *Object, |
| 65 | .u8 => try writer.writeAll("uint8_t"), | 84 | writer: Writer, |
| 66 | .i8 => try writer.writeAll("int8_t"), | 85 | ty: Type, |
| 67 | .u16 => try writer.writeAll("uint16_t"), | 86 | name: CValue, |
| 68 | .i16 => try writer.writeAll("int16_t"), | 87 | mutability: Mutability, |
| 69 | .u32 => try writer.writeAll("uint32_t"), | 88 | ) error{ OutOfMemory, AnalysisFail }!void { |
| 70 | .i32 => try writer.writeAll("int32_t"), | 89 | var suffix = std.ArrayList(u8).init(o.gpa); |
| 71 | .u64 => try writer.writeAll("uint64_t"), | 90 | defer suffix.deinit(); |
| 72 | .i64 => try writer.writeAll("int64_t"), | 91 | |
| 73 | .usize => try writer.writeAll("uintptr_t"), | 92 | var render_ty = ty; |
| 74 | .isize => try writer.writeAll("intptr_t"), | 93 | while (render_ty.zigTypeTag() == .Array) { |
| 75 | .c_short => try writer.writeAll("short"), | 94 | const sentinel_bit = @boolToInt(render_ty.sentinel() != null); |
| 76 | .c_ushort => try writer.writeAll("unsigned short"), | 95 | const c_len = render_ty.arrayLen() + sentinel_bit; |
| 77 | .c_int => try writer.writeAll("int"), | 96 | try suffix.writer().print("[{d}]", .{c_len}); |
| 78 | .c_uint => try writer.writeAll("unsigned int"), | 97 | render_ty = render_ty.elemType(); |
| 79 | .c_long => try writer.writeAll("long"), | 98 | } |
| 80 | .c_ulong => try writer.writeAll("unsigned long"), | 99 | |
| 81 | .c_longlong => try writer.writeAll("long long"), | 100 | try o.dg.renderType(writer, render_ty); |
| 82 | .c_ulonglong => try writer.writeAll("unsigned long long"), | 101 | |
| 83 | .int_signed, .int_unsigned => { | 102 | const const_prefix = switch (mutability) { |
| 84 | const info = t.intInfo(ctx.target); | 103 | .Const => "const ", |
| 85 | const sign_prefix = switch (info.signedness) { | 104 | .Mut => "", |
| 86 | .signed => "i", | 105 | }; |
| 87 | .unsigned => "", | 106 | try writer.print(" {s}", .{const_prefix}); |
| 88 | }; | 107 | try o.writeCValue(writer, name); |
| 89 | inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { | 108 | try writer.writeAll(suffix.items); |
| 90 | if (info.bits <= nbits) { | 109 | } |
| 91 | try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); | 110 | }; |
| 92 | break; | 111 | |
| 93 | } | 112 | /// This data is available both when outputting .c code and when outputting an .h file. |
| 113 | pub const DeclGen = struct { | ||
| 114 | module: *Module, | ||
| 115 | decl: *Decl, | ||
| 116 | fwd_decl: std.ArrayList(u8), | ||
| 117 | error_msg: ?*Compilation.ErrorMsg, | ||
| 118 | |||
| 119 | fn fail(dg: *DeclGen, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { | ||
| 120 | dg.error_msg = try Compilation.ErrorMsg.create(dg.module.gpa, src, format, args); | ||
| 121 | return error.AnalysisFail; | ||
| 122 | } | ||
| 123 | |||
| 124 | fn renderValue( | ||
| 125 | dg: *DeclGen, | ||
| 126 | writer: Writer, | ||
| 127 | t: Type, | ||
| 128 | val: Value, | ||
| 129 | ) error{ OutOfMemory, AnalysisFail }!void { | ||
| 130 | switch (t.zigTypeTag()) { | ||
| 131 | .Int => { | ||
| 132 | if (t.isSignedInt()) | ||
| 133 | return writer.print("{d}", .{val.toSignedInt()}); | ||
| 134 | return writer.print("{d}", .{val.toUnsignedInt()}); | ||
| 135 | }, | ||
| 136 | .Pointer => switch (val.tag()) { | ||
| 137 | .undef, .zero => try writer.writeAll("0"), | ||
| 138 | .one => try writer.writeAll("1"), | ||
| 139 | .decl_ref => { | ||
| 140 | const decl = val.castTag(.decl_ref).?.data; | ||
| 141 | |||
| 142 | // Determine if we must pointer cast. | ||
| 143 | const decl_tv = decl.typed_value.most_recent.typed_value; | ||
| 144 | if (t.eql(decl_tv.ty)) { | ||
| 145 | try writer.print("&{s}", .{decl.name}); | ||
| 94 | } else { | 146 | } else { |
| 95 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); | 147 | try writer.writeAll("("); |
| 148 | try dg.renderType(writer, t); | ||
| 149 | try writer.print(")&{s}", .{decl.name}); | ||
| 96 | } | 150 | } |
| 97 | }, | 151 | }, |
| 98 | else => unreachable, | 152 | .function => { |
| 99 | } | 153 | const func = val.castTag(.function).?.data; |
| 100 | }, | 154 | try writer.print("{s}", .{func.owner_decl.name}); |
| 101 | .Pointer => { | 155 | }, |
| 102 | if (t.isSlice()) { | 156 | .extern_fn => { |
| 103 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); | 157 | const decl = val.castTag(.extern_fn).?.data; |
| 104 | } else { | 158 | try writer.print("{s}", .{decl.name}); |
| 105 | try renderType(ctx, writer, t.elemType()); | 159 | }, |
| 106 | try writer.writeAll(" *"); | 160 | else => |e| return dg.fail( |
| 107 | if (t.isConstPtr()) { | 161 | dg.decl.src(), |
| 108 | try writer.writeAll("const "); | 162 | "TODO: C backend: implement Pointer value {s}", |
| 163 | .{@tagName(e)}, | ||
| 164 | ), | ||
| 165 | }, | ||
| 166 | .Array => { | ||
| 167 | // First try specific tag representations for more efficiency. | ||
| 168 | switch (val.tag()) { | ||
| 169 | .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), | ||
| 170 | .bytes => { | ||
| 171 | const bytes = val.castTag(.bytes).?.data; | ||
| 172 | // TODO: make our own C string escape instead of using {Z} | ||
| 173 | try writer.print("\"{Z}\"", .{bytes}); | ||
| 174 | }, | ||
| 175 | else => { | ||
| 176 | // Fall back to generic implementation. | ||
| 177 | var arena = std.heap.ArenaAllocator.init(dg.module.gpa); | ||
| 178 | defer arena.deinit(); | ||
| 179 | |||
| 180 | try writer.writeAll("{"); | ||
| 181 | var index: usize = 0; | ||
| 182 | const len = t.arrayLen(); | ||
| 183 | const elem_ty = t.elemType(); | ||
| 184 | while (index < len) : (index += 1) { | ||
| 185 | if (index != 0) try writer.writeAll(","); | ||
| 186 | const elem_val = try val.elemValue(&arena.allocator, index); | ||
| 187 | try dg.renderValue(writer, elem_ty, elem_val); | ||
| 188 | } | ||
| 189 | if (t.sentinel()) |sentinel_val| { | ||
| 190 | if (index != 0) try writer.writeAll(","); | ||
| 191 | try dg.renderValue(writer, elem_ty, sentinel_val); | ||
| 192 | } | ||
| 193 | try writer.writeAll("}"); | ||
| 194 | }, | ||
| 109 | } | 195 | } |
| 110 | if (t.isVolatilePtr()) { | 196 | }, |
| 111 | try writer.writeAll("volatile "); | 197 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement value {s}", .{ |
| 198 | @tagName(e), | ||
| 199 | }), | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | fn renderFunctionSignature(dg: *DeclGen, w: Writer, is_global: bool) !void { | ||
| 204 | if (!is_global) { | ||
| 205 | try w.writeAll("static "); | ||
| 206 | } | ||
| 207 | const tv = dg.decl.typed_value.most_recent.typed_value; | ||
| 208 | try dg.renderType(w, tv.ty.fnReturnType()); | ||
| 209 | const decl_name = mem.span(dg.decl.name); | ||
| 210 | try w.print(" {s}(", .{decl_name}); | ||
| 211 | var param_len = tv.ty.fnParamLen(); | ||
| 212 | if (param_len == 0) | ||
| 213 | try w.writeAll("void") | ||
| 214 | else { | ||
| 215 | var index: usize = 0; | ||
| 216 | while (index < param_len) : (index += 1) { | ||
| 217 | if (index > 0) { | ||
| 218 | try w.writeAll(", "); | ||
| 112 | } | 219 | } |
| 220 | try dg.renderType(w, tv.ty.fnParamType(index)); | ||
| 221 | try w.print(" a{d}", .{index}); | ||
| 113 | } | 222 | } |
| 114 | }, | 223 | } |
| 115 | .Array => { | 224 | try w.writeByte(')'); |
| 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 | }), | ||
| 122 | } | 225 | } |
| 123 | } | ||
| 124 | 226 | ||
| 125 | fn renderValue( | 227 | fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void { |
| 126 | ctx: *Context, | 228 | switch (t.zigTypeTag()) { |
| 127 | writer: Writer, | 229 | .NoReturn => { |
| 128 | t: Type, | 230 | try w.writeAll("zig_noreturn void"); |
| 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 | } | ||
| 152 | }, | ||
| 153 | .function => { | ||
| 154 | const func = val.castTag(.function).?.data; | ||
| 155 | try writer.print("{s}", .{func.owner_decl.name}); | ||
| 156 | }, | 231 | }, |
| 157 | .extern_fn => { | 232 | .Void => try w.writeAll("void"), |
| 158 | const decl = val.castTag(.extern_fn).?.data; | 233 | .Bool => try w.writeAll("bool"), |
| 159 | try writer.print("{s}", .{decl.name}); | 234 | .Int => { |
| 235 | switch (t.tag()) { | ||
| 236 | .u8 => try w.writeAll("uint8_t"), | ||
| 237 | .i8 => try w.writeAll("int8_t"), | ||
| 238 | .u16 => try w.writeAll("uint16_t"), | ||
| 239 | .i16 => try w.writeAll("int16_t"), | ||
| 240 | .u32 => try w.writeAll("uint32_t"), | ||
| 241 | .i32 => try w.writeAll("int32_t"), | ||
| 242 | .u64 => try w.writeAll("uint64_t"), | ||
| 243 | .i64 => try w.writeAll("int64_t"), | ||
| 244 | .usize => try w.writeAll("uintptr_t"), | ||
| 245 | .isize => try w.writeAll("intptr_t"), | ||
| 246 | .c_short => try w.writeAll("short"), | ||
| 247 | .c_ushort => try w.writeAll("unsigned short"), | ||
| 248 | .c_int => try w.writeAll("int"), | ||
| 249 | .c_uint => try w.writeAll("unsigned int"), | ||
| 250 | .c_long => try w.writeAll("long"), | ||
| 251 | .c_ulong => try w.writeAll("unsigned long"), | ||
| 252 | .c_longlong => try w.writeAll("long long"), | ||
| 253 | .c_ulonglong => try w.writeAll("unsigned long long"), | ||
| 254 | .int_signed, .int_unsigned => { | ||
| 255 | const info = t.intInfo(dg.module.getTarget()); | ||
| 256 | const sign_prefix = switch (info.signedness) { | ||
| 257 | .signed => "i", | ||
| 258 | .unsigned => "", | ||
| 259 | }; | ||
| 260 | inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { | ||
| 261 | if (info.bits <= nbits) { | ||
| 262 | try w.print("{s}int{d}_t", .{ sign_prefix, nbits }); | ||
| 263 | break; | ||
| 264 | } | ||
| 265 | } else { | ||
| 266 | return dg.fail(dg.decl.src(), "TODO: C backend: implement integer types larger than 128 bits", .{}); | ||
| 267 | } | ||
| 268 | }, | ||
| 269 | else => unreachable, | ||
| 270 | } | ||
| 160 | }, | 271 | }, |
| 161 | else => |e| return ctx.fail( | 272 | .Pointer => { |
| 162 | ctx.decl.src(), | 273 | if (t.isSlice()) { |
| 163 | "TODO: C backend: implement Pointer value {s}", | 274 | return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); |
| 164 | .{@tagName(e)}, | 275 | } else { |
| 165 | ), | 276 | try dg.renderType(w, t.elemType()); |
| 166 | }, | 277 | try w.writeAll(" *"); |
| 167 | .Array => { | 278 | if (t.isConstPtr()) { |
| 168 | // First try specific tag representations for more efficiency. | 279 | try w.writeAll("const "); |
| 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); | ||
| 186 | } | 280 | } |
| 187 | if (t.sentinel()) |sentinel_val| { | 281 | if (t.isVolatilePtr()) { |
| 188 | if (index != 0) try writer.writeAll(","); | 282 | try w.writeAll("volatile "); |
| 189 | try renderValue(ctx, writer, elem_ty, sentinel_val); | ||
| 190 | } | 283 | } |
| 191 | try writer.writeAll("}"); | 284 | } |
| 192 | }, | 285 | }, |
| 193 | } | 286 | .Array => { |
| 194 | }, | 287 | try dg.renderType(w, t.elemType()); |
| 195 | else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ | 288 | try w.writeAll(" *"); |
| 196 | @tagName(e), | 289 | }, |
| 197 | }), | 290 | else => |e| return dg.fail(dg.decl.src(), "TODO: C backend: implement type {s}", .{ |
| 291 | @tagName(e), | ||
| 292 | }), | ||
| 293 | } | ||
| 198 | } | 294 | } |
| 199 | } | ||
| 200 | 295 | ||
| 201 | fn renderFunctionSignature( | 296 | fn functionIsGlobal(dg: *DeclGen, tv: TypedValue) bool { |
| 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()) { | 297 | switch (tv.val.tag()) { |
| 210 | .extern_fn => break :blk true, | 298 | .extern_fn => return true, |
| 211 | .function => { | 299 | .function => { |
| 212 | const func = tv.val.castTag(.function).?.data; | 300 | const func = tv.val.castTag(.function).?.data; |
| 213 | break :blk ctx.module.decl_exports.contains(func.owner_decl); | 301 | return dg.module.decl_exports.contains(func.owner_decl); |
| 214 | }, | 302 | }, |
| 215 | else => unreachable, | 303 | else => unreachable, |
| 216 | } | 304 | } |
| 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}); | ||
| 239 | } | ||
| 240 | } | 305 | } |
| 241 | try writer.writeByte(')'); | 306 | }; |
| 242 | } | ||
| 243 | 307 | ||
| 244 | fn indent(file: *C) !void { | 308 | pub fn genDecl(o: *Object) !void { |
| 245 | const indent_size = 4; | 309 | const tracy = trace(@src()); |
| 246 | const indent_level = 1; | 310 | defer tracy.end(); |
| 247 | const indent_amt = indent_size * indent_level; | ||
| 248 | try file.main.writer().writeByteNTimes(' ', indent_amt); | ||
| 249 | } | ||
| 250 | 311 | ||
| 251 | pub fn generate(file: *C, module: *Module, decl: *Decl) !void { | 312 | const tv = o.dg.decl.typed_value.most_recent.typed_value; |
| 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 | } | ||
| 270 | 313 | ||
| 271 | if (tv.val.castTag(.function)) |func_payload| { | 314 | if (tv.val.castTag(.function)) |func_payload| { |
| 272 | const writer = file.main.writer(); | 315 | const is_global = o.dg.functionIsGlobal(tv); |
| 273 | try renderFunctionSignature(&ctx, writer, decl); | 316 | const fwd_decl_writer = o.dg.fwd_decl.writer(); |
| 274 | 317 | if (is_global) { | |
| 275 | try writer.writeAll(" {"); | 318 | try fwd_decl_writer.writeAll("ZIG_EXTERN_C "); |
| 319 | } | ||
| 320 | try o.dg.renderFunctionSignature(fwd_decl_writer, is_global); | ||
| 321 | try fwd_decl_writer.writeAll(";\n"); | ||
| 276 | 322 | ||
| 277 | const func: *Module.Fn = func_payload.data; | 323 | const func: *Module.Fn = func_payload.data; |
| 278 | const instructions = func.body.instructions; | 324 | const instructions = func.body.instructions; |
| 279 | if (instructions.len > 0) { | 325 | const writer = o.code.writer(); |
| 280 | try writer.writeAll("\n"); | 326 | try writer.writeAll("\n"); |
| 281 | for (instructions) |inst| { | 327 | try o.dg.renderFunctionSignature(writer, is_global); |
| 282 | if (switch (inst.tag) { | 328 | if (instructions.len == 0) { |
| 283 | .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"), | 329 | try writer.writeAll(" {}\n"); |
| 284 | .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?), | 330 | return; |
| 285 | .arg => try genArg(&ctx), | 331 | } |
| 286 | .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), | 332 | |
| 287 | .block => try genBlock(&ctx, file, inst.castTag(.block).?), | 333 | try writer.writeAll(" {"); |
| 288 | .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), | 334 | |
| 289 | .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), | 335 | try writer.writeAll("\n"); |
| 290 | .call => try genCall(&ctx, file, inst.castTag(.call).?), | 336 | for (instructions) |inst| { |
| 291 | .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), | 337 | const result_value = switch (inst.tag) { |
| 292 | .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"), | 338 | .add => try genBinOp(o, inst.castTag(.add).?, " + "), |
| 293 | .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="), | 339 | .alloc => try genAlloc(o, inst.castTag(.alloc).?), |
| 294 | .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"), | 340 | .arg => genArg(o), |
| 295 | .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="), | 341 | .assembly => try genAsm(o, inst.castTag(.assembly).?), |
| 296 | .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), | 342 | .block => try genBlock(o, inst.castTag(.block).?), |
| 297 | .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), | 343 | .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), |
| 298 | .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), | 344 | .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), |
| 299 | .load => try genLoad(&ctx, file, inst.castTag(.load).?), | 345 | .call => try genCall(o, inst.castTag(.call).?), |
| 300 | .ret => try genRet(&ctx, file, inst.castTag(.ret).?), | 346 | .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "), |
| 301 | .retvoid => try genRetVoid(file), | 347 | .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "), |
| 302 | .store => try genStore(&ctx, file, inst.castTag(.store).?), | 348 | .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "), |
| 303 | .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"), | 349 | .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "), |
| 304 | .unreach => try genUnreach(file, inst.castTag(.unreach).?), | 350 | .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "), |
| 305 | else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), | 351 | .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "), |
| 306 | }) |name| { | 352 | .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), |
| 307 | try ctx.inst_map.putNoClobber(inst, name); | 353 | .intcast => try genIntCast(o, inst.castTag(.intcast).?), |
| 308 | } | 354 | .load => try genLoad(o, inst.castTag(.load).?), |
| 355 | .ret => try genRet(o, inst.castTag(.ret).?), | ||
| 356 | .retvoid => try genRetVoid(o), | ||
| 357 | .store => try genStore(o, inst.castTag(.store).?), | ||
| 358 | .sub => try genBinOp(o, inst.castTag(.sub).?, " - "), | ||
| 359 | .unreach => try genUnreach(o, inst.castTag(.unreach).?), | ||
| 360 | else => |e| return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement codegen for {}", .{e}), | ||
| 361 | }; | ||
| 362 | switch (result_value) { | ||
| 363 | .none => {}, | ||
| 364 | else => try o.value_map.putNoClobber(inst, result_value), | ||
| 309 | } | 365 | } |
| 310 | } | 366 | } |
| 311 | 367 | ||
| 312 | try writer.writeAll("}\n\n"); | 368 | try writer.writeAll("}\n"); |
| 313 | } else if (tv.val.tag() == .extern_fn) { | 369 | } else if (tv.val.tag() == .extern_fn) { |
| 314 | return; // handled when referenced | 370 | const writer = o.code.writer(); |
| 371 | try writer.writeAll("ZIG_EXTERN_C "); | ||
| 372 | try o.dg.renderFunctionSignature(writer, true); | ||
| 373 | try writer.writeAll(";\n"); | ||
| 315 | } else { | 374 | } else { |
| 316 | const writer = file.constants.writer(); | 375 | const writer = o.code.writer(); |
| 317 | try writer.writeAll("static "); | 376 | try writer.writeAll("static "); |
| 318 | 377 | ||
| 319 | // TODO ask the Decl if it is const | 378 | // TODO ask the Decl if it is const |
| 320 | // https://github.com/ziglang/zig/issues/7582 | 379 | // https://github.com/ziglang/zig/issues/7582 |
| 321 | 380 | ||
| 322 | try renderTypeAndName(&ctx, writer, tv.ty, mem.span(decl.name), .Mut); | 381 | const decl_c_value: CValue = .{ .decl = o.dg.decl }; |
| 382 | try o.renderTypeAndName(writer, tv.ty, decl_c_value, .Mut); | ||
| 323 | 383 | ||
| 324 | try writer.writeAll(" = "); | 384 | try writer.writeAll(" = "); |
| 325 | try renderValue(&ctx, writer, tv.ty, tv.val); | 385 | try o.dg.renderValue(writer, tv.ty, tv.val); |
| 326 | try writer.writeAll(";\n"); | 386 | try writer.writeAll(";\n"); |
| 327 | } | 387 | } |
| 328 | } | 388 | } |
| 329 | 389 | ||
| 330 | pub fn generateHeader( | 390 | pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { |
| 331 | comp: *Compilation, | 391 | const tracy = trace(@src()); |
| 332 | module: *Module, | 392 | defer tracy.end(); |
| 333 | header: *C.Header, | 393 | |
| 334 | decl: *Decl, | 394 | const tv = dg.decl.typed_value.most_recent.typed_value; |
| 335 | ) error{ AnalysisFail, OutOfMemory }!void { | 395 | const writer = dg.fwd_decl.writer(); |
| 336 | switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { | 396 | |
| 397 | switch (tv.ty.zigTypeTag()) { | ||
| 337 | .Fn => { | 398 | .Fn => { |
| 338 | var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa); | 399 | const is_global = dg.functionIsGlobal(tv); |
| 339 | defer inst_map.deinit(); | 400 | if (is_global) { |
| 340 | 401 | try writer.writeAll("ZIG_EXTERN_C "); | |
| 341 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | 402 | } |
| 342 | defer arena.deinit(); | 403 | try dg.renderFunctionSignature(writer, is_global); |
| 343 | 404 | try dg.fwd_decl.appendSlice(";\n"); | |
| 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; | ||
| 358 | }; | ||
| 359 | try writer.writeAll(";\n"); | ||
| 360 | }, | 405 | }, |
| 361 | else => {}, | 406 | else => {}, |
| 362 | } | 407 | } |
| 363 | } | 408 | } |
| 364 | 409 | ||
| 365 | const Context = struct { | 410 | fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { |
| 366 | decl: *Decl, | 411 | const writer = o.code.writer(); |
| 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 | |||
| 401 | fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { | ||
| 402 | const writer = file.main.writer(); | ||
| 403 | 412 | ||
| 404 | // First line: the variable used as data storage. | 413 | // First line: the variable used as data storage. |
| 405 | try indent(file); | 414 | try o.indent(); |
| 406 | const local_name = try ctx.name(); | ||
| 407 | const elem_type = alloc.base.ty.elemType(); | 415 | const elem_type = alloc.base.ty.elemType(); |
| 408 | const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; | 416 | const mutability: Mutability = if (alloc.base.ty.isConstPtr()) .Const else .Mut; |
| 409 | try renderTypeAndName(ctx, writer, elem_type, local_name, mutability); | 417 | const local = try o.allocLocal(elem_type, mutability); |
| 410 | try writer.writeAll(";\n"); | 418 | try writer.writeAll(";\n"); |
| 411 | 419 | ||
| 412 | // Second line: a pointer to it so that we can refer to it as the allocation. | 420 | return CValue{ .local_ref = local.local }; |
| 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; | ||
| 420 | } | 421 | } |
| 421 | 422 | ||
| 422 | fn genArg(ctx: *Context) !?[]u8 { | 423 | fn genArg(o: *Object) CValue { |
| 423 | const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex}); | 424 | const i = o.next_arg_index; |
| 424 | ctx.argdex += 1; | 425 | o.next_arg_index += 1; |
| 425 | return name; | 426 | return .{ .arg = i }; |
| 426 | } | 427 | } |
| 427 | 428 | ||
| 428 | fn genRetVoid(file: *C) !?[]u8 { | 429 | fn genRetVoid(o: *Object) !CValue { |
| 429 | try indent(file); | 430 | try o.indent(); |
| 430 | try file.main.writer().print("return;\n", .{}); | 431 | try o.code.writer().print("return;\n", .{}); |
| 431 | return null; | 432 | return CValue.none; |
| 432 | } | 433 | } |
| 433 | 434 | ||
| 434 | fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 435 | fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { |
| 435 | const operand = try ctx.resolveInst(inst.operand); | 436 | const operand = try o.resolveInst(inst.operand); |
| 436 | const writer = file.main.writer(); | 437 | const writer = o.code.writer(); |
| 437 | try indent(file); | 438 | try o.indent(); |
| 438 | const local_name = try ctx.name(); | 439 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 439 | try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); | 440 | switch (operand) { |
| 440 | try writer.print(" = *{s};\n", .{operand}); | 441 | .local_ref => |i| { |
| 441 | return local_name; | 442 | const wrapped: CValue = .{ .local = i }; |
| 443 | try writer.writeAll(" = "); | ||
| 444 | try o.writeCValue(writer, wrapped); | ||
| 445 | try writer.writeAll(";\n"); | ||
| 446 | }, | ||
| 447 | else => { | ||
| 448 | try writer.writeAll(" = *"); | ||
| 449 | try o.writeCValue(writer, operand); | ||
| 450 | try writer.writeAll(";\n"); | ||
| 451 | }, | ||
| 452 | } | ||
| 453 | return local; | ||
| 442 | } | 454 | } |
| 443 | 455 | ||
| 444 | fn genRet(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 456 | fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { |
| 445 | try indent(file); | 457 | const operand = try o.resolveInst(inst.operand); |
| 446 | const writer = file.main.writer(); | 458 | try o.indent(); |
| 447 | try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)}); | 459 | const writer = o.code.writer(); |
| 448 | return null; | 460 | try writer.writeAll("return "); |
| 461 | try o.writeCValue(writer, operand); | ||
| 462 | try writer.writeAll(";\n"); | ||
| 463 | return CValue.none; | ||
| 449 | } | 464 | } |
| 450 | 465 | ||
| 451 | fn genIntCast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 466 | fn genIntCast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 452 | if (inst.base.isUnused()) | 467 | if (inst.base.isUnused()) |
| 453 | return null; | 468 | return CValue.none; |
| 454 | try indent(file); | 469 | |
| 455 | const writer = file.main.writer(); | 470 | const from = try o.resolveInst(inst.operand); |
| 456 | const name = try ctx.name(); | ||
| 457 | const from = try ctx.resolveInst(inst.operand); | ||
| 458 | 471 | ||
| 459 | try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); | 472 | try o.indent(); |
| 473 | const writer = o.code.writer(); | ||
| 474 | const local = try o.allocLocal(inst.base.ty, .Const); | ||
| 460 | try writer.writeAll(" = ("); | 475 | try writer.writeAll(" = ("); |
| 461 | try renderType(ctx, writer, inst.base.ty); | 476 | try o.dg.renderType(writer, inst.base.ty); |
| 462 | try writer.print("){s};\n", .{from}); | 477 | try writer.writeAll(")"); |
| 463 | return name; | 478 | try o.writeCValue(writer, from); |
| 479 | try writer.writeAll(";\n"); | ||
| 480 | return local; | ||
| 464 | } | 481 | } |
| 465 | 482 | ||
| 466 | fn genStore(ctx: *Context, file: *C, inst: *Inst.BinOp) !?[]u8 { | 483 | fn genStore(o: *Object, inst: *Inst.BinOp) !CValue { |
| 467 | // *a = b; | 484 | // *a = b; |
| 468 | try indent(file); | 485 | const dest_ptr = try o.resolveInst(inst.lhs); |
| 469 | const writer = file.main.writer(); | 486 | const src_val = try o.resolveInst(inst.rhs); |
| 470 | const dest_ptr_name = try ctx.resolveInst(inst.lhs); | 487 | |
| 471 | const src_val_name = try ctx.resolveInst(inst.rhs); | 488 | try o.indent(); |
| 472 | try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name }); | 489 | const writer = o.code.writer(); |
| 473 | return null; | 490 | switch (dest_ptr) { |
| 491 | .local_ref => |i| { | ||
| 492 | const dest: CValue = .{ .local = i }; | ||
| 493 | try o.writeCValue(writer, dest); | ||
| 494 | try writer.writeAll(" = "); | ||
| 495 | try o.writeCValue(writer, src_val); | ||
| 496 | try writer.writeAll(";\n"); | ||
| 497 | }, | ||
| 498 | else => { | ||
| 499 | try writer.writeAll("*"); | ||
| 500 | try o.writeCValue(writer, dest_ptr); | ||
| 501 | try writer.writeAll(" = "); | ||
| 502 | try o.writeCValue(writer, src_val); | ||
| 503 | try writer.writeAll(";\n"); | ||
| 504 | }, | ||
| 505 | } | ||
| 506 | return CValue.none; | ||
| 474 | } | 507 | } |
| 475 | 508 | ||
| 476 | fn genBinOp(ctx: *Context, file: *C, inst: *Inst.BinOp, operator: []const u8) !?[]u8 { | 509 | fn genBinOp(o: *Object, inst: *Inst.BinOp, operator: []const u8) !CValue { |
| 477 | if (inst.base.isUnused()) | 510 | if (inst.base.isUnused()) |
| 478 | return null; | 511 | return CValue.none; |
| 479 | try indent(file); | 512 | |
| 480 | const lhs = try ctx.resolveInst(inst.lhs); | 513 | const lhs = try o.resolveInst(inst.lhs); |
| 481 | const rhs = try ctx.resolveInst(inst.rhs); | 514 | const rhs = try o.resolveInst(inst.rhs); |
| 482 | const writer = file.main.writer(); | 515 | |
| 483 | const name = try ctx.name(); | 516 | try o.indent(); |
| 484 | try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); | 517 | const writer = o.code.writer(); |
| 485 | try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); | 518 | const local = try o.allocLocal(inst.base.ty, .Const); |
| 486 | return name; | 519 | |
| 520 | try writer.writeAll(" = "); | ||
| 521 | try o.writeCValue(writer, lhs); | ||
| 522 | try writer.writeAll(operator); | ||
| 523 | try o.writeCValue(writer, rhs); | ||
| 524 | try writer.writeAll(";\n"); | ||
| 525 | |||
| 526 | return local; | ||
| 487 | } | 527 | } |
| 488 | 528 | ||
| 489 | fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | 529 | fn genCall(o: *Object, inst: *Inst.Call) !CValue { |
| 490 | try indent(file); | ||
| 491 | const writer = file.main.writer(); | ||
| 492 | const header = file.header.buf.writer(); | ||
| 493 | if (inst.func.castTag(.constant)) |func_inst| { | 530 | if (inst.func.castTag(.constant)) |func_inst| { |
| 494 | const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| | 531 | const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| |
| 495 | extern_fn.data | 532 | extern_fn.data |
| ... | @@ -501,23 +538,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | ... | @@ -501,23 +538,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { |
| 501 | const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; | 538 | const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; |
| 502 | const ret_ty = fn_ty.fnReturnType(); | 539 | const ret_ty = fn_ty.fnReturnType(); |
| 503 | const unused_result = inst.base.isUnused(); | 540 | const unused_result = inst.base.isUnused(); |
| 504 | var result_name: ?[]u8 = null; | 541 | var result_local: CValue = .none; |
| 542 | |||
| 543 | try o.indent(); | ||
| 544 | const writer = o.code.writer(); | ||
| 505 | if (unused_result) { | 545 | if (unused_result) { |
| 506 | if (ret_ty.hasCodeGenBits()) { | 546 | if (ret_ty.hasCodeGenBits()) { |
| 507 | try writer.print("(void)", .{}); | 547 | try writer.print("(void)", .{}); |
| 508 | } | 548 | } |
| 509 | } else { | 549 | } else { |
| 510 | const local_name = try ctx.name(); | 550 | result_local = try o.allocLocal(ret_ty, .Const); |
| 511 | try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const); | ||
| 512 | try writer.writeAll(" = "); | 551 | try writer.writeAll(" = "); |
| 513 | result_name = local_name; | ||
| 514 | } | 552 | } |
| 515 | const fn_name = mem.spanZ(fn_decl.name); | 553 | 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 | } | ||
| 521 | try writer.print("{s}(", .{fn_name}); | 554 | try writer.print("{s}(", .{fn_name}); |
| 522 | if (inst.args.len != 0) { | 555 | if (inst.args.len != 0) { |
| 523 | for (inst.args) |arg, i| { | 556 | for (inst.args) |arg, i| { |
| ... | @@ -525,87 +558,98 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { | ... | @@ -525,87 +558,98 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { |
| 525 | try writer.writeAll(", "); | 558 | try writer.writeAll(", "); |
| 526 | } | 559 | } |
| 527 | if (arg.value()) |val| { | 560 | if (arg.value()) |val| { |
| 528 | try renderValue(ctx, writer, arg.ty, val); | 561 | try o.dg.renderValue(writer, arg.ty, val); |
| 529 | } else { | 562 | } else { |
| 530 | const val = try ctx.resolveInst(arg); | 563 | const val = try o.resolveInst(arg); |
| 531 | try writer.print("{s}", .{val}); | 564 | try o.writeCValue(writer, val); |
| 532 | } | 565 | } |
| 533 | } | 566 | } |
| 534 | } | 567 | } |
| 535 | try writer.writeAll(");\n"); | 568 | try writer.writeAll(");\n"); |
| 536 | return result_name; | 569 | return result_local; |
| 537 | } else { | 570 | } else { |
| 538 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement function pointers", .{}); | 571 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement function pointers", .{}); |
| 539 | } | 572 | } |
| 540 | } | 573 | } |
| 541 | 574 | ||
| 542 | fn genDbgStmt(ctx: *Context, inst: *Inst.NoOp) !?[]u8 { | 575 | fn genDbgStmt(o: *Object, inst: *Inst.NoOp) !CValue { |
| 543 | // TODO emit #line directive here with line number and filename | 576 | // TODO emit #line directive here with line number and filename |
| 544 | return null; | 577 | return CValue.none; |
| 545 | } | 578 | } |
| 546 | 579 | ||
| 547 | fn genBlock(ctx: *Context, file: *C, inst: *Inst.Block) !?[]u8 { | 580 | fn genBlock(o: *Object, inst: *Inst.Block) !CValue { |
| 548 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); | 581 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: implement blocks", .{}); |
| 549 | } | 582 | } |
| 550 | 583 | ||
| 551 | fn genBitcast(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | 584 | fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { |
| 552 | const writer = file.main.writer(); | 585 | const operand = try o.resolveInst(inst.operand); |
| 553 | try indent(file); | 586 | |
| 554 | const local_name = try ctx.name(); | 587 | const writer = o.code.writer(); |
| 555 | const operand = try ctx.resolveInst(inst.operand); | 588 | try o.indent(); |
| 556 | try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); | ||
| 557 | if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { | 589 | if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { |
| 590 | const local = try o.allocLocal(inst.base.ty, .Const); | ||
| 558 | try writer.writeAll(" = ("); | 591 | try writer.writeAll(" = ("); |
| 559 | try renderType(ctx, writer, inst.base.ty); | 592 | try o.dg.renderType(writer, inst.base.ty); |
| 560 | try writer.print("){s};\n", .{operand}); | 593 | |
| 561 | } else { | 594 | try writer.writeAll(")"); |
| 595 | try o.writeCValue(writer, operand); | ||
| 562 | try writer.writeAll(";\n"); | 596 | try writer.writeAll(";\n"); |
| 563 | try indent(file); | 597 | return local; |
| 564 | try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); | ||
| 565 | } | 598 | } |
| 566 | return local_name; | 599 | |
| 600 | const local = try o.allocLocal(inst.base.ty, .Mut); | ||
| 601 | try writer.writeAll(";\n"); | ||
| 602 | try o.indent(); | ||
| 603 | |||
| 604 | try writer.writeAll("memcpy(&"); | ||
| 605 | try o.writeCValue(writer, local); | ||
| 606 | try writer.writeAll(", &"); | ||
| 607 | try o.writeCValue(writer, operand); | ||
| 608 | try writer.writeAll(", sizeof "); | ||
| 609 | try o.writeCValue(writer, local); | ||
| 610 | try writer.writeAll(");\n"); | ||
| 611 | |||
| 612 | return local; | ||
| 567 | } | 613 | } |
| 568 | 614 | ||
| 569 | fn genBreakpoint(file: *C, inst: *Inst.NoOp) !?[]u8 { | 615 | fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { |
| 570 | try indent(file); | 616 | try o.indent(); |
| 571 | try file.main.writer().writeAll("zig_breakpoint();\n"); | 617 | try o.code.writer().writeAll("zig_breakpoint();\n"); |
| 572 | return null; | 618 | return CValue.none; |
| 573 | } | 619 | } |
| 574 | 620 | ||
| 575 | fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { | 621 | fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { |
| 576 | try indent(file); | 622 | try o.indent(); |
| 577 | try file.main.writer().writeAll("zig_unreachable();\n"); | 623 | try o.code.writer().writeAll("zig_unreachable();\n"); |
| 578 | return null; | 624 | return CValue.none; |
| 579 | } | 625 | } |
| 580 | 626 | ||
| 581 | fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | 627 | fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { |
| 582 | try indent(file); | 628 | if (as.base.isUnused() and !as.is_volatile) |
| 583 | const writer = file.main.writer(); | 629 | return CValue.none; |
| 630 | |||
| 631 | const writer = o.code.writer(); | ||
| 584 | for (as.inputs) |i, index| { | 632 | for (as.inputs) |i, index| { |
| 585 | if (i[0] == '{' and i[i.len - 1] == '}') { | 633 | if (i[0] == '{' and i[i.len - 1] == '}') { |
| 586 | const reg = i[1 .. i.len - 1]; | 634 | const reg = i[1 .. i.len - 1]; |
| 587 | const arg = as.args[index]; | 635 | const arg = as.args[index]; |
| 636 | const arg_c_value = try o.resolveInst(arg); | ||
| 637 | try o.indent(); | ||
| 588 | try writer.writeAll("register "); | 638 | try writer.writeAll("register "); |
| 589 | try renderType(ctx, writer, arg.ty); | 639 | try o.dg.renderType(writer, arg.ty); |
| 640 | |||
| 590 | try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); | 641 | try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); |
| 591 | // TODO merge constant handling into inst_map as well | 642 | try o.writeCValue(writer, arg_c_value); |
| 592 | if (arg.castTag(.constant)) |c| { | 643 | try writer.writeAll(";\n"); |
| 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 | } | ||
| 602 | } else { | 644 | } else { |
| 603 | return ctx.fail(ctx.decl.src(), "TODO non-explicit inline asm regs", .{}); | 645 | return o.dg.fail(o.dg.decl.src(), "TODO non-explicit inline asm regs", .{}); |
| 604 | } | 646 | } |
| 605 | } | 647 | } |
| 606 | try writer.print("__asm {s} (\"{s}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source }); | 648 | try o.indent(); |
| 607 | if (as.output) |o| { | 649 | const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; |
| 608 | return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); | 650 | try writer.print("__asm {s}(\"{s}\"", .{ volatile_string, as.asm_source }); |
| 651 | if (as.output) |_| { | ||
| 652 | return o.dg.fail(o.dg.decl.src(), "TODO inline asm output", .{}); | ||
| 609 | } | 653 | } |
| 610 | if (as.inputs.len > 0) { | 654 | if (as.inputs.len > 0) { |
| 611 | if (as.output == null) { | 655 | if (as.output == null) { |
| ... | @@ -619,7 +663,7 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | ... | @@ -619,7 +663,7 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { |
| 619 | if (index > 0) { | 663 | if (index > 0) { |
| 620 | try writer.writeAll(", "); | 664 | try writer.writeAll(", "); |
| 621 | } | 665 | } |
| 622 | try writer.print("\"\"({s}_constant)", .{reg}); | 666 | try writer.print("\"r\"({s}_constant)", .{reg}); |
| 623 | } else { | 667 | } else { |
| 624 | // This is blocked by the earlier test | 668 | // This is blocked by the earlier test |
| 625 | unreachable; | 669 | unreachable; |
| ... | @@ -627,5 +671,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | ... | @@ -627,5 +671,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { |
| 627 | } | 671 | } |
| 628 | } | 672 | } |
| 629 | try writer.writeAll(");\n"); | 673 | try writer.writeAll(");\n"); |
| 630 | return null; | 674 | |
| 675 | if (as.base.isUnused()) | ||
| 676 | return CValue.none; | ||
| 677 | |||
| 678 | return o.dg.fail(o.dg.decl.src(), "TODO: C backend: inline asm expression result used", .{}); | ||
| 631 | } | 679 | } |
src/link.zig+10-7| ... | @@ -130,7 +130,7 @@ pub const File = struct { | ... | @@ -130,7 +130,7 @@ pub const File = struct { |
| 130 | elf: Elf.TextBlock, | 130 | elf: Elf.TextBlock, |
| 131 | coff: Coff.TextBlock, | 131 | coff: Coff.TextBlock, |
| 132 | macho: MachO.TextBlock, | 132 | macho: MachO.TextBlock, |
| 133 | c: void, | 133 | c: C.DeclBlock, |
| 134 | wasm: void, | 134 | wasm: void, |
| 135 | }; | 135 | }; |
| 136 | 136 | ||
| ... | @@ -138,7 +138,7 @@ pub const File = struct { | ... | @@ -138,7 +138,7 @@ pub const File = struct { |
| 138 | elf: Elf.SrcFn, | 138 | elf: Elf.SrcFn, |
| 139 | coff: Coff.SrcFn, | 139 | coff: Coff.SrcFn, |
| 140 | macho: MachO.SrcFn, | 140 | macho: MachO.SrcFn, |
| 141 | c: void, | 141 | c: C.FnBlock, |
| 142 | wasm: ?Wasm.FnData, | 142 | wasm: ?Wasm.FnData, |
| 143 | }; | 143 | }; |
| 144 | 144 | ||
| ... | @@ -301,7 +301,8 @@ pub const File = struct { | ... | @@ -301,7 +301,8 @@ pub const File = struct { |
| 301 | .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl), | 301 | .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl), |
| 302 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), | 302 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), |
| 303 | .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), | 303 | .macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl), |
| 304 | .c, .wasm => {}, | 304 | .c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl), |
| 305 | .wasm => {}, | ||
| 305 | } | 306 | } |
| 306 | } | 307 | } |
| 307 | 308 | ||
| ... | @@ -312,7 +313,8 @@ pub const File = struct { | ... | @@ -312,7 +313,8 @@ pub const File = struct { |
| 312 | .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), | 313 | .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), |
| 313 | .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), | 314 | .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), |
| 314 | .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), | 315 | .macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl), |
| 315 | .c, .wasm => {}, | 316 | .c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl), |
| 317 | .wasm => {}, | ||
| 316 | } | 318 | } |
| 317 | } | 319 | } |
| 318 | 320 | ||
| ... | @@ -407,12 +409,13 @@ pub const File = struct { | ... | @@ -407,12 +409,13 @@ pub const File = struct { |
| 407 | } | 409 | } |
| 408 | } | 410 | } |
| 409 | 411 | ||
| 412 | /// Called when a Decl is deleted from the Module. | ||
| 410 | pub fn freeDecl(base: *File, decl: *Module.Decl) void { | 413 | pub fn freeDecl(base: *File, decl: *Module.Decl) void { |
| 411 | switch (base.tag) { | 414 | switch (base.tag) { |
| 412 | .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), | 415 | .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), |
| 413 | .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), | 416 | .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), |
| 414 | .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), | 417 | .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), |
| 415 | .c => unreachable, | 418 | .c => @fieldParentPtr(C, "base", base).freeDecl(decl), |
| 416 | .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), | 419 | .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), |
| 417 | } | 420 | } |
| 418 | } | 421 | } |
| ... | @@ -432,14 +435,14 @@ pub const File = struct { | ... | @@ -432,14 +435,14 @@ pub const File = struct { |
| 432 | pub fn updateDeclExports( | 435 | pub fn updateDeclExports( |
| 433 | base: *File, | 436 | base: *File, |
| 434 | module: *Module, | 437 | module: *Module, |
| 435 | decl: *const Module.Decl, | 438 | decl: *Module.Decl, |
| 436 | exports: []const *Module.Export, | 439 | exports: []const *Module.Export, |
| 437 | ) !void { | 440 | ) !void { |
| 438 | switch (base.tag) { | 441 | switch (base.tag) { |
| 439 | .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports), | 442 | .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports), |
| 440 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), | 443 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), |
| 441 | .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), | 444 | .macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports), |
| 442 | .c => return {}, | 445 | .c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports), |
| 443 | .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), | 446 | .wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports), |
| 444 | } | 447 | } |
| 445 | } | 448 | } |
src/link/C.zig+175-64| ... | @@ -8,46 +8,31 @@ const fs = std.fs; | ... | @@ -8,46 +8,31 @@ const fs = std.fs; |
| 8 | const codegen = @import("../codegen/c.zig"); | 8 | const codegen = @import("../codegen/c.zig"); |
| 9 | const link = @import("../link.zig"); | 9 | const link = @import("../link.zig"); |
| 10 | const trace = @import("../tracy.zig").trace; | 10 | const trace = @import("../tracy.zig").trace; |
| 11 | const File = link.File; | ||
| 12 | const C = @This(); | 11 | const C = @This(); |
| 13 | 12 | ||
| 14 | pub const base_tag: File.Tag = .c; | 13 | pub const base_tag: link.File.Tag = .c; |
| 14 | pub const zig_h = @embedFile("C/zig.h"); | ||
| 15 | 15 | ||
| 16 | pub const Header = struct { | 16 | base: link.File, |
| 17 | buf: std.ArrayList(u8), | ||
| 18 | emit_loc: ?Compilation.EmitLoc, | ||
| 19 | 17 | ||
| 20 | pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header { | 18 | /// Per-declaration data. For functions this is the body, and |
| 21 | return .{ | 19 | /// the forward declaration is stored in the FnBlock. |
| 22 | .buf = std.ArrayList(u8).init(allocator), | 20 | pub const DeclBlock = struct { |
| 23 | .emit_loc = emit_loc, | 21 | code: std.ArrayListUnmanaged(u8), |
| 24 | }; | ||
| 25 | } | ||
| 26 | |||
| 27 | pub fn flush(self: *const Header, writer: anytype) !void { | ||
| 28 | const tracy = trace(@src()); | ||
| 29 | defer tracy.end(); | ||
| 30 | |||
| 31 | try writer.writeAll(@embedFile("cbe.h")); | ||
| 32 | if (self.buf.items.len > 0) { | ||
| 33 | try writer.print("{s}", .{self.buf.items}); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | 22 | ||
| 37 | pub fn deinit(self: *Header) void { | 23 | pub const empty: DeclBlock = .{ |
| 38 | self.buf.deinit(); | 24 | .code = .{}, |
| 39 | self.* = undefined; | 25 | }; |
| 40 | } | ||
| 41 | }; | 26 | }; |
| 42 | 27 | ||
| 43 | base: File, | 28 | /// Per-function data. |
| 44 | 29 | pub const FnBlock = struct { | |
| 45 | header: Header, | 30 | fwd_decl: std.ArrayListUnmanaged(u8), |
| 46 | constants: std.ArrayList(u8), | ||
| 47 | main: std.ArrayList(u8), | ||
| 48 | 31 | ||
| 49 | called: std.StringHashMap(void), | 32 | pub const empty: FnBlock = .{ |
| 50 | error_msg: *Compilation.ErrorMsg = undefined, | 33 | .fwd_decl = .{}, |
| 34 | }; | ||
| 35 | }; | ||
| 51 | 36 | ||
| 52 | pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { | 37 | pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { |
| 53 | assert(options.object_format == .c); | 38 | assert(options.object_format == .c); |
| ... | @@ -55,7 +40,11 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio | ... | @@ -55,7 +40,11 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio |
| 55 | if (options.use_llvm) return error.LLVMHasNoCBackend; | 40 | if (options.use_llvm) return error.LLVMHasNoCBackend; |
| 56 | if (options.use_lld) return error.LLDHasNoCBackend; | 41 | if (options.use_lld) return error.LLDHasNoCBackend; |
| 57 | 42 | ||
| 58 | const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) }); | 43 | const file = try options.emit.?.directory.handle.createFile(sub_path, .{ |
| 44 | // Truncation is done on `flush`. | ||
| 45 | .truncate = false, | ||
| 46 | .mode = link.determineMode(options), | ||
| 47 | }); | ||
| 59 | errdefer file.close(); | 48 | errdefer file.close(); |
| 60 | 49 | ||
| 61 | var c_file = try allocator.create(C); | 50 | var c_file = try allocator.create(C); |
| ... | @@ -68,34 +57,69 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio | ... | @@ -68,34 +57,69 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio |
| 68 | .file = file, | 57 | .file = file, |
| 69 | .allocator = allocator, | 58 | .allocator = allocator, |
| 70 | }, | 59 | }, |
| 71 | .main = std.ArrayList(u8).init(allocator), | ||
| 72 | .header = Header.init(allocator, null), | ||
| 73 | .constants = std.ArrayList(u8).init(allocator), | ||
| 74 | .called = std.StringHashMap(void).init(allocator), | ||
| 75 | }; | 60 | }; |
| 76 | 61 | ||
| 77 | return c_file; | 62 | return c_file; |
| 78 | } | 63 | } |
| 79 | 64 | ||
| 80 | pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { | 65 | pub fn deinit(self: *C) void { |
| 81 | self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args); | 66 | const module = self.base.options.module orelse return; |
| 82 | return error.AnalysisFail; | 67 | for (module.decl_table.items()) |entry| { |
| 68 | self.freeDecl(entry.value); | ||
| 69 | } | ||
| 83 | } | 70 | } |
| 84 | 71 | ||
| 85 | pub fn deinit(self: *C) void { | 72 | pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {} |
| 86 | self.main.deinit(); | 73 | |
| 87 | self.header.deinit(); | 74 | pub fn freeDecl(self: *C, decl: *Module.Decl) void { |
| 88 | self.constants.deinit(); | 75 | decl.link.c.code.deinit(self.base.allocator); |
| 89 | self.called.deinit(); | 76 | decl.fn_link.c.fwd_decl.deinit(self.base.allocator); |
| 90 | } | 77 | } |
| 91 | 78 | ||
| 92 | pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { | 79 | pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { |
| 93 | codegen.generate(self, module, decl) catch |err| { | 80 | const tracy = trace(@src()); |
| 94 | if (err == error.AnalysisFail) { | 81 | defer tracy.end(); |
| 95 | try module.failed_decls.put(module.gpa, decl, self.error_msg); | 82 | |
| 96 | } | 83 | const fwd_decl = &decl.fn_link.c.fwd_decl; |
| 97 | return err; | 84 | const code = &decl.link.c.code; |
| 85 | fwd_decl.shrinkRetainingCapacity(0); | ||
| 86 | code.shrinkRetainingCapacity(0); | ||
| 87 | |||
| 88 | var object: codegen.Object = .{ | ||
| 89 | .dg = .{ | ||
| 90 | .module = module, | ||
| 91 | .error_msg = null, | ||
| 92 | .decl = decl, | ||
| 93 | .fwd_decl = fwd_decl.toManaged(module.gpa), | ||
| 94 | }, | ||
| 95 | .gpa = module.gpa, | ||
| 96 | .code = code.toManaged(module.gpa), | ||
| 97 | .value_map = codegen.CValueMap.init(module.gpa), | ||
| 98 | }; | ||
| 99 | defer object.value_map.deinit(); | ||
| 100 | defer object.code.deinit(); | ||
| 101 | defer object.dg.fwd_decl.deinit(); | ||
| 102 | |||
| 103 | codegen.genDecl(&object) catch |err| switch (err) { | ||
| 104 | error.AnalysisFail => { | ||
| 105 | try module.failed_decls.put(module.gpa, decl, object.dg.error_msg.?); | ||
| 106 | return; | ||
| 107 | }, | ||
| 108 | else => |e| return e, | ||
| 98 | }; | 109 | }; |
| 110 | |||
| 111 | fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged(); | ||
| 112 | code.* = object.code.moveToUnmanaged(); | ||
| 113 | |||
| 114 | // Free excess allocated memory for this Decl. | ||
| 115 | fwd_decl.shrink(module.gpa, fwd_decl.items.len); | ||
| 116 | code.shrink(module.gpa, code.items.len); | ||
| 117 | } | ||
| 118 | |||
| 119 | pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void { | ||
| 120 | // The C backend does not have the ability to fix line numbers without re-generating | ||
| 121 | // the entire Decl. | ||
| 122 | return self.updateDecl(module, decl); | ||
| 99 | } | 123 | } |
| 100 | 124 | ||
| 101 | pub fn flush(self: *C, comp: *Compilation) !void { | 125 | pub fn flush(self: *C, comp: *Compilation) !void { |
| ... | @@ -106,21 +130,108 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { | ... | @@ -106,21 +130,108 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 106 | const tracy = trace(@src()); | 130 | const tracy = trace(@src()); |
| 107 | defer tracy.end(); | 131 | defer tracy.end(); |
| 108 | 132 | ||
| 109 | const writer = self.base.file.?.writer(); | 133 | const module = self.base.options.module.?; |
| 110 | try self.header.flush(writer); | 134 | |
| 111 | if (self.header.buf.items.len > 0) { | 135 | // This code path happens exclusively with -ofmt=c. The flush logic for |
| 112 | try writer.writeByte('\n'); | 136 | // emit-h is in `flushEmitH` below. |
| 113 | } | 137 | |
| 114 | if (self.constants.items.len > 0) { | 138 | // We collect a list of buffers to write, and write them all at once with pwritev 😎 |
| 115 | try writer.print("{s}\n", .{self.constants.items}); | 139 | var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa); |
| 140 | defer all_buffers.deinit(); | ||
| 141 | |||
| 142 | // This is at least enough until we get to the function bodies without error handling. | ||
| 143 | try all_buffers.ensureCapacity(module.decl_table.count() + 1); | ||
| 144 | |||
| 145 | var file_size: u64 = zig_h.len; | ||
| 146 | all_buffers.appendAssumeCapacity(.{ | ||
| 147 | .iov_base = zig_h, | ||
| 148 | .iov_len = zig_h.len, | ||
| 149 | }); | ||
| 150 | |||
| 151 | var fn_count: usize = 0; | ||
| 152 | |||
| 153 | // Forward decls and non-functions first. | ||
| 154 | for (module.decl_table.items()) |kv| { | ||
| 155 | const decl = kv.value; | ||
| 156 | const decl_tv = decl.typed_value.most_recent.typed_value; | ||
| 157 | const buf = buf: { | ||
| 158 | if (decl_tv.val.castTag(.function)) |_| { | ||
| 159 | fn_count += 1; | ||
| 160 | break :buf decl.fn_link.c.fwd_decl.items; | ||
| 161 | } else { | ||
| 162 | break :buf decl.link.c.code.items; | ||
| 163 | } | ||
| 164 | }; | ||
| 165 | all_buffers.appendAssumeCapacity(.{ | ||
| 166 | .iov_base = buf.ptr, | ||
| 167 | .iov_len = buf.len, | ||
| 168 | }); | ||
| 169 | file_size += buf.len; | ||
| 116 | } | 170 | } |
| 117 | if (self.main.items.len > 1) { | 171 | |
| 118 | const last_two = self.main.items[self.main.items.len - 2 ..]; | 172 | // Now the function bodies. |
| 119 | if (std.mem.eql(u8, last_two, "\n\n")) { | 173 | try all_buffers.ensureCapacity(all_buffers.items.len + fn_count); |
| 120 | self.main.items.len -= 1; | 174 | for (module.decl_table.items()) |kv| { |
| 175 | const decl = kv.value; | ||
| 176 | const decl_tv = decl.typed_value.most_recent.typed_value; | ||
| 177 | if (decl_tv.val.castTag(.function)) |_| { | ||
| 178 | const buf = decl.link.c.code.items; | ||
| 179 | all_buffers.appendAssumeCapacity(.{ | ||
| 180 | .iov_base = buf.ptr, | ||
| 181 | .iov_len = buf.len, | ||
| 182 | }); | ||
| 183 | file_size += buf.len; | ||
| 121 | } | 184 | } |
| 122 | } | 185 | } |
| 123 | try writer.writeAll(self.main.items); | 186 | |
| 124 | self.base.file.?.close(); | 187 | const file = self.base.file.?; |
| 125 | self.base.file = null; | 188 | try file.setEndPos(file_size); |
| 189 | try file.pwritevAll(all_buffers.items, 0); | ||
| 126 | } | 190 | } |
| 191 | |||
| 192 | pub fn flushEmitH(module: *Module) !void { | ||
| 193 | const tracy = trace(@src()); | ||
| 194 | defer tracy.end(); | ||
| 195 | |||
| 196 | const emit_h_loc = module.emit_h orelse return; | ||
| 197 | |||
| 198 | // We collect a list of buffers to write, and write them all at once with pwritev 😎 | ||
| 199 | var all_buffers = std.ArrayList(std.os.iovec_const).init(module.gpa); | ||
| 200 | defer all_buffers.deinit(); | ||
| 201 | |||
| 202 | try all_buffers.ensureCapacity(module.decl_table.count() + 1); | ||
| 203 | |||
| 204 | var file_size: u64 = zig_h.len; | ||
| 205 | all_buffers.appendAssumeCapacity(.{ | ||
| 206 | .iov_base = zig_h, | ||
| 207 | .iov_len = zig_h.len, | ||
| 208 | }); | ||
| 209 | |||
| 210 | for (module.decl_table.items()) |kv| { | ||
| 211 | const emit_h = kv.value.getEmitH(module); | ||
| 212 | const buf = emit_h.fwd_decl.items; | ||
| 213 | all_buffers.appendAssumeCapacity(.{ | ||
| 214 | .iov_base = buf.ptr, | ||
| 215 | .iov_len = buf.len, | ||
| 216 | }); | ||
| 217 | file_size += buf.len; | ||
| 218 | } | ||
| 219 | |||
| 220 | const directory = emit_h_loc.directory orelse module.comp.local_cache_directory; | ||
| 221 | const file = try directory.handle.createFile(emit_h_loc.basename, .{ | ||
| 222 | // We set the end position explicitly below; by not truncating the file, we possibly | ||
| 223 | // make it easier on the file system by doing 1 reallocation instead of two. | ||
| 224 | .truncate = false, | ||
| 225 | }); | ||
| 226 | defer file.close(); | ||
| 227 | |||
| 228 | try file.setEndPos(file_size); | ||
| 229 | try file.pwritevAll(all_buffers.items, 0); | ||
| 230 | } | ||
| 231 | |||
| 232 | pub fn updateDeclExports( | ||
| 233 | self: *C, | ||
| 234 | module: *Module, | ||
| 235 | decl: *Module.Decl, | ||
| 236 | exports: []const *Module.Export, | ||
| 237 | ) !void {} |
src/link/C/zig.h created+58| ... | @@ -0,0 +1,58 @@ | ||
| 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 __STDC_VERSION__ >= 199901L | ||
| 26 | #define ZIG_RESTRICT restrict | ||
| 27 | #elif defined(__GNUC__) | ||
| 28 | #define ZIG_RESTRICT __restrict | ||
| 29 | #else | ||
| 30 | #define ZIG_RESTRICT | ||
| 31 | #endif | ||
| 32 | |||
| 33 | #ifdef __cplusplus | ||
| 34 | #define ZIG_EXTERN_C extern "C" | ||
| 35 | #else | ||
| 36 | #define ZIG_EXTERN_C | ||
| 37 | #endif | ||
| 38 | |||
| 39 | #if defined(_MSC_VER) | ||
| 40 | #define zig_breakpoint() __debugbreak() | ||
| 41 | #elif defined(__MINGW32__) || defined(__MINGW64__) | ||
| 42 | #define zig_breakpoint() __debugbreak() | ||
| 43 | #elif defined(__clang__) | ||
| 44 | #define zig_breakpoint() __builtin_debugtrap() | ||
| 45 | #elif defined(__GNUC__) | ||
| 46 | #define zig_breakpoint() __builtin_trap() | ||
| 47 | #elif defined(__i386__) || defined(__x86_64__) | ||
| 48 | #define zig_breakpoint() __asm__ volatile("int $0x03"); | ||
| 49 | #else | ||
| 50 | #define zig_breakpoint() raise(SIGTRAP) | ||
| 51 | #endif | ||
| 52 | |||
| 53 | #include <stdint.h> | ||
| 54 | #include <stddef.h> | ||
| 55 | #define int128_t __int128 | ||
| 56 | #define uint128_t unsigned __int128 | ||
| 57 | ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t); | ||
| 58 | |||
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 | ... | @@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d |
| 13 | const ThreadPool = @import("ThreadPool.zig"); | 13 | const ThreadPool = @import("ThreadPool.zig"); |
| 14 | const CrossTarget = std.zig.CrossTarget; | 14 | const CrossTarget = std.zig.CrossTarget; |
| 15 | 15 | ||
| 16 | const c_header = @embedFile("link/cbe.h"); | 16 | const zig_h = link.File.C.zig_h; |
| 17 | 17 | ||
| 18 | test "self-hosted" { | 18 | test "self-hosted" { |
| 19 | var ctx = TestContext.init(); | 19 | var ctx = TestContext.init(); |
| ... | @@ -324,11 +324,11 @@ pub const TestContext = struct { | ... | @@ -324,11 +324,11 @@ pub const TestContext = struct { |
| 324 | } | 324 | } |
| 325 | 325 | ||
| 326 | pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { | 326 | 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); |
| 328 | } | 328 | } |
| 329 | 329 | ||
| 330 | pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void { | 330 | 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); |
| 332 | } | 332 | } |
| 333 | 333 | ||
| 334 | pub fn addCompareOutput( | 334 | pub fn addCompareOutput( |
| ... | @@ -700,11 +700,12 @@ pub const TestContext = struct { | ... | @@ -700,11 +700,12 @@ pub const TestContext = struct { |
| 700 | }, | 700 | }, |
| 701 | } | 701 | } |
| 702 | } | 702 | } |
| 703 | if (comp.bin_file.cast(link.File.C)) |c_file| { | 703 | // TODO print generated C code |
| 704 | std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ | 704 | //if (comp.bin_file.cast(link.File.C)) |c_file| { |
| 705 | c_file.main.items, | 705 | // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ |
| 706 | }); | 706 | // c_file.main.items, |
| 707 | } | 707 | // }); |
| 708 | //} | ||
| 708 | std.debug.print("Test failed.\n", .{}); | 709 | std.debug.print("Test failed.\n", .{}); |
| 709 | std.process.exit(1); | 710 | std.process.exit(1); |
| 710 | } | 711 | } |
test/stage2/cbe.zig+114-237| ... | @@ -22,15 +22,107 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -22,15 +22,107 @@ pub fn addCases(ctx: *TestContext) !void { |
| 22 | , "hello world!" ++ std.cstr.line_sep); | 22 | , "hello world!" ++ std.cstr.line_sep); |
| 23 | 23 | ||
| 24 | // Now change the message only | 24 | // Now change the message only |
| 25 | // TODO fix C backend not supporting updates | 25 | case.addCompareOutput( |
| 26 | // https://github.com/ziglang/zig/issues/7589 | 26 | \\extern fn puts(s: [*:0]const u8) c_int; |
| 27 | //case.addCompareOutput( | 27 | \\export fn main() c_int { |
| 28 | // \\extern fn puts(s: [*:0]const u8) c_int; | 28 | \\ _ = puts("yo"); |
| 29 | // \\export fn main() c_int { | 29 | \\ return 0; |
| 30 | // \\ _ = puts("yo"); | 30 | \\} |
| 31 | // \\ return 0; | 31 | , "yo" ++ std.cstr.line_sep); |
| 32 | // \\} | 32 | } |
| 33 | //, "yo" ++ std.cstr.line_sep); | 33 | |
| 34 | { | ||
| 35 | var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64); | ||
| 36 | |||
| 37 | // Exit with 0 | ||
| 38 | case.addCompareOutput( | ||
| 39 | \\fn exitGood() noreturn { | ||
| 40 | \\ asm volatile ("syscall" | ||
| 41 | \\ : | ||
| 42 | \\ : [number] "{rax}" (231), | ||
| 43 | \\ [arg1] "{rdi}" (0) | ||
| 44 | \\ ); | ||
| 45 | \\ unreachable; | ||
| 46 | \\} | ||
| 47 | \\ | ||
| 48 | \\export fn main() c_int { | ||
| 49 | \\ exitGood(); | ||
| 50 | \\} | ||
| 51 | , ""); | ||
| 52 | |||
| 53 | // Pass a usize parameter to exit | ||
| 54 | case.addCompareOutput( | ||
| 55 | \\export fn main() c_int { | ||
| 56 | \\ exit(0); | ||
| 57 | \\} | ||
| 58 | \\ | ||
| 59 | \\fn exit(code: usize) noreturn { | ||
| 60 | \\ asm volatile ("syscall" | ||
| 61 | \\ : | ||
| 62 | \\ : [number] "{rax}" (231), | ||
| 63 | \\ [arg1] "{rdi}" (code) | ||
| 64 | \\ ); | ||
| 65 | \\ unreachable; | ||
| 66 | \\} | ||
| 67 | , ""); | ||
| 68 | |||
| 69 | // Change the parameter to u8 | ||
| 70 | case.addCompareOutput( | ||
| 71 | \\export fn main() c_int { | ||
| 72 | \\ exit(0); | ||
| 73 | \\} | ||
| 74 | \\ | ||
| 75 | \\fn exit(code: u8) noreturn { | ||
| 76 | \\ asm volatile ("syscall" | ||
| 77 | \\ : | ||
| 78 | \\ : [number] "{rax}" (231), | ||
| 79 | \\ [arg1] "{rdi}" (code) | ||
| 80 | \\ ); | ||
| 81 | \\ unreachable; | ||
| 82 | \\} | ||
| 83 | , ""); | ||
| 84 | |||
| 85 | // Do some arithmetic at the exit callsite | ||
| 86 | case.addCompareOutput( | ||
| 87 | \\export fn main() c_int { | ||
| 88 | \\ exitMath(1); | ||
| 89 | \\} | ||
| 90 | \\ | ||
| 91 | \\fn exitMath(a: u8) noreturn { | ||
| 92 | \\ exit(0 + a - a); | ||
| 93 | \\} | ||
| 94 | \\ | ||
| 95 | \\fn exit(code: u8) noreturn { | ||
| 96 | \\ asm volatile ("syscall" | ||
| 97 | \\ : | ||
| 98 | \\ : [number] "{rax}" (231), | ||
| 99 | \\ [arg1] "{rdi}" (code) | ||
| 100 | \\ ); | ||
| 101 | \\ unreachable; | ||
| 102 | \\} | ||
| 103 | \\ | ||
| 104 | , ""); | ||
| 105 | |||
| 106 | // Invert the arithmetic | ||
| 107 | case.addCompareOutput( | ||
| 108 | \\export fn main() c_int { | ||
| 109 | \\ exitMath(1); | ||
| 110 | \\} | ||
| 111 | \\ | ||
| 112 | \\fn exitMath(a: u8) noreturn { | ||
| 113 | \\ exit(a + 0 - a); | ||
| 114 | \\} | ||
| 115 | \\ | ||
| 116 | \\fn exit(code: u8) noreturn { | ||
| 117 | \\ asm volatile ("syscall" | ||
| 118 | \\ : | ||
| 119 | \\ : [number] "{rax}" (231), | ||
| 120 | \\ [arg1] "{rdi}" (code) | ||
| 121 | \\ ); | ||
| 122 | \\ unreachable; | ||
| 123 | \\} | ||
| 124 | \\ | ||
| 125 | , ""); | ||
| 34 | } | 126 | } |
| 35 | 127 | ||
| 36 | { | 128 | { |
| ... | @@ -88,6 +180,8 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -88,6 +180,8 @@ pub fn addCases(ctx: *TestContext) !void { |
| 88 | \\ unreachable; | 180 | \\ unreachable; |
| 89 | \\} | 181 | \\} |
| 90 | , | 182 | , |
| 183 | \\ZIG_EXTERN_C zig_noreturn void _start(void); | ||
| 184 | \\ | ||
| 91 | \\zig_noreturn void _start(void) { | 185 | \\zig_noreturn void _start(void) { |
| 92 | \\ zig_breakpoint(); | 186 | \\ zig_breakpoint(); |
| 93 | \\ zig_unreachable(); | 187 | \\ zig_unreachable(); |
| ... | @@ -97,254 +191,37 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -97,254 +191,37 @@ pub fn addCases(ctx: *TestContext) !void { |
| 97 | ctx.h("simple header", linux_x64, | 191 | ctx.h("simple header", linux_x64, |
| 98 | \\export fn start() void{} | 192 | \\export fn start() void{} |
| 99 | , | 193 | , |
| 100 | \\void start(void); | 194 | \\ZIG_EXTERN_C void start(void); |
| 101 | \\ | ||
| 102 | ); | ||
| 103 | ctx.c("less empty start function", linux_x64, | ||
| 104 | \\fn main() noreturn { | ||
| 105 | \\ unreachable; | ||
| 106 | \\} | ||
| 107 | \\ | ||
| 108 | \\export fn _start() noreturn { | ||
| 109 | \\ main(); | ||
| 110 | \\} | ||
| 111 | , | ||
| 112 | \\static zig_noreturn void main(void); | ||
| 113 | \\ | ||
| 114 | \\zig_noreturn void _start(void) { | ||
| 115 | \\ main(); | ||
| 116 | \\} | ||
| 117 | \\ | ||
| 118 | \\static zig_noreturn void main(void) { | ||
| 119 | \\ zig_breakpoint(); | ||
| 120 | \\ zig_unreachable(); | ||
| 121 | \\} | ||
| 122 | \\ | ||
| 123 | ); | ||
| 124 | // TODO: implement return values | ||
| 125 | // TODO: figure out a way to prevent asm constants from being generated | ||
| 126 | ctx.c("inline asm", linux_x64, | ||
| 127 | \\fn exitGood() noreturn { | ||
| 128 | \\ asm volatile ("syscall" | ||
| 129 | \\ : | ||
| 130 | \\ : [number] "{rax}" (231), | ||
| 131 | \\ [arg1] "{rdi}" (0) | ||
| 132 | \\ ); | ||
| 133 | \\ unreachable; | ||
| 134 | \\} | ||
| 135 | \\ | ||
| 136 | \\export fn _start() noreturn { | ||
| 137 | \\ exitGood(); | ||
| 138 | \\} | ||
| 139 | , | ||
| 140 | \\static zig_noreturn void exitGood(void); | ||
| 141 | \\ | ||
| 142 | \\static uint8_t exitGood__anon_0[6] = "{rax}"; | ||
| 143 | \\static uint8_t exitGood__anon_1[6] = "{rdi}"; | ||
| 144 | \\static uint8_t exitGood__anon_2[8] = "syscall"; | ||
| 145 | \\ | ||
| 146 | \\zig_noreturn void _start(void) { | ||
| 147 | \\ exitGood(); | ||
| 148 | \\} | ||
| 149 | \\ | ||
| 150 | \\static zig_noreturn void exitGood(void) { | ||
| 151 | \\ register uintptr_t rax_constant __asm__("rax") = 231; | ||
| 152 | \\ register uintptr_t rdi_constant __asm__("rdi") = 0; | ||
| 153 | \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); | ||
| 154 | \\ zig_breakpoint(); | ||
| 155 | \\ zig_unreachable(); | ||
| 156 | \\} | ||
| 157 | \\ | ||
| 158 | ); | ||
| 159 | ctx.c("exit with parameter", linux_x64, | ||
| 160 | \\export fn _start() noreturn { | ||
| 161 | \\ exit(0); | ||
| 162 | \\} | ||
| 163 | \\ | ||
| 164 | \\fn exit(code: usize) noreturn { | ||
| 165 | \\ asm volatile ("syscall" | ||
| 166 | \\ : | ||
| 167 | \\ : [number] "{rax}" (231), | ||
| 168 | \\ [arg1] "{rdi}" (code) | ||
| 169 | \\ ); | ||
| 170 | \\ unreachable; | ||
| 171 | \\} | ||
| 172 | \\ | ||
| 173 | , | ||
| 174 | \\static zig_noreturn void exit(uintptr_t arg0); | ||
| 175 | \\ | ||
| 176 | \\static uint8_t exit__anon_0[6] = "{rax}"; | ||
| 177 | \\static uint8_t exit__anon_1[6] = "{rdi}"; | ||
| 178 | \\static uint8_t exit__anon_2[8] = "syscall"; | ||
| 179 | \\ | ||
| 180 | \\zig_noreturn void _start(void) { | ||
| 181 | \\ exit(0); | ||
| 182 | \\} | ||
| 183 | \\ | ||
| 184 | \\static zig_noreturn void exit(uintptr_t arg0) { | ||
| 185 | \\ register uintptr_t rax_constant __asm__("rax") = 231; | ||
| 186 | \\ register uintptr_t rdi_constant __asm__("rdi") = arg0; | ||
| 187 | \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); | ||
| 188 | \\ zig_breakpoint(); | ||
| 189 | \\ zig_unreachable(); | ||
| 190 | \\} | ||
| 191 | \\ | ||
| 192 | ); | ||
| 193 | ctx.c("exit with u8 parameter", linux_x64, | ||
| 194 | \\export fn _start() noreturn { | ||
| 195 | \\ exit(0); | ||
| 196 | \\} | ||
| 197 | \\ | ||
| 198 | \\fn exit(code: u8) noreturn { | ||
| 199 | \\ asm volatile ("syscall" | ||
| 200 | \\ : | ||
| 201 | \\ : [number] "{rax}" (231), | ||
| 202 | \\ [arg1] "{rdi}" (code) | ||
| 203 | \\ ); | ||
| 204 | \\ unreachable; | ||
| 205 | \\} | ||
| 206 | \\ | ||
| 207 | , | ||
| 208 | \\static zig_noreturn void exit(uint8_t arg0); | ||
| 209 | \\ | ||
| 210 | \\static uint8_t exit__anon_0[6] = "{rax}"; | ||
| 211 | \\static uint8_t exit__anon_1[6] = "{rdi}"; | ||
| 212 | \\static uint8_t exit__anon_2[8] = "syscall"; | ||
| 213 | \\ | ||
| 214 | \\zig_noreturn void _start(void) { | ||
| 215 | \\ exit(0); | ||
| 216 | \\} | ||
| 217 | \\ | ||
| 218 | \\static zig_noreturn void exit(uint8_t arg0) { | ||
| 219 | \\ uintptr_t const __temp_0 = (uintptr_t)arg0; | ||
| 220 | \\ register uintptr_t rax_constant __asm__("rax") = 231; | ||
| 221 | \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; | ||
| 222 | \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); | ||
| 223 | \\ zig_breakpoint(); | ||
| 224 | \\ zig_unreachable(); | ||
| 225 | \\} | ||
| 226 | \\ | ||
| 227 | ); | ||
| 228 | ctx.c("exit with u8 arithmetic", linux_x64, | ||
| 229 | \\export fn _start() noreturn { | ||
| 230 | \\ exitMath(1); | ||
| 231 | \\} | ||
| 232 | \\ | ||
| 233 | \\fn exitMath(a: u8) noreturn { | ||
| 234 | \\ exit(0 + a - a); | ||
| 235 | \\} | ||
| 236 | \\ | ||
| 237 | \\fn exit(code: u8) noreturn { | ||
| 238 | \\ asm volatile ("syscall" | ||
| 239 | \\ : | ||
| 240 | \\ : [number] "{rax}" (231), | ||
| 241 | \\ [arg1] "{rdi}" (code) | ||
| 242 | \\ ); | ||
| 243 | \\ unreachable; | ||
| 244 | \\} | ||
| 245 | \\ | ||
| 246 | , | ||
| 247 | \\static zig_noreturn void exitMath(uint8_t arg0); | ||
| 248 | \\static zig_noreturn void exit(uint8_t arg0); | ||
| 249 | \\ | ||
| 250 | \\static uint8_t exit__anon_0[6] = "{rax}"; | ||
| 251 | \\static uint8_t exit__anon_1[6] = "{rdi}"; | ||
| 252 | \\static uint8_t exit__anon_2[8] = "syscall"; | ||
| 253 | \\ | ||
| 254 | \\zig_noreturn void _start(void) { | ||
| 255 | \\ exitMath(1); | ||
| 256 | \\} | ||
| 257 | \\ | ||
| 258 | \\static zig_noreturn void exitMath(uint8_t arg0) { | ||
| 259 | \\ uint8_t const __temp_0 = 0 + arg0; | ||
| 260 | \\ uint8_t const __temp_1 = __temp_0 - arg0; | ||
| 261 | \\ exit(__temp_1); | ||
| 262 | \\} | ||
| 263 | \\ | ||
| 264 | \\static zig_noreturn void exit(uint8_t arg0) { | ||
| 265 | \\ uintptr_t const __temp_0 = (uintptr_t)arg0; | ||
| 266 | \\ register uintptr_t rax_constant __asm__("rax") = 231; | ||
| 267 | \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; | ||
| 268 | \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); | ||
| 269 | \\ zig_breakpoint(); | ||
| 270 | \\ zig_unreachable(); | ||
| 271 | \\} | ||
| 272 | \\ | ||
| 273 | ); | ||
| 274 | ctx.c("exit with u8 arithmetic inverted", linux_x64, | ||
| 275 | \\export fn _start() noreturn { | ||
| 276 | \\ exitMath(1); | ||
| 277 | \\} | ||
| 278 | \\ | ||
| 279 | \\fn exitMath(a: u8) noreturn { | ||
| 280 | \\ exit(a + 0 - a); | ||
| 281 | \\} | ||
| 282 | \\ | ||
| 283 | \\fn exit(code: u8) noreturn { | ||
| 284 | \\ asm volatile ("syscall" | ||
| 285 | \\ : | ||
| 286 | \\ : [number] "{rax}" (231), | ||
| 287 | \\ [arg1] "{rdi}" (code) | ||
| 288 | \\ ); | ||
| 289 | \\ unreachable; | ||
| 290 | \\} | ||
| 291 | \\ | ||
| 292 | , | ||
| 293 | \\static zig_noreturn void exitMath(uint8_t arg0); | ||
| 294 | \\static zig_noreturn void exit(uint8_t arg0); | ||
| 295 | \\ | ||
| 296 | \\static uint8_t exit__anon_0[6] = "{rax}"; | ||
| 297 | \\static uint8_t exit__anon_1[6] = "{rdi}"; | ||
| 298 | \\static uint8_t exit__anon_2[8] = "syscall"; | ||
| 299 | \\ | ||
| 300 | \\zig_noreturn void _start(void) { | ||
| 301 | \\ exitMath(1); | ||
| 302 | \\} | ||
| 303 | \\ | ||
| 304 | \\static zig_noreturn void exitMath(uint8_t arg0) { | ||
| 305 | \\ uint8_t const __temp_0 = arg0 + 0; | ||
| 306 | \\ uint8_t const __temp_1 = __temp_0 - arg0; | ||
| 307 | \\ exit(__temp_1); | ||
| 308 | \\} | ||
| 309 | \\ | ||
| 310 | \\static zig_noreturn void exit(uint8_t arg0) { | ||
| 311 | \\ uintptr_t const __temp_0 = (uintptr_t)arg0; | ||
| 312 | \\ register uintptr_t rax_constant __asm__("rax") = 231; | ||
| 313 | \\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0; | ||
| 314 | \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant)); | ||
| 315 | \\ zig_breakpoint(); | ||
| 316 | \\ zig_unreachable(); | ||
| 317 | \\} | ||
| 318 | \\ | 195 | \\ |
| 319 | ); | 196 | ); |
| 320 | ctx.h("header with single param function", linux_x64, | 197 | ctx.h("header with single param function", linux_x64, |
| 321 | \\export fn start(a: u8) void{} | 198 | \\export fn start(a: u8) void{} |
| 322 | , | 199 | , |
| 323 | \\void start(uint8_t arg0); | 200 | \\ZIG_EXTERN_C void start(uint8_t a0); |
| 324 | \\ | 201 | \\ |
| 325 | ); | 202 | ); |
| 326 | ctx.h("header with multiple param function", linux_x64, | 203 | ctx.h("header with multiple param function", linux_x64, |
| 327 | \\export fn start(a: u8, b: u8, c: u8) void{} | 204 | \\export fn start(a: u8, b: u8, c: u8) void{} |
| 328 | , | 205 | , |
| 329 | \\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2); | 206 | \\ZIG_EXTERN_C void start(uint8_t a0, uint8_t a1, uint8_t a2); |
| 330 | \\ | 207 | \\ |
| 331 | ); | 208 | ); |
| 332 | ctx.h("header with u32 param function", linux_x64, | 209 | ctx.h("header with u32 param function", linux_x64, |
| 333 | \\export fn start(a: u32) void{} | 210 | \\export fn start(a: u32) void{} |
| 334 | , | 211 | , |
| 335 | \\void start(uint32_t arg0); | 212 | \\ZIG_EXTERN_C void start(uint32_t a0); |
| 336 | \\ | 213 | \\ |
| 337 | ); | 214 | ); |
| 338 | ctx.h("header with usize param function", linux_x64, | 215 | ctx.h("header with usize param function", linux_x64, |
| 339 | \\export fn start(a: usize) void{} | 216 | \\export fn start(a: usize) void{} |
| 340 | , | 217 | , |
| 341 | \\void start(uintptr_t arg0); | 218 | \\ZIG_EXTERN_C void start(uintptr_t a0); |
| 342 | \\ | 219 | \\ |
| 343 | ); | 220 | ); |
| 344 | ctx.h("header with bool param function", linux_x64, | 221 | ctx.h("header with bool param function", linux_x64, |
| 345 | \\export fn start(a: bool) void{} | 222 | \\export fn start(a: bool) void{} |
| 346 | , | 223 | , |
| 347 | \\void start(bool arg0); | 224 | \\ZIG_EXTERN_C void start(bool a0); |
| 348 | \\ | 225 | \\ |
| 349 | ); | 226 | ); |
| 350 | ctx.h("header with noreturn function", linux_x64, | 227 | ctx.h("header with noreturn function", linux_x64, |
| ... | @@ -352,7 +229,7 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -352,7 +229,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 352 | \\ unreachable; | 229 | \\ unreachable; |
| 353 | \\} | 230 | \\} |
| 354 | , | 231 | , |
| 355 | \\zig_noreturn void start(void); | 232 | \\ZIG_EXTERN_C zig_noreturn void start(void); |
| 356 | \\ | 233 | \\ |
| 357 | ); | 234 | ); |
| 358 | ctx.h("header with multiple functions", linux_x64, | 235 | ctx.h("header with multiple functions", linux_x64, |
| ... | @@ -360,15 +237,15 @@ pub fn addCases(ctx: *TestContext) !void { | ... | @@ -360,15 +237,15 @@ pub fn addCases(ctx: *TestContext) !void { |
| 360 | \\export fn b() void{} | 237 | \\export fn b() void{} |
| 361 | \\export fn c() void{} | 238 | \\export fn c() void{} |
| 362 | , | 239 | , |
| 363 | \\void a(void); | 240 | \\ZIG_EXTERN_C void a(void); |
| 364 | \\void b(void); | 241 | \\ZIG_EXTERN_C void b(void); |
| 365 | \\void c(void); | 242 | \\ZIG_EXTERN_C void c(void); |
| 366 | \\ | 243 | \\ |
| 367 | ); | 244 | ); |
| 368 | ctx.h("header with multiple includes", linux_x64, | 245 | ctx.h("header with multiple includes", linux_x64, |
| 369 | \\export fn start(a: u32, b: usize) void{} | 246 | \\export fn start(a: u32, b: usize) void{} |
| 370 | , | 247 | , |
| 371 | \\void start(uint32_t arg0, uintptr_t arg1); | 248 | \\ZIG_EXTERN_C void start(uint32_t a0, uintptr_t a1); |
| 372 | \\ | 249 | \\ |
| 373 | ); | 250 | ); |
| 374 | } | 251 | } |