mirror of
https://github.com/ziglang/zig.git
synced 2026-02-13 04:48:20 +00:00
Merge branch 'stage2-rework-cbe'
Reworks the C backend and -femit-h to properly participate in incremental compilation. closes #7602
This commit is contained in:
commit
38572ee894
@ -559,7 +559,7 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/MachO/Trie.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/Wasm.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/cbe.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/C/zig.h"
|
||||
"${CMAKE_SOURCE_DIR}/src/link/msdos-stub.bin"
|
||||
"${CMAKE_SOURCE_DIR}/src/liveness.zig"
|
||||
"${CMAKE_SOURCE_DIR}/src/llvm_backend.zig"
|
||||
|
||||
@ -100,10 +100,20 @@ pub fn ArrayListAligned(comptime T: type, comptime alignment: ?u29) type {
|
||||
|
||||
/// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
|
||||
/// of this ArrayList. This ArrayList retains ownership of underlying memory.
|
||||
/// Deprecated: use `moveToUnmanaged` which has different semantics.
|
||||
pub fn toUnmanaged(self: Self) ArrayListAlignedUnmanaged(T, alignment) {
|
||||
return .{ .items = self.items, .capacity = self.capacity };
|
||||
}
|
||||
|
||||
/// Initializes an ArrayListUnmanaged with the `items` and `capacity` fields
|
||||
/// of this ArrayList. Empties this ArrayList.
|
||||
pub fn moveToUnmanaged(self: *Self) ArrayListAlignedUnmanaged(T, alignment) {
|
||||
const allocator = self.allocator;
|
||||
const result = .{ .items = self.items, .capacity = self.capacity };
|
||||
self.* = init(allocator);
|
||||
return result;
|
||||
}
|
||||
|
||||
/// The caller owns the returned memory. Empties this ArrayList.
|
||||
pub fn toOwnedSlice(self: *Self) Slice {
|
||||
const allocator = self.allocator;
|
||||
@ -551,14 +561,6 @@ pub fn ArrayListAlignedUnmanaged(comptime T: type, comptime alignment: ?u29) typ
|
||||
mem.copy(T, self.items[oldlen..], items);
|
||||
}
|
||||
|
||||
/// Same as `append` except it returns the number of bytes written, which is always the same
|
||||
/// as `m.len`. The purpose of this function existing is to match `std.io.OutStream` API.
|
||||
/// This function may be called only when `T` is `u8`.
|
||||
fn appendWrite(self: *Self, allocator: *Allocator, m: []const u8) !usize {
|
||||
try self.appendSlice(allocator, m);
|
||||
return m.len;
|
||||
}
|
||||
|
||||
/// Append a value to the list `n` times.
|
||||
/// Allocates more memory as necessary.
|
||||
pub fn appendNTimes(self: *Self, allocator: *Allocator, value: T, n: usize) !void {
|
||||
@ -1129,13 +1131,13 @@ test "std.ArrayList/ArrayListUnmanaged: ArrayList(T) of struct T" {
|
||||
}
|
||||
}
|
||||
|
||||
test "std.ArrayList(u8) implements outStream" {
|
||||
test "std.ArrayList(u8) implements writer" {
|
||||
var buffer = ArrayList(u8).init(std.testing.allocator);
|
||||
defer buffer.deinit();
|
||||
|
||||
const x: i32 = 42;
|
||||
const y: i32 = 1234;
|
||||
try buffer.outStream().print("x: {}\ny: {}\n", .{ x, y });
|
||||
try buffer.writer().print("x: {}\ny: {}\n", .{ x, y });
|
||||
|
||||
testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", buffer.items);
|
||||
}
|
||||
|
||||
@ -459,6 +459,7 @@ pub const File = struct {
|
||||
return index;
|
||||
}
|
||||
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
|
||||
if (is_windows) {
|
||||
// TODO improve this to use ReadFileScatter
|
||||
@ -479,6 +480,7 @@ pub const File = struct {
|
||||
/// is not an error condition.
|
||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||
/// order to handle partial reads from the underlying OS layer.
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn readvAll(self: File, iovecs: []os.iovec) ReadError!usize {
|
||||
if (iovecs.len == 0) return;
|
||||
|
||||
@ -500,6 +502,7 @@ pub const File = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) PReadError!usize {
|
||||
if (is_windows) {
|
||||
// TODO improve this to use ReadFileScatter
|
||||
@ -520,6 +523,7 @@ pub const File = struct {
|
||||
/// is not an error condition.
|
||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||
/// order to handle partial reads from the underlying OS layer.
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn preadvAll(self: File, iovecs: []const os.iovec, offset: u64) PReadError!void {
|
||||
if (iovecs.len == 0) return;
|
||||
|
||||
@ -582,6 +586,7 @@ pub const File = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!usize {
|
||||
if (is_windows) {
|
||||
// TODO improve this to use WriteFileScatter
|
||||
@ -599,6 +604,7 @@ pub const File = struct {
|
||||
|
||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||
/// order to handle partial writes from the underlying OS layer.
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn writevAll(self: File, iovecs: []os.iovec_const) WriteError!void {
|
||||
if (iovecs.len == 0) return;
|
||||
|
||||
@ -615,6 +621,7 @@ pub const File = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn pwritev(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!usize {
|
||||
if (is_windows) {
|
||||
// TODO improve this to use WriteFileScatter
|
||||
@ -632,6 +639,7 @@ pub const File = struct {
|
||||
|
||||
/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
|
||||
/// order to handle partial writes from the underlying OS layer.
|
||||
/// See https://github.com/ziglang/zig/issues/7699
|
||||
pub fn pwritevAll(self: File, iovecs: []os.iovec_const, offset: u64) PWriteError!void {
|
||||
if (iovecs.len == 0) return;
|
||||
|
||||
|
||||
@ -27,7 +27,6 @@ const Cache = @import("Cache.zig");
|
||||
const stage1 = @import("stage1.zig");
|
||||
const translate_c = @import("translate_c.zig");
|
||||
const c_codegen = @import("codegen/c.zig");
|
||||
const c_link = @import("link/C.zig");
|
||||
const ThreadPool = @import("ThreadPool.zig");
|
||||
const WaitGroup = @import("WaitGroup.zig");
|
||||
const libtsan = @import("libtsan.zig");
|
||||
@ -138,8 +137,6 @@ emit_llvm_ir: ?EmitLoc,
|
||||
emit_analysis: ?EmitLoc,
|
||||
emit_docs: ?EmitLoc,
|
||||
|
||||
c_header: ?c_link.Header,
|
||||
|
||||
work_queue_wait_group: WaitGroup,
|
||||
|
||||
pub const InnerError = Module.InnerError;
|
||||
@ -164,6 +161,8 @@ pub const CSourceFile = struct {
|
||||
const Job = union(enum) {
|
||||
/// Write the machine code for a Decl to the output file.
|
||||
codegen_decl: *Module.Decl,
|
||||
/// Render the .h file snippet for the Decl.
|
||||
emit_h_decl: *Module.Decl,
|
||||
/// The Decl needs to be analyzed and possibly export itself.
|
||||
/// It may have already be analyzed, or it may have been determined
|
||||
/// to be outdated; in this case perform semantic analysis again.
|
||||
@ -866,9 +865,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
.root_pkg = root_pkg,
|
||||
.root_scope = root_scope,
|
||||
.zig_cache_artifact_directory = zig_cache_artifact_directory,
|
||||
.emit_h = options.emit_h,
|
||||
};
|
||||
break :blk module;
|
||||
} else null;
|
||||
} else blk: {
|
||||
if (options.emit_h != null) return error.NoZigModuleForCHeader;
|
||||
break :blk null;
|
||||
};
|
||||
errdefer if (module) |zm| zm.deinit();
|
||||
|
||||
const error_return_tracing = !strip and switch (options.optimize_mode) {
|
||||
@ -996,7 +999,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
|
||||
.local_cache_directory = options.local_cache_directory,
|
||||
.global_cache_directory = options.global_cache_directory,
|
||||
.bin_file = bin_file,
|
||||
.c_header = if (!use_llvm and options.emit_h != null) c_link.Header.init(gpa, options.emit_h) else null,
|
||||
.emit_asm = options.emit_asm,
|
||||
.emit_llvm_ir = options.emit_llvm_ir,
|
||||
.emit_analysis = options.emit_analysis,
|
||||
@ -1218,10 +1220,6 @@ pub fn destroy(self: *Compilation) void {
|
||||
}
|
||||
self.failed_c_objects.deinit(gpa);
|
||||
|
||||
if (self.c_header) |*header| {
|
||||
header.deinit();
|
||||
}
|
||||
|
||||
self.cache_parent.manifest_dir.close();
|
||||
if (self.owned_link_dir) |*dir| dir.close();
|
||||
|
||||
@ -1315,9 +1313,14 @@ pub fn update(self: *Compilation) !void {
|
||||
|
||||
// This is needed before reading the error flags.
|
||||
try self.bin_file.flush(self);
|
||||
|
||||
self.link_error_flags = self.bin_file.errorFlags();
|
||||
|
||||
if (!use_stage1) {
|
||||
if (self.bin_file.options.module) |module| {
|
||||
try link.File.C.flushEmitH(module);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are any errors, we anticipate the source files being loaded
|
||||
// to report error messages. Otherwise we unload all source files to save memory.
|
||||
if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) {
|
||||
@ -1325,20 +1328,6 @@ pub fn update(self: *Compilation) !void {
|
||||
module.root_scope.unload(self.gpa);
|
||||
}
|
||||
}
|
||||
|
||||
// If we've chosen to emit a C header, flush the header to the disk.
|
||||
if (self.c_header) |header| {
|
||||
const header_path = header.emit_loc.?;
|
||||
// If a directory has been provided, write the header there. Otherwise, just write it to the
|
||||
// cache directory.
|
||||
const header_dir = if (header_path.directory) |dir|
|
||||
dir.handle
|
||||
else
|
||||
self.local_cache_directory.handle;
|
||||
const header_file = try header_dir.createFile(header_path.basename, .{});
|
||||
defer header_file.close();
|
||||
try header.flush(header_file.writer());
|
||||
}
|
||||
}
|
||||
|
||||
/// Having the file open for writing is problematic as far as executing the
|
||||
@ -1357,7 +1346,8 @@ pub fn totalErrorCount(self: *Compilation) usize {
|
||||
var total: usize = self.failed_c_objects.items().len;
|
||||
|
||||
if (self.bin_file.options.module) |module| {
|
||||
total += module.failed_decls.items().len +
|
||||
total += module.failed_decls.count() +
|
||||
module.emit_h_failed_decls.count() +
|
||||
module.failed_exports.items().len +
|
||||
module.failed_files.items().len +
|
||||
@boolToInt(module.failed_root_src_file != null);
|
||||
@ -1396,6 +1386,12 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors {
|
||||
const source = try decl.scope.getSource(module);
|
||||
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
|
||||
}
|
||||
for (module.emit_h_failed_decls.items()) |entry| {
|
||||
const decl = entry.key;
|
||||
const err_msg = entry.value;
|
||||
const source = try decl.scope.getSource(module);
|
||||
try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*);
|
||||
}
|
||||
for (module.failed_exports.items()) |entry| {
|
||||
const decl = entry.key.owner_decl;
|
||||
const err_msg = entry.value;
|
||||
@ -1493,44 +1489,66 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
|
||||
|
||||
assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits());
|
||||
|
||||
self.bin_file.updateDecl(module, decl) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.AnalysisFail => {
|
||||
decl.analysis = .dependency_failure;
|
||||
},
|
||||
else => {
|
||||
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
|
||||
module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
|
||||
module.gpa,
|
||||
decl.src(),
|
||||
"unable to codegen: {s}",
|
||||
.{@errorName(err)},
|
||||
));
|
||||
decl.analysis = .codegen_failure_retryable;
|
||||
},
|
||||
}
|
||||
return;
|
||||
self.bin_file.updateDecl(module, decl) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.AnalysisFail => {
|
||||
decl.analysis = .codegen_failure;
|
||||
continue;
|
||||
},
|
||||
else => {
|
||||
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
|
||||
module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
|
||||
module.gpa,
|
||||
decl.src(),
|
||||
"unable to codegen: {s}",
|
||||
.{@errorName(err)},
|
||||
));
|
||||
decl.analysis = .codegen_failure_retryable;
|
||||
continue;
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
.emit_h_decl => |decl| switch (decl.analysis) {
|
||||
.unreferenced => unreachable,
|
||||
.in_progress => unreachable,
|
||||
.outdated => unreachable,
|
||||
|
||||
.sema_failure,
|
||||
.dependency_failure,
|
||||
.sema_failure_retryable,
|
||||
=> continue,
|
||||
|
||||
// emit-h only requires semantic analysis of the Decl to be complete,
|
||||
// it does not depend on machine code generation to succeed.
|
||||
.codegen_failure, .codegen_failure_retryable, .complete => {
|
||||
if (build_options.omit_stage2)
|
||||
@panic("sadly stage2 is omitted from this build to save memory on the CI server");
|
||||
const module = self.bin_file.options.module.?;
|
||||
const emit_loc = module.emit_h.?;
|
||||
const tv = decl.typed_value.most_recent.typed_value;
|
||||
const emit_h = decl.getEmitH(module);
|
||||
const fwd_decl = &emit_h.fwd_decl;
|
||||
fwd_decl.shrinkRetainingCapacity(0);
|
||||
|
||||
var dg: c_codegen.DeclGen = .{
|
||||
.module = module,
|
||||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
};
|
||||
defer dg.fwd_decl.deinit();
|
||||
|
||||
c_codegen.genHeader(&dg) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
try module.emit_h_failed_decls.put(module.gpa, decl, dg.error_msg.?);
|
||||
continue;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
if (self.c_header) |*header| {
|
||||
c_codegen.generateHeader(self, module, header, decl) catch |err| switch (err) {
|
||||
error.OutOfMemory => return error.OutOfMemory,
|
||||
error.AnalysisFail => {
|
||||
decl.analysis = .dependency_failure;
|
||||
},
|
||||
else => {
|
||||
try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1);
|
||||
module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create(
|
||||
module.gpa,
|
||||
decl.src(),
|
||||
"unable to generate C header: {s}",
|
||||
.{@errorName(err)},
|
||||
));
|
||||
decl.analysis = .codegen_failure_retryable;
|
||||
},
|
||||
};
|
||||
}
|
||||
fwd_decl.* = dg.fwd_decl.moveToUnmanaged();
|
||||
fwd_decl.shrink(module.gpa, fwd_decl.items.len);
|
||||
},
|
||||
},
|
||||
.analyze_decl => |decl| {
|
||||
@ -2998,9 +3016,9 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
|
||||
man.hash.add(comp.bin_file.options.function_sections);
|
||||
man.hash.add(comp.bin_file.options.is_test);
|
||||
man.hash.add(comp.bin_file.options.emit != null);
|
||||
man.hash.add(comp.c_header != null);
|
||||
if (comp.c_header) |header| {
|
||||
man.hash.addEmitLoc(header.emit_loc.?);
|
||||
man.hash.add(mod.emit_h != null);
|
||||
if (mod.emit_h) |emit_h| {
|
||||
man.hash.addEmitLoc(emit_h);
|
||||
}
|
||||
man.hash.addOptionalEmitLoc(comp.emit_asm);
|
||||
man.hash.addOptionalEmitLoc(comp.emit_llvm_ir);
|
||||
@ -3105,10 +3123,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node
|
||||
});
|
||||
break :blk try directory.join(arena, &[_][]const u8{bin_basename});
|
||||
} else "";
|
||||
if (comp.c_header != null) {
|
||||
if (mod.emit_h != null) {
|
||||
log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{});
|
||||
}
|
||||
const emit_h_path = try stage1LocPath(arena, if (comp.c_header) |header| header.emit_loc else null, directory);
|
||||
const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory);
|
||||
const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory);
|
||||
const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory);
|
||||
const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory);
|
||||
|
||||
@ -57,6 +57,10 @@ decl_table: std.ArrayHashMapUnmanaged(Scope.NameHash, *Decl, Scope.name_hash_has
|
||||
/// Note that a Decl can succeed but the Fn it represents can fail. In this case,
|
||||
/// a Decl can have a failed_decls entry but have analysis status of success.
|
||||
failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{},
|
||||
/// When emit_h is non-null, each Decl gets one more compile error slot for
|
||||
/// emit-h failing for that Decl. This table is also how we tell if a Decl has
|
||||
/// failed emit-h or succeeded.
|
||||
emit_h_failed_decls: std.AutoArrayHashMapUnmanaged(*Decl, *Compilation.ErrorMsg) = .{},
|
||||
/// Using a map here for consistency with the other fields here.
|
||||
/// The ErrorMsg memory is owned by the `Scope`, using Module's general purpose allocator.
|
||||
failed_files: std.AutoArrayHashMapUnmanaged(*Scope, *Compilation.ErrorMsg) = .{},
|
||||
@ -94,6 +98,8 @@ stage1_flags: packed struct {
|
||||
reserved: u2 = 0,
|
||||
} = .{},
|
||||
|
||||
emit_h: ?Compilation.EmitLoc,
|
||||
|
||||
pub const Export = struct {
|
||||
options: std.builtin.ExportOptions,
|
||||
/// Byte offset into the file that contains the export directive.
|
||||
@ -114,6 +120,13 @@ pub const Export = struct {
|
||||
},
|
||||
};
|
||||
|
||||
/// When Module emit_h field is non-null, each Decl is allocated via this struct, so that
|
||||
/// there can be EmitH state attached to each Decl.
|
||||
pub const DeclPlusEmitH = struct {
|
||||
decl: Decl,
|
||||
emit_h: EmitH,
|
||||
};
|
||||
|
||||
pub const Decl = struct {
|
||||
/// This name is relative to the containing namespace of the decl. It uses a null-termination
|
||||
/// to save bytes, since there can be a lot of decls in a compilation. The null byte is not allowed
|
||||
@ -202,14 +215,21 @@ pub const Decl = struct {
|
||||
/// stage1 compiler giving me: `error: struct 'Module.Decl' depends on itself`
|
||||
pub const DepsTable = std.ArrayHashMapUnmanaged(*Decl, void, std.array_hash_map.getAutoHashFn(*Decl), std.array_hash_map.getAutoEqlFn(*Decl), false);
|
||||
|
||||
pub fn destroy(self: *Decl, gpa: *Allocator) void {
|
||||
pub fn destroy(self: *Decl, module: *Module) void {
|
||||
const gpa = module.gpa;
|
||||
gpa.free(mem.spanZ(self.name));
|
||||
if (self.typedValueManaged()) |tvm| {
|
||||
tvm.deinit(gpa);
|
||||
}
|
||||
self.dependants.deinit(gpa);
|
||||
self.dependencies.deinit(gpa);
|
||||
gpa.destroy(self);
|
||||
if (module.emit_h != null) {
|
||||
const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", self);
|
||||
decl_plus_emit_h.emit_h.fwd_decl.deinit(gpa);
|
||||
gpa.destroy(decl_plus_emit_h);
|
||||
} else {
|
||||
gpa.destroy(self);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn src(self: Decl) usize {
|
||||
@ -275,6 +295,12 @@ pub const Decl = struct {
|
||||
return self.scope.cast(Scope.Container).?.file_scope;
|
||||
}
|
||||
|
||||
pub fn getEmitH(decl: *Decl, module: *Module) *EmitH {
|
||||
assert(module.emit_h != null);
|
||||
const decl_plus_emit_h = @fieldParentPtr(DeclPlusEmitH, "decl", decl);
|
||||
return &decl_plus_emit_h.emit_h;
|
||||
}
|
||||
|
||||
fn removeDependant(self: *Decl, other: *Decl) void {
|
||||
self.dependants.removeAssertDiscard(other);
|
||||
}
|
||||
@ -284,6 +310,11 @@ pub const Decl = struct {
|
||||
}
|
||||
};
|
||||
|
||||
/// This state is attached to every Decl when Module emit_h is non-null.
|
||||
pub const EmitH = struct {
|
||||
fwd_decl: std.ArrayListUnmanaged(u8) = .{},
|
||||
};
|
||||
|
||||
/// Fn struct memory is owned by the Decl's TypedValue.Managed arena allocator.
|
||||
/// Extern functions do not have this data structure; they are represented by
|
||||
/// the `Decl` only, with a `Value` tag of `extern_fn`.
|
||||
@ -881,7 +912,7 @@ pub fn deinit(self: *Module) void {
|
||||
self.deletion_set.deinit(gpa);
|
||||
|
||||
for (self.decl_table.items()) |entry| {
|
||||
entry.value.destroy(gpa);
|
||||
entry.value.destroy(self);
|
||||
}
|
||||
self.decl_table.deinit(gpa);
|
||||
|
||||
@ -890,6 +921,11 @@ pub fn deinit(self: *Module) void {
|
||||
}
|
||||
self.failed_decls.deinit(gpa);
|
||||
|
||||
for (self.emit_h_failed_decls.items()) |entry| {
|
||||
entry.value.destroy(gpa);
|
||||
}
|
||||
self.emit_h_failed_decls.deinit(gpa);
|
||||
|
||||
for (self.failed_files.items()) |entry| {
|
||||
entry.value.destroy(gpa);
|
||||
}
|
||||
@ -1148,6 +1184,10 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
|
||||
try self.comp.bin_file.allocateDeclIndexes(decl);
|
||||
try self.comp.work_queue.writeItem(.{ .codegen_decl = decl });
|
||||
|
||||
if (type_changed and self.emit_h != null) {
|
||||
try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
}
|
||||
|
||||
return type_changed;
|
||||
};
|
||||
|
||||
@ -1267,6 +1307,9 @@ fn astGenAndAnalyzeDecl(self: *Module, decl: *Decl) !bool {
|
||||
// increasing how many computations can be done in parallel.
|
||||
try self.comp.bin_file.allocateDeclIndexes(decl);
|
||||
try self.comp.work_queue.writeItem(.{ .codegen_decl = decl });
|
||||
if (type_changed and self.emit_h != null) {
|
||||
try self.comp.work_queue.writeItem(.{ .emit_h_decl = decl });
|
||||
}
|
||||
} else if (!prev_is_inline and prev_type_has_bits) {
|
||||
self.comp.bin_file.freeDecl(decl);
|
||||
}
|
||||
@ -1835,9 +1878,13 @@ pub fn deleteDecl(self: *Module, decl: *Decl) !void {
|
||||
if (self.failed_decls.remove(decl)) |entry| {
|
||||
entry.value.destroy(self.gpa);
|
||||
}
|
||||
if (self.emit_h_failed_decls.remove(decl)) |entry| {
|
||||
entry.value.destroy(self.gpa);
|
||||
}
|
||||
self.deleteDeclExports(decl);
|
||||
self.comp.bin_file.freeDecl(decl);
|
||||
decl.destroy(self.gpa);
|
||||
|
||||
decl.destroy(self);
|
||||
}
|
||||
|
||||
/// Delete all the Export objects that are caused by this Decl. Re-analysis of
|
||||
@ -1921,16 +1968,28 @@ fn markOutdatedDecl(self: *Module, decl: *Decl) !void {
|
||||
if (self.failed_decls.remove(decl)) |entry| {
|
||||
entry.value.destroy(self.gpa);
|
||||
}
|
||||
if (self.emit_h_failed_decls.remove(decl)) |entry| {
|
||||
entry.value.destroy(self.gpa);
|
||||
}
|
||||
decl.analysis = .outdated;
|
||||
}
|
||||
|
||||
fn allocateNewDecl(
|
||||
self: *Module,
|
||||
mod: *Module,
|
||||
scope: *Scope,
|
||||
src_index: usize,
|
||||
contents_hash: std.zig.SrcHash,
|
||||
) !*Decl {
|
||||
const new_decl = try self.gpa.create(Decl);
|
||||
// If we have emit-h then we must allocate a bigger structure to store the emit-h state.
|
||||
const new_decl: *Decl = if (mod.emit_h != null) blk: {
|
||||
const parent_struct = try mod.gpa.create(DeclPlusEmitH);
|
||||
parent_struct.* = .{
|
||||
.emit_h = .{},
|
||||
.decl = undefined,
|
||||
};
|
||||
break :blk &parent_struct.decl;
|
||||
} else try mod.gpa.create(Decl);
|
||||
|
||||
new_decl.* = .{
|
||||
.name = "",
|
||||
.scope = scope.namespace(),
|
||||
@ -1939,18 +1998,18 @@ fn allocateNewDecl(
|
||||
.analysis = .unreferenced,
|
||||
.deletion_flag = false,
|
||||
.contents_hash = contents_hash,
|
||||
.link = switch (self.comp.bin_file.tag) {
|
||||
.link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = link.File.Coff.TextBlock.empty },
|
||||
.elf => .{ .elf = link.File.Elf.TextBlock.empty },
|
||||
.macho => .{ .macho = link.File.MachO.TextBlock.empty },
|
||||
.c => .{ .c = {} },
|
||||
.c => .{ .c = link.File.C.DeclBlock.empty },
|
||||
.wasm => .{ .wasm = {} },
|
||||
},
|
||||
.fn_link = switch (self.comp.bin_file.tag) {
|
||||
.fn_link = switch (mod.comp.bin_file.tag) {
|
||||
.coff => .{ .coff = {} },
|
||||
.elf => .{ .elf = link.File.Elf.SrcFn.empty },
|
||||
.macho => .{ .macho = link.File.MachO.SrcFn.empty },
|
||||
.c => .{ .c = {} },
|
||||
.c => .{ .c = link.File.C.FnBlock.empty },
|
||||
.wasm => .{ .wasm = null },
|
||||
},
|
||||
.generation = 0,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
17
src/link.zig
17
src/link.zig
@ -130,7 +130,7 @@ pub const File = struct {
|
||||
elf: Elf.TextBlock,
|
||||
coff: Coff.TextBlock,
|
||||
macho: MachO.TextBlock,
|
||||
c: void,
|
||||
c: C.DeclBlock,
|
||||
wasm: void,
|
||||
};
|
||||
|
||||
@ -138,7 +138,7 @@ pub const File = struct {
|
||||
elf: Elf.SrcFn,
|
||||
coff: Coff.SrcFn,
|
||||
macho: MachO.SrcFn,
|
||||
c: void,
|
||||
c: C.FnBlock,
|
||||
wasm: ?Wasm.FnData,
|
||||
};
|
||||
|
||||
@ -301,7 +301,8 @@ pub const File = struct {
|
||||
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclLineNumber(module, decl),
|
||||
.c, .wasm => {},
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl),
|
||||
.wasm => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -312,7 +313,8 @@ pub const File = struct {
|
||||
.coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl),
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).allocateDeclIndexes(decl),
|
||||
.c, .wasm => {},
|
||||
.c => return @fieldParentPtr(C, "base", base).allocateDeclIndexes(decl),
|
||||
.wasm => {},
|
||||
}
|
||||
}
|
||||
|
||||
@ -407,12 +409,13 @@ pub const File = struct {
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when a Decl is deleted from the Module.
|
||||
pub fn freeDecl(base: *File, decl: *Module.Decl) void {
|
||||
switch (base.tag) {
|
||||
.coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl),
|
||||
.elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl),
|
||||
.macho => @fieldParentPtr(MachO, "base", base).freeDecl(decl),
|
||||
.c => unreachable,
|
||||
.c => @fieldParentPtr(C, "base", base).freeDecl(decl),
|
||||
.wasm => @fieldParentPtr(Wasm, "base", base).freeDecl(decl),
|
||||
}
|
||||
}
|
||||
@ -432,14 +435,14 @@ pub const File = struct {
|
||||
pub fn updateDeclExports(
|
||||
base: *File,
|
||||
module: *Module,
|
||||
decl: *const Module.Decl,
|
||||
decl: *Module.Decl,
|
||||
exports: []const *Module.Export,
|
||||
) !void {
|
||||
switch (base.tag) {
|
||||
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl, exports),
|
||||
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl, exports),
|
||||
.macho => return @fieldParentPtr(MachO, "base", base).updateDeclExports(module, decl, exports),
|
||||
.c => return {},
|
||||
.c => return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl, exports),
|
||||
.wasm => return @fieldParentPtr(Wasm, "base", base).updateDeclExports(module, decl, exports),
|
||||
}
|
||||
}
|
||||
|
||||
239
src/link/C.zig
239
src/link/C.zig
@ -8,46 +8,31 @@ const fs = std.fs;
|
||||
const codegen = @import("../codegen/c.zig");
|
||||
const link = @import("../link.zig");
|
||||
const trace = @import("../tracy.zig").trace;
|
||||
const File = link.File;
|
||||
const C = @This();
|
||||
|
||||
pub const base_tag: File.Tag = .c;
|
||||
pub const base_tag: link.File.Tag = .c;
|
||||
pub const zig_h = @embedFile("C/zig.h");
|
||||
|
||||
pub const Header = struct {
|
||||
buf: std.ArrayList(u8),
|
||||
emit_loc: ?Compilation.EmitLoc,
|
||||
base: link.File,
|
||||
|
||||
pub fn init(allocator: *Allocator, emit_loc: ?Compilation.EmitLoc) Header {
|
||||
return .{
|
||||
.buf = std.ArrayList(u8).init(allocator),
|
||||
.emit_loc = emit_loc,
|
||||
};
|
||||
}
|
||||
/// Per-declaration data. For functions this is the body, and
|
||||
/// the forward declaration is stored in the FnBlock.
|
||||
pub const DeclBlock = struct {
|
||||
code: std.ArrayListUnmanaged(u8),
|
||||
|
||||
pub fn flush(self: *const Header, writer: anytype) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
try writer.writeAll(@embedFile("cbe.h"));
|
||||
if (self.buf.items.len > 0) {
|
||||
try writer.print("{s}", .{self.buf.items});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Header) void {
|
||||
self.buf.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
pub const empty: DeclBlock = .{
|
||||
.code = .{},
|
||||
};
|
||||
};
|
||||
|
||||
base: File,
|
||||
/// Per-function data.
|
||||
pub const FnBlock = struct {
|
||||
fwd_decl: std.ArrayListUnmanaged(u8),
|
||||
|
||||
header: Header,
|
||||
constants: std.ArrayList(u8),
|
||||
main: std.ArrayList(u8),
|
||||
|
||||
called: std.StringHashMap(void),
|
||||
error_msg: *Compilation.ErrorMsg = undefined,
|
||||
pub const empty: FnBlock = .{
|
||||
.fwd_decl = .{},
|
||||
};
|
||||
};
|
||||
|
||||
pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Options) !*C {
|
||||
assert(options.object_format == .c);
|
||||
@ -55,7 +40,11 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
|
||||
if (options.use_llvm) return error.LLVMHasNoCBackend;
|
||||
if (options.use_lld) return error.LLDHasNoCBackend;
|
||||
|
||||
const file = try options.emit.?.directory.handle.createFile(sub_path, .{ .truncate = true, .read = true, .mode = link.determineMode(options) });
|
||||
const file = try options.emit.?.directory.handle.createFile(sub_path, .{
|
||||
// Truncation is done on `flush`.
|
||||
.truncate = false,
|
||||
.mode = link.determineMode(options),
|
||||
});
|
||||
errdefer file.close();
|
||||
|
||||
var c_file = try allocator.create(C);
|
||||
@ -68,34 +57,69 @@ pub fn openPath(allocator: *Allocator, sub_path: []const u8, options: link.Optio
|
||||
.file = file,
|
||||
.allocator = allocator,
|
||||
},
|
||||
.main = std.ArrayList(u8).init(allocator),
|
||||
.header = Header.init(allocator, null),
|
||||
.constants = std.ArrayList(u8).init(allocator),
|
||||
.called = std.StringHashMap(void).init(allocator),
|
||||
};
|
||||
|
||||
return c_file;
|
||||
}
|
||||
|
||||
pub fn fail(self: *C, src: usize, comptime format: []const u8, args: anytype) error{ AnalysisFail, OutOfMemory } {
|
||||
self.error_msg = try Compilation.ErrorMsg.create(self.base.allocator, src, format, args);
|
||||
return error.AnalysisFail;
|
||||
pub fn deinit(self: *C) void {
|
||||
const module = self.base.options.module orelse return;
|
||||
for (module.decl_table.items()) |entry| {
|
||||
self.freeDecl(entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn deinit(self: *C) void {
|
||||
self.main.deinit();
|
||||
self.header.deinit();
|
||||
self.constants.deinit();
|
||||
self.called.deinit();
|
||||
pub fn allocateDeclIndexes(self: *C, decl: *Module.Decl) !void {}
|
||||
|
||||
pub fn freeDecl(self: *C, decl: *Module.Decl) void {
|
||||
decl.link.c.code.deinit(self.base.allocator);
|
||||
decl.fn_link.c.fwd_decl.deinit(self.base.allocator);
|
||||
}
|
||||
|
||||
pub fn updateDecl(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
codegen.generate(self, module, decl) catch |err| {
|
||||
if (err == error.AnalysisFail) {
|
||||
try module.failed_decls.put(module.gpa, decl, self.error_msg);
|
||||
}
|
||||
return err;
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const fwd_decl = &decl.fn_link.c.fwd_decl;
|
||||
const code = &decl.link.c.code;
|
||||
fwd_decl.shrinkRetainingCapacity(0);
|
||||
code.shrinkRetainingCapacity(0);
|
||||
|
||||
var object: codegen.Object = .{
|
||||
.dg = .{
|
||||
.module = module,
|
||||
.error_msg = null,
|
||||
.decl = decl,
|
||||
.fwd_decl = fwd_decl.toManaged(module.gpa),
|
||||
},
|
||||
.gpa = module.gpa,
|
||||
.code = code.toManaged(module.gpa),
|
||||
.value_map = codegen.CValueMap.init(module.gpa),
|
||||
};
|
||||
defer object.value_map.deinit();
|
||||
defer object.code.deinit();
|
||||
defer object.dg.fwd_decl.deinit();
|
||||
|
||||
codegen.genDecl(&object) catch |err| switch (err) {
|
||||
error.AnalysisFail => {
|
||||
try module.failed_decls.put(module.gpa, decl, object.dg.error_msg.?);
|
||||
return;
|
||||
},
|
||||
else => |e| return e,
|
||||
};
|
||||
|
||||
fwd_decl.* = object.dg.fwd_decl.moveToUnmanaged();
|
||||
code.* = object.code.moveToUnmanaged();
|
||||
|
||||
// Free excess allocated memory for this Decl.
|
||||
fwd_decl.shrink(module.gpa, fwd_decl.items.len);
|
||||
code.shrink(module.gpa, code.items.len);
|
||||
}
|
||||
|
||||
pub fn updateDeclLineNumber(self: *C, module: *Module, decl: *Module.Decl) !void {
|
||||
// The C backend does not have the ability to fix line numbers without re-generating
|
||||
// the entire Decl.
|
||||
return self.updateDecl(module, decl);
|
||||
}
|
||||
|
||||
pub fn flush(self: *C, comp: *Compilation) !void {
|
||||
@ -106,21 +130,108 @@ pub fn flushModule(self: *C, comp: *Compilation) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const writer = self.base.file.?.writer();
|
||||
try self.header.flush(writer);
|
||||
if (self.header.buf.items.len > 0) {
|
||||
try writer.writeByte('\n');
|
||||
const module = self.base.options.module.?;
|
||||
|
||||
// This code path happens exclusively with -ofmt=c. The flush logic for
|
||||
// emit-h is in `flushEmitH` below.
|
||||
|
||||
// We collect a list of buffers to write, and write them all at once with pwritev 😎
|
||||
var all_buffers = std.ArrayList(std.os.iovec_const).init(comp.gpa);
|
||||
defer all_buffers.deinit();
|
||||
|
||||
// This is at least enough until we get to the function bodies without error handling.
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
|
||||
|
||||
var file_size: u64 = zig_h.len;
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
.iov_base = zig_h,
|
||||
.iov_len = zig_h.len,
|
||||
});
|
||||
|
||||
var fn_count: usize = 0;
|
||||
|
||||
// Forward decls and non-functions first.
|
||||
for (module.decl_table.items()) |kv| {
|
||||
const decl = kv.value;
|
||||
const decl_tv = decl.typed_value.most_recent.typed_value;
|
||||
const buf = buf: {
|
||||
if (decl_tv.val.castTag(.function)) |_| {
|
||||
fn_count += 1;
|
||||
break :buf decl.fn_link.c.fwd_decl.items;
|
||||
} else {
|
||||
break :buf decl.link.c.code.items;
|
||||
}
|
||||
};
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
.iov_base = buf.ptr,
|
||||
.iov_len = buf.len,
|
||||
});
|
||||
file_size += buf.len;
|
||||
}
|
||||
if (self.constants.items.len > 0) {
|
||||
try writer.print("{s}\n", .{self.constants.items});
|
||||
}
|
||||
if (self.main.items.len > 1) {
|
||||
const last_two = self.main.items[self.main.items.len - 2 ..];
|
||||
if (std.mem.eql(u8, last_two, "\n\n")) {
|
||||
self.main.items.len -= 1;
|
||||
|
||||
// Now the function bodies.
|
||||
try all_buffers.ensureCapacity(all_buffers.items.len + fn_count);
|
||||
for (module.decl_table.items()) |kv| {
|
||||
const decl = kv.value;
|
||||
const decl_tv = decl.typed_value.most_recent.typed_value;
|
||||
if (decl_tv.val.castTag(.function)) |_| {
|
||||
const buf = decl.link.c.code.items;
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
.iov_base = buf.ptr,
|
||||
.iov_len = buf.len,
|
||||
});
|
||||
file_size += buf.len;
|
||||
}
|
||||
}
|
||||
try writer.writeAll(self.main.items);
|
||||
self.base.file.?.close();
|
||||
self.base.file = null;
|
||||
|
||||
const file = self.base.file.?;
|
||||
try file.setEndPos(file_size);
|
||||
try file.pwritevAll(all_buffers.items, 0);
|
||||
}
|
||||
|
||||
pub fn flushEmitH(module: *Module) !void {
|
||||
const tracy = trace(@src());
|
||||
defer tracy.end();
|
||||
|
||||
const emit_h_loc = module.emit_h orelse return;
|
||||
|
||||
// We collect a list of buffers to write, and write them all at once with pwritev 😎
|
||||
var all_buffers = std.ArrayList(std.os.iovec_const).init(module.gpa);
|
||||
defer all_buffers.deinit();
|
||||
|
||||
try all_buffers.ensureCapacity(module.decl_table.count() + 1);
|
||||
|
||||
var file_size: u64 = zig_h.len;
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
.iov_base = zig_h,
|
||||
.iov_len = zig_h.len,
|
||||
});
|
||||
|
||||
for (module.decl_table.items()) |kv| {
|
||||
const emit_h = kv.value.getEmitH(module);
|
||||
const buf = emit_h.fwd_decl.items;
|
||||
all_buffers.appendAssumeCapacity(.{
|
||||
.iov_base = buf.ptr,
|
||||
.iov_len = buf.len,
|
||||
});
|
||||
file_size += buf.len;
|
||||
}
|
||||
|
||||
const directory = emit_h_loc.directory orelse module.comp.local_cache_directory;
|
||||
const file = try directory.handle.createFile(emit_h_loc.basename, .{
|
||||
// We set the end position explicitly below; by not truncating the file, we possibly
|
||||
// make it easier on the file system by doing 1 reallocation instead of two.
|
||||
.truncate = false,
|
||||
});
|
||||
defer file.close();
|
||||
|
||||
try file.setEndPos(file_size);
|
||||
try file.pwritevAll(all_buffers.items, 0);
|
||||
}
|
||||
|
||||
pub fn updateDeclExports(
|
||||
self: *C,
|
||||
module: *Module,
|
||||
decl: *Module.Decl,
|
||||
exports: []const *Module.Export,
|
||||
) !void {}
|
||||
|
||||
@ -22,23 +22,37 @@
|
||||
#define zig_unreachable()
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define zig_breakpoint __debugbreak()
|
||||
#else
|
||||
#if defined(__MINGW32__) || defined(__MINGW64__)
|
||||
#define zig_breakpoint __debugbreak()
|
||||
#elif defined(__clang__)
|
||||
#define zig_breakpoint __builtin_debugtrap()
|
||||
#if __STDC_VERSION__ >= 199901L
|
||||
#define ZIG_RESTRICT restrict
|
||||
#elif defined(__GNUC__)
|
||||
#define zig_breakpoint __builtin_trap()
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
#define zig_breakpoint __asm__ volatile("int $0x03");
|
||||
#define ZIG_RESTRICT __restrict
|
||||
#else
|
||||
#define zig_breakpoint raise(SIGTRAP)
|
||||
#define ZIG_RESTRICT
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
#define ZIG_EXTERN_C extern "C"
|
||||
#else
|
||||
#define ZIG_EXTERN_C
|
||||
#endif
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#define zig_breakpoint() __debugbreak()
|
||||
#elif defined(__MINGW32__) || defined(__MINGW64__)
|
||||
#define zig_breakpoint() __debugbreak()
|
||||
#elif defined(__clang__)
|
||||
#define zig_breakpoint() __builtin_debugtrap()
|
||||
#elif defined(__GNUC__)
|
||||
#define zig_breakpoint() __builtin_trap()
|
||||
#elif defined(__i386__) || defined(__x86_64__)
|
||||
#define zig_breakpoint() __asm__ volatile("int $0x03");
|
||||
#else
|
||||
#define zig_breakpoint() raise(SIGTRAP)
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#define int128_t __int128
|
||||
#define uint128_t unsigned __int128
|
||||
#include <string.h>
|
||||
ZIG_EXTERN_C void *memcpy (void *ZIG_RESTRICT, const void *ZIG_RESTRICT, size_t);
|
||||
|
||||
17
src/test.zig
17
src/test.zig
@ -13,7 +13,7 @@ const glibc_multi_install_dir: ?[]const u8 = build_options.glibc_multi_install_d
|
||||
const ThreadPool = @import("ThreadPool.zig");
|
||||
const CrossTarget = std.zig.CrossTarget;
|
||||
|
||||
const c_header = @embedFile("link/cbe.h");
|
||||
const zig_h = link.File.C.zig_h;
|
||||
|
||||
test "self-hosted" {
|
||||
var ctx = TestContext.init();
|
||||
@ -324,11 +324,11 @@ pub const TestContext = struct {
|
||||
}
|
||||
|
||||
pub fn c(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
|
||||
ctx.addC(name, target, .Zig).addCompareObjectFile(src, c_header ++ out);
|
||||
ctx.addC(name, target, .Zig).addCompareObjectFile(src, zig_h ++ out);
|
||||
}
|
||||
|
||||
pub fn h(ctx: *TestContext, name: []const u8, target: CrossTarget, src: [:0]const u8, comptime out: [:0]const u8) void {
|
||||
ctx.addC(name, target, .Zig).addHeader(src, c_header ++ out);
|
||||
ctx.addC(name, target, .Zig).addHeader(src, zig_h ++ out);
|
||||
}
|
||||
|
||||
pub fn addCompareOutput(
|
||||
@ -700,11 +700,12 @@ pub const TestContext = struct {
|
||||
},
|
||||
}
|
||||
}
|
||||
if (comp.bin_file.cast(link.File.C)) |c_file| {
|
||||
std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
|
||||
c_file.main.items,
|
||||
});
|
||||
}
|
||||
// TODO print generated C code
|
||||
//if (comp.bin_file.cast(link.File.C)) |c_file| {
|
||||
// std.debug.print("Generated C: \n===============\n{s}\n\n===========\n\n", .{
|
||||
// c_file.main.items,
|
||||
// });
|
||||
//}
|
||||
std.debug.print("Test failed.\n", .{});
|
||||
std.process.exit(1);
|
||||
}
|
||||
|
||||
@ -22,15 +22,107 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
, "hello world!" ++ std.cstr.line_sep);
|
||||
|
||||
// Now change the message only
|
||||
// TODO fix C backend not supporting updates
|
||||
// https://github.com/ziglang/zig/issues/7589
|
||||
//case.addCompareOutput(
|
||||
// \\extern fn puts(s: [*:0]const u8) c_int;
|
||||
// \\export fn main() c_int {
|
||||
// \\ _ = puts("yo");
|
||||
// \\ return 0;
|
||||
// \\}
|
||||
//, "yo" ++ std.cstr.line_sep);
|
||||
case.addCompareOutput(
|
||||
\\extern fn puts(s: [*:0]const u8) c_int;
|
||||
\\export fn main() c_int {
|
||||
\\ _ = puts("yo");
|
||||
\\ return 0;
|
||||
\\}
|
||||
, "yo" ++ std.cstr.line_sep);
|
||||
}
|
||||
|
||||
{
|
||||
var case = ctx.exeFromCompiledC("x86_64-linux inline assembly", linux_x64);
|
||||
|
||||
// Exit with 0
|
||||
case.addCompareOutput(
|
||||
\\fn exitGood() noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (0)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
\\export fn main() c_int {
|
||||
\\ exitGood();
|
||||
\\}
|
||||
, "");
|
||||
|
||||
// Pass a usize parameter to exit
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ exit(0);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: usize) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
// Change the parameter to u8
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ exit(0);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: u8) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
, "");
|
||||
|
||||
// Do some arithmetic at the exit callsite
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ exitMath(1);
|
||||
\\}
|
||||
\\
|
||||
\\fn exitMath(a: u8) noreturn {
|
||||
\\ exit(0 + a - a);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: u8) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
, "");
|
||||
|
||||
// Invert the arithmetic
|
||||
case.addCompareOutput(
|
||||
\\export fn main() c_int {
|
||||
\\ exitMath(1);
|
||||
\\}
|
||||
\\
|
||||
\\fn exitMath(a: u8) noreturn {
|
||||
\\ exit(a + 0 - a);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: u8) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
, "");
|
||||
}
|
||||
|
||||
{
|
||||
@ -88,6 +180,8 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
\\ unreachable;
|
||||
\\}
|
||||
,
|
||||
\\ZIG_EXTERN_C zig_noreturn void _start(void);
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
@ -97,254 +191,37 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
ctx.h("simple header", linux_x64,
|
||||
\\export fn start() void{}
|
||||
,
|
||||
\\void start(void);
|
||||
\\
|
||||
);
|
||||
ctx.c("less empty start function", linux_x64,
|
||||
\\fn main() noreturn {
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
\\export fn _start() noreturn {
|
||||
\\ main();
|
||||
\\}
|
||||
,
|
||||
\\static zig_noreturn void main(void);
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ main();
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void main(void) {
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
// TODO: implement return values
|
||||
// TODO: figure out a way to prevent asm constants from being generated
|
||||
ctx.c("inline asm", linux_x64,
|
||||
\\fn exitGood() noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (0)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
\\export fn _start() noreturn {
|
||||
\\ exitGood();
|
||||
\\}
|
||||
,
|
||||
\\static zig_noreturn void exitGood(void);
|
||||
\\
|
||||
\\static uint8_t exitGood__anon_0[6] = "{rax}";
|
||||
\\static uint8_t exitGood__anon_1[6] = "{rdi}";
|
||||
\\static uint8_t exitGood__anon_2[8] = "syscall";
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ exitGood();
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exitGood(void) {
|
||||
\\ register uintptr_t rax_constant __asm__("rax") = 231;
|
||||
\\ register uintptr_t rdi_constant __asm__("rdi") = 0;
|
||||
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
ctx.c("exit with parameter", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
\\ exit(0);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: usize) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
,
|
||||
\\static zig_noreturn void exit(uintptr_t arg0);
|
||||
\\
|
||||
\\static uint8_t exit__anon_0[6] = "{rax}";
|
||||
\\static uint8_t exit__anon_1[6] = "{rdi}";
|
||||
\\static uint8_t exit__anon_2[8] = "syscall";
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ exit(0);
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exit(uintptr_t arg0) {
|
||||
\\ register uintptr_t rax_constant __asm__("rax") = 231;
|
||||
\\ register uintptr_t rdi_constant __asm__("rdi") = arg0;
|
||||
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
ctx.c("exit with u8 parameter", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
\\ exit(0);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: u8) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
,
|
||||
\\static zig_noreturn void exit(uint8_t arg0);
|
||||
\\
|
||||
\\static uint8_t exit__anon_0[6] = "{rax}";
|
||||
\\static uint8_t exit__anon_1[6] = "{rdi}";
|
||||
\\static uint8_t exit__anon_2[8] = "syscall";
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ exit(0);
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exit(uint8_t arg0) {
|
||||
\\ uintptr_t const __temp_0 = (uintptr_t)arg0;
|
||||
\\ register uintptr_t rax_constant __asm__("rax") = 231;
|
||||
\\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0;
|
||||
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
ctx.c("exit with u8 arithmetic", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
\\ exitMath(1);
|
||||
\\}
|
||||
\\
|
||||
\\fn exitMath(a: u8) noreturn {
|
||||
\\ exit(0 + a - a);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: u8) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
,
|
||||
\\static zig_noreturn void exitMath(uint8_t arg0);
|
||||
\\static zig_noreturn void exit(uint8_t arg0);
|
||||
\\
|
||||
\\static uint8_t exit__anon_0[6] = "{rax}";
|
||||
\\static uint8_t exit__anon_1[6] = "{rdi}";
|
||||
\\static uint8_t exit__anon_2[8] = "syscall";
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ exitMath(1);
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exitMath(uint8_t arg0) {
|
||||
\\ uint8_t const __temp_0 = 0 + arg0;
|
||||
\\ uint8_t const __temp_1 = __temp_0 - arg0;
|
||||
\\ exit(__temp_1);
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exit(uint8_t arg0) {
|
||||
\\ uintptr_t const __temp_0 = (uintptr_t)arg0;
|
||||
\\ register uintptr_t rax_constant __asm__("rax") = 231;
|
||||
\\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0;
|
||||
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
\\}
|
||||
\\
|
||||
);
|
||||
ctx.c("exit with u8 arithmetic inverted", linux_x64,
|
||||
\\export fn _start() noreturn {
|
||||
\\ exitMath(1);
|
||||
\\}
|
||||
\\
|
||||
\\fn exitMath(a: u8) noreturn {
|
||||
\\ exit(a + 0 - a);
|
||||
\\}
|
||||
\\
|
||||
\\fn exit(code: u8) noreturn {
|
||||
\\ asm volatile ("syscall"
|
||||
\\ :
|
||||
\\ : [number] "{rax}" (231),
|
||||
\\ [arg1] "{rdi}" (code)
|
||||
\\ );
|
||||
\\ unreachable;
|
||||
\\}
|
||||
\\
|
||||
,
|
||||
\\static zig_noreturn void exitMath(uint8_t arg0);
|
||||
\\static zig_noreturn void exit(uint8_t arg0);
|
||||
\\
|
||||
\\static uint8_t exit__anon_0[6] = "{rax}";
|
||||
\\static uint8_t exit__anon_1[6] = "{rdi}";
|
||||
\\static uint8_t exit__anon_2[8] = "syscall";
|
||||
\\
|
||||
\\zig_noreturn void _start(void) {
|
||||
\\ exitMath(1);
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exitMath(uint8_t arg0) {
|
||||
\\ uint8_t const __temp_0 = arg0 + 0;
|
||||
\\ uint8_t const __temp_1 = __temp_0 - arg0;
|
||||
\\ exit(__temp_1);
|
||||
\\}
|
||||
\\
|
||||
\\static zig_noreturn void exit(uint8_t arg0) {
|
||||
\\ uintptr_t const __temp_0 = (uintptr_t)arg0;
|
||||
\\ register uintptr_t rax_constant __asm__("rax") = 231;
|
||||
\\ register uintptr_t rdi_constant __asm__("rdi") = __temp_0;
|
||||
\\ __asm volatile ("syscall" :: ""(rax_constant), ""(rdi_constant));
|
||||
\\ zig_breakpoint();
|
||||
\\ zig_unreachable();
|
||||
\\}
|
||||
\\ZIG_EXTERN_C void start(void);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with single param function", linux_x64,
|
||||
\\export fn start(a: u8) void{}
|
||||
,
|
||||
\\void start(uint8_t arg0);
|
||||
\\ZIG_EXTERN_C void start(uint8_t a0);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with multiple param function", linux_x64,
|
||||
\\export fn start(a: u8, b: u8, c: u8) void{}
|
||||
,
|
||||
\\void start(uint8_t arg0, uint8_t arg1, uint8_t arg2);
|
||||
\\ZIG_EXTERN_C void start(uint8_t a0, uint8_t a1, uint8_t a2);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with u32 param function", linux_x64,
|
||||
\\export fn start(a: u32) void{}
|
||||
,
|
||||
\\void start(uint32_t arg0);
|
||||
\\ZIG_EXTERN_C void start(uint32_t a0);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with usize param function", linux_x64,
|
||||
\\export fn start(a: usize) void{}
|
||||
,
|
||||
\\void start(uintptr_t arg0);
|
||||
\\ZIG_EXTERN_C void start(uintptr_t a0);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with bool param function", linux_x64,
|
||||
\\export fn start(a: bool) void{}
|
||||
,
|
||||
\\void start(bool arg0);
|
||||
\\ZIG_EXTERN_C void start(bool a0);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with noreturn function", linux_x64,
|
||||
@ -352,7 +229,7 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
\\ unreachable;
|
||||
\\}
|
||||
,
|
||||
\\zig_noreturn void start(void);
|
||||
\\ZIG_EXTERN_C zig_noreturn void start(void);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with multiple functions", linux_x64,
|
||||
@ -360,15 +237,15 @@ pub fn addCases(ctx: *TestContext) !void {
|
||||
\\export fn b() void{}
|
||||
\\export fn c() void{}
|
||||
,
|
||||
\\void a(void);
|
||||
\\void b(void);
|
||||
\\void c(void);
|
||||
\\ZIG_EXTERN_C void a(void);
|
||||
\\ZIG_EXTERN_C void b(void);
|
||||
\\ZIG_EXTERN_C void c(void);
|
||||
\\
|
||||
);
|
||||
ctx.h("header with multiple includes", linux_x64,
|
||||
\\export fn start(a: u32, b: usize) void{}
|
||||
,
|
||||
\\void start(uint32_t arg0, uintptr_t arg1);
|
||||
\\ZIG_EXTERN_C void start(uint32_t a0, uintptr_t a1);
|
||||
\\
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user