Skip to content

Commit 4f04759

Browse files
committed
build: add -Donly-c option
This option can be used to produce a C backend build of the self-hosted compiler, which only has the C backend enabled. Once the C backend is capable of self-hosting, this will be a way for us to replace our stage1 codebase with a C backend build of self-hosted, which we can then use for bootstrapping. See #5246 for more details. Using this option right now results in a crash because the C backend is not yet passing all the behavior tests.
1 parent 828735a commit 4f04759

File tree

3 files changed

+91
-14
lines changed

3 files changed

+91
-14
lines changed

build.zig

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ pub fn build(b: *Builder) !void {
4848

4949
const fmt_build_zig = b.addFmt(&[_][]const u8{"build.zig"});
5050

51+
const only_c = b.option(bool, "only-c", "Translate the Zig compiler to C code, with only the C backend enabled") orelse false;
52+
5153
const skip_debug = b.option(bool, "skip-debug", "Main test suite skips debug builds") orelse false;
5254
const skip_release = b.option(bool, "skip-release", "Main test suite skips release builds") orelse false;
5355
const skip_release_small = b.option(bool, "skip-release-small", "Main test suite skips release-small builds") orelse skip_release;
@@ -123,7 +125,7 @@ pub fn build(b: *Builder) !void {
123125
const tracy_callstack = b.option(bool, "tracy-callstack", "Include callstack information with Tracy data. Does nothing if -Dtracy is not provided") orelse false;
124126
const tracy_allocation = b.option(bool, "tracy-allocation", "Include allocation information with Tracy data. Does nothing if -Dtracy is not provided") orelse false;
125127
const force_gpa = b.option(bool, "force-gpa", "Force the compiler to use GeneralPurposeAllocator") orelse false;
126-
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse enable_llvm;
128+
const link_libc = b.option(bool, "force-link-libc", "Force self-hosted compiler to link libc") orelse (enable_llvm or only_c);
127129
const sanitize_thread = b.option(bool, "sanitize-thread", "Enable thread-sanitization") orelse false;
128130
const strip = b.option(bool, "strip", "Omit debug information") orelse false;
129131
const use_zig0 = b.option(bool, "zig0", "Bootstrap using zig0") orelse false;
@@ -166,6 +168,10 @@ pub fn build(b: *Builder) !void {
166168
test_cases.want_lto = false;
167169
}
168170

171+
if (only_c) {
172+
exe.ofmt = .c;
173+
}
174+
169175
const exe_options = b.addOptions();
170176
exe.addOptions("build_options", exe_options);
171177

@@ -176,6 +182,7 @@ pub fn build(b: *Builder) !void {
176182
exe_options.addOption(bool, "llvm_has_csky", llvm_has_csky);
177183
exe_options.addOption(bool, "llvm_has_arc", llvm_has_arc);
178184
exe_options.addOption(bool, "force_gpa", force_gpa);
185+
exe_options.addOption(bool, "only_c", only_c);
179186

180187
if (link_libc) {
181188
exe.linkLibC();
@@ -408,6 +415,7 @@ pub fn build(b: *Builder) !void {
408415
test_cases_options.addOption(bool, "llvm_has_csky", llvm_has_csky);
409416
test_cases_options.addOption(bool, "llvm_has_arc", llvm_has_arc);
410417
test_cases_options.addOption(bool, "force_gpa", force_gpa);
418+
test_cases_options.addOption(bool, "only_c", only_c);
411419
test_cases_options.addOption(bool, "enable_qemu", b.enable_qemu);
412420
test_cases_options.addOption(bool, "enable_wine", b.enable_wine);
413421
test_cases_options.addOption(bool, "enable_wasmtime", b.enable_wasmtime);

src/config.zig.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ pub const enable_tracy = false;
1010
pub const value_tracing = false;
1111
pub const have_stage1 = true;
1212
pub const skip_non_native = false;
13+
pub const only_c = false;

src/link.zig

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,8 @@ pub const File = struct {
284284
/// rewriting it. A malicious file is detected as incremental link failure
285285
/// and does not cause Illegal Behavior. This operation is not atomic.
286286
pub fn openPath(allocator: Allocator, options: Options) !*File {
287-
if (options.target.ofmt == .macho) {
287+
const have_macho = !build_options.only_c;
288+
if (have_macho and options.target.ofmt == .macho) {
288289
return &(try MachO.openPath(allocator, options)).base;
289290
}
290291

@@ -332,18 +333,40 @@ pub const File = struct {
332333
} else emit.sub_path;
333334
errdefer if (use_lld) allocator.free(sub_path);
334335

335-
const file: *File = switch (options.target.ofmt) {
336-
.coff => &(try Coff.openPath(allocator, sub_path, options)).base,
337-
.elf => &(try Elf.openPath(allocator, sub_path, options)).base,
338-
.macho => unreachable,
339-
.plan9 => &(try Plan9.openPath(allocator, sub_path, options)).base,
340-
.wasm => &(try Wasm.openPath(allocator, sub_path, options)).base,
341-
.c => &(try C.openPath(allocator, sub_path, options)).base,
342-
.spirv => &(try SpirV.openPath(allocator, sub_path, options)).base,
343-
.nvptx => &(try NvPtx.openPath(allocator, sub_path, options)).base,
344-
.hex => return error.HexObjectFormatUnimplemented,
345-
.raw => return error.RawObjectFormatUnimplemented,
346-
.dxcontainer => return error.DirectXContainerObjectFormatUnimplemented,
336+
const file: *File = f: {
337+
switch (options.target.ofmt) {
338+
.coff => {
339+
if (build_options.only_c) unreachable;
340+
break :f &(try Coff.openPath(allocator, sub_path, options)).base;
341+
},
342+
.elf => {
343+
if (build_options.only_c) unreachable;
344+
break :f &(try Elf.openPath(allocator, sub_path, options)).base;
345+
},
346+
.macho => unreachable,
347+
.plan9 => {
348+
if (build_options.only_c) unreachable;
349+
break :f &(try Plan9.openPath(allocator, sub_path, options)).base;
350+
},
351+
.wasm => {
352+
if (build_options.only_c) unreachable;
353+
break :f &(try Wasm.openPath(allocator, sub_path, options)).base;
354+
},
355+
.c => {
356+
break :f &(try C.openPath(allocator, sub_path, options)).base;
357+
},
358+
.spirv => {
359+
if (build_options.only_c) unreachable;
360+
break :f &(try SpirV.openPath(allocator, sub_path, options)).base;
361+
},
362+
.nvptx => {
363+
if (build_options.only_c) unreachable;
364+
break :f &(try NvPtx.openPath(allocator, sub_path, options)).base;
365+
},
366+
.hex => return error.HexObjectFormatUnimplemented,
367+
.raw => return error.RawObjectFormatUnimplemented,
368+
.dxcontainer => return error.DirectXContainerObjectFormatUnimplemented,
369+
}
347370
};
348371

349372
if (use_lld) {
@@ -366,6 +389,7 @@ pub const File = struct {
366389
pub fn makeWritable(base: *File) !void {
367390
switch (base.tag) {
368391
.coff, .elf, .macho, .plan9, .wasm => {
392+
if (build_options.only_c) unreachable;
369393
if (base.file != null) return;
370394
const emit = base.options.emit orelse return;
371395
base.file = try emit.directory.handle.createFile(emit.sub_path, .{
@@ -389,6 +413,7 @@ pub const File = struct {
389413
}
390414
switch (base.tag) {
391415
.macho => if (base.file) |f| {
416+
if (build_options.only_c) unreachable;
392417
if (comptime builtin.target.isDarwin() and builtin.target.cpu.arch == .aarch64) {
393418
if (base.options.target.cpu.arch == .aarch64) {
394419
// XNU starting with Big Sur running on arm64 is caching inodes of running binaries.
@@ -407,6 +432,7 @@ pub const File = struct {
407432
base.file = null;
408433
},
409434
.coff, .elf, .plan9, .wasm => if (base.file) |f| {
435+
if (build_options.only_c) unreachable;
410436
if (base.intermediary_basename != null) {
411437
// The file we have open is not the final file that we want to
412438
// make executable, so we don't have to close it.
@@ -454,6 +480,7 @@ pub const File = struct {
454480
/// constant. Returns the symbol index of the lowered constant in the read-only section
455481
/// of the final binary.
456482
pub fn lowerUnnamedConst(base: *File, tv: TypedValue, decl_index: Module.Decl.Index) UpdateDeclError!u32 {
483+
if (build_options.only_c) @compileError("unreachable");
457484
const decl = base.options.module.?.declPtr(decl_index);
458485
log.debug("lowerUnnamedConst {*} ({s})", .{ decl, decl.name });
459486
switch (base.tag) {
@@ -474,6 +501,7 @@ pub const File = struct {
474501
/// If no symbol exists yet with this name, a new undefined global symbol will
475502
/// be created. This symbol may get resolved once all relocatables are (re-)linked.
476503
pub fn getGlobalSymbol(base: *File, name: []const u8) UpdateDeclError!u32 {
504+
if (build_options.only_c) @compileError("unreachable");
477505
log.debug("getGlobalSymbol '{s}'", .{name});
478506
switch (base.tag) {
479507
// zig fmt: off
@@ -495,6 +523,10 @@ pub const File = struct {
495523
const decl = module.declPtr(decl_index);
496524
log.debug("updateDecl {*} ({s}), type={}", .{ decl, decl.name, decl.ty.fmtDebug() });
497525
assert(decl.has_tv);
526+
if (build_options.only_c) {
527+
assert(base.tag == .c);
528+
return @fieldParentPtr(C, "base", base).updateDecl(module, decl_index);
529+
}
498530
switch (base.tag) {
499531
// zig fmt: off
500532
.coff => return @fieldParentPtr(Coff, "base", base).updateDecl(module, decl_index),
@@ -516,6 +548,10 @@ pub const File = struct {
516548
log.debug("updateFunc {*} ({s}), type={}", .{
517549
owner_decl, owner_decl.name, owner_decl.ty.fmtDebug(),
518550
});
551+
if (build_options.only_c) {
552+
assert(base.tag == .c);
553+
return @fieldParentPtr(C, "base", base).updateFunc(module, func, air, liveness);
554+
}
519555
switch (base.tag) {
520556
// zig fmt: off
521557
.coff => return @fieldParentPtr(Coff, "base", base).updateFunc(module, func, air, liveness),
@@ -535,6 +571,10 @@ pub const File = struct {
535571
decl, decl.name, decl.src_line + 1,
536572
});
537573
assert(decl.has_tv);
574+
if (build_options.only_c) {
575+
assert(base.tag == .c);
576+
return @fieldParentPtr(C, "base", base).updateDeclLineNumber(module, decl);
577+
}
538578
switch (base.tag) {
539579
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclLineNumber(module, decl),
540580
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclLineNumber(module, decl),
@@ -554,6 +594,10 @@ pub const File = struct {
554594
pub fn allocateDeclIndexes(base: *File, decl_index: Module.Decl.Index) error{OutOfMemory}!void {
555595
const decl = base.options.module.?.declPtr(decl_index);
556596
log.debug("allocateDeclIndexes {*} ({s})", .{ decl, decl.name });
597+
if (build_options.only_c) {
598+
assert(base.tag == .c);
599+
return;
600+
}
557601
switch (base.tag) {
558602
.coff => return @fieldParentPtr(Coff, "base", base).allocateDeclIndexes(decl_index),
559603
.elf => return @fieldParentPtr(Elf, "base", base).allocateDeclIndexes(decl_index),
@@ -584,16 +628,19 @@ pub const File = struct {
584628
base.options.system_libs.deinit(base.allocator);
585629
switch (base.tag) {
586630
.coff => {
631+
if (build_options.only_c) unreachable;
587632
const parent = @fieldParentPtr(Coff, "base", base);
588633
parent.deinit();
589634
base.allocator.destroy(parent);
590635
},
591636
.elf => {
637+
if (build_options.only_c) unreachable;
592638
const parent = @fieldParentPtr(Elf, "base", base);
593639
parent.deinit();
594640
base.allocator.destroy(parent);
595641
},
596642
.macho => {
643+
if (build_options.only_c) unreachable;
597644
const parent = @fieldParentPtr(MachO, "base", base);
598645
parent.deinit();
599646
base.allocator.destroy(parent);
@@ -604,21 +651,25 @@ pub const File = struct {
604651
base.allocator.destroy(parent);
605652
},
606653
.wasm => {
654+
if (build_options.only_c) unreachable;
607655
const parent = @fieldParentPtr(Wasm, "base", base);
608656
parent.deinit();
609657
base.allocator.destroy(parent);
610658
},
611659
.spirv => {
660+
if (build_options.only_c) unreachable;
612661
const parent = @fieldParentPtr(SpirV, "base", base);
613662
parent.deinit();
614663
base.allocator.destroy(parent);
615664
},
616665
.plan9 => {
666+
if (build_options.only_c) unreachable;
617667
const parent = @fieldParentPtr(Plan9, "base", base);
618668
parent.deinit();
619669
base.allocator.destroy(parent);
620670
},
621671
.nvptx => {
672+
if (build_options.only_c) unreachable;
622673
const parent = @fieldParentPtr(NvPtx, "base", base);
623674
parent.deinit();
624675
base.allocator.destroy(parent);
@@ -629,6 +680,10 @@ pub const File = struct {
629680
/// Commit pending changes and write headers. Takes into account final output mode
630681
/// and `use_lld`, not only `effectiveOutputMode`.
631682
pub fn flush(base: *File, comp: *Compilation, prog_node: *std.Progress.Node) !void {
683+
if (build_options.only_c) {
684+
assert(base.tag == .c);
685+
return @fieldParentPtr(C, "base", base).flush(comp, prog_node);
686+
}
632687
if (comp.clang_preprocessor_mode == .yes) {
633688
const emit = base.options.emit orelse return; // -fno-emit-bin
634689
// TODO: avoid extra link step when it's just 1 object file (the `zig cc -c` case)
@@ -663,6 +718,10 @@ pub const File = struct {
663718
/// Commit pending changes and write headers. Works based on `effectiveOutputMode`
664719
/// rather than final output mode.
665720
pub fn flushModule(base: *File, comp: *Compilation, prog_node: *std.Progress.Node) !void {
721+
if (build_options.only_c) {
722+
assert(base.tag == .c);
723+
return @fieldParentPtr(C, "base", base).flushModule(comp, prog_node);
724+
}
666725
switch (base.tag) {
667726
.coff => return @fieldParentPtr(Coff, "base", base).flushModule(comp, prog_node),
668727
.elf => return @fieldParentPtr(Elf, "base", base).flushModule(comp, prog_node),
@@ -677,6 +736,10 @@ pub const File = struct {
677736

678737
/// Called when a Decl is deleted from the Module.
679738
pub fn freeDecl(base: *File, decl_index: Module.Decl.Index) void {
739+
if (build_options.only_c) {
740+
assert(base.tag == .c);
741+
return @fieldParentPtr(C, "base", base).freeDecl(decl_index);
742+
}
680743
switch (base.tag) {
681744
.coff => @fieldParentPtr(Coff, "base", base).freeDecl(decl_index),
682745
.elf => @fieldParentPtr(Elf, "base", base).freeDecl(decl_index),
@@ -716,6 +779,10 @@ pub const File = struct {
716779
const decl = module.declPtr(decl_index);
717780
log.debug("updateDeclExports {*} ({s})", .{ decl, decl.name });
718781
assert(decl.has_tv);
782+
if (build_options.only_c) {
783+
assert(base.tag == .c);
784+
return @fieldParentPtr(C, "base", base).updateDeclExports(module, decl_index, exports);
785+
}
719786
switch (base.tag) {
720787
.coff => return @fieldParentPtr(Coff, "base", base).updateDeclExports(module, decl_index, exports),
721788
.elf => return @fieldParentPtr(Elf, "base", base).updateDeclExports(module, decl_index, exports),
@@ -739,6 +806,7 @@ pub const File = struct {
739806
/// memory buffer, `offset`, so that it can make a note of potential relocation sites, should the
740807
/// `Decl`'s address was not yet resolved, or the containing atom gets moved in virtual memory.
741808
pub fn getDeclVAddr(base: *File, decl_index: Module.Decl.Index, reloc_info: RelocInfo) !u64 {
809+
if (build_options.only_c) unreachable;
742810
switch (base.tag) {
743811
.coff => return @fieldParentPtr(Coff, "base", base).getDeclVAddr(decl_index, reloc_info),
744812
.elf => return @fieldParentPtr(Elf, "base", base).getDeclVAddr(decl_index, reloc_info),

0 commit comments

Comments
 (0)