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:
Andrew Kelley 2021-01-05 17:42:16 -07:00
commit 38572ee894
11 changed files with 1030 additions and 889 deletions

View File

@ -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"

View File

@ -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);
}

View File

@ -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;

View File

@ -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);

View File

@ -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

View File

@ -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),
}
}

View File

@ -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 {}

View File

@ -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);

View File

@ -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);
}

View File

@ -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);
\\
);
}