From 7377dce368090e3c49a15d8996cc812adadd3d43 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 30 Aug 2022 12:43:10 -0700 Subject: [PATCH] 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. --- lib/std/target.zig | 10 ---- src/Compilation.zig | 36 +++++++------- src/Sema.zig | 7 ++- src/codegen/llvm.zig | 110 +++++++++++++++++++++++++++++++++++++++++ src/link.zig | 7 +-- src/mingw.zig | 8 +-- src/target.zig | 108 +++------------------------------------- test/behavior/call.zig | 14 +++++- 8 files changed, 161 insertions(+), 139 deletions(-) diff --git a/lib/std/target.zig b/lib/std/target.zig index 7b4a468a6125014efe68a96af756a86eb3a18eb6..64f9f9780961ea7ab7e964d29b2b3fd308d46fe3 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -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, diff --git a/src/Compilation.zig b/src/Compilation.zig index 8e4b322230c5ba924e155ef11d95b397d70a0773..c1321e40cfbcb38dc65ec8d5c80e8fa26de35aef 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -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( diff --git a/src/Sema.zig b/src/Sema.zig index 8cde9d7e12745ae5368c8e6e7ffa82ab46f578fc..0626fd30eebccb92ef9473450afc0ba430cf6b7b 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -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)) { diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 93d2eaa9dfd520bd4cb375ea060d0b828eac803e..45426c5ee09970816c96f6a16b9e7b7ae53e1e2c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -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, diff --git a/src/link.zig b/src/link.zig index 421188bd4756de5d8f5e312103074268ca3bebbb..a8845a0d578d562ef2906efa6328cff09b41111c 100644 --- a/src/link.zig +++ b/src/link.zig @@ -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) { diff --git a/src/mingw.zig b/src/mingw.zig index b50cc4b00994b102853e3d1f4c7c128f53e95bff..23035fe72b4f24412a64612bc6bf98987a8f00bf 100644 --- a/src/mingw.zig +++ b/src/mingw.zig @@ -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; diff --git a/src/target.zig b/src/target.zig index 405a7fe2bf6c22a081de56db3197d23e0e1d3619..f8b44bac0e8f12bb65fdda0776c4eaa0706f7257 100644 --- a/src/target.zig +++ b/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, + } +} diff --git a/test/behavior/call.zig b/test/behavior/call.zig index 4a4ff4fb781dccb1e75c1f50ba1edc65a14593dd..37c6ce369184357a95b37f3b0880a2d7e48da980 100644 --- a/test/behavior/call.zig +++ b/test/behavior/call.zig @@ -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 { -- 2.54.0