avoid exposing supportsTailCall in the standard library
This is problematic because in practice it depends on whether the
compiler backend supports it too, as evidenced by the TODO comment about
LLVM not supporting some architectures that in fact do support tail
calls.
Instead this logic is organized strategically in src/target.zig, part of
the internal compiler source code, and the behavior tests in question
duplicate some logic for deciding whether to proceed with the test.
The proper place to expose this flag is in `@import("builtin")` - the
generated source file - so that third party compilers can advertise
whether they support tail calls.
This commit is contained in:
@@ -1440,16 +1440,6 @@ pub const Target = struct {
|
||||
return !self.cpu.arch.isWasm();
|
||||
}
|
||||
|
||||
pub fn supportsTailCall(self: Target) bool {
|
||||
switch (self.cpu.arch) {
|
||||
.wasm32, .wasm64 => return wasm.featureSetHas(self.cpu.features, .tail_call),
|
||||
// TODO these might not be true but LLVM doesn't seem to be able to handle them
|
||||
.mips, .mipsel, .mips64, .mips64el => return false,
|
||||
.powerpc, .powerpcle, .powerpc64, .powerpc64le => return false,
|
||||
else => return true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const FloatAbi = enum {
|
||||
hard,
|
||||
soft,
|
||||
|
||||
@@ -4766,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void {
|
||||
std.debug.print("{s}\n", .{argv[argv.len - 1]});
|
||||
}
|
||||
|
||||
pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend {
|
||||
const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1;
|
||||
if (use_stage1) return .stage1;
|
||||
if (build_options.have_llvm and comp.bin_file.options.use_llvm) return .stage2_llvm;
|
||||
const target = comp.bin_file.options.target;
|
||||
if (target.ofmt == .c) return .stage2_c;
|
||||
return switch (target.cpu.arch) {
|
||||
.wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm,
|
||||
.arm, .armeb, .thumb, .thumbeb => .stage2_arm,
|
||||
.x86_64 => .stage2_x86_64,
|
||||
.i386 => .stage2_x86,
|
||||
.aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64,
|
||||
.riscv64 => .stage2_riscv64,
|
||||
.sparc64 => .stage2_sparc64,
|
||||
else => .other,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 {
|
||||
const tracy_trace = trace(@src());
|
||||
defer tracy_trace.end();
|
||||
@@ -4775,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
|
||||
|
||||
const target = comp.getTarget();
|
||||
const generic_arch_name = target.cpu.arch.genericName();
|
||||
const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1;
|
||||
|
||||
const zig_backend: std.builtin.CompilerBackend = blk: {
|
||||
if (use_stage1) break :blk .stage1;
|
||||
if (build_options.have_llvm and comp.bin_file.options.use_llvm) break :blk .stage2_llvm;
|
||||
if (target.ofmt == .c) break :blk .stage2_c;
|
||||
break :blk switch (target.cpu.arch) {
|
||||
.wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm,
|
||||
.arm, .armeb, .thumb, .thumbeb => .stage2_arm,
|
||||
.x86_64 => .stage2_x86_64,
|
||||
.i386 => .stage2_x86,
|
||||
.aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64,
|
||||
.riscv64 => .stage2_riscv64,
|
||||
.sparc64 => .stage2_sparc64,
|
||||
else => .other,
|
||||
};
|
||||
};
|
||||
const zig_backend = comp.getZigBackend();
|
||||
|
||||
@setEvalBranchQuota(4000);
|
||||
try buffer.writer().print(
|
||||
|
||||
@@ -6161,8 +6161,11 @@ fn analyzeCall(
|
||||
|
||||
fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {
|
||||
const target = sema.mod.getTarget();
|
||||
if (!target.supportsTailCall()) {
|
||||
return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{});
|
||||
const backend = sema.mod.comp.getZigBackend();
|
||||
if (!target_util.supportsTailCall(target, backend)) {
|
||||
return sema.fail(block, call_src, "unable to perform tail call: compiler backend '{s}' does not support tail calls on target architecture '{s}' with the selected CPU feature flags", .{
|
||||
@tagName(backend), @tagName(target.cpu.arch),
|
||||
});
|
||||
}
|
||||
const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl);
|
||||
if (!func_ty.eql(func_decl.ty, sema.mod)) {
|
||||
|
||||
@@ -177,6 +177,116 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 {
|
||||
return llvm_triple.toOwnedSliceSentinel(0);
|
||||
}
|
||||
|
||||
pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType {
|
||||
return switch (os_tag) {
|
||||
.freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS,
|
||||
.windows, .uefi => .Win32,
|
||||
.ananas => .Ananas,
|
||||
.cloudabi => .CloudABI,
|
||||
.dragonfly => .DragonFly,
|
||||
.freebsd => .FreeBSD,
|
||||
.fuchsia => .Fuchsia,
|
||||
.ios => .IOS,
|
||||
.kfreebsd => .KFreeBSD,
|
||||
.linux => .Linux,
|
||||
.lv2 => .Lv2,
|
||||
.macos => .MacOSX,
|
||||
.netbsd => .NetBSD,
|
||||
.openbsd => .OpenBSD,
|
||||
.solaris => .Solaris,
|
||||
.zos => .ZOS,
|
||||
.haiku => .Haiku,
|
||||
.minix => .Minix,
|
||||
.rtems => .RTEMS,
|
||||
.nacl => .NaCl,
|
||||
.aix => .AIX,
|
||||
.cuda => .CUDA,
|
||||
.nvcl => .NVCL,
|
||||
.amdhsa => .AMDHSA,
|
||||
.ps4 => .PS4,
|
||||
.elfiamcu => .ELFIAMCU,
|
||||
.tvos => .TvOS,
|
||||
.watchos => .WatchOS,
|
||||
.mesa3d => .Mesa3D,
|
||||
.contiki => .Contiki,
|
||||
.amdpal => .AMDPAL,
|
||||
.hermit => .HermitCore,
|
||||
.hurd => .Hurd,
|
||||
.wasi => .WASI,
|
||||
.emscripten => .Emscripten,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType {
|
||||
return switch (arch_tag) {
|
||||
.arm => .arm,
|
||||
.armeb => .armeb,
|
||||
.aarch64 => .aarch64,
|
||||
.aarch64_be => .aarch64_be,
|
||||
.aarch64_32 => .aarch64_32,
|
||||
.arc => .arc,
|
||||
.avr => .avr,
|
||||
.bpfel => .bpfel,
|
||||
.bpfeb => .bpfeb,
|
||||
.csky => .csky,
|
||||
.hexagon => .hexagon,
|
||||
.m68k => .m68k,
|
||||
.mips => .mips,
|
||||
.mipsel => .mipsel,
|
||||
.mips64 => .mips64,
|
||||
.mips64el => .mips64el,
|
||||
.msp430 => .msp430,
|
||||
.powerpc => .ppc,
|
||||
.powerpcle => .ppcle,
|
||||
.powerpc64 => .ppc64,
|
||||
.powerpc64le => .ppc64le,
|
||||
.r600 => .r600,
|
||||
.amdgcn => .amdgcn,
|
||||
.riscv32 => .riscv32,
|
||||
.riscv64 => .riscv64,
|
||||
.sparc => .sparc,
|
||||
.sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9.
|
||||
.sparcel => .sparcel,
|
||||
.s390x => .systemz,
|
||||
.tce => .tce,
|
||||
.tcele => .tcele,
|
||||
.thumb => .thumb,
|
||||
.thumbeb => .thumbeb,
|
||||
.i386 => .x86,
|
||||
.x86_64 => .x86_64,
|
||||
.xcore => .xcore,
|
||||
.nvptx => .nvptx,
|
||||
.nvptx64 => .nvptx64,
|
||||
.le32 => .le32,
|
||||
.le64 => .le64,
|
||||
.amdil => .amdil,
|
||||
.amdil64 => .amdil64,
|
||||
.hsail => .hsail,
|
||||
.hsail64 => .hsail64,
|
||||
.spir => .spir,
|
||||
.spir64 => .spir64,
|
||||
.kalimba => .kalimba,
|
||||
.shave => .shave,
|
||||
.lanai => .lanai,
|
||||
.wasm32 => .wasm32,
|
||||
.wasm64 => .wasm64,
|
||||
.renderscript32 => .renderscript32,
|
||||
.renderscript64 => .renderscript64,
|
||||
.ve => .ve,
|
||||
.spu_2, .spirv32, .spirv64 => .UnknownArch,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn supportsTailCall(target: std.Target) bool {
|
||||
switch (target.cpu.arch) {
|
||||
.wasm32, .wasm64 => return std.Target.wasm.featureSetHas(target.cpu.features, .tail_call),
|
||||
// Although these ISAs support tail calls, LLVM does not support tail calls on them.
|
||||
.mips, .mipsel, .mips64, .mips64el => return false,
|
||||
.powerpc, .powerpcle, .powerpc64, .powerpc64le => return false,
|
||||
else => return true,
|
||||
}
|
||||
}
|
||||
|
||||
pub const Object = struct {
|
||||
gpa: Allocator,
|
||||
module: *Module,
|
||||
|
||||
@@ -931,9 +931,10 @@ pub const File = struct {
|
||||
std.debug.print("\n", .{});
|
||||
}
|
||||
|
||||
const llvm = @import("codegen/llvm/bindings.zig");
|
||||
const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag);
|
||||
const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type);
|
||||
const llvm_bindings = @import("codegen/llvm/bindings.zig");
|
||||
const llvm = @import("codegen/llvm.zig");
|
||||
const os_tag = llvm.targetOs(base.options.target.os.tag);
|
||||
const bad = llvm_bindings.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_tag);
|
||||
if (bad) return error.UnableToWriteArchive;
|
||||
|
||||
if (!base.options.disable_lld_caching) {
|
||||
|
||||
@@ -6,7 +6,6 @@ const assert = std.debug.assert;
|
||||
const log = std.log.scoped(.mingw);
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const target_util = @import("target.zig");
|
||||
const Compilation = @import("Compilation.zig");
|
||||
const build_options = @import("build_options");
|
||||
const Cache = @import("Cache.zig");
|
||||
@@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
|
||||
});
|
||||
errdefer comp.gpa.free(lib_final_path);
|
||||
|
||||
const llvm = @import("codegen/llvm/bindings.zig");
|
||||
const arch_type = target_util.archToLLVM(target.cpu.arch);
|
||||
const llvm_bindings = @import("codegen/llvm/bindings.zig");
|
||||
const llvm = @import("codegen/llvm.zig");
|
||||
const arch_tag = llvm.targetArch(target.cpu.arch);
|
||||
const def_final_path_z = try arena.dupeZ(u8, def_final_path);
|
||||
const lib_final_path_z = try arena.dupeZ(u8, lib_final_path);
|
||||
if (llvm.WriteImportLibrary(def_final_path_z.ptr, arch_type, lib_final_path_z.ptr, true)) {
|
||||
if (llvm_bindings.WriteImportLibrary(def_final_path_z.ptr, arch_tag, lib_final_path_z.ptr, true)) {
|
||||
// TODO surface a proper error here
|
||||
log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name });
|
||||
return error.WritingImportLibFailed;
|
||||
|
||||
108
src/target.zig
108
src/target.zig
@@ -1,5 +1,4 @@
|
||||
const std = @import("std");
|
||||
const llvm = @import("codegen/llvm/bindings.zig");
|
||||
const Type = @import("type.zig").Type;
|
||||
|
||||
pub const ArchOsAbi = struct {
|
||||
@@ -317,106 +316,6 @@ pub fn supportsReturnAddress(target: std.Target) bool {
|
||||
};
|
||||
}
|
||||
|
||||
pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType {
|
||||
return switch (os_tag) {
|
||||
.freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS,
|
||||
.windows, .uefi => .Win32,
|
||||
.ananas => .Ananas,
|
||||
.cloudabi => .CloudABI,
|
||||
.dragonfly => .DragonFly,
|
||||
.freebsd => .FreeBSD,
|
||||
.fuchsia => .Fuchsia,
|
||||
.ios => .IOS,
|
||||
.kfreebsd => .KFreeBSD,
|
||||
.linux => .Linux,
|
||||
.lv2 => .Lv2,
|
||||
.macos => .MacOSX,
|
||||
.netbsd => .NetBSD,
|
||||
.openbsd => .OpenBSD,
|
||||
.solaris => .Solaris,
|
||||
.zos => .ZOS,
|
||||
.haiku => .Haiku,
|
||||
.minix => .Minix,
|
||||
.rtems => .RTEMS,
|
||||
.nacl => .NaCl,
|
||||
.aix => .AIX,
|
||||
.cuda => .CUDA,
|
||||
.nvcl => .NVCL,
|
||||
.amdhsa => .AMDHSA,
|
||||
.ps4 => .PS4,
|
||||
.elfiamcu => .ELFIAMCU,
|
||||
.tvos => .TvOS,
|
||||
.watchos => .WatchOS,
|
||||
.mesa3d => .Mesa3D,
|
||||
.contiki => .Contiki,
|
||||
.amdpal => .AMDPAL,
|
||||
.hermit => .HermitCore,
|
||||
.hurd => .Hurd,
|
||||
.wasi => .WASI,
|
||||
.emscripten => .Emscripten,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType {
|
||||
return switch (arch_tag) {
|
||||
.arm => .arm,
|
||||
.armeb => .armeb,
|
||||
.aarch64 => .aarch64,
|
||||
.aarch64_be => .aarch64_be,
|
||||
.aarch64_32 => .aarch64_32,
|
||||
.arc => .arc,
|
||||
.avr => .avr,
|
||||
.bpfel => .bpfel,
|
||||
.bpfeb => .bpfeb,
|
||||
.csky => .csky,
|
||||
.hexagon => .hexagon,
|
||||
.m68k => .m68k,
|
||||
.mips => .mips,
|
||||
.mipsel => .mipsel,
|
||||
.mips64 => .mips64,
|
||||
.mips64el => .mips64el,
|
||||
.msp430 => .msp430,
|
||||
.powerpc => .ppc,
|
||||
.powerpcle => .ppcle,
|
||||
.powerpc64 => .ppc64,
|
||||
.powerpc64le => .ppc64le,
|
||||
.r600 => .r600,
|
||||
.amdgcn => .amdgcn,
|
||||
.riscv32 => .riscv32,
|
||||
.riscv64 => .riscv64,
|
||||
.sparc => .sparc,
|
||||
.sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9.
|
||||
.sparcel => .sparcel,
|
||||
.s390x => .systemz,
|
||||
.tce => .tce,
|
||||
.tcele => .tcele,
|
||||
.thumb => .thumb,
|
||||
.thumbeb => .thumbeb,
|
||||
.i386 => .x86,
|
||||
.x86_64 => .x86_64,
|
||||
.xcore => .xcore,
|
||||
.nvptx => .nvptx,
|
||||
.nvptx64 => .nvptx64,
|
||||
.le32 => .le32,
|
||||
.le64 => .le64,
|
||||
.amdil => .amdil,
|
||||
.amdil64 => .amdil64,
|
||||
.hsail => .hsail,
|
||||
.hsail64 => .hsail64,
|
||||
.spir => .spir,
|
||||
.spir64 => .spir64,
|
||||
.kalimba => .kalimba,
|
||||
.shave => .shave,
|
||||
.lanai => .lanai,
|
||||
.wasm32 => .wasm32,
|
||||
.wasm64 => .wasm64,
|
||||
.renderscript32 => .renderscript32,
|
||||
.renderscript64 => .renderscript64,
|
||||
.ve => .ve,
|
||||
.spu_2, .spirv32, .spirv64 => .UnknownArch,
|
||||
};
|
||||
}
|
||||
|
||||
fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool {
|
||||
if (ignore_case) {
|
||||
return std.ascii.eqlIgnoreCase(a, b);
|
||||
@@ -770,3 +669,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool {
|
||||
else => true,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool {
|
||||
switch (backend) {
|
||||
.stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target),
|
||||
else => return false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +270,12 @@ test "forced tail call" {
|
||||
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
||||
|
||||
if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_llvm) {
|
||||
// Only attempt this test on targets we know have tail call support in LLVM.
|
||||
if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
}
|
||||
|
||||
const S = struct {
|
||||
fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
|
||||
@@ -298,7 +303,12 @@ test "inline call preserves tail call" {
|
||||
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
|
||||
if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
|
||||
|
||||
if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest;
|
||||
if (builtin.zig_backend == .stage2_llvm) {
|
||||
// Only attempt this test on targets we know have tail call support in LLVM.
|
||||
if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
|
||||
return error.SkipZigTest;
|
||||
}
|
||||
}
|
||||
|
||||
const max = std.math.maxInt(u16);
|
||||
const S = struct {
|
||||
|
||||
Reference in New Issue
Block a user