authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-08 20:46:06-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-08 20:46:06-07:00
log7bd0500589f02a6f2ba75525d803b2c1d7409ebe
tree63c26680b4ce918a718192ac540772e3152b9fe0
parent8e425c0c8d78acc64a4223a35010df478d5b7e16
parent0e1c7209e8632ebf398e60de9053e2e0fe8b5661

Merge remote-tracking branch 'origin/master' into register-allocation


17 files changed, 1883 insertions(+), 1218 deletions(-)

ci/azure/linux_script+5-1
...@@ -12,7 +12,7 @@ sudo apt-get update -q...@@ -12,7 +12,7 @@ sudo apt-get update -q
1212
13sudo apt-get remove -y llvm-*13sudo apt-get remove -y llvm-*
14sudo rm -rf /usr/local/*14sudo rm -rf /usr/local/*
15sudo apt-get install -y libxml2-dev libclang-10-dev llvm-10 llvm-10-dev liblld-10-dev cmake s3cmd gcc-7 g++-7 ninja-build15sudo apt-get install -y libxml2-dev libclang-10-dev llvm-10 llvm-10-dev liblld-10-dev cmake s3cmd gcc-7 g++-7 ninja-build tidy
1616
17QEMUBASE="qemu-linux-x86_64-5.0.0-49ee115552"17QEMUBASE="qemu-linux-x86_64-5.0.0-49ee115552"
18wget https://ziglang.org/deps/$QEMUBASE.tar.xz18wget https://ziglang.org/deps/$QEMUBASE.tar.xz
...@@ -51,6 +51,10 @@ cd build...@@ -51,6 +51,10 @@ cd build
51cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja51cmake .. -DCMAKE_BUILD_TYPE=Release -GNinja
52ninja install52ninja install
53./zig build test -Denable-qemu -Denable-wasmtime53./zig build test -Denable-qemu -Denable-wasmtime
54
55# look for HTML errors
56tidy -qe ../zig-cache/langref.html
57
54VERSION="$(./zig version)"58VERSION="$(./zig version)"
5559
56if [ "${BUILD_REASON}" != "PullRequest" ]; then60if [ "${BUILD_REASON}" != "PullRequest" ]; then
doc/langref.html.in+47-4
...@@ -97,7 +97,7 @@...@@ -97,7 +97,7 @@
97 margin: auto;97 margin: auto;
98 }98 }
9999
100 #index {100 #toc {
101 padding: 0 1em;101 padding: 0 1em;
102 }102 }
103103
...@@ -105,7 +105,7 @@...@@ -105,7 +105,7 @@
105 #main-wrapper {105 #main-wrapper {
106 flex-direction: row;106 flex-direction: row;
107 }107 }
108 #contents-wrapper, #index {108 #contents-wrapper, #toc {
109 overflow: auto;109 overflow: auto;
110 }110 }
111 }111 }
...@@ -181,7 +181,7 @@...@@ -181,7 +181,7 @@
181 </head>181 </head>
182 <body>182 <body>
183 <div id="main-wrapper">183 <div id="main-wrapper">
184 <div id="index">184 <div id="toc">
185 <a href="https://ziglang.org/documentation/0.1.1/">0.1.1</a> |185 <a href="https://ziglang.org/documentation/0.1.1/">0.1.1</a> |
186 <a href="https://ziglang.org/documentation/0.2.0/">0.2.0</a> |186 <a href="https://ziglang.org/documentation/0.2.0/">0.2.0</a> |
187 <a href="https://ziglang.org/documentation/0.3.0/">0.3.0</a> |187 <a href="https://ziglang.org/documentation/0.3.0/">0.3.0</a> |
...@@ -189,7 +189,7 @@...@@ -189,7 +189,7 @@
189 <a href="https://ziglang.org/documentation/0.5.0/">0.5.0</a> |189 <a href="https://ziglang.org/documentation/0.5.0/">0.5.0</a> |
190 <a href="https://ziglang.org/documentation/0.6.0/">0.6.0</a> |190 <a href="https://ziglang.org/documentation/0.6.0/">0.6.0</a> |
191 master191 master
192 <h1>Index</h1>192 <h1>Contents</h1>
193 {#nav#}193 {#nav#}
194 </div>194 </div>
195 <div id="contents-wrapper"><div id="contents">195 <div id="contents-wrapper"><div id="contents">
...@@ -3861,6 +3861,48 @@ test "if error union" {...@@ -3861,6 +3861,48 @@ test "if error union" {
3861 unreachable;3861 unreachable;
3862 }3862 }
3863}3863}
3864
3865test "if error union with optional" {
3866 // If expressions test for errors before unwrapping optionals.
3867 // The |optional_value| capture's type is ?u32.
3868
3869 const a: anyerror!?u32 = 0;
3870 if (a) |optional_value| {
3871 assert(optional_value.? == 0);
3872 } else |err| {
3873 unreachable;
3874 }
3875
3876 const b: anyerror!?u32 = null;
3877 if (b) |optional_value| {
3878 assert(optional_value == null);
3879 } else |err| {
3880 unreachable;
3881 }
3882
3883 const c: anyerror!?u32 = error.BadValue;
3884 if (c) |optional_value| {
3885 unreachable;
3886 } else |err| {
3887 assert(err == error.BadValue);
3888 }
3889
3890 // Access the value by reference by using a pointer capture each time.
3891 var d: anyerror!?u32 = 3;
3892 if (d) |*optional_value| {
3893 if (optional_value.*) |*value| {
3894 value.* = 9;
3895 }
3896 } else |err| {
3897 unreachable;
3898 }
3899
3900 if (d) |optional_value| {
3901 assert(optional_value.? == 9);
3902 } else |err| {
3903 unreachable;
3904 }
3905}
3864 {#code_end#}3906 {#code_end#}
3865 {#see_also|Optionals|Errors#}3907 {#see_also|Optionals|Errors#}
3866 {#header_close#}3908 {#header_close#}
...@@ -8393,6 +8435,7 @@ fn foo(comptime T: type, ptr: *T) T {...@@ -8393,6 +8435,7 @@ fn foo(comptime T: type, ptr: *T) T {
8393 {#header_close#}8435 {#header_close#}
83948436
8395 {#header_open|Opaque Types#}8437 {#header_open|Opaque Types#}
8438 <p>
8396 {#syntax#}@Type(.Opaque){#endsyntax#} creates a new type with an unknown (but non-zero) size and alignment.8439 {#syntax#}@Type(.Opaque){#endsyntax#} creates a new type with an unknown (but non-zero) size and alignment.
8397 </p>8440 </p>
8398 <p>8441 <p>
lib/std/fs.zig+2
...@@ -453,6 +453,8 @@ pub const Dir = struct {...@@ -453,6 +453,8 @@ pub const Dir = struct {
453453
454 pub const Error = IteratorError;454 pub const Error = IteratorError;
455455
456 /// Memory such as file names referenced in this returned entry becomes invalid
457 /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized.
456 pub fn next(self: *Self) Error!?Entry {458 pub fn next(self: *Self) Error!?Entry {
457 start_over: while (true) {459 start_over: while (true) {
458 const w = os.windows;460 const w = os.windows;
lib/std/fs/test.zig+44-1
...@@ -3,10 +3,53 @@ const testing = std.testing;...@@ -3,10 +3,53 @@ const testing = std.testing;
3const builtin = std.builtin;3const builtin = std.builtin;
4const fs = std.fs;4const fs = std.fs;
5const mem = std.mem;5const mem = std.mem;
6const wasi = std.os.wasi;
67
8const ArenaAllocator = std.heap.ArenaAllocator;
9const Dir = std.fs.Dir;
7const File = std.fs.File;10const File = std.fs.File;
8const tmpDir = testing.tmpDir;11const tmpDir = testing.tmpDir;
912
13test "Dir.Iterator" {
14 var tmp_dir = tmpDir(.{ .iterate = true });
15 defer tmp_dir.cleanup();
16
17 // First, create a couple of entries to iterate over.
18 const file = try tmp_dir.dir.createFile("some_file", .{});
19 file.close();
20
21 try tmp_dir.dir.makeDir("some_dir");
22
23 var arena = ArenaAllocator.init(testing.allocator);
24 defer arena.deinit();
25
26 var entries = std.ArrayList(Dir.Entry).init(&arena.allocator);
27
28 // Create iterator.
29 var iter = tmp_dir.dir.iterate();
30 while (try iter.next()) |entry| {
31 // We cannot just store `entry` as on Windows, we're re-using the name buffer
32 // which means we'll actually share the `name` pointer between entries!
33 const name = try arena.allocator.dupe(u8, entry.name);
34 try entries.append(Dir.Entry{ .name = name, .kind = entry.kind });
35 }
36
37 testing.expect(entries.items.len == 2); // note that the Iterator skips '.' and '..'
38 testing.expect(contains(&entries, Dir.Entry{ .name = "some_file", .kind = Dir.Entry.Kind.File }));
39 testing.expect(contains(&entries, Dir.Entry{ .name = "some_dir", .kind = Dir.Entry.Kind.Directory }));
40}
41
42fn entry_eql(lhs: Dir.Entry, rhs: Dir.Entry) bool {
43 return mem.eql(u8, lhs.name, rhs.name) and lhs.kind == rhs.kind;
44}
45
46fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool {
47 for (entries.items) |entry| {
48 if (entry_eql(entry, el)) return true;
49 }
50 return false;
51}
52
10test "readAllAlloc" {53test "readAllAlloc" {
11 var tmp_dir = tmpDir(.{});54 var tmp_dir = tmpDir(.{});
12 defer tmp_dir.cleanup();55 defer tmp_dir.cleanup();
...@@ -237,7 +280,7 @@ test "fs.copyFile" {...@@ -237,7 +280,7 @@ test "fs.copyFile" {
237 try expectFileContents(tmp.dir, dest_file2, data);280 try expectFileContents(tmp.dir, dest_file2, data);
238}281}
239282
240fn expectFileContents(dir: fs.Dir, file_path: []const u8, data: []const u8) !void {283fn expectFileContents(dir: Dir, file_path: []const u8, data: []const u8) !void {
241 const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000);284 const contents = try dir.readFileAlloc(testing.allocator, file_path, 1000);
242 defer testing.allocator.free(contents);285 defer testing.allocator.free(contents);
243286
lib/std/hash_map.zig+26-6
...@@ -533,12 +533,13 @@ pub fn HashMapUnmanaged(...@@ -533,12 +533,13 @@ pub fn HashMapUnmanaged(
533 }533 }
534534
535 pub fn clone(self: Self, allocator: *Allocator) !Self {535 pub fn clone(self: Self, allocator: *Allocator) !Self {
536 // TODO this can be made more efficient by directly allocating536 var other: Self = .{};
537 // the memory slices and memcpying the elements.537 try other.entries.appendSlice(allocator, self.entries.items);
538 var other = Self.init();538
539 try other.initCapacity(allocator, self.entries.len);539 if (self.index_header) |header| {
540 for (self.entries.items) |entry| {540 const new_header = try IndexHeader.alloc(allocator, header.indexes_len);
541 other.putAssumeCapacityNoClobber(entry.key, entry.value);541 other.insertAllEntriesIntoNewHeader(new_header);
542 other.index_header = new_header;
542 }543 }
543 return other;544 return other;
544 }545 }
...@@ -980,6 +981,25 @@ test "ensure capacity" {...@@ -980,6 +981,25 @@ test "ensure capacity" {
980 testing.expect(initial_capacity == map.capacity());981 testing.expect(initial_capacity == map.capacity());
981}982}
982983
984test "clone" {
985 var original = AutoHashMap(i32, i32).init(std.testing.allocator);
986 defer original.deinit();
987
988 // put more than `linear_scan_max` so we can test that the index header is properly cloned
989 var i: u8 = 0;
990 while (i < 10) : (i += 1) {
991 try original.putNoClobber(i, i * 10);
992 }
993
994 var copy = try original.clone();
995 defer copy.deinit();
996
997 i = 0;
998 while (i < 10) : (i += 1) {
999 testing.expect(copy.get(i).? == i * 10);
1000 }
1001}
1002
983pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {1003pub fn getHashPtrAddrFn(comptime K: type) (fn (K) u32) {
984 return struct {1004 return struct {
985 fn hash(key: K) u32 {1005 fn hash(key: K) u32 {
src-self-hosted/Module.zig+16-13
...@@ -27,7 +27,7 @@ root_pkg: *Package,...@@ -27,7 +27,7 @@ root_pkg: *Package,
27/// Module owns this resource.27/// Module owns this resource.
28/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`.28/// The `Scope` is either a `Scope.ZIRModule` or `Scope.File`.
29root_scope: *Scope,29root_scope: *Scope,
30bin_file: link.ElfFile,30bin_file: *link.File,
31bin_file_dir: std.fs.Dir,31bin_file_dir: std.fs.Dir,
32bin_file_path: []const u8,32bin_file_path: []const u8,
33/// It's rare for a decl to be exported, so we save memory by having a sparse map of33/// It's rare for a decl to be exported, so we save memory by having a sparse map of
...@@ -46,7 +46,7 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},...@@ -46,7 +46,7 @@ export_owners: std.AutoHashMapUnmanaged(*Decl, []*Export) = .{},
46decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},46decl_table: std.HashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_hash, Scope.name_hash_eql, false) = .{},
4747
48optimize_mode: std.builtin.Mode,48optimize_mode: std.builtin.Mode,
49link_error_flags: link.ElfFile.ErrorFlags = .{},49link_error_flags: link.File.ErrorFlags = .{},
5050
51work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),51work_queue: std.fifo.LinearFifo(WorkItem, .Dynamic),
5252
...@@ -90,7 +90,7 @@ pub const Export = struct {...@@ -90,7 +90,7 @@ pub const Export = struct {
90 /// Byte offset into the file that contains the export directive.90 /// Byte offset into the file that contains the export directive.
91 src: usize,91 src: usize,
92 /// Represents the position of the export, if any, in the output file.92 /// Represents the position of the export, if any, in the output file.
93 link: link.ElfFile.Export,93 link: link.File.Elf.Export,
94 /// The Decl that performs the export. Note that this is *not* the Decl being exported.94 /// The Decl that performs the export. Note that this is *not* the Decl being exported.
95 owner_decl: *Decl,95 owner_decl: *Decl,
96 /// The Decl being exported. Note this is *not* the Decl performing the export.96 /// The Decl being exported. Note this is *not* the Decl performing the export.
...@@ -168,7 +168,7 @@ pub const Decl = struct {...@@ -168,7 +168,7 @@ pub const Decl = struct {
168168
169 /// Represents the position of the code in the output file.169 /// Represents the position of the code in the output file.
170 /// This is populated regardless of semantic analysis and code generation.170 /// This is populated regardless of semantic analysis and code generation.
171 link: link.ElfFile.TextBlock = link.ElfFile.TextBlock.empty,171 link: link.File.Elf.TextBlock = link.File.Elf.TextBlock.empty,
172172
173 contents_hash: std.zig.SrcHash,173 contents_hash: std.zig.SrcHash,
174174
...@@ -723,17 +723,19 @@ pub const InitOptions = struct {...@@ -723,17 +723,19 @@ pub const InitOptions = struct {
723 object_format: ?std.builtin.ObjectFormat = null,723 object_format: ?std.builtin.ObjectFormat = null,
724 optimize_mode: std.builtin.Mode = .Debug,724 optimize_mode: std.builtin.Mode = .Debug,
725 keep_source_files_loaded: bool = false,725 keep_source_files_loaded: bool = false,
726 cbe: bool = false,
726};727};
727728
728pub fn init(gpa: *Allocator, options: InitOptions) !Module {729pub fn init(gpa: *Allocator, options: InitOptions) !Module {
729 const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();730 const bin_file_dir = options.bin_file_dir orelse std.fs.cwd();
730 var bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{731 const bin_file = try link.openBinFilePath(gpa, bin_file_dir, options.bin_file_path, .{
731 .target = options.target,732 .target = options.target,
732 .output_mode = options.output_mode,733 .output_mode = options.output_mode,
733 .link_mode = options.link_mode orelse .Static,734 .link_mode = options.link_mode orelse .Static,
734 .object_format = options.object_format orelse options.target.getObjectFormat(),735 .object_format = options.object_format orelse options.target.getObjectFormat(),
736 .cbe = options.cbe,
735 });737 });
736 errdefer bin_file.deinit();738 errdefer bin_file.destroy();
737739
738 const root_scope = blk: {740 const root_scope = blk: {
739 if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zig")) {741 if (mem.endsWith(u8, options.root_pkg.root_src_path, ".zig")) {
...@@ -776,7 +778,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {...@@ -776,7 +778,7 @@ pub fn init(gpa: *Allocator, options: InitOptions) !Module {
776}778}
777779
778pub fn deinit(self: *Module) void {780pub fn deinit(self: *Module) void {
779 self.bin_file.deinit();781 self.bin_file.destroy();
780 const gpa = self.gpa;782 const gpa = self.gpa;
781 self.deletion_set.deinit(gpa);783 self.deletion_set.deinit(gpa);
782 self.work_queue.deinit();784 self.work_queue.deinit();
...@@ -825,7 +827,7 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void {...@@ -825,7 +827,7 @@ fn freeExportList(gpa: *Allocator, export_list: []*Export) void {
825}827}
826828
827pub fn target(self: Module) std.Target {829pub fn target(self: Module) std.Target {
828 return self.bin_file.options.target;830 return self.bin_file.options().target;
829}831}
830832
831/// Detect changes to source files, perform semantic analysis, and update the output files.833/// Detect changes to source files, perform semantic analysis, and update the output files.
...@@ -872,7 +874,7 @@ pub fn update(self: *Module) !void {...@@ -872,7 +874,7 @@ pub fn update(self: *Module) !void {
872 try self.bin_file.flush();874 try self.bin_file.flush();
873 }875 }
874876
875 self.link_error_flags = self.bin_file.error_flags;877 self.link_error_flags = self.bin_file.errorFlags();
876 std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags});878 std.log.debug(.module, "link_error_flags: {}\n", .{self.link_error_flags});
877879
878 // If there are any errors, we anticipate the source files being loaded880 // If there are any errors, we anticipate the source files being loaded
...@@ -1985,8 +1987,9 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {...@@ -1985,8 +1987,9 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void {
1985 self.decl_exports.removeAssertDiscard(exp.exported_decl);1987 self.decl_exports.removeAssertDiscard(exp.exported_decl);
1986 }1988 }
1987 }1989 }
19881990 if (self.bin_file.cast(link.File.Elf)) |elf| {
1989 self.bin_file.deleteExport(exp.link);1991 elf.deleteExport(exp.link);
1992 }
1990 if (self.failed_exports.remove(exp)) |entry| {1993 if (self.failed_exports.remove(exp)) |entry| {
1991 entry.value.destroy(self.gpa);1994 entry.value.destroy(self.gpa);
1992 }1995 }
...@@ -2048,7 +2051,7 @@ fn allocateNewDecl(...@@ -2048,7 +2051,7 @@ fn allocateNewDecl(
2048 .analysis = .unreferenced,2051 .analysis = .unreferenced,
2049 .deletion_flag = false,2052 .deletion_flag = false,
2050 .contents_hash = contents_hash,2053 .contents_hash = contents_hash,
2051 .link = link.ElfFile.TextBlock.empty,2054 .link = link.File.Elf.TextBlock.empty,
2052 .generation = 0,2055 .generation = 0,
2053 };2056 };
2054 return new_decl;2057 return new_decl;
...@@ -2559,7 +2562,7 @@ fn createAnonymousDecl(...@@ -2559,7 +2562,7 @@ fn createAnonymousDecl(
2559) !*Decl {2562) !*Decl {
2560 const name_index = self.getNextAnonNameIndex();2563 const name_index = self.getNextAnonNameIndex();
2561 const scope_decl = scope.decl().?;2564 const scope_decl = scope.decl().?;
2562 const name = try std.fmt.allocPrint(self.gpa, "{}${}", .{ scope_decl.name, name_index });2565 const name = try std.fmt.allocPrint(self.gpa, "{}__anon_{}", .{ scope_decl.name, name_index });
2563 defer self.gpa.free(name);2566 defer self.gpa.free(name);
2564 const name_hash = scope.namespace().fullyQualifiedNameHash(name);2567 const name_hash = scope.namespace().fullyQualifiedNameHash(name);
2565 const src_hash: std.zig.SrcHash = undefined;2568 const src_hash: std.zig.SrcHash = undefined;
src-self-hosted/cbe.h created+8
...@@ -0,0 +1,8 @@
1#if __STDC_VERSION__ >= 201112L
2#define noreturn _Noreturn
3#elif __GNUC__ && !__STRICT_ANSI__
4#define noreturn __attribute__ ((noreturn))
5#else
6#define noreturn
7#endif
8
src-self-hosted/cgen.zig created+168
...@@ -0,0 +1,168 @@
1const link = @import("link.zig");
2const Module = @import("Module.zig");
3const ir = @import("ir.zig");
4const Value = @import("value.zig").Value;
5const Type = @import("type.zig").Type;
6const std = @import("std");
7
8const C = link.File.C;
9const Decl = Module.Decl;
10const mem = std.mem;
11
12/// Maps a name from Zig source to C. This will always give the same output for
13/// any given input.
14fn map(allocator: *std.mem.Allocator, name: []const u8) ![]const u8 {
15 return allocator.dupe(u8, name);
16}
17
18fn renderType(file: *C, writer: std.ArrayList(u8).Writer, T: Type, src: usize) !void {
19 if (T.tag() == .usize) {
20 file.need_stddef = true;
21 try writer.writeAll("size_t");
22 } else {
23 switch (T.zigTypeTag()) {
24 .NoReturn => {
25 file.need_noreturn = true;
26 try writer.writeAll("noreturn void");
27 },
28 .Void => try writer.writeAll("void"),
29 else => |e| return file.fail(src, "TODO implement type {}", .{e}),
30 }
31 }
32}
33
34fn renderFunctionSignature(file: *C, writer: std.ArrayList(u8).Writer, decl: *Decl) !void {
35 const tv = decl.typed_value.most_recent.typed_value;
36 try renderType(file, writer, tv.ty.fnReturnType(), decl.src());
37 const name = try map(file.allocator, mem.spanZ(decl.name));
38 defer file.allocator.free(name);
39 try writer.print(" {}(", .{name});
40 if (tv.ty.fnParamLen() == 0) {
41 try writer.writeAll("void)");
42 } else {
43 return file.fail(decl.src(), "TODO implement parameters", .{});
44 }
45}
46
47pub fn generate(file: *C, decl: *Decl) !void {
48 const writer = file.main.writer();
49 const header = file.header.writer();
50 const tv = decl.typed_value.most_recent.typed_value;
51 switch (tv.ty.zigTypeTag()) {
52 .Fn => {
53 try renderFunctionSignature(file, writer, decl);
54
55 try writer.writeAll(" {");
56
57 const func: *Module.Fn = tv.val.cast(Value.Payload.Function).?.func;
58 const instructions = func.analysis.success.instructions;
59 if (instructions.len > 0) {
60 for (instructions) |inst| {
61 try writer.writeAll("\n\t");
62 switch (inst.tag) {
63 .assembly => {
64 const as = inst.cast(ir.Inst.Assembly).?.args;
65 for (as.inputs) |i, index| {
66 if (i[0] == '{' and i[i.len - 1] == '}') {
67 const reg = i[1 .. i.len - 1];
68 const arg = as.args[index];
69 if (arg.cast(ir.Inst.Constant)) |c| {
70 if (c.val.tag() == .int_u64) {
71 try writer.writeAll("register ");
72 try renderType(file, writer, arg.ty, decl.src());
73 try writer.print(" {}_constant __asm__(\"{}\") = {};\n\t", .{ reg, reg, c.val.toUnsignedInt() });
74 } else {
75 return file.fail(decl.src(), "TODO inline asm {} args", .{c.val.tag()});
76 }
77 } else {
78 return file.fail(decl.src(), "TODO non-constant inline asm args", .{});
79 }
80 } else {
81 return file.fail(decl.src(), "TODO non-explicit inline asm regs", .{});
82 }
83 }
84 try writer.print("__asm {} (\"{}\"", .{ if (as.is_volatile) @as([]const u8, "volatile") else "", as.asm_source });
85 if (as.output) |o| {
86 return file.fail(decl.src(), "TODO inline asm output", .{});
87 }
88 if (as.inputs.len > 0) {
89 if (as.output == null) {
90 try writer.writeAll(" :");
91 }
92 try writer.writeAll(": ");
93 for (as.inputs) |i, index| {
94 if (i[0] == '{' and i[i.len - 1] == '}') {
95 const reg = i[1 .. i.len - 1];
96 const arg = as.args[index];
97 if (index > 0) {
98 try writer.writeAll(", ");
99 }
100 if (arg.cast(ir.Inst.Constant)) |c| {
101 try writer.print("\"\"({}_constant)", .{reg});
102 } else {
103 // This is blocked by the earlier test
104 unreachable;
105 }
106 } else {
107 // This is blocked by the earlier test
108 unreachable;
109 }
110 }
111 }
112 try writer.writeAll(");");
113 },
114 .call => {
115 const call = inst.cast(ir.Inst.Call).?.args;
116 if (call.func.cast(ir.Inst.Constant)) |func_inst| {
117 if (func_inst.val.cast(Value.Payload.Function)) |func_val| {
118 const target = func_val.func.owner_decl;
119 const tname = mem.spanZ(target.name);
120 if (file.called.get(tname) == null) {
121 try file.called.put(tname, void{});
122 try renderFunctionSignature(file, header, target);
123 try header.writeAll(";\n");
124 }
125 try writer.print("{}();", .{tname});
126 } else {
127 return file.fail(decl.src(), "TODO non-function call target?", .{});
128 }
129 if (call.args.len != 0) {
130 return file.fail(decl.src(), "TODO function arguments", .{});
131 }
132 } else {
133 return file.fail(decl.src(), "TODO non-constant call inst?", .{});
134 }
135 },
136 else => |e| {
137 return file.fail(decl.src(), "TODO {}", .{e});
138 },
139 }
140 }
141 try writer.writeAll("\n");
142 }
143
144 try writer.writeAll("}\n\n");
145 },
146 .Array => {
147 // TODO: prevent inline asm constants from being emitted
148 const name = try map(file.allocator, mem.span(decl.name));
149 defer file.allocator.free(name);
150 if (tv.val.cast(Value.Payload.Bytes)) |payload| {
151 if (tv.ty.arraySentinel()) |sentinel| {
152 if (sentinel.toUnsignedInt() == 0) {
153 try file.constants.writer().print("const char *const {} = \"{}\";\n", .{ name, payload.data });
154 } else {
155 return file.fail(decl.src(), "TODO byte arrays with non-zero sentinels", .{});
156 }
157 } else {
158 return file.fail(decl.src(), "TODO byte arrays without sentinels", .{});
159 }
160 } else {
161 return file.fail(decl.src(), "TODO non-byte arrays", .{});
162 }
163 },
164 else => |e| {
165 return file.fail(decl.src(), "TODO {}", .{e});
166 },
167 }
168}
src-self-hosted/codegen.zig+2-2
...@@ -33,7 +33,7 @@ pub const Result = union(enum) {...@@ -33,7 +33,7 @@ pub const Result = union(enum) {
33};33};
3434
35pub fn generateSymbol(35pub fn generateSymbol(
36 bin_file: *link.ElfFile,36 bin_file: *link.File.Elf,
37 src: usize,37 src: usize,
38 typed_value: TypedValue,38 typed_value: TypedValue,
39 code: *std.ArrayList(u8),39 code: *std.ArrayList(u8),
...@@ -237,7 +237,7 @@ const InnerError = error {...@@ -237,7 +237,7 @@ const InnerError = error {
237237
238const Function = struct {238const Function = struct {
239 gpa: *Allocator,239 gpa: *Allocator,
240 bin_file: *link.ElfFile,240 bin_file: *link.File.Elf,
241 target: *const std.Target,241 target: *const std.Target,
242 mod_fn: *const Module.Fn,242 mod_fn: *const Module.Fn,
243 code: *std.ArrayList(u8),243 code: *std.ArrayList(u8),
src-self-hosted/link.zig+1318-1107
...@@ -7,6 +7,7 @@ const Module = @import("Module.zig");...@@ -7,6 +7,7 @@ const Module = @import("Module.zig");
7const fs = std.fs;7const fs = std.fs;
8const elf = std.elf;8const elf = std.elf;
9const codegen = @import("codegen.zig");9const codegen = @import("codegen.zig");
10const cgen = @import("cgen.zig");
1011
11const default_entry_addr = 0x8000000;12const default_entry_addr = 0x8000000;
1213
...@@ -21,6 +22,7 @@ pub const Options = struct {...@@ -21,6 +22,7 @@ pub const Options = struct {
21 /// Used for calculating how much space to reserve for executable program code in case22 /// Used for calculating how much space to reserve for executable program code in case
22 /// the binary file deos not already have such a section.23 /// the binary file deos not already have such a section.
23 program_code_size_hint: u64 = 256 * 1024,24 program_code_size_hint: u64 = 256 * 1024,
25 cbe: bool = false,
24};26};
2527
26/// Attempts incremental linking, if the file already exists.28/// Attempts incremental linking, if the file already exists.
...@@ -32,13 +34,22 @@ pub fn openBinFilePath(...@@ -32,13 +34,22 @@ pub fn openBinFilePath(
32 dir: fs.Dir,34 dir: fs.Dir,
33 sub_path: []const u8,35 sub_path: []const u8,
34 options: Options,36 options: Options,
35) !ElfFile {37) !*File {
36 const file = try dir.createFile(sub_path, .{ .truncate = false, .read = true, .mode = determineMode(options) });38 const file = try dir.createFile(sub_path, .{ .truncate = options.cbe, .read = true, .mode = determineMode(options) });
37 errdefer file.close();39 errdefer file.close();
3840
39 var bin_file = try openBinFile(allocator, file, options);41 if (options.cbe) {
40 bin_file.owns_file_handle = true;42 var bin_file = try allocator.create(File.C);
41 return bin_file;43 errdefer allocator.destroy(bin_file);
44 bin_file.* = try openCFile(allocator, file, options);
45 return &bin_file.base;
46 } else {
47 var bin_file = try allocator.create(File.Elf);
48 errdefer allocator.destroy(bin_file);
49 bin_file.* = try openBinFile(allocator, file, options);
50 bin_file.owns_file_handle = true;
51 return &bin_file.base;
52 }
42}53}
4354
44/// Atomically overwrites the old file, if present.55/// Atomically overwrites the old file, if present.
...@@ -75,12 +86,24 @@ pub fn writeFilePath(...@@ -75,12 +86,24 @@ pub fn writeFilePath(
75 return result;86 return result;
76}87}
7788
89fn openCFile(allocator: *Allocator, file: fs.File, options: Options) !File.C {
90 return File.C{
91 .allocator = allocator,
92 .file = file,
93 .options = options,
94 .main = std.ArrayList(u8).init(allocator),
95 .header = std.ArrayList(u8).init(allocator),
96 .constants = std.ArrayList(u8).init(allocator),
97 .called = std.StringHashMap(void).init(allocator),
98 };
99}
100
78/// Attempts incremental linking, if the file already exists.101/// Attempts incremental linking, if the file already exists.
79/// If incremental linking fails, falls back to truncating the file and rewriting it.102/// If incremental linking fails, falls back to truncating the file and rewriting it.
80/// Returns an error if `file` is not already open with +read +write +seek abilities.103/// Returns an error if `file` is not already open with +read +write +seek abilities.
81/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.104/// A malicious file is detected as incremental link failure and does not cause Illegal Behavior.
82/// This operation is not atomic.105/// This operation is not atomic.
83pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile {106pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
84 return openBinFileInner(allocator, file, options) catch |err| switch (err) {107 return openBinFileInner(allocator, file, options) catch |err| switch (err) {
85 error.IncrFailed => {108 error.IncrFailed => {
86 return createElfFile(allocator, file, options);109 return createElfFile(allocator, file, options);
...@@ -89,514 +112,592 @@ pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfF...@@ -89,514 +112,592 @@ pub fn openBinFile(allocator: *Allocator, file: fs.File, options: Options) !ElfF
89 };112 };
90}113}
91114
92pub const ElfFile = struct {115pub const File = struct {
93 allocator: *Allocator,116 tag: Tag,
94 file: ?fs.File,117 pub fn cast(base: *File, comptime T: type) ?*T {
95 owns_file_handle: bool,118 if (base.tag != T.base_tag)
96 options: Options,119 return null;
97 ptr_width: enum { p32, p64 },
98
99 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
100 /// Same order as in the file.
101 sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){},
102 shdr_table_offset: ?u64 = null,
103
104 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
105 /// Same order as in the file.
106 program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){},
107 phdr_table_offset: ?u64 = null,
108 /// The index into the program headers of a PT_LOAD program header with Read and Execute flags
109 phdr_load_re_index: ?u16 = null,
110 /// The index into the program headers of the global offset table.
111 /// It needs PT_LOAD and Read flags.
112 phdr_got_index: ?u16 = null,
113 entry_addr: ?u64 = null,
114
115 shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
116 shstrtab_index: ?u16 = null,
117
118 text_section_index: ?u16 = null,
119 symtab_section_index: ?u16 = null,
120 got_section_index: ?u16 = null,
121
122 /// The same order as in the file. ELF requires global symbols to all be after the
123 /// local symbols, they cannot be mixed. So we must buffer all the global symbols and
124 /// write them at the end. These are only the local symbols. The length of this array
125 /// is the value used for sh_info in the .symtab section.
126 local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){},
127 global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){},
128
129 local_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
130 global_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
131 offset_table_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
132
133 /// Same order as in the file. The value is the absolute vaddr value.
134 /// If the vaddr of the executable program header changes, the entire
135 /// offset table needs to be rewritten.
136 offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){},
137
138 phdr_table_dirty: bool = false,
139 shdr_table_dirty: bool = false,
140 shstrtab_dirty: bool = false,
141 offset_table_count_dirty: bool = false,
142
143 error_flags: ErrorFlags = ErrorFlags{},
144
145 /// A list of text blocks that have surplus capacity. This list can have false
146 /// positives, as functions grow and shrink over time, only sometimes being added
147 /// or removed from the freelist.
148 ///
149 /// A text block has surplus capacity when its overcapacity value is greater than
150 /// minimum_text_block_size * alloc_num / alloc_den. That is, when it has so
151 /// much extra capacity, that we could fit a small new symbol in it, itself with
152 /// ideal_capacity or more.
153 ///
154 /// Ideal capacity is defined by size * alloc_num / alloc_den.
155 ///
156 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
157 /// overcapacity can be negative. A simple way to have negative overcapacity is to
158 /// allocate a fresh text block, which will have ideal capacity, and then grow it
159 /// by 1 byte. It will then have -1 overcapacity.
160 text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){},
161 last_text_block: ?*TextBlock = null,
162
163 /// `alloc_num / alloc_den` is the factor of padding when allocating.
164 const alloc_num = 4;
165 const alloc_den = 3;
166
167 /// In order for a slice of bytes to be considered eligible to keep metadata pointing at
168 /// it as a possible place to put new symbols, it must have enough room for this many bytes
169 /// (plus extra for reserved capacity).
170 const minimum_text_block_size = 64;
171 const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den;
172
173 pub const ErrorFlags = struct {
174 no_entry_point_found: bool = false,
175 };
176
177 pub const TextBlock = struct {
178 /// Each decl always gets a local symbol with the fully qualified name.
179 /// The vaddr and size are found here directly.
180 /// The file offset is found by computing the vaddr offset from the section vaddr
181 /// the symbol references, and adding that to the file offset of the section.
182 /// If this field is 0, it means the codegen size = 0 and there is no symbol or
183 /// offset table entry.
184 local_sym_index: u32,
185 /// This field is undefined for symbols with size = 0.
186 offset_table_index: u32,
187 /// Points to the previous and next neighbors, based on the `text_offset`.
188 /// This can be used to find, for example, the capacity of this `TextBlock`.
189 prev: ?*TextBlock,
190 next: ?*TextBlock,
191
192 pub const empty = TextBlock{
193 .local_sym_index = 0,
194 .offset_table_index = undefined,
195 .prev = null,
196 .next = null,
197 };
198
199 /// Returns how much room there is to grow in virtual address space.
200 /// File offset relocation happens transparently, so it is not included in
201 /// this calculation.
202 fn capacity(self: TextBlock, elf_file: ElfFile) u64 {
203 const self_sym = elf_file.local_symbols.items[self.local_sym_index];
204 if (self.next) |next| {
205 const next_sym = elf_file.local_symbols.items[next.local_sym_index];
206 return next_sym.st_value - self_sym.st_value;
207 } else {
208 // We are the last block. The capacity is limited only by virtual address space.
209 return std.math.maxInt(u32) - self_sym.st_value;
210 }
211 }
212120
213 fn freeListEligible(self: TextBlock, elf_file: ElfFile) bool {121 return @fieldParentPtr(T, "base", base);
214 // No need to keep a free list node for the last block.122 }
215 const next = self.next orelse return false;
216 const self_sym = elf_file.local_symbols.items[self.local_sym_index];
217 const next_sym = elf_file.local_symbols.items[next.local_sym_index];
218 const cap = next_sym.st_value - self_sym.st_value;
219 const ideal_cap = self_sym.st_size * alloc_num / alloc_den;
220 if (cap <= ideal_cap) return false;
221 const surplus = cap - ideal_cap;
222 return surplus >= min_text_capacity;
223 }
224 };
225
226 pub const Export = struct {
227 sym_index: ?u32 = null,
228 };
229123
230 pub fn deinit(self: *ElfFile) void {124 pub fn makeWritable(base: *File, dir: fs.Dir, sub_path: []const u8) !void {
231 self.sections.deinit(self.allocator);125 switch (base.tag) {
232 self.program_headers.deinit(self.allocator);126 .Elf => return @fieldParentPtr(Elf, "base", base).makeWritable(dir, sub_path),
233 self.shstrtab.deinit(self.allocator);127 .C => {},
234 self.local_symbols.deinit(self.allocator);128 else => unreachable,
235 self.global_symbols.deinit(self.allocator);
236 self.global_symbol_free_list.deinit(self.allocator);
237 self.local_symbol_free_list.deinit(self.allocator);
238 self.offset_table_free_list.deinit(self.allocator);
239 self.text_block_free_list.deinit(self.allocator);
240 self.offset_table.deinit(self.allocator);
241 if (self.owns_file_handle) {
242 if (self.file) |f| f.close();
243 }129 }
244 }130 }
245131
246 pub fn makeExecutable(self: *ElfFile) !void {132 pub fn makeExecutable(base: *File) !void {
247 assert(self.owns_file_handle);133 switch (base.tag) {
248 if (self.file) |f| {134 .Elf => return @fieldParentPtr(Elf, "base", base).makeExecutable(),
249 f.close();135 else => unreachable,
250 self.file = null;
251 }136 }
252 }137 }
253138
254 pub fn makeWritable(self: *ElfFile, dir: fs.Dir, sub_path: []const u8) !void {139 pub fn updateDecl(base: *File, module: *Module, decl: *Module.Decl) !void {
255 assert(self.owns_file_handle);140 switch (base.tag) {
256 if (self.file != null) return;141 .Elf => return @fieldParentPtr(Elf, "base", base).updateDecl(module, decl),
257 self.file = try dir.createFile(sub_path, .{142 .C => return @fieldParentPtr(C, "base", base).updateDecl(module, decl),
258 .truncate = false,143 else => unreachable,
259 .read = true,144 }
260 .mode = determineMode(self.options),
261 });
262 }145 }
263146
264 /// Returns end pos of collision, if any.147 pub fn allocateDeclIndexes(base: *File, decl: *Module.Decl) !void {
265 fn detectAllocCollision(self: *ElfFile, start: u64, size: u64) ?u64 {148 switch (base.tag) {
266 const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;149 .Elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
267 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);150 .C => {},
268 if (start < ehdr_size)151 else => unreachable,
269 return ehdr_size;
270
271 const end = start + satMul(size, alloc_num) / alloc_den;
272
273 if (self.shdr_table_offset) |off| {
274 const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr);
275 const tight_size = self.sections.items.len * shdr_size;
276 const increased_size = satMul(tight_size, alloc_num) / alloc_den;
277 const test_end = off + increased_size;
278 if (end > off and start < test_end) {
279 return test_end;
280 }
281 }152 }
153 }
282154
283 if (self.phdr_table_offset) |off| {155 pub fn deinit(base: *File) void {
284 const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr);156 switch (base.tag) {
285 const tight_size = self.sections.items.len * phdr_size;157 .Elf => @fieldParentPtr(Elf, "base", base).deinit(),
286 const increased_size = satMul(tight_size, alloc_num) / alloc_den;158 .C => @fieldParentPtr(C, "base", base).deinit(),
287 const test_end = off + increased_size;159 else => unreachable,
288 if (end > off and start < test_end) {
289 return test_end;
290 }
291 }160 }
161 }
292162
293 for (self.sections.items) |section| {163 pub fn destroy(base: *File) void {
294 const increased_size = satMul(section.sh_size, alloc_num) / alloc_den;164 switch (base.tag) {
295 const test_end = section.sh_offset + increased_size;165 .Elf => {
296 if (end > section.sh_offset and start < test_end) {166 const parent = @fieldParentPtr(Elf, "base", base);
297 return test_end;167 parent.deinit();
298 }168 parent.allocator.destroy(parent);
299 }169 },
300 for (self.program_headers.items) |program_header| {170 .C => {
301 const increased_size = satMul(program_header.p_filesz, alloc_num) / alloc_den;171 const parent = @fieldParentPtr(C, "base", base);
302 const test_end = program_header.p_offset + increased_size;172 parent.deinit();
303 if (end > program_header.p_offset and start < test_end) {173 parent.allocator.destroy(parent);
304 return test_end;174 },
305 }175 else => unreachable,
306 }176 }
307 return null;
308 }177 }
309178
310 fn allocatedSize(self: *ElfFile, start: u64) u64 {179 pub fn flush(base: *File) !void {
311 var min_pos: u64 = std.math.maxInt(u64);180 try switch (base.tag) {
312 if (self.shdr_table_offset) |off| {181 .Elf => @fieldParentPtr(Elf, "base", base).flush(),
313 if (off > start and off < min_pos) min_pos = off;182 .C => @fieldParentPtr(C, "base", base).flush(),
314 }183 else => unreachable,
315 if (self.phdr_table_offset) |off| {184 };
316 if (off > start and off < min_pos) min_pos = off;
317 }
318 for (self.sections.items) |section| {
319 if (section.sh_offset <= start) continue;
320 if (section.sh_offset < min_pos) min_pos = section.sh_offset;
321 }
322 for (self.program_headers.items) |program_header| {
323 if (program_header.p_offset <= start) continue;
324 if (program_header.p_offset < min_pos) min_pos = program_header.p_offset;
325 }
326 return min_pos - start;
327 }185 }
328186
329 fn findFreeSpace(self: *ElfFile, object_size: u64, min_alignment: u16) u64 {187 pub fn freeDecl(base: *File, decl: *Module.Decl) void {
330 var start: u64 = 0;188 switch (base.tag) {
331 while (self.detectAllocCollision(start, object_size)) |item_end| {189 .Elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
332 start = mem.alignForwardGeneric(u64, item_end, min_alignment);190 else => unreachable,
333 }191 }
334 return start;
335 }192 }
336193
337 fn makeString(self: *ElfFile, bytes: []const u8) !u32 {194 pub fn errorFlags(base: *File) ErrorFlags {
338 try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1);195 return switch (base.tag) {
339 const result = self.shstrtab.items.len;196 .Elf => @fieldParentPtr(Elf, "base", base).error_flags,
340 self.shstrtab.appendSliceAssumeCapacity(bytes);197 .C => return .{ .no_entry_point_found = false },
341 self.shstrtab.appendAssumeCapacity(0);198 else => unreachable,
342 return @intCast(u32, result);199 };
343 }200 }
344201
345 fn getString(self: *ElfFile, str_off: u32) []const u8 {202 pub fn options(base: *File) Options {
346 assert(str_off < self.shstrtab.items.len);203 return switch (base.tag) {
347 return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off));204 .Elf => @fieldParentPtr(Elf, "base", base).options,
205 .C => @fieldParentPtr(C, "base", base).options,
206 };
348 }207 }
349208
350 fn updateString(self: *ElfFile, old_str_off: u32, new_name: []const u8) !u32 {209 /// Must be called only after a successful call to `updateDecl`.
351 const existing_name = self.getString(old_str_off);210 pub fn updateDeclExports(
352 if (mem.eql(u8, existing_name, new_name)) {211 base: *File,
353 return old_str_off;212 module: *Module,
213 decl: *const Module.Decl,
214 exports: []const *Module.Export,
215 ) !void {
216 switch (base.tag) {
217 .Elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
218 .C => return {},
354 }219 }
355 return self.makeString(new_name);
356 }220 }
357221
358 pub fn populateMissingMetadata(self: *ElfFile) !void {222 pub const Tag = enum {
359 const small_ptr = switch (self.ptr_width) {223 Elf,
360 .p32 => true,224 C,
361 .p64 => false,225 };
362 };
363 const ptr_size: u8 = switch (self.ptr_width) {
364 .p32 => 4,
365 .p64 => 8,
366 };
367 if (self.phdr_load_re_index == null) {
368 self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
369 const file_size = self.options.program_code_size_hint;
370 const p_align = 0x1000;
371 const off = self.findFreeSpace(file_size, p_align);
372 std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
373 try self.program_headers.append(self.allocator, .{
374 .p_type = elf.PT_LOAD,
375 .p_offset = off,
376 .p_filesz = file_size,
377 .p_vaddr = default_entry_addr,
378 .p_paddr = default_entry_addr,
379 .p_memsz = file_size,
380 .p_align = p_align,
381 .p_flags = elf.PF_X | elf.PF_R,
382 });
383 self.entry_addr = null;
384 self.phdr_table_dirty = true;
385 }
386 if (self.phdr_got_index == null) {
387 self.phdr_got_index = @intCast(u16, self.program_headers.items.len);
388 const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint;
389 // We really only need ptr alignment but since we are using PROGBITS, linux requires
390 // page align.
391 const p_align = 0x1000;
392 const off = self.findFreeSpace(file_size, p_align);
393 std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
394 // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
395 // we'll need to re-use that function anyway, in case the GOT grows and overlaps something
396 // else in virtual memory.
397 const default_got_addr = 0x4000000;
398 try self.program_headers.append(self.allocator, .{
399 .p_type = elf.PT_LOAD,
400 .p_offset = off,
401 .p_filesz = file_size,
402 .p_vaddr = default_got_addr,
403 .p_paddr = default_got_addr,
404 .p_memsz = file_size,
405 .p_align = p_align,
406 .p_flags = elf.PF_R,
407 });
408 self.phdr_table_dirty = true;
409 }
410 if (self.shstrtab_index == null) {
411 self.shstrtab_index = @intCast(u16, self.sections.items.len);
412 assert(self.shstrtab.items.len == 0);
413 try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0
414 const off = self.findFreeSpace(self.shstrtab.items.len, 1);
415 std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len });
416 try self.sections.append(self.allocator, .{
417 .sh_name = try self.makeString(".shstrtab"),
418 .sh_type = elf.SHT_STRTAB,
419 .sh_flags = 0,
420 .sh_addr = 0,
421 .sh_offset = off,
422 .sh_size = self.shstrtab.items.len,
423 .sh_link = 0,
424 .sh_info = 0,
425 .sh_addralign = 1,
426 .sh_entsize = 0,
427 });
428 self.shstrtab_dirty = true;
429 self.shdr_table_dirty = true;
430 }
431 if (self.text_section_index == null) {
432 self.text_section_index = @intCast(u16, self.sections.items.len);
433 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
434226
435 try self.sections.append(self.allocator, .{227 pub const ErrorFlags = struct {
436 .sh_name = try self.makeString(".text"),228 no_entry_point_found: bool = false,
437 .sh_type = elf.SHT_PROGBITS,229 };
438 .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR,230
439 .sh_addr = phdr.p_vaddr,231 pub const C = struct {
440 .sh_offset = phdr.p_offset,232 pub const base_tag: Tag = .C;
441 .sh_size = phdr.p_filesz,233 base: File = File{ .tag = base_tag },
442 .sh_link = 0,234
443 .sh_info = 0,235 allocator: *Allocator,
444 .sh_addralign = phdr.p_align,236 header: std.ArrayList(u8),
445 .sh_entsize = 0,237 constants: std.ArrayList(u8),
446 });238 main: std.ArrayList(u8),
447 self.shdr_table_dirty = true;239 file: ?fs.File,
240 options: Options,
241 called: std.StringHashMap(void),
242 need_stddef: bool = false,
243 need_stdint: bool = false,
244 need_noreturn: bool = false,
245 error_msg: *Module.ErrorMsg = undefined,
246
247 pub fn fail(self: *C, src: usize, comptime format: []const u8, args: var) !void {
248 self.error_msg = try Module.ErrorMsg.create(self.allocator, src, format, args);
249 return error.CGenFailure;
448 }250 }
449 if (self.got_section_index == null) {
450 self.got_section_index = @intCast(u16, self.sections.items.len);
451 const phdr = &self.program_headers.items[self.phdr_got_index.?];
452251
453 try self.sections.append(self.allocator, .{252 pub fn deinit(self: *File.C) void {
454 .sh_name = try self.makeString(".got"),253 self.main.deinit();
455 .sh_type = elf.SHT_PROGBITS,254 self.header.deinit();
456 .sh_flags = elf.SHF_ALLOC,255 self.constants.deinit();
457 .sh_addr = phdr.p_vaddr,256 self.called.deinit();
458 .sh_offset = phdr.p_offset,257 if (self.file) |f|
459 .sh_size = phdr.p_filesz,258 f.close();
460 .sh_link = 0,
461 .sh_info = 0,
462 .sh_addralign = phdr.p_align,
463 .sh_entsize = 0,
464 });
465 self.shdr_table_dirty = true;
466 }259 }
467 if (self.symtab_section_index == null) {260
468 self.symtab_section_index = @intCast(u16, self.sections.items.len);261 pub fn updateDecl(self: *File.C, module: *Module, decl: *Module.Decl) !void {
469 const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);262 cgen.generate(self, decl) catch |err| {
470 const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);263 if (err == error.CGenFailure) {
471 const file_size = self.options.symbol_count_hint * each_size;264 try module.failed_decls.put(module.gpa, decl, self.error_msg);
472 const off = self.findFreeSpace(file_size, min_align);265 }
473 std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });266 return err;
474267 };
475 try self.sections.append(self.allocator, .{
476 .sh_name = try self.makeString(".symtab"),
477 .sh_type = elf.SHT_SYMTAB,
478 .sh_flags = 0,
479 .sh_addr = 0,
480 .sh_offset = off,
481 .sh_size = file_size,
482 // The section header index of the associated string table.
483 .sh_link = self.shstrtab_index.?,
484 .sh_info = @intCast(u32, self.local_symbols.items.len),
485 .sh_addralign = min_align,
486 .sh_entsize = each_size,
487 });
488 self.shdr_table_dirty = true;
489 try self.writeSymbol(0);
490 }268 }
491 const shsize: u64 = switch (self.ptr_width) {269
492 .p32 => @sizeOf(elf.Elf32_Shdr),270 pub fn flush(self: *File.C) !void {
493 .p64 => @sizeOf(elf.Elf64_Shdr),271 const writer = self.file.?.writer();
494 };272 try writer.writeAll(@embedFile("cbe.h"));
495 const shalign: u16 = switch (self.ptr_width) {273 var includes = false;
496 .p32 => @alignOf(elf.Elf32_Shdr),274 if (self.need_stddef) {
497 .p64 => @alignOf(elf.Elf64_Shdr),275 try writer.writeAll("#include <stddef.h>\n");
498 };276 includes = true;
499 if (self.shdr_table_offset == null) {277 }
500 self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign);278 if (self.need_stdint) {
501 self.shdr_table_dirty = true;279 try writer.writeAll("#include <stdint.h>\n");
280 includes = true;
281 }
282 if (includes) {
283 try writer.writeByte('\n');
284 }
285 if (self.header.items.len > 0) {
286 try writer.print("{}\n", .{self.header.items});
287 }
288 if (self.constants.items.len > 0) {
289 try writer.print("{}\n", .{self.constants.items});
290 }
291 if (self.main.items.len > 1) {
292 const last_two = self.main.items[self.main.items.len - 2 ..];
293 if (std.mem.eql(u8, last_two, "\n\n")) {
294 self.main.items.len -= 1;
295 }
296 }
297 try writer.writeAll(self.main.items);
298 self.file.?.close();
299 self.file = null;
502 }300 }
503 const phsize: u64 = switch (self.ptr_width) {301 };
504 .p32 => @sizeOf(elf.Elf32_Phdr),302
505 .p64 => @sizeOf(elf.Elf64_Phdr),303 pub const Elf = struct {
304 pub const base_tag: Tag = .Elf;
305 base: File = File{ .tag = base_tag },
306
307 allocator: *Allocator,
308 file: ?fs.File,
309 owns_file_handle: bool,
310 options: Options,
311 ptr_width: enum { p32, p64 },
312
313 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
314 /// Same order as in the file.
315 sections: std.ArrayListUnmanaged(elf.Elf64_Shdr) = std.ArrayListUnmanaged(elf.Elf64_Shdr){},
316 shdr_table_offset: ?u64 = null,
317
318 /// Stored in native-endian format, depending on target endianness needs to be bswapped on read/write.
319 /// Same order as in the file.
320 program_headers: std.ArrayListUnmanaged(elf.Elf64_Phdr) = std.ArrayListUnmanaged(elf.Elf64_Phdr){},
321 phdr_table_offset: ?u64 = null,
322 /// The index into the program headers of a PT_LOAD program header with Read and Execute flags
323 phdr_load_re_index: ?u16 = null,
324 /// The index into the program headers of the global offset table.
325 /// It needs PT_LOAD and Read flags.
326 phdr_got_index: ?u16 = null,
327 entry_addr: ?u64 = null,
328
329 shstrtab: std.ArrayListUnmanaged(u8) = std.ArrayListUnmanaged(u8){},
330 shstrtab_index: ?u16 = null,
331
332 text_section_index: ?u16 = null,
333 symtab_section_index: ?u16 = null,
334 got_section_index: ?u16 = null,
335
336 /// The same order as in the file. ELF requires global symbols to all be after the
337 /// local symbols, they cannot be mixed. So we must buffer all the global symbols and
338 /// write them at the end. These are only the local symbols. The length of this array
339 /// is the value used for sh_info in the .symtab section.
340 local_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){},
341 global_symbols: std.ArrayListUnmanaged(elf.Elf64_Sym) = std.ArrayListUnmanaged(elf.Elf64_Sym){},
342
343 local_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
344 global_symbol_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
345 offset_table_free_list: std.ArrayListUnmanaged(u32) = std.ArrayListUnmanaged(u32){},
346
347 /// Same order as in the file. The value is the absolute vaddr value.
348 /// If the vaddr of the executable program header changes, the entire
349 /// offset table needs to be rewritten.
350 offset_table: std.ArrayListUnmanaged(u64) = std.ArrayListUnmanaged(u64){},
351
352 phdr_table_dirty: bool = false,
353 shdr_table_dirty: bool = false,
354 shstrtab_dirty: bool = false,
355 offset_table_count_dirty: bool = false,
356
357 error_flags: ErrorFlags = ErrorFlags{},
358
359 /// A list of text blocks that have surplus capacity. This list can have false
360 /// positives, as functions grow and shrink over time, only sometimes being added
361 /// or removed from the freelist.
362 ///
363 /// A text block has surplus capacity when its overcapacity value is greater than
364 /// minimum_text_block_size * alloc_num / alloc_den. That is, when it has so
365 /// much extra capacity, that we could fit a small new symbol in it, itself with
366 /// ideal_capacity or more.
367 ///
368 /// Ideal capacity is defined by size * alloc_num / alloc_den.
369 ///
370 /// Overcapacity is measured by actual_capacity - ideal_capacity. Note that
371 /// overcapacity can be negative. A simple way to have negative overcapacity is to
372 /// allocate a fresh text block, which will have ideal capacity, and then grow it
373 /// by 1 byte. It will then have -1 overcapacity.
374 text_block_free_list: std.ArrayListUnmanaged(*TextBlock) = std.ArrayListUnmanaged(*TextBlock){},
375 last_text_block: ?*TextBlock = null,
376
377 /// `alloc_num / alloc_den` is the factor of padding when allocating.
378 const alloc_num = 4;
379 const alloc_den = 3;
380
381 /// In order for a slice of bytes to be considered eligible to keep metadata pointing at
382 /// it as a possible place to put new symbols, it must have enough room for this many bytes
383 /// (plus extra for reserved capacity).
384 const minimum_text_block_size = 64;
385 const min_text_capacity = minimum_text_block_size * alloc_num / alloc_den;
386
387 pub const TextBlock = struct {
388 /// Each decl always gets a local symbol with the fully qualified name.
389 /// The vaddr and size are found here directly.
390 /// The file offset is found by computing the vaddr offset from the section vaddr
391 /// the symbol references, and adding that to the file offset of the section.
392 /// If this field is 0, it means the codegen size = 0 and there is no symbol or
393 /// offset table entry.
394 local_sym_index: u32,
395 /// This field is undefined for symbols with size = 0.
396 offset_table_index: u32,
397 /// Points to the previous and next neighbors, based on the `text_offset`.
398 /// This can be used to find, for example, the capacity of this `TextBlock`.
399 prev: ?*TextBlock,
400 next: ?*TextBlock,
401
402 pub const empty = TextBlock{
403 .local_sym_index = 0,
404 .offset_table_index = undefined,
405 .prev = null,
406 .next = null,
407 };
408
409 /// Returns how much room there is to grow in virtual address space.
410 /// File offset relocation happens transparently, so it is not included in
411 /// this calculation.
412 fn capacity(self: TextBlock, elf_file: Elf) u64 {
413 const self_sym = elf_file.local_symbols.items[self.local_sym_index];
414 if (self.next) |next| {
415 const next_sym = elf_file.local_symbols.items[next.local_sym_index];
416 return next_sym.st_value - self_sym.st_value;
417 } else {
418 // We are the last block. The capacity is limited only by virtual address space.
419 return std.math.maxInt(u32) - self_sym.st_value;
420 }
421 }
422
423 fn freeListEligible(self: TextBlock, elf_file: Elf) bool {
424 // No need to keep a free list node for the last block.
425 const next = self.next orelse return false;
426 const self_sym = elf_file.local_symbols.items[self.local_sym_index];
427 const next_sym = elf_file.local_symbols.items[next.local_sym_index];
428 const cap = next_sym.st_value - self_sym.st_value;
429 const ideal_cap = self_sym.st_size * alloc_num / alloc_den;
430 if (cap <= ideal_cap) return false;
431 const surplus = cap - ideal_cap;
432 return surplus >= min_text_capacity;
433 }
506 };434 };
507 const phalign: u16 = switch (self.ptr_width) {435
508 .p32 => @alignOf(elf.Elf32_Phdr),436 pub const Export = struct {
509 .p64 => @alignOf(elf.Elf64_Phdr),437 sym_index: ?u32 = null,
510 };438 };
511 if (self.phdr_table_offset == null) {439
512 self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign);440 pub fn deinit(self: *Elf) void {
513 self.phdr_table_dirty = true;441 self.sections.deinit(self.allocator);
514 }442 self.program_headers.deinit(self.allocator);
515 {443 self.shstrtab.deinit(self.allocator);
516 // Iterate over symbols, populating free_list and last_text_block.444 self.local_symbols.deinit(self.allocator);
517 if (self.local_symbols.items.len != 1) {445 self.global_symbols.deinit(self.allocator);
518 @panic("TODO implement setting up free_list and last_text_block from existing ELF file");446 self.global_symbol_free_list.deinit(self.allocator);
447 self.local_symbol_free_list.deinit(self.allocator);
448 self.offset_table_free_list.deinit(self.allocator);
449 self.text_block_free_list.deinit(self.allocator);
450 self.offset_table.deinit(self.allocator);
451 if (self.owns_file_handle) {
452 if (self.file) |f| f.close();
519 }453 }
520 // We are starting with an empty file. The default values are correct, null and empty list.
521 }454 }
522 }
523455
524 /// Commit pending changes and write headers.456 pub fn makeExecutable(self: *Elf) !void {
525 pub fn flush(self: *ElfFile) !void {457 assert(self.owns_file_handle);
526 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();458 if (self.file) |f| {
459 f.close();
460 self.file = null;
461 }
462 }
527463
528 // Unfortunately these have to be buffered and done at the end because ELF does not allow464 pub fn makeWritable(self: *Elf, dir: fs.Dir, sub_path: []const u8) !void {
529 // mixing local and global symbols within a symbol table.465 assert(self.owns_file_handle);
530 try self.writeAllGlobalSymbols();466 if (self.file != null) return;
467 self.file = try dir.createFile(sub_path, .{
468 .truncate = false,
469 .read = true,
470 .mode = determineMode(self.options),
471 });
472 }
531473
532 if (self.phdr_table_dirty) {474 /// Returns end pos of collision, if any.
533 const phsize: u64 = switch (self.ptr_width) {475 fn detectAllocCollision(self: *Elf, start: u64, size: u64) ?u64 {
534 .p32 => @sizeOf(elf.Elf32_Phdr),476 const small_ptr = self.options.target.cpu.arch.ptrBitWidth() == 32;
535 .p64 => @sizeOf(elf.Elf64_Phdr),477 const ehdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Ehdr) else @sizeOf(elf.Elf64_Ehdr);
536 };478 if (start < ehdr_size)
537 const phalign: u16 = switch (self.ptr_width) {479 return ehdr_size;
538 .p32 => @alignOf(elf.Elf32_Phdr),480
539 .p64 => @alignOf(elf.Elf64_Phdr),481 const end = start + satMul(size, alloc_num) / alloc_den;
540 };482
541 const allocated_size = self.allocatedSize(self.phdr_table_offset.?);483 if (self.shdr_table_offset) |off| {
542 const needed_size = self.program_headers.items.len * phsize;484 const shdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Shdr) else @sizeOf(elf.Elf64_Shdr);
485 const tight_size = self.sections.items.len * shdr_size;
486 const increased_size = satMul(tight_size, alloc_num) / alloc_den;
487 const test_end = off + increased_size;
488 if (end > off and start < test_end) {
489 return test_end;
490 }
491 }
543492
544 if (needed_size > allocated_size) {493 if (self.phdr_table_offset) |off| {
545 self.phdr_table_offset = null; // free the space494 const phdr_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Phdr) else @sizeOf(elf.Elf64_Phdr);
546 self.phdr_table_offset = self.findFreeSpace(needed_size, phalign);495 const tight_size = self.sections.items.len * phdr_size;
496 const increased_size = satMul(tight_size, alloc_num) / alloc_den;
497 const test_end = off + increased_size;
498 if (end > off and start < test_end) {
499 return test_end;
500 }
547 }501 }
548502
549 switch (self.ptr_width) {503 for (self.sections.items) |section| {
550 .p32 => {504 const increased_size = satMul(section.sh_size, alloc_num) / alloc_den;
551 const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len);505 const test_end = section.sh_offset + increased_size;
552 defer self.allocator.free(buf);506 if (end > section.sh_offset and start < test_end) {
507 return test_end;
508 }
509 }
510 for (self.program_headers.items) |program_header| {
511 const increased_size = satMul(program_header.p_filesz, alloc_num) / alloc_den;
512 const test_end = program_header.p_offset + increased_size;
513 if (end > program_header.p_offset and start < test_end) {
514 return test_end;
515 }
516 }
517 return null;
518 }
553519
554 for (buf) |*phdr, i| {520 fn allocatedSize(self: *Elf, start: u64) u64 {
555 phdr.* = progHeaderTo32(self.program_headers.items[i]);521 var min_pos: u64 = std.math.maxInt(u64);
556 if (foreign_endian) {522 if (self.shdr_table_offset) |off| {
557 bswapAllFields(elf.Elf32_Phdr, phdr);523 if (off > start and off < min_pos) min_pos = off;
558 }524 }
559 }525 if (self.phdr_table_offset) |off| {
560 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);526 if (off > start and off < min_pos) min_pos = off;
561 },527 }
562 .p64 => {528 for (self.sections.items) |section| {
563 const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len);529 if (section.sh_offset <= start) continue;
564 defer self.allocator.free(buf);530 if (section.sh_offset < min_pos) min_pos = section.sh_offset;
531 }
532 for (self.program_headers.items) |program_header| {
533 if (program_header.p_offset <= start) continue;
534 if (program_header.p_offset < min_pos) min_pos = program_header.p_offset;
535 }
536 return min_pos - start;
537 }
565538
566 for (buf) |*phdr, i| {539 fn findFreeSpace(self: *Elf, object_size: u64, min_alignment: u16) u64 {
567 phdr.* = self.program_headers.items[i];540 var start: u64 = 0;
568 if (foreign_endian) {541 while (self.detectAllocCollision(start, object_size)) |item_end| {
569 bswapAllFields(elf.Elf64_Phdr, phdr);542 start = mem.alignForwardGeneric(u64, item_end, min_alignment);
570 }
571 }
572 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
573 },
574 }543 }
575 self.phdr_table_dirty = false;544 return start;
576 }545 }
577546
578 {547 fn makeString(self: *Elf, bytes: []const u8) !u32 {
579 const shstrtab_sect = &self.sections.items[self.shstrtab_index.?];548 try self.shstrtab.ensureCapacity(self.allocator, self.shstrtab.items.len + bytes.len + 1);
580 if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) {549 const result = self.shstrtab.items.len;
581 const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset);550 self.shstrtab.appendSliceAssumeCapacity(bytes);
582 const needed_size = self.shstrtab.items.len;551 self.shstrtab.appendAssumeCapacity(0);
552 return @intCast(u32, result);
553 }
583554
584 if (needed_size > allocated_size) {555 fn getString(self: *Elf, str_off: u32) []const u8 {
585 shstrtab_sect.sh_size = 0; // free the space556 assert(str_off < self.shstrtab.items.len);
586 shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1);557 return mem.spanZ(@ptrCast([*:0]const u8, self.shstrtab.items.ptr + str_off));
587 }558 }
588 shstrtab_sect.sh_size = needed_size;
589 std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
590559
591 try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset);560 fn updateString(self: *Elf, old_str_off: u32, new_name: []const u8) !u32 {
592 if (!self.shdr_table_dirty) {561 const existing_name = self.getString(old_str_off);
593 // Then it won't get written with the others and we need to do it.562 if (mem.eql(u8, existing_name, new_name)) {
594 try self.writeSectHeader(self.shstrtab_index.?);563 return old_str_off;
595 }
596 self.shstrtab_dirty = false;
597 }564 }
565 return self.makeString(new_name);
598 }566 }
599 if (self.shdr_table_dirty) {567
568 pub fn populateMissingMetadata(self: *Elf) !void {
569 const small_ptr = switch (self.ptr_width) {
570 .p32 => true,
571 .p64 => false,
572 };
573 const ptr_size: u8 = switch (self.ptr_width) {
574 .p32 => 4,
575 .p64 => 8,
576 };
577 if (self.phdr_load_re_index == null) {
578 self.phdr_load_re_index = @intCast(u16, self.program_headers.items.len);
579 const file_size = self.options.program_code_size_hint;
580 const p_align = 0x1000;
581 const off = self.findFreeSpace(file_size, p_align);
582 std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
583 try self.program_headers.append(self.allocator, .{
584 .p_type = elf.PT_LOAD,
585 .p_offset = off,
586 .p_filesz = file_size,
587 .p_vaddr = default_entry_addr,
588 .p_paddr = default_entry_addr,
589 .p_memsz = file_size,
590 .p_align = p_align,
591 .p_flags = elf.PF_X | elf.PF_R,
592 });
593 self.entry_addr = null;
594 self.phdr_table_dirty = true;
595 }
596 if (self.phdr_got_index == null) {
597 self.phdr_got_index = @intCast(u16, self.program_headers.items.len);
598 const file_size = @as(u64, ptr_size) * self.options.symbol_count_hint;
599 // We really only need ptr alignment but since we are using PROGBITS, linux requires
600 // page align.
601 const p_align = 0x1000;
602 const off = self.findFreeSpace(file_size, p_align);
603 std.log.debug(.link, "found PT_LOAD free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
604 // TODO instead of hard coding the vaddr, make a function to find a vaddr to put things at.
605 // we'll need to re-use that function anyway, in case the GOT grows and overlaps something
606 // else in virtual memory.
607 const default_got_addr = 0x4000000;
608 try self.program_headers.append(self.allocator, .{
609 .p_type = elf.PT_LOAD,
610 .p_offset = off,
611 .p_filesz = file_size,
612 .p_vaddr = default_got_addr,
613 .p_paddr = default_got_addr,
614 .p_memsz = file_size,
615 .p_align = p_align,
616 .p_flags = elf.PF_R,
617 });
618 self.phdr_table_dirty = true;
619 }
620 if (self.shstrtab_index == null) {
621 self.shstrtab_index = @intCast(u16, self.sections.items.len);
622 assert(self.shstrtab.items.len == 0);
623 try self.shstrtab.append(self.allocator, 0); // need a 0 at position 0
624 const off = self.findFreeSpace(self.shstrtab.items.len, 1);
625 std.log.debug(.link, "found shstrtab free space 0x{x} to 0x{x}\n", .{ off, off + self.shstrtab.items.len });
626 try self.sections.append(self.allocator, .{
627 .sh_name = try self.makeString(".shstrtab"),
628 .sh_type = elf.SHT_STRTAB,
629 .sh_flags = 0,
630 .sh_addr = 0,
631 .sh_offset = off,
632 .sh_size = self.shstrtab.items.len,
633 .sh_link = 0,
634 .sh_info = 0,
635 .sh_addralign = 1,
636 .sh_entsize = 0,
637 });
638 self.shstrtab_dirty = true;
639 self.shdr_table_dirty = true;
640 }
641 if (self.text_section_index == null) {
642 self.text_section_index = @intCast(u16, self.sections.items.len);
643 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
644
645 try self.sections.append(self.allocator, .{
646 .sh_name = try self.makeString(".text"),
647 .sh_type = elf.SHT_PROGBITS,
648 .sh_flags = elf.SHF_ALLOC | elf.SHF_EXECINSTR,
649 .sh_addr = phdr.p_vaddr,
650 .sh_offset = phdr.p_offset,
651 .sh_size = phdr.p_filesz,
652 .sh_link = 0,
653 .sh_info = 0,
654 .sh_addralign = phdr.p_align,
655 .sh_entsize = 0,
656 });
657 self.shdr_table_dirty = true;
658 }
659 if (self.got_section_index == null) {
660 self.got_section_index = @intCast(u16, self.sections.items.len);
661 const phdr = &self.program_headers.items[self.phdr_got_index.?];
662
663 try self.sections.append(self.allocator, .{
664 .sh_name = try self.makeString(".got"),
665 .sh_type = elf.SHT_PROGBITS,
666 .sh_flags = elf.SHF_ALLOC,
667 .sh_addr = phdr.p_vaddr,
668 .sh_offset = phdr.p_offset,
669 .sh_size = phdr.p_filesz,
670 .sh_link = 0,
671 .sh_info = 0,
672 .sh_addralign = phdr.p_align,
673 .sh_entsize = 0,
674 });
675 self.shdr_table_dirty = true;
676 }
677 if (self.symtab_section_index == null) {
678 self.symtab_section_index = @intCast(u16, self.sections.items.len);
679 const min_align: u16 = if (small_ptr) @alignOf(elf.Elf32_Sym) else @alignOf(elf.Elf64_Sym);
680 const each_size: u64 = if (small_ptr) @sizeOf(elf.Elf32_Sym) else @sizeOf(elf.Elf64_Sym);
681 const file_size = self.options.symbol_count_hint * each_size;
682 const off = self.findFreeSpace(file_size, min_align);
683 std.log.debug(.link, "found symtab free space 0x{x} to 0x{x}\n", .{ off, off + file_size });
684
685 try self.sections.append(self.allocator, .{
686 .sh_name = try self.makeString(".symtab"),
687 .sh_type = elf.SHT_SYMTAB,
688 .sh_flags = 0,
689 .sh_addr = 0,
690 .sh_offset = off,
691 .sh_size = file_size,
692 // The section header index of the associated string table.
693 .sh_link = self.shstrtab_index.?,
694 .sh_info = @intCast(u32, self.local_symbols.items.len),
695 .sh_addralign = min_align,
696 .sh_entsize = each_size,
697 });
698 self.shdr_table_dirty = true;
699 try self.writeSymbol(0);
700 }
600 const shsize: u64 = switch (self.ptr_width) {701 const shsize: u64 = switch (self.ptr_width) {
601 .p32 => @sizeOf(elf.Elf32_Shdr),702 .p32 => @sizeOf(elf.Elf32_Shdr),
602 .p64 => @sizeOf(elf.Elf64_Shdr),703 .p64 => @sizeOf(elf.Elf64_Shdr),
...@@ -605,757 +706,867 @@ pub const ElfFile = struct {...@@ -605,757 +706,867 @@ pub const ElfFile = struct {
605 .p32 => @alignOf(elf.Elf32_Shdr),706 .p32 => @alignOf(elf.Elf32_Shdr),
606 .p64 => @alignOf(elf.Elf64_Shdr),707 .p64 => @alignOf(elf.Elf64_Shdr),
607 };708 };
608 const allocated_size = self.allocatedSize(self.shdr_table_offset.?);709 if (self.shdr_table_offset == null) {
609 const needed_size = self.sections.items.len * shsize;710 self.shdr_table_offset = self.findFreeSpace(self.sections.items.len * shsize, shalign);
610711 self.shdr_table_dirty = true;
611 if (needed_size > allocated_size) {712 }
612 self.shdr_table_offset = null; // free the space713 const phsize: u64 = switch (self.ptr_width) {
613 self.shdr_table_offset = self.findFreeSpace(needed_size, shalign);714 .p32 => @sizeOf(elf.Elf32_Phdr),
715 .p64 => @sizeOf(elf.Elf64_Phdr),
716 };
717 const phalign: u16 = switch (self.ptr_width) {
718 .p32 => @alignOf(elf.Elf32_Phdr),
719 .p64 => @alignOf(elf.Elf64_Phdr),
720 };
721 if (self.phdr_table_offset == null) {
722 self.phdr_table_offset = self.findFreeSpace(self.program_headers.items.len * phsize, phalign);
723 self.phdr_table_dirty = true;
724 }
725 {
726 // Iterate over symbols, populating free_list and last_text_block.
727 if (self.local_symbols.items.len != 1) {
728 @panic("TODO implement setting up free_list and last_text_block from existing ELF file");
729 }
730 // We are starting with an empty file. The default values are correct, null and empty list.
614 }731 }
732 }
615733
616 switch (self.ptr_width) {734 /// Commit pending changes and write headers.
617 .p32 => {735 pub fn flush(self: *Elf) !void {
618 const buf = try self.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len);736 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
619 defer self.allocator.free(buf);
620737
621 for (buf) |*shdr, i| {738 // Unfortunately these have to be buffered and done at the end because ELF does not allow
622 shdr.* = sectHeaderTo32(self.sections.items[i]);739 // mixing local and global symbols within a symbol table.
623 if (foreign_endian) {740 try self.writeAllGlobalSymbols();
624 bswapAllFields(elf.Elf32_Shdr, shdr);741
742 if (self.phdr_table_dirty) {
743 const phsize: u64 = switch (self.ptr_width) {
744 .p32 => @sizeOf(elf.Elf32_Phdr),
745 .p64 => @sizeOf(elf.Elf64_Phdr),
746 };
747 const phalign: u16 = switch (self.ptr_width) {
748 .p32 => @alignOf(elf.Elf32_Phdr),
749 .p64 => @alignOf(elf.Elf64_Phdr),
750 };
751 const allocated_size = self.allocatedSize(self.phdr_table_offset.?);
752 const needed_size = self.program_headers.items.len * phsize;
753
754 if (needed_size > allocated_size) {
755 self.phdr_table_offset = null; // free the space
756 self.phdr_table_offset = self.findFreeSpace(needed_size, phalign);
757 }
758
759 switch (self.ptr_width) {
760 .p32 => {
761 const buf = try self.allocator.alloc(elf.Elf32_Phdr, self.program_headers.items.len);
762 defer self.allocator.free(buf);
763
764 for (buf) |*phdr, i| {
765 phdr.* = progHeaderTo32(self.program_headers.items[i]);
766 if (foreign_endian) {
767 bswapAllFields(elf.Elf32_Phdr, phdr);
768 }
625 }769 }
770 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
771 },
772 .p64 => {
773 const buf = try self.allocator.alloc(elf.Elf64_Phdr, self.program_headers.items.len);
774 defer self.allocator.free(buf);
775
776 for (buf) |*phdr, i| {
777 phdr.* = self.program_headers.items[i];
778 if (foreign_endian) {
779 bswapAllFields(elf.Elf64_Phdr, phdr);
780 }
781 }
782 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.phdr_table_offset.?);
783 },
784 }
785 self.phdr_table_dirty = false;
786 }
787
788 {
789 const shstrtab_sect = &self.sections.items[self.shstrtab_index.?];
790 if (self.shstrtab_dirty or self.shstrtab.items.len != shstrtab_sect.sh_size) {
791 const allocated_size = self.allocatedSize(shstrtab_sect.sh_offset);
792 const needed_size = self.shstrtab.items.len;
793
794 if (needed_size > allocated_size) {
795 shstrtab_sect.sh_size = 0; // free the space
796 shstrtab_sect.sh_offset = self.findFreeSpace(needed_size, 1);
626 }797 }
627 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);798 shstrtab_sect.sh_size = needed_size;
628 },799 std.log.debug(.link, "shstrtab start=0x{x} end=0x{x}\n", .{ shstrtab_sect.sh_offset, shstrtab_sect.sh_offset + needed_size });
629 .p64 => {
630 const buf = try self.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len);
631 defer self.allocator.free(buf);
632800
633 for (buf) |*shdr, i| {801 try self.file.?.pwriteAll(self.shstrtab.items, shstrtab_sect.sh_offset);
634 shdr.* = self.sections.items[i];802 if (!self.shdr_table_dirty) {
635 std.log.debug(.link, "writing section {}\n", .{shdr.*});803 // Then it won't get written with the others and we need to do it.
636 if (foreign_endian) {804 try self.writeSectHeader(self.shstrtab_index.?);
637 bswapAllFields(elf.Elf64_Shdr, shdr);
638 }
639 }805 }
640 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);806 self.shstrtab_dirty = false;
641 },807 }
642 }808 }
643 self.shdr_table_dirty = false;809 if (self.shdr_table_dirty) {
644 }810 const shsize: u64 = switch (self.ptr_width) {
645 if (self.entry_addr == null and self.options.output_mode == .Exe) {811 .p32 => @sizeOf(elf.Elf32_Shdr),
646 std.log.debug(.link, "no_entry_point_found = true\n", .{});812 .p64 => @sizeOf(elf.Elf64_Shdr),
647 self.error_flags.no_entry_point_found = true;813 };
648 } else {814 const shalign: u16 = switch (self.ptr_width) {
649 self.error_flags.no_entry_point_found = false;815 .p32 => @alignOf(elf.Elf32_Shdr),
650 try self.writeElfHeader();816 .p64 => @alignOf(elf.Elf64_Shdr),
651 }817 };
818 const allocated_size = self.allocatedSize(self.shdr_table_offset.?);
819 const needed_size = self.sections.items.len * shsize;
652820
653 // The point of flush() is to commit changes, so nothing should be dirty after this.821 if (needed_size > allocated_size) {
654 assert(!self.phdr_table_dirty);822 self.shdr_table_offset = null; // free the space
655 assert(!self.shdr_table_dirty);823 self.shdr_table_offset = self.findFreeSpace(needed_size, shalign);
656 assert(!self.shstrtab_dirty);824 }
657 assert(!self.offset_table_count_dirty);
658 const syms_sect = &self.sections.items[self.symtab_section_index.?];
659 assert(syms_sect.sh_info == self.local_symbols.items.len);
660 }
661825
662 fn writeElfHeader(self: *ElfFile) !void {826 switch (self.ptr_width) {
663 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;827 .p32 => {
828 const buf = try self.allocator.alloc(elf.Elf32_Shdr, self.sections.items.len);
829 defer self.allocator.free(buf);
664830
665 var index: usize = 0;831 for (buf) |*shdr, i| {
666 hdr_buf[0..4].* = "\x7fELF".*;832 shdr.* = sectHeaderTo32(self.sections.items[i]);
667 index += 4;833 if (foreign_endian) {
834 bswapAllFields(elf.Elf32_Shdr, shdr);
835 }
836 }
837 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);
838 },
839 .p64 => {
840 const buf = try self.allocator.alloc(elf.Elf64_Shdr, self.sections.items.len);
841 defer self.allocator.free(buf);
842
843 for (buf) |*shdr, i| {
844 shdr.* = self.sections.items[i];
845 std.log.debug(.link, "writing section {}\n", .{shdr.*});
846 if (foreign_endian) {
847 bswapAllFields(elf.Elf64_Shdr, shdr);
848 }
849 }
850 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), self.shdr_table_offset.?);
851 },
852 }
853 self.shdr_table_dirty = false;
854 }
855 if (self.entry_addr == null and self.options.output_mode == .Exe) {
856 std.log.debug(.link, "no_entry_point_found = true\n", .{});
857 self.error_flags.no_entry_point_found = true;
858 } else {
859 self.error_flags.no_entry_point_found = false;
860 try self.writeElfHeader();
861 }
668862
669 hdr_buf[index] = switch (self.ptr_width) {863 // The point of flush() is to commit changes, so nothing should be dirty after this.
670 .p32 => elf.ELFCLASS32,864 assert(!self.phdr_table_dirty);
671 .p64 => elf.ELFCLASS64,865 assert(!self.shdr_table_dirty);
672 };866 assert(!self.shstrtab_dirty);
673 index += 1;867 assert(!self.offset_table_count_dirty);
868 const syms_sect = &self.sections.items[self.symtab_section_index.?];
869 assert(syms_sect.sh_info == self.local_symbols.items.len);
870 }
674871
675 const endian = self.options.target.cpu.arch.endian();872 fn writeElfHeader(self: *Elf) !void {
676 hdr_buf[index] = switch (endian) {873 var hdr_buf: [@sizeOf(elf.Elf64_Ehdr)]u8 = undefined;
677 .Little => elf.ELFDATA2LSB,
678 .Big => elf.ELFDATA2MSB,
679 };
680 index += 1;
681874
682 hdr_buf[index] = 1; // ELF version875 var index: usize = 0;
683 index += 1;876 hdr_buf[0..4].* = "\x7fELF".*;
877 index += 4;
684878
685 // OS ABI, often set to 0 regardless of target platform879 hdr_buf[index] = switch (self.ptr_width) {
686 // ABI Version, possibly used by glibc but not by static executables880 .p32 => elf.ELFCLASS32,
687 // padding881 .p64 => elf.ELFCLASS64,
688 mem.set(u8, hdr_buf[index..][0..9], 0);882 };
689 index += 9;883 index += 1;
690884
691 assert(index == 16);885 const endian = self.options.target.cpu.arch.endian();
886 hdr_buf[index] = switch (endian) {
887 .Little => elf.ELFDATA2LSB,
888 .Big => elf.ELFDATA2MSB,
889 };
890 index += 1;
692891
693 const elf_type = switch (self.options.output_mode) {892 hdr_buf[index] = 1; // ELF version
694 .Exe => elf.ET.EXEC,893 index += 1;
695 .Obj => elf.ET.REL,
696 .Lib => switch (self.options.link_mode) {
697 .Static => elf.ET.REL,
698 .Dynamic => elf.ET.DYN,
699 },
700 };
701 mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
702 index += 2;
703894
704 const machine = self.options.target.cpu.arch.toElfMachine();895 // OS ABI, often set to 0 regardless of target platform
705 mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian);896 // ABI Version, possibly used by glibc but not by static executables
706 index += 2;897 // padding
898 mem.set(u8, hdr_buf[index..][0..9], 0);
899 index += 9;
707900
708 // ELF Version, again901 assert(index == 16);
709 mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian);
710 index += 4;
711902
712 const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?;903 const elf_type = switch (self.options.output_mode) {
904 .Exe => elf.ET.EXEC,
905 .Obj => elf.ET.REL,
906 .Lib => switch (self.options.link_mode) {
907 .Static => elf.ET.REL,
908 .Dynamic => elf.ET.DYN,
909 },
910 };
911 mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(elf_type), endian);
912 index += 2;
713913
714 switch (self.ptr_width) {914 const machine = self.options.target.cpu.arch.toElfMachine();
715 .p32 => {915 mem.writeInt(u16, hdr_buf[index..][0..2], @enumToInt(machine), endian);
716 mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian);916 index += 2;
717 index += 4;
718917
719 // e_phoff918 // ELF Version, again
720 mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.phdr_table_offset.?), endian);919 mem.writeInt(u32, hdr_buf[index..][0..4], 1, endian);
721 index += 4;920 index += 4;
722921
723 // e_shoff922 const e_entry = if (elf_type == .REL) 0 else self.entry_addr.?;
724 mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.shdr_table_offset.?), endian);
725 index += 4;
726 },
727 .p64 => {
728 // e_entry
729 mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian);
730 index += 8;
731
732 // e_phoff
733 mem.writeInt(u64, hdr_buf[index..][0..8], self.phdr_table_offset.?, endian);
734 index += 8;
735
736 // e_shoff
737 mem.writeInt(u64, hdr_buf[index..][0..8], self.shdr_table_offset.?, endian);
738 index += 8;
739 },
740 }
741923
742 const e_flags = 0;924 switch (self.ptr_width) {
743 mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian);925 .p32 => {
744 index += 4;926 mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, e_entry), endian);
927 index += 4;
745928
746 const e_ehsize: u16 = switch (self.ptr_width) {929 // e_phoff
747 .p32 => @sizeOf(elf.Elf32_Ehdr),930 mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.phdr_table_offset.?), endian);
748 .p64 => @sizeOf(elf.Elf64_Ehdr),931 index += 4;
749 };
750 mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian);
751 index += 2;
752932
753 const e_phentsize: u16 = switch (self.ptr_width) {933 // e_shoff
754 .p32 => @sizeOf(elf.Elf32_Phdr),934 mem.writeInt(u32, hdr_buf[index..][0..4], @intCast(u32, self.shdr_table_offset.?), endian);
755 .p64 => @sizeOf(elf.Elf64_Phdr),935 index += 4;
756 };936 },
757 mem.writeInt(u16, hdr_buf[index..][0..2], e_phentsize, endian);937 .p64 => {
758 index += 2;938 // e_entry
939 mem.writeInt(u64, hdr_buf[index..][0..8], e_entry, endian);
940 index += 8;
759941
760 const e_phnum = @intCast(u16, self.program_headers.items.len);942 // e_phoff
761 mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian);943 mem.writeInt(u64, hdr_buf[index..][0..8], self.phdr_table_offset.?, endian);
762 index += 2;944 index += 8;
763945
764 const e_shentsize: u16 = switch (self.ptr_width) {946 // e_shoff
765 .p32 => @sizeOf(elf.Elf32_Shdr),947 mem.writeInt(u64, hdr_buf[index..][0..8], self.shdr_table_offset.?, endian);
766 .p64 => @sizeOf(elf.Elf64_Shdr),948 index += 8;
767 };949 },
768 mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian);950 }
769 index += 2;
770951
771 const e_shnum = @intCast(u16, self.sections.items.len);952 const e_flags = 0;
772 mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian);953 mem.writeInt(u32, hdr_buf[index..][0..4], e_flags, endian);
773 index += 2;954 index += 4;
774955
775 mem.writeInt(u16, hdr_buf[index..][0..2], self.shstrtab_index.?, endian);956 const e_ehsize: u16 = switch (self.ptr_width) {
776 index += 2;957 .p32 => @sizeOf(elf.Elf32_Ehdr),
958 .p64 => @sizeOf(elf.Elf64_Ehdr),
959 };
960 mem.writeInt(u16, hdr_buf[index..][0..2], e_ehsize, endian);
961 index += 2;
777962
778 assert(index == e_ehsize);963 const e_phentsize: u16 = switch (self.ptr_width) {
964 .p32 => @sizeOf(elf.Elf32_Phdr),
965 .p64 => @sizeOf(elf.Elf64_Phdr),
966 };
967 mem.writeInt(u16, hdr_buf[index..][0..2], e_phentsize, endian);
968 index += 2;
779969
780 try self.file.?.pwriteAll(hdr_buf[0..index], 0);970 const e_phnum = @intCast(u16, self.program_headers.items.len);
781 }971 mem.writeInt(u16, hdr_buf[index..][0..2], e_phnum, endian);
972 index += 2;
782973
783 fn freeTextBlock(self: *ElfFile, text_block: *TextBlock) void {974 const e_shentsize: u16 = switch (self.ptr_width) {
784 var already_have_free_list_node = false;975 .p32 => @sizeOf(elf.Elf32_Shdr),
785 {976 .p64 => @sizeOf(elf.Elf64_Shdr),
786 var i: usize = 0;977 };
787 while (i < self.text_block_free_list.items.len) {978 mem.writeInt(u16, hdr_buf[index..][0..2], e_shentsize, endian);
788 if (self.text_block_free_list.items[i] == text_block) {979 index += 2;
789 _ = self.text_block_free_list.swapRemove(i);
790 continue;
791 }
792 if (self.text_block_free_list.items[i] == text_block.prev) {
793 already_have_free_list_node = true;
794 }
795 i += 1;
796 }
797 }
798980
799 if (self.last_text_block == text_block) {981 const e_shnum = @intCast(u16, self.sections.items.len);
800 // TODO shrink the .text section size here982 mem.writeInt(u16, hdr_buf[index..][0..2], e_shnum, endian);
801 self.last_text_block = text_block.prev;983 index += 2;
802 }
803984
804 if (text_block.prev) |prev| {985 mem.writeInt(u16, hdr_buf[index..][0..2], self.shstrtab_index.?, endian);
805 prev.next = text_block.next;986 index += 2;
806987
807 if (!already_have_free_list_node and prev.freeListEligible(self.*)) {988 assert(index == e_ehsize);
808 // The free list is heuristics, it doesn't have to be perfect, so we can
809 // ignore the OOM here.
810 self.text_block_free_list.append(self.allocator, prev) catch {};
811 }
812 } else {
813 text_block.prev = null;
814 }
815989
816 if (text_block.next) |next| {990 try self.file.?.pwriteAll(hdr_buf[0..index], 0);
817 next.prev = text_block.prev;
818 } else {
819 text_block.next = null;
820 }991 }
821 }
822992
823 fn shrinkTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64) void {993 fn freeTextBlock(self: *Elf, text_block: *TextBlock) void {
824 // TODO check the new capacity, and if it crosses the size threshold into a big enough994 var already_have_free_list_node = false;
825 // capacity, insert a free list node for it.995 {
826 }996 var i: usize = 0;
827997 while (i < self.text_block_free_list.items.len) {
828 fn growTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {998 if (self.text_block_free_list.items[i] == text_block) {
829 const sym = self.local_symbols.items[text_block.local_sym_index];
830 const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value;
831 const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*);
832 if (!need_realloc) return sym.st_value;
833 return self.allocateTextBlock(text_block, new_block_size, alignment);
834 }
835
836 fn allocateTextBlock(self: *ElfFile, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
837 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
838 const shdr = &self.sections.items[self.text_section_index.?];
839 const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den;
840
841 // We use these to indicate our intention to update metadata, placing the new block,
842 // and possibly removing a free list node.
843 // It would be simpler to do it inside the for loop below, but that would cause a
844 // problem if an error was returned later in the function. So this action
845 // is actually carried out at the end of the function, when errors are no longer possible.
846 var block_placement: ?*TextBlock = null;
847 var free_list_removal: ?usize = null;
848
849 // First we look for an appropriately sized free list node.
850 // The list is unordered. We'll just take the first thing that works.
851 const vaddr = blk: {
852 var i: usize = 0;
853 while (i < self.text_block_free_list.items.len) {
854 const big_block = self.text_block_free_list.items[i];
855 // We now have a pointer to a live text block that has too much capacity.
856 // Is it enough that we could fit this new text block?
857 const sym = self.local_symbols.items[big_block.local_sym_index];
858 const capacity = big_block.capacity(self.*);
859 const ideal_capacity = capacity * alloc_num / alloc_den;
860 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
861 const capacity_end_vaddr = sym.st_value + capacity;
862 const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity;
863 const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment);
864 if (new_start_vaddr < ideal_capacity_end_vaddr) {
865 // Additional bookkeeping here to notice if this free list node
866 // should be deleted because the block that it points to has grown to take up
867 // more of the extra capacity.
868 if (!big_block.freeListEligible(self.*)) {
869 _ = self.text_block_free_list.swapRemove(i);999 _ = self.text_block_free_list.swapRemove(i);
870 } else {1000 continue;
871 i += 1;
872 }1001 }
873 continue;1002 if (self.text_block_free_list.items[i] == text_block.prev) {
874 }1003 already_have_free_list_node = true;
875 // At this point we know that we will place the new block here. But the1004 }
876 // remaining question is whether there is still yet enough capacity left1005 i += 1;
877 // over for there to still be a free list node.
878 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
879 const keep_free_list_node = remaining_capacity >= min_text_capacity;
880
881 // Set up the metadata to be updated, after errors are no longer possible.
882 block_placement = big_block;
883 if (!keep_free_list_node) {
884 free_list_removal = i;
885 }1006 }
886 break :blk new_start_vaddr;
887 } else if (self.last_text_block) |last| {
888 const sym = self.local_symbols.items[last.local_sym_index];
889 const ideal_capacity = sym.st_size * alloc_num / alloc_den;
890 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
891 const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment);
892 // Set up the metadata to be updated, after errors are no longer possible.
893 block_placement = last;
894 break :blk new_start_vaddr;
895 } else {
896 break :blk phdr.p_vaddr;
897 }1007 }
898 };
8991008
900 const expand_text_section = block_placement == null or block_placement.?.next == null;1009 if (self.last_text_block == text_block) {
901 if (expand_text_section) {1010 // TODO shrink the .text section size here
902 const text_capacity = self.allocatedSize(shdr.sh_offset);1011 self.last_text_block = text_block.prev;
903 const needed_size = (vaddr + new_block_size) - phdr.p_vaddr;
904 if (needed_size > text_capacity) {
905 // Must move the entire text section.
906 const new_offset = self.findFreeSpace(needed_size, 0x1000);
907 const text_size = if (self.last_text_block) |last| blk: {
908 const sym = self.local_symbols.items[last.local_sym_index];
909 break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr;
910 } else 0;
911 const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size);
912 if (amt != text_size) return error.InputOutput;
913 shdr.sh_offset = new_offset;
914 phdr.p_offset = new_offset;
915 }1012 }
916 self.last_text_block = text_block;
9171013
918 shdr.sh_size = needed_size;1014 if (text_block.prev) |prev| {
919 phdr.p_memsz = needed_size;1015 prev.next = text_block.next;
920 phdr.p_filesz = needed_size;
9211016
922 self.phdr_table_dirty = true; // TODO look into making only the one program header dirty1017 if (!already_have_free_list_node and prev.freeListEligible(self.*)) {
923 self.shdr_table_dirty = true; // TODO look into making only the one section dirty1018 // The free list is heuristics, it doesn't have to be perfect, so we can
924 }1019 // ignore the OOM here.
1020 self.text_block_free_list.append(self.allocator, prev) catch {};
1021 }
1022 } else {
1023 text_block.prev = null;
1024 }
9251025
926 // This function can also reallocate a text block.1026 if (text_block.next) |next| {
927 // In this case we need to "unplug" it from its previous location before1027 next.prev = text_block.prev;
928 // plugging it in to its new location.1028 } else {
929 if (text_block.prev) |prev| {1029 text_block.next = null;
930 prev.next = text_block.next;1030 }
931 }
932 if (text_block.next) |next| {
933 next.prev = text_block.prev;
934 }1031 }
9351032
936 if (block_placement) |big_block| {1033 fn shrinkTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64) void {
937 text_block.prev = big_block;1034 // TODO check the new capacity, and if it crosses the size threshold into a big enough
938 text_block.next = big_block.next;1035 // capacity, insert a free list node for it.
939 big_block.next = text_block;
940 } else {
941 text_block.prev = null;
942 text_block.next = null;
943 }1036 }
944 if (free_list_removal) |i| {
945 _ = self.text_block_free_list.swapRemove(i);
946 }
947 return vaddr;
948 }
9491037
950 pub fn allocateDeclIndexes(self: *ElfFile, decl: *Module.Decl) !void {1038 fn growTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
951 if (decl.link.local_sym_index != 0) return;1039 const sym = self.local_symbols.items[text_block.local_sym_index];
9521040 const align_ok = mem.alignBackwardGeneric(u64, sym.st_value, alignment) == sym.st_value;
953 // Here we also ensure capacity for the free lists so that they can be appended to without fail.1041 const need_realloc = !align_ok or new_block_size > text_block.capacity(self.*);
954 try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);1042 if (!need_realloc) return sym.st_value;
955 try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);1043 return self.allocateTextBlock(text_block, new_block_size, alignment);
956 try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
957 try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
958
959 if (self.local_symbol_free_list.popOrNull()) |i| {
960 std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name});
961 decl.link.local_sym_index = i;
962 } else {
963 std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name});
964 decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len);
965 _ = self.local_symbols.addOneAssumeCapacity();
966 }1044 }
9671045
968 if (self.offset_table_free_list.popOrNull()) |i| {1046 fn allocateTextBlock(self: *Elf, text_block: *TextBlock, new_block_size: u64, alignment: u64) !u64 {
969 decl.link.offset_table_index = i;1047 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
970 } else {1048 const shdr = &self.sections.items[self.text_section_index.?];
971 decl.link.offset_table_index = @intCast(u32, self.offset_table.items.len);1049 const new_block_ideal_capacity = new_block_size * alloc_num / alloc_den;
972 _ = self.offset_table.addOneAssumeCapacity();1050
973 self.offset_table_count_dirty = true;1051 // We use these to indicate our intention to update metadata, placing the new block,
974 }1052 // and possibly removing a free list node.
1053 // It would be simpler to do it inside the for loop below, but that would cause a
1054 // problem if an error was returned later in the function. So this action
1055 // is actually carried out at the end of the function, when errors are no longer possible.
1056 var block_placement: ?*TextBlock = null;
1057 var free_list_removal: ?usize = null;
1058
1059 // First we look for an appropriately sized free list node.
1060 // The list is unordered. We'll just take the first thing that works.
1061 const vaddr = blk: {
1062 var i: usize = 0;
1063 while (i < self.text_block_free_list.items.len) {
1064 const big_block = self.text_block_free_list.items[i];
1065 // We now have a pointer to a live text block that has too much capacity.
1066 // Is it enough that we could fit this new text block?
1067 const sym = self.local_symbols.items[big_block.local_sym_index];
1068 const capacity = big_block.capacity(self.*);
1069 const ideal_capacity = capacity * alloc_num / alloc_den;
1070 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
1071 const capacity_end_vaddr = sym.st_value + capacity;
1072 const new_start_vaddr_unaligned = capacity_end_vaddr - new_block_ideal_capacity;
1073 const new_start_vaddr = mem.alignBackwardGeneric(u64, new_start_vaddr_unaligned, alignment);
1074 if (new_start_vaddr < ideal_capacity_end_vaddr) {
1075 // Additional bookkeeping here to notice if this free list node
1076 // should be deleted because the block that it points to has grown to take up
1077 // more of the extra capacity.
1078 if (!big_block.freeListEligible(self.*)) {
1079 _ = self.text_block_free_list.swapRemove(i);
1080 } else {
1081 i += 1;
1082 }
1083 continue;
1084 }
1085 // At this point we know that we will place the new block here. But the
1086 // remaining question is whether there is still yet enough capacity left
1087 // over for there to still be a free list node.
1088 const remaining_capacity = new_start_vaddr - ideal_capacity_end_vaddr;
1089 const keep_free_list_node = remaining_capacity >= min_text_capacity;
1090
1091 // Set up the metadata to be updated, after errors are no longer possible.
1092 block_placement = big_block;
1093 if (!keep_free_list_node) {
1094 free_list_removal = i;
1095 }
1096 break :blk new_start_vaddr;
1097 } else if (self.last_text_block) |last| {
1098 const sym = self.local_symbols.items[last.local_sym_index];
1099 const ideal_capacity = sym.st_size * alloc_num / alloc_den;
1100 const ideal_capacity_end_vaddr = sym.st_value + ideal_capacity;
1101 const new_start_vaddr = mem.alignForwardGeneric(u64, ideal_capacity_end_vaddr, alignment);
1102 // Set up the metadata to be updated, after errors are no longer possible.
1103 block_placement = last;
1104 break :blk new_start_vaddr;
1105 } else {
1106 break :blk phdr.p_vaddr;
1107 }
1108 };
9751109
976 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];1110 const expand_text_section = block_placement == null or block_placement.?.next == null;
1111 if (expand_text_section) {
1112 const text_capacity = self.allocatedSize(shdr.sh_offset);
1113 const needed_size = (vaddr + new_block_size) - phdr.p_vaddr;
1114 if (needed_size > text_capacity) {
1115 // Must move the entire text section.
1116 const new_offset = self.findFreeSpace(needed_size, 0x1000);
1117 const text_size = if (self.last_text_block) |last| blk: {
1118 const sym = self.local_symbols.items[last.local_sym_index];
1119 break :blk (sym.st_value + sym.st_size) - phdr.p_vaddr;
1120 } else 0;
1121 const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, text_size);
1122 if (amt != text_size) return error.InputOutput;
1123 shdr.sh_offset = new_offset;
1124 phdr.p_offset = new_offset;
1125 }
1126 self.last_text_block = text_block;
9771127
978 self.local_symbols.items[decl.link.local_sym_index] = .{1128 shdr.sh_size = needed_size;
979 .st_name = 0,1129 phdr.p_memsz = needed_size;
980 .st_info = 0,1130 phdr.p_filesz = needed_size;
981 .st_other = 0,
982 .st_shndx = 0,
983 .st_value = phdr.p_vaddr,
984 .st_size = 0,
985 };
986 self.offset_table.items[decl.link.offset_table_index] = 0;
987 }
9881131
989 pub fn freeDecl(self: *ElfFile, decl: *Module.Decl) void {1132 self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
990 self.freeTextBlock(&decl.link);1133 self.shdr_table_dirty = true; // TODO look into making only the one section dirty
991 if (decl.link.local_sym_index != 0) {1134 }
992 self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index);
993 self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index);
9941135
995 self.local_symbols.items[decl.link.local_sym_index].st_info = 0;1136 // This function can also reallocate a text block.
1137 // In this case we need to "unplug" it from its previous location before
1138 // plugging it in to its new location.
1139 if (text_block.prev) |prev| {
1140 prev.next = text_block.next;
1141 }
1142 if (text_block.next) |next| {
1143 next.prev = text_block.prev;
1144 }
9961145
997 decl.link.local_sym_index = 0;1146 if (block_placement) |big_block| {
1147 text_block.prev = big_block;
1148 text_block.next = big_block.next;
1149 big_block.next = text_block;
1150 } else {
1151 text_block.prev = null;
1152 text_block.next = null;
1153 }
1154 if (free_list_removal) |i| {
1155 _ = self.text_block_free_list.swapRemove(i);
1156 }
1157 return vaddr;
998 }1158 }
999 }
10001159
1001 pub fn updateDecl(self: *ElfFile, module: *Module, decl: *Module.Decl) !void {1160 pub fn allocateDeclIndexes(self: *Elf, decl: *Module.Decl) !void {
1002 var code_buffer = std.ArrayList(u8).init(self.allocator);1161 if (decl.link.local_sym_index != 0) return;
1003 defer code_buffer.deinit();
1004
1005 const typed_value = decl.typed_value.most_recent.typed_value;
1006 const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) {
1007 .externally_managed => |x| x,
1008 .appended => code_buffer.items,
1009 .fail => |em| {
1010 decl.analysis = .codegen_failure;
1011 _ = try module.failed_decls.put(module.gpa, decl, em);
1012 return;
1013 },
1014 };
10151162
1016 const required_alignment = typed_value.ty.abiAlignment(self.options.target);1163 // Here we also ensure capacity for the free lists so that they can be appended to without fail.
1164 try self.local_symbols.ensureCapacity(self.allocator, self.local_symbols.items.len + 1);
1165 try self.local_symbol_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
1166 try self.offset_table.ensureCapacity(self.allocator, self.offset_table.items.len + 1);
1167 try self.offset_table_free_list.ensureCapacity(self.allocator, self.local_symbols.items.len);
10171168
1018 const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {1169 if (self.local_symbol_free_list.popOrNull()) |i| {
1019 .Fn => elf.STT_FUNC,1170 std.log.debug(.link, "reusing symbol index {} for {}\n", .{i, decl.name});
1020 else => elf.STT_OBJECT,1171 decl.link.local_sym_index = i;
1021 };1172 } else {
1173 std.log.debug(.link, "allocating symbol index {} for {}\n", .{self.local_symbols.items.len, decl.name});
1174 decl.link.local_sym_index = @intCast(u32, self.local_symbols.items.len);
1175 _ = self.local_symbols.addOneAssumeCapacity();
1176 }
10221177
1023 assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()1178 if (self.offset_table_free_list.popOrNull()) |i| {
1024 const local_sym = &self.local_symbols.items[decl.link.local_sym_index];1179 decl.link.offset_table_index = i;
1025 if (local_sym.st_size != 0) {1180 } else {
1026 const capacity = decl.link.capacity(self.*);1181 decl.link.offset_table_index = @intCast(u32, self.offset_table.items.len);
1027 const need_realloc = code.len > capacity or1182 _ = self.offset_table.addOneAssumeCapacity();
1028 !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);1183 self.offset_table_count_dirty = true;
1029 if (need_realloc) {
1030 const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment);
1031 std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr });
1032 if (vaddr != local_sym.st_value) {
1033 local_sym.st_value = vaddr;
1034
1035 std.log.debug(.link, " (writing new offset table entry)\n", .{});
1036 self.offset_table.items[decl.link.offset_table_index] = vaddr;
1037 try self.writeOffsetTableEntry(decl.link.offset_table_index);
1038 }
1039 } else if (code.len < local_sym.st_size) {
1040 self.shrinkTextBlock(&decl.link, code.len);
1041 }1184 }
1042 local_sym.st_size = code.len;1185
1043 local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name));1186 const phdr = &self.program_headers.items[self.phdr_load_re_index.?];
1044 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;1187
1045 local_sym.st_other = 0;1188 self.local_symbols.items[decl.link.local_sym_index] = .{
1046 local_sym.st_shndx = self.text_section_index.?;1189 .st_name = 0,
1047 // TODO this write could be avoided if no fields of the symbol were changed.1190 .st_info = 0,
1048 try self.writeSymbol(decl.link.local_sym_index);
1049 } else {
1050 const decl_name = mem.spanZ(decl.name);
1051 const name_str_index = try self.makeString(decl_name);
1052 const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment);
1053 std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr });
1054 errdefer self.freeTextBlock(&decl.link);
1055
1056 local_sym.* = .{
1057 .st_name = name_str_index,
1058 .st_info = (elf.STB_LOCAL << 4) | stt_bits,
1059 .st_other = 0,1191 .st_other = 0,
1060 .st_shndx = self.text_section_index.?,1192 .st_shndx = 0,
1061 .st_value = vaddr,1193 .st_value = phdr.p_vaddr,
1062 .st_size = code.len,1194 .st_size = 0,
1063 };1195 };
1064 self.offset_table.items[decl.link.offset_table_index] = vaddr;1196 self.offset_table.items[decl.link.offset_table_index] = 0;
1065
1066 try self.writeSymbol(decl.link.local_sym_index);
1067 try self.writeOffsetTableEntry(decl.link.offset_table_index);
1068 }1197 }
10691198
1070 const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr;1199 pub fn freeDecl(self: *Elf, decl: *Module.Decl) void {
1071 const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset;1200 self.freeTextBlock(&decl.link);
1072 try self.file.?.pwriteAll(code, file_offset);1201 if (decl.link.local_sym_index != 0) {
1202 self.local_symbol_free_list.appendAssumeCapacity(decl.link.local_sym_index);
1203 self.offset_table_free_list.appendAssumeCapacity(decl.link.offset_table_index);
10731204
1074 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.1205 self.local_symbols.items[decl.link.local_sym_index].st_info = 0;
1075 const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
1076 return self.updateDeclExports(module, decl, decl_exports);
1077 }
10781206
1079 /// Must be called only after a successful call to `updateDecl`.1207 decl.link.local_sym_index = 0;
1080 pub fn updateDeclExports(
1081 self: *ElfFile,
1082 module: *Module,
1083 decl: *const Module.Decl,
1084 exports: []const *Module.Export,
1085 ) !void {
1086 // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of
1087 // them, so that deleting exports is guaranteed to succeed.
1088 try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len);
1089 try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len);
1090 const typed_value = decl.typed_value.most_recent.typed_value;
1091 if (decl.link.local_sym_index == 0) return;
1092 const decl_sym = self.local_symbols.items[decl.link.local_sym_index];
1093
1094 for (exports) |exp| {
1095 if (exp.options.section) |section_name| {
1096 if (!mem.eql(u8, section_name, ".text")) {
1097 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
1098 module.failed_exports.putAssumeCapacityNoClobber(
1099 exp,
1100 try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
1101 );
1102 continue;
1103 }
1104 }1208 }
1105 const stb_bits: u8 = switch (exp.options.linkage) {1209 }
1106 .Internal => elf.STB_LOCAL,1210
1107 .Strong => blk: {1211 pub fn updateDecl(self: *Elf, module: *Module, decl: *Module.Decl) !void {
1108 if (mem.eql(u8, exp.options.name, "_start")) {1212 var code_buffer = std.ArrayList(u8).init(self.allocator);
1109 self.entry_addr = decl_sym.st_value;1213 defer code_buffer.deinit();
1110 }1214
1111 break :blk elf.STB_GLOBAL;1215 const typed_value = decl.typed_value.most_recent.typed_value;
1112 },1216 const code = switch (try codegen.generateSymbol(self, decl.src(), typed_value, &code_buffer)) {
1113 .Weak => elf.STB_WEAK,1217 .externally_managed => |x| x,
1114 .LinkOnce => {1218 .appended => code_buffer.items,
1115 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);1219 .fail => |em| {
1116 module.failed_exports.putAssumeCapacityNoClobber(1220 decl.analysis = .codegen_failure;
1117 exp,1221 try module.failed_decls.put(module.gpa, decl, em);
1118 try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),1222 return;
1119 );
1120 continue;
1121 },1223 },
1122 };1224 };
1123 const stt_bits: u8 = @truncate(u4, decl_sym.st_info);1225
1124 if (exp.link.sym_index) |i| {1226 const required_alignment = typed_value.ty.abiAlignment(self.options.target);
1125 const sym = &self.global_symbols.items[i];1227
1126 sym.* = .{1228 const stt_bits: u8 = switch (typed_value.ty.zigTypeTag()) {
1127 .st_name = try self.updateString(sym.st_name, exp.options.name),1229 .Fn => elf.STT_FUNC,
1128 .st_info = (stb_bits << 4) | stt_bits,1230 else => elf.STT_OBJECT,
1129 .st_other = 0,1231 };
1130 .st_shndx = self.text_section_index.?,1232
1131 .st_value = decl_sym.st_value,1233 assert(decl.link.local_sym_index != 0); // Caller forgot to allocateDeclIndexes()
1132 .st_size = decl_sym.st_size,1234 const local_sym = &self.local_symbols.items[decl.link.local_sym_index];
1133 };1235 if (local_sym.st_size != 0) {
1236 const capacity = decl.link.capacity(self.*);
1237 const need_realloc = code.len > capacity or
1238 !mem.isAlignedGeneric(u64, local_sym.st_value, required_alignment);
1239 if (need_realloc) {
1240 const vaddr = try self.growTextBlock(&decl.link, code.len, required_alignment);
1241 std.log.debug(.link, "growing {} from 0x{x} to 0x{x}\n", .{ decl.name, local_sym.st_value, vaddr });
1242 if (vaddr != local_sym.st_value) {
1243 local_sym.st_value = vaddr;
1244
1245 std.log.debug(.link, " (writing new offset table entry)\n", .{});
1246 self.offset_table.items[decl.link.offset_table_index] = vaddr;
1247 try self.writeOffsetTableEntry(decl.link.offset_table_index);
1248 }
1249 } else if (code.len < local_sym.st_size) {
1250 self.shrinkTextBlock(&decl.link, code.len);
1251 }
1252 local_sym.st_size = code.len;
1253 local_sym.st_name = try self.updateString(local_sym.st_name, mem.spanZ(decl.name));
1254 local_sym.st_info = (elf.STB_LOCAL << 4) | stt_bits;
1255 local_sym.st_other = 0;
1256 local_sym.st_shndx = self.text_section_index.?;
1257 // TODO this write could be avoided if no fields of the symbol were changed.
1258 try self.writeSymbol(decl.link.local_sym_index);
1134 } else {1259 } else {
1135 const name = try self.makeString(exp.options.name);1260 const decl_name = mem.spanZ(decl.name);
1136 const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: {1261 const name_str_index = try self.makeString(decl_name);
1137 _ = self.global_symbols.addOneAssumeCapacity();1262 const vaddr = try self.allocateTextBlock(&decl.link, code.len, required_alignment);
1138 break :blk self.global_symbols.items.len - 1;1263 std.log.debug(.link, "allocated text block for {} at 0x{x}\n", .{ decl_name, vaddr });
1139 };1264 errdefer self.freeTextBlock(&decl.link);
1140 self.global_symbols.items[i] = .{1265
1141 .st_name = name,1266 local_sym.* = .{
1142 .st_info = (stb_bits << 4) | stt_bits,1267 .st_name = name_str_index,
1268 .st_info = (elf.STB_LOCAL << 4) | stt_bits,
1143 .st_other = 0,1269 .st_other = 0,
1144 .st_shndx = self.text_section_index.?,1270 .st_shndx = self.text_section_index.?,
1145 .st_value = decl_sym.st_value,1271 .st_value = vaddr,
1146 .st_size = decl_sym.st_size,1272 .st_size = code.len,
1147 };1273 };
1274 self.offset_table.items[decl.link.offset_table_index] = vaddr;
11481275
1149 exp.link.sym_index = @intCast(u32, i);1276 try self.writeSymbol(decl.link.local_sym_index);
1277 try self.writeOffsetTableEntry(decl.link.offset_table_index);
1150 }1278 }
1151 }
1152 }
11531279
1154 pub fn deleteExport(self: *ElfFile, exp: Export) void {1280 const section_offset = local_sym.st_value - self.program_headers.items[self.phdr_load_re_index.?].p_vaddr;
1155 const sym_index = exp.sym_index orelse return;1281 const file_offset = self.sections.items[self.text_section_index.?].sh_offset + section_offset;
1156 self.global_symbol_free_list.appendAssumeCapacity(sym_index);1282 try self.file.?.pwriteAll(code, file_offset);
1157 self.global_symbols.items[sym_index].st_info = 0;
1158 }
11591283
1160 fn writeProgHeader(self: *ElfFile, index: usize) !void {1284 // Since we updated the vaddr and the size, each corresponding export symbol also needs to be updated.
1161 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();1285 const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
1162 const offset = self.program_headers.items[index].p_offset;1286 return self.updateDeclExports(module, decl, decl_exports);
1163 switch (self.options.target.cpu.arch.ptrBitWidth()) {
1164 32 => {
1165 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
1166 if (foreign_endian) {
1167 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
1168 }
1169 return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
1170 },
1171 64 => {
1172 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};
1173 if (foreign_endian) {
1174 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
1175 }
1176 return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
1177 },
1178 else => return error.UnsupportedArchitecture,
1179 }1287 }
1180 }
11811288
1182 fn writeSectHeader(self: *ElfFile, index: usize) !void {1289 /// Must be called only after a successful call to `updateDecl`.
1183 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();1290 pub fn updateDeclExports(
1184 const offset = self.sections.items[index].sh_offset;1291 self: *Elf,
1185 switch (self.options.target.cpu.arch.ptrBitWidth()) {1292 module: *Module,
1186 32 => {1293 decl: *const Module.Decl,
1187 var shdr: [1]elf.Elf32_Shdr = undefined;1294 exports: []const *Module.Export,
1188 shdr[0] = sectHeaderTo32(self.sections.items[index]);1295 ) !void {
1189 if (foreign_endian) {1296 // In addition to ensuring capacity for global_symbols, we also ensure capacity for freeing all of
1190 bswapAllFields(elf.Elf32_Shdr, &shdr[0]);1297 // them, so that deleting exports is guaranteed to succeed.
1191 }1298 try self.global_symbols.ensureCapacity(self.allocator, self.global_symbols.items.len + exports.len);
1192 return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);1299 try self.global_symbol_free_list.ensureCapacity(self.allocator, self.global_symbols.items.len);
1193 },1300 const typed_value = decl.typed_value.most_recent.typed_value;
1194 64 => {1301 if (decl.link.local_sym_index == 0) return;
1195 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};1302 const decl_sym = self.local_symbols.items[decl.link.local_sym_index];
1196 if (foreign_endian) {1303
1197 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);1304 for (exports) |exp| {
1305 if (exp.options.section) |section_name| {
1306 if (!mem.eql(u8, section_name, ".text")) {
1307 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
1308 module.failed_exports.putAssumeCapacityNoClobber(
1309 exp,
1310 try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: ExportOptions.section", .{}),
1311 );
1312 continue;
1313 }
1198 }1314 }
1199 return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);1315 const stb_bits: u8 = switch (exp.options.linkage) {
1200 },1316 .Internal => elf.STB_LOCAL,
1201 else => return error.UnsupportedArchitecture,1317 .Strong => blk: {
1202 }1318 if (mem.eql(u8, exp.options.name, "_start")) {
1203 }1319 self.entry_addr = decl_sym.st_value;
1320 }
1321 break :blk elf.STB_GLOBAL;
1322 },
1323 .Weak => elf.STB_WEAK,
1324 .LinkOnce => {
1325 try module.failed_exports.ensureCapacity(module.gpa, module.failed_exports.items().len + 1);
1326 module.failed_exports.putAssumeCapacityNoClobber(
1327 exp,
1328 try Module.ErrorMsg.create(self.allocator, 0, "Unimplemented: GlobalLinkage.LinkOnce", .{}),
1329 );
1330 continue;
1331 },
1332 };
1333 const stt_bits: u8 = @truncate(u4, decl_sym.st_info);
1334 if (exp.link.sym_index) |i| {
1335 const sym = &self.global_symbols.items[i];
1336 sym.* = .{
1337 .st_name = try self.updateString(sym.st_name, exp.options.name),
1338 .st_info = (stb_bits << 4) | stt_bits,
1339 .st_other = 0,
1340 .st_shndx = self.text_section_index.?,
1341 .st_value = decl_sym.st_value,
1342 .st_size = decl_sym.st_size,
1343 };
1344 } else {
1345 const name = try self.makeString(exp.options.name);
1346 const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: {
1347 _ = self.global_symbols.addOneAssumeCapacity();
1348 break :blk self.global_symbols.items.len - 1;
1349 };
1350 self.global_symbols.items[i] = .{
1351 .st_name = name,
1352 .st_info = (stb_bits << 4) | stt_bits,
1353 .st_other = 0,
1354 .st_shndx = self.text_section_index.?,
1355 .st_value = decl_sym.st_value,
1356 .st_size = decl_sym.st_size,
1357 };
12041358
1205 fn writeOffsetTableEntry(self: *ElfFile, index: usize) !void {1359 exp.link.sym_index = @intCast(u32, i);
1206 const shdr = &self.sections.items[self.got_section_index.?];1360 }
1207 const phdr = &self.program_headers.items[self.phdr_got_index.?];
1208 const entry_size: u16 = switch (self.ptr_width) {
1209 .p32 => 4,
1210 .p64 => 8,
1211 };
1212 if (self.offset_table_count_dirty) {
1213 // TODO Also detect virtual address collisions.
1214 const allocated_size = self.allocatedSize(shdr.sh_offset);
1215 const needed_size = self.local_symbols.items.len * entry_size;
1216 if (needed_size > allocated_size) {
1217 // Must move the entire got section.
1218 const new_offset = self.findFreeSpace(needed_size, entry_size);
1219 const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, shdr.sh_size);
1220 if (amt != shdr.sh_size) return error.InputOutput;
1221 shdr.sh_offset = new_offset;
1222 phdr.p_offset = new_offset;
1223 }1361 }
1224 shdr.sh_size = needed_size;1362 }
1225 phdr.p_memsz = needed_size;
1226 phdr.p_filesz = needed_size;
12271363
1228 self.shdr_table_dirty = true; // TODO look into making only the one section dirty1364 pub fn deleteExport(self: *Elf, exp: Export) void {
1229 self.phdr_table_dirty = true; // TODO look into making only the one program header dirty1365 const sym_index = exp.sym_index orelse return;
1366 self.global_symbol_free_list.appendAssumeCapacity(sym_index);
1367 self.global_symbols.items[sym_index].st_info = 0;
1368 }
12301369
1231 self.offset_table_count_dirty = false;1370 fn writeProgHeader(self: *Elf, index: usize) !void {
1371 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
1372 const offset = self.program_headers.items[index].p_offset;
1373 switch (self.options.target.cpu.arch.ptrBitWidth()) {
1374 32 => {
1375 var phdr = [1]elf.Elf32_Phdr{progHeaderTo32(self.program_headers.items[index])};
1376 if (foreign_endian) {
1377 bswapAllFields(elf.Elf32_Phdr, &phdr[0]);
1378 }
1379 return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
1380 },
1381 64 => {
1382 var phdr = [1]elf.Elf64_Phdr{self.program_headers.items[index]};
1383 if (foreign_endian) {
1384 bswapAllFields(elf.Elf64_Phdr, &phdr[0]);
1385 }
1386 return self.file.?.pwriteAll(mem.sliceAsBytes(&phdr), offset);
1387 },
1388 else => return error.UnsupportedArchitecture,
1389 }
1232 }1390 }
1233 const endian = self.options.target.cpu.arch.endian();1391
1234 const off = shdr.sh_offset + @as(u64, entry_size) * index;1392 fn writeSectHeader(self: *Elf, index: usize) !void {
1235 switch (self.ptr_width) {1393 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
1236 .p32 => {1394 const offset = self.sections.items[index].sh_offset;
1237 var buf: [4]u8 = undefined;1395 switch (self.options.target.cpu.arch.ptrBitWidth()) {
1238 mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian);1396 32 => {
1239 try self.file.?.pwriteAll(&buf, off);1397 var shdr: [1]elf.Elf32_Shdr = undefined;
1240 },1398 shdr[0] = sectHeaderTo32(self.sections.items[index]);
1241 .p64 => {1399 if (foreign_endian) {
1242 var buf: [8]u8 = undefined;1400 bswapAllFields(elf.Elf32_Shdr, &shdr[0]);
1243 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);1401 }
1244 try self.file.?.pwriteAll(&buf, off);1402 return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
1245 },1403 },
1404 64 => {
1405 var shdr = [1]elf.Elf64_Shdr{self.sections.items[index]};
1406 if (foreign_endian) {
1407 bswapAllFields(elf.Elf64_Shdr, &shdr[0]);
1408 }
1409 return self.file.?.pwriteAll(mem.sliceAsBytes(&shdr), offset);
1410 },
1411 else => return error.UnsupportedArchitecture,
1412 }
1246 }1413 }
1247 }
12481414
1249 fn writeSymbol(self: *ElfFile, index: usize) !void {1415 fn writeOffsetTableEntry(self: *Elf, index: usize) !void {
1250 const syms_sect = &self.sections.items[self.symtab_section_index.?];1416 const shdr = &self.sections.items[self.got_section_index.?];
1251 // Make sure we are not pointlessly writing symbol data that will have to get relocated1417 const phdr = &self.program_headers.items[self.phdr_got_index.?];
1252 // due to running out of space.1418 const entry_size: u16 = switch (self.ptr_width) {
1253 if (self.local_symbols.items.len != syms_sect.sh_info) {1419 .p32 => 4,
1254 const sym_size: u64 = switch (self.ptr_width) {1420 .p64 => 8,
1255 .p32 => @sizeOf(elf.Elf32_Sym),
1256 .p64 => @sizeOf(elf.Elf64_Sym),
1257 };
1258 const sym_align: u16 = switch (self.ptr_width) {
1259 .p32 => @alignOf(elf.Elf32_Sym),
1260 .p64 => @alignOf(elf.Elf64_Sym),
1261 };1421 };
1262 const needed_size = (self.local_symbols.items.len + self.global_symbols.items.len) * sym_size;1422 if (self.offset_table_count_dirty) {
1263 if (needed_size > self.allocatedSize(syms_sect.sh_offset)) {1423 // TODO Also detect virtual address collisions.
1264 // Move all the symbols to a new file location.1424 const allocated_size = self.allocatedSize(shdr.sh_offset);
1265 const new_offset = self.findFreeSpace(needed_size, sym_align);1425 const needed_size = self.local_symbols.items.len * entry_size;
1266 const existing_size = @as(u64, syms_sect.sh_info) * sym_size;1426 if (needed_size > allocated_size) {
1267 const amt = try self.file.?.copyRangeAll(syms_sect.sh_offset, self.file.?, new_offset, existing_size);1427 // Must move the entire got section.
1268 if (amt != existing_size) return error.InputOutput;1428 const new_offset = self.findFreeSpace(needed_size, entry_size);
1269 syms_sect.sh_offset = new_offset;1429 const amt = try self.file.?.copyRangeAll(shdr.sh_offset, self.file.?, new_offset, shdr.sh_size);
1430 if (amt != shdr.sh_size) return error.InputOutput;
1431 shdr.sh_offset = new_offset;
1432 phdr.p_offset = new_offset;
1433 }
1434 shdr.sh_size = needed_size;
1435 phdr.p_memsz = needed_size;
1436 phdr.p_filesz = needed_size;
1437
1438 self.shdr_table_dirty = true; // TODO look into making only the one section dirty
1439 self.phdr_table_dirty = true; // TODO look into making only the one program header dirty
1440
1441 self.offset_table_count_dirty = false;
1442 }
1443 const endian = self.options.target.cpu.arch.endian();
1444 const off = shdr.sh_offset + @as(u64, entry_size) * index;
1445 switch (self.ptr_width) {
1446 .p32 => {
1447 var buf: [4]u8 = undefined;
1448 mem.writeInt(u32, &buf, @intCast(u32, self.offset_table.items[index]), endian);
1449 try self.file.?.pwriteAll(&buf, off);
1450 },
1451 .p64 => {
1452 var buf: [8]u8 = undefined;
1453 mem.writeInt(u64, &buf, self.offset_table.items[index], endian);
1454 try self.file.?.pwriteAll(&buf, off);
1455 },
1270 }1456 }
1271 syms_sect.sh_info = @intCast(u32, self.local_symbols.items.len);
1272 syms_sect.sh_size = needed_size; // anticipating adding the global symbols later
1273 self.shdr_table_dirty = true; // TODO look into only writing one section
1274 }1457 }
1275 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();1458
1276 switch (self.ptr_width) {1459 fn writeSymbol(self: *Elf, index: usize) !void {
1277 .p32 => {1460 const syms_sect = &self.sections.items[self.symtab_section_index.?];
1278 var sym = [1]elf.Elf32_Sym{1461 // Make sure we are not pointlessly writing symbol data that will have to get relocated
1279 .{1462 // due to running out of space.
1280 .st_name = self.local_symbols.items[index].st_name,1463 if (self.local_symbols.items.len != syms_sect.sh_info) {
1281 .st_value = @intCast(u32, self.local_symbols.items[index].st_value),1464 const sym_size: u64 = switch (self.ptr_width) {
1282 .st_size = @intCast(u32, self.local_symbols.items[index].st_size),1465 .p32 => @sizeOf(elf.Elf32_Sym),
1283 .st_info = self.local_symbols.items[index].st_info,1466 .p64 => @sizeOf(elf.Elf64_Sym),
1284 .st_other = self.local_symbols.items[index].st_other,
1285 .st_shndx = self.local_symbols.items[index].st_shndx,
1286 },
1287 };1467 };
1288 if (foreign_endian) {1468 const sym_align: u16 = switch (self.ptr_width) {
1289 bswapAllFields(elf.Elf32_Sym, &sym[0]);1469 .p32 => @alignOf(elf.Elf32_Sym),
1290 }1470 .p64 => @alignOf(elf.Elf64_Sym),
1291 const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index;1471 };
1292 try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);1472 const needed_size = (self.local_symbols.items.len + self.global_symbols.items.len) * sym_size;
1293 },1473 if (needed_size > self.allocatedSize(syms_sect.sh_offset)) {
1294 .p64 => {1474 // Move all the symbols to a new file location.
1295 var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]};1475 const new_offset = self.findFreeSpace(needed_size, sym_align);
1296 if (foreign_endian) {1476 const existing_size = @as(u64, syms_sect.sh_info) * sym_size;
1297 bswapAllFields(elf.Elf64_Sym, &sym[0]);1477 const amt = try self.file.?.copyRangeAll(syms_sect.sh_offset, self.file.?, new_offset, existing_size);
1478 if (amt != existing_size) return error.InputOutput;
1479 syms_sect.sh_offset = new_offset;
1298 }1480 }
1299 const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index;1481 syms_sect.sh_info = @intCast(u32, self.local_symbols.items.len);
1300 try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);1482 syms_sect.sh_size = needed_size; // anticipating adding the global symbols later
1301 },1483 self.shdr_table_dirty = true; // TODO look into only writing one section
1302 }1484 }
1303 }1485 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
13041486 switch (self.ptr_width) {
1305 fn writeAllGlobalSymbols(self: *ElfFile) !void {1487 .p32 => {
1306 const syms_sect = &self.sections.items[self.symtab_section_index.?];1488 var sym = [1]elf.Elf32_Sym{
1307 const sym_size: u64 = switch (self.ptr_width) {1489 .{
1308 .p32 => @sizeOf(elf.Elf32_Sym),1490 .st_name = self.local_symbols.items[index].st_name,
1309 .p64 => @sizeOf(elf.Elf64_Sym),1491 .st_value = @intCast(u32, self.local_symbols.items[index].st_value),
1310 };1492 .st_size = @intCast(u32, self.local_symbols.items[index].st_size),
1311 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();1493 .st_info = self.local_symbols.items[index].st_info,
1312 const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size;1494 .st_other = self.local_symbols.items[index].st_other,
1313 switch (self.ptr_width) {1495 .st_shndx = self.local_symbols.items[index].st_shndx,
1314 .p32 => {1496 },
1315 const buf = try self.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len);
1316 defer self.allocator.free(buf);
1317
1318 for (buf) |*sym, i| {
1319 sym.* = .{
1320 .st_name = self.global_symbols.items[i].st_name,
1321 .st_value = @intCast(u32, self.global_symbols.items[i].st_value),
1322 .st_size = @intCast(u32, self.global_symbols.items[i].st_size),
1323 .st_info = self.global_symbols.items[i].st_info,
1324 .st_other = self.global_symbols.items[i].st_other,
1325 .st_shndx = self.global_symbols.items[i].st_shndx,
1326 };1497 };
1327 if (foreign_endian) {1498 if (foreign_endian) {
1328 bswapAllFields(elf.Elf32_Sym, sym);1499 bswapAllFields(elf.Elf32_Sym, &sym[0]);
1329 }1500 }
1330 }1501 const off = syms_sect.sh_offset + @sizeOf(elf.Elf32_Sym) * index;
1331 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off);1502 try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
1332 },1503 },
1333 .p64 => {1504 .p64 => {
1334 const buf = try self.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len);1505 var sym = [1]elf.Elf64_Sym{self.local_symbols.items[index]};
1335 defer self.allocator.free(buf);
1336
1337 for (buf) |*sym, i| {
1338 sym.* = .{
1339 .st_name = self.global_symbols.items[i].st_name,
1340 .st_value = self.global_symbols.items[i].st_value,
1341 .st_size = self.global_symbols.items[i].st_size,
1342 .st_info = self.global_symbols.items[i].st_info,
1343 .st_other = self.global_symbols.items[i].st_other,
1344 .st_shndx = self.global_symbols.items[i].st_shndx,
1345 };
1346 if (foreign_endian) {1506 if (foreign_endian) {
1347 bswapAllFields(elf.Elf64_Sym, sym);1507 bswapAllFields(elf.Elf64_Sym, &sym[0]);
1348 }1508 }
1349 }1509 const off = syms_sect.sh_offset + @sizeOf(elf.Elf64_Sym) * index;
1350 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off);1510 try self.file.?.pwriteAll(mem.sliceAsBytes(sym[0..1]), off);
1351 },1511 },
1512 }
1352 }1513 }
1353 }1514
1515 fn writeAllGlobalSymbols(self: *Elf) !void {
1516 const syms_sect = &self.sections.items[self.symtab_section_index.?];
1517 const sym_size: u64 = switch (self.ptr_width) {
1518 .p32 => @sizeOf(elf.Elf32_Sym),
1519 .p64 => @sizeOf(elf.Elf64_Sym),
1520 };
1521 const foreign_endian = self.options.target.cpu.arch.endian() != std.Target.current.cpu.arch.endian();
1522 const global_syms_off = syms_sect.sh_offset + self.local_symbols.items.len * sym_size;
1523 switch (self.ptr_width) {
1524 .p32 => {
1525 const buf = try self.allocator.alloc(elf.Elf32_Sym, self.global_symbols.items.len);
1526 defer self.allocator.free(buf);
1527
1528 for (buf) |*sym, i| {
1529 sym.* = .{
1530 .st_name = self.global_symbols.items[i].st_name,
1531 .st_value = @intCast(u32, self.global_symbols.items[i].st_value),
1532 .st_size = @intCast(u32, self.global_symbols.items[i].st_size),
1533 .st_info = self.global_symbols.items[i].st_info,
1534 .st_other = self.global_symbols.items[i].st_other,
1535 .st_shndx = self.global_symbols.items[i].st_shndx,
1536 };
1537 if (foreign_endian) {
1538 bswapAllFields(elf.Elf32_Sym, sym);
1539 }
1540 }
1541 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off);
1542 },
1543 .p64 => {
1544 const buf = try self.allocator.alloc(elf.Elf64_Sym, self.global_symbols.items.len);
1545 defer self.allocator.free(buf);
1546
1547 for (buf) |*sym, i| {
1548 sym.* = .{
1549 .st_name = self.global_symbols.items[i].st_name,
1550 .st_value = self.global_symbols.items[i].st_value,
1551 .st_size = self.global_symbols.items[i].st_size,
1552 .st_info = self.global_symbols.items[i].st_info,
1553 .st_other = self.global_symbols.items[i].st_other,
1554 .st_shndx = self.global_symbols.items[i].st_shndx,
1555 };
1556 if (foreign_endian) {
1557 bswapAllFields(elf.Elf64_Sym, sym);
1558 }
1559 }
1560 try self.file.?.pwriteAll(mem.sliceAsBytes(buf), global_syms_off);
1561 },
1562 }
1563 }
1564 };
1354};1565};
13551566
1356/// Truncates the existing file contents and overwrites the contents.1567/// Truncates the existing file contents and overwrites the contents.
1357/// Returns an error if `file` is not already open with +read +write +seek abilities.1568/// Returns an error if `file` is not already open with +read +write +seek abilities.
1358pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !ElfFile {1569pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
1359 switch (options.output_mode) {1570 switch (options.output_mode) {
1360 .Exe => {},1571 .Exe => {},
1361 .Obj => {},1572 .Obj => {},
...@@ -1369,7 +1580,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !El...@@ -1369,7 +1580,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !El
1369 .wasm => return error.TODOImplementWritingWasmObjects,1580 .wasm => return error.TODOImplementWritingWasmObjects,
1370 }1581 }
13711582
1372 var self: ElfFile = .{1583 var self: File.Elf = .{
1373 .allocator = allocator,1584 .allocator = allocator,
1374 .file = file,1585 .file = file,
1375 .options = options,1586 .options = options,
...@@ -1413,7 +1624,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !El...@@ -1413,7 +1624,7 @@ pub fn createElfFile(allocator: *Allocator, file: fs.File, options: Options) !El
1413}1624}
14141625
1415/// Returns error.IncrFailed if incremental update could not be performed.1626/// Returns error.IncrFailed if incremental update could not be performed.
1416fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !ElfFile {1627fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !File.Elf {
1417 switch (options.output_mode) {1628 switch (options.output_mode) {
1418 .Exe => {},1629 .Exe => {},
1419 .Obj => {},1630 .Obj => {},
...@@ -1426,7 +1637,7 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Elf...@@ -1426,7 +1637,7 @@ fn openBinFileInner(allocator: *Allocator, file: fs.File, options: Options) !Elf
1426 .macho => return error.IncrFailed,1637 .macho => return error.IncrFailed,
1427 .wasm => return error.IncrFailed,1638 .wasm => return error.IncrFailed,
1428 }1639 }
1429 var self: ElfFile = .{1640 var self: File.Elf = .{
1430 .allocator = allocator,1641 .allocator = allocator,
1431 .file = file,1642 .file = file,
1432 .owns_file_handle = false,1643 .owns_file_handle = false,
src-self-hosted/main.zig+51-43
...@@ -74,7 +74,7 @@ pub fn main() !void {...@@ -74,7 +74,7 @@ pub fn main() !void {
74 const args = try process.argsAlloc(arena);74 const args = try process.argsAlloc(arena);
7575
76 if (args.len <= 1) {76 if (args.len <= 1) {
77 std.debug.warn("expected command argument\n\n{}", .{usage});77 std.debug.print("expected command argument\n\n{}", .{usage});
78 process.exit(1);78 process.exit(1);
79 }79 }
8080
...@@ -94,14 +94,14 @@ pub fn main() !void {...@@ -94,14 +94,14 @@ pub fn main() !void {
94 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);94 return @import("print_targets.zig").cmdTargets(arena, cmd_args, stdout, info.target);
95 } else if (mem.eql(u8, cmd, "version")) {95 } else if (mem.eql(u8, cmd, "version")) {
96 // Need to set up the build script to give the version as a comptime value.96 // Need to set up the build script to give the version as a comptime value.
97 std.debug.warn("TODO version command not implemented yet\n", .{});97 std.debug.print("TODO version command not implemented yet\n", .{});
98 return error.Unimplemented;98 return error.Unimplemented;
99 } else if (mem.eql(u8, cmd, "zen")) {99 } else if (mem.eql(u8, cmd, "zen")) {
100 try io.getStdOut().writeAll(info_zen);100 try io.getStdOut().writeAll(info_zen);
101 } else if (mem.eql(u8, cmd, "help")) {101 } else if (mem.eql(u8, cmd, "help")) {
102 try io.getStdOut().writeAll(usage);102 try io.getStdOut().writeAll(usage);
103 } else {103 } else {
104 std.debug.warn("unknown command: {}\n\n{}", .{ args[1], usage });104 std.debug.print("unknown command: {}\n\n{}", .{ args[1], usage });
105 process.exit(1);105 process.exit(1);
106 }106 }
107}107}
...@@ -194,6 +194,7 @@ fn buildOutputType(...@@ -194,6 +194,7 @@ fn buildOutputType(
194 var emit_zir: Emit = .no;194 var emit_zir: Emit = .no;
195 var target_arch_os_abi: []const u8 = "native";195 var target_arch_os_abi: []const u8 = "native";
196 var target_mcpu: ?[]const u8 = null;196 var target_mcpu: ?[]const u8 = null;
197 var cbe: bool = false;
197 var target_dynamic_linker: ?[]const u8 = null;198 var target_dynamic_linker: ?[]const u8 = null;
198199
199 var system_libs = std.ArrayList([]const u8).init(gpa);200 var system_libs = std.ArrayList([]const u8).init(gpa);
...@@ -209,7 +210,7 @@ fn buildOutputType(...@@ -209,7 +210,7 @@ fn buildOutputType(
209 process.exit(0);210 process.exit(0);
210 } else if (mem.eql(u8, arg, "--color")) {211 } else if (mem.eql(u8, arg, "--color")) {
211 if (i + 1 >= args.len) {212 if (i + 1 >= args.len) {
212 std.debug.warn("expected [auto|on|off] after --color\n", .{});213 std.debug.print("expected [auto|on|off] after --color\n", .{});
213 process.exit(1);214 process.exit(1);
214 }215 }
215 i += 1;216 i += 1;
...@@ -221,12 +222,12 @@ fn buildOutputType(...@@ -221,12 +222,12 @@ fn buildOutputType(
221 } else if (mem.eql(u8, next_arg, "off")) {222 } else if (mem.eql(u8, next_arg, "off")) {
222 color = .Off;223 color = .Off;
223 } else {224 } else {
224 std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});225 std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
225 process.exit(1);226 process.exit(1);
226 }227 }
227 } else if (mem.eql(u8, arg, "--mode")) {228 } else if (mem.eql(u8, arg, "--mode")) {
228 if (i + 1 >= args.len) {229 if (i + 1 >= args.len) {
229 std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});230 std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode\n", .{});
230 process.exit(1);231 process.exit(1);
231 }232 }
232 i += 1;233 i += 1;
...@@ -240,52 +241,54 @@ fn buildOutputType(...@@ -240,52 +241,54 @@ fn buildOutputType(
240 } else if (mem.eql(u8, next_arg, "ReleaseSmall")) {241 } else if (mem.eql(u8, next_arg, "ReleaseSmall")) {
241 build_mode = .ReleaseSmall;242 build_mode = .ReleaseSmall;
242 } else {243 } else {
243 std.debug.warn("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});244 std.debug.print("expected [Debug|ReleaseSafe|ReleaseFast|ReleaseSmall] after --mode, found '{}'\n", .{next_arg});
244 process.exit(1);245 process.exit(1);
245 }246 }
246 } else if (mem.eql(u8, arg, "--name")) {247 } else if (mem.eql(u8, arg, "--name")) {
247 if (i + 1 >= args.len) {248 if (i + 1 >= args.len) {
248 std.debug.warn("expected parameter after --name\n", .{});249 std.debug.print("expected parameter after --name\n", .{});
249 process.exit(1);250 process.exit(1);
250 }251 }
251 i += 1;252 i += 1;
252 provided_name = args[i];253 provided_name = args[i];
253 } else if (mem.eql(u8, arg, "--library")) {254 } else if (mem.eql(u8, arg, "--library")) {
254 if (i + 1 >= args.len) {255 if (i + 1 >= args.len) {
255 std.debug.warn("expected parameter after --library\n", .{});256 std.debug.print("expected parameter after --library\n", .{});
256 process.exit(1);257 process.exit(1);
257 }258 }
258 i += 1;259 i += 1;
259 try system_libs.append(args[i]);260 try system_libs.append(args[i]);
260 } else if (mem.eql(u8, arg, "--version")) {261 } else if (mem.eql(u8, arg, "--version")) {
261 if (i + 1 >= args.len) {262 if (i + 1 >= args.len) {
262 std.debug.warn("expected parameter after --version\n", .{});263 std.debug.print("expected parameter after --version\n", .{});
263 process.exit(1);264 process.exit(1);
264 }265 }
265 i += 1;266 i += 1;
266 version = std.builtin.Version.parse(args[i]) catch |err| {267 version = std.builtin.Version.parse(args[i]) catch |err| {
267 std.debug.warn("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });268 std.debug.print("unable to parse --version '{}': {}\n", .{ args[i], @errorName(err) });
268 process.exit(1);269 process.exit(1);
269 };270 };
270 } else if (mem.eql(u8, arg, "-target")) {271 } else if (mem.eql(u8, arg, "-target")) {
271 if (i + 1 >= args.len) {272 if (i + 1 >= args.len) {
272 std.debug.warn("expected parameter after -target\n", .{});273 std.debug.print("expected parameter after -target\n", .{});
273 process.exit(1);274 process.exit(1);
274 }275 }
275 i += 1;276 i += 1;
276 target_arch_os_abi = args[i];277 target_arch_os_abi = args[i];
277 } else if (mem.eql(u8, arg, "-mcpu")) {278 } else if (mem.eql(u8, arg, "-mcpu")) {
278 if (i + 1 >= args.len) {279 if (i + 1 >= args.len) {
279 std.debug.warn("expected parameter after -mcpu\n", .{});280 std.debug.print("expected parameter after -mcpu\n", .{});
280 process.exit(1);281 process.exit(1);
281 }282 }
282 i += 1;283 i += 1;
283 target_mcpu = args[i];284 target_mcpu = args[i];
285 } else if (mem.eql(u8, arg, "--c")) {
286 cbe = true;
284 } else if (mem.startsWith(u8, arg, "-mcpu=")) {287 } else if (mem.startsWith(u8, arg, "-mcpu=")) {
285 target_mcpu = arg["-mcpu=".len..];288 target_mcpu = arg["-mcpu=".len..];
286 } else if (mem.eql(u8, arg, "--dynamic-linker")) {289 } else if (mem.eql(u8, arg, "--dynamic-linker")) {
287 if (i + 1 >= args.len) {290 if (i + 1 >= args.len) {
288 std.debug.warn("expected parameter after --dynamic-linker\n", .{});291 std.debug.print("expected parameter after --dynamic-linker\n", .{});
289 process.exit(1);292 process.exit(1);
290 }293 }
291 i += 1;294 i += 1;
...@@ -327,39 +330,39 @@ fn buildOutputType(...@@ -327,39 +330,39 @@ fn buildOutputType(
327 } else if (mem.startsWith(u8, arg, "-l")) {330 } else if (mem.startsWith(u8, arg, "-l")) {
328 try system_libs.append(arg[2..]);331 try system_libs.append(arg[2..]);
329 } else {332 } else {
330 std.debug.warn("unrecognized parameter: '{}'", .{arg});333 std.debug.print("unrecognized parameter: '{}'", .{arg});
331 process.exit(1);334 process.exit(1);
332 }335 }
333 } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {336 } else if (mem.endsWith(u8, arg, ".s") or mem.endsWith(u8, arg, ".S")) {
334 std.debug.warn("assembly files not supported yet", .{});337 std.debug.print("assembly files not supported yet", .{});
335 process.exit(1);338 process.exit(1);
336 } else if (mem.endsWith(u8, arg, ".o") or339 } else if (mem.endsWith(u8, arg, ".o") or
337 mem.endsWith(u8, arg, ".obj") or340 mem.endsWith(u8, arg, ".obj") or
338 mem.endsWith(u8, arg, ".a") or341 mem.endsWith(u8, arg, ".a") or
339 mem.endsWith(u8, arg, ".lib"))342 mem.endsWith(u8, arg, ".lib"))
340 {343 {
341 std.debug.warn("object files and static libraries not supported yet", .{});344 std.debug.print("object files and static libraries not supported yet", .{});
342 process.exit(1);345 process.exit(1);
343 } else if (mem.endsWith(u8, arg, ".c") or346 } else if (mem.endsWith(u8, arg, ".c") or
344 mem.endsWith(u8, arg, ".cpp"))347 mem.endsWith(u8, arg, ".cpp"))
345 {348 {
346 std.debug.warn("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});349 std.debug.print("compilation of C and C++ source code requires LLVM extensions which are not implemented yet", .{});
347 process.exit(1);350 process.exit(1);
348 } else if (mem.endsWith(u8, arg, ".so") or351 } else if (mem.endsWith(u8, arg, ".so") or
349 mem.endsWith(u8, arg, ".dylib") or352 mem.endsWith(u8, arg, ".dylib") or
350 mem.endsWith(u8, arg, ".dll"))353 mem.endsWith(u8, arg, ".dll"))
351 {354 {
352 std.debug.warn("linking against dynamic libraries not yet supported", .{});355 std.debug.print("linking against dynamic libraries not yet supported", .{});
353 process.exit(1);356 process.exit(1);
354 } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {357 } else if (mem.endsWith(u8, arg, ".zig") or mem.endsWith(u8, arg, ".zir")) {
355 if (root_src_file) |other| {358 if (root_src_file) |other| {
356 std.debug.warn("found another zig file '{}' after root source file '{}'", .{ arg, other });359 std.debug.print("found another zig file '{}' after root source file '{}'", .{ arg, other });
357 process.exit(1);360 process.exit(1);
358 } else {361 } else {
359 root_src_file = arg;362 root_src_file = arg;
360 }363 }
361 } else {364 } else {
362 std.debug.warn("unrecognized file extension of parameter '{}'", .{arg});365 std.debug.print("unrecognized file extension of parameter '{}'", .{arg});
363 }366 }
364 }367 }
365 }368 }
...@@ -370,13 +373,13 @@ fn buildOutputType(...@@ -370,13 +373,13 @@ fn buildOutputType(
370 var it = mem.split(basename, ".");373 var it = mem.split(basename, ".");
371 break :blk it.next() orelse basename;374 break :blk it.next() orelse basename;
372 } else {375 } else {
373 std.debug.warn("--name [name] not provided and unable to infer\n", .{});376 std.debug.print("--name [name] not provided and unable to infer\n", .{});
374 process.exit(1);377 process.exit(1);
375 }378 }
376 };379 };
377380
378 if (system_libs.items.len != 0) {381 if (system_libs.items.len != 0) {
379 std.debug.warn("linking against system libraries not yet supported", .{});382 std.debug.print("linking against system libraries not yet supported", .{});
380 process.exit(1);383 process.exit(1);
381 }384 }
382385
...@@ -388,17 +391,17 @@ fn buildOutputType(...@@ -388,17 +391,17 @@ fn buildOutputType(
388 .diagnostics = &diags,391 .diagnostics = &diags,
389 }) catch |err| switch (err) {392 }) catch |err| switch (err) {
390 error.UnknownCpuModel => {393 error.UnknownCpuModel => {
391 std.debug.warn("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{394 std.debug.print("Unknown CPU: '{}'\nAvailable CPUs for architecture '{}':\n", .{
392 diags.cpu_name.?,395 diags.cpu_name.?,
393 @tagName(diags.arch.?),396 @tagName(diags.arch.?),
394 });397 });
395 for (diags.arch.?.allCpuModels()) |cpu| {398 for (diags.arch.?.allCpuModels()) |cpu| {
396 std.debug.warn(" {}\n", .{cpu.name});399 std.debug.print(" {}\n", .{cpu.name});
397 }400 }
398 process.exit(1);401 process.exit(1);
399 },402 },
400 error.UnknownCpuFeature => {403 error.UnknownCpuFeature => {
401 std.debug.warn(404 std.debug.print(
402 \\Unknown CPU feature: '{}'405 \\Unknown CPU feature: '{}'
403 \\Available CPU features for architecture '{}':406 \\Available CPU features for architecture '{}':
404 \\407 \\
...@@ -407,7 +410,7 @@ fn buildOutputType(...@@ -407,7 +410,7 @@ fn buildOutputType(
407 @tagName(diags.arch.?),410 @tagName(diags.arch.?),
408 });411 });
409 for (diags.arch.?.allFeaturesList()) |feature| {412 for (diags.arch.?.allFeaturesList()) |feature| {
410 std.debug.warn(" {}: {}\n", .{ feature.name, feature.description });413 std.debug.print(" {}: {}\n", .{ feature.name, feature.description });
411 }414 }
412 process.exit(1);415 process.exit(1);
413 },416 },
...@@ -419,21 +422,25 @@ fn buildOutputType(...@@ -419,21 +422,25 @@ fn buildOutputType(
419 if (target_info.cpu_detection_unimplemented) {422 if (target_info.cpu_detection_unimplemented) {
420 // TODO We want to just use detected_info.target but implementing423 // TODO We want to just use detected_info.target but implementing
421 // CPU model & feature detection is todo so here we rely on LLVM.424 // CPU model & feature detection is todo so here we rely on LLVM.
422 std.debug.warn("CPU features detection is not yet available for this system without LLVM extensions\n", .{});425 std.debug.print("CPU features detection is not yet available for this system without LLVM extensions\n", .{});
423 process.exit(1);426 process.exit(1);
424 }427 }
425428
426 const src_path = root_src_file orelse {429 const src_path = root_src_file orelse {
427 std.debug.warn("expected at least one file argument", .{});430 std.debug.print("expected at least one file argument", .{});
428 process.exit(1);431 process.exit(1);
429 };432 };
430433
431 const bin_path = switch (emit_bin) {434 const bin_path = switch (emit_bin) {
432 .no => {435 .no => {
433 std.debug.warn("-fno-emit-bin not supported yet", .{});436 std.debug.print("-fno-emit-bin not supported yet", .{});
434 process.exit(1);437 process.exit(1);
435 },438 },
436 .yes_default_path => try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode),439 .yes_default_path => if (cbe)
440 try std.fmt.allocPrint(arena, "{}.c", .{root_name})
441 else
442 try std.zig.binNameAlloc(arena, root_name, target_info.target, output_mode, link_mode),
443
437 .yes => |p| p,444 .yes => |p| p,
438 };445 };
439446
...@@ -463,6 +470,7 @@ fn buildOutputType(...@@ -463,6 +470,7 @@ fn buildOutputType(
463 .object_format = object_format,470 .object_format = object_format,
464 .optimize_mode = build_mode,471 .optimize_mode = build_mode,
465 .keep_source_files_loaded = zir_out_path != null,472 .keep_source_files_loaded = zir_out_path != null,
473 .cbe = cbe,
466 });474 });
467 defer module.deinit();475 defer module.deinit();
468476
...@@ -509,7 +517,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo...@@ -509,7 +517,7 @@ fn updateModule(gpa: *Allocator, module: *Module, zir_out_path: ?[]const u8) !vo
509517
510 if (errors.list.len != 0) {518 if (errors.list.len != 0) {
511 for (errors.list) |full_err_msg| {519 for (errors.list) |full_err_msg| {
512 std.debug.warn("{}:{}:{}: error: {}\n", .{520 std.debug.print("{}:{}:{}: error: {}\n", .{
513 full_err_msg.src_path,521 full_err_msg.src_path,
514 full_err_msg.line + 1,522 full_err_msg.line + 1,
515 full_err_msg.column + 1,523 full_err_msg.column + 1,
...@@ -586,7 +594,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -586,7 +594,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
586 process.exit(0);594 process.exit(0);
587 } else if (mem.eql(u8, arg, "--color")) {595 } else if (mem.eql(u8, arg, "--color")) {
588 if (i + 1 >= args.len) {596 if (i + 1 >= args.len) {
589 std.debug.warn("expected [auto|on|off] after --color\n", .{});597 std.debug.print("expected [auto|on|off] after --color\n", .{});
590 process.exit(1);598 process.exit(1);
591 }599 }
592 i += 1;600 i += 1;
...@@ -598,7 +606,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -598,7 +606,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
598 } else if (mem.eql(u8, next_arg, "off")) {606 } else if (mem.eql(u8, next_arg, "off")) {
599 color = .Off;607 color = .Off;
600 } else {608 } else {
601 std.debug.warn("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});609 std.debug.print("expected [auto|on|off] after --color, found '{}'\n", .{next_arg});
602 process.exit(1);610 process.exit(1);
603 }611 }
604 } else if (mem.eql(u8, arg, "--stdin")) {612 } else if (mem.eql(u8, arg, "--stdin")) {
...@@ -606,7 +614,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -606,7 +614,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
606 } else if (mem.eql(u8, arg, "--check")) {614 } else if (mem.eql(u8, arg, "--check")) {
607 check_flag = true;615 check_flag = true;
608 } else {616 } else {
609 std.debug.warn("unrecognized parameter: '{}'", .{arg});617 std.debug.print("unrecognized parameter: '{}'", .{arg});
610 process.exit(1);618 process.exit(1);
611 }619 }
612 } else {620 } else {
...@@ -617,7 +625,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -617,7 +625,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
617625
618 if (stdin_flag) {626 if (stdin_flag) {
619 if (input_files.items.len != 0) {627 if (input_files.items.len != 0) {
620 std.debug.warn("cannot use --stdin with positional arguments\n", .{});628 std.debug.print("cannot use --stdin with positional arguments\n", .{});
621 process.exit(1);629 process.exit(1);
622 }630 }
623631
...@@ -627,7 +635,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -627,7 +635,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
627 defer gpa.free(source_code);635 defer gpa.free(source_code);
628636
629 const tree = std.zig.parse(gpa, source_code) catch |err| {637 const tree = std.zig.parse(gpa, source_code) catch |err| {
630 std.debug.warn("error parsing stdin: {}\n", .{err});638 std.debug.print("error parsing stdin: {}\n", .{err});
631 process.exit(1);639 process.exit(1);
632 };640 };
633 defer tree.deinit();641 defer tree.deinit();
...@@ -650,7 +658,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -650,7 +658,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
650 }658 }
651659
652 if (input_files.items.len == 0) {660 if (input_files.items.len == 0) {
653 std.debug.warn("expected at least one source file argument\n", .{});661 std.debug.print("expected at least one source file argument\n", .{});
654 process.exit(1);662 process.exit(1);
655 }663 }
656664
...@@ -667,7 +675,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {...@@ -667,7 +675,7 @@ pub fn cmdFmt(gpa: *Allocator, args: []const []const u8) !void {
667 for (input_files.span()) |file_path| {675 for (input_files.span()) |file_path| {
668 // Get the real path here to avoid Windows failing on relative file paths with . or .. in them.676 // Get the real path here to avoid Windows failing on relative file paths with . or .. in them.
669 const real_path = fs.realpathAlloc(gpa, file_path) catch |err| {677 const real_path = fs.realpathAlloc(gpa, file_path) catch |err| {
670 std.debug.warn("unable to open '{}': {}\n", .{ file_path, err });678 std.debug.print("unable to open '{}': {}\n", .{ file_path, err });
671 process.exit(1);679 process.exit(1);
672 };680 };
673 defer gpa.free(real_path);681 defer gpa.free(real_path);
...@@ -705,7 +713,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_...@@ -705,7 +713,7 @@ fn fmtPath(fmt: *Fmt, file_path: []const u8, check_mode: bool, dir: fs.Dir, sub_
705 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {713 fmtPathFile(fmt, file_path, check_mode, dir, sub_path) catch |err| switch (err) {
706 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),714 error.IsDir, error.AccessDenied => return fmtPathDir(fmt, file_path, check_mode, dir, sub_path),
707 else => {715 else => {
708 std.debug.warn("unable to format '{}': {}\n", .{ file_path, err });716 std.debug.print("unable to format '{}': {}\n", .{ file_path, err });
709 fmt.any_error = true;717 fmt.any_error = true;
710 return;718 return;
711 },719 },
...@@ -736,7 +744,7 @@ fn fmtPathDir(...@@ -736,7 +744,7 @@ fn fmtPathDir(
736 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);744 try fmtPathDir(fmt, full_path, check_mode, dir, entry.name);
737 } else {745 } else {
738 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {746 fmtPathFile(fmt, full_path, check_mode, dir, entry.name) catch |err| {
739 std.debug.warn("unable to format '{}': {}\n", .{ full_path, err });747 std.debug.print("unable to format '{}': {}\n", .{ full_path, err });
740 fmt.any_error = true;748 fmt.any_error = true;
741 return;749 return;
742 };750 };
...@@ -787,7 +795,7 @@ fn fmtPathFile(...@@ -787,7 +795,7 @@ fn fmtPathFile(
787 if (check_mode) {795 if (check_mode) {
788 const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);796 const anything_changed = try std.zig.render(fmt.gpa, io.null_out_stream, tree);
789 if (anything_changed) {797 if (anything_changed) {
790 std.debug.warn("{}\n", .{file_path});798 std.debug.print("{}\n", .{file_path});
791 fmt.any_error = true;799 fmt.any_error = true;
792 }800 }
793 } else {801 } else {
...@@ -803,7 +811,7 @@ fn fmtPathFile(...@@ -803,7 +811,7 @@ fn fmtPathFile(
803811
804 try af.file.writeAll(fmt.out_buffer.items);812 try af.file.writeAll(fmt.out_buffer.items);
805 try af.finish();813 try af.finish();
806 std.debug.warn("{}\n", .{file_path});814 std.debug.print("{}\n", .{file_path});
807 }815 }
808}816}
809817
src-self-hosted/test.zig+83-32
...@@ -5,6 +5,8 @@ const Allocator = std.mem.Allocator;...@@ -5,6 +5,8 @@ const Allocator = std.mem.Allocator;
5const zir = @import("zir.zig");5const zir = @import("zir.zig");
6const Package = @import("Package.zig");6const Package = @import("Package.zig");
77
8const cheader = @embedFile("cbe.h");
9
8test "self-hosted" {10test "self-hosted" {
9 var ctx = TestContext.init();11 var ctx = TestContext.init();
10 defer ctx.deinit();12 defer ctx.deinit();
...@@ -68,6 +70,7 @@ pub const TestContext = struct {...@@ -68,6 +70,7 @@ pub const TestContext = struct {
68 output_mode: std.builtin.OutputMode,70 output_mode: std.builtin.OutputMode,
69 updates: std.ArrayList(Update),71 updates: std.ArrayList(Update),
70 extension: TestType,72 extension: TestType,
73 cbe: bool = false,
7174
72 /// Adds a subcase in which the module is updated with `src`, and the75 /// Adds a subcase in which the module is updated with `src`, and the
73 /// resulting ZIR is validated against `result`.76 /// resulting ZIR is validated against `result`.
...@@ -187,6 +190,22 @@ pub const TestContext = struct {...@@ -187,6 +190,22 @@ pub const TestContext = struct {
187 return ctx.addObj(name, target, .ZIR);190 return ctx.addObj(name, target, .ZIR);
188 }191 }
189192
193 pub fn addC(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, T: TestType) *Case {
194 ctx.cases.append(Case{
195 .name = name,
196 .target = target,
197 .updates = std.ArrayList(Update).init(ctx.cases.allocator),
198 .output_mode = .Obj,
199 .extension = T,
200 .cbe = true,
201 }) catch unreachable;
202 return &ctx.cases.items[ctx.cases.items.len - 1];
203 }
204
205 pub fn c(ctx: *TestContext, name: []const u8, target: std.zig.CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
206 ctx.addC(name, target, .Zig).addTransform(src, cheader ++ out);
207 }
208
190 pub fn addCompareOutput(209 pub fn addCompareOutput(
191 ctx: *TestContext,210 ctx: *TestContext,
192 name: []const u8,211 name: []const u8,
...@@ -365,13 +384,13 @@ pub const TestContext = struct {...@@ -365,13 +384,13 @@ pub const TestContext = struct {
365 }384 }
366385
367 fn deinit(self: *TestContext) void {386 fn deinit(self: *TestContext) void {
368 for (self.cases.items) |c| {387 for (self.cases.items) |case| {
369 for (c.updates.items) |u| {388 for (case.updates.items) |u| {
370 if (u.case == .Error) {389 if (u.case == .Error) {
371 c.updates.allocator.free(u.case.Error);390 case.updates.allocator.free(u.case.Error);
372 }391 }
373 }392 }
374 c.updates.deinit();393 case.updates.deinit();
375 }394 }
376 self.cases.deinit();395 self.cases.deinit();
377 self.* = undefined;396 self.* = undefined;
...@@ -415,9 +434,6 @@ pub const TestContext = struct {...@@ -415,9 +434,6 @@ pub const TestContext = struct {
415434
416 var module = try Module.init(allocator, .{435 var module = try Module.init(allocator, .{
417 .target = target,436 .target = target,
418 // This is an Executable, as opposed to e.g. a *library*. This does
419 // not mean no ZIR is generated.
420 //
421 // TODO: support tests for object file building, and library builds437 // TODO: support tests for object file building, and library builds
422 // and linking. This will require a rework to support multi-file438 // and linking. This will require a rework to support multi-file
423 // tests.439 // tests.
...@@ -428,6 +444,7 @@ pub const TestContext = struct {...@@ -428,6 +444,7 @@ pub const TestContext = struct {
428 .bin_file_path = bin_name,444 .bin_file_path = bin_name,
429 .root_pkg = root_pkg,445 .root_pkg = root_pkg,
430 .keep_source_files_loaded = true,446 .keep_source_files_loaded = true,
447 .cbe = case.cbe,
431 });448 });
432 defer module.deinit();449 defer module.deinit();
433450
...@@ -447,33 +464,65 @@ pub const TestContext = struct {...@@ -447,33 +464,65 @@ pub const TestContext = struct {
447 try module.update();464 try module.update();
448 module_node.end();465 module_node.end();
449466
467 if (update.case != .Error) {
468 var all_errors = try module.getAllErrorsAlloc();
469 defer all_errors.deinit(allocator);
470 if (all_errors.list.len != 0) {
471 std.debug.warn("\nErrors occurred updating the module:\n================\n", .{});
472 for (all_errors.list) |err| {
473 std.debug.warn(":{}:{}: error: {}\n================\n", .{ err.line + 1, err.column + 1, err.msg });
474 }
475 std.debug.warn("Test failed.\n", .{});
476 std.process.exit(1);
477 }
478 }
479
450 switch (update.case) {480 switch (update.case) {
451 .Transformation => |expected_output| {481 .Transformation => |expected_output| {
452 update_node.estimated_total_items = 5;482 if (case.cbe) {
453 var emit_node = update_node.start("emit", null);483 // The C file is always closed after an update, because we don't support
454 emit_node.activate();484 // incremental updates
455 var new_zir_module = try zir.emit(allocator, module);485 var file = try tmp.dir.openFile(bin_name, .{ .read = true });
456 defer new_zir_module.deinit(allocator);486 defer file.close();
457 emit_node.end();487 var out = file.reader().readAllAlloc(allocator, 1024 * 1024) catch @panic("Unable to read C output!");
458488 defer allocator.free(out);
459 var write_node = update_node.start("write", null);489
460 write_node.activate();490 if (expected_output.len != out.len) {
461 var out_zir = std.ArrayList(u8).init(allocator);491 std.debug.warn("\nTransformed C length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
462 defer out_zir.deinit();492 std.process.exit(1);
463 try new_zir_module.writeToStream(allocator, out_zir.outStream());493 }
464 write_node.end();494 for (expected_output) |e, i| {
465495 if (out[i] != e) {
466 var test_node = update_node.start("assert", null);496 std.debug.warn("\nTransformed C differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ expected_output, out });
467 test_node.activate();497 std.process.exit(1);
468 defer test_node.end();498 }
469 if (expected_output.len != out_zir.items.len) {499 }
470 std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });500 } else {
471 std.process.exit(1);501 update_node.estimated_total_items = 5;
472 }502 var emit_node = update_node.start("emit", null);
473 for (expected_output) |e, i| {503 emit_node.activate();
474 if (out_zir.items[i] != e) {504 var new_zir_module = try zir.emit(allocator, module);
475 if (expected_output.len != out_zir.items.len) {505 defer new_zir_module.deinit(allocator);
476 std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound: {}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });506 emit_node.end();
507
508 var write_node = update_node.start("write", null);
509 write_node.activate();
510 var out_zir = std.ArrayList(u8).init(allocator);
511 defer out_zir.deinit();
512 try new_zir_module.writeToStream(allocator, out_zir.outStream());
513 write_node.end();
514
515 var test_node = update_node.start("assert", null);
516 test_node.activate();
517 defer test_node.end();
518
519 if (expected_output.len != out_zir.items.len) {
520 std.debug.warn("{}\nTransformed ZIR length differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
521 std.process.exit(1);
522 }
523 for (expected_output) |e, i| {
524 if (out_zir.items[i] != e) {
525 std.debug.warn("{}\nTransformed ZIR differs:\n================\nExpected:\n================\n{}\n================\nFound:\n================\n{}\n================\nTest failed.\n", .{ case.name, expected_output, out_zir.items });
477 std.process.exit(1);526 std.process.exit(1);
478 }527 }
479 }528 }
...@@ -511,6 +560,8 @@ pub const TestContext = struct {...@@ -511,6 +560,8 @@ pub const TestContext = struct {
511 }560 }
512 },561 },
513 .Execution => |expected_stdout| {562 .Execution => |expected_stdout| {
563 std.debug.assert(!case.cbe);
564
514 update_node.estimated_total_items = 4;565 update_node.estimated_total_items = 4;
515 var exec_result = x: {566 var exec_result = x: {
516 var exec_node = update_node.start("execute", null);567 var exec_node = update_node.start("execute", null);
src/analyze.cpp+4
...@@ -4012,6 +4012,10 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) {...@@ -4012,6 +4012,10 @@ static void resolve_decl_var(CodeGen *g, TldVar *tld_var, bool allow_lazy) {
4012 } else if (!is_extern) {4012 } else if (!is_extern) {
4013 add_node_error(g, source_node, buf_sprintf("variables must be initialized"));4013 add_node_error(g, source_node, buf_sprintf("variables must be initialized"));
4014 implicit_type = g->builtin_types.entry_invalid;4014 implicit_type = g->builtin_types.entry_invalid;
4015 } else if (explicit_type == nullptr) {
4016 // extern variable without explicit type
4017 add_node_error(g, source_node, buf_sprintf("unable to infer variable type"));
4018 implicit_type = g->builtin_types.entry_invalid;
4015 }4019 }
40164020
4017 ZigType *type = explicit_type ? explicit_type : implicit_type;4021 ZigType *type = explicit_type ? explicit_type : implicit_type;
test/compile_errors.zig+9
...@@ -2,6 +2,15 @@ const tests = @import("tests.zig");...@@ -2,6 +2,15 @@ const tests = @import("tests.zig");
2const std = @import("std");2const std = @import("std");
33
4pub fn addCases(cases: *tests.CompileErrorContext) void {4pub fn addCases(cases: *tests.CompileErrorContext) void {
5 cases.add("extern variable has no type",
6 \\extern var foo;
7 \\pub export fn entry() void {
8 \\ foo;
9 \\}
10 , &[_][]const u8{
11 "tmp.zig:1:1: error: unable to infer variable type",
12 });
13
5 cases.add("@src outside function",14 cases.add("@src outside function",
6 \\comptime {15 \\comptime {
7 \\ @src();16 \\ @src();
test/stage2/cbe.zig created+90
...@@ -0,0 +1,90 @@
1const std = @import("std");
2const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
3
4// These tests should work with all platforms, but we're using linux_x64 for
5// now for consistency. Will be expanded eventually.
6const linux_x64 = std.zig.CrossTarget{
7 .cpu_arch = .x86_64,
8 .os_tag = .linux,
9};
10
11pub fn addCases(ctx: *TestContext) !void {
12 ctx.c("empty start function", linux_x64,
13 \\export fn _start() noreturn {}
14 ,
15 \\noreturn void _start(void) {}
16 \\
17 );
18 ctx.c("less empty start function", linux_x64,
19 \\fn main() noreturn {}
20 \\
21 \\export fn _start() noreturn {
22 \\ main();
23 \\}
24 ,
25 \\noreturn void main(void);
26 \\
27 \\noreturn void _start(void) {
28 \\ main();
29 \\}
30 \\
31 \\noreturn void main(void) {}
32 \\
33 );
34 // TODO: implement return values
35 // TODO: figure out a way to prevent asm constants from being generated
36 ctx.c("inline asm", linux_x64,
37 \\fn exitGood() void {
38 \\ asm volatile ("syscall"
39 \\ :
40 \\ : [number] "{rax}" (231),
41 \\ [arg1] "{rdi}" (0)
42 \\ );
43 \\}
44 \\
45 \\export fn _start() noreturn {
46 \\ exitGood();
47 \\}
48 ,
49 \\#include <stddef.h>
50 \\
51 \\void exitGood(void);
52 \\
53 \\const char *const exitGood__anon_0 = "{rax}";
54 \\const char *const exitGood__anon_1 = "{rdi}";
55 \\const char *const exitGood__anon_2 = "syscall";
56 \\
57 \\noreturn void _start(void) {
58 \\ exitGood();
59 \\}
60 \\
61 \\void exitGood(void) {
62 \\ register size_t rax_constant __asm__("rax") = 231;
63 \\ register size_t rdi_constant __asm__("rdi") = 0;
64 \\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
65 \\}
66 \\
67 );
68 //ctx.c("basic return", linux_x64,
69 // \\fn main() u8 {
70 // \\ return 103;
71 // \\}
72 // \\
73 // \\export fn _start() noreturn {
74 // \\ _ = main();
75 // \\}
76 //,
77 // \\#include <stdint.h>
78 // \\
79 // \\uint8_t main(void);
80 // \\
81 // \\noreturn void _start(void) {
82 // \\ (void)main();
83 // \\}
84 // \\
85 // \\uint8_t main(void) {
86 // \\ return 103;
87 // \\}
88 // \\
89 //);
90}
test/stage2/test.zig+1
...@@ -4,4 +4,5 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -4,4 +4,5 @@ pub fn addCases(ctx: *TestContext) !void {
4 try @import("compile_errors.zig").addCases(ctx);4 try @import("compile_errors.zig").addCases(ctx);
5 try @import("compare_output.zig").addCases(ctx);5 try @import("compare_output.zig").addCases(ctx);
6 try @import("zir.zig").addCases(ctx);6 try @import("zir.zig").addCases(ctx);
7 try @import("cbe.zig").addCases(ctx);
7}8}
test/stage2/zir.zig+9-9
...@@ -22,8 +22,8 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -22,8 +22,8 @@ pub fn addCases(ctx: *TestContext) !void {
22 ,22 ,
23 \\@void = primitive(void)23 \\@void = primitive(void)
24 \\@fnty = fntype([], @void, cc=C)24 \\@fnty = fntype([], @void, cc=C)
25 \\@9 = declref("9$0")25 \\@9 = declref("9__anon_0")
26 \\@9$0 = str("entry")26 \\@9__anon_0 = str("entry")
27 \\@unnamed$4 = str("entry")27 \\@unnamed$4 = str("entry")
28 \\@unnamed$5 = export(@unnamed$4, "entry")28 \\@unnamed$5 = export(@unnamed$4, "entry")
29 \\@unnamed$6 = fntype([], @void, cc=C)29 \\@unnamed$6 = fntype([], @void, cc=C)
...@@ -77,9 +77,9 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -77,9 +77,9 @@ pub fn addCases(ctx: *TestContext) !void {
77 \\@entry = fn(@unnamed$6, {77 \\@entry = fn(@unnamed$6, {
78 \\ %0 = returnvoid()78 \\ %0 = returnvoid()
79 \\})79 \\})
80 \\@entry$1 = str("2\x08\x01\n")80 \\@entry__anon_1 = str("2\x08\x01\n")
81 \\@9 = declref("9$0")81 \\@9 = declref("9__anon_0")
82 \\@9$0 = str("entry")82 \\@9__anon_0 = str("entry")
83 \\@unnamed$11 = str("entry")83 \\@unnamed$11 = str("entry")
84 \\@unnamed$12 = export(@unnamed$11, "entry")84 \\@unnamed$12 = export(@unnamed$11, "entry")
85 \\85 \\
...@@ -111,8 +111,8 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -111,8 +111,8 @@ pub fn addCases(ctx: *TestContext) !void {
111 ,111 ,
112 \\@void = primitive(void)112 \\@void = primitive(void)
113 \\@fnty = fntype([], @void, cc=C)113 \\@fnty = fntype([], @void, cc=C)
114 \\@9 = declref("9$0")114 \\@9 = declref("9__anon_0")
115 \\@9$0 = str("entry")115 \\@9__anon_0 = str("entry")
116 \\@unnamed$4 = str("entry")116 \\@unnamed$4 = str("entry")
117 \\@unnamed$5 = export(@unnamed$4, "entry")117 \\@unnamed$5 = export(@unnamed$4, "entry")
118 \\@unnamed$6 = fntype([], @void, cc=C)118 \\@unnamed$6 = fntype([], @void, cc=C)
...@@ -187,8 +187,8 @@ pub fn addCases(ctx: *TestContext) !void {...@@ -187,8 +187,8 @@ pub fn addCases(ctx: *TestContext) !void {
187 ,187 ,
188 \\@void = primitive(void)188 \\@void = primitive(void)
189 \\@fnty = fntype([], @void, cc=C)189 \\@fnty = fntype([], @void, cc=C)
190 \\@9 = declref("9$2")190 \\@9 = declref("9__anon_2")
191 \\@9$2 = str("entry")191 \\@9__anon_2 = str("entry")
192 \\@unnamed$4 = str("entry")192 \\@unnamed$4 = str("entry")
193 \\@unnamed$5 = export(@unnamed$4, "entry")193 \\@unnamed$5 = export(@unnamed$4, "entry")
194 \\@unnamed$6 = fntype([], @void, cc=C)194 \\@unnamed$6 = fntype([], @void, cc=C)