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 {...@@ -1440,16 +1440,6 @@ pub const Target = struct {
1440 return !self.cpu.arch.isWasm();1440 return !self.cpu.arch.isWasm();
1441 }1441 }
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
1453 pub const FloatAbi = enum {1443 pub const FloatAbi = enum {
1454 hard,1444 hard,
1455 soft,1445 soft,
src/Compilation.zig+19-17
...@@ -4766,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void {...@@ -4766,6 +4766,24 @@ pub fn dump_argv(argv: []const []const u8) void {
4766 std.debug.print("{s}\n", .{argv[argv.len - 1]});4766 std.debug.print("{s}\n", .{argv[argv.len - 1]});
4767}4767}
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
4769pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 {4787pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Allocator.Error![:0]u8 {
4770 const tracy_trace = trace(@src());4788 const tracy_trace = trace(@src());
4771 defer tracy_trace.end();4789 defer tracy_trace.end();
...@@ -4775,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca...@@ -4775,23 +4793,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: Allocator) Alloca
47754793
4776 const target = comp.getTarget();4794 const target = comp.getTarget();
4777 const generic_arch_name = target.cpu.arch.genericName();4795 const generic_arch_name = target.cpu.arch.genericName();
4778 const use_stage1 = build_options.have_stage1 and comp.bin_file.options.use_stage1;4796 const zig_backend = comp.getZigBackend();
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 };
47954797
4796 @setEvalBranchQuota(4000);4798 @setEvalBranchQuota(4000);
4797 try buffer.writer().print(4799 try buffer.writer().print(
src/Sema.zig+5-2
...@@ -6161,8 +6161,11 @@ fn analyzeCall(...@@ -6161,8 +6161,11 @@ fn analyzeCall(
61616161
6162fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {6162fn handleTailCall(sema: *Sema, block: *Block, call_src: LazySrcLoc, func_ty: Type, result: Air.Inst.Ref) !Air.Inst.Ref {
6163 const target = sema.mod.getTarget();6163 const target = sema.mod.getTarget();
6164 if (!target.supportsTailCall()) {6164 const backend = sema.mod.comp.getZigBackend();
6165 return sema.fail(block, call_src, "unable to perform tail call: target does not support tail calls", .{});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 });
6166 }6169 }
6167 const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl);6170 const func_decl = sema.mod.declPtr(sema.owner_func.?.owner_decl);
6168 if (!func_ty.eql(func_decl.ty, sema.mod)) {6171 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 {...@@ -177,6 +177,116 @@ pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 {
177 return llvm_triple.toOwnedSliceSentinel(0);177 return llvm_triple.toOwnedSliceSentinel(0);
178}178}
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
180pub const Object = struct {290pub const Object = struct {
181 gpa: Allocator,291 gpa: Allocator,
182 module: *Module,292 module: *Module,
src/link.zig+4-3
...@@ -931,9 +931,10 @@ pub const File = struct {...@@ -931,9 +931,10 @@ pub const File = struct {
931 std.debug.print("\n", .{});931 std.debug.print("\n", .{});
932 }932 }
933933
934 const llvm = @import("codegen/llvm/bindings.zig");934 const llvm_bindings = @import("codegen/llvm/bindings.zig");
935 const os_type = @import("target.zig").osToLLVM(base.options.target.os.tag);935 const llvm = @import("codegen/llvm.zig");
936 const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type);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);
937 if (bad) return error.UnableToWriteArchive;938 if (bad) return error.UnableToWriteArchive;
938939
939 if (!base.options.disable_lld_caching) {940 if (!base.options.disable_lld_caching) {
src/mingw.zig+4-4
...@@ -6,7 +6,6 @@ const assert = std.debug.assert;...@@ -6,7 +6,6 @@ const assert = std.debug.assert;
6const log = std.log.scoped(.mingw);6const log = std.log.scoped(.mingw);
77
8const builtin = @import("builtin");8const builtin = @import("builtin");
9const target_util = @import("target.zig");
10const Compilation = @import("Compilation.zig");9const Compilation = @import("Compilation.zig");
11const build_options = @import("build_options");10const build_options = @import("build_options");
12const Cache = @import("Cache.zig");11const Cache = @import("Cache.zig");
...@@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {...@@ -404,11 +403,12 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
404 });403 });
405 errdefer comp.gpa.free(lib_final_path);404 errdefer comp.gpa.free(lib_final_path);
406405
407 const llvm = @import("codegen/llvm/bindings.zig");406 const llvm_bindings = @import("codegen/llvm/bindings.zig");
408 const arch_type = target_util.archToLLVM(target.cpu.arch);407 const llvm = @import("codegen/llvm.zig");
408 const arch_tag = llvm.targetArch(target.cpu.arch);
409 const def_final_path_z = try arena.dupeZ(u8, def_final_path);409 const def_final_path_z = try arena.dupeZ(u8, def_final_path);
410 const lib_final_path_z = try arena.dupeZ(u8, lib_final_path);410 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)) {
412 // TODO surface a proper error here412 // TODO surface a proper error here
413 log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name });413 log.err("unable to turn {s}.def into {s}.lib", .{ lib_name, lib_name });
414 return error.WritingImportLibFailed;414 return error.WritingImportLibFailed;
src/target.zig+7-101
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1const std = @import("std");1const std = @import("std");
2const llvm = @import("codegen/llvm/bindings.zig");
3const Type = @import("type.zig").Type;2const Type = @import("type.zig").Type;
43
5pub const ArchOsAbi = struct {4pub const ArchOsAbi = struct {
...@@ -317,106 +316,6 @@ pub fn supportsReturnAddress(target: std.Target) bool {...@@ -317,106 +316,6 @@ pub fn supportsReturnAddress(target: std.Target) bool {
317 };316 };
318}317}
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
420fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool {319fn eqlIgnoreCase(ignore_case: bool, a: []const u8, b: []const u8) bool {
421 if (ignore_case) {320 if (ignore_case) {
422 return std.ascii.eqlIgnoreCase(a, b);321 return std.ascii.eqlIgnoreCase(a, b);
...@@ -770,3 +669,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool {...@@ -770,3 +669,10 @@ pub fn supportsFunctionAlignment(target: std.Target) bool {
770 else => true,669 else => true,
771 };670 };
772}671}
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" {...@@ -270,7 +270,12 @@ test "forced tail call" {
270 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO270 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
271 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO271 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
275 const S = struct {280 const S = struct {
276 fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {281 fn fibonacciTailInternal(n: u16, a: u16, b: u16) u16 {
...@@ -298,7 +303,12 @@ test "inline call preserves tail call" {...@@ -298,7 +303,12 @@ test "inline call preserves tail call" {
298 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO303 if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
299 if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest; // TODO304 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
303 const max = std.math.maxInt(u16);313 const max = std.math.maxInt(u16);
304 const S = struct {314 const S = struct {