| 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 | 559 | "${CMAKE_SOURCE_DIR}/src/link/MachO.zig" |
| 560 | 560 | "${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig" |
| 561 | 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 | 563 | "${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin" |
| 564 | 564 | "${CMAKE_SOURCE_DIR}/src/liveness.zig" |
| 565 | 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 | 100 | |
| 101 | 101 | /// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields |
| 102 | 102 | /// of this ArrayList. This ArrayList retains ownership of underlying memory. |
| 103 | /// Deprecated: use `moveToUnmanaged` which has different semantics. | |
| 103 | 104 | pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) { |
| 104 | 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 | 117 | /// The caller owns the returned memory. Empties this ArrayList. |
| 108 | 118 | pub fn toOwnedSlice(self: *Self) Slice { |
| 109 | 119 | const allocator = self.allocator; |
| ... | ... | @@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ |
| 551 | 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 | 564 | /// Append a value to the list `n` times. |
| 563 | 565 | /// Allocates more memory as necessary. |
| 564 | 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 | 1131 | } |
| 1130 | 1132 | } |
| 1131 | 1133 | |
| 1132 | test "std.ArrayList(u8) implements outStream" { | |
| 1134 | test "std.ArrayList(u8) implements writer" { | |
| 1133 | 1135 | var buffer = ArrayList(u8).init(std.testing.allocator); |
| 1134 | 1136 | defer buffer.deinit(); |
| 1135 | 1137 | |
| 1136 | 1138 | const x: i32 = 42; |
| 1137 | 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 | 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 | 459 | return index; |
| 460 | 460 | } |
| 461 | 461 | |
| 462 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 462 | 463 | pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { |
| 463 | 464 | if (is_windows) { |
| 464 | 465 | // TODO improve this to use ReadFileScatter |
| ... | ... | @@ -479,6 +480,7 @@ pub const File = struct { |
| 479 | 480 | /// is not an error condition. |
| 480 | 481 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 481 | 482 | /// order to handle partial reads from the underlying OS layer. |
| 483 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 482 | 484 | pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize { |
| 483 | 485 | if (iovecs.len == 0) return; |
| 484 | 486 | |
| ... | ... | @@ -500,6 +502,7 @@ pub const File = struct { |
| 500 | 502 | } |
| 501 | 503 | } |
| 502 | 504 | |
| 505 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 503 | 506 | pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize { |
| 504 | 507 | if (is_windows) { |
| 505 | 508 | // TODO improve this to use ReadFileScatter |
| ... | ... | @@ -520,6 +523,7 @@ pub const File = struct { |
| 520 | 523 | /// is not an error condition. |
| 521 | 524 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 522 | 525 | /// order to handle partial reads from the underlying OS layer. |
| 526 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 523 | 527 | pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void { |
| 524 | 528 | if (iovecs.len == 0) return; |
| 525 | 529 | |
| ... | ... | @@ -582,6 +586,7 @@ pub const File = struct { |
| 582 | 586 | } |
| 583 | 587 | } |
| 584 | 588 | |
| 589 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 585 | 590 | pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize { |
| 586 | 591 | if (is_windows) { |
| 587 | 592 | // TODO improve this to use WriteFileScatter |
| ... | ... | @@ -599,6 +604,7 @@ pub const File = struct { |
| 599 | 604 | |
| 600 | 605 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 601 | 606 | /// order to handle partial writes from the underlying OS layer. |
| 607 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 602 | 608 | pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void { |
| 603 | 609 | if (iovecs.len == 0) return; |
| 604 | 610 | |
| ... | ... | @@ -615,6 +621,7 @@ pub const File = struct { |
| 615 | 621 | } |
| 616 | 622 | } |
| 617 | 623 | |
| 624 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 618 | 625 | pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize { |
| 619 | 626 | if (is_windows) { |
| 620 | 627 | // TODO improve this to use WriteFileScatter |
| ... | ... | @@ -632,6 +639,7 @@ pub const File = struct { |
| 632 | 639 | |
| 633 | 640 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 634 | 641 | /// order to handle partial writes from the underlying OS layer. |
| 642 | /// See https://github.com/ziglang/zig/issues/7699 | |
| 635 | 643 | pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void { |
| 636 | 644 | if (iovecs.len == 0) return; |
| 637 | 645 |
src/Compilation.zig+84-66| ... | ... | @@ -27,7 +27,6 @@ const Cache = @import("Cache.zig"); |
| 27 | 27 | const stage1 = @import("stage1.zig"); |
| 28 | 28 | const translate_c = @import("translate_c.zig"); |
| 29 | 29 | const c_codegen = @import("codegen/c.zig"); |
| 30 | const c_link = @import("link/C.zig"); | |
| 31 | 30 | const ThreadPool = @import("ThreadPool.zig"); |
| 32 | 31 | const WaitGroup = @import("WaitGroup.zig"); |
| 33 | 32 | const libtsan = @import("libtsan.zig"); |
| ... | ... | @@ -138,8 +137,6 @@ emit_llvm_ir: ?EmitLoc, |
| 138 | 137 | emit_analysis: ?EmitLoc, |
| 139 | 138 | emit_docs: ?EmitLoc, |
| 140 | 139 | |
| 141 | c_header: ?c_link.Header, | |
| 142 | ||
| 143 | 140 | work_queue_wait_group: WaitGroup, |
| 144 | 141 | |
| 145 | 142 | pub const InnerError = Module.InnerError; |
| ... | ... | @@ -164,6 +161,8 @@ pub const CSourceFile = struct { |
| 164 | 161 | const Job = union(enum) { |
| 165 | 162 | /// Write the machine code for a Decl to the output file. |
| 166 | 163 | codegen_decl: *Module.Decl, |
| 164 | /// Render the .h file snippet for the Decl. | |
| 165 | emit_h_decl: *Module.Decl, | |
| 167 | 166 | /// The Decl needs to be analyzed and possibly export itself. |
| 168 | 167 | /// It may have already be analyzed, or it may have been determined |
| 169 | 168 | /// to be outdated; in this case perform semantic analysis again. |
| ... | ... | @@ -866,9 +865,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 866 | 865 | .root_pkg = root_pkg, |
| 867 | 866 | .root_scope = root_scope, |
| 868 | 867 | .zig_cache_artifact_directory = zig_cache_artifact_directory, |
| 868 | .emit_h = options.emit_h, | |
| 869 | 869 | }; |
| 870 | 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 | 875 | errdefer if (module) |zm| zm.deinit(); |
| 873 | 876 | |
| 874 | 877 | const error_return_tracing = !strip and switch (options.optimize_mode) { |
| ... | ... | @@ -996,7 +999,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { |
| 996 | 999 | .local_cache_directory = options.local_cache_directory, |
| 997 | 1000 | .global_cache_directory = options.global_cache_directory, |
| 998 | 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 | 1002 | .emit_asm = options.emit_asm, |
| 1001 | 1003 | .emit_llvm_ir = options.emit_llvm_ir, |
| 1002 | 1004 | .emit_analysis = options.emit_analysis, |
| ... | ... | @@ -1218,10 +1220,6 @@ pub fn destroy(self: *Compilation) void { |
| 1218 | 1220 | } |
| 1219 | 1221 | self.failed_c_objects.deinit(gpa); |
| 1220 | 1222 | |
| 1221 | if (self.c_header) |*header| { | |
| 1222 | header.deinit(); | |
| 1223 | } | |
| 1224 | ||
| 1225 | 1223 | self.cache_parent.manifest_dir.close(); |
| 1226 | 1224 | if (self.owned_link_dir) |*dir| dir.close(); |
| 1227 | 1225 | |
| ... | ... | @@ -1315,9 +1313,14 @@ pub fn update(self: *Compilation) !void { |
| 1315 | 1313 | |
| 1316 | 1314 | // This is needed before reading the error flags. |
| 1317 | 1315 | try self.bin_file.flush(self); |
| 1318 | ||
| 1319 | 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 | 1324 | // If there are any errors, we anticipate the source files being loaded |
| 1322 | 1325 | // to report error messages. Otherwise we unload all source files to save memory. |
| 1323 | 1326 | if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { |
| ... | ... | @@ -1325,20 +1328,6 @@ pub fn update(self: *Compilation) !void { |
| 1325 | 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 | 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 | 1346 | var total: usize = self.failed_c_objects.items().len; |
| 1358 | 1347 | |
| 1359 | 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 | 1351 | module.failed_exports.items().len + |
| 1362 | 1352 | module.failed_files.items().len + |
| 1363 | 1353 | @boolToInt(module.failed_root_src_file != null); |
| ... | ... | @@ -1396,6 +1386,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { |
| 1396 | 1386 | const source = try decl.scope.getSource(module); |
| 1397 | 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 | 1395 | for (module.failed_exports.items()) |entry| { |
| 1400 | 1396 | const decl = entry.key.owner_decl; |
| 1401 | 1397 | const err_msg = entry.value; |
| ... | ... | @@ -1493,44 +1489,66 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor |
| 1493 | 1489 | |
| 1494 | 1490 | assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); |
| 1495 | 1491 | |
| 1496 | self.bin_file.updateDecl(module, decl) catch |err| { | |
| 1497 | switch (err) { | |
| 1498 | error.OutOfMemory => return error.OutOfMemory, | |
| 1499 | error.AnalysisFail => { | |
| 1500 | decl.analysis = .dependency_failure; | |
| 1501 | }, | |
| 1502 | else => { | |
| 1503 | try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); | |
| 1504 | module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | |
| 1505 | module.gpa, | |
| 1506 | decl.src(), | |
| 1507 | "unable to codegen: {s}", | |
| 1508 | .{@errorName(err)}, | |
| 1509 | )); | |
| 1510 | decl.analysis = .codegen_failure_retryable; | |
| 1511 | }, | |
| 1512 | } | |
| 1513 | return; | |
| 1492 | self.bin_file.updateDecl(module, decl) catch |err| switch (err) { | |
| 1493 | error.OutOfMemory => return error.OutOfMemory, | |
| 1494 | error.AnalysisFail => { | |
| 1495 | decl.analysis = .codegen_failure; | |
| 1496 | continue; | |
| 1497 | }, | |
| 1498 | else => { | |
| 1499 | try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); | |
| 1500 | module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | |
| 1501 | module.gpa, | |
| 1502 | decl.src(), | |
| 1503 | "unable to codegen: {s}", | |
| 1504 | .{@errorName(err)}, | |
| 1505 | )); | |
| 1506 | decl.analysis = .codegen_failure_retryable; | |
| 1507 | continue; | |
| 1508 | }, | |
| 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 | c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) { | |
| 1518 | error.OutOfMemory => return error.OutOfMemory, | |
| 1519 | error.AnalysisFail => { | |
| 1520 | decl.analysis = .dependency_failure; | |
| 1521 | }, | |
| 1522 | else => { | |
| 1523 | try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); | |
| 1524 | module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( | |
| 1525 | module.gpa, | |
| 1526 | decl.src(), | |
| 1527 | "unable to generate C header: {s}", | |
| 1528 | .{@errorName(err)}, | |
| 1529 | )); | |
| 1530 | decl.analysis = .codegen_failure_retryable; | |
| 1531 | }, | |
| 1532 | }; | |
| 1533 | } | |
| 1517 | .sema_failure, | |
| 1518 | .dependency_failure, | |
| 1519 | .sema_failure_retryable, | |
| 1520 | => continue, | |
| 1521 | ||
| 1522 | // emit-h only requires semantic analysis of the Decl to be complete, | |
| 1523 | // it does not depend on machine code generation to succeed. | |
| 1524 | .codegen_failure, .codegen_failure_retryable, .complete => { | |
| 1525 | if (build_options.omit_stage2) | |
| 1526 | @panic("sadly stage2 is omitted from this build to save memory on the CI server"); | |
| 1527 | const module = self.bin_file.options.module.?; | |
| 1528 | const emit_loc = module.emit_h.?; | |
| 1529 | const tv = decl.typed_value.most_recent.typed_value; | |
| 1530 | const emit_h = decl.getEmitH(module); | |
| 1531 | const fwd_decl = &emit_h.fwd_decl; | |
| 1532 | fwd_decl.shrinkRetainingCapacity(0); | |
| 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 | 1554 | .analyze_decl => |decl| { |
| ... | ... | @@ -2998,9 +3016,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 2998 | 3016 | man.hash.add(comp.bin_file.options.function_sections); |
| 2999 | 3017 | man.hash.add(comp.bin_file.options.is_test); |
| 3000 | 3018 | man.hash.add(comp.bin_file.options.emit != null); |
| 3001 | man.hash.add(comp.c_header != null); | |
| 3002 | if (comp.c_header) |header| { | |
| 3003 | man.hash.addEmitLoc(header.emit_loc.?); | |
| 3019 | man.hash.add(mod.emit_h != null); | |
| 3020 | if (mod.emit_h) |emit_h| { | |
| 3021 | man.hash.addEmitLoc(emit_h); | |
| 3004 | 3022 | } |
| 3005 | 3023 | man.hash.addOptionalEmitLoc(comp.emit_asm); |
| 3006 | 3024 | man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); |
| ... | ... | @@ -3105,10 +3123,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node |
| 3105 | 3123 | }); |
| 3106 | 3124 | break :blk try directory.join(arena, &[_][]const u8{bin_basename}); |
| 3107 | 3125 | } else ""; |
| 3108 | if (comp.c_header != null) { | |
| 3126 | if (mod.emit_h != null) { | |
| 3109 | 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 | 3130 | const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); |
| 3113 | 3131 | const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); |
| 3114 | 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 | 57 | /// Note that a Decl can succeed but the Fn it represents can fail. In this case, |
| 58 | 58 | /// a Decl can have a failed_decls entry but have analysis status of success. |
| 59 | 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 | 64 | /// Using a map here for consistency with the other fields here. |
| 61 | 65 | /// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator. |
| 62 | 66 | failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{}, |
| ... | ... | @@ -94,6 +98,8 @@ stage1_flags: packed struct { |
| 94 | 98 | reserved: u2 = 0, |
| 95 | 99 | } = .{}, |
| 96 | 100 | |
| 101 | emit_h: ?Compilation.EmitLoc, | |
| 102 | ||
| 97 | 103 | pub const Export = struct { |
| 98 | 104 | options: std.builtin.ExportOptions, |
| 99 | 105 | /// Byte offset into the file that contains the export directive. |
| ... | ... | @@ -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 | 130 | pub const Decl = struct { |
| 118 | 131 | /// This name is relative to the containing namespace of the decl. It uses a null-termination |
| 119 | 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 | 215 | /// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself` |
| 203 | 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 | 220 | gpa.free(mem.spanZ(self.name)); |
| 207 | 221 | if (self.typedValueManaged()) |tvm| { |
| 208 | 222 | tvm.deinit(gpa); |
| 209 | 223 | } |
| 210 | 224 | self.dependants.deinit(gpa); |
| 211 | 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 | 235 | pub fn src(self: Decl) usize { |
| ... | ... | @@ -275,6 +295,12 @@ pub const Decl = struct { |
| 275 | 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 | 304 | fn removeDependant(self: *Decl, other: *Decl) void { |
| 279 | 305 | self.dependants.removeAssertDiscard(other); |
| 280 | 306 | } |
| ... | ... | @@ -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 | 318 | /// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator. |
| 288 | 319 | /// Extern functions do not have this data structure; they are represented by |
| 289 | 320 | /// the `Decl` only, with a `Value` tag of `extern_fn`. |
| ... | ... | @@ -881,7 +912,7 @@ pub fn deinit(self: *Module) void { |
| 881 | 912 | self.deletion_set.deinit(gpa); |
| 882 | 913 | |
| 883 | 914 | for (self.decl_table.items()) |entry| { |
| 884 | entry.value.destroy(gpa); | |
| 915 | entry.value.destroy(self); | |
| 885 | 916 | } |
| 886 | 917 | self.decl_table.deinit(gpa); |
| 887 | 918 | |
| ... | ... | @@ -890,6 +921,11 @@ pub fn deinit(self: *Module) void { |
| 890 | 921 | } |
| 891 | 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 | 929 | for (self.failed_files.items()) |entry| { |
| 894 | 930 | entry.value.destroy(gpa); |
| 895 | 931 | } |
| ... | ... | @@ -1148,6 +1184,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1148 | 1184 | try self.comp.bin_file.allocateDeclIndexes(decl); |
| 1149 | 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 | 1191 | return type_changed; |
| 1152 | 1192 | }; |
| 1153 | 1193 | |
| ... | ... | @@ -1267,6 +1307,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool { |
| 1267 | 1307 | // increasing how many computations can be done in parallel. |
| 1268 | 1308 | try self.comp.bin_file.allocateDeclIndexes(decl); |
| 1269 | 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 | 1313 | } else if (!prev_is_inline and prev_type_has_bits) { |
| 1271 | 1314 | self.comp.bin_file.freeDecl(decl); |
| 1272 | 1315 | } |
| ... | ... | @@ -1835,9 +1878,13 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void { |
| 1835 | 1878 | if (self.failed_decls.remove(decl)) |entry| { |
| 1836 | 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 | 1884 | self.deleteDeclExports(decl); |
| 1839 | 1885 | self.comp.bin_file.freeDecl(decl); |
| 1840 | decl.destroy(self.gpa); | |
| 1886 | ||
| 1887 | decl.destroy(self); | |
| 1841 | 1888 | } |
| 1842 | 1889 | |
| 1843 | 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 | 1968 | if (self.failed_decls.remove(decl)) |entry| { |
| 1922 | 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 | 1974 | decl.analysis = .outdated; |
| 1925 | 1975 | } |
| 1926 | 1976 | |
| 1927 | 1977 | fn allocateNewDecl( |
| 1928 | self: *Module, | |
| 1978 | mod: *Module, | |
| 1929 | 1979 | scope: *Scope, |
| 1930 | 1980 | src_index: usize, |
| 1931 | 1981 | contents_hash: std.zig.SrcHash, |
| 1932 | 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 | 1993 | new_decl.* = .{ |
| 1935 | 1994 | .name = "", |
| 1936 | 1995 | .scope = scope.namespace(), |
| ... | ... | @@ -1939,18 +1998,18 @@ fn allocateNewDecl( |
| 1939 | 1998 | .analysis = .unreferenced, |
| 1940 | 1999 | .deletion_flag = false, |
| 1941 | 2000 | .contents_hash = contents_hash, |
| 1942 | .link = switch (self.comp.bin_file.tag) { | |
| 2001 | .link = switch (mod.comp.bin_file.tag) { | |
| 1943 | 2002 | .coff => .{ .coff = link.File.Coff.TextBlock.empty }, |
| 1944 | 2003 | .elf => .{ .elf = link.File.Elf.TextBlock.empty }, |
| 1945 | 2004 | .macho => .{ .macho = link.File.MachO.TextBlock.empty }, |
| 1946 | .c => .{ .c = {} }, | |
| 2005 | .c => .{ .c = link.File.C.DeclBlock.empty }, | |
| 1947 | 2006 | .wasm => .{ .wasm = {} }, |
| 1948 | 2007 | }, |
| 1949 | .fn_link = switch (self.comp.bin_file.tag) { | |
| 2008 | .fn_link = switch (mod.comp.bin_file.tag) { | |
| 1950 | 2009 | .coff => .{ .coff = {} }, |
| 1951 | 2010 | .elf => .{ .elf = link.File.Elf.SrcFn.empty }, |
| 1952 | 2011 | .macho => .{ .macho = link.File.MachO.SrcFn.empty }, |
| 1953 | .c => .{ .c = {} }, | |
| 2012 | .c => .{ .c = link.File.C.FnBlock.empty }, | |
| 1954 | 2013 | .wasm => .{ .wasm = null }, |
| 1955 | 2014 | }, |
| 1956 | 2015 | .generation = 0, |
src/codegen/c.zig+504-456| ... | ... | @@ -1,495 +1,532 @@ |
| 1 | 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 | 6 | const link = @import("../link.zig"); |
| 4 | 7 | const Module = @import("../Module.zig"); |
| 5 | 8 | const Compilation = @import("../Compilation.zig"); |
| 6 | ||
| 7 | 9 | const Inst = @import("../ir.zig").Inst; |
| 8 | 10 | const Value = @import("../value.zig").Value; |
| 9 | 11 | const Type = @import("../type.zig").Type; |
| 10 | ||
| 12 | const TypedValue = @import("../TypedValue.zig"); | |
| 11 | 13 | const C = link.File.C; |
| 12 | 14 | const Decl = Module.Decl; |
| 13 | const mem = std.mem; | |
| 14 | const log = std.log.scoped(.c); | |
| 15 | const trace = @import("../tracy.zig").trace; | |
| 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 | /// output for any given input, sometimes resulting in broken identifiers. | |
| 20 | fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 { | |
| 21 | return allocator.dupe(u8, name); | |
| 22 | } | |
| 19 | pub const CValue = union(enum) { | |
| 20 | none: void, | |
| 21 | /// Index into local_names | |
| 22 | local: usize, | |
| 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( | |
| 27 | ctx: *Context, | |
| 28 | writer: Writer, | |
| 29 | ty: Type, | |
| 30 | name: []const u8, | |
| 31 | mutability: Mutability, | |
| 32 | ) error{ OutOfMemory, AnalysisFail }!void { | |
| 33 | var suffix = std.ArrayList(u8).init(&ctx.arena.allocator); | |
| 34 | ||
| 35 | var render_ty = ty; | |
| 36 | while (render_ty.zigTypeTag() == .Array) { | |
| 37 | const sentinel_bit = @boolToInt(render_ty.sentinel() != null); | |
| 38 | const c_len = render_ty.arrayLen() + sentinel_bit; | |
| 39 | try suffix.writer().print("[{d}]", .{c_len}); | |
| 40 | render_ty = render_ty.elemType(); | |
| 52 | fn allocLocalValue(o: *Object) CValue { | |
| 53 | const result = o.next_local_index; | |
| 54 | o.next_local_index += 1; | |
| 55 | return .{ .local = result }; | |
| 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) { | |
| 46 | .Const => "const ", | |
| 47 | .Mut => "", | |
| 48 | }; | |
| 49 | try writer.print(" {s}{s}{s}", .{ const_prefix, name, suffix.items }); | |
| 50 | } | |
| 64 | fn indent(o: *Object) !void { | |
| 65 | const indent_size = 4; | |
| 66 | const indent_level = 1; | |
| 67 | const indent_amt = indent_size * indent_level; | |
| 68 | try o.code.writer().writeByteNTimes(' ', indent_amt); | |
| 69 | } | |
| 51 | 70 | |
| 52 | fn renderType( | |
| 53 | ctx: *Context, | |
| 54 | writer: Writer, | |
| 55 | t: Type, | |
| 56 | ) error{ OutOfMemory, AnalysisFail }!void { | |
| 57 | switch (t.zigTypeTag()) { | |
| 58 | .NoReturn => { | |
| 59 | try writer.writeAll("zig_noreturn void"); | |
| 60 | }, | |
| 61 | .Void => try writer.writeAll("void"), | |
| 62 | .Bool => try writer.writeAll("bool"), | |
| 63 | .Int => { | |
| 64 | switch (t.tag()) { | |
| 65 | .u8 => try writer.writeAll("uint8_t"), | |
| 66 | .i8 => try writer.writeAll("int8_t"), | |
| 67 | .u16 => try writer.writeAll("uint16_t"), | |
| 68 | .i16 => try writer.writeAll("int16_t"), | |
| 69 | .u32 => try writer.writeAll("uint32_t"), | |
| 70 | .i32 => try writer.writeAll("int32_t"), | |
| 71 | .u64 => try writer.writeAll("uint64_t"), | |
| 72 | .i64 => try writer.writeAll("int64_t"), | |
| 73 | .usize => try writer.writeAll("uintptr_t"), | |
| 74 | .isize => try writer.writeAll("intptr_t"), | |
| 75 | .c_short => try writer.writeAll("short"), | |
| 76 | .c_ushort => try writer.writeAll("unsigned short"), | |
| 77 | .c_int => try writer.writeAll("int"), | |
| 78 | .c_uint => try writer.writeAll("unsigned int"), | |
| 79 | .c_long => try writer.writeAll("long"), | |
| 80 | .c_ulong => try writer.writeAll("unsigned long"), | |
| 81 | .c_longlong => try writer.writeAll("long long"), | |
| 82 | .c_ulonglong => try writer.writeAll("unsigned long long"), | |
| 83 | .int_signed, .int_unsigned => { | |
| 84 | const info = t.intInfo(ctx.target); | |
| 85 | const sign_prefix = switch (info.signedness) { | |
| 86 | .signed => "i", | |
| 87 | .unsigned => "", | |
| 88 | }; | |
| 89 | inline for (.{ 8, 16, 32, 64, 128 }) |nbits| { | |
| 90 | if (info.bits <= nbits) { | |
| 91 | try writer.print("{s}int{d}_t", .{ sign_prefix, nbits }); | |
| 92 | break; | |
| 93 | } | |
| 71 | fn writeCValue(o: *Object, writer: Writer, c_value: CValue) !void { | |
| 72 | switch (c_value) { | |
| 73 | .none => unreachable, | |
| 74 | .local => |i| return writer.print("t{d}", .{i}), | |
| 75 | .local_ref => |i| return writer.print("&t{d}", .{i}), | |
| 76 | .constant => |inst| return o.dg.renderValue(writer, inst.ty, inst.value().?), | |
| 77 | .arg => |i| return writer.print("a{d}", .{i}), | |
| 78 | .decl => |decl| return writer.writeAll(mem.span(decl.name)), | |
| 79 | } | |
| 80 | } | |
| 81 | ||
| 82 | fn renderTypeAndName( | |
| 83 | o: *Object, | |
| 84 | writer: Writer, | |
| 85 | ty: Type, | |
| 86 | name: CValue, | |
| 87 | mutability: Mutability, | |
| 88 | ) error{ OutOfMemory, AnalysisFail }!void { | |
| 89 | var suffix = std.ArrayList(u8).init(o.gpa); | |
| 90 | defer suffix.deinit(); | |
| 91 | ||
| 92 | var render_ty = ty; | |
| 93 | while (render_ty.zigTypeTag() == .Array) { | |
| 94 | const sentinel_bit = @boolToInt(render_ty.sentinel() != null); | |
| 95 | const c_len = render_ty.arrayLen() + sentinel_bit; | |
| 96 | try suffix.writer().print("[{d}]", .{c_len}); | |
| 97 | render_ty = render_ty.elemType(); | |
| 98 | } | |
| 99 | ||
| 100 | try o.dg.renderType(writer, render_ty); | |
| 101 | ||
| 102 | const const_prefix = switch (mutability) { | |
| 103 | .Const => "const ", | |
| 104 | .Mut => "", | |
| 105 | }; | |
| 106 | try writer.print(" {s}", .{const_prefix}); | |
| 107 | try o.writeCValue(writer, name); | |
| 108 | try writer.writeAll(suffix.items); | |
| 109 | } | |
| 110 | }; | |
| 111 | ||
| 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 | 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, | |
| 99 | } | |
| 100 | }, | |
| 101 | .Pointer => { | |
| 102 | if (t.isSlice()) { | |
| 103 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement slices", .{}); | |
| 104 | } else { | |
| 105 | try renderType(ctx, writer, t.elemType()); | |
| 106 | try writer.writeAll(" *"); | |
| 107 | if (t.isConstPtr()) { | |
| 108 | try writer.writeAll("const "); | |
| 152 | .function => { | |
| 153 | const func = val.castTag(.function).?.data; | |
| 154 | try writer.print("{s}", .{func.owner_decl.name}); | |
| 155 | }, | |
| 156 | .extern_fn => { | |
| 157 | const decl = val.castTag(.extern_fn).?.data; | |
| 158 | try writer.print("{s}", .{decl.name}); | |
| 159 | }, | |
| 160 | else => |e| return dg.fail( | |
| 161 | dg.decl.src(), | |
| 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()) { | |
| 111 | try writer.writeAll("volatile "); | |
| 196 | }, | |
| 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 | }, | |
| 115 | .Array => { | |
| 116 | try renderType(ctx, writer, t.elemType()); | |
| 117 | try writer.writeAll(" *"); | |
| 118 | }, | |
| 119 | else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement type {s}", .{ | |
| 120 | @tagName(e), | |
| 121 | }), | |
| 223 | } | |
| 224 | try w.writeByte(')'); | |
| 122 | 225 | } |
| 123 | } | |
| 124 | 226 | |
| 125 | fn renderValue( | |
| 126 | ctx: *Context, | |
| 127 | writer: Writer, | |
| 128 | t: Type, | |
| 129 | val: Value, | |
| 130 | ) error{ OutOfMemory, AnalysisFail }!void { | |
| 131 | switch (t.zigTypeTag()) { | |
| 132 | .Int => { | |
| 133 | if (t.isSignedInt()) | |
| 134 | return writer.print("{d}", .{val.toSignedInt()}); | |
| 135 | return writer.print("{d}", .{val.toUnsignedInt()}); | |
| 136 | }, | |
| 137 | .Pointer => switch (val.tag()) { | |
| 138 | .undef, .zero => try writer.writeAll("0"), | |
| 139 | .one => try writer.writeAll("1"), | |
| 140 | .decl_ref => { | |
| 141 | const decl = val.castTag(.decl_ref).?.data; | |
| 142 | ||
| 143 | // Determine if we must pointer cast. | |
| 144 | const decl_tv = decl.typed_value.most_recent.typed_value; | |
| 145 | if (t.eql(decl_tv.ty)) { | |
| 146 | try writer.print("&{s}", .{decl.name}); | |
| 147 | } else { | |
| 148 | try writer.writeAll("("); | |
| 149 | try renderType(ctx, writer, t); | |
| 150 | try writer.print(")&{s}", .{decl.name}); | |
| 151 | } | |
| 152 | }, | |
| 153 | .function => { | |
| 154 | const func = val.castTag(.function).?.data; | |
| 155 | try writer.print("{s}", .{func.owner_decl.name}); | |
| 227 | fn renderType(dg: *DeclGen, w: Writer, t: Type) error{ OutOfMemory, AnalysisFail }!void { | |
| 228 | switch (t.zigTypeTag()) { | |
| 229 | .NoReturn => { | |
| 230 | try w.writeAll("zig_noreturn void"); | |
| 156 | 231 | }, |
| 157 | .extern_fn => { | |
| 158 | const decl = val.castTag(.extern_fn).?.data; | |
| 159 | try writer.print("{s}", .{decl.name}); | |
| 232 | .Void => try w.writeAll("void"), | |
| 233 | .Bool => try w.writeAll("bool"), | |
| 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( | |
| 162 | ctx.decl.src(), | |
| 163 | "TODO: C backend: implement Pointer value {s}", | |
| 164 | .{@tagName(e)}, | |
| 165 | ), | |
| 166 | }, | |
| 167 | .Array => { | |
| 168 | // First try specific tag representations for more efficiency. | |
| 169 | switch (val.tag()) { | |
| 170 | .undef, .empty_struct_value, .empty_array => try writer.writeAll("{}"), | |
| 171 | .bytes => { | |
| 172 | const bytes = val.castTag(.bytes).?.data; | |
| 173 | // TODO: make our own C string escape instead of using {Z} | |
| 174 | try writer.print("\"{Z}\"", .{bytes}); | |
| 175 | }, | |
| 176 | else => { | |
| 177 | // Fall back to generic implementation. | |
| 178 | try writer.writeAll("{"); | |
| 179 | var index: usize = 0; | |
| 180 | const len = t.arrayLen(); | |
| 181 | const elem_ty = t.elemType(); | |
| 182 | while (index < len) : (index += 1) { | |
| 183 | if (index != 0) try writer.writeAll(","); | |
| 184 | const elem_val = try val.elemValue(&ctx.arena.allocator, index); | |
| 185 | try renderValue(ctx, writer, elem_ty, elem_val); | |
| 272 | .Pointer => { | |
| 273 | if (t.isSlice()) { | |
| 274 | return dg.fail(dg.decl.src(), "TODO: C backend: implement slices", .{}); | |
| 275 | } else { | |
| 276 | try dg.renderType(w, t.elemType()); | |
| 277 | try w.writeAll(" *"); | |
| 278 | if (t.isConstPtr()) { | |
| 279 | try w.writeAll("const "); | |
| 186 | 280 | } |
| 187 | if (t.sentinel()) |sentinel_val| { | |
| 188 | if (index != 0) try writer.writeAll(","); | |
| 189 | try renderValue(ctx, writer, elem_ty, sentinel_val); | |
| 281 | if (t.isVolatilePtr()) { | |
| 282 | try w.writeAll("volatile "); | |
| 190 | 283 | } |
| 191 | try writer.writeAll("}"); | |
| 192 | }, | |
| 193 | } | |
| 194 | }, | |
| 195 | else => |e| return ctx.fail(ctx.decl.src(), "TODO: C backend: implement value {s}", .{ | |
| 196 | @tagName(e), | |
| 197 | }), | |
| 284 | } | |
| 285 | }, | |
| 286 | .Array => { | |
| 287 | try dg.renderType(w, t.elemType()); | |
| 288 | try w.writeAll(" *"); | |
| 289 | }, | |
| 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( | |
| 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: { | |
| 296 | fn functionIsGlobal(dg: *DeclGen, tv: TypedValue) bool { | |
| 209 | 297 | switch (tv.val.tag()) { |
| 210 | .extern_fn => break :blk true, | |
| 298 | .extern_fn => return true, | |
| 211 | 299 | .function => { |
| 212 | 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 | 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(')'); | |
| 242 | } | |
| 306 | }; | |
| 243 | 307 | |
| 244 | fn indent(file: *C) !void { | |
| 245 | const indent_size = 4; | |
| 246 | const indent_level = 1; | |
| 247 | const indent_amt = indent_size * indent_level; | |
| 248 | try file.main.writer().writeByteNTimes(' ', indent_amt); | |
| 249 | } | |
| 308 | pub fn genDecl(o: *Object) !void { | |
| 309 | const tracy = trace(@src()); | |
| 310 | defer tracy.end(); | |
| 250 | 311 | |
| 251 | pub fn generate(file: *C, module: *Module, decl: *Decl) !void { | |
| 252 | const tv = decl.typed_value.most_recent.typed_value; | |
| 253 | ||
| 254 | var arena = std.heap.ArenaAllocator.init(file.base.allocator); | |
| 255 | defer arena.deinit(); | |
| 256 | var inst_map = std.AutoHashMap(*Inst, []u8).init(&arena.allocator); | |
| 257 | defer inst_map.deinit(); | |
| 258 | var ctx = Context{ | |
| 259 | .decl = decl, | |
| 260 | .arena = &arena, | |
| 261 | .inst_map = &inst_map, | |
| 262 | .target = file.base.options.target, | |
| 263 | .header = &file.header, | |
| 264 | .module = module, | |
| 265 | }; | |
| 266 | defer { | |
| 267 | file.error_msg = ctx.error_msg; | |
| 268 | ctx.deinit(); | |
| 269 | } | |
| 312 | const tv = o.dg.decl.typed_value.most_recent.typed_value; | |
| 270 | 313 | |
| 271 | 314 | if (tv.val.castTag(.function)) |func_payload| { |
| 272 | const writer = file.main.writer(); | |
| 273 | try renderFunctionSignature(&ctx, writer, decl); | |
| 274 | ||
| 275 | try writer.writeAll(" {"); | |
| 315 | const is_global = o.dg.functionIsGlobal(tv); | |
| 316 | const fwd_decl_writer = o.dg.fwd_decl.writer(); | |
| 317 | if (is_global) { | |
| 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 | 323 | const func: *Module.Fn = func_payload.data; |
| 278 | 324 | const instructions = func.body.instructions; |
| 279 | if (instructions.len > 0) { | |
| 280 | try writer.writeAll("\n"); | |
| 281 | for (instructions) |inst| { | |
| 282 | if (switch (inst.tag) { | |
| 283 | .add => try genBinOp(&ctx, file, inst.castTag(.add).?, "+"), | |
| 284 | .alloc => try genAlloc(&ctx, file, inst.castTag(.alloc).?), | |
| 285 | .arg => try genArg(&ctx), | |
| 286 | .assembly => try genAsm(&ctx, file, inst.castTag(.assembly).?), | |
| 287 | .block => try genBlock(&ctx, file, inst.castTag(.block).?), | |
| 288 | .bitcast => try genBitcast(&ctx, file, inst.castTag(.bitcast).?), | |
| 289 | .breakpoint => try genBreakpoint(file, inst.castTag(.breakpoint).?), | |
| 290 | .call => try genCall(&ctx, file, inst.castTag(.call).?), | |
| 291 | .cmp_eq => try genBinOp(&ctx, file, inst.castTag(.cmp_eq).?, "=="), | |
| 292 | .cmp_gt => try genBinOp(&ctx, file, inst.castTag(.cmp_gt).?, ">"), | |
| 293 | .cmp_gte => try genBinOp(&ctx, file, inst.castTag(.cmp_gte).?, ">="), | |
| 294 | .cmp_lt => try genBinOp(&ctx, file, inst.castTag(.cmp_lt).?, "<"), | |
| 295 | .cmp_lte => try genBinOp(&ctx, file, inst.castTag(.cmp_lte).?, "<="), | |
| 296 | .cmp_neq => try genBinOp(&ctx, file, inst.castTag(.cmp_neq).?, "!="), | |
| 297 | .dbg_stmt => try genDbgStmt(&ctx, inst.castTag(.dbg_stmt).?), | |
| 298 | .intcast => try genIntCast(&ctx, file, inst.castTag(.intcast).?), | |
| 299 | .load => try genLoad(&ctx, file, inst.castTag(.load).?), | |
| 300 | .ret => try genRet(&ctx, file, inst.castTag(.ret).?), | |
| 301 | .retvoid => try genRetVoid(file), | |
| 302 | .store => try genStore(&ctx, file, inst.castTag(.store).?), | |
| 303 | .sub => try genBinOp(&ctx, file, inst.castTag(.sub).?, "-"), | |
| 304 | .unreach => try genUnreach(file, inst.castTag(.unreach).?), | |
| 305 | else => |e| return ctx.fail(decl.src(), "TODO: C backend: implement codegen for {}", .{e}), | |
| 306 | }) |name| { | |
| 307 | try ctx.inst_map.putNoClobber(inst, name); | |
| 308 | } | |
| 325 | const writer = o.code.writer(); | |
| 326 | try writer.writeAll("\n"); | |
| 327 | try o.dg.renderFunctionSignature(writer, is_global); | |
| 328 | if (instructions.len == 0) { | |
| 329 | try writer.writeAll(" {}\n"); | |
| 330 | return; | |
| 331 | } | |
| 332 | ||
| 333 | try writer.writeAll(" {"); | |
| 334 | ||
| 335 | try writer.writeAll("\n"); | |
| 336 | for (instructions) |inst| { | |
| 337 | const result_value = switch (inst.tag) { | |
| 338 | .add => try genBinOp(o, inst.castTag(.add).?, " + "), | |
| 339 | .alloc => try genAlloc(o, inst.castTag(.alloc).?), | |
| 340 | .arg => genArg(o), | |
| 341 | .assembly => try genAsm(o, inst.castTag(.assembly).?), | |
| 342 | .block => try genBlock(o, inst.castTag(.block).?), | |
| 343 | .bitcast => try genBitcast(o, inst.castTag(.bitcast).?), | |
| 344 | .breakpoint => try genBreakpoint(o, inst.castTag(.breakpoint).?), | |
| 345 | .call => try genCall(o, inst.castTag(.call).?), | |
| 346 | .cmp_eq => try genBinOp(o, inst.castTag(.cmp_eq).?, " == "), | |
| 347 | .cmp_gt => try genBinOp(o, inst.castTag(.cmp_gt).?, " > "), | |
| 348 | .cmp_gte => try genBinOp(o, inst.castTag(.cmp_gte).?, " >= "), | |
| 349 | .cmp_lt => try genBinOp(o, inst.castTag(.cmp_lt).?, " < "), | |
| 350 | .cmp_lte => try genBinOp(o, inst.castTag(.cmp_lte).?, " <= "), | |
| 351 | .cmp_neq => try genBinOp(o, inst.castTag(.cmp_neq).?, " != "), | |
| 352 | .dbg_stmt => try genDbgStmt(o, inst.castTag(.dbg_stmt).?), | |
| 353 | .intcast => try genIntCast(o, inst.castTag(.intcast).?), | |
| 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 | 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 | 374 | } else { |
| 316 | const writer = file.constants.writer(); | |
| 375 | const writer = o.code.writer(); | |
| 317 | 376 | try writer.writeAll("static "); |
| 318 | 377 | |
| 319 | 378 | // TODO ask the Decl if it is const |
| 320 | 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 | 384 | try writer.writeAll(" = "); |
| 325 | try renderValue(&ctx, writer, tv.ty, tv.val); | |
| 385 | try o.dg.renderValue(writer, tv.ty, tv.val); | |
| 326 | 386 | try writer.writeAll(";\n"); |
| 327 | 387 | } |
| 328 | 388 | } |
| 329 | 389 | |
| 330 | pub fn generateHeader( | |
| 331 | comp: *Compilation, | |
| 332 | module: *Module, | |
| 333 | header: *C.Header, | |
| 334 | decl: *Decl, | |
| 335 | ) error{ AnalysisFail, OutOfMemory }!void { | |
| 336 | switch (decl.typed_value.most_recent.typed_value.ty.zigTypeTag()) { | |
| 390 | pub fn genHeader(dg: *DeclGen) error{ AnalysisFail, OutOfMemory }!void { | |
| 391 | const tracy = trace(@src()); | |
| 392 | defer tracy.end(); | |
| 393 | ||
| 394 | const tv = dg.decl.typed_value.most_recent.typed_value; | |
| 395 | const writer = dg.fwd_decl.writer(); | |
| 396 | ||
| 397 | switch (tv.ty.zigTypeTag()) { | |
| 337 | 398 | .Fn => { |
| 338 | var inst_map = std.AutoHashMap(*Inst, []u8).init(comp.gpa); | |
| 339 | defer inst_map.deinit(); | |
| 340 | ||
| 341 | var arena = std.heap.ArenaAllocator.init(comp.gpa); | |
| 342 | defer arena.deinit(); | |
| 343 | ||
| 344 | var ctx = Context{ | |
| 345 | .decl = decl, | |
| 346 | .arena = &arena, | |
| 347 | .inst_map = &inst_map, | |
| 348 | .target = comp.getTarget(), | |
| 349 | .header = header, | |
| 350 | .module = module, | |
| 351 | }; | |
| 352 | const writer = header.buf.writer(); | |
| 353 | renderFunctionSignature(&ctx, writer, decl) catch |err| { | |
| 354 | if (err == error.AnalysisFail) { | |
| 355 | try module.failed_decls.put(module.gpa, decl, ctx.error_msg); | |
| 356 | } | |
| 357 | return err; | |
| 358 | }; | |
| 359 | try writer.writeAll(";\n"); | |
| 399 | const is_global = dg.functionIsGlobal(tv); | |
| 400 | if (is_global) { | |
| 401 | try writer.writeAll("ZIG_EXTERN_C "); | |
| 402 | } | |
| 403 | try dg.renderFunctionSignature(writer, is_global); | |
| 404 | try dg.fwd_decl.appendSlice(";\n"); | |
| 360 | 405 | }, |
| 361 | 406 | else => {}, |
| 362 | 407 | } |
| 363 | 408 | } |
| 364 | 409 | |
| 365 | const Context = struct { | |
| 366 | decl: *Decl, | |
| 367 | inst_map: *std.AutoHashMap(*Inst, []u8), | |
| 368 | arena: *std.heap.ArenaAllocator, | |
| 369 | argdex: usize = 0, | |
| 370 | unnamed_index: usize = 0, | |
| 371 | error_msg: *Compilation.ErrorMsg = undefined, | |
| 372 | target: std.Target, | |
| 373 | header: *C.Header, | |
| 374 | module: *Module, | |
| 375 | ||
| 376 | fn resolveInst(self: *Context, inst: *Inst) ![]u8 { | |
| 377 | if (inst.value()) |val| { | |
| 378 | var out = std.ArrayList(u8).init(&self.arena.allocator); | |
| 379 | try renderValue(self, out.writer(), inst.ty, val); | |
| 380 | return out.toOwnedSlice(); | |
| 381 | } | |
| 382 | return self.inst_map.get(inst).?; // Instruction does not dominate all uses! | |
| 383 | } | |
| 384 | ||
| 385 | fn name(self: *Context) ![]u8 { | |
| 386 | const val = try std.fmt.allocPrint(&self.arena.allocator, "__temp_{d}", .{self.unnamed_index}); | |
| 387 | self.unnamed_index += 1; | |
| 388 | return val; | |
| 389 | } | |
| 390 | ||
| 391 | fn fail(self: *Context, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } { | |
| 392 | self.error_msg = try Compilation.ErrorMsg.create(self.arena.child_allocator, src, format, args); | |
| 393 | return error.AnalysisFail; | |
| 394 | } | |
| 395 | ||
| 396 | fn deinit(self: *Context) void { | |
| 397 | self.* = undefined; | |
| 398 | } | |
| 399 | }; | |
| 400 | ||
| 401 | fn genAlloc(ctx: *Context, file: *C, alloc: *Inst.NoOp) !?[]u8 { | |
| 402 | const writer = file.main.writer(); | |
| 410 | fn genAlloc(o: *Object, alloc: *Inst.NoOp) !CValue { | |
| 411 | const writer = o.code.writer(); | |
| 403 | 412 | |
| 404 | 413 | // First line: the variable used as data storage. |
| 405 | try indent(file); | |
| 406 | const local_name = try ctx.name(); | |
| 414 | try o.indent(); | |
| 407 | 415 | const elem_type = alloc.base.ty.elemType(); |
| 408 | 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 | 418 | try writer.writeAll(";\n"); |
| 411 | 419 | |
| 412 | // Second line: a pointer to it so that we can refer to it as the allocation. | |
| 413 | // One line for the variable, one line for the pointer to the variable, which we return. | |
| 414 | try indent(file); | |
| 415 | const ptr_local_name = try ctx.name(); | |
| 416 | try renderTypeAndName(ctx, writer, alloc.base.ty, ptr_local_name, .Const); | |
| 417 | try writer.print(" = &{s};\n", .{local_name}); | |
| 418 | ||
| 419 | return ptr_local_name; | |
| 420 | return CValue{ .local_ref = local.local }; | |
| 420 | 421 | } |
| 421 | 422 | |
| 422 | fn genArg(ctx: *Context) !?[]u8 { | |
| 423 | const name = try std.fmt.allocPrint(&ctx.arena.allocator, "arg{d}", .{ctx.argdex}); | |
| 424 | ctx.argdex += 1; | |
| 425 | return name; | |
| 423 | fn genArg(o: *Object) CValue { | |
| 424 | const i = o.next_arg_index; | |
| 425 | o.next_arg_index += 1; | |
| 426 | return .{ .arg = i }; | |
| 426 | 427 | } |
| 427 | 428 | |
| 428 | fn genRetVoid(file: *C) !?[]u8 { | |
| 429 | try indent(file); | |
| 430 | try file.main.writer().print("return;\n", .{}); | |
| 431 | return null; | |
| 429 | fn genRetVoid(o: *Object) !CValue { | |
| 430 | try o.indent(); | |
| 431 | try o.code.writer().print("return;\n", .{}); | |
| 432 | return CValue.none; | |
| 432 | 433 | } |
| 433 | 434 | |
| 434 | fn genLoad(ctx: *Context, file: *C, inst: *Inst.UnOp) !?[]u8 { | |
| 435 | const operand = try ctx.resolveInst(inst.operand); | |
| 436 | const writer = file.main.writer(); | |
| 437 | try indent(file); | |
| 438 | const local_name = try ctx.name(); | |
| 439 | try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); | |
| 440 | try writer.print(" = *{s};\n", .{operand}); | |
| 441 | return local_name; | |
| 435 | fn genLoad(o: *Object, inst: *Inst.UnOp) !CValue { | |
| 436 | const operand = try o.resolveInst(inst.operand); | |
| 437 | const writer = o.code.writer(); | |
| 438 | try o.indent(); | |
| 439 | const local = try o.allocLocal(inst.base.ty, .Const); | |
| 440 | switch (operand) { | |
| 441 | .local_ref => |i| { | |
| 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 { | |
| 445 | try indent(file); | |
| 446 | const writer = file.main.writer(); | |
| 447 | try writer.print("return {s};\n", .{try ctx.resolveInst(inst.operand)}); | |
| 448 | return null; | |
| 456 | fn genRet(o: *Object, inst: *Inst.UnOp) !CValue { | |
| 457 | const operand = try o.resolveInst(inst.operand); | |
| 458 | try o.indent(); | |
| 459 | const writer = o.code.writer(); | |
| 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 | 467 | if (inst.base.isUnused()) |
| 453 | return null; | |
| 454 | try indent(file); | |
| 455 | const writer = file.main.writer(); | |
| 456 | const name = try ctx.name(); | |
| 457 | const from = try ctx.resolveInst(inst.operand); | |
| 468 | return CValue.none; | |
| 469 | ||
| 470 | const from = try o.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 | 475 | try writer.writeAll(" = ("); |
| 461 | try renderType(ctx, writer, inst.base.ty); | |
| 462 | try writer.print("){s};\n", .{from}); | |
| 463 | return name; | |
| 476 | try o.dg.renderType(writer, inst.base.ty); | |
| 477 | try writer.writeAll(")"); | |
| 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 | 484 | // *a = b; |
| 468 | try indent(file); | |
| 469 | const writer = file.main.writer(); | |
| 470 | const dest_ptr_name = try ctx.resolveInst(inst.lhs); | |
| 471 | const src_val_name = try ctx.resolveInst(inst.rhs); | |
| 472 | try writer.print("*{s} = {s};\n", .{ dest_ptr_name, src_val_name }); | |
| 473 | return null; | |
| 485 | const dest_ptr = try o.resolveInst(inst.lhs); | |
| 486 | const src_val = try o.resolveInst(inst.rhs); | |
| 487 | ||
| 488 | try o.indent(); | |
| 489 | const writer = o.code.writer(); | |
| 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 | 510 | if (inst.base.isUnused()) |
| 478 | return null; | |
| 479 | try indent(file); | |
| 480 | const lhs = try ctx.resolveInst(inst.lhs); | |
| 481 | const rhs = try ctx.resolveInst(inst.rhs); | |
| 482 | const writer = file.main.writer(); | |
| 483 | const name = try ctx.name(); | |
| 484 | try renderTypeAndName(ctx, writer, inst.base.ty, name, .Const); | |
| 485 | try writer.print(" = {s} {s} {s};\n", .{ lhs, operator, rhs }); | |
| 486 | return name; | |
| 511 | return CValue.none; | |
| 512 | ||
| 513 | const lhs = try o.resolveInst(inst.lhs); | |
| 514 | const rhs = try o.resolveInst(inst.rhs); | |
| 515 | ||
| 516 | try o.indent(); | |
| 517 | const writer = o.code.writer(); | |
| 518 | const local = try o.allocLocal(inst.base.ty, .Const); | |
| 519 | ||
| 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 { | |
| 490 | try indent(file); | |
| 491 | const writer = file.main.writer(); | |
| 492 | const header = file.header.buf.writer(); | |
| 529 | fn genCall(o: *Object, inst: *Inst.Call) !CValue { | |
| 493 | 530 | if (inst.func.castTag(.constant)) |func_inst| { |
| 494 | 531 | const fn_decl = if (func_inst.val.castTag(.extern_fn)) |extern_fn| |
| 495 | 532 | extern_fn.data |
| ... | ... | @@ -501,23 +538,19 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { |
| 501 | 538 | const fn_ty = fn_decl.typed_value.most_recent.typed_value.ty; |
| 502 | 539 | const ret_ty = fn_ty.fnReturnType(); |
| 503 | 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 | 545 | if (unused_result) { |
| 506 | 546 | if (ret_ty.hasCodeGenBits()) { |
| 507 | 547 | try writer.print("(void)", .{}); |
| 508 | 548 | } |
| 509 | 549 | } else { |
| 510 | const local_name = try ctx.name(); | |
| 511 | try renderTypeAndName(ctx, writer, ret_ty, local_name, .Const); | |
| 550 | result_local = try o.allocLocal(ret_ty, .Const); | |
| 512 | 551 | try writer.writeAll(" = "); |
| 513 | result_name = local_name; | |
| 514 | 552 | } |
| 515 | 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 | 554 | try writer.print("{s}(", .{fn_name}); |
| 522 | 555 | if (inst.args.len != 0) { |
| 523 | 556 | for (inst.args) |arg, i| { |
| ... | ... | @@ -525,87 +558,98 @@ fn genCall(ctx: *Context, file: *C, inst: *Inst.Call) !?[]u8 { |
| 525 | 558 | try writer.writeAll(", "); |
| 526 | 559 | } |
| 527 | 560 | if (arg.value()) |val| { |
| 528 | try renderValue(ctx, writer, arg.ty, val); | |
| 561 | try o.dg.renderValue(writer, arg.ty, val); | |
| 529 | 562 | } else { |
| 530 | const val = try ctx.resolveInst(arg); | |
| 531 | try writer.print("{s}", .{val}); | |
| 563 | const val = try o.resolveInst(arg); | |
| 564 | try o.writeCValue(writer, val); | |
| 532 | 565 | } |
| 533 | 566 | } |
| 534 | 567 | } |
| 535 | 568 | try writer.writeAll(");\n"); |
| 536 | return result_name; | |
| 569 | return result_local; | |
| 537 | 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 | 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 { | |
| 548 | return ctx.fail(ctx.decl.src(), "TODO: C backend: implement blocks", .{}); | |
| 580 | fn genBlock(o: *Object, inst: *Inst.Block) !CValue { | |
| 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 { | |
| 552 | const writer = file.main.writer(); | |
| 553 | try indent(file); | |
| 554 | const local_name = try ctx.name(); | |
| 555 | const operand = try ctx.resolveInst(inst.operand); | |
| 556 | try renderTypeAndName(ctx, writer, inst.base.ty, local_name, .Const); | |
| 584 | fn genBitcast(o: *Object, inst: *Inst.UnOp) !CValue { | |
| 585 | const operand = try o.resolveInst(inst.operand); | |
| 586 | ||
| 587 | const writer = o.code.writer(); | |
| 588 | try o.indent(); | |
| 557 | 589 | if (inst.base.ty.zigTypeTag() == .Pointer and inst.operand.ty.zigTypeTag() == .Pointer) { |
| 590 | const local = try o.allocLocal(inst.base.ty, .Const); | |
| 558 | 591 | try writer.writeAll(" = ("); |
| 559 | try renderType(ctx, writer, inst.base.ty); | |
| 560 | try writer.print("){s};\n", .{operand}); | |
| 561 | } else { | |
| 592 | try o.dg.renderType(writer, inst.base.ty); | |
| 593 | ||
| 594 | try writer.writeAll(")"); | |
| 595 | try o.writeCValue(writer, operand); | |
| 562 | 596 | try writer.writeAll(";\n"); |
| 563 | try indent(file); | |
| 564 | try writer.print("memcpy(&{s}, &{s}, sizeof {s});\n", .{ local_name, operand, local_name }); | |
| 597 | return local; | |
| 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 { | |
| 570 | try indent(file); | |
| 571 | try file.main.writer().writeAll("zig_breakpoint();\n"); | |
| 572 | return null; | |
| 615 | fn genBreakpoint(o: *Object, inst: *Inst.NoOp) !CValue { | |
| 616 | try o.indent(); | |
| 617 | try o.code.writer().writeAll("zig_breakpoint();\n"); | |
| 618 | return CValue.none; | |
| 573 | 619 | } |
| 574 | 620 | |
| 575 | fn genUnreach(file: *C, inst: *Inst.NoOp) !?[]u8 { | |
| 576 | try indent(file); | |
| 577 | try file.main.writer().writeAll("zig_unreachable();\n"); | |
| 578 | return null; | |
| 621 | fn genUnreach(o: *Object, inst: *Inst.NoOp) !CValue { | |
| 622 | try o.indent(); | |
| 623 | try o.code.writer().writeAll("zig_unreachable();\n"); | |
| 624 | return CValue.none; | |
| 579 | 625 | } |
| 580 | 626 | |
| 581 | fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { | |
| 582 | try indent(file); | |
| 583 | const writer = file.main.writer(); | |
| 627 | fn genAsm(o: *Object, as: *Inst.Assembly) !CValue { | |
| 628 | if (as.base.isUnused() and !as.is_volatile) | |
| 629 | return CValue.none; | |
| 630 | ||
| 631 | const writer = o.code.writer(); | |
| 584 | 632 | for (as.inputs) |i, index| { |
| 585 | 633 | if (i[0] == '{' and i[i.len - 1] == '}') { |
| 586 | 634 | const reg = i[1 .. i.len - 1]; |
| 587 | 635 | const arg = as.args[index]; |
| 636 | const arg_c_value = try o.resolveInst(arg); | |
| 637 | try o.indent(); | |
| 588 | 638 | try writer.writeAll("register "); |
| 589 | try renderType(ctx, writer, arg.ty); | |
| 639 | try o.dg.renderType(writer, arg.ty); | |
| 640 | ||
| 590 | 641 | try writer.print(" {s}_constant __asm__(\"{s}\") = ", .{ reg, reg }); |
| 591 | // TODO merge constant handling into inst_map as well | |
| 592 | if (arg.castTag(.constant)) |c| { | |
| 593 | try renderValue(ctx, writer, arg.ty, c.val); | |
| 594 | try writer.writeAll(";\n "); | |
| 595 | } else { | |
| 596 | const gop = try ctx.inst_map.getOrPut(arg); | |
| 597 | if (!gop.found_existing) { | |
| 598 | return ctx.fail(ctx.decl.src(), "Internal error in C backend: asm argument not found in inst_map", .{}); | |
| 599 | } | |
| 600 | try writer.print("{s};\n ", .{gop.entry.value}); | |
| 601 | } | |
| 642 | try o.writeCValue(writer, arg_c_value); | |
| 643 | try writer.writeAll(";\n"); | |
| 602 | 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 }); | |
| 607 | if (as.output) |o| { | |
| 608 | return ctx.fail(ctx.decl.src(), "TODO inline asm output", .{}); | |
| 648 | try o.indent(); | |
| 649 | const volatile_string: []const u8 = if (as.is_volatile) "volatile " else ""; | |
| 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 | 654 | if (as.inputs.len > 0) { |
| 611 | 655 | if (as.output == null) { |
| ... | ... | @@ -619,7 +663,7 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { |
| 619 | 663 | if (index > 0) { |
| 620 | 664 | try writer.writeAll(", "); |
| 621 | 665 | } |
| 622 | try writer.print("\"\"({s}_constant)", .{reg}); | |
| 666 | try writer.print("\"r\"({s}_constant)", .{reg}); | |
| 623 | 667 | } else { |
| 624 | 668 | // This is blocked by the earlier test |
| 625 | 669 | unreachable; |
| ... | ... | @@ -627,5 +671,9 @@ fn genAsm(ctx: *Context, file: *C, as: *Inst.Assembly) !?[]u8 { |
| 627 | 671 | } |
| 628 | 672 | } |
| 629 | 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 | 130 | elf: Elf.TextBlock, |
| 131 | 131 | coff: Coff.TextBlock, |
| 132 | 132 | macho: MachO.TextBlock, |
| 133 | c: void, | |
| 133 | c: C.DeclBlock, | |
| 134 | 134 | wasm: void, |
| 135 | 135 | }; |
| 136 | 136 | |
| ... | ... | @@ -138,7 +138,7 @@ pub const File = struct { |
| 138 | 138 | elf: Elf.SrcFn, |
| 139 | 139 | coff: Coff.SrcFn, |
| 140 | 140 | macho: MachO.SrcFn, |
| 141 | c: void, | |
| 141 | c: C.FnBlock, | |
| 142 | 142 | wasm: ?Wasm.FnData, |
| 143 | 143 | }; |
| 144 | 144 | |
| ... | ... | @@ -301,7 +301,8 @@ pub const File = struct { |
| 301 | 301 | .coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl), |
| 302 | 302 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl), |
| 303 | 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 | 313 | .coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl), |
| 313 | 314 | .elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl), |
| 314 | 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 | 409 | } |
| 408 | 410 | } |
| 409 | 411 | |
| 412 | /// Called when a Decl is deleted from the Module. | |
| 410 | 413 | pub fn freeDecl(base: *File, decl: *Module.Decl) void { |
| 411 | 414 | switch (base.tag) { |
| 412 | 415 | .coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl), |
| 413 | 416 | .elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl), |
| 414 | 417 | .macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl), |
| 415 | .c => unreachable, | |
| 418 | .c => @fieldParentPtr(C, "base", base).freeDecl(decl), | |
| 416 | 419 | .wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl), |
| 417 | 420 | } |
| 418 | 421 | } |
| ... | ... | @@ -432,14 +435,14 @@ pub const File = struct { |
| 432 | 435 | pub fn updateDeclExports( |
| 433 | 436 | base: *File, |
| 434 | 437 | module: *Module, |
| 435 | decl: *const Module.Decl, | |
| 438 | decl: *Module.Decl, | |
| 436 | 439 | exports: []const *Module.Export, |
| 437 | 440 | ) !void { |
| 438 | 441 | switch (base.tag) { |
| 439 | 442 | .coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports), |
| 440 | 443 | .elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports), |
| 441 | 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 | 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 | 8 | const codegen = @import("../codegen/c.zig"); |
| 9 | 9 | const link = @import("../link.zig"); |
| 10 | 10 | const trace = @import("../tracy.zig").trace; |
| 11 | const File = link.File; | |
| 12 | 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 { | |
| 17 | buf: std.ArrayList(u8), | |
| 18 | emit_loc: ?Compilation.EmitLoc, | |
| 16 | base: link.File, | |
| 19 | 17 | |
| 20 | pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header { | |
| 21 | return .{ | |
| 22 | .buf = std.ArrayList(u8).init(allocator), | |
| 23 | .emit_loc = emit_loc, | |
| 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 | } | |
| 18 | /// Per-declaration data. For functions this is the body, and | |
| 19 | /// the forward declaration is stored in the FnBlock. | |
| 20 | pub const DeclBlock = struct { | |
| 21 | code: std.ArrayListUnmanaged(u8), | |
| 36 | 22 | |
| 37 | pub fn deinit(self: *Header) void { | |
| 38 | self.buf.deinit(); | |
| 39 | self.* = undefined; | |
| 40 | } | |
| 23 | pub const empty: DeclBlock = .{ | |
| 24 | .code = .{}, | |
| 25 | }; | |
| 41 | 26 | }; |
| 42 | 27 | |
| 43 | base: File, | |
| 44 | ||
| 45 | header: Header, | |
| 46 | constants: std.ArrayList(u8), | |
| 47 | main: std.ArrayList(u8), | |
| 28 | /// Per-function data. | |
| 29 | pub const FnBlock = struct { | |
| 30 | fwd_decl: std.ArrayListUnmanaged(u8), | |
| 48 | 31 | |
| 49 | called: std.StringHashMap(void), | |
| 50 | error_msg: *Compilation.ErrorMsg = undefined, | |
| 32 | pub const empty: FnBlock = .{ | |
| 33 | .fwd_decl = .{}, | |
| 34 | }; | |
| 35 | }; | |
| 51 | 36 | |
| 52 | 37 | pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C { |
| 53 | 38 | assert(options.object_format == .c); |
| ... | ... | @@ -55,7 +40,11 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio |
| 55 | 40 | if (options.use_llvm) return error.LLVMHasNoCBackend; |
| 56 | 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 | 48 | errdefer file.close(); |
| 60 | 49 | |
| 61 | 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 | 57 | .file = file, |
| 69 | 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 | 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 } { | |
| 81 | self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args); | |
| 82 | return error.AnalysisFail; | |
| 65 | pub fn deinit(self: *C) void { | |
| 66 | const module = self.base.options.module orelse return; | |
| 67 | for (module.decl_table.items()) |entry| { | |
| 68 | self.freeDecl(entry.value); | |
| 69 | } | |
| 83 | 70 | } |
| 84 | 71 | |
| 85 | pub fn deinit(self: *C) void { | |
| 86 | self.main.deinit(); | |
| 87 | self.header.deinit(); | |
| 88 | self.constants.deinit(); | |
| 89 | self.called.deinit(); | |
| 72 | pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {} | |
| 73 | ||
| 74 | pub fn freeDecl(self: *C, decl: *Module.Decl) void { | |
| 75 | decl.link.c.code.deinit(self.base.allocator); | |
| 76 | decl.fn_link.c.fwd_decl.deinit(self.base.allocator); | |
| 90 | 77 | } |
| 91 | 78 | |
| 92 | 79 | pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void { |
| 93 | codegen.generate(self, module, decl) catch |err| { | |
| 94 | if (err == error.AnalysisFail) { | |
| 95 | try module.failed_decls.put(module.gpa, decl, self.error_msg); | |
| 96 | } | |
| 97 | return err; | |
| 80 | const tracy = trace(@src()); | |
| 81 | defer tracy.end(); | |
| 82 | ||
| 83 | const fwd_decl = &decl.fn_link.c.fwd_decl; | |
| 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 | 125 | pub fn flush(self: *C, comp: *Compilation) !void { |
| ... | ... | @@ -106,21 +130,108 @@ pub fn flushModule(self: *C, comp: *Compilation) !void { |
| 106 | 130 | const tracy = trace(@src()); |
| 107 | 131 | defer tracy.end(); |
| 108 | 132 | |
| 109 | const writer = self.base.file.?.writer(); | |
| 110 | try self.header.flush(writer); | |
| 111 | if (self.header.buf.items.len > 0) { | |
| 112 | try writer.writeByte('\n'); | |
| 113 | } | |
| 114 | if (self.constants.items.len > 0) { | |
| 115 | try writer.print("{s}\n", .{self.constants.items}); | |
| 133 | const module = self.base.options.module.?; | |
| 134 | ||
| 135 | // This code path happens exclusively with -ofmt=c. The flush logic for | |
| 136 | // emit-h is in `flushEmitH` below. | |
| 137 | ||
| 138 | // We collect a list of buffers to write, and write them all at once with pwritev 😎 | |
| 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) { | |
| 118 | const last_two = self.main.items[self.main.items.len - 2 ..]; | |
| 119 | if (std.mem.eql(u8, last_two, "\n\n")) { | |
| 120 | self.main.items.len -= 1; | |
| 171 | ||
| 172 | // Now the function bodies. | |
| 173 | try all_buffers.ensureCapacity(all_buffers.items.len + fn_count); | |
| 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); | |
| 124 | self.base.file.?.close(); | |
| 125 | self.base.file = null; | |
| 186 | ||
| 187 | const file = self.base.file.?; | |
| 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 | 13 | const ThreadPool = @import("ThreadPool.zig"); |
| 14 | 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 | 18 | test "self-hosted" { |
| 19 | 19 | var ctx = TestContext.init(); |
| ... | ... | @@ -324,11 +324,11 @@ pub const TestContext = struct { |
| 324 | 324 | } |
| 325 | 325 | |
| 326 | 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 | 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 | 334 | pub fn addCompareOutput( |
| ... | ... | @@ -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| { | |
| 704 | std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ | |
| 705 | c_file.main.items, | |
| 706 | }); | |
| 707 | } | |
| 703 | // TODO print generated C code | |
| 704 | //if (comp.bin_file.cast(link.File.C)) |c_file| { | |
| 705 | // std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{ | |
| 706 | // c_file.main.items, | |
| 707 | // }); | |
| 708 | //} | |
| 708 | 709 | std.debug.print("Test failed.\n", .{}); |
| 709 | 710 | std.process.exit(1); |
| 710 | 711 | } |
test/stage2/cbe.zig+114-237| ... | ... | @@ -22,15 +22,107 @@ pub fn addCases(ctx: *TestContext) !void { |
| 22 | 22 | , "hello world!" ++ std.cstr.line_sep); |
| 23 | 23 | |
| 24 | 24 | // Now change the message only |
| 25 | // TODO fix C backend not supporting updates | |
| 26 | // https://github.com/ziglang/zig/issues/7589 | |
| 27 | //case.addCompareOutput( | |
| 28 | // \\extern fn puts(s: [*:0]const u8) c_int; | |
| 29 | // \\export fn main() c_int { | |
| 30 | // \\ _ = puts("yo"); | |
| 31 | // \\ return 0; | |
| 32 | // \\} | |
| 33 | //, "yo" ++ std.cstr.line_sep); | |
| 25 | case.addCompareOutput( | |
| 26 | \\extern fn puts(s: [*:0]const u8) c_int; | |
| 27 | \\export fn main() c_int { | |
| 28 | \\ _ = puts("yo"); | |
| 29 | \\ return 0; | |
| 30 | \\} | |
| 31 | , "yo" ++ std.cstr.line_sep); | |
| 32 | } | |
| 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 | 180 | \\ unreachable; |
| 89 | 181 | \\} |
| 90 | 182 | , |
| 183 | \\ZIG_EXTERN_C zig_noreturn void _start(void); | |
| 184 | \\ | |
| 91 | 185 | \\zig_noreturn void _start(void) { |
| 92 | 186 | \\ zig_breakpoint(); |
| 93 | 187 | \\ zig_unreachable(); |
| ... | ... | @@ -97,254 +191,37 @@ pub fn addCases(ctx: *TestContext) !void { |
| 97 | 191 | ctx.h("simple header", linux_x64, |
| 98 | 192 | \\export fn start() void{} |
| 99 | 193 | , |
| 100 | \\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 | \\} | |
| 194 | \\ZIG_EXTERN_C void start(void); | |
| 318 | 195 | \\ |
| 319 | 196 | ); |
| 320 | 197 | ctx.h("header with single param function", linux_x64, |
| 321 | 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 | 203 | ctx.h("header with multiple param function", linux_x64, |
| 327 | 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 | 209 | ctx.h("header with u32 param function", linux_x64, |
| 333 | 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 | 215 | ctx.h("header with usize param function", linux_x64, |
| 339 | 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 | 221 | ctx.h("header with bool param function", linux_x64, |
| 345 | 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 | 227 | ctx.h("header with noreturn function", linux_x64, |
| ... | ... | @@ -352,7 +229,7 @@ pub fn addCases(ctx: *TestContext) !void { |
| 352 | 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 | 235 | ctx.h("header with multiple functions", linux_x64, |
| ... | ... | @@ -360,15 +237,15 @@ pub fn addCases(ctx: *TestContext) !void { |
| 360 | 237 | \\export fn b() void{} |
| 361 | 238 | \\export fn c() void{} |
| 362 | 239 | , |
| 363 | \\void a(void); | |
| 364 | \\void b(void); | |
| 365 | \\void c(void); | |
| 240 | \\ZIG_EXTERN_C void a(void); | |
| 241 | \\ZIG_EXTERN_C void b(void); | |
| 242 | \\ZIG_EXTERN_C void c(void); | |
| 366 | 243 | \\ |
| 367 | 244 | ); |
| 368 | 245 | ctx.h("header with multiple includes", linux_x64, |
| 369 | 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 | } |