authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-30 12:43:10-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-08-30 12:50:15-07:00
log7377dce368090e3c49a15d8996cc812adadd3d43
tree6d9714e54fa2223aa6842d8d496d5f3dadd360e8
parent65d37239682b6418b6dd25f07187ae99088e67f0

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.

8 files changed, 161 insertions(+), 139 deletions(-)

lib/std/target.zig-10
......@@ -1440,16 +1440,6 @@ pub const Target = struct {
14401440 return !self.cpu.arch.isWasm();
14411441 }
14421442
1443 pub fn supportsTailCall(self: Target) bool {
1444 switch (self.cpu.arch) {
1445 .wasm32, .wasm64 => return wasm.featureSetHas(self.cpu.features, .tail_call),
1446 // TODO these might not be true but LLVM doesn't seem to be able to handle them
1447 .mips, .mipsel, .mips64, .mips64el => return false,
1448 .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false,
1449 else => return true,
1450 }
1451 }
1452
14531443 pub const FloatAbi = enum {
14541444 hard,
14551445 soft,
src/Compilation.zig+19-17
......@@ -4766,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void {
47664766 std.debug.print("{s}\n", .{argv[argv.len - 1]});
47674767}
47684768
4769pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend {
4770 const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1;
4771 if (use_stage1) return .stage1;
4772 if (build_options.have_llvm and comp.bin_file.options.use_llvm) return .stage2_llvm;
4773 const target = comp.bin_file.options.target;
4774 if (target.ofmt == .c) return .stage2_c;
4775 return switch (target.cpu.arch) {
4776 .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm,
4777 .arm, .armeb, .thumb, .thumbeb => .stage2_arm,
4778 .x86_64 => .stage2_x86_64,
4779 .i386 => .stage2_x86,
4780 .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64,
4781 .riscv64 => .stage2_riscv64,
4782 .sparc64 => .stage2_sparc64,
4783 else => .other,
4784 };
4785}
4786
47694787pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 {
47704788 const tracy_trace = trace(@src());
47714789 defer tracy_trace.end();
......@@ -4775,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
47754793
47764794 const target = comp.getTarget();
47774795 const generic_arch_name = target.cpu.arch.genericName();
4778 const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1;
4779
4780 const zig_backend: std.builtin.CompilerBackend = blk: {
4781 if (use_stage1) break :blk .stage1;
4782 if (build_options.have_llvm and comp.bin_file.options.use_llvm) break :blk .stage2_llvm;
4783 if (target.ofmt == .c) break :blk .stage2_c;
4784 break :blk switch (target.cpu.arch) {
4785 .wasm32, .wasm64 => std.builtin.CompilerBackend.stage2_wasm,
4786 .arm, .armeb, .thumb, .thumbeb => .stage2_arm,
4787 .x86_64 => .stage2_x86_64,
4788 .i386 => .stage2_x86,
4789 .aarch64, .aarch64_be, .aarch64_32 => .stage2_aarch64,
4790 .riscv64 => .stage2_riscv64,
4791 .sparc64 => .stage2_sparc64,
4792 else => .other,
4793 };
4794 };
4796 const zig_backend = comp.getZigBackend();
47954797
47964798 @setEvalBranchQuota(4000);
47974799 try buffer.writer().print(
src/Sema.zig+5-2
......@@ -6161,8 +6161,11 @@ fn analyzeCall(
61616161
61626162fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {
61636163 const target = sema.mod.getTarget();
6164 if (!target.supportsTailCall()) {
6165 return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{});
6164 const backend = sema.mod.comp.getZigBackend();
6165 if (!target_util.supportsTailCall(target, backend)) {
6166 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", .{
6167 @tagName(backend), @tagName(target.cpu.arch),
6168 });
61666169 }
61676170 const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl);
61686171 if (!func_ty.eql(func_decl.ty, sema.mod)) {
src/codegen/llvm.zig+110
......@@ -177,6 +177,116 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 {
177177 return llvm_triple.toOwnedSliceSentinel(0);
178178}
179179
180pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType {
181 return switch (os_tag) {
182 .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS,
183 .windows, .uefi => .Win32,
184 .ananas => .Ananas,
185 .cloudabi => .CloudABI,
186 .dragonfly => .DragonFly,
187 .freebsd => .FreeBSD,
188 .fuchsia => .Fuchsia,
189 .ios => .IOS,
190 .kfreebsd => .KFreeBSD,
191 .linux => .Linux,
192 .lv2 => .Lv2,
193 .macos => .MacOSX,
194 .netbsd => .NetBSD,
195 .openbsd => .OpenBSD,
196 .solaris => .Solaris,
197 .zos => .ZOS,
198 .haiku => .Haiku,
199 .minix => .Minix,
200 .rtems => .RTEMS,
201 .nacl => .NaCl,
202 .aix => .AIX,
203 .cuda => .CUDA,
204 .nvcl => .NVCL,
205 .amdhsa => .AMDHSA,
206 .ps4 => .PS4,
207 .elfiamcu => .ELFIAMCU,
208 .tvos => .TvOS,
209 .watchos => .WatchOS,
210 .mesa3d => .Mesa3D,
211 .contiki => .Contiki,
212 .amdpal => .AMDPAL,
213 .hermit => .HermitCore,
214 .hurd => .Hurd,
215 .wasi => .WASI,
216 .emscripten => .Emscripten,
217 };
218}
219
220pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType {
221 return switch (arch_tag) {
222 .arm => .arm,
223 .armeb => .armeb,
224 .aarch64 => .aarch64,
225 .aarch64_be => .aarch64_be,
226 .aarch64_32 => .aarch64_32,
227 .arc => .arc,
228 .avr => .avr,
229 .bpfel => .bpfel,
230 .bpfeb => .bpfeb,
231 .csky => .csky,
232 .hexagon => .hexagon,
233 .m68k => .m68k,
234 .mips => .mips,
235 .mipsel => .mipsel,
236 .mips64 => .mips64,
237 .mips64el => .mips64el,
238 .msp430 => .msp430,
239 .powerpc => .ppc,
240 .powerpcle => .ppcle,
241 .powerpc64 => .ppc64,
242 .powerpc64le => .ppc64le,
243 .r600 => .r600,
244 .amdgcn => .amdgcn,
245 .riscv32 => .riscv32,
246 .riscv64 => .riscv64,
247 .sparc => .sparc,
248 .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9.
249 .sparcel => .sparcel,
250 .s390x => .systemz,
251 .tce => .tce,
252 .tcele => .tcele,
253 .thumb => .thumb,
254 .thumbeb => .thumbeb,
255 .i386 => .x86,
256 .x86_64 => .x86_64,
257 .xcore => .xcore,
258 .nvptx => .nvptx,
259 .nvptx64 => .nvptx64,
260 .le32 => .le32,
261 .le64 => .le64,
262 .amdil => .amdil,
263 .amdil64 => .amdil64,
264 .hsail => .hsail,
265 .hsail64 => .hsail64,
266 .spir => .spir,
267 .spir64 => .spir64,
268 .kalimba => .kalimba,
269 .shave => .shave,
270 .lanai => .lanai,
271 .wasm32 => .wasm32,
272 .wasm64 => .wasm64,
273 .renderscript32 => .renderscript32,
274 .renderscript64 => .renderscript64,
275 .ve => .ve,
276 .spu_2, .spirv32, .spirv64 => .UnknownArch,
277 };
278}
279
280pub fn supportsTailCall(target: std.Target) bool {
281 switch (target.cpu.arch) {
282 .wasm32, .wasm64 => return std.Target.wasm.featureSetHas(target.cpu.features, .tail_call),
283 // Although these ISAs support tail calls, LLVM does not support tail calls on them.
284 .mips, .mipsel, .mips64, .mips64el => return false,
285 .powerpc, .powerpcle, .powerpc64, .powerpc64le => return false,
286 else => return true,
287 }
288}
289
180290pub const Object = struct {
181291 gpa: Allocator,
182292 module: *Module,
src/link.zig+4-3
......@@ -931,9 +931,10 @@ pub const File = struct {
931931 std.debug.print("\n", .{});
932932 }
933933
934 const llvm = @import("codegen/llvm/bindings.zig");
935 const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag);
936 const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type);
934 const llvm_bindings = @import("codegen/llvm/bindings.zig");
935 const llvm = @import("codegen/llvm.zig");
936 const os_tag = llvm.targetOs(base.options.target.os.tag);
937 const bad = llvm_bindings.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_tag);
937938 if (bad) return error.UnableToWriteArchive;
938939
939940 if (!base.options.disable_lld_caching) {
src/mingw.zig+4-4
......@@ -6,7 +6,6 @@ const assert = std.debug.assert;
66const log = std.log.scoped(.mingw);
77
88const builtin = @import("builtin");
9const target_util = @import("target.zig");
109const Compilation = @import("Compilation.zig");
1110const build_options = @import("build_options");
1211const Cache = @import("Cache.zig");
......@@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
404403 });
405404 errdefer comp.gpa.free(lib_final_path);
406405
407 const llvm = @import("codegen/llvm/bindings.zig");
408 const arch_type = target_util.archToLLVM(target.cpu.arch);
406 const llvm_bindings = @import("codegen/llvm/bindings.zig");
407 const llvm = @import("codegen/llvm.zig");
408 const arch_tag = llvm.targetArch(target.cpu.arch);
409409 const def_final_path_z = try arena.dupeZ(u8, def_final_path);
410410 const lib_final_path_z = try arena.dupeZ(u8, lib_final_path);
411 if (llvm.WriteImportLibrary(def_final_path_z.ptr, arch_type, lib_final_path_z.ptr, true)) {
411 if (llvm_bindings.WriteImportLibrary(def_final_path_z.ptr, arch_tag, lib_final_path_z.ptr, true)) {
412412 // TODO surface a proper error here
413413 log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name });
414414 return error.WritingImportLibFailed;
src/target.zig+7-101
......@@ -1,5 +1,4 @@
11const std = @import("std");
2const llvm = @import("codegen/llvm/bindings.zig");
32const Type = @import("type.zig").Type;
43
54pub const ArchOsAbi = struct {
......@@ -317,106 +316,6 @@ pub fn supportsReturnAddress(target: std.Target) bool {
317316 };
318317}
319318
320pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType {
321 return switch (os_tag) {
322 .freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS,
323 .windows, .uefi => .Win32,
324 .ananas => .Ananas,
325 .cloudabi => .CloudABI,
326 .dragonfly => .DragonFly,
327 .freebsd => .FreeBSD,
328 .fuchsia => .Fuchsia,
329 .ios => .IOS,
330 .kfreebsd => .KFreeBSD,
331 .linux => .Linux,
332 .lv2 => .Lv2,
333 .macos => .MacOSX,
334 .netbsd => .NetBSD,
335 .openbsd => .OpenBSD,
336 .solaris => .Solaris,
337 .zos => .ZOS,
338 .haiku => .Haiku,
339 .minix => .Minix,
340 .rtems => .RTEMS,
341 .nacl => .NaCl,
342 .aix => .AIX,
343 .cuda => .CUDA,
344 .nvcl => .NVCL,
345 .amdhsa => .AMDHSA,
346 .ps4 => .PS4,
347 .elfiamcu => .ELFIAMCU,
348 .tvos => .TvOS,
349 .watchos => .WatchOS,
350 .mesa3d => .Mesa3D,
351 .contiki => .Contiki,
352 .amdpal => .AMDPAL,
353 .hermit => .HermitCore,
354 .hurd => .Hurd,
355 .wasi => .WASI,
356 .emscripten => .Emscripten,
357 };
358}
359
360pub fn archToLLVM(arch_tag: std.Target.Cpu.Arch) llvm.ArchType {
361 return switch (arch_tag) {
362 .arm => .arm,
363 .armeb => .armeb,
364 .aarch64 => .aarch64,
365 .aarch64_be => .aarch64_be,
366 .aarch64_32 => .aarch64_32,
367 .arc => .arc,
368 .avr => .avr,
369 .bpfel => .bpfel,
370 .bpfeb => .bpfeb,
371 .csky => .csky,
372 .hexagon => .hexagon,
373 .m68k => .m68k,
374 .mips => .mips,
375 .mipsel => .mipsel,
376 .mips64 => .mips64,
377 .mips64el => .mips64el,
378 .msp430 => .msp430,
379 .powerpc => .ppc,
380 .powerpcle => .ppcle,
381 .powerpc64 => .ppc64,
382 .powerpc64le => .ppc64le,
383 .r600 => .r600,
384 .amdgcn => .amdgcn,
385 .riscv32 => .riscv32,
386 .riscv64 => .riscv64,
387 .sparc => .sparc,
388 .sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9.
389 .sparcel => .sparcel,
390 .s390x => .systemz,
391 .tce => .tce,
392 .tcele => .tcele,
393 .thumb => .thumb,
394 .thumbeb => .thumbeb,
395 .i386 => .x86,
396 .x86_64 => .x86_64,
397 .xcore => .xcore,
398 .nvptx => .nvptx,
399 .nvptx64 => .nvptx64,
400 .le32 => .le32,
401 .le64 => .le64,
402 .amdil => .amdil,
403 .amdil64 => .amdil64,
404 .hsail => .hsail,
405 .hsail64 => .hsail64,
406 .spir => .spir,
407 .spir64 => .spir64,
408 .kalimba => .kalimba,
409 .shave => .shave,
410 .lanai => .lanai,
411 .wasm32 => .wasm32,
412 .wasm64 => .wasm64,
413 .renderscript32 => .renderscript32,
414 .renderscript64 => .renderscript64,
415 .ve => .ve,
416 .spu_2, .spirv32, .spirv64 => .UnknownArch,
417 };
418}
419
420319fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool {
421320 if (ignore_case) {
422321 return std.ascii.eqlIgnoreCase(a, b);
......@@ -770,3 +669,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool {
770669 else => true,
771670 };
772671}
672
673pub fn supportsTailCall(target: std.Target, backend: std.builtin.CompilerBackend) bool {
674 switch (backend) {
675 .stage1, .stage2_llvm => return @import("codegen/llvm.zig").supportsTailCall(target),
676 else => return false,
677 }
678}
test/behavior/call.zig+12-2
......@@ -270,7 +270,12 @@ test "forced tail call" {
270270 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
271271 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
272272
273 if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest;
273 if (builtin.zig_backend == .stage2_llvm) {
274 // Only attempt this test on targets we know have tail call support in LLVM.
275 if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
276 return error.SkipZigTest;
277 }
278 }
274279
275280 const S = struct {
276281 fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
......@@ -298,7 +303,12 @@ test "inline call preserves tail call" {
298303 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
299304 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO
300305
301 if (comptime !builtin.target.supportsTailCall()) return error.SkipZigTest;
306 if (builtin.zig_backend == .stage2_llvm) {
307 // Only attempt this test on targets we know have tail call support in LLVM.
308 if (builtin.cpu.arch != .x86_64 and builtin.cpu.arch != .aarch64) {
309 return error.SkipZigTest;
310 }
311 }
302312
303313 const max = std.math.maxInt(u16);
304314 const S = struct {