diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index f21ef8bc8f7b..991283dbc526 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -294,7 +294,7 @@ pub fn evalZigProcess( s: *Step, argv: []const []const u8, prog_node: *std.Progress.Node, -) ![]const u8 { +) !?[]const u8 { assert(argv.len != 0); const b = s.owner; const arena = b.allocator; @@ -423,6 +423,8 @@ pub fn evalZigProcess( }); } + if (s.cast(Compile)) |compile| if (compile.emit_bin == .no_emit) return result; + return result orelse return s.fail( "the following command failed to communicate the compilation result:\n{s}", .{try allocPrintCmd(arena, null, argv)}, diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 7ff82e4b7b10..6dd0bd2d11de 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1997,7 +1997,7 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { try zig_args.append(resolved_args_file); } - const output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { + const maybe_output_bin_path = step.evalZigProcess(zig_args.items, prog_node) catch |err| switch (err) { error.NeedCompileErrorCheck => { assert(self.expect_errors.len != 0); try checkCompileErrors(self); @@ -2005,10 +2005,11 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { }, else => |e| return e, }; - const output_dir = fs.path.dirname(output_bin_path).?; // Update generated files - { + if (maybe_output_bin_path) |output_bin_path| { + const output_dir = fs.path.dirname(output_bin_path).?; + self.output_dirname_source.path = output_dir; self.output_path_source.path = b.pathJoin( diff --git a/lib/std/Build/Step/TranslateC.zig b/lib/std/Build/Step/TranslateC.zig index 60e35e940b11..cfbabc2fa958 100644 --- a/lib/std/Build/Step/TranslateC.zig +++ b/lib/std/Build/Step/TranslateC.zig @@ -148,8 +148,8 @@ fn make(step: *Step, prog_node: *std.Progress.Node) !void { const output_path = try step.evalZigProcess(argv_list.items, prog_node); - self.out_basename = fs.path.basename(output_path); - const output_dir = fs.path.dirname(output_path).?; + self.out_basename = fs.path.basename(output_path.?); + const output_dir = fs.path.dirname(output_path.?).?; self.output_file.path = try fs.path.join( b.allocator, diff --git a/lib/std/target.zig b/lib/std/target.zig index e00a1c15d780..015aee61ba7b 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -1912,7 +1912,7 @@ pub const Target = struct { return switch (target.cpu.arch) { .amdgcn => 4, .x86 => switch (target.os.tag) { - .windows => 4, + .windows, .uefi => 4, else => 16, }, .arm, @@ -1931,8 +1931,6 @@ pub const Target = struct { .bpfel, .mips64, .mips64el, - .powerpc64, - .powerpc64le, .riscv32, .riscv64, .sparc64, @@ -1941,6 +1939,12 @@ pub const Target = struct { .wasm32, .wasm64, => 16, + .powerpc64, + .powerpc64le, + => switch (target.os.tag) { + else => 8, + .linux => 16, + }, else => @divExact(target.ptrBitWidth(), 8), }; } diff --git a/src/Compilation.zig b/src/Compilation.zig index dc3101d598d8..ba0e643fda32 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1053,6 +1053,7 @@ pub fn create(gpa: Allocator, options: InitOptions) !*Compilation { buf.appendSliceAssumeCapacity(","); } } + if (buf.items.len == 0) break :blk ""; assert(mem.endsWith(u8, buf.items, ",")); buf.items[buf.items.len - 1] = 0; buf.shrinkAndFree(buf.items.len); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 57842ef1e09e..87ed52c31b77 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -340,7 +340,6 @@ const DataLayoutBuilder = struct { _: std.fmt.FormatOptions, writer: anytype, ) @TypeOf(writer).Error!void { - const is_aarch64_windows = self.target.cpu.arch == .aarch64 and self.target.os.tag == .windows; try writer.writeByte(switch (self.target.cpu.arch.endian()) { .Little => 'e', .Big => 'E', @@ -359,7 +358,7 @@ const DataLayoutBuilder = struct { .macho => 'o', // Mach-O mangling: Private symbols get `L` prefix. // Other symbols get a `_` prefix. .coff => switch (self.target.os.tag) { - .windows => switch (self.target.cpu.arch) { + .uefi, .windows => switch (self.target.cpu.arch) { .x86 => 'x', // Windows x86 COFF mangling: Private symbols get the usual // prefix. Regular C symbols get a `_` prefix. Functions with `__stdcall`, //`__fastcall`, and `__vectorcall` have custom mangling that appends `@N` @@ -407,7 +406,8 @@ const DataLayoutBuilder = struct { }; if (self.target.cpu.arch == .aarch64_32) continue; if (!info.force_in_data_layout and matches_default and - self.target.cpu.arch != .riscv64 and !is_aarch64_windows and + self.target.cpu.arch != .riscv64 and !(self.target.cpu.arch == .aarch64 and + (self.target.os.tag == .uefi or self.target.os.tag == .windows)) and self.target.cpu.arch != .bpfeb and self.target.cpu.arch != .bpfel) continue; try writer.writeAll("-p"); if (info.llvm != .default) try writer.print("{d}", .{@intFromEnum(info.llvm)}); @@ -423,7 +423,7 @@ const DataLayoutBuilder = struct { if (self.target.cpu.arch == .s390x) try self.typeAlignment(.integer, 1, 8, 8, false, writer); try self.typeAlignment(.integer, 8, 8, 8, false, writer); try self.typeAlignment(.integer, 16, 16, 16, false, writer); - try self.typeAlignment(.integer, 32, if (is_aarch64_windows) 0 else 32, 32, false, writer); + try self.typeAlignment(.integer, 32, 32, 32, false, writer); try self.typeAlignment(.integer, 64, 32, 64, false, writer); try self.typeAlignment(.integer, 128, 32, 64, false, writer); if (backendSupportsF16(self.target)) try self.typeAlignment(.float, 16, 16, 16, false, writer); @@ -453,8 +453,15 @@ const DataLayoutBuilder = struct { try self.typeAlignment(.vector, 128, 128, 128, true, writer); }, } - if (self.target.os.tag != .windows and self.target.cpu.arch != .avr) - try self.typeAlignment(.aggregate, 0, 0, 64, false, writer); + const swap_agg_nat = switch (self.target.cpu.arch) { + .x86, .x86_64 => switch (self.target.os.tag) { + .uefi, .windows => true, + else => false, + }, + .avr => true, + else => false, + }; + if (!swap_agg_nat) try self.typeAlignment(.aggregate, 0, 0, 64, false, writer); for (@as([]const u24, switch (self.target.cpu.arch) { .avr => &.{8}, .msp430 => &.{ 8, 16 }, @@ -498,6 +505,7 @@ const DataLayoutBuilder = struct { 0 => try writer.print("-n{d}", .{natural}), else => try writer.print(":{d}", .{natural}), }; + if (swap_agg_nat) try self.typeAlignment(.aggregate, 0, 0, 64, false, writer); if (self.target.cpu.arch == .hexagon) { try self.typeAlignment(.integer, 64, 64, 64, true, writer); try self.typeAlignment(.integer, 32, 32, 32, true, writer); @@ -506,11 +514,9 @@ const DataLayoutBuilder = struct { try self.typeAlignment(.float, 32, 32, 32, true, writer); try self.typeAlignment(.float, 64, 64, 64, true, writer); } - if (self.target.os.tag == .windows or self.target.cpu.arch == .avr) - try self.typeAlignment(.aggregate, 0, 0, 64, false, writer); const stack_abi = self.target.stackAlignment() * 8; - if (self.target.os.tag == .windows or self.target.cpu.arch == .msp430 or - stack_abi != ptr_bit_width) + if (self.target.os.tag == .uefi or self.target.os.tag == .windows or + self.target.cpu.arch == .msp430 or stack_abi != ptr_bit_width) try writer.print("-S{d}", .{stack_abi}); switch (self.target.cpu.arch) { .hexagon, .ve => { @@ -571,22 +577,21 @@ const DataLayoutBuilder = struct { .integer => { if (self.target.ptrBitWidth() <= 16 and size >= 128) return; abi = @min(abi, self.target.maxIntAlignment() * 8); - switch (self.target.os.tag) { - .linux => switch (self.target.cpu.arch) { - .aarch64, - .aarch64_be, - .aarch64_32, - .mips, - .mipsel, - => pref = @max(pref, 32), - else => {}, - }, - else => {}, - } switch (self.target.cpu.arch) { .aarch64, .aarch64_be, .aarch64_32, + => if (size == 128) { + abi = size; + pref = size; + } else switch (self.target.os.tag) { + .macos => {}, + .uefi, .windows => { + pref = size; + force_abi = size >= 32; + }, + else => pref = @max(pref, 32), + }, .bpfeb, .bpfel, .nvptx, @@ -597,6 +602,9 @@ const DataLayoutBuilder = struct { pref = size; }, .hexagon => force_abi = true, + .mips, + .mipsel, + => pref = @max(pref, 32), .mips64, .mips64el, => if (size <= 32) { @@ -617,7 +625,8 @@ const DataLayoutBuilder = struct { 128 => abi = 64, else => {}, } - } else if ((self.target.cpu.arch.isPPC64() and (size == 256 or size == 512)) or + } else if ((self.target.cpu.arch.isPPC64() and self.target.os.tag == .linux and + (size == 256 or size == 512)) or (self.target.cpu.arch.isNvptx() and (size == 16 or size == 32))) { force_abi = true; @@ -646,17 +655,21 @@ const DataLayoutBuilder = struct { .hexagon => if (size == 32 or size == 64) { force_abi = true; }, - .aarch64_32 => if (size == 128) { + .aarch64_32, .amdgcn => if (size == 128) { abi = size; pref = size; }, + .wasm32, .wasm64 => if (self.target.os.tag == .emscripten and size == 128) { + abi = 64; + pref = 64; + }, .ve => if (size == 64) { abi = size; pref = size; }, else => {}, }, - .aggregate => if (self.target.os.tag == .windows or + .aggregate => if (self.target.os.tag == .uefi or self.target.os.tag == .windows or self.target.cpu.arch.isARM() or self.target.cpu.arch.isThumb()) { pref = @min(pref, self.target.ptrBitWidth()); @@ -794,7 +807,7 @@ pub const Object = struct { builder.llvm.di_compile_unit = builder.llvm.di_builder.?.createCompileUnit( DW.LANG.C99, builder.llvm.di_builder.?.createFile(options.root_name, compile_unit_dir_z), - producer.toSlice(&builder).?, + producer.slice(&builder).?, options.optimize_mode != .Debug, "", // flags 0, // runtime version @@ -830,7 +843,7 @@ pub const Object = struct { target_machine = llvm.TargetMachine.create( builder.llvm.target.?, - builder.target_triple.toSlice(&builder).?, + builder.target_triple.slice(&builder).?, if (options.target.cpu.model.llvm_name) |s| s.ptr else null, options.llvm_cpu_features, opt_level, @@ -861,7 +874,7 @@ pub const Object = struct { defer llvm.disposeMessage(rep); std.testing.expectEqualStrings( std.mem.span(rep), - builder.data_layout.toSlice(&builder).?, + builder.data_layout.slice(&builder).?, ) catch unreachable; } } @@ -963,7 +976,7 @@ pub const Object = struct { llvm_error.* = try o.builder.structConst(llvm_slice_ty, &.{ global_index.toConst(), - try o.builder.intConst(llvm_usize_ty, name.toSlice(&o.builder).?.len), + try o.builder.intConst(llvm_usize_ty, name.slice(&o.builder).?.len), }); } @@ -1021,15 +1034,11 @@ pub const Object = struct { fn genModuleLevelAssembly(object: *Object) !void { const mod = object.module; - if (mod.global_assembly.count() == 0) return; - var buffer = std.ArrayList(u8).init(mod.gpa); - defer buffer.deinit(); - var it = mod.global_assembly.iterator(); - while (it.next()) |kv| { - try buffer.appendSlice(kv.value_ptr.*); - try buffer.append('\n'); - } - object.llvm_module.setModuleInlineAsm2(buffer.items.ptr, buffer.items.len - 1); + + const writer = object.builder.setModuleAsm(); + var it = mod.global_assembly.valueIterator(); + while (it.next()) |assembly| try writer.print("{s}\n", .{assembly.*}); + try object.builder.finishModuleAsm(); } fn resolveExportExternCollisions(object: *Object) !void { @@ -1223,6 +1232,7 @@ pub const Object = struct { const func = mod.funcInfo(func_index); const decl_index = func.owner_decl; const decl = mod.declPtr(decl_index); + const fn_info = mod.typeToFunc(decl.ty).?; const target = mod.getTarget(); const ip = &mod.intern_pool; @@ -1237,28 +1247,43 @@ pub const Object = struct { const global = function.ptrConst(&o.builder).global; const llvm_func = global.toLlvm(&o.builder); + var attributes = try function.ptrConst(&o.builder).attributes.toWip(&o.builder); + defer attributes.deinit(&o.builder); + if (func.analysis(ip).is_noinline) { + try attributes.addFnAttr(.@"noinline", &o.builder); o.addFnAttr(llvm_func, "noinline"); } else { + _ = try attributes.removeFnAttr(.@"noinline"); Object.removeFnAttr(llvm_func, "noinline"); } if (func.analysis(ip).stack_alignment.toByteUnitsOptional()) |alignment| { + try attributes.addFnAttr(.{ .alignstack = Builder.Alignment.fromByteUnits(alignment) }, &o.builder); + try attributes.addFnAttr(.@"noinline", &o.builder); o.addFnAttrInt(llvm_func, "alignstack", alignment); o.addFnAttr(llvm_func, "noinline"); } else { + _ = try attributes.removeFnAttr(.alignstack); Object.removeFnAttr(llvm_func, "alignstack"); } if (func.analysis(ip).is_cold) { + try attributes.addFnAttr(.cold, &o.builder); o.addFnAttr(llvm_func, "cold"); } else { + _ = try attributes.removeFnAttr(.cold); Object.removeFnAttr(llvm_func, "cold"); } // TODO: disable this if safety is off for the function scope const ssp_buf_size = mod.comp.bin_file.options.stack_protector; if (ssp_buf_size != 0) { + try attributes.addFnAttr(.sspstrong, &o.builder); + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("stack-protector-buffer-size"), + .value = try o.builder.fmt("{d}", .{ssp_buf_size}), + } }, &o.builder); var buf: [12]u8 = undefined; const arg = std.fmt.bufPrintZ(&buf, "{d}", .{ssp_buf_size}) catch unreachable; o.addFnAttr(llvm_func, "sspstrong"); @@ -1267,8 +1292,16 @@ pub const Object = struct { // TODO: disable this if safety is off for the function scope if (mod.comp.bin_file.options.stack_check) { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("probe-stack"), + .value = try o.builder.string("__zig_probe_stack"), + } }, &o.builder); o.addFnAttrString(llvm_func, "probe-stack", "__zig_probe_stack"); } else if (target.os.tag == .uefi) { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("no-stack-arg-probe"), + .value = .empty, + } }, &o.builder); o.addFnAttrString(llvm_func, "no-stack-arg-probe", ""); } @@ -1286,18 +1319,22 @@ pub const Object = struct { var llvm_arg_i: u32 = 0; // This gets the LLVM values from the function and stores them in `dg.args`. - const fn_info = mod.typeToFunc(decl.ty).?; const sret = firstParamSRet(fn_info, mod); const ret_ptr: Builder.Value = if (sret) param: { const param = wip.arg(llvm_arg_i); llvm_arg_i += 1; break :param param; } else .none; - const gpa = o.gpa; if (ccAbiPromoteInt(fn_info.cc, mod, fn_info.return_type.toType())) |s| switch (s) { - .signed => o.addAttr(llvm_func, 0, "signext"), - .unsigned => o.addAttr(llvm_func, 0, "zeroext"), + .signed => { + try attributes.addRetAttr(.signext, &o.builder); + o.addAttr(llvm_func, 0, "signext"); + }, + .unsigned => { + try attributes.addRetAttr(.zeroext, &o.builder); + o.addAttr(llvm_func, 0, "zeroext"); + }, }; const err_return_tracing = fn_info.return_type.toType().isError(mod) and @@ -1312,6 +1349,7 @@ pub const Object = struct { // This is the list of args we will use that correspond directly to the AIR arg // instructions. Depending on the calling convention, this list is not necessarily // a bijection with the actual LLVM parameters of the function. + const gpa = o.gpa; var args: std.ArrayListUnmanaged(Builder.Value) = .{}; defer args.deinit(gpa); @@ -1337,7 +1375,7 @@ pub const Object = struct { } else { args.appendAssumeCapacity(param); - o.addByValParamAttrs(llvm_func, param_ty, param_index, fn_info, @intCast(llvm_arg_i)); + try o.addByValParamAttrsOld(&attributes, llvm_func, param_ty, param_index, fn_info, llvm_arg_i); } llvm_arg_i += 1; }, @@ -1347,7 +1385,7 @@ pub const Object = struct { const param = wip.arg(llvm_arg_i); const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod)); - o.addByRefParamAttrs(llvm_func, @intCast(llvm_arg_i), @intCast(alignment.toByteUnits() orelse 0), it.byval_attr, param_llvm_ty); + try o.addByRefParamAttrsOld(&attributes, llvm_func, llvm_arg_i, alignment, it.byval_attr, param_llvm_ty); llvm_arg_i += 1; if (isByRef(param_ty, mod)) { @@ -1362,7 +1400,8 @@ pub const Object = struct { const param = wip.arg(llvm_arg_i); const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod)); - o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "noundef"); + try attributes.addParamAttr(llvm_arg_i, .noundef, &o.builder); + o.addArgAttr(llvm_func, llvm_arg_i, "noundef"); llvm_arg_i += 1; if (isByRef(param_ty, mod)) { @@ -1398,21 +1437,28 @@ pub const Object = struct { if (math.cast(u5, it.zig_index - 1)) |i| { if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) { - o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "noalias"); + try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder); + o.addArgAttr(llvm_func, llvm_arg_i, "noalias"); } } if (param_ty.zigTypeTag(mod) != .Optional) { - o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "nonnull"); + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + o.addArgAttr(llvm_func, llvm_arg_i, "nonnull"); } if (ptr_info.flags.is_const) { - o.addArgAttr(llvm_func, @intCast(llvm_arg_i), "readonly"); + try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); + o.addArgAttr(llvm_func, llvm_arg_i, "readonly"); } - const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse - @max(ptr_info.child.toType().abiAlignment(mod), 1); - o.addArgAttrInt(llvm_func, @intCast(llvm_arg_i), "align", elem_align); - const ptr_param = wip.arg(llvm_arg_i + 0); - const len_param = wip.arg(llvm_arg_i + 1); - llvm_arg_i += 2; + const elem_align = Builder.Alignment.fromByteUnits( + ptr_info.flags.alignment.toByteUnitsOptional() orelse + @max(ptr_info.child.toType().abiAlignment(mod), 1), + ); + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder); + o.addArgAttrInt(llvm_func, llvm_arg_i, "align", elem_align.toByteUnits() orelse 0); + const ptr_param = wip.arg(llvm_arg_i); + llvm_arg_i += 1; + const len_param = wip.arg(llvm_arg_i); + llvm_arg_i += 1; const slice_llvm_ty = try o.lowerType(param_ty); args.appendAssumeCapacity( @@ -1482,6 +1528,8 @@ pub const Object = struct { } } + function.ptr(&o.builder).attributes = try attributes.finish(&o.builder); + var di_file: ?*llvm.DIFile = null; var di_scope: ?*llvm.DIScope = null; @@ -1618,7 +1666,7 @@ pub const Object = struct { llvm_global.setDLLStorageClass(.Default); } if (self.di_map.get(decl)) |di_node| { - const decl_name_slice = decl_name.toSlice(&self.builder).?; + const decl_name_slice = decl_name.slice(&self.builder).?; if (try decl.isFunction(mod)) { const di_func: *llvm.DISubprogram = @ptrCast(di_node); const linkage_name = llvm.MDString.get(self.builder.llvm.context, decl_name_slice.ptr, decl_name_slice.len); @@ -1655,7 +1703,7 @@ pub const Object = struct { llvm_global.setDLLStorageClass(.DLLExport); } if (self.di_map.get(decl)) |di_node| { - const exp_name_slice = exp_name.toSlice(&self.builder).?; + const exp_name_slice = exp_name.slice(&self.builder).?; if (try decl.isFunction(mod)) { const di_func: *llvm.DISubprogram = @ptrCast(di_node); const linkage_name = llvm.MDString.get(self.builder.llvm.context, exp_name_slice.ptr, exp_name_slice.len); @@ -2816,7 +2864,7 @@ pub const Object = struct { const fqn = try o.builder.string(ip.stringToSlice(try decl.getFullyQualifiedName(mod))); const llvm_addrspace = toLlvmAddressSpace(decl.@"addrspace", target); - const llvm_fn = o.llvm_module.addFunctionInAddressSpace(fqn.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder), @intFromEnum(llvm_addrspace)); + const llvm_fn = o.llvm_module.addFunctionInAddressSpace(fqn.slice(&o.builder).?, fn_type.toLlvm(&o.builder), @intFromEnum(llvm_addrspace)); var global = Builder.Global{ .type = fn_type, @@ -2826,6 +2874,9 @@ pub const Object = struct { .global = @enumFromInt(o.builder.globals.count()), }; + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); + const is_extern = decl.isExtern(mod); if (!is_extern) { global.linkage = .internal; @@ -2834,43 +2885,64 @@ pub const Object = struct { llvm_fn.setUnnamedAddr(.True); } else { if (target.isWasm()) { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("wasm-import-name"), + .value = try o.builder.string(ip.stringToSlice(decl.name)), + } }, &o.builder); o.addFnAttrString(llvm_fn, "wasm-import-name", ip.stringToSlice(decl.name)); if (ip.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| { if (!std.mem.eql(u8, lib_name, "c")) { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("wasm-import-module"), + .value = try o.builder.string(lib_name), + } }, &o.builder); o.addFnAttrString(llvm_fn, "wasm-import-module", lib_name); } } } } + var llvm_arg_i: u32 = 0; if (sret) { - o.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0 - o.addArgAttr(llvm_fn, 0, "noalias"); + // Sret pointers must not be address 0 + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder); + o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull"); // Sret pointers must not be address 0 + o.addArgAttr(llvm_fn, llvm_arg_i, "noalias"); - const raw_llvm_ret_ty = (try o.lowerType(fn_info.return_type.toType())).toLlvm(&o.builder); - llvm_fn.addSretAttr(raw_llvm_ret_ty); + const raw_llvm_ret_ty = try o.lowerType(fn_info.return_type.toType()); + try attributes.addParamAttr(llvm_arg_i, .{ .sret = raw_llvm_ret_ty }, &o.builder); + llvm_fn.addSretAttr(raw_llvm_ret_ty.toLlvm(&o.builder)); + + llvm_arg_i += 1; } const err_return_tracing = fn_info.return_type.toType().isError(mod) and mod.comp.bin_file.options.error_return_tracing; if (err_return_tracing) { - o.addArgAttr(llvm_fn, @intFromBool(sret), "nonnull"); + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull"); + llvm_arg_i += 1; } switch (fn_info.cc) { .Unspecified, .Inline => { + function.call_conv = .fastcc; llvm_fn.setFunctionCallConv(.Fast); }, .Naked => { + try attributes.addFnAttr(.naked, &o.builder); o.addFnAttr(llvm_fn, "naked"); }, .Async => { + function.call_conv = .fastcc; llvm_fn.setFunctionCallConv(.Fast); @panic("TODO: LLVM backend lower async function"); }, else => { - llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target)); + function.call_conv = toLlvmCallConv(fn_info.cc, target); + llvm_fn.setFunctionCallConv(@enumFromInt(@intFromEnum(function.call_conv))); }, } @@ -2880,9 +2952,10 @@ pub const Object = struct { } // Function attributes that are independent of analysis results of the function body. - o.addCommonFnAttributes(llvm_fn); + try o.addCommonFnAttributes(&attributes, llvm_fn); if (fn_info.return_type == .noreturn_type) { + try attributes.addFnAttr(.noreturn, &o.builder); o.addFnAttr(llvm_fn, "noreturn"); } @@ -2890,23 +2963,24 @@ pub const Object = struct { // because functions with bodies are handled in `updateFunc`. if (is_extern) { var it = iterateParamTypes(o, fn_info); - it.llvm_index += @intFromBool(sret); - it.llvm_index += @intFromBool(err_return_tracing); + it.llvm_index = llvm_arg_i; while (try it.next()) |lowering| switch (lowering) { .byval => { const param_index = it.zig_index - 1; const param_ty = fn_info.param_types.get(ip)[param_index].toType(); if (!isByRef(param_ty, mod)) { - o.addByValParamAttrs(llvm_fn, param_ty, param_index, fn_info, it.llvm_index - 1); + try o.addByValParamAttrsOld(&attributes, llvm_fn, param_ty, param_index, fn_info, it.llvm_index - 1); } }, .byref => { const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1]; const param_llvm_ty = try o.lowerType(param_ty.toType()); - const alignment = param_ty.toType().abiAlignment(mod); - o.addByRefParamAttrs(llvm_fn, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty); + const alignment = + Builder.Alignment.fromByteUnits(param_ty.toType().abiAlignment(mod)); + try o.addByRefParamAttrsOld(&attributes, llvm_fn, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty); }, .byref_mut => { + try attributes.addParamAttr(it.llvm_index - 1, .noundef, &o.builder); o.addArgAttr(llvm_fn, it.llvm_index - 1, "noundef"); }, // No attributes needed for these. @@ -2924,25 +2998,42 @@ pub const Object = struct { }; } + function.attributes = try attributes.finish(&o.builder); + try o.builder.llvm.globals.append(o.gpa, llvm_fn); gop.value_ptr.* = try o.builder.addGlobal(fqn, global); try o.builder.functions.append(o.gpa, function); return global.kind.function; } - fn addCommonFnAttributes(o: *Object, llvm_fn: *llvm.Value) void { + fn addCommonFnAttributes( + o: *Object, + attributes: *Builder.FunctionAttributes.Wip, + llvm_fn: *llvm.Value, + ) Allocator.Error!void { const comp = o.module.comp; if (!comp.bin_file.options.red_zone) { + try attributes.addFnAttr(.noredzone, &o.builder); o.addFnAttr(llvm_fn, "noredzone"); } if (comp.bin_file.options.omit_frame_pointer) { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("frame-pointer"), + .value = try o.builder.string("none"), + } }, &o.builder); o.addFnAttrString(llvm_fn, "frame-pointer", "none"); } else { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("frame-pointer"), + .value = try o.builder.string("all"), + } }, &o.builder); o.addFnAttrString(llvm_fn, "frame-pointer", "all"); } + try attributes.addFnAttr(.nounwind, &o.builder); o.addFnAttr(llvm_fn, "nounwind"); if (comp.unwind_tables) { + try attributes.addFnAttr(.{ .uwtable = Builder.Attribute.UwTable.default }, &o.builder); o.addFnAttrInt(llvm_fn, "uwtable", 2); } if (comp.bin_file.options.skip_linker_dependencies or @@ -2953,22 +3044,38 @@ pub const Object = struct { // and llvm detects that the body is equivalent to memcpy, it may replace the // body of memcpy with a call to memcpy, which would then cause a stack // overflow instead of performing memcpy. + try attributes.addFnAttr(.nobuiltin, &o.builder); o.addFnAttr(llvm_fn, "nobuiltin"); } if (comp.bin_file.options.optimize_mode == .ReleaseSmall) { + try attributes.addFnAttr(.minsize, &o.builder); + try attributes.addFnAttr(.optsize, &o.builder); o.addFnAttr(llvm_fn, "minsize"); o.addFnAttr(llvm_fn, "optsize"); } if (comp.bin_file.options.tsan) { + try attributes.addFnAttr(.sanitize_thread, &o.builder); o.addFnAttr(llvm_fn, "sanitize_thread"); } if (comp.getTarget().cpu.model.llvm_name) |s| { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("target-cpu"), + .value = try o.builder.string(s), + } }, &o.builder); llvm_fn.addFunctionAttr("target-cpu", s); } if (comp.bin_file.options.llvm_cpu_features) |s| { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("target-features"), + .value = try o.builder.string(std.mem.span(s)), + } }, &o.builder); llvm_fn.addFunctionAttr("target-features", s); } if (comp.getTarget().cpu.arch.isBpf()) { + try attributes.addFnAttr(.{ .string = .{ + .kind = try o.builder.string("no-builtins"), + .value = .empty, + } }, &o.builder); llvm_fn.addFunctionAttr("no-builtins", ""); } } @@ -3002,7 +3109,7 @@ pub const Object = struct { fqn; const llvm_global = o.llvm_module.addGlobalInAddressSpace( global.type.toLlvm(&o.builder), - fqn.toSlice(&o.builder).?, + fqn.slice(&o.builder).?, @intFromEnum(global.addr_space), ); @@ -4403,47 +4510,114 @@ pub const Object = struct { fn addByValParamAttrs( o: *Object, + attributes: *Builder.FunctionAttributes.Wip, + param_ty: Type, + param_index: u32, + fn_info: InternPool.Key.FuncType, + llvm_arg_i: u32, + ) Allocator.Error!void { + const mod = o.module; + if (param_ty.isPtrAtRuntime(mod)) { + const ptr_info = param_ty.ptrInfo(mod); + if (math.cast(u5, param_index)) |i| { + if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) { + try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder); + } + } + if (!param_ty.isPtrLikeOptional(mod) and !ptr_info.flags.is_allowzero) { + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + } + if (ptr_info.flags.is_const) { + try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); + } + const elem_align = Builder.Alignment.fromByteUnits( + ptr_info.flags.alignment.toByteUnitsOptional() orelse + @max(ptr_info.child.toType().abiAlignment(mod), 1), + ); + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder); + } else if (ccAbiPromoteInt(fn_info.cc, mod, param_ty)) |s| switch (s) { + .signed => try attributes.addParamAttr(llvm_arg_i, .signext, &o.builder), + .unsigned => try attributes.addParamAttr(llvm_arg_i, .zeroext, &o.builder), + }; + } + + fn addByRefParamAttrs( + o: *Object, + attributes: *Builder.FunctionAttributes.Wip, + llvm_arg_i: u32, + alignment: Builder.Alignment, + byval_attr: bool, + param_llvm_ty: Builder.Type, + ) Allocator.Error!void { + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = alignment }, &o.builder); + if (byval_attr) { + try attributes.addParamAttr(llvm_arg_i, .{ .byval = param_llvm_ty }, &o.builder); + } + } + + fn addByValParamAttrsOld( + o: *Object, + attributes: *Builder.FunctionAttributes.Wip, llvm_fn: *llvm.Value, param_ty: Type, param_index: u32, fn_info: InternPool.Key.FuncType, llvm_arg_i: u32, - ) void { + ) Allocator.Error!void { const mod = o.module; if (param_ty.isPtrAtRuntime(mod)) { const ptr_info = param_ty.ptrInfo(mod); if (math.cast(u5, param_index)) |i| { if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) { + try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder); o.addArgAttr(llvm_fn, llvm_arg_i, "noalias"); } } if (!param_ty.isPtrLikeOptional(mod) and !ptr_info.flags.is_allowzero) { + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull"); } if (ptr_info.flags.is_const) { + try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); o.addArgAttr(llvm_fn, llvm_arg_i, "readonly"); } - const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse - @max(ptr_info.child.toType().abiAlignment(mod), 1); - o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", elem_align); + const elem_align = Builder.Alignment.fromByteUnits( + ptr_info.flags.alignment.toByteUnitsOptional() orelse + @max(ptr_info.child.toType().abiAlignment(mod), 1), + ); + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder); + o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", elem_align.toByteUnits() orelse 0); } else if (ccAbiPromoteInt(fn_info.cc, mod, param_ty)) |s| switch (s) { - .signed => o.addArgAttr(llvm_fn, llvm_arg_i, "signext"), - .unsigned => o.addArgAttr(llvm_fn, llvm_arg_i, "zeroext"), + .signed => { + try attributes.addParamAttr(llvm_arg_i, .signext, &o.builder); + o.addArgAttr(llvm_fn, llvm_arg_i, "signext"); + }, + .unsigned => { + try attributes.addParamAttr(llvm_arg_i, .zeroext, &o.builder); + o.addArgAttr(llvm_fn, llvm_arg_i, "zeroext"); + }, }; } - fn addByRefParamAttrs( + fn addByRefParamAttrsOld( o: *Object, + attributes: *Builder.FunctionAttributes.Wip, llvm_fn: *llvm.Value, llvm_arg_i: u32, - alignment: u32, + alignment: Builder.Alignment, byval_attr: bool, param_llvm_ty: Builder.Type, - ) void { + ) Allocator.Error!void { + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = alignment }, &o.builder); o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull"); o.addArgAttr(llvm_fn, llvm_arg_i, "readonly"); - o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", alignment); + o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", alignment.toByteUnits() orelse 0); if (byval_attr) { + try attributes.addParamAttr(llvm_arg_i, .{ .byval = param_llvm_ty }, &o.builder); llvm_fn.addByValAttr(llvm_arg_i, param_llvm_ty.toLlvm(&o.builder)); } } @@ -4841,10 +5015,10 @@ pub const FuncGen = struct { .slice_ptr => try self.airSliceField(inst, 0), .slice_len => try self.airSliceField(inst, 1), - .call => try self.airCall(inst, .Auto), - .call_always_tail => try self.airCall(inst, .AlwaysTail), - .call_never_tail => try self.airCall(inst, .NeverTail), - .call_never_inline => try self.airCall(inst, .NeverInline), + .call => try self.airCall(inst, .auto), + .call_always_tail => try self.airCall(inst, .always_tail), + .call_never_tail => try self.airCall(inst, .never_tail), + .call_never_inline => try self.airCall(inst, .never_inline), .ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0), .ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1), @@ -4953,7 +5127,15 @@ pub const FuncGen = struct { } } - fn airCall(self: *FuncGen, inst: Air.Inst.Index, attr: llvm.CallAttr) !Builder.Value { + pub const CallAttr = enum { + Auto, + NeverTail, + NeverInline, + AlwaysTail, + AlwaysInline, + }; + + fn airCall(self: *FuncGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !Builder.Value { const pl_op = self.air.instructions.items(.data)[inst].pl_op; const extra = self.air.extraData(Air.Call, pl_op.payload); const args: []const Air.Inst.Ref = @ptrCast(self.air.extra[extra.end..][0..extra.data.args_len]); @@ -4972,14 +5154,25 @@ pub const FuncGen = struct { const target = mod.getTarget(); const sret = firstParamSRet(fn_info, mod); - var llvm_args = std.ArrayList(*llvm.Value).init(self.gpa); + var llvm_args = std.ArrayList(Builder.Value).init(self.gpa); defer llvm_args.deinit(); + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); + + switch (modifier) { + .auto, .never_tail, .always_tail => {}, + .never_inline => try attributes.addFnAttr(.@"noinline", &o.builder), + .async_kw, .no_async, .always_inline, .compile_time => unreachable, + } + const ret_ptr = if (!sret) null else blk: { const llvm_ret_ty = try o.lowerType(return_type); + try attributes.addParamAttr(0, .{ .sret = llvm_ret_ty }, &o.builder); + const alignment = Builder.Alignment.fromByteUnits(return_type.abiAlignment(mod)); const ret_ptr = try self.buildAlloca(llvm_ret_ty, alignment); - try llvm_args.append(ret_ptr.toLlvm(&self.wip)); + try llvm_args.append(ret_ptr); break :blk ret_ptr; }; @@ -4987,7 +5180,7 @@ pub const FuncGen = struct { o.module.comp.bin_file.options.error_return_tracing; if (err_return_tracing) { assert(self.err_ret_trace != .none); - try llvm_args.append(self.err_ret_trace.toLlvm(&self.wip)); + try llvm_args.append(self.err_ret_trace); } var it = iterateParamTypes(o, fn_info); @@ -5001,9 +5194,9 @@ pub const FuncGen = struct { if (isByRef(param_ty, mod)) { const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod)); const loaded = try self.wip.load(.normal, llvm_param_ty, llvm_arg, alignment, ""); - try llvm_args.append(loaded.toLlvm(&self.wip)); + try llvm_args.append(loaded); } else { - try llvm_args.append(llvm_arg.toLlvm(&self.wip)); + try llvm_args.append(llvm_arg); } }, .byref => { @@ -5011,13 +5204,13 @@ pub const FuncGen = struct { const param_ty = self.typeOf(arg); const llvm_arg = try self.resolveInst(arg); if (isByRef(param_ty, mod)) { - try llvm_args.append(llvm_arg.toLlvm(&self.wip)); + try llvm_args.append(llvm_arg); } else { const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod)); const param_llvm_ty = llvm_arg.typeOfWip(&self.wip); const arg_ptr = try self.buildAlloca(param_llvm_ty, alignment); _ = try self.wip.store(.normal, llvm_arg, arg_ptr, alignment); - try llvm_args.append(arg_ptr.toLlvm(&self.wip)); + try llvm_args.append(arg_ptr); } }, .byref_mut => { @@ -5034,7 +5227,7 @@ pub const FuncGen = struct { } else { _ = try self.wip.store(.normal, llvm_arg, arg_ptr, alignment); } - try llvm_args.append(arg_ptr.toLlvm(&self.wip)); + try llvm_args.append(arg_ptr); }, .abi_sized_int => { const arg = args[it.zig_index - 1]; @@ -5045,7 +5238,7 @@ pub const FuncGen = struct { if (isByRef(param_ty, mod)) { const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod)); const loaded = try self.wip.load(.normal, int_llvm_ty, llvm_arg, alignment, ""); - try llvm_args.append(loaded.toLlvm(&self.wip)); + try llvm_args.append(loaded); } else { // LLVM does not allow bitcasting structs so we must allocate // a local, store as one type, and then load as another type. @@ -5056,7 +5249,7 @@ pub const FuncGen = struct { const int_ptr = try self.buildAlloca(int_llvm_ty, alignment); _ = try self.wip.store(.normal, llvm_arg, int_ptr, alignment); const loaded = try self.wip.load(.normal, int_llvm_ty, int_ptr, alignment, ""); - try llvm_args.append(loaded.toLlvm(&self.wip)); + try llvm_args.append(loaded); } }, .slice => { @@ -5064,7 +5257,7 @@ pub const FuncGen = struct { const llvm_arg = try self.resolveInst(arg); const ptr = try self.wip.extractValue(llvm_arg, &.{0}, ""); const len = try self.wip.extractValue(llvm_arg, &.{1}, ""); - try llvm_args.appendSlice(&.{ ptr.toLlvm(&self.wip), len.toLlvm(&self.wip) }); + try llvm_args.appendSlice(&.{ ptr, len }); }, .multiple_llvm_types => { const arg = args[it.zig_index - 1]; @@ -5086,14 +5279,14 @@ pub const FuncGen = struct { Builder.Alignment.fromByteUnits(@divExact(target.ptrBitWidth(), 8)); const field_ptr = try self.wip.gepStruct(llvm_ty, arg_ptr, i, ""); const loaded = try self.wip.load(.normal, field_ty, field_ptr, alignment, ""); - llvm_args.appendAssumeCapacity(loaded.toLlvm(&self.wip)); + llvm_args.appendAssumeCapacity(loaded); } }, .as_u16 => { const arg = args[it.zig_index - 1]; const llvm_arg = try self.resolveInst(arg); const casted = try self.wip.cast(.bitcast, llvm_arg, .i16, ""); - try llvm_args.append(casted.toLlvm(&self.wip)); + try llvm_args.append(casted); }, .float_array => |count| { const arg = args[it.zig_index - 1]; @@ -5110,7 +5303,7 @@ pub const FuncGen = struct { const array_ty = try o.builder.arrayType(count, float_ty); const loaded = try self.wip.load(.normal, array_ty, llvm_arg, alignment, ""); - try llvm_args.append(loaded.toLlvm(&self.wip)); + try llvm_args.append(loaded); }, .i32_array, .i64_array => |arr_len| { const elem_size: u8 = if (lowering == .i32_array) 32 else 64; @@ -5127,24 +5320,10 @@ pub const FuncGen = struct { const array_ty = try o.builder.arrayType(arr_len, try o.builder.intType(@intCast(elem_size))); const loaded = try self.wip.load(.normal, array_ty, llvm_arg, alignment, ""); - try llvm_args.append(loaded.toLlvm(&self.wip)); + try llvm_args.append(loaded); }, }; - const llvm_fn_ty = try o.lowerType(zig_fn_ty); - const call = (try self.wip.unimplemented(llvm_fn_ty.functionReturn(&o.builder), "")).finish( - self.builder.buildCall( - llvm_fn_ty.toLlvm(&o.builder), - llvm_fn.toLlvm(&self.wip), - llvm_args.items.ptr, - @intCast(llvm_args.items.len), - toLlvmCallConv(fn_info.cc, target), - attr, - "", - ), - &self.wip, - ); - if (callee_ty.zigTypeTag(mod) == .Pointer) { // Add argument attributes for function pointer calls. it = iterateParamTypes(o, fn_info); @@ -5155,19 +5334,17 @@ pub const FuncGen = struct { const param_index = it.zig_index - 1; const param_ty = fn_info.param_types.get(ip)[param_index].toType(); if (!isByRef(param_ty, mod)) { - o.addByValParamAttrs(call.toLlvm(&self.wip), param_ty, param_index, fn_info, it.llvm_index - 1); + try o.addByValParamAttrs(&attributes, param_ty, param_index, fn_info, it.llvm_index - 1); } }, .byref => { const param_index = it.zig_index - 1; const param_ty = fn_info.param_types.get(ip)[param_index].toType(); const param_llvm_ty = try o.lowerType(param_ty); - const alignment = param_ty.abiAlignment(mod); - o.addByRefParamAttrs(call.toLlvm(&self.wip), it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty); - }, - .byref_mut => { - o.addArgAttr(call.toLlvm(&self.wip), it.llvm_index - 1, "noundef"); + const alignment = Builder.Alignment.fromByteUnits(param_ty.abiAlignment(mod)); + try o.addByRefParamAttrs(&attributes, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty); }, + .byref_mut => try attributes.addParamAttr(it.llvm_index - 1, .noundef, &o.builder), // No attributes needed for these. .no_bits, .abi_sized_int, @@ -5186,23 +5363,40 @@ pub const FuncGen = struct { if (math.cast(u5, it.zig_index - 1)) |i| { if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) { - o.addArgAttr(call.toLlvm(&self.wip), llvm_arg_i, "noalias"); + try attributes.addParamAttr(llvm_arg_i, .@"noalias", &o.builder); } } if (param_ty.zigTypeTag(mod) != .Optional) { - o.addArgAttr(call.toLlvm(&self.wip), llvm_arg_i, "nonnull"); + try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder); } if (ptr_info.flags.is_const) { - o.addArgAttr(call.toLlvm(&self.wip), llvm_arg_i, "readonly"); + try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder); } - const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse - @max(ptr_info.child.toType().abiAlignment(mod), 1); - o.addArgAttrInt(call.toLlvm(&self.wip), llvm_arg_i, "align", elem_align); + const elem_align = Builder.Alignment.fromByteUnits( + ptr_info.flags.alignment.toByteUnitsOptional() orelse + @max(ptr_info.child.toType().abiAlignment(mod), 1), + ); + try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder); }, }; } - if (fn_info.return_type == .noreturn_type and attr != .AlwaysTail) { + const call = try self.wip.call( + switch (modifier) { + .auto, .never_inline => .normal, + .never_tail => .notail, + .always_tail => .musttail, + .async_kw, .no_async, .always_inline, .compile_time => unreachable, + }, + toLlvmCallConv(fn_info.cc, target), + try attributes.finish(&o.builder), + try o.lowerType(zig_fn_ty), + llvm_fn, + llvm_args.items, + "", + ); + + if (fn_info.return_type == .noreturn_type and modifier != .always_tail) { return .none; } @@ -5211,9 +5405,7 @@ pub const FuncGen = struct { } const llvm_ret_ty = try o.lowerType(return_type); - if (ret_ptr) |rp| { - call.toLlvm(&self.wip).setCallSret(llvm_ret_ty.toLlvm(&o.builder)); if (isByRef(return_type, mod)) { return rp; } else { @@ -5269,25 +5461,24 @@ pub const FuncGen = struct { // ptr null, ; stack trace // ptr @2, ; addr (null ?usize) // ) - const args = [4]*llvm.Value{ - msg_ptr.toLlvm(&o.builder), - (try o.builder.intConst(llvm_usize, msg_len)).toLlvm(&o.builder), - (try o.builder.nullConst(.ptr)).toLlvm(&o.builder), - null_opt_addr_global.toLlvm(&o.builder), - }; const panic_func = mod.funcInfo(mod.panic_func_index); const panic_decl = mod.declPtr(panic_func.owner_decl); const fn_info = mod.typeToFunc(panic_decl.ty).?; const panic_global = try o.resolveLlvmFunction(panic_func.owner_decl); - _ = (try fg.wip.unimplemented(.void, "")).finish(fg.builder.buildCall( - (try o.lowerType(panic_decl.ty)).toLlvm(&o.builder), - panic_global.toLlvm(&o.builder), - &args, - args.len, + _ = try fg.wip.call( + .normal, toLlvmCallConv(fn_info.cc, target), - .Auto, + .none, + panic_global.typeOf(&o.builder), + panic_global.toValue(&o.builder), + &.{ + msg_ptr.toValue(), + try o.builder.intValue(llvm_usize, msg_len), + try o.builder.nullValue(.ptr), + null_opt_addr_global.toValue(), + }, "", - ), &fg.wip); + ); _ = try fg.wip.@"unreachable"(); } @@ -5395,7 +5586,7 @@ pub const FuncGen = struct { o.llvm_module.addFunction(llvm_fn_name, llvm_fn_ty.toLlvm(&o.builder)); const args: [2]*llvm.Value = .{ dest_list.toLlvm(&self.wip), src_list.toLlvm(&self.wip) }; - _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall( + _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, &args, @@ -5422,7 +5613,7 @@ pub const FuncGen = struct { o.llvm_module.addFunction(llvm_fn_name, llvm_fn_ty.toLlvm(&o.builder)); const args: [1]*llvm.Value = .{list.toLlvm(&self.wip)}; - _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall( + _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, &args, @@ -5449,7 +5640,7 @@ pub const FuncGen = struct { o.llvm_module.addFunction(llvm_fn_name, llvm_fn_ty.toLlvm(&o.builder)); const args: [1]*llvm.Value = .{list.toLlvm(&self.wip)}; - _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall( + _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, &args, @@ -5495,16 +5686,15 @@ pub const FuncGen = struct { const un_op = self.air.instructions.items(.data)[inst].un_op; const operand = try self.resolveInst(un_op); const llvm_fn = try self.getCmpLtErrorsLenFunction(); - const args: [1]*llvm.Value = .{operand.toLlvm(&self.wip)}; - return (try self.wip.unimplemented(.i1, "")).finish(self.builder.buildCall( - llvm_fn.typeOf(&o.builder).toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - &args, - args.len, - .Fast, - .Auto, + return self.wip.call( + .normal, + .fastcc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + &.{operand}, "", - ), &self.wip); + ); } fn cmp( @@ -5953,16 +6143,15 @@ pub const FuncGen = struct { } const libc_fn = try self.getLibcFunction(fn_name, &.{param_type}, dest_llvm_ty); - const params = [1]*llvm.Value{extended.toLlvm(&self.wip)}; - return (try self.wip.unimplemented(dest_llvm_ty, "")).finish(self.builder.buildCall( - libc_fn.typeOf(&o.builder).toLlvm(&o.builder), - libc_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .C, - .Auto, + return self.wip.call( + .normal, + .ccc, + .none, + libc_fn.typeOf(&o.builder), + libc_fn.toValue(&o.builder), + &.{extended}, "", - ), &self.wip); + ); } fn airIntFromFloat(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !Builder.Value { @@ -6013,16 +6202,15 @@ pub const FuncGen = struct { const operand_llvm_ty = try o.lowerType(operand_ty); const libc_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, libc_ret_ty); - const params = [1]*llvm.Value{operand.toLlvm(&self.wip)}; - var result = (try self.wip.unimplemented(libc_ret_ty, "")).finish(self.builder.buildCall( - libc_fn.typeOf(&o.builder).toLlvm(&o.builder), - libc_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .C, - .Auto, + var result = try self.wip.call( + .normal, + .ccc, + .none, + libc_fn.typeOf(&o.builder), + libc_fn.toValue(&o.builder), + &.{operand}, "", - ), &self.wip); + ); if (libc_ret_ty != ret_ty) result = try self.wip.cast(.bitcast, result, ret_ty, ""); if (ret_ty != dest_llvm_ty) result = try self.wip.cast(.trunc, result, dest_llvm_ty, ""); @@ -6588,7 +6776,7 @@ pub const FuncGen = struct { const max_param_count = inputs.len + outputs.len; const llvm_param_types = try arena.alloc(Builder.Type, max_param_count); - const llvm_param_values = try arena.alloc(*llvm.Value, max_param_count); + const llvm_param_values = try arena.alloc(Builder.Value, max_param_count); // This stores whether we need to add an elementtype attribute and // if so, the element type itself. const llvm_param_attrs = try arena.alloc(Builder.Type, max_param_count); @@ -6628,7 +6816,7 @@ pub const FuncGen = struct { // Pass the result by reference as an indirect output (e.g. "=*m") llvm_constraints.appendAssumeCapacity('*'); - llvm_param_values[llvm_param_i] = output_inst.toLlvm(&self.wip); + llvm_param_values[llvm_param_i] = output_inst; llvm_param_types[llvm_param_i] = output_inst.typeOfWip(&self.wip); llvm_param_attrs[llvm_param_i] = elem_llvm_ty; llvm_param_i += 1; @@ -6678,25 +6866,25 @@ pub const FuncGen = struct { if (isByRef(arg_ty, mod)) { llvm_elem_ty = try o.lowerPtrElemTy(arg_ty); if (constraintAllowsMemory(constraint)) { - llvm_param_values[llvm_param_i] = arg_llvm_value.toLlvm(&self.wip); + llvm_param_values[llvm_param_i] = arg_llvm_value; llvm_param_types[llvm_param_i] = arg_llvm_value.typeOfWip(&self.wip); } else { const alignment = Builder.Alignment.fromByteUnits(arg_ty.abiAlignment(mod)); const arg_llvm_ty = try o.lowerType(arg_ty); const load_inst = try self.wip.load(.normal, arg_llvm_ty, arg_llvm_value, alignment, ""); - llvm_param_values[llvm_param_i] = load_inst.toLlvm(&self.wip); + llvm_param_values[llvm_param_i] = load_inst; llvm_param_types[llvm_param_i] = arg_llvm_ty; } } else { if (constraintAllowsRegister(constraint)) { - llvm_param_values[llvm_param_i] = arg_llvm_value.toLlvm(&self.wip); + llvm_param_values[llvm_param_i] = arg_llvm_value; llvm_param_types[llvm_param_i] = arg_llvm_value.typeOfWip(&self.wip); } else { const alignment = Builder.Alignment.fromByteUnits(arg_ty.abiAlignment(mod)); const arg_ptr = try self.buildAlloca(arg_llvm_value.typeOfWip(&self.wip), alignment); _ = try self.wip.store(.normal, arg_llvm_value, arg_ptr, alignment); - llvm_param_values[llvm_param_i] = arg_ptr.toLlvm(&self.wip); + llvm_param_values[llvm_param_i] = arg_ptr; llvm_param_types[llvm_param_i] = arg_ptr.typeOfWip(&self.wip); } } @@ -6843,38 +7031,26 @@ pub const FuncGen = struct { } } + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); + for (llvm_param_attrs[0..param_count], 0..) |llvm_elem_ty, i| if (llvm_elem_ty != .none) + try attributes.addParamAttr(i, .{ .elementtype = llvm_elem_ty }, &o.builder); + const ret_llvm_ty = switch (return_count) { 0 => .void, 1 => llvm_ret_types[0], else => try o.builder.structType(.normal, llvm_ret_types), }; - const llvm_fn_ty = try o.builder.fnType(ret_llvm_ty, llvm_param_types[0..param_count], .normal); - const asm_fn = llvm.getInlineAsm( - llvm_fn_ty.toLlvm(&o.builder), - rendered_template.items.ptr, - rendered_template.items.len, - llvm_constraints.items.ptr, - llvm_constraints.items.len, - llvm.Bool.fromBool(is_volatile), - .False, - .ATT, - .False, - ); - const call = (try self.wip.unimplemented(ret_llvm_ty, "")).finish(self.builder.buildCall( - llvm_fn_ty.toLlvm(&o.builder), - asm_fn, - llvm_param_values.ptr, - @intCast(param_count), - .C, - .Auto, + const call = try self.wip.callAsm( + try attributes.finish(&o.builder), + llvm_fn_ty, + .{ .sideeffect = is_volatile }, + try o.builder.string(rendered_template.items), + try o.builder.string(llvm_constraints.items), + llvm_param_values[0..param_count], "", - ), &self.wip); - for (llvm_param_attrs[0..param_count], 0..) |llvm_elem_ty, i| { - if (llvm_elem_ty != .none) { - llvm.setCallElemTypeAttr(call.toLlvm(&self.wip), i, llvm_elem_ty.toLlvm(&o.builder)); - } - } + ); var ret_val = call; llvm_ret_i = 0; @@ -7287,7 +7463,7 @@ pub const FuncGen = struct { const args: [1]*llvm.Value = .{ (try o.builder.intConst(.i32, index)).toLlvm(&o.builder), }; - return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCall( + return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(.i32, &.{.i32}, .normal)).toLlvm(&o.builder), llvm_fn, &args, @@ -7308,7 +7484,7 @@ pub const FuncGen = struct { (try o.builder.intConst(.i32, index)).toLlvm(&o.builder), operand.toLlvm(&self.wip), }; - return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCall( + return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(.i32, &.{ .i32, .i32 }, .normal)).toLlvm(&o.builder), llvm_fn, &args, @@ -7425,7 +7601,7 @@ pub const FuncGen = struct { }); const llvm_fn_ty = try o.builder.fnType(llvm_ret_ty, &.{ llvm_inst_ty, llvm_inst_ty }, .normal); const llvm_fn = try fg.getIntrinsic(intrinsic_name, &.{llvm_inst_ty}); - const result_struct = (try fg.wip.unimplemented(llvm_ret_ty, "")).finish(fg.builder.buildCall( + const result_struct = (try fg.wip.unimplemented(llvm_ret_ty, "")).finish(fg.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, &[_]*llvm.Value{ lhs.toLlvm(&fg.wip), rhs.toLlvm(&fg.wip) }, @@ -7768,7 +7944,7 @@ pub const FuncGen = struct { ); const llvm_fn_ty = try o.builder.fnType(llvm_ret_ty, &.{ llvm_lhs_ty, llvm_lhs_ty }, .normal); const result_struct = (try self.wip.unimplemented(llvm_ret_ty, "")).finish( - self.builder.buildCall( + self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, &[_]*llvm.Value{ lhs.toLlvm(&self.wip), rhs.toLlvm(&self.wip) }, @@ -7818,29 +7994,23 @@ pub const FuncGen = struct { const o = self.dg.object; assert(args_vectors.len <= 3); - const llvm_fn_ty = llvm_fn.typeOf(&o.builder); - const llvm_scalar_ty = llvm_fn_ty.functionReturn(&o.builder); - var i: usize = 0; var result = result_vector; while (i < vector_len) : (i += 1) { const index_i32 = try o.builder.intValue(.i32, i); - var args: [3]*llvm.Value = undefined; + var args: [3]Builder.Value = undefined; for (args[0..args_vectors.len], args_vectors) |*arg_elem, arg_vector| { - arg_elem.* = (try self.wip.extractElement(arg_vector, index_i32, "")).toLlvm(&self.wip); + arg_elem.* = try self.wip.extractElement(arg_vector, index_i32, ""); } - const result_elem = (try self.wip.unimplemented(llvm_scalar_ty, "")).finish( - self.builder.buildCall( - llvm_fn_ty.toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - &args, - @intCast(args_vectors.len), - .C, - .Auto, - "", - ), - &self.wip, + const result_elem = try self.wip.call( + .normal, + .ccc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + args[0..args_vectors.len], + "", ); result = try self.wip.insertElement(result, result_elem, index_i32, ""); } @@ -7861,7 +8031,7 @@ pub const FuncGen = struct { }; const fn_type = try o.builder.fnType(return_type, param_types, .normal); - const f = o.llvm_module.addFunction(fn_name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder)); + const f = o.llvm_module.addFunction(fn_name.slice(&o.builder).?, fn_type.toLlvm(&o.builder)); var global = Builder.Global{ .type = fn_type, @@ -7942,20 +8112,15 @@ pub const FuncGen = struct { return self.wip.icmp(int_cond, result, zero_vector, ""); } - const llvm_fn_ty = libc_fn.typeOf(&o.builder); - const llvm_params = [2]*llvm.Value{ params[0].toLlvm(&self.wip), params[1].toLlvm(&self.wip) }; - const result = (try self.wip.unimplemented( - llvm_fn_ty.functionReturn(&o.builder), - "", - )).finish(self.builder.buildCall( - libc_fn.typeOf(&o.builder).toLlvm(&o.builder), - libc_fn.toLlvm(&o.builder), - &llvm_params, - llvm_params.len, - .C, - .Auto, + const result = try self.wip.call( + .normal, + .ccc, + .none, + libc_fn.typeOf(&o.builder), + libc_fn.toValue(&o.builder), + ¶ms, "", - ), &self.wip); + ); return self.wip.icmp(int_cond, result, zero.toValue(), ""); } @@ -8085,7 +8250,7 @@ pub const FuncGen = struct { ); var llvm_params: [params_len]*llvm.Value = undefined; for (&llvm_params, params) |*llvm_param, param| llvm_param.* = param.toLlvm(&self.wip); - return (try self.wip.unimplemented(llvm_ty, "")).finish(self.builder.buildCall( + return (try self.wip.unimplemented(llvm_ty, "")).finish(self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, &llvm_params, @@ -8311,17 +8476,16 @@ pub const FuncGen = struct { compilerRtFloatAbbrev(src_bits), compilerRtFloatAbbrev(dest_bits), }); - const llvm_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty); - const params = [1]*llvm.Value{operand.toLlvm(&self.wip)}; - return (try self.wip.unimplemented(dest_llvm_ty, "")).finish(self.builder.buildCall( - llvm_fn.typeOf(&o.builder).toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .C, - .Auto, + const libc_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty); + return self.wip.call( + .normal, + .ccc, + .none, + libc_fn.typeOf(&o.builder), + libc_fn.toValue(&o.builder), + &.{operand}, "", - ), &self.wip); + ); } } @@ -8346,17 +8510,16 @@ pub const FuncGen = struct { compilerRtFloatAbbrev(src_bits), compilerRtFloatAbbrev(dest_bits), }); - const llvm_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty); - const params = [1]*llvm.Value{operand.toLlvm(&self.wip)}; - return (try self.wip.unimplemented(dest_llvm_ty, "")).finish(self.builder.buildCall( - llvm_fn.typeOf(&o.builder).toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .C, - .Auto, + const libc_fn = try self.getLibcFunction(fn_name, &.{operand_llvm_ty}, dest_llvm_ty); + return self.wip.call( + .normal, + .ccc, + .none, + libc_fn.typeOf(&o.builder), + libc_fn.toValue(&o.builder), + &.{operand}, "", - ), &self.wip); + ); } } @@ -8657,7 +8820,7 @@ pub const FuncGen = struct { _ = inst; const o = self.dg.object; const llvm_fn = try self.getIntrinsic("llvm.trap", &.{}); - _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall( + _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(.void, &.{}, .normal)).toLlvm(&o.builder), llvm_fn, undefined, @@ -8674,7 +8837,7 @@ pub const FuncGen = struct { _ = inst; const o = self.dg.object; const llvm_fn = try self.getIntrinsic("llvm.debugtrap", &.{}); - _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall( + _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(.void, &.{}, .normal)).toLlvm(&o.builder), llvm_fn, undefined, @@ -8701,7 +8864,7 @@ pub const FuncGen = struct { const params = [_]*llvm.Value{ (try o.builder.intConst(.i32, 0)).toLlvm(&o.builder), }; - const ptr_val = (try self.wip.unimplemented(.ptr, "")).finish(self.builder.buildCall( + const ptr_val = (try self.wip.unimplemented(.ptr, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(.ptr, &.{.i32}, .normal)).toLlvm(&o.builder), llvm_fn, ¶ms, @@ -8727,7 +8890,7 @@ pub const FuncGen = struct { (try o.builder.intConst(.i32, 0)).toLlvm(&o.builder), }; const ptr_val = (try self.wip.unimplemented(llvm_fn_ty.functionReturn(&o.builder), "")).finish( - self.builder.buildCall( + self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), llvm_fn, ¶ms, @@ -9256,7 +9419,7 @@ pub const FuncGen = struct { Builder.Constant.false.toLlvm(&o.builder), }; const wrong_size_result = (try self.wip.unimplemented(llvm_operand_ty, "")).finish( - self.builder.buildCall( + self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), fn_val, ¶ms, @@ -9283,7 +9446,7 @@ pub const FuncGen = struct { const params = [_]*llvm.Value{operand.toLlvm(&self.wip)}; const wrong_size_result = (try self.wip.unimplemented(llvm_operand_ty, "")).finish( - self.builder.buildCall( + self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), fn_val, ¶ms, @@ -9331,7 +9494,7 @@ pub const FuncGen = struct { const params = [_]*llvm.Value{operand.toLlvm(&self.wip)}; const wrong_size_result = (try self.wip.unimplemented(llvm_operand_ty, "")).finish( - self.builder.buildCall( + self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), fn_val, ¶ms, @@ -9389,16 +9552,15 @@ pub const FuncGen = struct { const enum_ty = self.typeOf(un_op); const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty); - const params = [_]*llvm.Value{operand.toLlvm(&self.wip)}; - return (try self.wip.unimplemented(.i1, "")).finish(self.builder.buildCall( - llvm_fn.typeOf(&o.builder).toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .Fast, - .Auto, + return self.wip.call( + .normal, + .fastcc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + &.{operand}, "", - ), &self.wip); + ); } fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !Builder.Function.Index { @@ -9416,13 +9578,16 @@ pub const FuncGen = struct { fqn.fmt(&mod.intern_pool), }); + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); + const fn_type = try o.builder.fnType(.i1, &.{ try o.lowerType(enum_type.tag_ty.toType()), }, .normal); - const fn_val = o.llvm_module.addFunction(llvm_fn_name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder)); + const fn_val = o.llvm_module.addFunction(llvm_fn_name.slice(&o.builder).?, fn_type.toLlvm(&o.builder)); fn_val.setLinkage(.Internal); fn_val.setFunctionCallConv(.Fast); - o.addCommonFnAttributes(fn_val); + try o.addCommonFnAttributes(&attributes, fn_val); var global = Builder.Global{ .linkage = .internal, @@ -9431,6 +9596,8 @@ pub const FuncGen = struct { }; var function = Builder.Function{ .global = @enumFromInt(o.builder.globals.count()), + .call_conv = .fastcc, + .attributes = try attributes.finish(&o.builder), }; try o.builder.llvm.globals.append(self.gpa, fn_val); _ = try o.builder.addGlobal(llvm_fn_name, global); @@ -9470,19 +9637,14 @@ pub const FuncGen = struct { const enum_ty = self.typeOf(un_op); const llvm_fn = try self.getEnumTagNameFunction(enum_ty); - const llvm_fn_ty = llvm_fn.typeOf(&o.builder); - const params = [_]*llvm.Value{operand.toLlvm(&self.wip)}; - return (try self.wip.unimplemented(llvm_fn_ty.functionReturn(&o.builder), "")).finish( - self.builder.buildCall( - llvm_fn_ty.toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .Fast, - .Auto, - "", - ), - &self.wip, + return self.wip.call( + .normal, + .fastcc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + &.{operand}, + "", ); } @@ -9499,16 +9661,19 @@ pub const FuncGen = struct { const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod); const llvm_fn_name = try o.builder.fmt("__zig_tag_name_{}", .{fqn.fmt(&mod.intern_pool)}); + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); + const ret_ty = try o.lowerType(Type.slice_const_u8_sentinel_0); const usize_ty = try o.lowerType(Type.usize); const fn_type = try o.builder.fnType(ret_ty, &.{ try o.lowerType(enum_type.tag_ty.toType()), }, .normal); - const fn_val = o.llvm_module.addFunction(llvm_fn_name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder)); + const fn_val = o.llvm_module.addFunction(llvm_fn_name.slice(&o.builder).?, fn_type.toLlvm(&o.builder)); fn_val.setLinkage(.Internal); fn_val.setFunctionCallConv(.Fast); - o.addCommonFnAttributes(fn_val); + try o.addCommonFnAttributes(&attributes, fn_val); var global = Builder.Global{ .linkage = .internal, @@ -9517,6 +9682,8 @@ pub const FuncGen = struct { }; var function = Builder.Function{ .global = @enumFromInt(o.builder.globals.count()), + .call_conv = .fastcc, + .attributes = try attributes.finish(&o.builder), }; try o.builder.llvm.globals.append(self.gpa, fn_val); gop.value_ptr.* = try o.builder.addGlobal(llvm_fn_name, global); @@ -9561,7 +9728,7 @@ pub const FuncGen = struct { const slice_val = try o.builder.structValue(ret_ty, &.{ global_index.toConst(), - try o.builder.intConst(usize_ty, name.toSlice(&o.builder).?.len), + try o.builder.intConst(usize_ty, name.slice(&o.builder).?.len), }); const return_block = try wip.block(1, "Name"); @@ -9590,11 +9757,14 @@ pub const FuncGen = struct { // Function signature: fn (anyerror) bool const fn_type = try o.builder.fnType(.i1, &.{Builder.Type.err_int}, .normal); - const llvm_fn = o.llvm_module.addFunction(name.toSlice(&o.builder).?, fn_type.toLlvm(&o.builder)); + const llvm_fn = o.llvm_module.addFunction(name.slice(&o.builder).?, fn_type.toLlvm(&o.builder)); + + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); llvm_fn.setLinkage(.Internal); llvm_fn.setFunctionCallConv(.Fast); - o.addCommonFnAttributes(llvm_fn); + try o.addCommonFnAttributes(&attributes, llvm_fn); var global = Builder.Global{ .linkage = .internal, @@ -9603,6 +9773,8 @@ pub const FuncGen = struct { }; var function = Builder.Function{ .global = @enumFromInt(o.builder.globals.count()), + .call_conv = .fastcc, + .attributes = try attributes.finish(&o.builder), }; try o.builder.llvm.globals.append(self.gpa, llvm_fn); @@ -9731,18 +9903,14 @@ pub const FuncGen = struct { // accum = f(accum, vec[i]); const accum = try self.wip.load(.normal, llvm_result_ty, accum_ptr, .default, ""); const element = try self.wip.extractElement(operand_vector, i, ""); - const params = [2]*llvm.Value{ accum.toLlvm(&self.wip), element.toLlvm(&self.wip) }; - const new_accum = (try self.wip.unimplemented(llvm_result_ty, "")).finish( - self.builder.buildCall( - llvm_fn.typeOf(&o.builder).toLlvm(&o.builder), - llvm_fn.toLlvm(&o.builder), - ¶ms, - params.len, - .C, - .Auto, - "", - ), - &self.wip, + const new_accum = try self.wip.call( + .normal, + .ccc, + .none, + llvm_fn.typeOf(&o.builder), + llvm_fn.toValue(&o.builder), + &.{ accum, element }, + "", ); _ = try self.wip.store(.normal, new_accum, accum_ptr, .default); @@ -10190,7 +10358,7 @@ pub const FuncGen = struct { (try o.builder.intConst(.i32, prefetch.locality)).toLlvm(&o.builder), (try o.builder.intConst(.i32, @intFromEnum(prefetch.cache))).toLlvm(&o.builder), }; - _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCall( + _ = (try self.wip.unimplemented(.void, "")).finish(self.builder.buildCallOld( llvm_fn_ty.toLlvm(&o.builder), fn_val, ¶ms, @@ -10222,7 +10390,7 @@ pub const FuncGen = struct { const args: [0]*llvm.Value = .{}; const llvm_fn = try self.getIntrinsic(llvm_fn_name, &.{}); - return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCall( + return (try self.wip.unimplemented(.i32, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(.i32, &.{}, .normal)).toLlvm(&o.builder), llvm_fn, &args, @@ -10252,12 +10420,15 @@ pub const FuncGen = struct { const dimension = pl_op.payload; if (dimension >= 3) return o.builder.intValue(.i32, 1); + var attributes: Builder.FunctionAttributes.Wip = .{}; + defer attributes.deinit(&o.builder); + // Fetch the dispatch pointer, which points to this structure: // https://github.com/RadeonOpenCompute/ROCR-Runtime/blob/adae6c61e10d371f7cbc3d0e94ae2c070cab18a4/src/inc/hsa.h#L2913 const llvm_fn = try self.getIntrinsic("llvm.amdgcn.dispatch.ptr", &.{}); const args: [0]*llvm.Value = .{}; const llvm_ret_ty = try o.builder.ptrType(Builder.AddrSpace.amdgpu.constant); - const dispatch_ptr = (try self.wip.unimplemented(llvm_ret_ty, "")).finish(self.builder.buildCall( + const dispatch_ptr = (try self.wip.unimplemented(llvm_ret_ty, "")).finish(self.builder.buildCallOld( (try o.builder.fnType(llvm_ret_ty, &.{}, .normal)).toLlvm(&o.builder), llvm_fn, &args, @@ -10266,6 +10437,9 @@ pub const FuncGen = struct { .Auto, "", ), &self.wip); + try attributes.addRetAttr(.{ + .@"align" = comptime Builder.Alignment.fromByteUnits(4), + }, &o.builder); o.addAttrInt(dispatch_ptr.toLlvm(&self.wip), 0, "align", 4); // Load the work_group_* member from the struct as u16. @@ -10298,7 +10472,7 @@ pub const FuncGen = struct { const undef_init = try o.builder.undefConst(.ptr); // TODO: Address space const name = try o.builder.string("__zig_err_name_table"); - const error_name_table_global = o.llvm_module.addGlobal(Builder.Type.ptr.toLlvm(&o.builder), name.toSlice(&o.builder).?); + const error_name_table_global = o.llvm_module.addGlobal(Builder.Type.ptr.toLlvm(&o.builder), name.slice(&o.builder).?); error_name_table_global.setInitializer(undef_init.toLlvm(&o.builder)); error_name_table_global.setLinkage(.Private); error_name_table_global.setGlobalConstant(.True); @@ -10751,7 +10925,7 @@ pub const FuncGen = struct { ); const call = (try fg.wip.unimplemented(llvm_usize, "")).finish( - fg.builder.buildCall(fn_llvm_ty, asm_fn, &args, args.len, .C, .Auto, ""), + fg.builder.buildCallOld(fn_llvm_ty, asm_fn, &args, args.len, .C, .Auto, ""), &fg.wip, ); return call; @@ -10991,33 +11165,33 @@ fn toLlvmAtomicRmwBinOp( }; } -fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.CallConv { +fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) Builder.CallConv { return switch (cc) { - .Unspecified, .Inline, .Async => .Fast, - .C, .Naked => .C, - .Stdcall => .X86_StdCall, - .Fastcall => .X86_FastCall, + .Unspecified, .Inline, .Async => .fastcc, + .C, .Naked => .ccc, + .Stdcall => .x86_stdcallcc, + .Fastcall => .x86_fastcallcc, .Vectorcall => return switch (target.cpu.arch) { - .x86, .x86_64 => .X86_VectorCall, - .aarch64, .aarch64_be, .aarch64_32 => .AArch64_VectorCall, + .x86, .x86_64 => .x86_vectorcallcc, + .aarch64, .aarch64_be, .aarch64_32 => .aarch64_vector_pcs, else => unreachable, }, - .Thiscall => .X86_ThisCall, - .APCS => .ARM_APCS, - .AAPCS => .ARM_AAPCS, - .AAPCSVFP => .ARM_AAPCS_VFP, + .Thiscall => .x86_thiscallcc, + .APCS => .arm_apcscc, + .AAPCS => .arm_aapcscc, + .AAPCSVFP => .arm_aapcs_vfpcc, .Interrupt => return switch (target.cpu.arch) { - .x86, .x86_64 => .X86_INTR, - .avr => .AVR_INTR, - .msp430 => .MSP430_INTR, + .x86, .x86_64 => .x86_intrcc, + .avr => .avr_intrcc, + .msp430 => .msp430_intrcc, else => unreachable, }, - .Signal => .AVR_SIGNAL, - .SysV => .X86_64_SysV, - .Win64 => .Win64, + .Signal => .avr_signalcc, + .SysV => .x86_64_sysvcc, + .Win64 => .win64cc, .Kernel => return switch (target.cpu.arch) { - .nvptx, .nvptx64 => .PTX_Kernel, - .amdgcn => .AMDGPU_KERNEL, + .nvptx, .nvptx64 => .ptx_kernel, + .amdgcn => .amdgpu_kernel, else => unreachable, }, }; diff --git a/src/codegen/llvm/Builder.zig b/src/codegen/llvm/Builder.zig index 0bb65b22942b..1b25e285ff06 100644 --- a/src/codegen/llvm/Builder.zig +++ b/src/codegen/llvm/Builder.zig @@ -4,22 +4,25 @@ strip: bool, llvm: if (build_options.have_llvm) struct { context: *llvm.Context, - module: ?*llvm.Module = null, - target: ?*llvm.Target = null, - di_builder: ?*llvm.DIBuilder = null, - di_compile_unit: ?*llvm.DICompileUnit = null, - types: std.ArrayListUnmanaged(*llvm.Type) = .{}, - globals: std.ArrayListUnmanaged(*llvm.Value) = .{}, - constants: std.ArrayListUnmanaged(*llvm.Value) = .{}, + module: ?*llvm.Module, + target: ?*llvm.Target, + di_builder: ?*llvm.DIBuilder, + di_compile_unit: ?*llvm.DICompileUnit, + attribute_kind_ids: ?*[Attribute.Kind.len]c_uint, + attributes: std.ArrayListUnmanaged(*llvm.Attribute), + types: std.ArrayListUnmanaged(*llvm.Type), + globals: std.ArrayListUnmanaged(*llvm.Value), + constants: std.ArrayListUnmanaged(*llvm.Value), } else void, source_filename: String, data_layout: String, target_triple: String, +module_asm: std.ArrayListUnmanaged(u8), string_map: std.AutoArrayHashMapUnmanaged(void, void), -string_bytes: std.ArrayListUnmanaged(u8), string_indices: std.ArrayListUnmanaged(u32), +string_bytes: std.ArrayListUnmanaged(u8), types: std.AutoArrayHashMapUnmanaged(String, Type), next_unnamed_type: String, @@ -28,6 +31,11 @@ type_map: std.AutoArrayHashMapUnmanaged(void, void), type_items: std.ArrayListUnmanaged(Type.Item), type_extra: std.ArrayListUnmanaged(u32), +attributes: std.AutoArrayHashMapUnmanaged(Attribute.Storage, void), +attributes_map: std.AutoArrayHashMapUnmanaged(void, void), +attributes_indices: std.ArrayListUnmanaged(u32), +attributes_extra: std.ArrayListUnmanaged(u32), + globals: std.AutoArrayHashMapUnmanaged(String, Global), next_unnamed_global: String, next_replaced_global: String, @@ -41,6 +49,7 @@ constant_items: std.MultiArrayList(Constant.Item), constant_extra: std.ArrayListUnmanaged(u32), constant_limbs: std.ArrayListUnmanaged(std.math.big.Limb), +pub const expected_args_len = 16; pub const expected_fields_len = 32; pub const expected_gep_indices_len = 8; pub const expected_cases_len = 8; @@ -65,7 +74,7 @@ pub const String = enum(u32) { return self.toIndex() == null; } - pub fn toSlice(self: String, b: *const Builder) ?[:0]const u8 { + pub fn slice(self: String, b: *const Builder) ?[:0]const u8 { const index = self.toIndex() orelse return null; const start = b.string_indices.items[index]; const end = b.string_indices.items[index + 1]; @@ -85,20 +94,14 @@ pub const String = enum(u32) { if (comptime std.mem.indexOfNone(u8, fmt_str, "@\"")) |_| @compileError("invalid format string: '" ++ fmt_str ++ "'"); assert(data.string != .none); - const slice = data.string.toSlice(data.builder) orelse + const sentinel_slice = data.string.slice(data.builder) orelse return writer.print("{d}", .{@intFromEnum(data.string)}); - const full_slice = slice[0 .. slice.len + comptime @intFromBool( + try printEscapedString(sentinel_slice[0 .. sentinel_slice.len + comptime @intFromBool( std.mem.indexOfScalar(u8, fmt_str, '@') != null, - )]; - const need_quotes = (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) or - !isValidIdentifier(full_slice); - if (need_quotes) try writer.writeByte('"'); - for (full_slice) |character| switch (character) { - '\\' => try writer.writeAll("\\\\"), - ' '...'"' - 1, '"' + 1...'\\' - 1, '\\' + 1...'~' => try writer.writeByte(character), - else => try writer.print("\\{X:0>2}", .{character}), - }; - if (need_quotes) try writer.writeByte('"'); + )], if (comptime std.mem.indexOfScalar(u8, fmt_str, '"')) |_| + .always_quote + else + .quote_unless_valid_identifier, writer); } pub fn fmt(self: String, builder: *const Builder) std.fmt.Formatter(format) { return .{ .data = .{ .string = self, .builder = builder } }; @@ -108,6 +111,7 @@ pub const String = enum(u32) { return @enumFromInt(@as(u32, @intCast((index orelse return .none) + @intFromEnum(String.empty)))); } + fn toIndex(self: String) ?usize { return std.math.sub(u32, @intFromEnum(self), @intFromEnum(String.empty)) catch null; } @@ -118,7 +122,7 @@ pub const String = enum(u32) { return @truncate(std.hash.Wyhash.hash(0, key)); } pub fn eql(ctx: Adapter, lhs_key: []const u8, _: void, rhs_index: usize) bool { - return std.mem.eql(u8, lhs_key, String.fromIndex(rhs_index).toSlice(ctx.builder).?); + return std.mem.eql(u8, lhs_key, String.fromIndex(rhs_index).slice(ctx.builder).?); } }; }; @@ -290,6 +294,17 @@ pub const Type = enum(u32) { }; } + pub fn pointerAddrSpace(self: Type, builder: *const Builder) AddrSpace { + switch (self) { + .ptr => return .default, + else => { + const item = builder.type_items.items[@intFromEnum(self)]; + assert(item.tag == .pointer); + return @enumFromInt(item.data); + }, + } + } + pub fn isFunction(self: Type, builder: *const Builder) bool { return switch (self.tag(builder)) { .function, .vararg_function => true, @@ -606,7 +621,7 @@ pub const Type = enum(u32) { var extra = data.builder.typeExtraDataTrail(Type.Target, item.data); const types = extra.trail.next(extra.data.types_len, Type, data.builder); const ints = extra.trail.next(extra.data.ints_len, u32, data.builder); - try writer.print("t{s}", .{extra.data.name.toSlice(data.builder).?}); + try writer.print("t{s}", .{extra.data.name.slice(data.builder).?}); for (types) |ty| try writer.print("_{m}", .{ty.fmt(data.builder)}); for (ints) |int| try writer.print("_{d}", .{int}); try writer.writeByte('t'); @@ -641,7 +656,7 @@ pub const Type = enum(u32) { .named_structure => { const extra = data.builder.typeExtraData(Type.NamedStructure, item.data); try writer.writeAll("s_"); - if (extra.id.toSlice(data.builder)) |id| try writer.writeAll(id); + if (extra.id.slice(data.builder)) |id| try writer.writeAll(id); }, } return; @@ -823,6 +838,849 @@ pub const Type = enum(u32) { } }; +pub const Attribute = union(Kind) { + // Parameter Attributes + zeroext, + signext, + inreg, + byval: Type, + byref: Type, + preallocated: Type, + inalloca: Type, + sret: Type, + elementtype: Type, + @"align": Alignment, + @"noalias", + nocapture, + nofree, + nest, + returned, + nonnull, + dereferenceable: u32, + dereferenceable_or_null: u32, + swiftself, + swiftasync, + swifterror, + immarg, + noundef, + nofpclass: FpClass, + alignstack: Alignment, + allocalign, + allocptr, + readnone, + readonly, + writeonly, + + // Function Attributes + //alignstack: Alignment, + allockind: AllocKind, + allocsize: AllocSize, + alwaysinline, + builtin, + cold, + convergent, + disable_sanitizer_information, + fn_ret_thunk_extern, + hot, + inlinehint, + jumptable, + memory: Memory, + minsize, + naked, + nobuiltin, + nocallback, + noduplicate, + //nofree, + noimplicitfloat, + @"noinline", + nomerge, + nonlazybind, + noprofile, + skipprofile, + noredzone, + noreturn, + norecurse, + willreturn, + nosync, + nounwind, + nosanitize_bounds, + nosanitize_coverage, + null_pointer_is_valid, + optforfuzzing, + optnone, + optsize, + //preallocated: Type, + returns_twice, + safestack, + sanitize_address, + sanitize_memory, + sanitize_thread, + sanitize_hwaddress, + sanitize_memtag, + speculative_load_hardening, + speculatable, + ssp, + sspstrong, + sspreq, + strictfp, + uwtable: UwTable, + nocf_check, + shadowcallstack, + mustprogress, + vscale_range: VScaleRange, + + // Global Attributes + no_sanitize_address, + no_sanitize_hwaddress, + //sanitize_memtag, + sanitize_address_dyninit, + + string: struct { kind: String, value: String }, + none: noreturn, + + pub const Index = enum(u32) { + _, + + pub fn getKind(self: Index, builder: *const Builder) Kind { + return self.toStorage(builder).kind; + } + + pub fn toAttribute(self: Index, builder: *const Builder) Attribute { + @setEvalBranchQuota(2_000); + const storage = self.toStorage(builder); + if (storage.kind.toString()) |kind| return .{ .string = .{ + .kind = kind, + .value = @enumFromInt(storage.value), + } } else return switch (storage.kind) { + inline .zeroext, + .signext, + .inreg, + .byval, + .byref, + .preallocated, + .inalloca, + .sret, + .elementtype, + .@"align", + .@"noalias", + .nocapture, + .nofree, + .nest, + .returned, + .nonnull, + .dereferenceable, + .dereferenceable_or_null, + .swiftself, + .swiftasync, + .swifterror, + .immarg, + .noundef, + .nofpclass, + .alignstack, + .allocalign, + .allocptr, + .readnone, + .readonly, + .writeonly, + //.alignstack, + .allockind, + .allocsize, + .alwaysinline, + .builtin, + .cold, + .convergent, + .disable_sanitizer_information, + .fn_ret_thunk_extern, + .hot, + .inlinehint, + .jumptable, + .memory, + .minsize, + .naked, + .nobuiltin, + .nocallback, + .noduplicate, + //.nofree, + .noimplicitfloat, + .@"noinline", + .nomerge, + .nonlazybind, + .noprofile, + .skipprofile, + .noredzone, + .noreturn, + .norecurse, + .willreturn, + .nosync, + .nounwind, + .nosanitize_bounds, + .nosanitize_coverage, + .null_pointer_is_valid, + .optforfuzzing, + .optnone, + .optsize, + //.preallocated, + .returns_twice, + .safestack, + .sanitize_address, + .sanitize_memory, + .sanitize_thread, + .sanitize_hwaddress, + .sanitize_memtag, + .speculative_load_hardening, + .speculatable, + .ssp, + .sspstrong, + .sspreq, + .strictfp, + .uwtable, + .nocf_check, + .shadowcallstack, + .mustprogress, + .vscale_range, + .no_sanitize_address, + .no_sanitize_hwaddress, + .sanitize_address_dyninit, + => |kind| { + const field = @typeInfo(Attribute).Union.fields[@intFromEnum(kind)]; + comptime assert(std.mem.eql(u8, @tagName(kind), field.name)); + return @unionInit(Attribute, field.name, switch (field.type) { + void => {}, + u32 => storage.value, + Alignment, String, Type, UwTable => @enumFromInt(storage.value), + AllocKind, AllocSize, FpClass, Memory, VScaleRange => @bitCast(storage.value), + else => @compileError("bad payload type: " ++ @typeName(field.type)), + }); + }, + .string, .none => unreachable, + _ => unreachable, + }; + } + + const FormatData = struct { + attribute_index: Index, + builder: *const Builder, + }; + fn format( + data: FormatData, + comptime fmt_str: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + if (comptime std.mem.indexOfNone(u8, fmt_str, "\"#")) |_| + @compileError("invalid format string: '" ++ fmt_str ++ "'"); + const attribute = data.attribute_index.toAttribute(data.builder); + switch (attribute) { + .zeroext, + .signext, + .inreg, + .@"noalias", + .nocapture, + .nofree, + .nest, + .returned, + .nonnull, + .swiftself, + .swiftasync, + .swifterror, + .immarg, + .noundef, + .allocalign, + .allocptr, + .readnone, + .readonly, + .writeonly, + .alwaysinline, + .builtin, + .cold, + .convergent, + .disable_sanitizer_information, + .fn_ret_thunk_extern, + .hot, + .inlinehint, + .jumptable, + .minsize, + .naked, + .nobuiltin, + .nocallback, + .noduplicate, + .noimplicitfloat, + .@"noinline", + .nomerge, + .nonlazybind, + .noprofile, + .skipprofile, + .noredzone, + .noreturn, + .norecurse, + .willreturn, + .nosync, + .nounwind, + .nosanitize_bounds, + .nosanitize_coverage, + .null_pointer_is_valid, + .optforfuzzing, + .optnone, + .optsize, + .returns_twice, + .safestack, + .sanitize_address, + .sanitize_memory, + .sanitize_thread, + .sanitize_hwaddress, + .sanitize_memtag, + .speculative_load_hardening, + .speculatable, + .ssp, + .sspstrong, + .sspreq, + .strictfp, + .nocf_check, + .shadowcallstack, + .mustprogress, + .no_sanitize_address, + .no_sanitize_hwaddress, + .sanitize_address_dyninit, + => try writer.print(" {s}", .{@tagName(attribute)}), + .byval, + .byref, + .preallocated, + .inalloca, + .sret, + .elementtype, + => |ty| try writer.print(" {s}({%})", .{ @tagName(attribute), ty.fmt(data.builder) }), + .@"align" => |alignment| try writer.print("{}", .{alignment}), + .dereferenceable, + .dereferenceable_or_null, + => |size| try writer.print(" {s}({d})", .{ @tagName(attribute), size }), + .nofpclass => |fpclass| { + const Int = @typeInfo(FpClass).Struct.backing_integer.?; + try writer.print("{s}(", .{@tagName(attribute)}); + var any = false; + var remaining: Int = @bitCast(fpclass); + inline for (@typeInfo(FpClass).Struct.decls) |decl| { + if (!decl.is_pub) continue; + const pattern: Int = @bitCast(@field(FpClass, decl.name)); + if (remaining & pattern == pattern) { + if (!any) { + try writer.writeByte(' '); + any = true; + } + try writer.writeAll(decl.name); + remaining &= ~pattern; + } + } + try writer.writeByte(')'); + }, + .alignstack => |alignment| try writer.print( + if (comptime std.mem.indexOfScalar(u8, fmt_str, '#') != null) + "{s}={d}" + else + "{s}({d})", + .{ @tagName(attribute), alignment.toByteUnits() orelse return }, + ), + .allockind => |allockind| { + try writer.print("{s}(\"", .{@tagName(attribute)}); + var any = false; + inline for (@typeInfo(AllocKind).Struct.fields) |field| { + if (comptime std.mem.eql(u8, field.name, "_")) continue; + if (@field(allockind, field.name)) { + if (!any) { + try writer.writeByte(','); + any = true; + } + try writer.writeAll(field.name); + } + } + try writer.writeAll("\")"); + }, + .allocsize => |allocsize| { + try writer.print("{s}({d}", .{ @tagName(attribute), allocsize.elem_size }); + if (allocsize.num_elems != AllocSize.none) + try writer.print(",{d}", .{allocsize.num_elems}); + try writer.writeByte(')'); + }, + .memory => |memory| try writer.print("{s}({s}, argmem: {s}, inaccessiblemem: {s})", .{ + @tagName(attribute), + @tagName(memory.other), + @tagName(memory.argmem), + @tagName(memory.inaccessiblemem), + }), + .uwtable => |uwtable| if (uwtable != .none) { + try writer.writeAll(@tagName(attribute)); + if (uwtable != UwTable.default) try writer.print("({s})", .{@tagName(uwtable)}); + }, + .vscale_range => |vscale_range| try writer.print("{s}({d},{d})", .{ + @tagName(attribute), + vscale_range.min.toByteUnits().?, + vscale_range.max.toByteUnits() orelse 0, + }), + .string => |string_attr| if (comptime std.mem.indexOfScalar(u8, fmt_str, '"') != null) { + try writer.print(" {\"}", .{string_attr.kind.fmt(data.builder)}); + if (string_attr.value != .empty) + try writer.print("={\"}", .{string_attr.value.fmt(data.builder)}); + }, + .none => unreachable, + } + } + pub fn fmt(self: Index, builder: *const Builder) std.fmt.Formatter(format) { + return .{ .data = .{ .attribute_index = self, .builder = builder } }; + } + + fn toStorage(self: Index, builder: *const Builder) Storage { + return builder.attributes.keys()[@intFromEnum(self)]; + } + + fn toLlvm(self: Index, builder: *const Builder) *llvm.Attribute { + assert(builder.useLibLlvm()); + return builder.llvm.attributes.items[@intFromEnum(self)]; + } + }; + + pub const Kind = enum(u32) { + // Parameter Attributes + zeroext, + signext, + inreg, + byval, + byref, + preallocated, + inalloca, + sret, + elementtype, + @"align", + @"noalias", + nocapture, + nofree, + nest, + returned, + nonnull, + dereferenceable, + dereferenceable_or_null, + swiftself, + swiftasync, + swifterror, + immarg, + noundef, + nofpclass, + alignstack, + allocalign, + allocptr, + readnone, + readonly, + writeonly, + + // Function Attributes + //alignstack, + allockind, + allocsize, + alwaysinline, + builtin, + cold, + convergent, + disable_sanitizer_information, + fn_ret_thunk_extern, + hot, + inlinehint, + jumptable, + memory, + minsize, + naked, + nobuiltin, + nocallback, + noduplicate, + //nofree, + noimplicitfloat, + @"noinline", + nomerge, + nonlazybind, + noprofile, + skipprofile, + noredzone, + noreturn, + norecurse, + willreturn, + nosync, + nounwind, + nosanitize_bounds, + nosanitize_coverage, + null_pointer_is_valid, + optforfuzzing, + optnone, + optsize, + //preallocated, + returns_twice, + safestack, + sanitize_address, + sanitize_memory, + sanitize_thread, + sanitize_hwaddress, + sanitize_memtag, + speculative_load_hardening, + speculatable, + ssp, + sspstrong, + sspreq, + strictfp, + uwtable, + nocf_check, + shadowcallstack, + mustprogress, + vscale_range, + + // Global Attributes + no_sanitize_address, + no_sanitize_hwaddress, + //sanitize_memtag, + sanitize_address_dyninit, + + string = std.math.maxInt(u31) - 1, + none = std.math.maxInt(u31), + _, + + pub const len = @typeInfo(Kind).Enum.fields.len - 2; + + pub fn fromString(str: String) Kind { + assert(!str.isAnon()); + return @enumFromInt(@intFromEnum(str)); + } + + fn toString(self: Kind) ?String { + const str: String = @enumFromInt(@intFromEnum(self)); + return if (str.isAnon()) null else str; + } + }; + + pub const FpClass = packed struct(u32) { + signaling_nan: bool = false, + quiet_nan: bool = false, + negative_infinity: bool = false, + negative_normal: bool = false, + negative_subnormal: bool = false, + negative_zero: bool = false, + positive_zero: bool = false, + positive_subnormal: bool = false, + positive_normal: bool = false, + positive_infinity: bool = false, + _: u22 = 0, + + pub const all = FpClass{ + .signaling_nan = true, + .quiet_nan = true, + .negative_infinity = true, + .negative_normal = true, + .negative_subnormal = true, + .negative_zero = true, + .positive_zero = true, + .positive_subnormal = true, + .positive_normal = true, + .positive_infinity = true, + }; + + pub const nan = FpClass{ .signaling_nan = true, .quiet_nan = true }; + pub const snan = FpClass{ .signaling_nan = true }; + pub const qnan = FpClass{ .quiet_nan = true }; + + pub const inf = FpClass{ .negative_infinity = true, .positive_infinity = true }; + pub const ninf = FpClass{ .negative_infinity = true }; + pub const pinf = FpClass{ .positive_infinity = true }; + + pub const zero = FpClass{ .positive_zero = true, .negative_zero = true }; + pub const nzero = FpClass{ .negative_zero = true }; + pub const pzero = FpClass{ .positive_zero = true }; + + pub const sub = FpClass{ .positive_subnormal = true, .negative_subnormal = true }; + pub const nsub = FpClass{ .negative_subnormal = true }; + pub const psub = FpClass{ .positive_subnormal = true }; + + pub const norm = FpClass{ .positive_normal = true, .negative_normal = true }; + pub const nnorm = FpClass{ .negative_normal = true }; + pub const pnorm = FpClass{ .positive_normal = true }; + }; + + pub const AllocKind = packed struct(u32) { + alloc: bool, + realloc: bool, + free: bool, + uninitialized: bool, + zeroed: bool, + aligned: bool, + _: u26 = 0, + }; + + pub const AllocSize = packed struct(u32) { + elem_size: u16, + num_elems: u16, + + pub const none = std.math.maxInt(u16); + + fn toLlvm(self: AllocSize) packed struct(u64) { num_elems: u32, elem_size: u32 } { + return .{ .num_elems = switch (self.num_elems) { + else => self.num_elems, + none => std.math.maxInt(u32), + }, .elem_size = self.elem_size }; + } + }; + + pub const Memory = packed struct(u32) { + argmem: Effect, + inaccessiblemem: Effect, + other: Effect, + _: u26 = 0, + + pub const Effect = enum(u2) { none, read, write, readwrite }; + }; + + pub const UwTable = enum(u32) { + none, + sync, + @"async", + + pub const default = UwTable.@"async"; + }; + + pub const VScaleRange = packed struct(u32) { + min: Alignment, + max: Alignment, + _: u20 = 0, + + fn toLlvm(self: VScaleRange) packed struct(u64) { max: u32, min: u32 } { + return .{ + .max = @intCast(self.max.toByteUnits() orelse 0), + .min = @intCast(self.min.toByteUnits().?), + }; + } + }; + + pub fn getKind(self: Attribute) Kind { + return switch (self) { + else => self, + .string => |string_attr| Kind.fromString(string_attr.kind), + }; + } + + const Storage = extern struct { + kind: Kind, + value: u32, + }; + + fn toStorage(self: Attribute) Storage { + return switch (self) { + inline else => |value| .{ .kind = @as(Kind, self), .value = switch (@TypeOf(value)) { + void => 0, + u32 => value, + Alignment, String, Type, UwTable => @intFromEnum(value), + AllocKind, AllocSize, FpClass, Memory, VScaleRange => @bitCast(value), + else => @compileError("bad payload type: " ++ @typeName(@TypeOf(value))), + } }, + .string => |string_attr| .{ + .kind = Kind.fromString(string_attr.kind), + .value = @intFromEnum(string_attr.value), + }, + .none => unreachable, + }; + } +}; + +pub const Attributes = enum(u32) { + none, + _, + + pub fn slice(self: Attributes, builder: *const Builder) []const Attribute.Index { + const start = builder.attributes_indices.items[@intFromEnum(self)]; + const end = builder.attributes_indices.items[@intFromEnum(self) + 1]; + return @ptrCast(builder.attributes_extra.items[start..end]); + } + + const FormatData = struct { + attributes: Attributes, + builder: *const Builder, + }; + fn format( + data: FormatData, + comptime fmt_str: []const u8, + fmt_opts: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + for (data.attributes.slice(data.builder)) |attribute_index| try Attribute.Index.format(.{ + .attribute_index = attribute_index, + .builder = data.builder, + }, fmt_str, fmt_opts, writer); + } + pub fn fmt(self: Attributes, builder: *const Builder) std.fmt.Formatter(format) { + return .{ .data = .{ .attributes = self, .builder = builder } }; + } +}; + +pub const FunctionAttributes = enum(u32) { + none, + _, + + const function_index = 0; + const return_index = 1; + const params_index = 2; + + pub const Wip = struct { + maps: Maps = .{}, + + const Map = std.AutoArrayHashMapUnmanaged(Attribute.Kind, Attribute.Index); + const Maps = std.ArrayListUnmanaged(Map); + + pub fn deinit(self: *Wip, builder: *const Builder) void { + for (self.maps.items) |*map| map.deinit(builder.gpa); + self.maps.deinit(builder.gpa); + self.* = undefined; + } + + pub fn addFnAttr(self: *Wip, attribute: Attribute, builder: *Builder) Allocator.Error!void { + try self.addAttr(function_index, attribute, builder); + } + + pub fn addFnAttrIndex( + self: *Wip, + attribute_index: Attribute.Index, + builder: *const Builder, + ) Allocator.Error!void { + try self.addAttrIndex(function_index, attribute_index, builder); + } + + pub fn removeFnAttr(self: *Wip, attribute_kind: Attribute.Kind) Allocator.Error!bool { + return self.removeAttr(function_index, attribute_kind); + } + + pub fn addRetAttr(self: *Wip, attribute: Attribute, builder: *Builder) Allocator.Error!void { + try self.addAttr(return_index, attribute, builder); + } + + pub fn addRetAttrIndex( + self: *Wip, + attribute_index: Attribute.Index, + builder: *const Builder, + ) Allocator.Error!void { + try self.addAttrIndex(return_index, attribute_index, builder); + } + + pub fn removeRetAttr(self: *Wip, attribute_kind: Attribute.Kind) Allocator.Error!bool { + return self.removeAttr(return_index, attribute_kind); + } + + pub fn addParamAttr( + self: *Wip, + param_index: usize, + attribute: Attribute, + builder: *Builder, + ) Allocator.Error!void { + try self.addAttr(params_index + param_index, attribute, builder); + } + + pub fn addParamAttrIndex( + self: *Wip, + param_index: usize, + attribute_index: Attribute.Index, + builder: *const Builder, + ) Allocator.Error!void { + try self.addAttrIndex(params_index + param_index, attribute_index, builder); + } + + pub fn removeParamAttr( + self: *Wip, + param_index: usize, + attribute_kind: Attribute.Kind, + ) Allocator.Error!bool { + return self.removeAttr(params_index + param_index, attribute_kind); + } + + pub fn finish(self: *const Wip, builder: *Builder) Allocator.Error!FunctionAttributes { + const attributes = try builder.gpa.alloc(Attributes, self.maps.items.len); + defer builder.gpa.free(attributes); + for (attributes, self.maps.items) |*attribute, map| + attribute.* = try builder.attrs(map.values()); + return builder.fnAttrs(attributes); + } + + fn addAttr( + self: *Wip, + index: usize, + attribute: Attribute, + builder: *Builder, + ) Allocator.Error!void { + const map = try self.getOrPutMap(builder.gpa, index); + try map.put(builder.gpa, attribute.getKind(), try builder.attr(attribute)); + } + + fn addAttrIndex( + self: *Wip, + index: usize, + attribute_index: Attribute.Index, + builder: *const Builder, + ) Allocator.Error!void { + const map = try self.getOrPutMap(builder.gpa, index); + try map.put(builder.gpa, attribute_index.getKind(builder), attribute_index); + } + + fn removeAttr(self: *Wip, index: usize, attribute_kind: Attribute.Kind) Allocator.Error!bool { + const map = self.getMap(index) orelse return false; + return map.swapRemove(attribute_kind); + } + + fn getOrPutMap(self: *Wip, allocator: Allocator, index: usize) Allocator.Error!*Map { + if (index >= self.maps.items.len) + try self.maps.appendNTimes(allocator, .{}, index + 1 - self.maps.items.len); + return &self.maps.items[index]; + } + + fn getMap(self: *Wip, index: usize) ?*Map { + return if (index >= self.maps.items.len) null else &self.maps.items[index]; + } + + fn ensureTotalLength(self: *Wip, new_len: usize) Allocator.Error!void { + try self.maps.appendNTimes( + .{}, + std.math.sub(usize, new_len, self.maps.items.len) catch return, + ); + } + }; + + pub fn func(self: FunctionAttributes, builder: *const Builder) Attributes { + return self.get(function_index, builder); + } + + pub fn ret(self: FunctionAttributes, builder: *const Builder) Attributes { + return self.get(return_index, builder); + } + + pub fn param(self: FunctionAttributes, param_index: usize, builder: *const Builder) Attributes { + return self.get(params_index + param_index, builder); + } + + pub fn toWip(self: FunctionAttributes, builder: *const Builder) Allocator.Error!Wip { + var wip: Wip = .{}; + errdefer wip.deinit(builder); + const attributes_slice = self.slice(builder); + try wip.maps.ensureTotalCapacityPrecise(builder.gpa, attributes_slice.len); + for (attributes_slice) |attributes| { + const map = wip.maps.addOneAssumeCapacity(); + map.* = .{}; + const attribute_slice = attributes.slice(builder); + try map.ensureTotalCapacity(builder.gpa, attribute_slice.len); + for (attributes.slice(builder)) |attribute| + map.putAssumeCapacityNoClobber(attribute.getKind(builder), attribute); + } + return wip; + } + + fn get(self: FunctionAttributes, index: usize, builder: *const Builder) Attributes { + const attribute_slice = self.slice(builder); + return if (index < attribute_slice.len) attribute_slice[index] else .none; + } + + fn slice(self: FunctionAttributes, builder: *const Builder) []const Attributes { + const start = builder.attributes_indices.items[@intFromEnum(self)]; + const end = builder.attributes_indices.items[@intFromEnum(self) + 1]; + return @ptrCast(builder.attributes_extra.items[start..end]); + } +}; + pub const Linkage = enum { external, private, @@ -1053,6 +1911,127 @@ pub const Alignment = enum(u6) { } }; +pub const CallConv = enum(u10) { + ccc, + + fastcc = 8, + coldcc, + ghccc, + + webkit_jscc = 12, + anyregcc, + preserve_mostcc, + preserve_allcc, + swiftcc, + cxx_fast_tlscc, + tailcc, + cfguard_checkcc, + swifttailcc, + + x86_stdcallcc = 64, + x86_fastcallcc, + arm_apcscc, + arm_aapcscc, + arm_aapcs_vfpcc, + msp430_intrcc, + x86_thiscallcc, + ptx_kernel, + ptx_device, + + spir_func = 75, + spir_kernel, + intel_ocl_bicc, + x86_64_sysvcc, + win64cc, + x86_vectorcallcc, + hhvmcc, + hhvm_ccc, + x86_intrcc, + avr_intrcc, + avr_signalcc, + + amdgpu_vs = 87, + amdgpu_gs, + amdgpu_ps, + amdgpu_cs, + amdgpu_kernel, + x86_regcallcc, + amdgpu_hs, + + amdgpu_ls = 95, + amdgpu_es, + aarch64_vector_pcs, + aarch64_sve_vector_pcs, + + amdgpu_gfx = 100, + + aarch64_sme_preservemost_from_x0 = 102, + aarch64_sme_preservemost_from_x2, + + _, + + pub const default = CallConv.ccc; + + pub fn format( + self: CallConv, + comptime _: []const u8, + _: std.fmt.FormatOptions, + writer: anytype, + ) @TypeOf(writer).Error!void { + switch (self) { + default => {}, + .fastcc, + .coldcc, + .ghccc, + .webkit_jscc, + .anyregcc, + .preserve_mostcc, + .preserve_allcc, + .swiftcc, + .cxx_fast_tlscc, + .tailcc, + .cfguard_checkcc, + .swifttailcc, + .x86_stdcallcc, + .x86_fastcallcc, + .arm_apcscc, + .arm_aapcscc, + .arm_aapcs_vfpcc, + .msp430_intrcc, + .x86_thiscallcc, + .ptx_kernel, + .ptx_device, + .spir_func, + .spir_kernel, + .intel_ocl_bicc, + .x86_64_sysvcc, + .win64cc, + .x86_vectorcallcc, + .hhvmcc, + .hhvm_ccc, + .x86_intrcc, + .avr_intrcc, + .avr_signalcc, + .amdgpu_vs, + .amdgpu_gs, + .amdgpu_ps, + .amdgpu_cs, + .amdgpu_kernel, + .x86_regcallcc, + .amdgpu_hs, + .amdgpu_ls, + .amdgpu_es, + .aarch64_vector_pcs, + .aarch64_sve_vector_pcs, + .amdgpu_gfx, + .aarch64_sme_preservemost_from_x0, + .aarch64_sme_preservemost_from_x2, + => try writer.print(" {s}", .{@tagName(self)}), + _ => try writer.print(" cc{d}", .{@intFromEnum(self)}), + } + } +}; + pub const Global = struct { linkage: Linkage = .external, preemption: Preemption = .dso_preemptable, @@ -1170,7 +2149,7 @@ pub const Global = struct { fn updateName(self: Index, builder: *const Builder) void { if (!builder.useLibLlvm()) return; const index = @intFromEnum(self.unwrap(builder)); - const name_slice = self.name(builder).toSlice(builder) orelse ""; + const name_slice = self.name(builder).slice(builder) orelse ""; builder.llvm.globals.items[index].setValueName2(name_slice.ptr, name_slice.len); } @@ -1301,6 +2280,8 @@ pub const Variable = struct { pub const Function = struct { global: Global.Index, + call_conv: CallConv = CallConv.default, + attributes: FunctionAttributes = .none, section: String = .none, alignment: Alignment = .default, blocks: []const Block = &.{}, @@ -1364,6 +2345,8 @@ pub const Function = struct { block, br, br_cond, + call, + @"call fast", extractelement, extractvalue, fadd, @@ -1454,6 +2437,10 @@ pub const Function = struct { @"mul nsw", @"mul nuw", @"mul nuw nsw", + @"musttail call", + @"musttail call fast", + @"notail call", + @"notail call fast", @"or", phi, @"phi fast", @@ -1481,6 +2468,8 @@ pub const Function = struct { @"sub nuw", @"sub nuw nsw", @"switch", + @"tail call", + @"tail call fast", trunc, udiv, @"udiv exact", @@ -1511,6 +2500,7 @@ pub const Function = struct { .br_cond, .ret, .@"ret void", + .@"switch", .@"unreachable", => true, else => false, @@ -1528,8 +2518,19 @@ pub const Function = struct { .@"store atomic", .@"store atomic volatile", .@"store volatile", + .@"switch", .@"unreachable", => false, + .call, + .@"call fast", + .@"musttail call", + .@"musttail call fast", + .@"notail call", + .@"notail call fast", + .@"tail call", + .@"tail call fast", + .unimplemented, + => self.typeOfWip(wip) != .void, else => true, }; } @@ -1625,6 +2626,15 @@ pub const Function = struct { .@"switch", .@"unreachable", => .none, + .call, + .@"call fast", + .@"musttail call", + .@"musttail call fast", + .@"notail call", + .@"notail call fast", + .@"tail call", + .@"tail call fast", + => wip.extraData(Call, instruction.data).ty.functionReturn(wip.builder), .extractelement => wip.extraData(ExtractElement, instruction.data) .val.typeOfWip(wip).childType(wip.builder), .extractvalue => { @@ -1813,6 +2823,15 @@ pub const Function = struct { .@"switch", .@"unreachable", => .none, + .call, + .@"call fast", + .@"musttail call", + .@"musttail call fast", + .@"notail call", + .@"notail call fast", + .@"tail call", + .@"tail call fast", + => function.extraData(Call, instruction.data).ty.functionReturn(builder), .extractelement => function.extraData(ExtractElement, instruction.data) .val.typeOf(function_index, builder).childType(builder), .extractvalue => { @@ -1955,7 +2974,7 @@ pub const Function = struct { return if (wip.builder.strip) "" else - wip.names.items[@intFromEnum(self)].toSlice(wip.builder).?; + wip.names.items[@intFromEnum(self)].slice(wip.builder).?; } }; @@ -2063,6 +3082,30 @@ pub const Function = struct { rhs: Value, }; + pub const Call = struct { + info: Info, + attributes: FunctionAttributes, + ty: Type, + callee: Value, + args_len: u32, + //args: [args_len]Value, + + pub const Kind = enum { + normal, + fast, + musttail, + musttail_fast, + notail, + notail_fast, + tail, + tail_fast, + }; + pub const Info = packed struct(u32) { + call_conv: CallConv, + _: u22 = undefined, + }; + }; + pub const VaArg = struct { list: Value, type: Type, @@ -2117,8 +3160,17 @@ pub const Function = struct { inline for (fields, self.extra[index..][0..fields.len]) |field, value| @field(result, field.name) = switch (field.type) { u32 => value, - Alignment, AtomicOrdering, Block.Index, Type, Value => @enumFromInt(value), - MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value), + Alignment, + AtomicOrdering, + Block.Index, + FunctionAttributes, + Type, + Value, + => @enumFromInt(value), + MemoryAccessInfo, + Instruction.Alloca.Info, + Instruction.Call.Info, + => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ @@ -2243,7 +3295,7 @@ pub const WipFunction = struct { if (self.builder.useLibLlvm()) self.llvm.blocks.appendAssumeCapacity( self.builder.llvm.context.appendBasicBlock( self.function.toLlvm(self.builder), - final_name.toSlice(self.builder).?, + final_name.slice(self.builder).?, ), ); return index; @@ -2755,7 +3807,7 @@ pub const WipFunction = struct { @intFromEnum(addr_space), instruction.llvmName(self), ); - if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a)); + if (alignment.toByteUnits()) |bytes| llvm_instruction.setAlignment(@intCast(bytes)); self.llvm.instructions.appendAssumeCapacity(llvm_instruction); } return instruction.toValue(); @@ -2811,7 +3863,7 @@ pub const WipFunction = struct { instruction.llvmName(self), ); if (ordering != .none) llvm_instruction.setOrdering(@enumFromInt(@intFromEnum(ordering))); - if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a)); + if (alignment.toByteUnits()) |bytes| llvm_instruction.setAlignment(@intCast(bytes)); self.llvm.instructions.appendAssumeCapacity(llvm_instruction); } return instruction.toValue(); @@ -2865,7 +3917,7 @@ pub const WipFunction = struct { .@"volatile" => llvm_instruction.setVolatile(.True), } if (ordering != .none) llvm_instruction.setOrdering(@enumFromInt(@intFromEnum(ordering))); - if (alignment.toByteUnits()) |a| llvm_instruction.setAlignment(@intCast(a)); + if (alignment.toByteUnits()) |bytes| llvm_instruction.setAlignment(@intCast(bytes)); self.llvm.instructions.appendAssumeCapacity(llvm_instruction); } return instruction; @@ -3162,6 +4214,102 @@ pub const WipFunction = struct { return self.selectTag(.@"select fast", cond, lhs, rhs, name); } + pub fn call( + self: *WipFunction, + kind: Instruction.Call.Kind, + call_conv: CallConv, + function_attributes: FunctionAttributes, + ty: Type, + callee: Value, + args: []const Value, + name: []const u8, + ) Allocator.Error!Value { + const ret_ty = ty.functionReturn(self.builder); + assert(ty.isFunction(self.builder)); + assert(callee.typeOfWip(self).isPointer(self.builder)); + const params = ty.functionParameters(self.builder); + for (params, args[0..params.len]) |param, arg_val| assert(param == arg_val.typeOfWip(self)); + + try self.ensureUnusedExtraCapacity(1, Instruction.Call, args.len); + const instruction = try self.addInst(switch (ret_ty) { + .void => null, + else => name, + }, .{ + .tag = .call, + .data = self.addExtraAssumeCapacity(Instruction.Call{ + .info = .{ .call_conv = call_conv }, + .attributes = function_attributes, + .ty = ty, + .callee = callee, + .args_len = @intCast(args.len), + }), + }); + self.extra.appendSliceAssumeCapacity(@ptrCast(args)); + if (self.builder.useLibLlvm()) { + const ExpectedContents = [expected_args_len]*llvm.Value; + var stack align(@alignOf(ExpectedContents)) = + std.heap.stackFallback(@sizeOf(ExpectedContents), self.builder.gpa); + const allocator = stack.get(); + + const llvm_args = try allocator.alloc(*llvm.Value, args.len); + defer allocator.free(llvm_args); + for (llvm_args, args) |*llvm_arg, arg_val| llvm_arg.* = arg_val.toLlvm(self); + + switch (kind) { + .normal, + .musttail, + .notail, + .tail, + => self.llvm.builder.setFastMath(false), + .fast, + .musttail_fast, + .notail_fast, + .tail_fast, + => self.llvm.builder.setFastMath(true), + } + const llvm_instruction = self.llvm.builder.buildCall( + ty.toLlvm(self.builder), + callee.toLlvm(self), + llvm_args.ptr, + @intCast(llvm_args.len), + switch (ret_ty) { + .void => "", + else => instruction.llvmName(self), + }, + ); + llvm_instruction.setInstructionCallConv(@enumFromInt(@intFromEnum(call_conv))); + llvm_instruction.setTailCallKind(switch (kind) { + .normal, .fast => .None, + .musttail, .musttail_fast => .MustTail, + .notail, .notail_fast => .NoTail, + .tail, .tail_fast => .Tail, + }); + for (0.., function_attributes.slice(self.builder)) |index, attributes| { + const attribute_index = @as(llvm.AttributeIndex, @intCast(index)) -% 1; + for (attributes.slice(self.builder)) |attribute| llvm_instruction.addCallSiteAttribute( + attribute_index, + attribute.toLlvm(self.builder), + ); + } + self.llvm.instructions.appendAssumeCapacity(llvm_instruction); + } + return instruction.toValue(); + } + + pub fn callAsm( + self: *WipFunction, + function_attributes: FunctionAttributes, + ty: Type, + kind: Constant.Asm.Info, + assembly: String, + constraints: String, + args: []const Value, + name: []const u8, + ) Allocator.Error!Value { + const callee = try self.builder.asmValue(ty, kind, assembly, constraints); + return self.call(.normal, CallConv.default, function_attributes, ty, callee, args, name); + } + pub fn vaArg(self: *WipFunction, list: Value, ty: Type, name: []const u8) Allocator.Error!Value { try self.ensureUnusedExtraCapacity(1, Instruction.VaArg, 0); const instruction = try self.addInst(name, .{ @@ -3246,8 +4394,17 @@ pub const WipFunction = struct { const value = @field(extra, field.name); wip_extra.items[wip_extra.index] = switch (field.type) { u32 => value, - Alignment, AtomicOrdering, Block.Index, Type, Value => @intFromEnum(value), - MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value), + Alignment, + AtomicOrdering, + Block.Index, + FunctionAttributes, + Type, + Value, + => @intFromEnum(value), + MemoryAccessInfo, + Instruction.Alloca.Info, + Instruction.Call.Info, + => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; wip_extra.index += 1; @@ -3256,13 +4413,14 @@ pub const WipFunction = struct { } fn appendSlice(wip_extra: *@This(), slice: anytype) void { - if (@typeInfo(@TypeOf(slice)).Pointer.child == Value) @compileError("use appendValues"); + if (@typeInfo(@TypeOf(slice)).Pointer.child == Value) + @compileError("use appendMappedValues"); const data: []const u32 = @ptrCast(slice); @memcpy(wip_extra.items[wip_extra.index..][0..data.len], data); wip_extra.index += @intCast(data.len); } - fn appendValues(wip_extra: *@This(), vals: []const Value, ctx: anytype) void { + fn appendMappedValues(wip_extra: *@This(), vals: []const Value, ctx: anytype) void { for (wip_extra.items[wip_extra.index..][0..vals.len], vals) |*extra, val| extra.* = @intFromEnum(ctx.map(val)); wip_extra.index += @intCast(vals.len); @@ -3494,6 +4652,26 @@ pub const WipFunction = struct { .@"else" = extra.@"else", }); }, + .call, + .@"call fast", + .@"musttail call", + .@"musttail call fast", + .@"notail call", + .@"notail call fast", + .@"tail call", + .@"tail call fast", + => { + var extra = self.extraDataTrail(Instruction.Call, instruction.data); + const args = extra.trail.next(extra.data.args_len, Value, self); + instruction.data = wip_extra.addExtra(Instruction.Call{ + .info = extra.data.info, + .attributes = extra.data.attributes, + .ty = extra.data.ty, + .callee = instructions.map(extra.data.callee), + .args_len = extra.data.args_len, + }); + wip_extra.appendMappedValues(args, instructions); + }, .extractvalue => { var extra = self.extraDataTrail(Instruction.ExtractValue, instruction.data); const indices = extra.trail.next(extra.data.indices_len, u32, self); @@ -3517,7 +4695,7 @@ pub const WipFunction = struct { .base = instructions.map(extra.data.base), .indices_len = extra.data.indices_len, }); - wip_extra.appendValues(indices, instructions); + wip_extra.appendMappedValues(indices, instructions); }, .insertelement => { const extra = self.extraData(Instruction.InsertElement, instruction.data); @@ -3559,7 +4737,7 @@ pub const WipFunction = struct { instruction.data = wip_extra.addExtra(Instruction.Phi{ .type = extra.data.type, }); - wip_extra.appendValues(incoming_vals, instructions); + wip_extra.appendMappedValues(incoming_vals, instructions); wip_extra.appendSlice(incoming_blocks); }, .select, @@ -3932,8 +5110,17 @@ pub const WipFunction = struct { const value = @field(extra, field.name); self.extra.appendAssumeCapacity(switch (field.type) { u32 => value, - Alignment, AtomicOrdering, Block.Index, Type, Value => @intFromEnum(value), - MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value), + Alignment, + AtomicOrdering, + Block.Index, + FunctionAttributes, + Type, + Value, + => @intFromEnum(value), + MemoryAccessInfo, + Instruction.Alloca.Info, + Instruction.Call.Info, + => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }); } @@ -3971,8 +5158,17 @@ pub const WipFunction = struct { inline for (fields, self.extra.items[index..][0..fields.len]) |field, value| @field(result, field.name) = switch (field.type) { u32 => value, - Alignment, AtomicOrdering, Block.Index, Type, Value => @enumFromInt(value), - MemoryAccessInfo, Instruction.Alloca.Info => @bitCast(value), + Alignment, + AtomicOrdering, + Block.Index, + FunctionAttributes, + Type, + Value, + => @enumFromInt(value), + MemoryAccessInfo, + Instruction.Alloca.Info, + Instruction.Call.Info, + => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; return .{ @@ -4092,7 +5288,7 @@ pub const Constant = enum(u32) { const first_global: Constant = @enumFromInt(1 << 30); - pub const Tag = enum(u6) { + pub const Tag = enum(u7) { positive_integer, negative_integer, half, @@ -4152,6 +5348,22 @@ pub const Constant = enum(u32) { @"and", @"or", xor, + @"asm", + @"asm sideeffect", + @"asm alignstack", + @"asm sideeffect alignstack", + @"asm inteldialect", + @"asm sideeffect inteldialect", + @"asm alignstack inteldialect", + @"asm sideeffect alignstack inteldialect", + @"asm unwind", + @"asm sideeffect unwind", + @"asm alignstack unwind", + @"asm sideeffect alignstack unwind", + @"asm inteldialect unwind", + @"asm sideeffect inteldialect unwind", + @"asm alignstack inteldialect unwind", + @"asm sideeffect alignstack inteldialect unwind", }; pub const Item = struct { @@ -4247,6 +5459,19 @@ pub const Constant = enum(u32) { rhs: Constant, }; + pub const Asm = extern struct { + type: Type, + assembly: String, + constraints: String, + + pub const Info = packed struct { + sideeffect: bool = false, + alignstack: bool = false, + inteldialect: bool = false, + unwind: bool = false, + }; + }; + pub fn unwrap(self: Constant) union(enum) { constant: u30, global: Global.Index, @@ -4294,7 +5519,7 @@ pub const Constant = enum(u32) { .string, .string_null, => builder.arrayTypeAssumeCapacity( - @as(String, @enumFromInt(item.data)).toSlice(builder).?.len + + @as(String, @enumFromInt(item.data)).slice(builder).?.len + @intFromBool(item.tag == .string_null), .i8, ), @@ -4365,6 +5590,23 @@ pub const Constant = enum(u32) { .@"or", .xor, => builder.constantExtraData(Binary, item.data).lhs.typeOf(builder), + .@"asm", + .@"asm sideeffect", + .@"asm alignstack", + .@"asm sideeffect alignstack", + .@"asm inteldialect", + .@"asm sideeffect inteldialect", + .@"asm alignstack inteldialect", + .@"asm sideeffect alignstack inteldialect", + .@"asm unwind", + .@"asm sideeffect unwind", + .@"asm alignstack unwind", + .@"asm sideeffect alignstack unwind", + .@"asm inteldialect unwind", + .@"asm sideeffect inteldialect unwind", + .@"asm alignstack inteldialect unwind", + .@"asm sideeffect alignstack inteldialect unwind", + => .ptr, }; }, .global => |global| return builder.ptrTypeAssumeCapacity( @@ -4712,6 +5954,30 @@ pub const Constant = enum(u32) { extra.rhs.fmt(data.builder), }); }, + .@"asm", + .@"asm sideeffect", + .@"asm alignstack", + .@"asm sideeffect alignstack", + .@"asm inteldialect", + .@"asm sideeffect inteldialect", + .@"asm alignstack inteldialect", + .@"asm sideeffect alignstack inteldialect", + .@"asm unwind", + .@"asm sideeffect unwind", + .@"asm alignstack unwind", + .@"asm sideeffect alignstack unwind", + .@"asm inteldialect unwind", + .@"asm sideeffect inteldialect unwind", + .@"asm alignstack inteldialect unwind", + .@"asm sideeffect alignstack inteldialect unwind", + => |tag| { + const extra = data.builder.constantExtraData(Asm, item.data); + try writer.print("{s} {\"}, {\"}", .{ + @tagName(tag), + extra.assembly.fmt(data.builder), + extra.constraints.fmt(data.builder), + }); + }, } }, .global => |global| try writer.print("{}", .{global.fmt(data.builder)}), @@ -4819,10 +6085,11 @@ pub fn init(options: Options) InitError!Builder { .source_filename = .none, .data_layout = .none, .target_triple = .none, + .module_asm = .{}, .string_map = .{}, - .string_bytes = .{}, .string_indices = .{}, + .string_bytes = .{}, .types = .{}, .next_unnamed_type = @enumFromInt(0), @@ -4831,6 +6098,11 @@ pub fn init(options: Options) InitError!Builder { .type_items = .{}, .type_extra = .{}, + .attributes = .{}, + .attributes_map = .{}, + .attributes_indices = .{}, + .attributes_extra = .{}, + .globals = .{}, .next_unnamed_global = @enumFromInt(0), .next_replaced_global = .none, @@ -4844,7 +6116,18 @@ pub fn init(options: Options) InitError!Builder { .constant_extra = .{}, .constant_limbs = .{}, }; - if (self.useLibLlvm()) self.llvm = .{ .context = llvm.Context.create() }; + if (self.useLibLlvm()) self.llvm = .{ + .context = llvm.Context.create(), + .module = null, + .target = null, + .di_builder = null, + .di_compile_unit = null, + .attribute_kind_ids = null, + .attributes = .{}, + .types = .{}, + .globals = .{}, + .constants = .{}, + }; errdefer self.deinit(); try self.string_indices.append(self.gpa, 0); @@ -4853,7 +6136,7 @@ pub fn init(options: Options) InitError!Builder { if (options.name.len > 0) self.source_filename = try self.string(options.name); self.initializeLLVMTarget(options.target.cpu.arch); if (self.useLibLlvm()) self.llvm.module = llvm.Module.createWithName( - (self.source_filename.toSlice(&self) orelse "").ptr, + (self.source_filename.slice(&self) orelse "").ptr, self.llvm.context, ); @@ -4864,20 +6147,20 @@ pub fn init(options: Options) InitError!Builder { var error_message: [*:0]const u8 = undefined; var target: *llvm.Target = undefined; if (llvm.Target.getFromTriple( - self.target_triple.toSlice(&self).?, + self.target_triple.slice(&self).?, &target, &error_message, ).toBool()) { defer llvm.disposeMessage(error_message); log.err("LLVM failed to parse '{s}': {s}", .{ - self.target_triple.toSlice(&self).?, + self.target_triple.slice(&self).?, error_message, }); return InitError.InvalidLlvmTriple; } self.llvm.target = target; - self.llvm.module.?.setTarget(self.target_triple.toSlice(&self).?); + self.llvm.module.?.setTarget(self.target_triple.slice(&self).?); } } @@ -4902,6 +6185,16 @@ pub fn init(options: Options) InitError!Builder { assert(self.ptrTypeAssumeCapacity(@enumFromInt(addr_space)) == .ptr); } + { + if (self.useLibLlvm()) { + self.llvm.attribute_kind_ids = try self.gpa.create([Attribute.Kind.len]c_uint); + @memset(self.llvm.attribute_kind_ids.?, 0); + } + try self.attributes_indices.append(self.gpa, 0); + assert(try self.attrs(&.{}) == .none); + assert(try self.fnAttrs(&.{}) == .none); + } + assert(try self.intConst(.i1, 0) == .false); assert(try self.intConst(.i1, 1) == .true); assert(try self.noneConst(.token) == .none); @@ -4910,9 +6203,11 @@ pub fn init(options: Options) InitError!Builder { } pub fn deinit(self: *Builder) void { + self.module_asm.deinit(self.gpa); + self.string_map.deinit(self.gpa); - self.string_bytes.deinit(self.gpa); self.string_indices.deinit(self.gpa); + self.string_bytes.deinit(self.gpa); self.types.deinit(self.gpa); self.next_unique_type_id.deinit(self.gpa); @@ -4920,6 +6215,11 @@ pub fn deinit(self: *Builder) void { self.type_items.deinit(self.gpa); self.type_extra.deinit(self.gpa); + self.attributes.deinit(self.gpa); + self.attributes_map.deinit(self.gpa); + self.attributes_indices.deinit(self.gpa); + self.attributes_extra.deinit(self.gpa); + self.globals.deinit(self.gpa); self.next_unique_global_id.deinit(self.gpa); self.aliases.deinit(self.gpa); @@ -4936,6 +6236,8 @@ pub fn deinit(self: *Builder) void { self.llvm.constants.deinit(self.gpa); self.llvm.globals.deinit(self.gpa); self.llvm.types.deinit(self.gpa); + self.llvm.attributes.deinit(self.gpa); + if (self.llvm.attribute_kind_ids) |attribute_kind_ids| self.gpa.destroy(attribute_kind_ids); if (self.llvm.di_builder) |di_builder| di_builder.dispose(); if (self.llvm.module) |module| module.dispose(); self.llvm.context.dispose(); @@ -5136,6 +6438,22 @@ pub fn initializeLLVMTarget(self: *const Builder, arch: std.Target.Cpu.Arch) voi } } +pub fn setModuleAsm(self: *Builder) std.ArrayListUnmanaged(u8).Writer { + self.module_asm.clearRetainingCapacity(); + return self.appendModuleAsm(); +} + +pub fn appendModuleAsm(self: *Builder) std.ArrayListUnmanaged(u8).Writer { + return self.module_asm.writer(self.gpa); +} + +pub fn finishModuleAsm(self: *Builder) Allocator.Error!void { + if (self.module_asm.getLastOrNull()) |last| if (last != '\n') + try self.module_asm.append(self.gpa, '\n'); + if (self.useLibLlvm()) + self.llvm.module.?.setModuleInlineAsm(self.module_asm.items.ptr, self.module_asm.items.len); +} + pub fn string(self: *Builder, bytes: []const u8) Allocator.Error!String { try self.string_bytes.ensureUnusedCapacity(self.gpa, bytes.len + 1); try self.string_indices.ensureUnusedCapacity(self.gpa, 1); @@ -5230,7 +6548,7 @@ pub fn structType( pub fn opaqueType(self: *Builder, name: String) Allocator.Error!Type { try self.string_map.ensureUnusedCapacity(self.gpa, 1); - if (name.toSlice(self)) |id| { + if (name.slice(self)) |id| { const count: usize = comptime std.fmt.count("{d}" ++ .{0}, .{std.math.maxInt(u32)}); try self.string_bytes.ensureUnusedCapacity(self.gpa, id.len + count); } @@ -5268,6 +6586,99 @@ pub fn namedTypeSetBody( } } +pub fn attr(self: *Builder, attribute: Attribute) Allocator.Error!Attribute.Index { + try self.attributes.ensureUnusedCapacity(self.gpa, 1); + if (self.useLibLlvm()) try self.llvm.attributes.ensureUnusedCapacity(self.gpa, 1); + + const gop = self.attributes.getOrPutAssumeCapacity(attribute.toStorage()); + if (!gop.found_existing) { + gop.value_ptr.* = {}; + if (self.useLibLlvm()) self.llvm.attributes.appendAssumeCapacity(switch (attribute) { + else => llvm_attr: { + const kind_id = &self.llvm.attribute_kind_ids.?[@intFromEnum(attribute)]; + if (kind_id.* == 0) { + const name = @tagName(attribute); + kind_id.* = llvm.getEnumAttributeKindForName(name.ptr, name.len); + assert(kind_id.* != 0); + } + break :llvm_attr switch (attribute) { + else => switch (attribute) { + inline else => |value| self.llvm.context.createEnumAttribute( + kind_id.*, + switch (@TypeOf(value)) { + void => 0, + u32 => value, + Attribute.FpClass, + Attribute.AllocKind, + Attribute.Memory, + => @as(u32, @bitCast(value)), + Alignment => value.toByteUnits() orelse 0, + Attribute.AllocSize, + Attribute.VScaleRange, + => @bitCast(value.toLlvm()), + Attribute.UwTable => @intFromEnum(value), + else => @compileError( + "bad payload type: " ++ @typeName(@TypeOf(value)), + ), + }, + ), + .byval, + .byref, + .preallocated, + .inalloca, + .sret, + .elementtype, + .string, + .none, + => unreachable, + }, + .byval, + .byref, + .preallocated, + .inalloca, + .sret, + .elementtype, + => |ty| self.llvm.context.createTypeAttribute(kind_id.*, ty.toLlvm(self)), + .string, .none => unreachable, + }; + }, + .string => |string_attr| llvm_attr: { + const kind = string_attr.kind.slice(self).?; + const value = string_attr.value.slice(self).?; + break :llvm_attr self.llvm.context.createStringAttribute( + kind.ptr, + @intCast(kind.len), + value.ptr, + @intCast(value.len), + ); + }, + .none => unreachable, + }); + } + return @enumFromInt(gop.index); +} + +pub fn attrs(self: *Builder, attributes: []Attribute.Index) Allocator.Error!Attributes { + std.sort.heap(Attribute.Index, attributes, self, struct { + pub fn lessThan(builder: *const Builder, lhs: Attribute.Index, rhs: Attribute.Index) bool { + const lhs_kind = lhs.getKind(builder); + const rhs_kind = rhs.getKind(builder); + assert(lhs_kind != rhs_kind); + return @intFromEnum(lhs_kind) < @intFromEnum(rhs_kind); + } + }.lessThan); + return @enumFromInt(try self.attrGeneric(@ptrCast(attributes))); +} + +pub fn fnAttrs(self: *Builder, fn_attributes: []const Attributes) Allocator.Error!FunctionAttributes { + return @enumFromInt(try self.attrGeneric(@ptrCast( + fn_attributes[0..if (std.mem.lastIndexOfNone(Attributes, fn_attributes, &.{.none})) |last| + last + 1 + else + 0], + ))); +} + pub fn addGlobal(self: *Builder, name: String, global: Global) Allocator.Error!Global.Index { assert(!name.isAnon()); try self.ensureUnusedTypeCapacity(1, NoExtra, 0); @@ -5295,7 +6706,7 @@ pub fn addGlobalAssumeCapacity(self: *Builder, name: String, global: Global) Glo const unique_gop = self.next_unique_global_id.getOrPutAssumeCapacity(name); if (!unique_gop.found_existing) unique_gop.value_ptr.* = 2; - id = self.fmtAssumeCapacity("{s}.{d}", .{ name.toSlice(self).?, unique_gop.value_ptr.* }); + id = self.fmtAssumeCapacity("{s}.{d}", .{ name.slice(self).?, unique_gop.value_ptr.* }); unique_gop.value_ptr.* += 1; } } @@ -5309,8 +6720,9 @@ pub fn intConst(self: *Builder, ty: Type, value: anytype) Allocator.Error!Consta switch (@typeInfo(@TypeOf(value))) { .Int => |info| std.math.big.int.calcTwosCompLimbCount(info.bits), .ComptimeInt => std.math.big.int.calcLimbLen(value), - else => @compileError("intConst expected an integral value, got " ++ - @typeName(@TypeOf(value))), + else => @compileError( + "intConst expected an integral value, got " ++ @typeName(@TypeOf(value)), + ), } ]std.math.big.Limb = undefined; return self.bigIntConst(ty, std.math.big.int.Mutable.init(&limbs, value).toConst()); @@ -5721,6 +7133,27 @@ pub fn binValue(self: *Builder, tag: Constant.Tag, lhs: Constant, rhs: Constant) return (try self.binConst(tag, lhs, rhs)).toValue(); } +pub fn asmConst( + self: *Builder, + ty: Type, + info: Constant.Asm.Info, + assembly: String, + constraints: String, +) Allocator.Error!Constant { + try self.ensureUnusedConstantCapacity(1, Constant.Asm, 0); + return self.asmConstAssumeCapacity(ty, info, assembly, constraints); +} + +pub fn asmValue( + self: *Builder, + ty: Type, + info: Constant.Asm.Info, + assembly: String, + constraints: String, +) Allocator.Error!Value { + return (try self.asmConst(ty, info, assembly, constraints)).toValue(); +} + pub fn dump(self: *Builder) void { if (self.useLibLlvm()) self.llvm.module.?.dump() @@ -5766,457 +7199,604 @@ pub fn printUnbuffered( self: *Builder, writer: anytype, ) (@TypeOf(writer).Error || Allocator.Error)!void { - if (self.source_filename != .none) try writer.print( - \\; ModuleID = '{s}' - \\source_filename = {"} - \\ - , .{ self.source_filename.toSlice(self).?, self.source_filename.fmt(self) }); - if (self.data_layout != .none) try writer.print( - \\target datalayout = {"} - \\ - , .{self.data_layout.fmt(self)}); - if (self.target_triple != .none) try writer.print( - \\target triple = {"} - \\ - , .{self.target_triple.fmt(self)}); - try writer.writeByte('\n'); - for (self.types.keys(), self.types.values()) |id, ty| try writer.print( - \\%{} = type {} - \\ - , .{ id.fmt(self), ty.fmt(self) }); - try writer.writeByte('\n'); - for (self.variables.items) |variable| { - if (variable.global.getReplacement(self) != .none) continue; - const global = variable.global.ptrConst(self); - try writer.print( - \\{} ={}{}{}{}{}{}{}{} {s} {%}{ }{,} + var need_newline = false; + + if (self.source_filename != .none or self.data_layout != .none or self.target_triple != .none) { + if (need_newline) try writer.writeByte('\n'); + if (self.source_filename != .none) try writer.print( + \\; ModuleID = '{s}' + \\source_filename = {"} \\ - , .{ - variable.global.fmt(self), - global.linkage, - global.preemption, - global.visibility, - global.dll_storage_class, - variable.thread_local, - global.unnamed_addr, - global.addr_space, - global.externally_initialized, - @tagName(variable.mutability), - global.type.fmt(self), - variable.init.fmt(self), - variable.alignment, - }); + , .{ self.source_filename.slice(self).?, self.source_filename.fmt(self) }); + if (self.data_layout != .none) try writer.print( + \\target datalayout = {"} + \\ + , .{self.data_layout.fmt(self)}); + if (self.target_triple != .none) try writer.print( + \\target triple = {"} + \\ + , .{self.target_triple.fmt(self)}); + need_newline = true; } - try writer.writeByte('\n'); - for (0.., self.functions.items) |function_i, function| { - const function_index: Function.Index = @enumFromInt(function_i); - if (function.global.getReplacement(self) != .none) continue; - const global = function.global.ptrConst(self); - const params_len = global.type.functionParameters(self).len; - try writer.print( - \\{s}{}{}{}{} {} {}( - , .{ - if (function.instructions.len > 0) "define" else "declare", - global.linkage, - global.preemption, - global.visibility, - global.dll_storage_class, - global.type.functionReturn(self).fmt(self), - function.global.fmt(self), - }); - for (0..params_len) |arg| { - if (arg > 0) try writer.writeAll(", "); - if (function.instructions.len > 0) - try writer.print("{%}", .{function.arg(@intCast(arg)).fmt(function_index, self)}) - else - try writer.print("{%}", .{global.type.functionParameters(self)[arg].fmt(self)}); + + if (self.module_asm.items.len > 0) { + if (need_newline) try writer.writeByte('\n'); + var line_it = std.mem.tokenizeScalar(u8, self.module_asm.items, '\n'); + while (line_it.next()) |line| { + try writer.writeAll("module asm "); + try printEscapedString(line, .always_quote, writer); + try writer.writeByte('\n'); } - switch (global.type.functionKind(self)) { - .normal => {}, - .vararg => { - if (params_len > 0) try writer.writeAll(", "); - try writer.writeAll("..."); - }, + need_newline = true; + } + + if (self.types.count() > 0) { + if (need_newline) try writer.writeByte('\n'); + for (self.types.keys(), self.types.values()) |id, ty| try writer.print( + \\%{} = type {} + \\ + , .{ id.fmt(self), ty.fmt(self) }); + need_newline = true; + } + + if (self.variables.items.len > 0) { + if (need_newline) try writer.writeByte('\n'); + for (self.variables.items) |variable| { + if (variable.global.getReplacement(self) != .none) continue; + const global = variable.global.ptrConst(self); + try writer.print( + \\{} ={}{}{}{}{}{}{}{} {s} {%}{ }{,} + \\ + , .{ + variable.global.fmt(self), + global.linkage, + global.preemption, + global.visibility, + global.dll_storage_class, + variable.thread_local, + global.unnamed_addr, + global.addr_space, + global.externally_initialized, + @tagName(variable.mutability), + global.type.fmt(self), + variable.init.fmt(self), + variable.alignment, + }); } - try writer.print("){}{}", .{ global.unnamed_addr, function.alignment }); - if (function.instructions.len > 0) { - var block_incoming_len: u32 = undefined; - try writer.writeAll(" {\n"); - for (params_len..function.instructions.len) |instruction_i| { - const instruction_index: Function.Instruction.Index = @enumFromInt(instruction_i); - const instruction = function.instructions.get(@intFromEnum(instruction_index)); - switch (instruction.tag) { - .add, - .@"add nsw", - .@"add nuw", - .@"add nuw nsw", - .@"and", - .ashr, - .@"ashr exact", - .fadd, - .@"fadd fast", - .@"fcmp false", - .@"fcmp fast false", - .@"fcmp fast oeq", - .@"fcmp fast oge", - .@"fcmp fast ogt", - .@"fcmp fast ole", - .@"fcmp fast olt", - .@"fcmp fast one", - .@"fcmp fast ord", - .@"fcmp fast true", - .@"fcmp fast ueq", - .@"fcmp fast uge", - .@"fcmp fast ugt", - .@"fcmp fast ule", - .@"fcmp fast ult", - .@"fcmp fast une", - .@"fcmp fast uno", - .@"fcmp oeq", - .@"fcmp oge", - .@"fcmp ogt", - .@"fcmp ole", - .@"fcmp olt", - .@"fcmp one", - .@"fcmp ord", - .@"fcmp true", - .@"fcmp ueq", - .@"fcmp uge", - .@"fcmp ugt", - .@"fcmp ule", - .@"fcmp ult", - .@"fcmp une", - .@"fcmp uno", - .fdiv, - .@"fdiv fast", - .fmul, - .@"fmul fast", - .frem, - .@"frem fast", - .fsub, - .@"fsub fast", - .@"icmp eq", - .@"icmp ne", - .@"icmp sge", - .@"icmp sgt", - .@"icmp sle", - .@"icmp slt", - .@"icmp uge", - .@"icmp ugt", - .@"icmp ule", - .@"icmp ult", - .lshr, - .@"lshr exact", - .mul, - .@"mul nsw", - .@"mul nuw", - .@"mul nuw nsw", - .@"or", - .sdiv, - .@"sdiv exact", - .srem, - .shl, - .@"shl nsw", - .@"shl nuw", - .@"shl nuw nsw", - .sub, - .@"sub nsw", - .@"sub nuw", - .@"sub nuw nsw", - .udiv, - .@"udiv exact", - .urem, - .xor, - => |tag| { - const extra = function.extraData(Function.Instruction.Binary, instruction.data); - try writer.print(" %{} = {s} {%}, {}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.lhs.fmt(function_index, self), - extra.rhs.fmt(function_index, self), - }); - }, - .addrspacecast, - .bitcast, - .fpext, - .fptosi, - .fptoui, - .fptrunc, - .inttoptr, - .ptrtoint, - .sext, - .sitofp, - .trunc, - .uitofp, - .zext, - => |tag| { - const extra = function.extraData(Function.Instruction.Cast, instruction.data); - try writer.print(" %{} = {s} {%} to {%}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.val.fmt(function_index, self), - extra.type.fmt(self), - }); - }, - .alloca, - .@"alloca inalloca", - => |tag| { - const extra = function.extraData(Function.Instruction.Alloca, instruction.data); - try writer.print(" %{} = {s} {%}{,%}{,}{,}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.type.fmt(self), - extra.len.fmt(function_index, self), - extra.info.alignment, - extra.info.addr_space, - }); - }, - .arg => unreachable, - .block => { - block_incoming_len = instruction.data; - const name = instruction_index.name(&function); - if (@intFromEnum(instruction_index) > params_len) try writer.writeByte('\n'); - try writer.print("{}:\n", .{name.fmt(self)}); - }, - .br => |tag| { - const target: Function.Block.Index = @enumFromInt(instruction.data); - try writer.print(" {s} {%}\n", .{ - @tagName(tag), target.toInst(&function).fmt(function_index, self), - }); - }, - .br_cond => { - const extra = function.extraData(Function.Instruction.BrCond, instruction.data); - try writer.print(" br {%}, {%}, {%}\n", .{ - extra.cond.fmt(function_index, self), - extra.then.toInst(&function).fmt(function_index, self), - extra.@"else".toInst(&function).fmt(function_index, self), - }); - }, - .extractelement => |tag| { - const extra = - function.extraData(Function.Instruction.ExtractElement, instruction.data); - try writer.print(" %{} = {s} {%}, {%}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.val.fmt(function_index, self), - extra.index.fmt(function_index, self), - }); - }, - .extractvalue => |tag| { - var extra = - function.extraDataTrail(Function.Instruction.ExtractValue, instruction.data); - const indices = extra.trail.next(extra.data.indices_len, u32, &function); - try writer.print(" %{} = {s} {%}", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.data.val.fmt(function_index, self), - }); - for (indices) |index| try writer.print(", {d}", .{index}); - try writer.writeByte('\n'); - }, - .fence => |tag| { - const info: MemoryAccessInfo = @bitCast(instruction.data); - try writer.print(" {s}{}{}", .{ @tagName(tag), info.scope, info.ordering }); - }, - .fneg, - .@"fneg fast", - .ret, - => |tag| { - const val: Value = @enumFromInt(instruction.data); - try writer.print(" {s} {%}\n", .{ - @tagName(tag), - val.fmt(function_index, self), - }); - }, - .getelementptr, - .@"getelementptr inbounds", - => |tag| { - var extra = function.extraDataTrail( - Function.Instruction.GetElementPtr, - instruction.data, - ); - const indices = extra.trail.next(extra.data.indices_len, Value, &function); - try writer.print(" %{} = {s} {%}, {%}", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.data.type.fmt(self), - extra.data.base.fmt(function_index, self), - }); - for (indices) |index| try writer.print(", {%}", .{ - index.fmt(function_index, self), - }); - try writer.writeByte('\n'); - }, - .insertelement => |tag| { - const extra = - function.extraData(Function.Instruction.InsertElement, instruction.data); - try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.val.fmt(function_index, self), - extra.elem.fmt(function_index, self), - extra.index.fmt(function_index, self), - }); - }, - .insertvalue => |tag| { - var extra = - function.extraDataTrail(Function.Instruction.InsertValue, instruction.data); - const indices = extra.trail.next(extra.data.indices_len, u32, &function); - try writer.print(" %{} = {s} {%}, {%}", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.data.val.fmt(function_index, self), - extra.data.elem.fmt(function_index, self), - }); - for (indices) |index| try writer.print(", {d}", .{index}); - try writer.writeByte('\n'); - }, - .@"llvm.maxnum.", - .@"llvm.minnum.", - .@"llvm.sadd.sat.", - .@"llvm.smax.", - .@"llvm.smin.", - .@"llvm.smul.fix.sat.", - .@"llvm.sshl.sat.", - .@"llvm.ssub.sat.", - .@"llvm.uadd.sat.", - .@"llvm.umax.", - .@"llvm.umin.", - .@"llvm.umul.fix.sat.", - .@"llvm.ushl.sat.", - .@"llvm.usub.sat.", - => |tag| { - const extra = function.extraData(Function.Instruction.Binary, instruction.data); - const ty = instruction_index.typeOf(function_index, self); - try writer.print(" %{} = call {%} @{s}{m}({%}, {%})\n", .{ - instruction_index.name(&function).fmt(self), - ty.fmt(self), - @tagName(tag), - ty.fmt(self), - extra.lhs.fmt(function_index, self), - extra.rhs.fmt(function_index, self), - }); - }, - .load, - .@"load atomic", - .@"load atomic volatile", - .@"load volatile", - => |tag| { - const extra = function.extraData(Function.Instruction.Load, instruction.data); - try writer.print(" %{} = {s} {%}, {%}{}{}{,}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.type.fmt(self), - extra.ptr.fmt(function_index, self), - extra.info.scope, - extra.info.ordering, - extra.info.alignment, - }); - }, - .phi, - .@"phi fast", - => |tag| { - var extra = function.extraDataTrail(Function.Instruction.Phi, instruction.data); - const vals = extra.trail.next(block_incoming_len, Value, &function); - const blocks = - extra.trail.next(block_incoming_len, Function.Block.Index, &function); - try writer.print(" %{} = {s} {%} ", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - vals[0].typeOf(function_index, self).fmt(self), - }); - for (0.., vals, blocks) |incoming_index, incoming_val, incoming_block| { - if (incoming_index > 0) try writer.writeAll(", "); - try writer.print("[ {}, {} ]", .{ - incoming_val.fmt(function_index, self), - incoming_block.toInst(&function).fmt(function_index, self), + need_newline = true; + } + + var attribute_groups: std.AutoArrayHashMapUnmanaged(Attributes, void) = .{}; + defer attribute_groups.deinit(self.gpa); + + if (self.functions.items.len > 0) { + if (need_newline) try writer.writeByte('\n'); + for (0.., self.functions.items) |function_i, function| { + if (function_i > 0) try writer.writeByte('\n'); + const function_index: Function.Index = @enumFromInt(function_i); + if (function.global.getReplacement(self) != .none) continue; + const global = function.global.ptrConst(self); + const params_len = global.type.functionParameters(self).len; + const function_attributes = function.attributes.func(self); + if (function_attributes != .none) try writer.print( + \\; Function Attrs:{} + \\ + , .{function_attributes.fmt(self)}); + try writer.print( + \\{s}{}{}{}{}{}{"} {} {}( + , .{ + if (function.instructions.len > 0) "define" else "declare", + global.linkage, + global.preemption, + global.visibility, + global.dll_storage_class, + function.call_conv, + function.attributes.ret(self).fmt(self), + global.type.functionReturn(self).fmt(self), + function.global.fmt(self), + }); + for (0..params_len) |arg| { + if (arg > 0) try writer.writeAll(", "); + try writer.print( + \\{%}{"} + , .{ + global.type.functionParameters(self)[arg].fmt(self), + function.attributes.param(arg, self).fmt(self), + }); + if (function.instructions.len > 0) + try writer.print(" {}", .{function.arg(@intCast(arg)).fmt(function_index, self)}); + } + switch (global.type.functionKind(self)) { + .normal => {}, + .vararg => { + if (params_len > 0) try writer.writeAll(", "); + try writer.writeAll("..."); + }, + } + try writer.print("){}{}", .{ global.unnamed_addr, global.addr_space }); + if (function_attributes != .none) try writer.print(" #{d}", .{ + (try attribute_groups.getOrPutValue(self.gpa, function_attributes, {})).index, + }); + try writer.print("{}", .{function.alignment}); + if (function.instructions.len > 0) { + var block_incoming_len: u32 = undefined; + try writer.writeAll(" {\n"); + for (params_len..function.instructions.len) |instruction_i| { + const instruction_index: Function.Instruction.Index = @enumFromInt(instruction_i); + const instruction = function.instructions.get(@intFromEnum(instruction_index)); + switch (instruction.tag) { + .add, + .@"add nsw", + .@"add nuw", + .@"add nuw nsw", + .@"and", + .ashr, + .@"ashr exact", + .fadd, + .@"fadd fast", + .@"fcmp false", + .@"fcmp fast false", + .@"fcmp fast oeq", + .@"fcmp fast oge", + .@"fcmp fast ogt", + .@"fcmp fast ole", + .@"fcmp fast olt", + .@"fcmp fast one", + .@"fcmp fast ord", + .@"fcmp fast true", + .@"fcmp fast ueq", + .@"fcmp fast uge", + .@"fcmp fast ugt", + .@"fcmp fast ule", + .@"fcmp fast ult", + .@"fcmp fast une", + .@"fcmp fast uno", + .@"fcmp oeq", + .@"fcmp oge", + .@"fcmp ogt", + .@"fcmp ole", + .@"fcmp olt", + .@"fcmp one", + .@"fcmp ord", + .@"fcmp true", + .@"fcmp ueq", + .@"fcmp uge", + .@"fcmp ugt", + .@"fcmp ule", + .@"fcmp ult", + .@"fcmp une", + .@"fcmp uno", + .fdiv, + .@"fdiv fast", + .fmul, + .@"fmul fast", + .frem, + .@"frem fast", + .fsub, + .@"fsub fast", + .@"icmp eq", + .@"icmp ne", + .@"icmp sge", + .@"icmp sgt", + .@"icmp sle", + .@"icmp slt", + .@"icmp uge", + .@"icmp ugt", + .@"icmp ule", + .@"icmp ult", + .lshr, + .@"lshr exact", + .mul, + .@"mul nsw", + .@"mul nuw", + .@"mul nuw nsw", + .@"or", + .sdiv, + .@"sdiv exact", + .srem, + .shl, + .@"shl nsw", + .@"shl nuw", + .@"shl nuw nsw", + .sub, + .@"sub nsw", + .@"sub nuw", + .@"sub nuw nsw", + .udiv, + .@"udiv exact", + .urem, + .xor, + => |tag| { + const extra = + function.extraData(Function.Instruction.Binary, instruction.data); + try writer.print(" %{} = {s} {%}, {}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.lhs.fmt(function_index, self), + extra.rhs.fmt(function_index, self), }); - } - try writer.writeByte('\n'); - }, - .@"ret void", - .@"unreachable", - => |tag| try writer.print(" {s}\n", .{@tagName(tag)}), - .select, - .@"select fast", - => |tag| { - const extra = function.extraData(Function.Instruction.Select, instruction.data); - try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.cond.fmt(function_index, self), - extra.lhs.fmt(function_index, self), - extra.rhs.fmt(function_index, self), - }); - }, - .shufflevector => |tag| { - const extra = - function.extraData(Function.Instruction.ShuffleVector, instruction.data); - try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.lhs.fmt(function_index, self), - extra.rhs.fmt(function_index, self), - extra.mask.fmt(function_index, self), - }); - }, - .store, - .@"store atomic", - .@"store atomic volatile", - .@"store volatile", - => |tag| { - const extra = function.extraData(Function.Instruction.Store, instruction.data); - try writer.print(" {s} {%}, {%}{}{}{,}\n", .{ - @tagName(tag), - extra.val.fmt(function_index, self), - extra.ptr.fmt(function_index, self), - extra.info.scope, - extra.info.ordering, - extra.info.alignment, - }); - }, - .@"switch" => |tag| { - var extra = - function.extraDataTrail(Function.Instruction.Switch, instruction.data); - const vals = extra.trail.next(extra.data.cases_len, Constant, &function); - const blocks = - extra.trail.next(extra.data.cases_len, Function.Block.Index, &function); - try writer.print(" {s} {%}, {%} [", .{ - @tagName(tag), - extra.data.val.fmt(function_index, self), - extra.data.default.toInst(&function).fmt(function_index, self), - }); - for (vals, blocks) |case_val, case_block| try writer.print(" {%}, {%}\n", .{ - case_val.fmt(self), - case_block.toInst(&function).fmt(function_index, self), - }); - try writer.writeAll(" ]\n"); - }, - .unimplemented => |tag| { - const ty: Type = @enumFromInt(instruction.data); - try writer.writeAll(" "); - switch (ty) { - .none, .void => {}, - else => try writer.print("%{} = ", .{ + }, + .addrspacecast, + .bitcast, + .fpext, + .fptosi, + .fptoui, + .fptrunc, + .inttoptr, + .ptrtoint, + .sext, + .sitofp, + .trunc, + .uitofp, + .zext, + => |tag| { + const extra = + function.extraData(Function.Instruction.Cast, instruction.data); + try writer.print(" %{} = {s} {%} to {%}\n", .{ instruction_index.name(&function).fmt(self), - }), - } - try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) }); - }, - .va_arg => |tag| { - const extra = function.extraData(Function.Instruction.VaArg, instruction.data); - try writer.print(" %{} = {s} {%}, {%}\n", .{ - instruction_index.name(&function).fmt(self), - @tagName(tag), - extra.list.fmt(function_index, self), - extra.type.fmt(self), - }); - }, + @tagName(tag), + extra.val.fmt(function_index, self), + extra.type.fmt(self), + }); + }, + .alloca, + .@"alloca inalloca", + => |tag| { + const extra = + function.extraData(Function.Instruction.Alloca, instruction.data); + try writer.print(" %{} = {s} {%}{,%}{,}{,}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.type.fmt(self), + extra.len.fmt(function_index, self), + extra.info.alignment, + extra.info.addr_space, + }); + }, + .arg => unreachable, + .block => { + block_incoming_len = instruction.data; + const name = instruction_index.name(&function); + if (@intFromEnum(instruction_index) > params_len) + try writer.writeByte('\n'); + try writer.print("{}:\n", .{name.fmt(self)}); + }, + .br => |tag| { + const target: Function.Block.Index = @enumFromInt(instruction.data); + try writer.print(" {s} {%}\n", .{ + @tagName(tag), target.toInst(&function).fmt(function_index, self), + }); + }, + .br_cond => { + const extra = + function.extraData(Function.Instruction.BrCond, instruction.data); + try writer.print(" br {%}, {%}, {%}\n", .{ + extra.cond.fmt(function_index, self), + extra.then.toInst(&function).fmt(function_index, self), + extra.@"else".toInst(&function).fmt(function_index, self), + }); + }, + .call, + .@"call fast", + .@"musttail call", + .@"musttail call fast", + .@"notail call", + .@"notail call fast", + .@"tail call", + .@"tail call fast", + => |tag| { + var extra = + function.extraDataTrail(Function.Instruction.Call, instruction.data); + const args = extra.trail.next(extra.data.args_len, Value, &function); + try writer.writeAll(" "); + const ret_ty = extra.data.ty.functionReturn(self); + switch (ret_ty) { + .void => {}, + else => try writer.print("%{} = ", .{ + instruction_index.name(&function).fmt(self), + }), + .none => unreachable, + } + try writer.print("{s}{}{}{} {%} {}(", .{ + @tagName(tag), + extra.data.info.call_conv, + extra.data.attributes.ret(self).fmt(self), + extra.data.callee.typeOf(function_index, self).pointerAddrSpace(self), + switch (extra.data.ty.functionKind(self)) { + .normal => ret_ty, + .vararg => extra.data.ty, + }.fmt(self), + extra.data.callee.fmt(function_index, self), + }); + for (0.., args) |arg_index, arg| { + if (arg_index > 0) try writer.writeAll(", "); + try writer.print("{%}{} {}", .{ + arg.typeOf(function_index, self).fmt(self), + extra.data.attributes.param(arg_index, self).fmt(self), + arg.fmt(function_index, self), + }); + } + try writer.writeByte(')'); + const call_function_attributes = extra.data.attributes.func(self); + if (call_function_attributes != .none) try writer.print(" #{d}", .{ + (try attribute_groups.getOrPutValue( + self.gpa, + call_function_attributes, + {}, + )).index, + }); + try writer.writeByte('\n'); + }, + .extractelement => |tag| { + const extra = function.extraData( + Function.Instruction.ExtractElement, + instruction.data, + ); + try writer.print(" %{} = {s} {%}, {%}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.val.fmt(function_index, self), + extra.index.fmt(function_index, self), + }); + }, + .extractvalue => |tag| { + var extra = function.extraDataTrail( + Function.Instruction.ExtractValue, + instruction.data, + ); + const indices = extra.trail.next(extra.data.indices_len, u32, &function); + try writer.print(" %{} = {s} {%}", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.data.val.fmt(function_index, self), + }); + for (indices) |index| try writer.print(", {d}", .{index}); + try writer.writeByte('\n'); + }, + .fence => |tag| { + const info: MemoryAccessInfo = @bitCast(instruction.data); + try writer.print(" {s}{}{}", .{ @tagName(tag), info.scope, info.ordering }); + }, + .fneg, + .@"fneg fast", + .ret, + => |tag| { + const val: Value = @enumFromInt(instruction.data); + try writer.print(" {s} {%}\n", .{ + @tagName(tag), + val.fmt(function_index, self), + }); + }, + .getelementptr, + .@"getelementptr inbounds", + => |tag| { + var extra = function.extraDataTrail( + Function.Instruction.GetElementPtr, + instruction.data, + ); + const indices = extra.trail.next(extra.data.indices_len, Value, &function); + try writer.print(" %{} = {s} {%}, {%}", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.data.type.fmt(self), + extra.data.base.fmt(function_index, self), + }); + for (indices) |index| try writer.print(", {%}", .{ + index.fmt(function_index, self), + }); + try writer.writeByte('\n'); + }, + .insertelement => |tag| { + const extra = function.extraData( + Function.Instruction.InsertElement, + instruction.data, + ); + try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.val.fmt(function_index, self), + extra.elem.fmt(function_index, self), + extra.index.fmt(function_index, self), + }); + }, + .insertvalue => |tag| { + var extra = function.extraDataTrail( + Function.Instruction.InsertValue, + instruction.data, + ); + const indices = extra.trail.next(extra.data.indices_len, u32, &function); + try writer.print(" %{} = {s} {%}, {%}", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.data.val.fmt(function_index, self), + extra.data.elem.fmt(function_index, self), + }); + for (indices) |index| try writer.print(", {d}", .{index}); + try writer.writeByte('\n'); + }, + .@"llvm.maxnum.", + .@"llvm.minnum.", + .@"llvm.sadd.sat.", + .@"llvm.smax.", + .@"llvm.smin.", + .@"llvm.smul.fix.sat.", + .@"llvm.sshl.sat.", + .@"llvm.ssub.sat.", + .@"llvm.uadd.sat.", + .@"llvm.umax.", + .@"llvm.umin.", + .@"llvm.umul.fix.sat.", + .@"llvm.ushl.sat.", + .@"llvm.usub.sat.", + => |tag| { + const extra = + function.extraData(Function.Instruction.Binary, instruction.data); + const ty = instruction_index.typeOf(function_index, self); + try writer.print(" %{} = call {%} @{s}{m}({%}, {%}{s})\n", .{ + instruction_index.name(&function).fmt(self), + ty.fmt(self), + @tagName(tag), + ty.fmt(self), + extra.lhs.fmt(function_index, self), + extra.rhs.fmt(function_index, self), + switch (tag) { + .@"llvm.smul.fix.sat.", + .@"llvm.umul.fix.sat.", + => ", i32 0", + else => "", + }, + }); + }, + .load, + .@"load atomic", + .@"load atomic volatile", + .@"load volatile", + => |tag| { + const extra = + function.extraData(Function.Instruction.Load, instruction.data); + try writer.print(" %{} = {s} {%}, {%}{}{}{,}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.type.fmt(self), + extra.ptr.fmt(function_index, self), + extra.info.scope, + extra.info.ordering, + extra.info.alignment, + }); + }, + .phi, + .@"phi fast", + => |tag| { + var extra = + function.extraDataTrail(Function.Instruction.Phi, instruction.data); + const vals = extra.trail.next(block_incoming_len, Value, &function); + const blocks = + extra.trail.next(block_incoming_len, Function.Block.Index, &function); + try writer.print(" %{} = {s} {%} ", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + vals[0].typeOf(function_index, self).fmt(self), + }); + for (0.., vals, blocks) |incoming_index, incoming_val, incoming_block| { + if (incoming_index > 0) try writer.writeAll(", "); + try writer.print("[ {}, {} ]", .{ + incoming_val.fmt(function_index, self), + incoming_block.toInst(&function).fmt(function_index, self), + }); + } + try writer.writeByte('\n'); + }, + .@"ret void", + .@"unreachable", + => |tag| try writer.print(" {s}\n", .{@tagName(tag)}), + .select, + .@"select fast", + => |tag| { + const extra = + function.extraData(Function.Instruction.Select, instruction.data); + try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.cond.fmt(function_index, self), + extra.lhs.fmt(function_index, self), + extra.rhs.fmt(function_index, self), + }); + }, + .shufflevector => |tag| { + const extra = function.extraData( + Function.Instruction.ShuffleVector, + instruction.data, + ); + try writer.print(" %{} = {s} {%}, {%}, {%}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.lhs.fmt(function_index, self), + extra.rhs.fmt(function_index, self), + extra.mask.fmt(function_index, self), + }); + }, + .store, + .@"store atomic", + .@"store atomic volatile", + .@"store volatile", + => |tag| { + const extra = + function.extraData(Function.Instruction.Store, instruction.data); + try writer.print(" {s} {%}, {%}{}{}{,}\n", .{ + @tagName(tag), + extra.val.fmt(function_index, self), + extra.ptr.fmt(function_index, self), + extra.info.scope, + extra.info.ordering, + extra.info.alignment, + }); + }, + .@"switch" => |tag| { + var extra = + function.extraDataTrail(Function.Instruction.Switch, instruction.data); + const vals = extra.trail.next(extra.data.cases_len, Constant, &function); + const blocks = + extra.trail.next(extra.data.cases_len, Function.Block.Index, &function); + try writer.print(" {s} {%}, {%} [\n", .{ + @tagName(tag), + extra.data.val.fmt(function_index, self), + extra.data.default.toInst(&function).fmt(function_index, self), + }); + for (vals, blocks) |case_val, case_block| try writer.print( + " {%}, {%}\n", + .{ + case_val.fmt(self), + case_block.toInst(&function).fmt(function_index, self), + }, + ); + try writer.writeAll(" ]\n"); + }, + .unimplemented => |tag| { + const ty: Type = @enumFromInt(instruction.data); + if (true) { + try writer.writeAll(" "); + switch (ty) { + .none, .void => {}, + else => try writer.print("%{} = ", .{ + instruction_index.name(&function).fmt(self), + }), + } + try writer.print("{s} {%}\n", .{ @tagName(tag), ty.fmt(self) }); + } else switch (ty) { + .none, .void => {}, + else => try writer.print(" %{} = load {%}, ptr undef\n", .{ + instruction_index.name(&function).fmt(self), + ty.fmt(self), + }), + } + }, + .va_arg => |tag| { + const extra = + function.extraData(Function.Instruction.VaArg, instruction.data); + try writer.print(" %{} = {s} {%}, {%}\n", .{ + instruction_index.name(&function).fmt(self), + @tagName(tag), + extra.list.fmt(function_index, self), + extra.type.fmt(self), + }); + }, + } } + try writer.writeByte('}'); } - try writer.writeByte('}'); + try writer.writeByte('\n'); } - try writer.writeAll("\n\n"); + need_newline = true; + } + + if (attribute_groups.count() > 0) { + if (need_newline) try writer.writeByte('\n') else need_newline = true; + for (0.., attribute_groups.keys()) |attribute_group_index, attribute_group| + try writer.print( + \\attributes #{d} = {{{#"} }} + \\ + , .{ attribute_group_index, attribute_group.fmt(self) }); + need_newline = true; } } @@ -6227,7 +7807,7 @@ pub inline fn useLibLlvm(self: *const Builder) bool { const NoExtra = struct {}; fn isValidIdentifier(id: []const u8) bool { - for (id, 0..) |character, index| switch (character) { + for (id, 0..) |byte, index| switch (byte) { '$', '-', '.', 'A'...'Z', '_', 'a'...'z' => {}, '0'...'9' => if (index == 0) return false, else => return false, @@ -6235,10 +7815,29 @@ fn isValidIdentifier(id: []const u8) bool { return true; } +const QuoteBehavior = enum { always_quote, quote_unless_valid_identifier }; +fn printEscapedString( + slice: []const u8, + quotes: QuoteBehavior, + writer: anytype, +) @TypeOf(writer).Error!void { + const need_quotes = switch (quotes) { + .always_quote => true, + .quote_unless_valid_identifier => !isValidIdentifier(slice), + }; + if (need_quotes) try writer.writeByte('"'); + for (slice) |byte| switch (byte) { + '\\' => try writer.writeAll("\\\\"), + ' '...'"' - 1, '"' + 1...'\\' - 1, '\\' + 1...'~' => try writer.writeByte(byte), + else => try writer.print("\\{X:0>2}", .{byte}), + }; + if (need_quotes) try writer.writeByte('"'); +} + fn ensureUnusedGlobalCapacity(self: *Builder, name: String) Allocator.Error!void { if (self.useLibLlvm()) try self.llvm.globals.ensureUnusedCapacity(self.gpa, 1); try self.string_map.ensureUnusedCapacity(self.gpa, 1); - if (name.toSlice(self)) |id| { + if (name.slice(self)) |id| { const count: usize = comptime std.fmt.count("{d}" ++ .{0}, .{std.math.maxInt(u32)}); try self.string_bytes.ensureUnusedCapacity(self.gpa, id.len + count); } @@ -6528,14 +8127,14 @@ fn opaqueTypeAssumeCapacity(self: *Builder, name: String) Type { const result: Type = @enumFromInt(gop.index); type_gop.value_ptr.* = result; if (self.useLibLlvm()) self.llvm.types.appendAssumeCapacity( - self.llvm.context.structCreateNamed(id.toSlice(self) orelse ""), + self.llvm.context.structCreateNamed(id.slice(self) orelse ""), ); return result; } const unique_gop = self.next_unique_type_id.getOrPutAssumeCapacity(name); if (!unique_gop.found_existing) unique_gop.value_ptr.* = 2; - id = self.fmtAssumeCapacity("{s}.{d}", .{ name.toSlice(self).?, unique_gop.value_ptr.* }); + id = self.fmtAssumeCapacity("{s}.{d}", .{ name.slice(self).?, unique_gop.value_ptr.* }); unique_gop.value_ptr.* += 1; } } @@ -6636,6 +8235,30 @@ fn typeExtraData(self: *const Builder, comptime T: type, index: Type.Item.ExtraI return self.typeExtraDataTrail(T, index).data; } +fn attrGeneric(self: *Builder, data: []const u32) Allocator.Error!u32 { + try self.attributes_map.ensureUnusedCapacity(self.gpa, 1); + try self.attributes_indices.ensureUnusedCapacity(self.gpa, 1); + try self.attributes_extra.ensureUnusedCapacity(self.gpa, data.len); + + const Adapter = struct { + builder: *const Builder, + pub fn hash(_: @This(), key: []const u32) u32 { + return @truncate(std.hash.Wyhash.hash(1, std.mem.sliceAsBytes(key))); + } + pub fn eql(ctx: @This(), lhs_key: []const u32, _: void, rhs_index: usize) bool { + const start = ctx.builder.attributes_indices.items[rhs_index]; + const end = ctx.builder.attributes_indices.items[rhs_index + 1]; + return std.mem.eql(u32, lhs_key, ctx.builder.attributes_extra.items[start..end]); + } + }; + const gop = self.attributes_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); + if (!gop.found_existing) { + self.attributes_extra.appendSliceAssumeCapacity(data); + self.attributes_indices.appendAssumeCapacity(@intCast(self.attributes_extra.items.len)); + } + return @intCast(gop.index); +} + fn bigIntConstAssumeCapacity( self: *Builder, ty: Type, @@ -7073,7 +8696,7 @@ fn arrayConstAssumeCapacity( } fn stringConstAssumeCapacity(self: *Builder, val: String) Constant { - const slice = val.toSlice(self).?; + const slice = val.slice(self).?; const ty = self.arrayTypeAssumeCapacity(slice.len, .i8); if (std.mem.allEqual(u8, slice, 0)) return self.zeroInitConstAssumeCapacity(ty); const result = self.getOrPutConstantNoExtraAssumeCapacity( @@ -7086,7 +8709,7 @@ fn stringConstAssumeCapacity(self: *Builder, val: String) Constant { } fn stringNullConstAssumeCapacity(self: *Builder, val: String) Constant { - const slice = val.toSlice(self).?; + const slice = val.slice(self).?; const ty = self.arrayTypeAssumeCapacity(slice.len + 1, .i8); if (std.mem.allEqual(u8, slice, 0)) return self.zeroInitConstAssumeCapacity(ty); const result = self.getOrPutConstantNoExtraAssumeCapacity( @@ -7737,30 +9360,30 @@ fn binConstAssumeCapacity( => {}, else => unreachable, } - const Key = struct { tag: Constant.Tag, bin: Constant.Binary }; + const Key = struct { tag: Constant.Tag, extra: Constant.Binary }; const Adapter = struct { builder: *const Builder, pub fn hash(_: @This(), key: Key) u32 { return @truncate(std.hash.Wyhash.hash( std.hash.uint32(@intFromEnum(key.tag)), - std.mem.asBytes(&key.bin), + std.mem.asBytes(&key.extra), )); } pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false; const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; const rhs_extra = ctx.builder.constantExtraData(Constant.Binary, rhs_data); - return std.meta.eql(lhs_key.bin, rhs_extra); + return std.meta.eql(lhs_key.extra, rhs_extra); } }; - const data = Key{ .tag = tag, .bin = .{ .lhs = lhs, .rhs = rhs } }; + const data = Key{ .tag = tag, .extra = .{ .lhs = lhs, .rhs = rhs } }; const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); if (!gop.found_existing) { gop.key_ptr.* = {}; gop.value_ptr.* = {}; self.constant_items.appendAssumeCapacity(.{ .tag = tag, - .data = self.addConstantExtraAssumeCapacity(data.bin), + .data = self.addConstantExtraAssumeCapacity(data.extra), }); if (self.useLibLlvm()) self.llvm.constants.appendAssumeCapacity(switch (tag) { .add => &llvm.Value.constAdd, @@ -7778,6 +9401,63 @@ fn binConstAssumeCapacity( return @enumFromInt(gop.index); } +fn asmConstAssumeCapacity( + self: *Builder, + ty: Type, + info: Constant.Asm.Info, + assembly: String, + constraints: String, +) Constant { + assert(ty.functionKind(self) == .normal); + + const Key = struct { tag: Constant.Tag, extra: Constant.Asm }; + const Adapter = struct { + builder: *const Builder, + pub fn hash(_: @This(), key: Key) u32 { + return @truncate(std.hash.Wyhash.hash( + std.hash.uint32(@intFromEnum(key.tag)), + std.mem.asBytes(&key.extra), + )); + } + pub fn eql(ctx: @This(), lhs_key: Key, _: void, rhs_index: usize) bool { + if (lhs_key.tag != ctx.builder.constant_items.items(.tag)[rhs_index]) return false; + const rhs_data = ctx.builder.constant_items.items(.data)[rhs_index]; + const rhs_extra = ctx.builder.constantExtraData(Constant.Asm, rhs_data); + return std.meta.eql(lhs_key.extra, rhs_extra); + } + }; + + const data = Key{ + .tag = @enumFromInt(@intFromEnum(Constant.Tag.@"asm") + @as(u4, @bitCast(info))), + .extra = .{ .type = ty, .assembly = assembly, .constraints = constraints }, + }; + const gop = self.constant_map.getOrPutAssumeCapacityAdapted(data, Adapter{ .builder = self }); + if (!gop.found_existing) { + gop.key_ptr.* = {}; + gop.value_ptr.* = {}; + self.constant_items.appendAssumeCapacity(.{ + .tag = data.tag, + .data = self.addConstantExtraAssumeCapacity(data.extra), + }); + if (self.useLibLlvm()) { + const assembly_slice = assembly.slice(self).?; + const constraints_slice = constraints.slice(self).?; + self.llvm.constants.appendAssumeCapacity(llvm.getInlineAsm( + ty.toLlvm(self), + assembly_slice.ptr, + assembly_slice.len, + constraints_slice.ptr, + constraints_slice.len, + llvm.Bool.fromBool(info.sideeffect), + llvm.Bool.fromBool(info.alignstack), + if (info.inteldialect) .Intel else .ATT, + llvm.Bool.fromBool(info.unwind), + )); + } + } + return @enumFromInt(gop.index); +} + fn ensureUnusedConstantCapacity( self: *Builder, count: usize, @@ -7868,7 +9548,7 @@ fn addConstantExtraAssumeCapacity(self: *Builder, extra: anytype) Constant.Item. const value = @field(extra, field.name); self.constant_extra.appendAssumeCapacity(switch (field.type) { u32 => value, - Type, Constant, Function.Index, Function.Block.Index => @intFromEnum(value), + String, Type, Constant, Function.Index, Function.Block.Index => @intFromEnum(value), Constant.GetElementPtr.Info => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }); @@ -7907,7 +9587,7 @@ fn constantExtraDataTrail( inline for (fields, self.constant_extra.items[index..][0..fields.len]) |field, value| @field(result, field.name) = switch (field.type) { u32 => value, - Type, Constant, Function.Index, Function.Block.Index => @enumFromInt(value), + String, Type, Constant, Function.Index, Function.Block.Index => @enumFromInt(value), Constant.GetElementPtr.Info => @bitCast(value), else => @compileError("bad field type: " ++ @typeName(field.type)), }; diff --git a/src/codegen/llvm/bindings.zig b/src/codegen/llvm/bindings.zig index 3b99ae1fe14b..aee9cea384c3 100644 --- a/src/codegen/llvm/bindings.zig +++ b/src/codegen/llvm/bindings.zig @@ -26,10 +26,13 @@ pub const Context = opaque { extern fn LLVMContextDispose(C: *Context) void; pub const createEnumAttribute = LLVMCreateEnumAttribute; - extern fn LLVMCreateEnumAttribute(*Context, KindID: c_uint, Val: u64) *Attribute; + extern fn LLVMCreateEnumAttribute(C: *Context, KindID: c_uint, Val: u64) *Attribute; + + pub const createTypeAttribute = LLVMCreateTypeAttribute; + extern fn LLVMCreateTypeAttribute(C: *Context, KindID: c_uint, Type: *Type) *Attribute; pub const createStringAttribute = LLVMCreateStringAttribute; - extern fn LLVMCreateStringAttribute(*Context, Key: [*]const u8, Key_Len: c_uint, Value: [*]const u8, Value_Len: c_uint) *Attribute; + extern fn LLVMCreateStringAttribute(C: *Context, Key: [*]const u8, Key_Len: c_uint, Value: [*]const u8, Value_Len: c_uint) *Attribute; pub const pointerType = LLVMPointerTypeInContext; extern fn LLVMPointerTypeInContext(C: *Context, AddressSpace: c_uint) *Type; @@ -309,12 +312,18 @@ pub const Value = opaque { pub const setAlignment = LLVMSetAlignment; extern fn LLVMSetAlignment(V: *Value, Bytes: c_uint) void; - pub const getFunctionCallConv = LLVMGetFunctionCallConv; - extern fn LLVMGetFunctionCallConv(Fn: *Value) CallConv; - pub const setFunctionCallConv = LLVMSetFunctionCallConv; extern fn LLVMSetFunctionCallConv(Fn: *Value, CC: CallConv) void; + pub const setInstructionCallConv = LLVMSetInstructionCallConv; + extern fn LLVMSetInstructionCallConv(Instr: *Value, CC: CallConv) void; + + pub const setTailCallKind = ZigLLVMSetTailCallKind; + extern fn ZigLLVMSetTailCallKind(CallInst: *Value, TailCallKind: TailCallKind) void; + + pub const addCallSiteAttribute = LLVMAddCallSiteAttribute; + extern fn LLVMAddCallSiteAttribute(C: *Value, Idx: AttributeIndex, A: *Attribute) void; + pub const fnSetSubprogram = ZigLLVMFnSetSubprogram; extern fn ZigLLVMFnSetSubprogram(f: *Value, subprogram: *DISubprogram) void; @@ -531,7 +540,7 @@ pub const Module = opaque { pub const createDIBuilder = ZigLLVMCreateDIBuilder; extern fn ZigLLVMCreateDIBuilder(module: *Module, allow_unresolved: bool) *DIBuilder; - pub const setModuleInlineAsm2 = LLVMSetModuleInlineAsm2; + pub const setModuleInlineAsm = LLVMSetModuleInlineAsm2; extern fn LLVMSetModuleInlineAsm2(M: *Module, Asm: [*]const u8, Len: usize) void; pub const printModuleToFile = LLVMPrintModuleToFile; @@ -642,7 +651,17 @@ pub const Builder = opaque { Name: [*:0]const u8, ) *Value; - pub const buildCall = ZigLLVMBuildCall; + pub const buildCall = LLVMBuildCall2; + extern fn LLVMBuildCall2( + *Builder, + *Type, + Fn: *Value, + Args: [*]const *Value, + NumArgs: c_uint, + Name: [*:0]const u8, + ) *Value; + + pub const buildCallOld = ZigLLVMBuildCall; extern fn ZigLLVMBuildCall( *Builder, *Type, @@ -1605,6 +1624,13 @@ pub const CallAttr = enum(c_int) { AlwaysInline, }; +pub const TailCallKind = enum(c_uint) { + None, + Tail, + MustTail, + NoTail, +}; + pub const DLLStorageClass = enum(c_uint) { Default, DLLImport, diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index 26ea04aca616..d22bfc1b4765 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -453,6 +453,10 @@ LLVMValueRef ZigLLVMBuildCall(LLVMBuilderRef B, LLVMTypeRef Ty, LLVMValueRef Fn, return wrap(call_inst); } +ZIG_EXTERN_C void ZigLLVMSetTailCallKind(LLVMValueRef Call, CallInst::TailCallKind TailCallKind) { + unwrap(Call)->setTailCallKind(TailCallKind); +} + void ZigLLVMAddAttributeAtIndex(LLVMValueRef Val, unsigned Idx, LLVMAttributeRef A) { if (isa(unwrap(Val))) { unwrap(Val)->addAttributeAtIndex(Idx, unwrap(A)); @@ -461,7 +465,6 @@ void ZigLLVMAddAttributeAtIndex(LLVMValueRef Val, unsigned Idx, LLVMAttributeRef } } - LLVMValueRef ZigLLVMBuildMemCpy(LLVMBuilderRef B, LLVMValueRef Dst, unsigned DstAlign, LLVMValueRef Src, unsigned SrcAlign, LLVMValueRef Size, bool isVolatile) { @@ -1116,11 +1119,6 @@ void ZigLLVMAddFunctionAttr(LLVMValueRef fn_ref, const char *attr_name, const ch func->addFnAttr(attr_name, attr_value); } -void ZigLLVMAddFunctionAttrCold(LLVMValueRef fn_ref) { - Function *func = unwrap(fn_ref); - func->addFnAttr(Attribute::Cold); -} - void ZigLLVMParseCommandLineOptions(size_t argc, const char *const *argv) { cl::ParseCommandLineOptions(argc, argv); } diff --git a/test/cases.zig b/test/cases.zig index ffe046c70ede..6c4b1c994f3a 100644 --- a/test/cases.zig +++ b/test/cases.zig @@ -4,5 +4,6 @@ const Cases = @import("src/Cases.zig"); pub fn addCases(cases: *Cases) !void { try @import("compile_errors.zig").addCases(cases); try @import("cbe.zig").addCases(cases); + try @import("llvm_targets.zig").addCases(cases); try @import("nvptx.zig").addCases(cases); } diff --git a/test/llvm_targets.zig b/test/llvm_targets.zig new file mode 100644 index 000000000000..d042ce500cfb --- /dev/null +++ b/test/llvm_targets.zig @@ -0,0 +1,117 @@ +const std = @import("std"); +const Cases = @import("src/Cases.zig"); + +const targets = [_]std.zig.CrossTarget{ + .{ .cpu_arch = .aarch64, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .aarch64, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .aarch64, .os_tag = .macos, .abi = .none }, + .{ .cpu_arch = .aarch64, .os_tag = .uefi, .abi = .none }, + .{ .cpu_arch = .aarch64, .os_tag = .windows, .abi = .gnu }, + .{ .cpu_arch = .aarch64, .os_tag = .windows, .abi = .msvc }, + .{ .cpu_arch = .aarch64_be, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .aarch64_be, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .aarch64_32, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .aarch64_32, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .amdgcn, .os_tag = .amdhsa, .abi = .none }, + .{ .cpu_arch = .amdgcn, .os_tag = .amdpal, .abi = .none }, + .{ .cpu_arch = .amdgcn, .os_tag = .linux, .abi = .none }, + //.{ .cpu_arch = .amdgcn, .os_tag = .mesa3d, .abi = .none }, + .{ .cpu_arch = .arm, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .arm, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .arm, .os_tag = .uefi, .abi = .none }, + .{ .cpu_arch = .armeb, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .armeb, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .avr, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .avr, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .avr, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .bpfel, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .bpfel, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .bpfeb, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .bpfeb, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .hexagon, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .mips, .os_tag = .linux, .abi = .gnueabihf }, + .{ .cpu_arch = .mips, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .mips, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .mipsel, .os_tag = .linux, .abi = .gnueabihf }, + .{ .cpu_arch = .mipsel, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .mipsel, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .mips64, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .mips64el, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .msp430, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .msp430, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .msp430, .os_tag = .linux, .abi = .none }, + //.{ .cpu_arch = .nvptx, .os_tag = .cuda, .abi = .none }, + //.{ .cpu_arch = .nvptx64, .os_tag = .cuda, .abi = .none }, + .{ .cpu_arch = .powerpc, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .powerpc, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .powerpc, .os_tag = .linux, .abi = .gnueabihf }, + .{ .cpu_arch = .powerpc, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .powerpc, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .powerpcle, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .powerpcle, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .powerpcle, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .powerpcle, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .powerpcle, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .powerpc64, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .powerpc64, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .powerpc64, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .powerpc64, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .powerpc64, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .powerpc64le, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .powerpc64le, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .powerpc64le, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .powerpc64le, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .powerpc64le, .os_tag = .linux, .abi = .none }, + //.{ .cpu_arch = .r600, .os_tag = .mesa3d, .abi = .none }, + .{ .cpu_arch = .riscv32, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .riscv32, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .riscv64, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .riscv64, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .riscv64, .os_tag = .linux, .abi = .musl }, + .{ .cpu_arch = .riscv64, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .s390x, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .s390x, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .sparc, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .sparc, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .sparc, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .sparcel, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .sparcel, .os_tag = .linux, .abi = .gnu }, + .{ .cpu_arch = .sparc64, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .sparc64, .os_tag = .linux, .abi = .gnu }, + //.{ .cpu_arch = .spirv32, .os_tag = .opencl, .abi = .none }, + //.{ .cpu_arch = .spirv32, .os_tag = .glsl450, .abi = .none }, + //.{ .cpu_arch = .spirv32, .os_tag = .vulkan, .abi = .none }, + //.{ .cpu_arch = .spirv64, .os_tag = .opencl, .abi = .none }, + //.{ .cpu_arch = .spirv64, .os_tag = .glsl450, .abi = .none }, + //.{ .cpu_arch = .spirv64, .os_tag = .vulkan, .abi = .none }, + .{ .cpu_arch = .thumb, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .thumbeb, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .ve, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .wasm32, .os_tag = .emscripten, .abi = .none }, + .{ .cpu_arch = .wasm32, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .wasm32, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .wasm32, .os_tag = .wasi, .abi = .none }, + .{ .cpu_arch = .wasm64, .os_tag = .emscripten, .abi = .none }, + .{ .cpu_arch = .wasm64, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .wasm64, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .wasm64, .os_tag = .wasi, .abi = .none }, + .{ .cpu_arch = .x86, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .x86, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .x86, .os_tag = .uefi, .abi = .none }, + .{ .cpu_arch = .x86, .os_tag = .windows, .abi = .gnu }, + .{ .cpu_arch = .x86, .os_tag = .windows, .abi = .msvc }, + .{ .cpu_arch = .x86_64, .os_tag = .freebsd, .abi = .none }, + .{ .cpu_arch = .x86_64, .os_tag = .freestanding, .abi = .none }, + .{ .cpu_arch = .x86_64, .os_tag = .linux, .abi = .none }, + .{ .cpu_arch = .x86_64, .os_tag = .macos, .abi = .none }, + .{ .cpu_arch = .x86_64, .os_tag = .uefi, .abi = .none }, + .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .gnu }, + .{ .cpu_arch = .x86_64, .os_tag = .windows, .abi = .msvc }, +}; + +pub fn addCases(ctx: *Cases) !void { + for (targets) |target| { + var case = ctx.noEmitUsingLlvmBackend("llvm_targets", target); + case.addCompile(""); + } +} diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 92ec3cb35f96..ce080b5490f8 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -76,6 +76,7 @@ pub const Case = struct { output_mode: std.builtin.OutputMode, optimize_mode: std.builtin.Mode = .Debug, updates: std.ArrayList(Update), + emit_bin: bool = true, emit_h: bool = false, is_test: bool = false, expect_exact: bool = false, @@ -176,6 +177,19 @@ pub fn exeFromCompiledC(ctx: *Cases, name: []const u8, target: CrossTarget) *Cas return &ctx.cases.items[ctx.cases.items.len - 1]; } +pub fn noEmitUsingLlvmBackend(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { + ctx.cases.append(Case{ + .name = name, + .target = target, + .updates = std.ArrayList(Update).init(ctx.cases.allocator), + .output_mode = .Obj, + .emit_bin = false, + .deps = std.ArrayList(DepModule).init(ctx.arena), + .backend = .llvm, + }) catch @panic("out of memory"); + return &ctx.cases.items[ctx.cases.items.len - 1]; +} + /// Adds a test case that uses the LLVM backend to emit an executable. /// Currently this implies linking libc, because only then we can generate a testable executable. pub fn exeUsingLlvmBackend(ctx: *Cases, name: []const u8, target: CrossTarget) *Case { @@ -537,6 +551,8 @@ pub fn lowerToBuildSteps( }), }; + artifact.emit_bin = if (case.emit_bin) .default else .no_emit; + if (case.link_libc) artifact.linkLibC(); switch (case.backend) {