mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-04-26 13:01:34 +03:00
4901 lines
219 KiB
Zig
4901 lines
219 KiB
Zig
const builtin = @import("builtin");
|
|
|
|
const FuncGen = @import("llvm/FuncGen.zig");
|
|
const buildAllocaInner = FuncGen.buildAllocaInner;
|
|
const isByRef = FuncGen.isByRef;
|
|
const firstParamSRet = FuncGen.firstParamSRet;
|
|
const lowerFnRetTy = FuncGen.lowerFnRetTy;
|
|
const iterateParamTypes = FuncGen.iterateParamTypes;
|
|
const ccAbiPromoteInt = FuncGen.ccAbiPromoteInt;
|
|
const aarch64_c_abi = @import("aarch64/abi.zig");
|
|
|
|
const std = @import("std");
|
|
const Io = std.Io;
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const log = std.log.scoped(.codegen);
|
|
const DW = std.dwarf;
|
|
const Builder = std.zig.llvm.Builder;
|
|
|
|
const build_options = @import("build_options");
|
|
const bindings = if (build_options.have_llvm)
|
|
@import("llvm/bindings.zig")
|
|
else
|
|
@compileError("LLVM unavailable");
|
|
|
|
const link = @import("../link.zig");
|
|
const Compilation = @import("../Compilation.zig");
|
|
const Zcu = @import("../Zcu.zig");
|
|
const InternPool = @import("../InternPool.zig");
|
|
const Package = @import("../Package.zig");
|
|
const Air = @import("../Air.zig");
|
|
const Value = @import("../Value.zig");
|
|
const Type = @import("../Type.zig");
|
|
const codegen = @import("../codegen.zig");
|
|
const dev = @import("../dev.zig");
|
|
|
|
const target_util = @import("../target.zig");
|
|
|
|
pub fn legalizeFeatures(_: *const std.Target) ?*const Air.Legalize.Features {
|
|
return comptime &.initMany(&.{
|
|
.expand_int_from_float_safe,
|
|
.expand_int_from_float_optimized_safe,
|
|
});
|
|
}
|
|
|
|
fn subArchName(target: *const std.Target, comptime family: std.Target.Cpu.Arch.Family, mappings: anytype) ?[]const u8 {
|
|
inline for (mappings) |mapping| {
|
|
if (target.cpu.has(family, mapping[0])) return mapping[1];
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn targetTriple(allocator: Allocator, target: *const std.Target) ![]const u8 {
|
|
var llvm_triple = std.array_list.Managed(u8).init(allocator);
|
|
defer llvm_triple.deinit();
|
|
|
|
const llvm_arch = switch (target.cpu.arch) {
|
|
.arm => "arm",
|
|
.armeb => "armeb",
|
|
.aarch64 => if (target.abi == .ilp32) "aarch64_32" else "aarch64",
|
|
.aarch64_be => "aarch64_be",
|
|
.arc => "arc",
|
|
.avr => "avr",
|
|
.bpfel => "bpfel",
|
|
.bpfeb => "bpfeb",
|
|
.csky => "csky",
|
|
.hexagon => "hexagon",
|
|
.loongarch32 => "loongarch32",
|
|
.loongarch64 => "loongarch64",
|
|
.m68k => "m68k",
|
|
// MIPS sub-architectures are a bit irregular, so we handle them manually here.
|
|
.mips => if (target.cpu.has(.mips, .mips32r6)) "mipsisa32r6" else "mips",
|
|
.mipsel => if (target.cpu.has(.mips, .mips32r6)) "mipsisa32r6el" else "mipsel",
|
|
.mips64 => if (target.cpu.has(.mips, .mips64r6)) "mipsisa64r6" else "mips64",
|
|
.mips64el => if (target.cpu.has(.mips, .mips64r6)) "mipsisa64r6el" else "mips64el",
|
|
.msp430 => "msp430",
|
|
.powerpc => "powerpc",
|
|
.powerpcle => "powerpcle",
|
|
.powerpc64 => "powerpc64",
|
|
.powerpc64le => "powerpc64le",
|
|
.amdgcn => "amdgcn",
|
|
.riscv32 => "riscv32",
|
|
.riscv32be => "riscv32be",
|
|
.riscv64 => "riscv64",
|
|
.riscv64be => "riscv64be",
|
|
.sparc => "sparc",
|
|
.sparc64 => "sparc64",
|
|
.s390x => "s390x",
|
|
.thumb => "thumb",
|
|
.thumbeb => "thumbeb",
|
|
.x86 => "i386",
|
|
.x86_64 => "x86_64",
|
|
.xcore => "xcore",
|
|
.xtensa => "xtensa",
|
|
.nvptx => "nvptx",
|
|
.nvptx64 => "nvptx64",
|
|
.spirv32 => switch (target.os.tag) {
|
|
.vulkan, .opengl => "spirv",
|
|
else => "spirv32",
|
|
},
|
|
.spirv64 => "spirv64",
|
|
.lanai => "lanai",
|
|
.wasm32 => "wasm32",
|
|
.wasm64 => "wasm64",
|
|
.ve => "ve",
|
|
|
|
.alpha,
|
|
.arceb,
|
|
.ez80,
|
|
.hppa,
|
|
.hppa64,
|
|
.kalimba,
|
|
.kvx,
|
|
.microblaze,
|
|
.microblazeel,
|
|
.or1k,
|
|
.propeller,
|
|
.sh,
|
|
.sheb,
|
|
.x86_16,
|
|
.xtensaeb,
|
|
=> unreachable, // Gated by hasLlvmSupport().
|
|
};
|
|
|
|
try llvm_triple.appendSlice(llvm_arch);
|
|
|
|
const llvm_sub_arch: ?[]const u8 = switch (target.cpu.arch) {
|
|
.arm, .armeb, .thumb, .thumbeb => subArchName(target, .arm, .{
|
|
.{ .v4t, "v4t" },
|
|
.{ .v5t, "v5t" },
|
|
.{ .v5te, "v5te" },
|
|
.{ .v5tej, "v5tej" },
|
|
.{ .v6, "v6" },
|
|
.{ .v6k, "v6k" },
|
|
.{ .v6kz, "v6kz" },
|
|
.{ .v6m, "v6m" },
|
|
.{ .v6t2, "v6t2" },
|
|
.{ .v7a, "v7a" },
|
|
.{ .v7em, "v7em" },
|
|
.{ .v7m, "v7m" },
|
|
.{ .v7r, "v7r" },
|
|
.{ .v7ve, "v7ve" },
|
|
.{ .v8a, "v8a" },
|
|
.{ .v8_1a, "v8.1a" },
|
|
.{ .v8_2a, "v8.2a" },
|
|
.{ .v8_3a, "v8.3a" },
|
|
.{ .v8_4a, "v8.4a" },
|
|
.{ .v8_5a, "v8.5a" },
|
|
.{ .v8_6a, "v8.6a" },
|
|
.{ .v8_7a, "v8.7a" },
|
|
.{ .v8_8a, "v8.8a" },
|
|
.{ .v8_9a, "v8.9a" },
|
|
.{ .v8m, "v8m.base" },
|
|
.{ .v8m_main, "v8m.main" },
|
|
.{ .v8_1m_main, "v8.1m.main" },
|
|
.{ .v8r, "v8r" },
|
|
.{ .v9a, "v9a" },
|
|
.{ .v9_1a, "v9.1a" },
|
|
.{ .v9_2a, "v9.2a" },
|
|
.{ .v9_3a, "v9.3a" },
|
|
.{ .v9_4a, "v9.4a" },
|
|
.{ .v9_5a, "v9.5a" },
|
|
.{ .v9_6a, "v9.6a" },
|
|
.{ .v9_7a, "v9.7a" },
|
|
}),
|
|
.powerpc => subArchName(target, .powerpc, .{
|
|
.{ .spe, "spe" },
|
|
}),
|
|
.spirv32, .spirv64 => subArchName(target, .spirv, .{
|
|
.{ .v1_6, "1.6" },
|
|
.{ .v1_5, "1.5" },
|
|
.{ .v1_4, "1.4" },
|
|
.{ .v1_3, "1.3" },
|
|
.{ .v1_2, "1.2" },
|
|
.{ .v1_1, "1.1" },
|
|
}),
|
|
else => null,
|
|
};
|
|
|
|
if (llvm_sub_arch) |sub| try llvm_triple.appendSlice(sub);
|
|
try llvm_triple.append('-');
|
|
|
|
try llvm_triple.appendSlice(switch (target.os.tag) {
|
|
.driverkit,
|
|
.ios,
|
|
.maccatalyst,
|
|
.macos,
|
|
.tvos,
|
|
.visionos,
|
|
.watchos,
|
|
=> "apple",
|
|
.ps4,
|
|
.ps5,
|
|
=> "scei",
|
|
.amdhsa,
|
|
.amdpal,
|
|
=> "amd",
|
|
.cuda,
|
|
.nvcl,
|
|
=> "nvidia",
|
|
.mesa3d,
|
|
=> "mesa",
|
|
else => "unknown",
|
|
});
|
|
try llvm_triple.append('-');
|
|
|
|
const llvm_os = switch (target.os.tag) {
|
|
.dragonfly => "dragonfly",
|
|
.freebsd => "freebsd",
|
|
.fuchsia => "fuchsia",
|
|
.linux => "linux",
|
|
.netbsd => "netbsd",
|
|
.openbsd => "openbsd",
|
|
.illumos => "solaris",
|
|
.windows, .uefi => "windows",
|
|
.haiku => "haiku",
|
|
.rtems => "rtems",
|
|
.cuda => "cuda",
|
|
.nvcl => "nvcl",
|
|
.amdhsa => "amdhsa",
|
|
.ps3 => "lv2",
|
|
.ps4 => "ps4",
|
|
.ps5 => "ps5",
|
|
.mesa3d => "mesa3d",
|
|
.amdpal => "amdpal",
|
|
.hermit => "hermit",
|
|
.hurd => "hurd",
|
|
.wasi => "wasi",
|
|
.emscripten => "emscripten",
|
|
.macos => "macosx",
|
|
.ios, .maccatalyst => "ios",
|
|
.tvos => "tvos",
|
|
.watchos => "watchos",
|
|
.driverkit => "driverkit",
|
|
.visionos => "xros",
|
|
.serenity => "serenity",
|
|
.vulkan => "vulkan",
|
|
.managarm => "managarm",
|
|
|
|
.@"3ds",
|
|
.contiki,
|
|
.freestanding,
|
|
.opencl, // https://llvm.org/docs/SPIRVUsage.html#target-triples
|
|
.opengl,
|
|
.other,
|
|
.plan9,
|
|
.psp,
|
|
.tios,
|
|
.vita,
|
|
=> "unknown",
|
|
};
|
|
try llvm_triple.appendSlice(llvm_os);
|
|
|
|
switch (target.os.versionRange()) {
|
|
.none,
|
|
.windows,
|
|
=> {},
|
|
.semver => |ver| if (target.os.tag == .wasi and ver.min.major == 0) {
|
|
try llvm_triple.print("p{d}", .{ver.min.minor});
|
|
} else {
|
|
try llvm_triple.print("{d}.{d}.{d}", .{
|
|
ver.min.major,
|
|
ver.min.minor,
|
|
ver.min.patch,
|
|
});
|
|
},
|
|
inline .linux, .hurd => |ver| try llvm_triple.print("{d}.{d}.{d}", .{
|
|
ver.range.min.major,
|
|
ver.range.min.minor,
|
|
ver.range.min.patch,
|
|
}),
|
|
}
|
|
try llvm_triple.append('-');
|
|
|
|
const llvm_abi = switch (target.abi) {
|
|
.none => if (target.os.tag == .maccatalyst) "macabi" else "unknown",
|
|
.gnu => "gnu",
|
|
.gnuabin32 => "gnuabin32",
|
|
.gnuabi64 => "gnuabi64",
|
|
.gnueabi => "gnueabi",
|
|
.gnueabihf => "gnueabihf",
|
|
.gnuf32 => "gnuf32",
|
|
.gnusf => "gnusf",
|
|
.gnux32 => "gnux32",
|
|
.ilp32 => "unknown",
|
|
.eabi => "eabi",
|
|
.eabihf => "eabihf",
|
|
.android => "android",
|
|
.androideabi => "androideabi",
|
|
.musl => switch (target.os.tag) {
|
|
// For WASI/Emscripten, "musl" refers to the libc, not really the ABI.
|
|
// "unknown" provides better compatibility with LLVM-based tooling for these targets.
|
|
.wasi, .emscripten => "unknown",
|
|
else => "musl",
|
|
},
|
|
.muslabin32 => "muslabin32",
|
|
.muslabi64 => "muslabi64",
|
|
.musleabi => "musleabi",
|
|
.musleabihf => "musleabihf",
|
|
.muslf32 => "muslf32",
|
|
.muslsf => "muslsf",
|
|
.muslx32 => "muslx32",
|
|
.msvc => "msvc",
|
|
.itanium => "itanium",
|
|
.simulator => "simulator",
|
|
.ohos, .ohoseabi => "ohos",
|
|
};
|
|
try llvm_triple.appendSlice(llvm_abi);
|
|
|
|
switch (target.os.versionRange()) {
|
|
.none,
|
|
.semver,
|
|
.windows,
|
|
=> {},
|
|
inline .hurd, .linux => |ver| if (target.abi.isGnu()) {
|
|
try llvm_triple.print("{d}.{d}.{d}", .{
|
|
ver.glibc.major,
|
|
ver.glibc.minor,
|
|
ver.glibc.patch,
|
|
});
|
|
} else if (@TypeOf(ver) == std.Target.Os.LinuxVersionRange and target.abi.isAndroid()) {
|
|
try llvm_triple.print("{d}", .{ver.android});
|
|
},
|
|
}
|
|
|
|
return llvm_triple.toOwnedSlice();
|
|
}
|
|
|
|
pub fn supportsTailCall(target: *const std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.wasm32, .wasm64 => target.cpu.has(.wasm, .tail_call),
|
|
// Although these ISAs support tail calls, LLVM does not support tail calls on them.
|
|
.mips, .mipsel, .mips64, .mips64el => false,
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => false,
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
pub fn dataLayout(target: *const std.Target) []const u8 {
|
|
// These data layouts should match Clang.
|
|
return switch (target.cpu.arch) {
|
|
.arc => "e-m:e-p:32:32-i1:8:32-i8:8:32-i16:16:32-i32:32:32-f32:32:32-i64:32-f64:32-a:0:32-n32",
|
|
.xcore => "e-m:e-p:32:32-i1:8:32-i8:8:32-i16:16:32-i64:32-f64:32-a:0:32-n32",
|
|
.hexagon => "e-m:e-p:32:32:32-a:0-n16:32-i64:64:64-i32:32:32-i16:16:16-i1:8:8-f32:32:32-f64:64:64-v32:32:32-v64:64:64-v512:512:512-v1024:1024:1024-v2048:2048:2048",
|
|
.lanai => "E-m:e-p:32:32-i64:64-a:0:32-n32-S64",
|
|
.aarch64 => if (target.ofmt == .macho)
|
|
if (target.os.tag == .windows or target.os.tag == .uefi)
|
|
"e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
|
|
else if (target.abi == .ilp32)
|
|
"e-m:o-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
|
|
else
|
|
"e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-n32:64-S128-Fn32"
|
|
else if (target.os.tag == .windows or target.os.tag == .uefi)
|
|
"e-m:w-p270:32:32-p271:32:32-p272:64:64-p:64:64-i32:32-i64:64-i128:128-n32:64-S128-Fn32"
|
|
else
|
|
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
|
|
.aarch64_be => "E-m:e-p270:32:32-p271:32:32-p272:64:64-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128-Fn32",
|
|
.arm => if (target.ofmt == .macho)
|
|
"e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
|
else
|
|
"e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
|
|
.armeb, .thumbeb => if (target.ofmt == .macho)
|
|
"E-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
|
else
|
|
"E-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
|
|
.thumb => if (target.ofmt == .macho)
|
|
"e-m:o-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
|
else if (target.os.tag == .windows or target.os.tag == .uefi)
|
|
"e-m:w-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64"
|
|
else
|
|
"e-m:e-p:32:32-Fi8-i64:64-v128:64:128-a:0:32-n32-S64",
|
|
.avr => "e-P1-p:16:8-i8:8-i16:8-i32:8-i64:8-f32:8-f64:8-n8-a:8",
|
|
.bpfeb => "E-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
|
|
.bpfel => "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
|
|
.msp430 => "e-m:e-p:16:16-i32:16-i64:16-f32:16-f64:16-a:8-n8:16-S16",
|
|
.mips => "E-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64",
|
|
.mipsel => "e-m:m-p:32:32-i8:8:32-i16:16:32-i64:64-n32-S64",
|
|
.mips64 => switch (target.abi) {
|
|
.gnuabin32, .muslabin32 => "E-m:e-p:32:32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
|
else => "E-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
|
},
|
|
.mips64el => switch (target.abi) {
|
|
.gnuabin32, .muslabin32 => "e-m:e-p:32:32-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
|
else => "e-m:e-i8:8:32-i16:16:32-i64:64-i128:128-n32:64-S128",
|
|
},
|
|
.m68k => "E-m:e-p:32:16:32-i8:8:8-i16:16:16-i32:16:32-n8:16:32-a:0:16-S16",
|
|
.powerpc => "E-m:e-p:32:32-Fn32-i64:64-n32",
|
|
.powerpcle => "e-m:e-p:32:32-Fn32-i64:64-n32",
|
|
.powerpc64 => switch (target.os.tag) {
|
|
.linux => "E-m:e-Fn32-i64:64-i128:128-n32:64-S128-v256:256:256-v512:512:512",
|
|
.ps3 => "E-m:e-p:32:32-Fi64-i64:64-i128:128-n32:64",
|
|
else => "E-m:e-Fn32-i64:64-i128:128-n32:64",
|
|
},
|
|
.powerpc64le => if (target.os.tag == .linux)
|
|
"e-m:e-Fn32-i64:64-i128:128-n32:64-S128-v256:256:256-v512:512:512"
|
|
else
|
|
"e-m:e-Fn32-i64:64-i128:128-n32:64",
|
|
.nvptx => "e-p:32:32-p6:32:32-p7:32:32-i64:64-i128:128-v16:16-v32:32-n16:32:64",
|
|
.nvptx64 => "e-p6:32:32-i64:64-i128:128-v16:16-v32:32-n16:32:64",
|
|
.amdgcn => "e-m:e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-p7:160:256:256:32-p8:128:128:128:48-p9:192:256:256:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5-G1-ni:7:8:9",
|
|
.riscv32 => if (target.cpu.has(.riscv, .e))
|
|
"e-m:e-p:32:32-i64:64-n32-S32"
|
|
else
|
|
"e-m:e-p:32:32-i64:64-n32-S128",
|
|
.riscv32be => if (target.cpu.has(.riscv, .e))
|
|
"E-m:e-p:32:32-i64:64-n32-S32"
|
|
else
|
|
"E-m:e-p:32:32-i64:64-n32-S128",
|
|
.riscv64 => if (target.cpu.has(.riscv, .e))
|
|
"e-m:e-p:64:64-i64:64-i128:128-n32:64-S64"
|
|
else
|
|
"e-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
|
|
.riscv64be => if (target.cpu.has(.riscv, .e))
|
|
"E-m:e-p:64:64-i64:64-i128:128-n32:64-S64"
|
|
else
|
|
"E-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
|
|
.sparc => "E-m:e-p:32:32-i64:64-i128:128-f128:64-n32-S64",
|
|
.sparc64 => "E-m:e-i64:64-i128:128-n32:64-S128",
|
|
.s390x => "E-m:e-i1:8:16-i8:8:16-i64:64-f128:64-v128:64-a:8:16-n32:64",
|
|
.x86 => if (target.os.tag == .windows or target.os.tag == .uefi) switch (target.abi) {
|
|
.gnu => if (target.ofmt == .coff)
|
|
"e-m:x-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:32-n8:16:32-a:0:32-S32"
|
|
else
|
|
"e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:32-n8:16:32-a:0:32-S32",
|
|
else => blk: {
|
|
const msvc = switch (target.abi) {
|
|
.none, .msvc => true,
|
|
else => false,
|
|
};
|
|
|
|
break :blk if (target.ofmt == .coff)
|
|
if (msvc)
|
|
"e-m:x-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32-a:0:32-S32"
|
|
else
|
|
"e-m:x-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:32-n8:16:32-a:0:32-S32"
|
|
else if (msvc)
|
|
"e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32-a:0:32-S32"
|
|
else
|
|
"e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:32-n8:16:32-a:0:32-S32";
|
|
},
|
|
} else if (target.ofmt == .macho)
|
|
"e-m:o-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128"
|
|
else
|
|
"e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i128:128-f64:32:64-f80:32-n8:16:32-S128",
|
|
.x86_64 => if (target.os.tag.isDarwin() or target.ofmt == .macho)
|
|
"e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
|
|
else switch (target.abi) {
|
|
.gnux32, .muslx32 => "e-m:e-p:32:32-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
|
else => if ((target.os.tag == .windows or target.os.tag == .uefi) and target.ofmt == .coff)
|
|
"e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
|
|
else
|
|
"e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
|
|
},
|
|
.spirv32 => switch (target.os.tag) {
|
|
.vulkan, .opengl => "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1",
|
|
else => "e-p:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1",
|
|
},
|
|
.spirv64 => "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-G1",
|
|
.wasm32 => if (target.os.tag == .emscripten)
|
|
"e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-f128:64-n32:64-S128-ni:1:10:20"
|
|
else
|
|
"e-m:e-p:32:32-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20",
|
|
.wasm64 => if (target.os.tag == .emscripten)
|
|
"e-m:e-p:64:64-p10:8:8-p20:8:8-i64:64-i128:128-f128:64-n32:64-S128-ni:1:10:20"
|
|
else
|
|
"e-m:e-p:64:64-p10:8:8-p20:8:8-i64:64-i128:128-n32:64-S128-ni:1:10:20",
|
|
.ve => "e-m:e-i64:64-n32:64-S128-v64:64:64-v128:64:64-v256:64:64-v512:64:64-v1024:64:64-v2048:64:64-v4096:64:64-v8192:64:64-v16384:64:64",
|
|
.csky => "e-m:e-S32-p:32:32-i32:32:32-i64:32:32-f32:32:32-f64:32:32-v64:32:32-v128:32:32-a:0:32-Fi32-n32",
|
|
.loongarch32 => "e-m:e-p:32:32-i64:64-n32-S128",
|
|
.loongarch64 => "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128",
|
|
.xtensa => "e-m:e-p:32:32-i8:8:32-i16:16:32-i64:64-n32",
|
|
|
|
.alpha,
|
|
.arceb,
|
|
.ez80,
|
|
.hppa,
|
|
.hppa64,
|
|
.kalimba,
|
|
.kvx,
|
|
.microblaze,
|
|
.microblazeel,
|
|
.or1k,
|
|
.propeller,
|
|
.sh,
|
|
.sheb,
|
|
.x86_16,
|
|
.xtensaeb,
|
|
=> unreachable, // Gated by hasLlvmSupport().
|
|
};
|
|
}
|
|
|
|
// Avoid depending on `bindings.CodeModel` in the bitcode-only case.
|
|
const CodeModel = enum {
|
|
default,
|
|
tiny,
|
|
small,
|
|
kernel,
|
|
medium,
|
|
large,
|
|
};
|
|
|
|
fn codeModel(model: std.builtin.CodeModel, target: *const std.Target) CodeModel {
|
|
// Roughly match Clang's mapping of GCC code models to LLVM code models.
|
|
return switch (model) {
|
|
.default => .default,
|
|
.extreme, .large => .large,
|
|
.kernel => .kernel,
|
|
.medany => if (target.cpu.arch.isRISCV()) .medium else .large,
|
|
.medium => .medium,
|
|
.medmid => .medium,
|
|
.normal, .medlow, .small => .small,
|
|
.tiny => .tiny,
|
|
};
|
|
}
|
|
|
|
pub const Object = struct {
|
|
gpa: Allocator,
|
|
builder: Builder,
|
|
|
|
/// This pool contains only types (and not `@as(type, undefined)`). It has two purposes:
|
|
///
|
|
/// * Lazily tracking ABI alignment of types, so that `@"align"` attributes can be set to a
|
|
/// type's ABI alignment before that type is fully resolved. Each type in the pool has a
|
|
/// corresponding entry in `lazy_abi_aligns`.
|
|
///
|
|
/// * If `!Object.builder.strip`, lazily tracking debug information types, so that debug
|
|
/// information can handle indirect self-reference (and so that debug information works
|
|
/// correctly across incremental updates). Each type has a corresponding entry in
|
|
/// `debug_types`, provided that `Object.builder.strip` is `false`.
|
|
type_pool: link.ConstPool,
|
|
|
|
/// Keyed on `link.ConstPool.Index`.
|
|
lazy_abi_aligns: std.ArrayList(Builder.Alignment.Lazy),
|
|
|
|
debug_compile_unit: Builder.Metadata.Optional,
|
|
|
|
debug_enums_fwd_ref: Builder.Metadata.Optional,
|
|
debug_globals_fwd_ref: Builder.Metadata.Optional,
|
|
|
|
debug_enums: std.ArrayList(Builder.Metadata),
|
|
debug_globals: std.ArrayList(Builder.Metadata),
|
|
|
|
debug_file_map: std.AutoHashMapUnmanaged(Zcu.File.Index, Builder.Metadata),
|
|
|
|
/// Keyed on `link.ConstPool.Index`.
|
|
debug_types: std.ArrayList(Builder.Metadata),
|
|
/// Initially `.none`, set if the type `anyerror` is lowered to a debug type. The type will not
|
|
/// actually be created until `emit`, which must resolve this reference with an appropriate enum
|
|
/// type from the global error set.
|
|
debug_anyerror_fwd_ref: Builder.Metadata.Optional,
|
|
|
|
zcu: *Zcu,
|
|
/// Maps a `Nav` to the corresponding LLVM global.
|
|
nav_map: std.AutoHashMapUnmanaged(InternPool.Nav.Index, Builder.Global.Index),
|
|
/// Same as `nav_map` but for UAVs (which are always global constants).
|
|
uav_map: std.AutoHashMapUnmanaged(struct {
|
|
val: InternPool.Index,
|
|
@"addrspace": std.builtin.AddressSpace,
|
|
}, Builder.Variable.Index),
|
|
/// Maps enum types to their corresponding LLVM functions for implementing the `tag_name` instruction.
|
|
enum_tag_name_map: std.AutoHashMapUnmanaged(InternPool.Index, Builder.Function.Index),
|
|
/// Serves the same purpose as `enum_tag_name_map` but for the `is_named_enum_value` instruction.
|
|
named_enum_map: std.AutoHashMapUnmanaged(InternPool.Index, Builder.Function.Index),
|
|
/// Maps Zig types to LLVM types. The table memory is backed by the GPA of
|
|
/// the compiler.
|
|
/// TODO when InternPool garbage collection is implemented, this map needs
|
|
/// to be garbage collected as well.
|
|
type_map: TypeMap,
|
|
/// The LLVM global table which holds the names corresponding to Zig errors.
|
|
/// Note that the values are not added until `emit`, when all errors in
|
|
/// the compilation are known.
|
|
error_name_table: Builder.Variable.Index,
|
|
/// Constant variable whose value is the number of errors in the Zcu.
|
|
///
|
|
/// Initially `.none`---populated lazily by `getErrorsLen`.
|
|
///
|
|
/// If this is not `.none`, the variable's initializer is set in `emit`.
|
|
errors_len_variable: Builder.Variable.Index,
|
|
|
|
/// Values for `@llvm.used`.
|
|
used: std.ArrayList(Builder.Constant),
|
|
|
|
pub const Ptr = if (dev.env.supports(.llvm_backend)) *Object else noreturn;
|
|
|
|
const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, Builder.Type);
|
|
|
|
pub fn create(arena: Allocator, zcu: *Zcu) !Ptr {
|
|
dev.check(.llvm_backend);
|
|
const comp = zcu.comp;
|
|
const gpa = comp.gpa;
|
|
const target = zcu.getTarget();
|
|
const llvm_target_triple = try targetTriple(arena, target);
|
|
|
|
var builder = try Builder.init(.{
|
|
.allocator = gpa,
|
|
.strip = comp.config.debug_format == .strip,
|
|
.name = comp.root_name,
|
|
.target = target,
|
|
.triple = llvm_target_triple,
|
|
});
|
|
errdefer builder.deinit();
|
|
|
|
builder.data_layout = try builder.string(dataLayout(target));
|
|
|
|
const debug_compile_unit, const debug_enums_fwd_ref, const debug_globals_fwd_ref =
|
|
if (!builder.strip) debug_info: {
|
|
// We fully resolve all paths at this point to avoid lack of
|
|
// source line info in stack traces or lack of debugging
|
|
// information which, if relative paths were used, would be
|
|
// very location dependent.
|
|
// TODO: the only concern I have with this is WASI as either host or target, should
|
|
// we leave the paths as relative then?
|
|
// TODO: This is totally wrong. In dwarf, paths are encoded as relative to
|
|
// a particular directory, and then the directory path is specified elsewhere.
|
|
// In the compiler frontend we have it stored correctly in this
|
|
// way already, but here we throw all that sweet information
|
|
// into the garbage can by converting into absolute paths. What
|
|
// a terrible tragedy.
|
|
const compile_unit_dir = try zcu.main_mod.root.toAbsolute(comp.dirs, arena);
|
|
|
|
const debug_file = try builder.debugFile(
|
|
try builder.metadataString(comp.root_name),
|
|
try builder.metadataString(compile_unit_dir),
|
|
);
|
|
|
|
const debug_enums_fwd_ref = try builder.debugForwardReference();
|
|
const debug_globals_fwd_ref = try builder.debugForwardReference();
|
|
|
|
const debug_compile_unit = try builder.debugCompileUnit(
|
|
debug_file,
|
|
// Don't use the version string here; LLVM misparses it when it
|
|
// includes the git revision.
|
|
try builder.metadataStringFmt("zig {d}.{d}.{d}", .{
|
|
build_options.semver.major,
|
|
build_options.semver.minor,
|
|
build_options.semver.patch,
|
|
}),
|
|
debug_enums_fwd_ref,
|
|
debug_globals_fwd_ref,
|
|
.{ .optimized = comp.root_mod.optimize_mode != .Debug },
|
|
);
|
|
|
|
try builder.addNamedMetadata(try builder.string("llvm.dbg.cu"), &.{debug_compile_unit});
|
|
break :debug_info .{
|
|
debug_compile_unit.toOptional(),
|
|
debug_enums_fwd_ref.toOptional(),
|
|
debug_globals_fwd_ref.toOptional(),
|
|
};
|
|
} else .{Builder.Metadata.Optional.none} ** 3;
|
|
|
|
const obj = try arena.create(Object);
|
|
obj.* = .{
|
|
.gpa = gpa,
|
|
.builder = builder,
|
|
.type_pool = .empty,
|
|
.lazy_abi_aligns = .empty,
|
|
.debug_compile_unit = debug_compile_unit,
|
|
.debug_enums_fwd_ref = debug_enums_fwd_ref,
|
|
.debug_globals_fwd_ref = debug_globals_fwd_ref,
|
|
.debug_enums = .empty,
|
|
.debug_globals = .empty,
|
|
.debug_file_map = .empty,
|
|
.debug_types = .empty,
|
|
.debug_anyerror_fwd_ref = .none,
|
|
.zcu = zcu,
|
|
.nav_map = .empty,
|
|
.uav_map = .empty,
|
|
.enum_tag_name_map = .empty,
|
|
.named_enum_map = .empty,
|
|
.type_map = .empty,
|
|
.error_name_table = .none,
|
|
.errors_len_variable = .none,
|
|
.used = .empty,
|
|
};
|
|
return obj;
|
|
}
|
|
|
|
pub fn deinit(self: *Object) void {
|
|
const gpa = self.gpa;
|
|
self.type_pool.deinit(gpa);
|
|
self.lazy_abi_aligns.deinit(gpa);
|
|
self.debug_enums.deinit(gpa);
|
|
self.debug_globals.deinit(gpa);
|
|
self.debug_file_map.deinit(gpa);
|
|
self.debug_types.deinit(gpa);
|
|
self.nav_map.deinit(gpa);
|
|
self.uav_map.deinit(gpa);
|
|
self.enum_tag_name_map.deinit(gpa);
|
|
self.named_enum_map.deinit(gpa);
|
|
self.type_map.deinit(gpa);
|
|
self.builder.deinit();
|
|
self.* = undefined;
|
|
}
|
|
|
|
fn genErrorNameTable(o: *Object) Allocator.Error!void {
|
|
// If o.error_name_table is null, then it was not referenced by any instructions.
|
|
if (o.error_name_table == .none) return;
|
|
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
|
|
const error_name_list = ip.global_error_set.getNamesFromMainThread();
|
|
const llvm_errors = try zcu.gpa.alloc(Builder.Constant, 1 + error_name_list.len);
|
|
defer zcu.gpa.free(llvm_errors);
|
|
|
|
// TODO: Address space
|
|
const slice_ty = Type.slice_const_u8_sentinel_0;
|
|
const llvm_usize_ty = try o.lowerType(.usize);
|
|
const llvm_slice_ty = try o.lowerType(slice_ty);
|
|
const llvm_table_ty = try o.builder.arrayType(1 + error_name_list.len, llvm_slice_ty);
|
|
|
|
llvm_errors[0] = try o.builder.undefConst(llvm_slice_ty);
|
|
for (llvm_errors[1..], error_name_list) |*llvm_error, name| {
|
|
const name_string = try o.builder.stringNull(name.toSlice(ip));
|
|
const name_init = try o.builder.stringConst(name_string);
|
|
const name_variable_index = try o.builder.addVariable(.empty, name_init.typeOf(&o.builder), .default);
|
|
try name_variable_index.setInitializer(name_init, &o.builder);
|
|
name_variable_index.setMutability(.constant, &o.builder);
|
|
name_variable_index.setAlignment(comptime .fromByteUnits(1), &o.builder);
|
|
const global_index = name_variable_index.ptrConst(&o.builder).global;
|
|
global_index.setLinkage(.private, &o.builder);
|
|
global_index.setUnnamedAddr(.unnamed_addr, &o.builder);
|
|
|
|
llvm_error.* = try o.builder.structConst(llvm_slice_ty, &.{
|
|
name_variable_index.toConst(&o.builder),
|
|
try o.builder.intConst(llvm_usize_ty, name_string.slice(&o.builder).?.len - 1),
|
|
});
|
|
}
|
|
|
|
try o.error_name_table.setInitializer(
|
|
try o.builder.arrayConst(llvm_table_ty, llvm_errors),
|
|
&o.builder,
|
|
);
|
|
}
|
|
|
|
fn genModuleLevelAssembly(object: *Object) Allocator.Error!void {
|
|
const b = &object.builder;
|
|
const gpa = b.gpa;
|
|
b.module_asm.clearRetainingCapacity();
|
|
for (object.zcu.global_assembly.values()) |assembly| {
|
|
try b.module_asm.ensureUnusedCapacity(gpa, assembly.len + 1);
|
|
b.module_asm.appendSliceAssumeCapacity(assembly);
|
|
b.module_asm.appendAssumeCapacity('\n');
|
|
}
|
|
if (b.module_asm.getLastOrNull()) |last| {
|
|
if (last != '\n') try b.module_asm.append(gpa, '\n');
|
|
}
|
|
}
|
|
|
|
pub const EmitOptions = struct {
|
|
pre_ir_path: ?[]const u8,
|
|
pre_bc_path: ?[]const u8,
|
|
bin_path: ?[:0]const u8,
|
|
asm_path: ?[:0]const u8,
|
|
post_ir_path: ?[:0]const u8,
|
|
post_bc_path: ?[]const u8,
|
|
|
|
is_debug: bool,
|
|
is_small: bool,
|
|
time_report: ?*Compilation.TimeReport,
|
|
sanitize_thread: bool,
|
|
fuzz: bool,
|
|
lto: std.zig.LtoMode,
|
|
};
|
|
|
|
pub fn emit(o: *Object, pt: Zcu.PerThread, options: EmitOptions) error{ LinkFailure, OutOfMemory }!void {
|
|
const zcu = o.zcu;
|
|
const comp = zcu.comp;
|
|
const io = comp.io;
|
|
const diags = &comp.link_diags;
|
|
|
|
{
|
|
if (o.errors_len_variable != .none) {
|
|
const errors_len = zcu.intern_pool.global_error_set.getNamesFromMainThread().len;
|
|
const init_val = try o.builder.intConst(try o.errorIntType(), errors_len);
|
|
try o.errors_len_variable.setInitializer(init_val, &o.builder);
|
|
}
|
|
try o.genErrorNameTable();
|
|
try o.genModuleLevelAssembly();
|
|
|
|
if (o.used.items.len > 0) {
|
|
const array_llvm_ty = try o.builder.arrayType(o.used.items.len, .ptr);
|
|
const init_val = try o.builder.arrayConst(array_llvm_ty, o.used.items);
|
|
const compiler_used_variable = try o.builder.addVariable(
|
|
try o.builder.strtabString("llvm.used"),
|
|
array_llvm_ty,
|
|
.default,
|
|
);
|
|
try compiler_used_variable.setInitializer(init_val, &o.builder);
|
|
compiler_used_variable.setSection(try o.builder.string("llvm.metadata"), &o.builder);
|
|
compiler_used_variable.ptrConst(&o.builder).global.setLinkage(.appending, &o.builder);
|
|
}
|
|
|
|
if (!o.builder.strip) {
|
|
if (o.debug_anyerror_fwd_ref.unwrap()) |fwd_ref| {
|
|
const debug_anyerror_type = try o.lowerDebugAnyerrorType();
|
|
o.builder.resolveDebugForwardReference(fwd_ref, debug_anyerror_type);
|
|
}
|
|
|
|
try o.flushTypePool(pt);
|
|
|
|
o.builder.resolveDebugForwardReference(
|
|
o.debug_enums_fwd_ref.unwrap().?,
|
|
try o.builder.metadataTuple(o.debug_enums.items),
|
|
);
|
|
|
|
o.builder.resolveDebugForwardReference(
|
|
o.debug_globals_fwd_ref.unwrap().?,
|
|
try o.builder.metadataTuple(o.debug_globals.items),
|
|
);
|
|
}
|
|
}
|
|
|
|
{
|
|
var module_flags = try std.array_list.Managed(Builder.Metadata).initCapacity(o.gpa, 8);
|
|
defer module_flags.deinit();
|
|
|
|
const behavior_error = try o.builder.metadataConstant(try o.builder.intConst(.i32, 1));
|
|
const behavior_warning = try o.builder.metadataConstant(try o.builder.intConst(.i32, 2));
|
|
const behavior_max = try o.builder.metadataConstant(try o.builder.intConst(.i32, 7));
|
|
const behavior_min = try o.builder.metadataConstant(try o.builder.intConst(.i32, 8));
|
|
|
|
if (target_util.llvmMachineAbi(&comp.root_mod.resolved_target.result)) |abi| {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_error,
|
|
(try o.builder.metadataString("target-abi")).toMetadata(),
|
|
(try o.builder.metadataString(abi)).toMetadata(),
|
|
}));
|
|
}
|
|
|
|
const pic_level = target_util.picLevel(&comp.root_mod.resolved_target.result);
|
|
if (comp.root_mod.pic) {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_min,
|
|
(try o.builder.metadataString("PIC Level")).toMetadata(),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i32, pic_level)),
|
|
}));
|
|
}
|
|
|
|
if (comp.config.pie) {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_max,
|
|
(try o.builder.metadataString("PIE Level")).toMetadata(),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i32, pic_level)),
|
|
}));
|
|
}
|
|
|
|
if (comp.root_mod.code_model != .default) {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_error,
|
|
(try o.builder.metadataString("Code Model")).toMetadata(),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i32, @as(
|
|
i32,
|
|
switch (codeModel(comp.root_mod.code_model, &comp.root_mod.resolved_target.result)) {
|
|
.default => unreachable,
|
|
.tiny => 0,
|
|
.small => 1,
|
|
.kernel => 2,
|
|
.medium => 3,
|
|
.large => 4,
|
|
},
|
|
))),
|
|
}));
|
|
}
|
|
|
|
if (!o.builder.strip) {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_warning,
|
|
(try o.builder.metadataString("Debug Info Version")).toMetadata(),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i32, 3)),
|
|
}));
|
|
|
|
switch (comp.config.debug_format) {
|
|
.strip => unreachable,
|
|
.dwarf => |f| {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_max,
|
|
(try o.builder.metadataString("Dwarf Version")).toMetadata(),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i32, 4)),
|
|
}));
|
|
|
|
if (f == .@"64") {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_max,
|
|
(try o.builder.metadataString("DWARF64")).toMetadata(),
|
|
try o.builder.metadataConstant(.@"1"),
|
|
}));
|
|
}
|
|
},
|
|
.code_view => {
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_warning,
|
|
(try o.builder.metadataString("CodeView")).toMetadata(),
|
|
try o.builder.metadataConstant(.@"1"),
|
|
}));
|
|
},
|
|
}
|
|
}
|
|
|
|
const target = &comp.root_mod.resolved_target.result;
|
|
if (target.os.tag == .windows and (target.cpu.arch == .x86_64 or target.cpu.arch == .x86)) {
|
|
// Add the "RegCallv4" flag so that any functions using `x86_regcallcc` use regcall
|
|
// v4, which is essentially a requirement on Windows. See corresponding logic in
|
|
// `toLlvmCallConvTag`.
|
|
module_flags.appendAssumeCapacity(try o.builder.metadataTuple(&.{
|
|
behavior_max,
|
|
(try o.builder.metadataString("RegCallv4")).toMetadata(),
|
|
try o.builder.metadataConstant(.@"1"),
|
|
}));
|
|
}
|
|
|
|
try o.builder.addNamedMetadata(try o.builder.string("llvm.module.flags"), module_flags.items);
|
|
}
|
|
|
|
const target_triple_sentinel =
|
|
try o.gpa.dupeZ(u8, o.builder.target_triple.slice(&o.builder).?);
|
|
defer o.gpa.free(target_triple_sentinel);
|
|
|
|
const emit_asm_msg = options.asm_path orelse "(none)";
|
|
const emit_bin_msg = options.bin_path orelse "(none)";
|
|
const post_llvm_ir_msg = options.post_ir_path orelse "(none)";
|
|
const post_llvm_bc_msg = options.post_bc_path orelse "(none)";
|
|
log.debug("emit LLVM object asm={s} bin={s} ir={s} bc={s}", .{
|
|
emit_asm_msg, emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg,
|
|
});
|
|
|
|
const context, const module = emit: {
|
|
if (options.pre_ir_path) |path| {
|
|
if (std.mem.eql(u8, path, "-")) {
|
|
o.builder.dump(io);
|
|
} else {
|
|
o.builder.printToFilePath(io, Io.Dir.cwd(), path) catch |err| {
|
|
log.err("failed printing LLVM module to \"{s}\": {t}", .{ path, err });
|
|
};
|
|
}
|
|
}
|
|
|
|
const bitcode = try o.builder.toBitcode(o.gpa, .{
|
|
.name = "zig",
|
|
.version = build_options.semver,
|
|
});
|
|
defer o.gpa.free(bitcode);
|
|
|
|
if (options.pre_bc_path) |path| {
|
|
var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err|
|
|
return diags.fail("failed to create '{s}': {t}", .{ path, err });
|
|
defer file.close(io);
|
|
|
|
const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
|
|
file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err|
|
|
return diags.fail("failed to write to '{s}': {t}", .{ path, err });
|
|
}
|
|
|
|
if (options.asm_path == null and options.bin_path == null and
|
|
options.post_ir_path == null and options.post_bc_path == null) return;
|
|
|
|
if (options.post_bc_path) |path| {
|
|
var file = Io.Dir.cwd().createFile(io, path, .{}) catch |err|
|
|
return diags.fail("failed to create '{s}': {t}", .{ path, err });
|
|
defer file.close(io);
|
|
|
|
const ptr: [*]const u8 = @ptrCast(bitcode.ptr);
|
|
file.writeStreamingAll(io, ptr[0..(bitcode.len * 4)]) catch |err|
|
|
return diags.fail("failed to write to '{s}': {t}", .{ path, err });
|
|
}
|
|
|
|
if (!build_options.have_llvm or !comp.config.use_lib_llvm) {
|
|
return diags.fail("emitting without libllvm not implemented", .{});
|
|
}
|
|
|
|
initializeLLVMTarget(comp.root_mod.resolved_target.result.cpu.arch);
|
|
|
|
const context: *bindings.Context = .create();
|
|
errdefer context.dispose();
|
|
|
|
const bitcode_memory_buffer = bindings.MemoryBuffer.createMemoryBufferWithMemoryRange(
|
|
@ptrCast(bitcode.ptr),
|
|
bitcode.len * 4,
|
|
"BitcodeBuffer",
|
|
bindings.Bool.False,
|
|
);
|
|
defer bitcode_memory_buffer.dispose();
|
|
|
|
context.enableBrokenDebugInfoCheck();
|
|
|
|
var module: *bindings.Module = undefined;
|
|
if (context.parseBitcodeInContext2(bitcode_memory_buffer, &module).toBool() or context.getBrokenDebugInfo()) {
|
|
return diags.fail("Failed to parse bitcode", .{});
|
|
}
|
|
break :emit .{ context, module };
|
|
};
|
|
defer context.dispose();
|
|
|
|
var target: *bindings.Target = undefined;
|
|
var error_message: [*:0]const u8 = undefined;
|
|
if (bindings.Target.getFromTriple(target_triple_sentinel, &target, &error_message).toBool()) {
|
|
defer bindings.disposeMessage(error_message);
|
|
return diags.fail("LLVM failed to parse '{s}': {s}", .{ target_triple_sentinel, error_message });
|
|
}
|
|
|
|
const optimize_mode = comp.root_mod.optimize_mode;
|
|
|
|
const opt_level: bindings.CodeGenOptLevel = if (optimize_mode == .Debug)
|
|
.None
|
|
else
|
|
.Aggressive;
|
|
|
|
const reloc_mode: bindings.RelocMode = if (comp.root_mod.pic)
|
|
.PIC
|
|
else if (comp.config.link_mode == .dynamic)
|
|
bindings.RelocMode.DynamicNoPIC
|
|
else
|
|
.Static;
|
|
|
|
const code_model: bindings.CodeModel = switch (codeModel(comp.root_mod.code_model, &comp.root_mod.resolved_target.result)) {
|
|
.default => .Default,
|
|
.tiny => .Tiny,
|
|
.small => .Small,
|
|
.kernel => .Kernel,
|
|
.medium => .Medium,
|
|
.large => .Large,
|
|
};
|
|
|
|
const float_abi: bindings.TargetMachine.FloatABI = if (comp.root_mod.resolved_target.result.abi.float() == .hard)
|
|
.Hard
|
|
else
|
|
.Soft;
|
|
|
|
var target_machine = bindings.TargetMachine.create(
|
|
target,
|
|
target_triple_sentinel,
|
|
if (comp.root_mod.resolved_target.result.cpu.model.llvm_name) |s| s.ptr else null,
|
|
comp.root_mod.resolved_target.llvm_cpu_features.?,
|
|
opt_level,
|
|
reloc_mode,
|
|
code_model,
|
|
comp.function_sections,
|
|
comp.data_sections,
|
|
float_abi,
|
|
if (target_util.llvmMachineAbi(&comp.root_mod.resolved_target.result)) |s| s.ptr else null,
|
|
target_util.useEmulatedTls(&comp.root_mod.resolved_target.result),
|
|
);
|
|
errdefer target_machine.dispose();
|
|
|
|
if (comp.llvm_opt_bisect_limit >= 0) {
|
|
context.setOptBisectLimit(comp.llvm_opt_bisect_limit);
|
|
}
|
|
|
|
// Unfortunately, LLVM shits the bed when we ask for both binary and assembly.
|
|
// So we call the entire pipeline multiple times if this is requested.
|
|
// var error_message: [*:0]const u8 = undefined;
|
|
var lowered_options: bindings.TargetMachine.EmitOptions = .{
|
|
.is_debug = options.is_debug,
|
|
.is_small = options.is_small,
|
|
.time_report_out = null, // set below to make sure it's only set for a single `emitToFile`
|
|
.tsan = options.sanitize_thread,
|
|
.lto = switch (options.lto) {
|
|
.none => .None,
|
|
.thin => .ThinPreLink,
|
|
.full => .FullPreLink,
|
|
},
|
|
.allow_fast_isel = true,
|
|
// LLVM's RISC-V backend for some reason enables the machine outliner by default even
|
|
// though it's clearly not ready and produces multiple miscompilations in our std tests.
|
|
.allow_machine_outliner = !comp.root_mod.resolved_target.result.cpu.arch.isRISCV(),
|
|
.asm_filename = null,
|
|
.bin_filename = if (options.bin_path) |x| x.ptr else null,
|
|
.llvm_ir_filename = if (options.post_ir_path) |x| x.ptr else null,
|
|
.bitcode_filename = null,
|
|
|
|
// `.coverage` value is only used when `.sancov` is enabled.
|
|
.sancov = options.fuzz or comp.config.san_cov_trace_pc_guard,
|
|
.coverage = .{
|
|
.CoverageType = .Edge,
|
|
// Works in tandem with Inline8bitCounters or InlineBoolFlag.
|
|
// Zig does not yet implement its own version of this but it
|
|
// needs to for better fuzzing logic.
|
|
.IndirectCalls = false,
|
|
.TraceBB = false,
|
|
.TraceCmp = false,
|
|
.TraceDiv = false,
|
|
.TraceGep = false,
|
|
.Use8bitCounters = false,
|
|
.TracePC = false,
|
|
.TracePCGuard = comp.config.san_cov_trace_pc_guard,
|
|
// Zig emits its own inline 8-bit counters instrumentation.
|
|
.Inline8bitCounters = false,
|
|
.InlineBoolFlag = false,
|
|
// Zig emits its own PC table instrumentation.
|
|
.PCTable = false,
|
|
.NoPrune = false,
|
|
// Workaround for https://github.com/llvm/llvm-project/pull/106464
|
|
.StackDepth = true,
|
|
.TraceLoads = false,
|
|
.TraceStores = false,
|
|
.CollectControlFlow = false,
|
|
},
|
|
};
|
|
if (options.asm_path != null and options.bin_path != null) {
|
|
if (target_machine.emitToFile(module, &error_message, &lowered_options)) {
|
|
defer bindings.disposeMessage(error_message);
|
|
return diags.fail("LLVM failed to emit bin={s} ir={s}: {s}", .{
|
|
emit_bin_msg, post_llvm_ir_msg, error_message,
|
|
});
|
|
}
|
|
lowered_options.bin_filename = null;
|
|
lowered_options.llvm_ir_filename = null;
|
|
}
|
|
|
|
var time_report_c_str: [*:0]u8 = undefined;
|
|
if (options.time_report != null) {
|
|
lowered_options.time_report_out = &time_report_c_str;
|
|
}
|
|
|
|
lowered_options.asm_filename = if (options.asm_path) |x| x.ptr else null;
|
|
if (target_machine.emitToFile(module, &error_message, &lowered_options)) {
|
|
defer bindings.disposeMessage(error_message);
|
|
return diags.fail("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
|
|
emit_asm_msg, emit_bin_msg, post_llvm_ir_msg, post_llvm_bc_msg, error_message,
|
|
});
|
|
}
|
|
if (options.time_report) |tr| {
|
|
defer std.c.free(time_report_c_str);
|
|
const time_report_data = std.mem.span(time_report_c_str);
|
|
assert(tr.llvm_pass_timings.len == 0);
|
|
tr.llvm_pass_timings = try comp.gpa.dupe(u8, time_report_data);
|
|
}
|
|
}
|
|
|
|
pub fn updateFunc(
|
|
o: *Object,
|
|
pt: Zcu.PerThread,
|
|
func_index: InternPool.Index,
|
|
air: *const Air,
|
|
liveness: *const ?Air.Liveness,
|
|
) Zcu.CodegenFailError!void {
|
|
const zcu = o.zcu;
|
|
const comp = zcu.comp;
|
|
const gpa = comp.gpa;
|
|
const ip = &zcu.intern_pool;
|
|
const func = zcu.funcInfo(func_index);
|
|
const nav = ip.getNav(func.owner_nav);
|
|
const file_scope = zcu.navFileScopeIndex(func.owner_nav);
|
|
const owner_mod = zcu.fileByIndex(file_scope).mod.?;
|
|
const fn_ty = Type.fromInterned(func.ty);
|
|
const fn_info = zcu.typeToFunc(fn_ty).?;
|
|
const target = &owner_mod.resolved_target.result;
|
|
|
|
const gop = try o.nav_map.getOrPut(gpa, func.owner_nav);
|
|
if (!gop.found_existing) {
|
|
errdefer assert(o.nav_map.remove(func.owner_nav));
|
|
// First time lowering this NAV! Create a fresh global.
|
|
const llvm_name = try o.builder.strtabString(nav.fqn.toSlice(ip));
|
|
gop.value_ptr.* = try o.builder.addGlobal(llvm_name, .{
|
|
.type = .void, // placeholder; populated below
|
|
.kind = .{ .alias = .none }, // placeholder; populated below
|
|
});
|
|
}
|
|
const llvm_global = gop.value_ptr.*;
|
|
|
|
const llvm_function: Builder.Function.Index = switch (llvm_global.ptrConst(&o.builder).kind) {
|
|
.function => |function| function, // re-use existing `Builder.Function`
|
|
.replaced, .alias, .variable => try llvm_global.toNewFunction(&o.builder),
|
|
};
|
|
{
|
|
const global = llvm_function.ptrConst(&o.builder).global.ptr(&o.builder);
|
|
global.type = try o.lowerType(fn_ty);
|
|
global.addr_space = toLlvmAddressSpace(nav.resolved.?.@"addrspace", target);
|
|
global.linkage = if (o.builder.strip) .private else .internal;
|
|
global.visibility = .default;
|
|
global.dll_storage_class = .default;
|
|
global.unnamed_addr = .unnamed_addr;
|
|
}
|
|
llvm_function.setAlignment(switch (nav.resolved.?.@"align") {
|
|
.none => fn_ty.abiAlignment(zcu).toLlvm(),
|
|
else => |a| a.toLlvm(),
|
|
}, &o.builder);
|
|
llvm_function.setSection(s: {
|
|
const section = nav.resolved.?.@"linksection".toSlice(ip) orelse break :s .none;
|
|
break :s try o.builder.string(section);
|
|
}, &o.builder);
|
|
try o.addLlvmFunctionAttributes(pt, func.owner_nav, llvm_function);
|
|
|
|
var attributes = try llvm_function.ptrConst(&o.builder).attributes.toWip(&o.builder);
|
|
defer attributes.deinit(&o.builder);
|
|
|
|
const func_analysis = func.analysisUnordered(ip);
|
|
if (func_analysis.is_noinline) {
|
|
try attributes.addFnAttr(.@"noinline", &o.builder);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.@"noinline");
|
|
}
|
|
|
|
if (func_analysis.branch_hint == .cold) {
|
|
try attributes.addFnAttr(.cold, &o.builder);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.cold);
|
|
}
|
|
|
|
if (owner_mod.sanitize_thread and !func_analysis.disable_instrumentation) {
|
|
try attributes.addFnAttr(.sanitize_thread, &o.builder);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.sanitize_thread);
|
|
}
|
|
const is_naked = fn_info.cc == .naked;
|
|
if (!func_analysis.disable_instrumentation and !is_naked) {
|
|
if (owner_mod.fuzz) {
|
|
try attributes.addFnAttr(.optforfuzzing, &o.builder);
|
|
}
|
|
_ = try attributes.removeFnAttr(.skipprofile);
|
|
_ = try attributes.removeFnAttr(.nosanitize_coverage);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.optforfuzzing);
|
|
try attributes.addFnAttr(.skipprofile, &o.builder);
|
|
try attributes.addFnAttr(.nosanitize_coverage, &o.builder);
|
|
}
|
|
|
|
const disable_intrinsics = func_analysis.disable_intrinsics or owner_mod.no_builtin;
|
|
if (disable_intrinsics) {
|
|
// The intent here is for compiler-rt and libc functions to not generate
|
|
// infinite recursion. For example, if we are compiling the memcpy function,
|
|
// 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(.{ .string = .{
|
|
.kind = try o.builder.string("no-builtins"),
|
|
.value = .empty,
|
|
} }, &o.builder);
|
|
}
|
|
|
|
// TODO: disable this if safety is off for the function scope
|
|
const ssp_buf_size = owner_mod.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);
|
|
}
|
|
|
|
// TODO: disable this if safety is off for the function scope
|
|
if (owner_mod.stack_check) {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("probe-stack"),
|
|
.value = try o.builder.string("__zig_probe_stack"),
|
|
} }, &o.builder);
|
|
} else if (target.os.tag == .uefi) {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("no-stack-arg-probe"),
|
|
.value = .empty,
|
|
} }, &o.builder);
|
|
}
|
|
|
|
var deinit_wip = true;
|
|
var wip = try Builder.WipFunction.init(&o.builder, .{
|
|
.function = llvm_function,
|
|
.strip = owner_mod.strip,
|
|
});
|
|
defer if (deinit_wip) wip.deinit();
|
|
wip.cursor = .{ .block = try wip.block(0, "Entry") };
|
|
|
|
// 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.
|
|
var args: std.ArrayList(Builder.Value) = .empty;
|
|
defer args.deinit(gpa);
|
|
|
|
const ret_ptr: Builder.Value, const err_ret_trace: Builder.Value = implicit_args: {
|
|
var it = iterateParamTypes(o, fn_info);
|
|
|
|
const ret_ptr: Builder.Value = if (firstParamSRet(fn_info, zcu, target)) param: {
|
|
const param = wip.arg(it.llvm_index);
|
|
it.llvm_index += 1;
|
|
break :param param;
|
|
} else .none;
|
|
|
|
const err_return_tracing = fn_info.cc == .auto and comp.config.any_error_tracing;
|
|
const err_ret_trace: Builder.Value = if (err_return_tracing) param: {
|
|
const param = wip.arg(it.llvm_index);
|
|
it.llvm_index += 1;
|
|
break :param param;
|
|
} else .none;
|
|
|
|
while (try it.next()) |lowering| {
|
|
try args.ensureUnusedCapacity(gpa, 1);
|
|
|
|
switch (lowering) {
|
|
.no_bits => continue,
|
|
.byval => {
|
|
assert(!it.byval_attr);
|
|
const param_index = it.zig_index - 1;
|
|
const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[param_index]);
|
|
const param = wip.arg(it.llvm_index - 1);
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
const alignment = param_ty.abiAlignment(zcu).toLlvm();
|
|
const param_llvm_ty = param.typeOfWip(&wip);
|
|
const arg_ptr = try buildAllocaInner(&wip, param_llvm_ty, alignment, target);
|
|
_ = try wip.store(.normal, param, arg_ptr, alignment);
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
args.appendAssumeCapacity(param);
|
|
}
|
|
},
|
|
.byref => {
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param = wip.arg(it.llvm_index - 1);
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
args.appendAssumeCapacity(param);
|
|
} else {
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const alignment = param_ty.abiAlignment(zcu).toLlvm();
|
|
args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, param, alignment, ""));
|
|
}
|
|
},
|
|
.byref_mut => {
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param = wip.arg(it.llvm_index - 1);
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
args.appendAssumeCapacity(param);
|
|
} else {
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const alignment = param_ty.abiAlignment(zcu).toLlvm();
|
|
args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, param, alignment, ""));
|
|
}
|
|
},
|
|
.abi_sized_int => {
|
|
assert(!it.byval_attr);
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param = wip.arg(it.llvm_index - 1);
|
|
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const alignment = param_ty.abiAlignment(zcu).toLlvm();
|
|
const arg_ptr = try buildAllocaInner(&wip, param_llvm_ty, alignment, target);
|
|
_ = try wip.store(.normal, param, arg_ptr, alignment);
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, arg_ptr, alignment, ""));
|
|
}
|
|
},
|
|
.slice => {
|
|
assert(!it.byval_attr);
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
assert(!isByRef(param_ty, zcu));
|
|
const slice_val = try wip.buildAggregate(
|
|
try o.lowerType(param_ty),
|
|
&.{ wip.arg(it.llvm_index - 2), wip.arg(it.llvm_index - 1) },
|
|
"",
|
|
);
|
|
args.appendAssumeCapacity(slice_val);
|
|
},
|
|
.multiple_llvm_types => {
|
|
assert(!it.byval_attr);
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param_alignment = param_ty.abiAlignment(zcu);
|
|
const llvm_ty = try o.builder.arrayType(it.offsets_buffer[it.types_len], .i8);
|
|
const arg_ptr = try buildAllocaInner(&wip, llvm_ty, param_alignment.toLlvm(), target);
|
|
const llvm_args_start = it.llvm_index - it.types_len;
|
|
for (llvm_args_start.., it.offsets_buffer[0..it.types_len]) |llvm_arg_index, offset| {
|
|
const param = wip.arg(@intCast(llvm_arg_index));
|
|
const part_ptr = try o.ptraddConst(&wip, arg_ptr, offset);
|
|
_ = try wip.store(.normal, param, part_ptr, param_alignment.offset(offset).toLlvm());
|
|
}
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, arg_ptr, param_alignment.toLlvm(), ""));
|
|
}
|
|
},
|
|
.float_array => {
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param = wip.arg(it.llvm_index - 1);
|
|
|
|
const alignment = param_ty.abiAlignment(zcu).toLlvm();
|
|
const arg_ptr = try buildAllocaInner(&wip, param_llvm_ty, alignment, target);
|
|
_ = try wip.store(.normal, param, arg_ptr, alignment);
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, arg_ptr, alignment, ""));
|
|
}
|
|
},
|
|
.i32_array, .i64_array => {
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param = wip.arg(it.llvm_index - 1);
|
|
|
|
const alignment = param_ty.abiAlignment(zcu).toLlvm();
|
|
const arg_ptr = try buildAllocaInner(&wip, param.typeOfWip(&wip), alignment, target);
|
|
_ = try wip.store(.normal, param, arg_ptr, alignment);
|
|
|
|
if (isByRef(param_ty, zcu)) {
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
args.appendAssumeCapacity(try wip.load(.normal, param_llvm_ty, arg_ptr, alignment, ""));
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
break :implicit_args .{ ret_ptr, err_ret_trace };
|
|
};
|
|
|
|
const file, const subprogram = if (!wip.strip) debug_info: {
|
|
const file = try o.getDebugFile(file_scope);
|
|
|
|
const line_number = zcu.navSrcLine(func.owner_nav) + 1;
|
|
const is_internal_linkage = ip.indexToKey(nav.resolved.?.value) != .@"extern";
|
|
const debug_decl_type = try o.getDebugType(pt, fn_ty);
|
|
|
|
const subprogram = try o.builder.debugSubprogram(
|
|
file,
|
|
try o.builder.metadataString(nav.name.toSlice(ip)),
|
|
try o.builder.metadataString(nav.fqn.toSlice(ip)),
|
|
line_number,
|
|
line_number + func.lbrace_line,
|
|
debug_decl_type,
|
|
.{
|
|
.di_flags = .{
|
|
.StaticMember = true,
|
|
.NoReturn = fn_info.return_type == .noreturn_type,
|
|
},
|
|
.sp_flags = .{
|
|
.Optimized = owner_mod.optimize_mode != .Debug,
|
|
.Definition = true,
|
|
.LocalToUnit = is_internal_linkage,
|
|
},
|
|
},
|
|
o.debug_compile_unit.unwrap().?,
|
|
);
|
|
llvm_function.setSubprogram(subprogram, &o.builder);
|
|
break :debug_info .{ file, subprogram };
|
|
} else .{undefined} ** 2;
|
|
|
|
const fuzz: ?FuncGen.Fuzz = f: {
|
|
if (!owner_mod.fuzz) break :f null;
|
|
if (func_analysis.disable_instrumentation) break :f null;
|
|
if (is_naked) break :f null;
|
|
if (comp.config.san_cov_trace_pc_guard) break :f null;
|
|
|
|
// The void type used here is a placeholder to be replaced with an
|
|
// array of the appropriate size after the POI count is known.
|
|
|
|
// Due to error "members of llvm.compiler.used must be named", this global needs a name.
|
|
const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
|
|
const counters_variable = try o.builder.addVariable(anon_name, .void, .default);
|
|
try o.used.append(gpa, counters_variable.toConst(&o.builder));
|
|
counters_variable.ptrConst(&o.builder).global.setLinkage(.private, &o.builder);
|
|
counters_variable.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
|
|
|
|
if (target.ofmt == .macho) {
|
|
counters_variable.setSection(try o.builder.string("__DATA,__sancov_cntrs"), &o.builder);
|
|
} else {
|
|
counters_variable.setSection(try o.builder.string("__sancov_cntrs"), &o.builder);
|
|
}
|
|
|
|
break :f .{
|
|
.counters_variable = counters_variable,
|
|
.pcs = .empty,
|
|
};
|
|
};
|
|
|
|
var fg: FuncGen = .{
|
|
.object = o,
|
|
.nav_index = func.owner_nav,
|
|
.pt = pt,
|
|
.gpa = gpa,
|
|
.air = air.*,
|
|
.liveness = liveness.*.?,
|
|
.wip = wip,
|
|
.is_naked = fn_info.cc == .naked,
|
|
.fuzz = fuzz,
|
|
.ret_ptr = ret_ptr,
|
|
.args = args.items,
|
|
.arg_index = 0,
|
|
.arg_inline_index = 0,
|
|
.func_inst_table = .empty,
|
|
.blocks = .empty,
|
|
.loops = .empty,
|
|
.switch_dispatch_info = .empty,
|
|
.sync_scope = if (owner_mod.single_threaded) .singlethread else .system,
|
|
.file = file,
|
|
.scope = subprogram,
|
|
.inlined_at = .none,
|
|
.base_line = zcu.navSrcLine(func.owner_nav),
|
|
.prev_dbg_line = 0,
|
|
.prev_dbg_column = 0,
|
|
.err_ret_trace = err_ret_trace,
|
|
.disable_intrinsics = disable_intrinsics,
|
|
.allowzero_access = false,
|
|
};
|
|
defer fg.deinit();
|
|
deinit_wip = false;
|
|
|
|
try fg.genBody(air.getMainBody(), .poi);
|
|
|
|
// If we saw any loads or stores involving `allowzero` pointers, we need to mark the whole
|
|
// function as considering null pointers valid so that LLVM's optimizers don't remove these
|
|
// operations on the assumption that they're undefined behavior.
|
|
if (fg.allowzero_access) {
|
|
try attributes.addFnAttr(.null_pointer_is_valid, &o.builder);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.null_pointer_is_valid);
|
|
}
|
|
|
|
llvm_function.setAttributes(try attributes.finish(&o.builder), &o.builder);
|
|
|
|
if (fg.fuzz) |*f| {
|
|
{
|
|
const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .i8);
|
|
f.counters_variable.ptrConst(&o.builder).global.ptr(&o.builder).type = array_llvm_ty;
|
|
const zero_init = try o.builder.zeroInitConst(array_llvm_ty);
|
|
try f.counters_variable.setInitializer(zero_init, &o.builder);
|
|
}
|
|
|
|
const array_llvm_ty = try o.builder.arrayType(f.pcs.items.len, .ptr);
|
|
const init_val = try o.builder.arrayConst(array_llvm_ty, f.pcs.items);
|
|
// Due to error "members of llvm.compiler.used must be named", this global needs a name.
|
|
const anon_name = try o.builder.strtabStringFmt("__sancov_gen_.{d}", .{o.used.items.len});
|
|
const pcs_variable = try o.builder.addVariable(anon_name, array_llvm_ty, .default);
|
|
try pcs_variable.setInitializer(init_val, &o.builder);
|
|
pcs_variable.setMutability(.constant, &o.builder);
|
|
pcs_variable.setSection(switch (target.ofmt) {
|
|
.macho => try o.builder.string("__DATA,__sancov_pcs1"),
|
|
else => try o.builder.string("__sancov_pcs1"),
|
|
}, &o.builder);
|
|
pcs_variable.setAlignment(Type.usize.abiAlignment(zcu).toLlvm(), &o.builder);
|
|
const pcs_global = pcs_variable.ptrConst(&o.builder).global;
|
|
pcs_global.setLinkage(.private, &o.builder);
|
|
try o.used.append(gpa, pcs_global.toConst());
|
|
}
|
|
|
|
try fg.wip.finish();
|
|
try o.flushTypePool(pt);
|
|
}
|
|
|
|
pub fn updateNav(o: *Object, pt: Zcu.PerThread, nav_id: InternPool.Nav.Index) !void {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const comp = zcu.comp;
|
|
const gpa = comp.gpa;
|
|
|
|
const nav = ip.getNav(nav_id);
|
|
const resolved = nav.resolved.?;
|
|
|
|
const opt_extern: ?InternPool.Key.Extern = switch (ip.indexToKey(resolved.value)) {
|
|
.@"extern" => |@"extern"| @"extern",
|
|
else => null,
|
|
};
|
|
const nav_ty: Type = .fromInterned(resolved.type);
|
|
const llvm_ty: Builder.Type = if (opt_extern != null) ty: {
|
|
// We *must* lower this declaration no matter what. If it has a type we can't actually
|
|
// represent (because it doesn't have runtime bits), we instead lower as the zero-size
|
|
// type `[0 x i8]`. I don't think the type on an extern declaration actually does much
|
|
// anyway.
|
|
if (nav_ty.isRuntimeFnOrHasRuntimeBits(zcu)) break :ty try o.lowerType(nav_ty);
|
|
break :ty try o.builder.arrayType(0, .i8);
|
|
} else if (nav_ty.hasRuntimeBits(zcu)) ty: {
|
|
break :ty try o.lowerType(nav_ty);
|
|
} else {
|
|
// This is a non-extern zero-bit `Nav`---we're not interested in it.
|
|
// TODO: we might need to rethink this a little under incremental compilation. If a
|
|
// declaration becomes zero-bit, we can't just leave its old value there, because it
|
|
// might now be ill-formed.
|
|
return;
|
|
};
|
|
|
|
const gop = try o.nav_map.getOrPut(gpa, nav_id);
|
|
if (!gop.found_existing) {
|
|
errdefer assert(o.nav_map.remove(nav_id));
|
|
// First time lowering this NAV! Create a fresh global.
|
|
const llvm_name = try o.builder.strtabString(nav.fqn.toSlice(ip));
|
|
gop.value_ptr.* = try o.builder.addGlobal(llvm_name, .{
|
|
.type = .void, // placeholder; populated below
|
|
.kind = .{ .alias = .none }, // placeholder; populated below
|
|
});
|
|
}
|
|
const llvm_global = gop.value_ptr.*;
|
|
|
|
llvm_global.ptr(&o.builder).type = llvm_ty;
|
|
llvm_global.ptr(&o.builder).addr_space = toLlvmAddressSpace(resolved.@"addrspace", zcu.getTarget());
|
|
|
|
if (opt_extern) |@"extern"| {
|
|
const name = name: {
|
|
const name_slice = nav.name.toSlice(ip);
|
|
if (zcu.getTarget().cpu.arch.isWasm() and nav_ty.zigTypeTag(zcu) == .@"fn") {
|
|
if (@"extern".lib_name.toSlice(ip)) |lib_name_slice| {
|
|
if (!std.mem.eql(u8, lib_name_slice, "c")) {
|
|
break :name try o.builder.strtabStringFmt("{s}|{s}", .{ name_slice, lib_name_slice });
|
|
}
|
|
}
|
|
}
|
|
break :name try o.builder.strtabString(name_slice);
|
|
};
|
|
if (o.builder.getGlobal(name)) |other_global| {
|
|
if (other_global != llvm_global) {
|
|
// Another global already has this name; just use it in place of this global.
|
|
try llvm_global.replace(other_global, &o.builder);
|
|
return;
|
|
}
|
|
}
|
|
try llvm_global.rename(name, &o.builder);
|
|
llvm_global.ptr(&o.builder).unnamed_addr = .default;
|
|
llvm_global.ptr(&o.builder).dll_storage_class = switch (@"extern".is_dll_import) {
|
|
true => .dllimport,
|
|
false => .default,
|
|
};
|
|
llvm_global.ptr(&o.builder).linkage = switch (@"extern".linkage) {
|
|
.internal => if (o.builder.strip) .private else .internal,
|
|
.strong => .external,
|
|
.weak => .extern_weak,
|
|
.link_once => unreachable,
|
|
};
|
|
llvm_global.ptr(&o.builder).visibility = .fromSymbolVisibility(@"extern".visibility);
|
|
} else {
|
|
llvm_global.ptr(&o.builder).linkage = if (o.builder.strip) .private else .internal;
|
|
llvm_global.ptr(&o.builder).visibility = .default;
|
|
llvm_global.ptr(&o.builder).dll_storage_class = .default;
|
|
llvm_global.ptr(&o.builder).unnamed_addr = .unnamed_addr;
|
|
}
|
|
|
|
const llvm_align = switch (resolved.@"align") {
|
|
.none => nav_ty.abiAlignment(zcu).toLlvm(),
|
|
else => |a| a.toLlvm(),
|
|
};
|
|
const llvm_section: Builder.String = if (resolved.@"linksection".toSlice(ip)) |section| s: {
|
|
break :s try o.builder.string(section);
|
|
} else .none;
|
|
|
|
// Actual function bodies with AIR go through `updateFunc` instead, so the only functions we
|
|
// can see are extern functions or other comptime function body values (e.g. undefined). Of
|
|
// these, only extern functions need to be lowered to LLVM functions.
|
|
if (opt_extern != null and nav_ty.zigTypeTag(zcu) == .@"fn" and nav_ty.fnHasRuntimeBits(zcu)) {
|
|
const llvm_function: Builder.Function.Index = switch (llvm_global.ptrConst(&o.builder).kind) {
|
|
.function => |function| function, // re-use existing `Builder.Function`
|
|
.replaced, .alias, .variable => try llvm_global.toNewFunction(&o.builder),
|
|
};
|
|
llvm_function.setAlignment(llvm_align, &o.builder);
|
|
llvm_function.setSection(llvm_section, &o.builder);
|
|
try o.addLlvmFunctionAttributes(pt, nav_id, llvm_function);
|
|
} else {
|
|
const file_scope = nav.srcInst(ip).resolveFile(ip);
|
|
const mod = zcu.fileByIndex(file_scope).mod.?;
|
|
|
|
const llvm_variable: Builder.Variable.Index = switch (llvm_global.ptrConst(&o.builder).kind) {
|
|
.variable => |variable| variable, // re-use existing `Builder.Variable`
|
|
.replaced, .alias, .function => try llvm_global.toNewVariable(&o.builder),
|
|
};
|
|
llvm_variable.setAlignment(llvm_align, &o.builder);
|
|
llvm_variable.setSection(llvm_section, &o.builder);
|
|
llvm_variable.setMutability(if (resolved.@"const") .constant else .global, &o.builder);
|
|
try llvm_variable.setInitializer(if (opt_extern != null) .no_init else try o.lowerValue(resolved.value), &o.builder);
|
|
llvm_variable.setThreadLocal(tl: {
|
|
if (resolved.@"threadlocal" and !mod.single_threaded) break :tl .generaldynamic;
|
|
break :tl .default;
|
|
}, &o.builder);
|
|
|
|
if (!mod.strip) {
|
|
const debug_file = try o.getDebugFile(file_scope);
|
|
const debug_global_var_expr = try o.builder.debugGlobalVarExpression(
|
|
try o.builder.debugGlobalVar(
|
|
try o.builder.metadataString(nav.name.toSlice(ip)), // Name
|
|
try o.builder.metadataString(nav.fqn.toSlice(ip)), // Linkage name
|
|
debug_file, // File
|
|
debug_file, // Scope
|
|
zcu.navSrcLine(nav_id) + 1,
|
|
try o.getDebugType(pt, nav_ty),
|
|
llvm_variable,
|
|
.{ .local = llvm_global.ptrConst(&o.builder).linkage == .internal },
|
|
),
|
|
try o.builder.debugExpression(&.{}),
|
|
);
|
|
llvm_variable.setGlobalVariableExpression(debug_global_var_expr, &o.builder);
|
|
try o.debug_globals.append(o.gpa, debug_global_var_expr);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn flushTypePool(o: *Object, pt: Zcu.PerThread) Allocator.Error!void {
|
|
try o.type_pool.flushPending(pt, .{ .llvm = o });
|
|
}
|
|
|
|
pub fn updateExports(
|
|
o: *Object,
|
|
exported: Zcu.Exported,
|
|
export_indices: []const Zcu.Export.Index,
|
|
) link.File.UpdateExportsError!void {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const ty: Type, const llvm_ptr: Builder.Constant = switch (exported) {
|
|
.nav => |nav| exp: {
|
|
const nav_ty: Type = .fromInterned(ip.getNav(nav).resolved.?.type);
|
|
const nav_ref = try o.lowerNavRef(nav);
|
|
break :exp .{ nav_ty, nav_ref };
|
|
},
|
|
.uav => |uav| exp: {
|
|
const uav_ty = Value.fromInterned(uav).typeOf(zcu);
|
|
const uav_ref = try o.lowerUavRef(
|
|
uav,
|
|
uav_ty.abiAlignment(zcu),
|
|
target_util.defaultAddressSpace(zcu.getTarget(), .global_constant),
|
|
);
|
|
break :exp .{ uav_ty, uav_ref };
|
|
},
|
|
};
|
|
switch (llvm_ptr.unwrap()) {
|
|
.global => |global| return o.updateExportedGlobal(global, ty, export_indices),
|
|
.constant => @panic("LLVM TODO: export zero-bit value"),
|
|
}
|
|
}
|
|
|
|
fn updateExportedGlobal(
|
|
o: *Object,
|
|
global_index: Builder.Global.Index,
|
|
ty: Type,
|
|
export_indices: []const Zcu.Export.Index,
|
|
) link.File.UpdateExportsError!void {
|
|
const zcu = o.zcu;
|
|
const comp = zcu.comp;
|
|
const ip = &zcu.intern_pool;
|
|
|
|
// If we're on COFF and linking with LLD, the linker cares about our exports to determine the subsystem in use.
|
|
coff_export_flags: {
|
|
const lf = comp.bin_file orelse break :coff_export_flags;
|
|
const lld = lf.cast(.lld) orelse break :coff_export_flags;
|
|
const coff = switch (lld.ofmt) {
|
|
.elf, .wasm => break :coff_export_flags,
|
|
.coff => |*coff| coff,
|
|
};
|
|
if (ty.zigTypeTag(zcu) != .@"fn") break :coff_export_flags;
|
|
const flags = &coff.lld_export_flags;
|
|
for (export_indices) |export_index| {
|
|
const name = export_index.ptr(zcu).opts.name;
|
|
if (name.eqlSlice("main", ip)) flags.c_main = true;
|
|
if (name.eqlSlice("WinMain", ip)) flags.winmain = true;
|
|
if (name.eqlSlice("wWinMain", ip)) flags.wwinmain = true;
|
|
if (name.eqlSlice("WinMainCRTStartup", ip)) flags.winmain_crt_startup = true;
|
|
if (name.eqlSlice("wWinMainCRTStartup", ip)) flags.wwinmain_crt_startup = true;
|
|
if (name.eqlSlice("DllMainCRTStartup", ip)) flags.dllmain_crt_startup = true;
|
|
}
|
|
}
|
|
|
|
// If the first export specifies a linksection, set the exported variable's section to that
|
|
// one. This is kind of a hack because `std.builtin.ExportOptions.section` doesn't actually
|
|
// make much sense: the linksection should be associated with the declaration itself rather
|
|
// than some particular symbol it is exported as!
|
|
if (export_indices[0].ptr(zcu).opts.section.toSlice(ip)) |section_slice| {
|
|
const variable = &global_index.ptrConst(&o.builder).kind.variable;
|
|
variable.setSection(try o.builder.string(section_slice), &o.builder);
|
|
}
|
|
|
|
const llvm_global_ty = global_index.typeOf(&o.builder);
|
|
|
|
// All exports are represented as aliases to the original global.
|
|
|
|
// TODO: we currently do not delete old exports. To do that we'll need to track which
|
|
// globals actually *are* exports.
|
|
|
|
for (export_indices) |export_idx| {
|
|
const exp = export_idx.ptr(zcu);
|
|
const exp_name = try o.builder.strtabString(exp.opts.name.toSlice(ip));
|
|
|
|
// Our goal is to make an alias with the name `exp_name`, but if that name is already
|
|
// taken by some existing global, we need to figure out what to do with that existing
|
|
// global.
|
|
//
|
|
// The name, aliasee, and type will be set within this block. Other properties of the
|
|
// alias will be set below.
|
|
const alias_global: Builder.Global.Index = global: {
|
|
const existing_global = o.builder.getGlobal(exp_name) orelse {
|
|
// There is no existing global with this name, so make a new alias.
|
|
const alias = try o.builder.addAlias(
|
|
exp_name,
|
|
llvm_global_ty,
|
|
.default,
|
|
global_index.toConst(),
|
|
);
|
|
break :global alias.ptrConst(&o.builder).global;
|
|
};
|
|
// There is an existing global with this name, so we can't just create an alias. We
|
|
// need to figure out what to do with the existing global instead.
|
|
switch (existing_global.ptrConst(&o.builder).kind) {
|
|
.alias => |alias| {
|
|
// We can just repurpose the existing alias.
|
|
alias.setAliasee(global_index.toConst(), &o.builder);
|
|
alias.ptrConst(&o.builder).global.ptr(&o.builder).type = global_index.typeOf(&o.builder);
|
|
break :global existing_global;
|
|
},
|
|
.variable, .function => {
|
|
// This must be an extern, which is no good to us---we need an alias. The
|
|
// extern should refer to the value we're exporting, so replace it with the
|
|
// exported value. That will free up the name for us to create a new alias.
|
|
// We need to make a new global which is an alias. Replace this existing one
|
|
// with the target global, making the name available and fixing references
|
|
// to this global to point to the target.
|
|
try existing_global.replace(global_index, &o.builder);
|
|
// The name is now free, so create an alias.
|
|
const alias = try o.builder.addAlias(
|
|
exp_name,
|
|
llvm_global_ty,
|
|
.default,
|
|
global_index.toConst(),
|
|
);
|
|
break :global alias.ptrConst(&o.builder).global;
|
|
},
|
|
.replaced => unreachable, // a replaced global would have lost the name `exp_name`
|
|
}
|
|
};
|
|
|
|
// Now for a bit of setup which
|
|
|
|
// We need the alias to *not* be `unnamed_addr` to ensure that the alias address equals
|
|
// the address of the original global.
|
|
alias_global.setUnnamedAddr(.default, &o.builder);
|
|
|
|
if (comp.config.dll_export_fns and exp.opts.visibility != .hidden)
|
|
alias_global.setDllStorageClass(.dllexport, &o.builder);
|
|
alias_global.setLinkage(switch (exp.opts.linkage) {
|
|
.internal => if (o.builder.strip) .private else .internal, // we still did useful work in replacing an existing symbol if there was one
|
|
.strong => .external,
|
|
.weak => .weak_odr,
|
|
.link_once => .linkonce_odr,
|
|
}, &o.builder);
|
|
alias_global.setVisibility(switch (exp.opts.visibility) {
|
|
.default => .default,
|
|
.hidden => .hidden,
|
|
.protected => .protected,
|
|
}, &o.builder);
|
|
}
|
|
}
|
|
|
|
pub fn updateContainerType(o: *Object, pt: Zcu.PerThread, ty: InternPool.Index, success: bool) Allocator.Error!void {
|
|
try o.type_pool.updateContainerType(pt, .{ .llvm = o }, ty, success);
|
|
if (o.named_enum_map.get(ty)) |function_index| {
|
|
try o.updateIsNamedEnumValueFunction(.fromInterned(ty), function_index);
|
|
}
|
|
if (o.enum_tag_name_map.get(ty)) |function_index| {
|
|
try o.updateEnumTagNameFunction(.fromInterned(ty), function_index);
|
|
}
|
|
}
|
|
|
|
/// Should only be called by the `link.ConstPool` implementation.
|
|
///
|
|
/// `val` is always a type because `o.type_pool` only contains types.
|
|
pub fn addConst(o: *Object, pt: Zcu.PerThread, index: link.ConstPool.Index, val: InternPool.Index) Allocator.Error!void {
|
|
_ = pt;
|
|
const zcu = o.zcu;
|
|
const gpa = zcu.comp.gpa;
|
|
assert(zcu.intern_pool.typeOf(val) == .type_type);
|
|
|
|
{
|
|
assert(@intFromEnum(index) == o.lazy_abi_aligns.items.len);
|
|
try o.lazy_abi_aligns.ensureUnusedCapacity(gpa, 1);
|
|
const fwd_ref = try o.builder.alignmentForwardReference();
|
|
o.lazy_abi_aligns.appendAssumeCapacity(fwd_ref);
|
|
}
|
|
|
|
if (!o.builder.strip) {
|
|
assert(@intFromEnum(index) == o.debug_types.items.len);
|
|
try o.debug_types.ensureUnusedCapacity(gpa, 1);
|
|
const fwd_ref = try o.builder.debugForwardReference();
|
|
o.debug_types.appendAssumeCapacity(fwd_ref);
|
|
if (val == .anyerror_type) {
|
|
assert(o.debug_anyerror_fwd_ref.is_none);
|
|
o.debug_anyerror_fwd_ref = fwd_ref.toOptional();
|
|
}
|
|
}
|
|
}
|
|
/// Should only be called by the `link.ConstPool` implementation.
|
|
///
|
|
/// `val` is always a type because `o.type_pool` only contains types.
|
|
pub fn updateConstIncomplete(o: *Object, pt: Zcu.PerThread, index: link.ConstPool.Index, val: InternPool.Index) Allocator.Error!void {
|
|
const zcu = o.zcu;
|
|
assert(zcu.intern_pool.typeOf(val) == .type_type);
|
|
|
|
const ty: Type = .fromInterned(val);
|
|
|
|
{
|
|
const fwd_ref = o.lazy_abi_aligns.items[@intFromEnum(index)];
|
|
o.builder.resolveAlignmentForwardReference(fwd_ref, .fromByteUnits(1));
|
|
}
|
|
|
|
if (!o.builder.strip) {
|
|
assert(val != .anyerror_type);
|
|
const fwd_ref = o.debug_types.items[@intFromEnum(index)];
|
|
const name_str = try o.builder.metadataStringFmt("{f}", .{ty.fmt(pt)});
|
|
// If `ty` is a function, use a dummy *function* type to prevent existing debug
|
|
// subprograms from becoming ill-formed.
|
|
const debug_incomplete_type = switch (ty.zigTypeTag(zcu)) {
|
|
.@"fn" => try o.builder.debugSubroutineType(null),
|
|
else => try o.builder.debugSignedType(name_str, 0),
|
|
};
|
|
o.builder.resolveDebugForwardReference(fwd_ref, debug_incomplete_type);
|
|
}
|
|
}
|
|
/// Should only be called by the `link.ConstPool` implementation.
|
|
///
|
|
/// `val` is always a type because `o.type_pool` only contains types.
|
|
pub fn updateConst(o: *Object, pt: Zcu.PerThread, index: link.ConstPool.Index, val: InternPool.Index) Allocator.Error!void {
|
|
const zcu = o.zcu;
|
|
assert(zcu.intern_pool.typeOf(val) == .type_type);
|
|
|
|
const ty: Type = .fromInterned(val);
|
|
|
|
{
|
|
const fwd_ref = o.lazy_abi_aligns.items[@intFromEnum(index)];
|
|
o.builder.resolveAlignmentForwardReference(fwd_ref, ty.abiAlignment(zcu).toLlvm());
|
|
}
|
|
|
|
if (!o.builder.strip) {
|
|
const fwd_ref = o.debug_types.items[@intFromEnum(index)];
|
|
if (val == .anyerror_type) {
|
|
// Don't lower this now; it will be populated in `emit` instead.
|
|
assert(o.debug_anyerror_fwd_ref == fwd_ref.toOptional());
|
|
} else {
|
|
const debug_type = try o.lowerDebugType(pt, ty, fwd_ref);
|
|
o.builder.resolveDebugForwardReference(fwd_ref, debug_type);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn getDebugFile(o: *Object, file_index: Zcu.File.Index) Allocator.Error!Builder.Metadata {
|
|
const gpa = o.gpa;
|
|
const gop = try o.debug_file_map.getOrPut(gpa, file_index);
|
|
errdefer assert(o.debug_file_map.remove(file_index));
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
const path = o.zcu.fileByIndex(file_index).path;
|
|
const abs_path = try path.toAbsolute(o.zcu.comp.dirs, gpa);
|
|
defer gpa.free(abs_path);
|
|
|
|
gop.value_ptr.* = try o.builder.debugFile(
|
|
try o.builder.metadataString(std.fs.path.basename(abs_path)),
|
|
try o.builder.metadataString(std.fs.path.dirname(abs_path) orelse ""),
|
|
);
|
|
return gop.value_ptr.*;
|
|
}
|
|
|
|
pub fn getDebugType(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error!Builder.Metadata {
|
|
assert(!o.builder.strip);
|
|
const index = try o.type_pool.get(pt, .{ .llvm = o }, ty.toIntern());
|
|
return o.debug_types.items[@intFromEnum(index)];
|
|
}
|
|
|
|
/// In codegen logic, instead of calling this directly, use `getDebugType` to get a forward
|
|
/// reference which will be populated only when all necessary type resolution is complete.
|
|
fn lowerDebugType(
|
|
o: *Object,
|
|
pt: Zcu.PerThread,
|
|
ty: Type,
|
|
ty_fwd_ref: Builder.Metadata,
|
|
) Allocator.Error!Builder.Metadata {
|
|
assert(!o.builder.strip);
|
|
|
|
const gpa = o.gpa;
|
|
const zcu = o.zcu;
|
|
const target = zcu.getTarget();
|
|
const ip = &zcu.intern_pool;
|
|
|
|
const name = try o.builder.metadataStringFmt("{f}", .{ty.fmt(pt)});
|
|
|
|
// lldb cannot handle non-byte-sized types, so in the logic below, bit sizes are padded up.
|
|
// For instance, `bool` is considered to be 8 bits, and `u60` is considered to be 64 bits.
|
|
|
|
// I tried using variants (DW_TAG_variant_part + DW_TAG_variant) to encode error unions,
|
|
// tagged unions, etc; this would have told debuggers which field was active, which could
|
|
// improve UX significantly. GDB handles this perfectly fine, but unfortunately, LLDB has no
|
|
// handling for variants at all, and will never print fields in them, so I opted not to use
|
|
// them for now.
|
|
|
|
switch (ty.zigTypeTag(zcu)) {
|
|
.void,
|
|
.noreturn,
|
|
.comptime_int,
|
|
.comptime_float,
|
|
.type,
|
|
.undefined,
|
|
.null,
|
|
.enum_literal,
|
|
=> return o.builder.debugSignedType(name, 0),
|
|
|
|
.float => return o.builder.debugFloatType(name, ty.floatBits(target)),
|
|
|
|
.bool => return o.builder.debugBoolType(name, 8),
|
|
|
|
.int => {
|
|
const info = ty.intInfo(zcu);
|
|
const bits = ty.abiSize(zcu) * 8;
|
|
return switch (info.signedness) {
|
|
.signed => try o.builder.debugSignedType(name, bits),
|
|
.unsigned => try o.builder.debugUnsignedType(name, bits),
|
|
};
|
|
},
|
|
|
|
.pointer => {
|
|
const ptr_size = Type.ptrAbiSize(zcu.getTarget());
|
|
const ptr_align = Type.ptrAbiAlignment(zcu.getTarget());
|
|
|
|
if (ty.isSlice(zcu)) {
|
|
const debug_ptr_type = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("ptr"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, ty.slicePtrFieldType(zcu)),
|
|
ptr_size * 8,
|
|
ptr_align.toByteUnits().? * 8,
|
|
0, // offset
|
|
);
|
|
|
|
const debug_len_type = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("len"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, .usize),
|
|
ptr_size * 8,
|
|
ptr_align.toByteUnits().? * 8,
|
|
ptr_size * 8,
|
|
);
|
|
|
|
return o.builder.debugStructType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
null, // underlying type
|
|
ptr_size * 2 * 8,
|
|
ptr_align.toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&.{
|
|
debug_ptr_type,
|
|
debug_len_type,
|
|
}),
|
|
);
|
|
}
|
|
|
|
return o.builder.debugPointerType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
try o.getDebugType(pt, ty.childType(zcu)),
|
|
ptr_size * 8,
|
|
ptr_align.toByteUnits().? * 8,
|
|
0, // offset
|
|
);
|
|
},
|
|
.array => return o.builder.debugArrayType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
try o.getDebugType(pt, ty.childType(zcu)),
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&.{
|
|
try o.builder.debugSubrange(
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i64, 0)),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i64, ty.arrayLen(zcu))),
|
|
),
|
|
}),
|
|
),
|
|
.vector => {
|
|
const elem_ty = ty.childType(zcu);
|
|
// Vector elements cannot be padded since that would make
|
|
// @bitSizeOf(elem) * len > @bitSizOf(vec).
|
|
// Neither gdb nor lldb seem to be able to display non-byte sized
|
|
// vectors properly.
|
|
const debug_elem_type = switch (elem_ty.zigTypeTag(zcu)) {
|
|
.int => blk: {
|
|
const info = elem_ty.intInfo(zcu);
|
|
break :blk switch (info.signedness) {
|
|
.signed => try o.builder.debugSignedType(name, info.bits),
|
|
.unsigned => try o.builder.debugUnsignedType(name, info.bits),
|
|
};
|
|
},
|
|
.bool => try o.builder.debugBoolType(try o.builder.metadataString("bool"), 1),
|
|
// We don't pad pointers or floats, so we can lower those normally.
|
|
.pointer, .optional, .float => try o.getDebugType(pt, elem_ty),
|
|
else => unreachable,
|
|
};
|
|
|
|
return o.builder.debugVectorType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
debug_elem_type,
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&.{
|
|
try o.builder.debugSubrange(
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i64, 0)),
|
|
try o.builder.metadataConstant(try o.builder.intConst(.i64, ty.vectorLen(zcu))),
|
|
),
|
|
}),
|
|
);
|
|
},
|
|
.optional => {
|
|
const payload_ty = ty.optionalChild(zcu);
|
|
if (ty.optionalReprIsPayload(zcu)) {
|
|
return o.builder.debugTypedefType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
try o.getDebugType(pt, payload_ty),
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
0, // offset
|
|
);
|
|
}
|
|
|
|
const payload_size = payload_ty.abiSize(zcu);
|
|
|
|
const non_null_ty = Type.u8;
|
|
const non_null_size = non_null_ty.abiSize(zcu);
|
|
const non_null_align = non_null_ty.abiAlignment(zcu);
|
|
const non_null_offset = non_null_align.forward(payload_size);
|
|
|
|
const debug_payload_type = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("payload"),
|
|
null, // file
|
|
ty_fwd_ref, // scope
|
|
0, // line
|
|
try o.getDebugType(pt, payload_ty),
|
|
payload_size * 8,
|
|
payload_ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
0, // offset
|
|
);
|
|
|
|
const debug_some_type = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("some"),
|
|
null,
|
|
ty_fwd_ref,
|
|
0,
|
|
try o.getDebugType(pt, non_null_ty),
|
|
non_null_size * 8,
|
|
non_null_align.toByteUnits().? * 8,
|
|
non_null_offset * 8,
|
|
);
|
|
|
|
return o.builder.debugStructType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&.{
|
|
debug_payload_type,
|
|
debug_some_type,
|
|
}),
|
|
);
|
|
},
|
|
.error_union => {
|
|
const error_ty = ty.errorUnionSet(zcu);
|
|
const payload_ty = ty.errorUnionPayload(zcu);
|
|
|
|
const error_size = error_ty.abiSize(zcu);
|
|
const error_align = error_ty.abiAlignment(zcu);
|
|
const payload_size = payload_ty.abiSize(zcu);
|
|
const payload_align = payload_ty.abiAlignment(zcu);
|
|
|
|
const error_offset: u64, const payload_offset: u64 = offsets: {
|
|
if (error_align.compare(.gt, payload_align)) {
|
|
break :offsets .{ 0, payload_align.forward(error_size) };
|
|
} else {
|
|
break :offsets .{ error_align.forward(payload_size), 0 };
|
|
}
|
|
};
|
|
|
|
const error_field = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("error"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, error_ty),
|
|
error_size * 8,
|
|
error_align.toByteUnits().? * 8,
|
|
error_offset * 8,
|
|
);
|
|
const payload_field = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("payload"),
|
|
null, // file
|
|
ty_fwd_ref, // scope
|
|
0, // line
|
|
try o.getDebugType(pt, payload_ty),
|
|
payload_size * 8,
|
|
payload_align.toByteUnits().? * 8,
|
|
payload_offset * 8,
|
|
);
|
|
|
|
return try o.builder.debugStructType(
|
|
name,
|
|
null, // File
|
|
o.debug_compile_unit.unwrap().?, // Scope
|
|
0, // Line
|
|
null, // Underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&.{ error_field, payload_field }),
|
|
);
|
|
},
|
|
.error_set => {
|
|
assert(ty.toIntern() != .anyerror_type); // handled specially in `updateConst`; will be populated by `emit` instead
|
|
// Error sets are just named wrappers around `anyerror`.
|
|
return o.builder.debugTypedefType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
try o.getDebugType(pt, .anyerror),
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
0, // offset
|
|
);
|
|
},
|
|
.@"fn" => {
|
|
if (!ty.fnHasRuntimeBits(zcu)) {
|
|
// Use a dummy *function* type to prevent existing debug subprograms from
|
|
// becoming ill-formed.
|
|
return o.builder.debugSubroutineType(null);
|
|
}
|
|
|
|
const fn_info = zcu.typeToFunc(ty).?;
|
|
|
|
var debug_param_types: std.ArrayList(Builder.Metadata) = try .initCapacity(gpa, 3 + fn_info.param_types.len);
|
|
defer debug_param_types.deinit(gpa);
|
|
|
|
// Return type goes first.
|
|
if (firstParamSRet(fn_info, zcu, target)) {
|
|
// Actual return type is void, then first arg is the sret pointer.
|
|
const ptr_ty = try pt.singleMutPtrType(.fromInterned(fn_info.return_type));
|
|
debug_param_types.appendAssumeCapacity(try o.getDebugType(pt, .void));
|
|
debug_param_types.appendAssumeCapacity(try o.getDebugType(pt, ptr_ty));
|
|
} else {
|
|
const ret_ty: Type = .fromInterned(fn_info.return_type);
|
|
debug_param_types.appendAssumeCapacity(try o.getDebugType(pt, ret_ty));
|
|
}
|
|
|
|
if (fn_info.cc == .auto and zcu.comp.config.any_error_tracing) {
|
|
// Stack trace pointer.
|
|
debug_param_types.appendAssumeCapacity(try o.getDebugType(pt, .ptr_usize));
|
|
}
|
|
|
|
for (fn_info.param_types.get(ip)) |param_ty_ip| {
|
|
const param_ty: Type = .fromInterned(param_ty_ip);
|
|
if (!param_ty.hasRuntimeBits(zcu)) continue;
|
|
if (isByRef(param_ty, zcu)) {
|
|
const ptr_ty = try pt.singleConstPtrType(param_ty);
|
|
debug_param_types.appendAssumeCapacity(try o.getDebugType(pt, ptr_ty));
|
|
} else {
|
|
debug_param_types.appendAssumeCapacity(try o.getDebugType(pt, param_ty));
|
|
}
|
|
}
|
|
|
|
return o.builder.debugSubroutineType(
|
|
try o.builder.metadataTuple(debug_param_types.items),
|
|
);
|
|
},
|
|
.@"struct" => {
|
|
if (ty.isTuple(zcu)) {
|
|
const tuple = ip.indexToKey(ty.toIntern()).tuple_type;
|
|
var fields: std.ArrayList(Builder.Metadata) = .empty;
|
|
defer fields.deinit(gpa);
|
|
|
|
try fields.ensureUnusedCapacity(gpa, tuple.types.len);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
|
|
for (tuple.types.get(ip), tuple.values.get(ip), 0..) |field_ty_ip, field_val, i| {
|
|
const field_ty: Type = .fromInterned(field_ty_ip);
|
|
if (field_val != .none or !field_ty.hasRuntimeBits(zcu)) continue;
|
|
|
|
const field_size = field_ty.abiSize(zcu);
|
|
const field_align = field_ty.abiAlignment(zcu);
|
|
const field_offset = field_align.forward(offset);
|
|
offset = field_offset + field_size;
|
|
|
|
fields.appendAssumeCapacity(try o.builder.debugMemberType(
|
|
try o.builder.metadataStringFmt("{d}", .{i}),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, field_ty),
|
|
field_size * 8,
|
|
field_align.toByteUnits().? * 8,
|
|
field_offset * 8,
|
|
));
|
|
}
|
|
|
|
return o.builder.debugStructType(
|
|
name,
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?,
|
|
0, // line
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
(ty.abiAlignment(zcu).toByteUnits() orelse 0) * 8,
|
|
try o.builder.metadataTuple(fields.items),
|
|
);
|
|
}
|
|
|
|
const struct_type = zcu.typeToStruct(ty).?;
|
|
|
|
const file = try o.getDebugFile(struct_type.zir_index.resolveFile(ip));
|
|
const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
|
|
try o.namespaceToDebugScope(pt, parent_namespace)
|
|
else
|
|
file;
|
|
|
|
const line = ty.typeDeclSrcLine(zcu).? + 1;
|
|
|
|
var fields: std.ArrayList(Builder.Metadata) = .empty;
|
|
defer fields.deinit(gpa);
|
|
|
|
switch (struct_type.layout) {
|
|
.@"packed" => {
|
|
try fields.ensureTotalCapacityPrecise(gpa, 1);
|
|
fields.appendAssumeCapacity(try o.builder.debugMemberType(
|
|
try o.builder.metadataString("bits"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, .fromInterned(struct_type.packed_backing_int_type)),
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
0, // offset
|
|
));
|
|
},
|
|
.auto, .@"extern" => {
|
|
comptime assert(struct_layout_version == 2);
|
|
try fields.ensureTotalCapacityPrecise(gpa, struct_type.field_types.len);
|
|
var it = struct_type.iterateRuntimeOrder(ip);
|
|
while (it.next()) |field_index| {
|
|
const field_ty: Type = .fromInterned(struct_type.field_types.get(ip)[field_index]);
|
|
if (!field_ty.hasRuntimeBits(zcu)) continue;
|
|
const field_size = field_ty.abiSize(zcu);
|
|
const field_align = switch (ty.explicitFieldAlignment(field_index, zcu)) {
|
|
.none => field_ty.abiAlignment(zcu),
|
|
else => |a| a,
|
|
};
|
|
const field_offset = struct_type.field_offsets.get(ip)[field_index];
|
|
const field_name = struct_type.field_names.get(ip)[field_index];
|
|
fields.appendAssumeCapacity(try o.builder.debugMemberType(
|
|
try o.builder.metadataString(field_name.toSlice(ip)),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, field_ty),
|
|
field_size * 8,
|
|
field_align.toByteUnits().? * 8,
|
|
field_offset * 8,
|
|
));
|
|
}
|
|
},
|
|
}
|
|
|
|
return o.builder.debugStructType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(fields.items),
|
|
);
|
|
},
|
|
.@"union" => {
|
|
const union_type = ip.loadUnionType(ty.toIntern());
|
|
|
|
const file = try o.getDebugFile(union_type.zir_index.resolveFile(ip));
|
|
const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
|
|
try o.namespaceToDebugScope(pt, parent_namespace)
|
|
else
|
|
file;
|
|
|
|
const line = ty.typeDeclSrcLine(zcu).? + 1;
|
|
|
|
const enum_tag_ty: Type = .fromInterned(union_type.enum_tag_type);
|
|
|
|
if (union_type.layout == .@"packed") {
|
|
const bitpack_field = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("bits"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, .fromInterned(union_type.packed_backing_int_type)),
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
0, // offset
|
|
);
|
|
return o.builder.debugStructType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&.{bitpack_field}),
|
|
);
|
|
}
|
|
|
|
const layout = Type.getUnionLayout(union_type, zcu);
|
|
|
|
if (layout.payload_size == 0) {
|
|
const fields_tuple: ?Builder.Metadata = fields: {
|
|
if (layout.tag_size == 0) break :fields null;
|
|
break :fields try o.builder.metadataTuple(&.{
|
|
try o.builder.debugMemberType(
|
|
try o.builder.metadataString("tag"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, enum_tag_ty),
|
|
layout.tag_size * 8,
|
|
layout.tag_align.toByteUnits().? * 8,
|
|
0, // offset
|
|
),
|
|
});
|
|
};
|
|
return o.builder.debugStructType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
fields_tuple,
|
|
);
|
|
}
|
|
|
|
var fields: std.ArrayList(Builder.Metadata) = try .initCapacity(gpa, union_type.field_types.len);
|
|
defer fields.deinit(gpa);
|
|
|
|
const payload_fwd_ref = if (layout.tag_size == 0)
|
|
ty_fwd_ref
|
|
else
|
|
try o.builder.debugForwardReference();
|
|
|
|
for (0..union_type.field_types.len) |field_index| {
|
|
const field_ty = union_type.field_types.get(ip)[field_index];
|
|
|
|
const field_size = Type.fromInterned(field_ty).abiSize(zcu);
|
|
const field_align: InternPool.Alignment = ty.explicitFieldAlignment(field_index, zcu);
|
|
|
|
const field_name = enum_tag_ty.enumFieldName(field_index, zcu);
|
|
fields.appendAssumeCapacity(try o.builder.debugMemberType(
|
|
try o.builder.metadataString(field_name.toSlice(ip)),
|
|
null, // file
|
|
payload_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, .fromInterned(field_ty)),
|
|
field_size * 8,
|
|
(field_align.toByteUnits() orelse 0) * 8,
|
|
0, // offset
|
|
));
|
|
}
|
|
|
|
const debug_payload_type = try o.builder.debugUnionType(
|
|
payload_name: {
|
|
if (layout.tag_size == 0) break :payload_name name;
|
|
break :payload_name try o.builder.metadataStringFmt("{f}:Payload", .{ty.fmt(pt)});
|
|
},
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
layout.payload_size * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(fields.items),
|
|
);
|
|
|
|
if (layout.tag_size == 0) {
|
|
return debug_payload_type;
|
|
}
|
|
|
|
o.builder.resolveDebugForwardReference(payload_fwd_ref, debug_payload_type);
|
|
|
|
const tag_offset: u64, const payload_offset: u64 = offsets: {
|
|
if (layout.tag_align.compare(.gte, layout.payload_align)) {
|
|
break :offsets .{ 0, layout.payload_align.forward(layout.tag_size) };
|
|
} else {
|
|
break :offsets .{ layout.tag_align.forward(layout.payload_size), 0 };
|
|
}
|
|
};
|
|
|
|
const tag_member_type = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("tag"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
try o.getDebugType(pt, enum_tag_ty),
|
|
layout.tag_size * 8,
|
|
layout.tag_align.toByteUnits().? * 8,
|
|
tag_offset * 8,
|
|
);
|
|
|
|
const payload_member_type = try o.builder.debugMemberType(
|
|
try o.builder.metadataString("payload"),
|
|
null, // file
|
|
ty_fwd_ref,
|
|
0, // line
|
|
debug_payload_type,
|
|
layout.payload_size * 8,
|
|
layout.payload_align.toByteUnits().? * 8,
|
|
payload_offset * 8,
|
|
);
|
|
|
|
const full_fields: [2]Builder.Metadata =
|
|
if (layout.tag_align.compare(.gte, layout.payload_align))
|
|
.{ tag_member_type, payload_member_type }
|
|
else
|
|
.{ payload_member_type, tag_member_type };
|
|
|
|
return o.builder.debugStructType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(&full_fields),
|
|
);
|
|
},
|
|
.@"enum" => {
|
|
const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip));
|
|
const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
|
|
try o.namespaceToDebugScope(pt, parent_namespace)
|
|
else
|
|
file;
|
|
|
|
const line = ty.typeDeclSrcLine(zcu).? + 1;
|
|
|
|
if (!ty.hasRuntimeBits(zcu)) {
|
|
return o.builder.debugStructType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
null, // fields
|
|
);
|
|
}
|
|
|
|
const enum_type = ip.loadEnumType(ty.toIntern());
|
|
const enumerators = try gpa.alloc(Builder.Metadata, enum_type.field_names.len);
|
|
defer gpa.free(enumerators);
|
|
|
|
const int_ty: Type = .fromInterned(enum_type.int_tag_type);
|
|
const int_info = ty.intInfo(zcu);
|
|
assert(int_info.bits != 0);
|
|
|
|
for (enumerators, enum_type.field_names.get(ip), 0..) |*out, field_name, field_index| {
|
|
var space: Value.BigIntSpace = undefined;
|
|
const field_val: std.math.big.int.Const = switch (enum_type.field_values.len) {
|
|
0 => std.math.big.int.Mutable.init(&space.limbs, field_index).toConst(),
|
|
else => Value.fromInterned(enum_type.field_values.get(ip)[field_index]).toBigInt(&space, zcu),
|
|
};
|
|
out.* = try o.builder.debugEnumerator(
|
|
try o.builder.metadataString(field_name.toSlice(ip)),
|
|
int_info.signedness == .unsigned,
|
|
int_info.bits,
|
|
field_val,
|
|
);
|
|
}
|
|
|
|
const debug_enum_type = try o.builder.debugEnumerationType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
try o.getDebugType(pt, int_ty),
|
|
ty.abiSize(zcu) * 8,
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(enumerators),
|
|
);
|
|
try o.debug_enums.append(gpa, debug_enum_type);
|
|
return debug_enum_type;
|
|
},
|
|
.@"opaque" => {
|
|
if (ty.toIntern() == .anyopaque_type) {
|
|
return o.builder.debugSignedType(name, 0);
|
|
}
|
|
|
|
const file = try o.getDebugFile(ty.typeDeclInstAllowGeneratedTag(zcu).?.resolveFile(ip));
|
|
const scope = if (ty.getParentNamespace(zcu).unwrap()) |parent_namespace|
|
|
try o.namespaceToDebugScope(pt, parent_namespace)
|
|
else
|
|
file;
|
|
|
|
const line = ty.typeDeclSrcLine(zcu).? + 1;
|
|
|
|
return o.builder.debugStructType(
|
|
name,
|
|
file,
|
|
scope,
|
|
line,
|
|
null, // underlying type
|
|
0, // size
|
|
ty.abiAlignment(zcu).toByteUnits().? * 8,
|
|
null, // fields
|
|
);
|
|
},
|
|
.frame => @panic("TODO implement lowerDebugType for Frame types"),
|
|
.@"anyframe" => @panic("TODO implement lowerDebugType for AnyFrame types"),
|
|
}
|
|
}
|
|
|
|
/// Called in `emit` so that the global error set is fully populated.
|
|
fn lowerDebugAnyerrorType(o: *Object) Allocator.Error!Builder.Metadata {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const gpa = zcu.comp.gpa;
|
|
|
|
const error_set_bits = zcu.errorSetBits();
|
|
const error_names = ip.global_error_set.getNamesFromMainThread();
|
|
|
|
const enumerators = try gpa.alloc(Builder.Metadata, error_names.len + 1);
|
|
defer gpa.free(enumerators);
|
|
|
|
// The value 0 means "no error" in optionals and error unions.
|
|
enumerators[0] = try o.builder.debugEnumerator(
|
|
try o.builder.metadataString("null"),
|
|
true, // unsigned,
|
|
error_set_bits,
|
|
.{ .limbs = &.{0}, .positive = true }, // zero
|
|
);
|
|
|
|
for (enumerators[1..], error_names, 1..) |*out, error_name, error_value| {
|
|
var space: Value.BigIntSpace = undefined;
|
|
var bigint: std.math.big.int.Mutable = .init(&space.limbs, error_value);
|
|
out.* = try o.builder.debugEnumerator(
|
|
try o.builder.metadataStringFmt("error.{f}", .{error_name.fmtId(ip)}),
|
|
true, // unsigned
|
|
error_set_bits,
|
|
bigint.toConst(),
|
|
);
|
|
}
|
|
|
|
const debug_enum_type = try o.builder.debugEnumerationType(
|
|
try o.builder.metadataString("anyerror"),
|
|
null, // file
|
|
o.debug_compile_unit.unwrap().?, // scope
|
|
0, // line
|
|
try o.builder.debugUnsignedType(null, error_set_bits),
|
|
Type.anyerror.abiSize(zcu) * 8,
|
|
Type.anyerror.abiAlignment(zcu).toByteUnits().? * 8,
|
|
try o.builder.metadataTuple(enumerators),
|
|
);
|
|
try o.debug_enums.append(gpa, debug_enum_type);
|
|
return debug_enum_type;
|
|
}
|
|
|
|
fn namespaceToDebugScope(o: *Object, pt: Zcu.PerThread, namespace_index: InternPool.NamespaceIndex) !Builder.Metadata {
|
|
const zcu = o.zcu;
|
|
const namespace = zcu.namespacePtr(namespace_index);
|
|
if (namespace.parent == .none) return try o.getDebugFile(namespace.file_scope);
|
|
return o.getDebugType(pt, .fromInterned(namespace.owner_type));
|
|
}
|
|
|
|
/// Sets the attributes and callconv of the given `Builder.Function`, which corresponds to the
|
|
/// given `Nav` (which is a function).
|
|
fn addLlvmFunctionAttributes(
|
|
o: *Object,
|
|
pt: Zcu.PerThread,
|
|
nav_id: InternPool.Nav.Index,
|
|
function_index: Builder.Function.Index,
|
|
) Allocator.Error!void {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const nav = ip.getNav(nav_id);
|
|
const owner_mod = zcu.navFileScope(nav_id).mod.?;
|
|
const ty: Type = .fromInterned(nav.resolved.?.type);
|
|
|
|
const fn_info = zcu.typeToFunc(ty).?;
|
|
const target = &owner_mod.resolved_target.result;
|
|
|
|
var attributes: Builder.FunctionAttributes.Wip = .{};
|
|
defer attributes.deinit(&o.builder);
|
|
|
|
if (target.cpu.arch.isWasm()) if (nav.getExtern(ip)) |@"extern"| {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("wasm-import-name"),
|
|
.value = try o.builder.string(nav.name.toSlice(ip)),
|
|
} }, &o.builder);
|
|
if (@"extern".lib_name.toSlice(ip)) |lib_name_slice| {
|
|
if (!std.mem.eql(u8, lib_name_slice, "c")) try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("wasm-import-module"),
|
|
.value = try o.builder.string(lib_name_slice),
|
|
} }, &o.builder);
|
|
}
|
|
};
|
|
|
|
if (fn_info.cc == .async) {
|
|
@panic("TODO: LLVM backend lower async function");
|
|
}
|
|
|
|
{
|
|
const cc_info = toLlvmCallConv(fn_info.cc, target).?;
|
|
|
|
function_index.setCallConv(cc_info.llvm_cc, &o.builder);
|
|
|
|
if (cc_info.align_stack) {
|
|
try attributes.addFnAttr(.{ .alignstack = .wrap(.fromByteUnits(target.stackAlignment())) }, &o.builder);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.alignstack);
|
|
}
|
|
|
|
if (cc_info.naked) {
|
|
try attributes.addFnAttr(.naked, &o.builder);
|
|
} else {
|
|
_ = try attributes.removeFnAttr(.naked);
|
|
}
|
|
|
|
for (0..cc_info.inreg_param_count) |param_idx| {
|
|
try attributes.addParamAttr(param_idx, .inreg, &o.builder);
|
|
}
|
|
for (cc_info.inreg_param_count..std.math.maxInt(u2)) |param_idx| {
|
|
_ = try attributes.removeParamAttr(param_idx, .inreg);
|
|
}
|
|
|
|
switch (fn_info.cc) {
|
|
inline .riscv64_interrupt,
|
|
.riscv32_interrupt,
|
|
.mips_interrupt,
|
|
.mips64_interrupt,
|
|
=> |info| {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("interrupt"),
|
|
.value = try o.builder.string(@tagName(info.mode)),
|
|
} }, &o.builder);
|
|
},
|
|
.arm_interrupt,
|
|
=> |info| {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("interrupt"),
|
|
.value = try o.builder.string(switch (info.type) {
|
|
.generic => "",
|
|
.irq => "IRQ",
|
|
.fiq => "FIQ",
|
|
.swi => "SWI",
|
|
.abort => "ABORT",
|
|
.undef => "UNDEF",
|
|
}),
|
|
} }, &o.builder);
|
|
},
|
|
// these function attributes serve as a backup against any mistakes LLVM makes.
|
|
// clang sets both the function's calling convention and the function attributes
|
|
// in its backend, so future patches to the AVR backend could end up checking only one,
|
|
// possibly breaking our support. it's safer to just emit both.
|
|
.avr_interrupt, .avr_signal, .csky_interrupt => {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string(switch (fn_info.cc) {
|
|
.avr_interrupt,
|
|
.csky_interrupt,
|
|
=> "interrupt",
|
|
.avr_signal => "signal",
|
|
else => unreachable,
|
|
}),
|
|
.value = .empty,
|
|
} }, &o.builder);
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
// Function attributes that are independent of analysis results of the function body.
|
|
try o.addCommonFnAttributes(
|
|
&attributes,
|
|
owner_mod,
|
|
// Some backends don't respect the `naked` attribute in `TargetFrameLowering::hasFP()`,
|
|
// so for these backends, LLVM will happily emit code that accesses the stack through
|
|
// the frame pointer. This is nonsensical since what the `naked` attribute does is
|
|
// suppress generation of the prologue and epilogue, and the prologue is where the
|
|
// frame pointer normally gets set up. At time of writing, this is the case for at
|
|
// least x86 and RISC-V.
|
|
owner_mod.omit_frame_pointer or fn_info.cc == .naked,
|
|
);
|
|
|
|
if (fn_info.return_type == .noreturn_type) try attributes.addFnAttr(.noreturn, &o.builder);
|
|
|
|
var it = iterateParamTypes(o, fn_info);
|
|
if (firstParamSRet(fn_info, zcu, target)) {
|
|
// Sret pointers must not be address 0
|
|
try attributes.addParamAttr(it.llvm_index, .nonnull, &o.builder);
|
|
try attributes.addParamAttr(it.llvm_index, .@"noalias", &o.builder);
|
|
|
|
const raw_llvm_ret_ty = try o.lowerType(.fromInterned(fn_info.return_type));
|
|
try attributes.addParamAttr(it.llvm_index, .{ .sret = raw_llvm_ret_ty }, &o.builder);
|
|
it.llvm_index += 1;
|
|
} else if (ccAbiPromoteInt(fn_info.cc, zcu, Type.fromInterned(fn_info.return_type))) |s| switch (s) {
|
|
.signed => try attributes.addRetAttr(.signext, &o.builder),
|
|
.unsigned => try attributes.addRetAttr(.zeroext, &o.builder),
|
|
};
|
|
|
|
const err_return_tracing = fn_info.cc == .auto and zcu.comp.config.any_error_tracing;
|
|
if (err_return_tracing) {
|
|
try attributes.addParamAttr(it.llvm_index, .nonnull, &o.builder);
|
|
it.llvm_index += 1;
|
|
}
|
|
while (try it.next()) |lowering| switch (lowering) {
|
|
.byval => {
|
|
const param_index = it.zig_index - 1;
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[param_index]);
|
|
if (!isByRef(param_ty, zcu)) {
|
|
try o.addByValParamAttrs(pt, &attributes, param_ty, param_index, fn_info, it.llvm_index - 1);
|
|
}
|
|
},
|
|
.byref => {
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const alignment = param_ty.abiAlignment(zcu);
|
|
try o.addByRefParamAttrs(&attributes, it.llvm_index - 1, alignment.toLlvm(), it.byval_attr, param_llvm_ty);
|
|
},
|
|
.byref_mut => try attributes.addParamAttr(it.llvm_index - 1, .noundef, &o.builder),
|
|
.slice => {
|
|
const param_ty: Type = .fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const ptr_info = param_ty.ptrInfo(zcu);
|
|
const llvm_ptr_index = it.llvm_index - 2;
|
|
if (std.math.cast(u5, it.zig_index - 1)) |i| {
|
|
if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
|
|
try attributes.addParamAttr(llvm_ptr_index, .@"noalias", &o.builder);
|
|
}
|
|
}
|
|
if (param_ty.zigTypeTag(zcu) != .optional and
|
|
!ptr_info.flags.is_allowzero and
|
|
ptr_info.flags.address_space == .generic)
|
|
{
|
|
try attributes.addParamAttr(llvm_ptr_index, .nonnull, &o.builder);
|
|
}
|
|
if (ptr_info.flags.is_const) {
|
|
try attributes.addParamAttr(llvm_ptr_index, .readonly, &o.builder);
|
|
}
|
|
const elem_align: Builder.Alignment.Lazy = switch (ptr_info.flags.alignment) {
|
|
else => |a| .wrap(a.toLlvm()),
|
|
.none => try o.lazyAbiAlignment(pt, .fromInterned(ptr_info.child)),
|
|
};
|
|
try attributes.addParamAttr(llvm_ptr_index, .{ .@"align" = elem_align }, &o.builder);
|
|
},
|
|
// No attributes needed for these.
|
|
.no_bits,
|
|
.abi_sized_int,
|
|
.multiple_llvm_types,
|
|
.float_array,
|
|
.i32_array,
|
|
.i64_array,
|
|
=> continue,
|
|
};
|
|
|
|
function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
|
|
}
|
|
|
|
fn addCommonFnAttributes(
|
|
o: *Object,
|
|
attributes: *Builder.FunctionAttributes.Wip,
|
|
owner_mod: *Package.Module,
|
|
omit_frame_pointer: bool,
|
|
) Allocator.Error!void {
|
|
if (!owner_mod.red_zone) {
|
|
try attributes.addFnAttr(.noredzone, &o.builder);
|
|
}
|
|
if (omit_frame_pointer) {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("frame-pointer"),
|
|
.value = try o.builder.string("none"),
|
|
} }, &o.builder);
|
|
} else {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("frame-pointer"),
|
|
.value = try o.builder.string("all"),
|
|
} }, &o.builder);
|
|
}
|
|
try attributes.addFnAttr(.nounwind, &o.builder);
|
|
if (owner_mod.unwind_tables != .none) {
|
|
try attributes.addFnAttr(
|
|
.{ .uwtable = if (owner_mod.unwind_tables == .async) .async else .sync },
|
|
&o.builder,
|
|
);
|
|
}
|
|
if (owner_mod.optimize_mode == .ReleaseSmall) {
|
|
try attributes.addFnAttr(.minsize, &o.builder);
|
|
try attributes.addFnAttr(.optsize, &o.builder);
|
|
}
|
|
const target = &owner_mod.resolved_target.result;
|
|
if (target.cpu.model.llvm_name) |s| {
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("target-cpu"),
|
|
.value = try o.builder.string(s),
|
|
} }, &o.builder);
|
|
}
|
|
if (owner_mod.resolved_target.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);
|
|
}
|
|
if (target.abi.float() == .soft) {
|
|
// `use-soft-float` means "use software routines for floating point computations". In
|
|
// other words, it configures how LLVM lowers basic float instructions like `fcmp`,
|
|
// `fadd`, etc. The float calling convention is configured on `TargetMachine` and is
|
|
// mostly an orthogonal concept, although obviously we do need hardware float operations
|
|
// to actually be able to pass float values in float registers.
|
|
//
|
|
// Ideally, we would support something akin to the `-mfloat-abi=softfp` option that GCC
|
|
// and Clang support for Arm32 and CSKY. We don't currently expose such an option in
|
|
// Zig, and using CPU features as the source of truth for this makes for a miserable
|
|
// user experience since people expect e.g. `arm-linux-gnueabi` to mean full soft float
|
|
// unless the compiler has explicitly been told otherwise. (And note that our baseline
|
|
// CPU models almost all include FPU features!)
|
|
//
|
|
// Revisit this at some point.
|
|
try attributes.addFnAttr(.{ .string = .{
|
|
.kind = try o.builder.string("use-soft-float"),
|
|
.value = try o.builder.string("true"),
|
|
} }, &o.builder);
|
|
|
|
// This prevents LLVM from using FPU/SIMD code for things like `memcpy`. As for the
|
|
// above, this should be revisited if `softfp` support is added.
|
|
try attributes.addFnAttr(.noimplicitfloat, &o.builder);
|
|
}
|
|
}
|
|
|
|
pub fn errorIntType(o: *Object) Allocator.Error!Builder.Type {
|
|
return o.builder.intType(o.zcu.errorSetBits());
|
|
}
|
|
|
|
pub fn lowerType(o: *Object, t: Type) Allocator.Error!Builder.Type {
|
|
const zcu = o.zcu;
|
|
const target = zcu.getTarget();
|
|
const ip = &zcu.intern_pool;
|
|
return switch (t.toIntern()) {
|
|
.u0_type, .i0_type => unreachable, // no runtime bits
|
|
inline .u1_type,
|
|
.u8_type,
|
|
.i8_type,
|
|
.u16_type,
|
|
.i16_type,
|
|
.u29_type,
|
|
.u32_type,
|
|
.i32_type,
|
|
.u64_type,
|
|
.i64_type,
|
|
.u80_type,
|
|
.u128_type,
|
|
.i128_type,
|
|
=> |tag| @field(Builder.Type, "i" ++ @tagName(tag)[1 .. @tagName(tag).len - "_type".len]),
|
|
.usize_type, .isize_type => try o.builder.intType(target.ptrBitWidth()),
|
|
inline .c_char_type,
|
|
.c_short_type,
|
|
.c_ushort_type,
|
|
.c_int_type,
|
|
.c_uint_type,
|
|
.c_long_type,
|
|
.c_ulong_type,
|
|
.c_longlong_type,
|
|
.c_ulonglong_type,
|
|
=> |tag| try o.builder.intType(target.cTypeBitSize(
|
|
@field(std.Target.CType, @tagName(tag)["c_".len .. @tagName(tag).len - "_type".len]),
|
|
)),
|
|
.c_longdouble_type,
|
|
.f16_type,
|
|
.f32_type,
|
|
.f64_type,
|
|
.f80_type,
|
|
.f128_type,
|
|
=> switch (t.floatBits(target)) {
|
|
16 => if (backendSupportsF16(target)) .half else .i16,
|
|
32 => .float,
|
|
64 => .double,
|
|
80 => if (backendSupportsF80(target)) .x86_fp80 else .i80,
|
|
128 => .fp128,
|
|
else => unreachable,
|
|
},
|
|
.anyopaque_type => {
|
|
// This is unreachable except when used as the type for an extern global.
|
|
// For example: `@extern(*anyopaque, .{ .name = "foo"})` should produce
|
|
// @foo = external global i8
|
|
return .i8;
|
|
},
|
|
.bool_type => .i1,
|
|
.anyerror_type => try o.errorIntType(),
|
|
.void_type => unreachable, // no runtime bits
|
|
.type_type => unreachable, // no runtime bits
|
|
.comptime_int_type => unreachable, // no runtime bits
|
|
.comptime_float_type => unreachable, // no runtime bits
|
|
.noreturn_type => unreachable, // no runtime bits
|
|
.null_type => unreachable, // no runtime bits
|
|
.undefined_type => unreachable, // no runtime bits
|
|
.enum_literal_type => unreachable, // no runtime bits
|
|
.optional_noreturn_type => unreachable, // no runtime bits
|
|
.empty_tuple_type => unreachable, // no runtime bits
|
|
.anyframe_type => @panic("TODO implement lowerType for AnyFrame types"),
|
|
.ptr_usize_type,
|
|
.ptr_const_comptime_int_type,
|
|
.manyptr_u8_type,
|
|
.manyptr_const_u8_type,
|
|
.manyptr_const_u8_sentinel_0_type,
|
|
=> .ptr,
|
|
.slice_const_u8_type,
|
|
.slice_const_u8_sentinel_0_type,
|
|
=> try o.builder.structType(.normal, &.{ .ptr, try o.lowerType(.usize) }),
|
|
.anyerror_void_error_union_type,
|
|
.adhoc_inferred_error_set_type,
|
|
=> try o.errorIntType(),
|
|
.generic_poison_type => unreachable,
|
|
// values, not types
|
|
.undef,
|
|
.undef_bool,
|
|
.undef_usize,
|
|
.undef_u1,
|
|
.zero,
|
|
.zero_usize,
|
|
.zero_u1,
|
|
.zero_u8,
|
|
.one,
|
|
.one_usize,
|
|
.one_u1,
|
|
.one_u8,
|
|
.four_u8,
|
|
.negative_one,
|
|
.void_value,
|
|
.unreachable_value,
|
|
.null_value,
|
|
.bool_true,
|
|
.bool_false,
|
|
.empty_tuple,
|
|
.none,
|
|
=> unreachable,
|
|
else => switch (ip.indexToKey(t.toIntern())) {
|
|
.int_type => |int_type| try o.builder.intType(int_type.bits),
|
|
.ptr_type => |ptr_type| type: {
|
|
const ptr_ty = try o.builder.ptrType(
|
|
toLlvmAddressSpace(ptr_type.flags.address_space, target),
|
|
);
|
|
break :type switch (ptr_type.flags.size) {
|
|
.one, .many, .c => ptr_ty,
|
|
.slice => try o.builder.structType(.normal, &.{
|
|
ptr_ty,
|
|
try o.lowerType(.usize),
|
|
}),
|
|
};
|
|
},
|
|
.array_type => |array_type| o.builder.arrayType(
|
|
array_type.lenIncludingSentinel(),
|
|
try o.lowerType(.fromInterned(array_type.child)),
|
|
),
|
|
.vector_type => |vector_type| o.builder.vectorType(
|
|
.normal,
|
|
vector_type.len,
|
|
try o.lowerType(.fromInterned(vector_type.child)),
|
|
),
|
|
.opt_type => |child_ty| {
|
|
// Must stay in sync with `opt_payload` logic in `lowerPtr`.
|
|
switch (Type.fromInterned(child_ty).classify(zcu)) {
|
|
.no_possible_value, .fully_comptime => unreachable,
|
|
.one_possible_value => return .i8,
|
|
.runtime, .partially_comptime => {},
|
|
}
|
|
|
|
const payload_ty = try o.lowerType(.fromInterned(child_ty));
|
|
if (t.optionalReprIsPayload(zcu)) return payload_ty;
|
|
|
|
comptime assert(optional_layout_version == 3);
|
|
var fields: [3]Builder.Type = .{ payload_ty, .i8, undefined };
|
|
var fields_len: usize = 2;
|
|
const offset = Type.fromInterned(child_ty).abiSize(zcu) + 1;
|
|
const abi_size = t.abiSize(zcu);
|
|
const padding_len = abi_size - offset;
|
|
if (padding_len > 0) {
|
|
fields[2] = try o.builder.arrayType(padding_len, .i8);
|
|
fields_len = 3;
|
|
}
|
|
return o.builder.structType(.normal, fields[0..fields_len]);
|
|
},
|
|
.anyframe_type => @panic("TODO implement lowerType for AnyFrame types"),
|
|
.error_union_type => |error_union_type| {
|
|
// Must stay in sync with `codegen.errUnionPayloadOffset`.
|
|
// See logic in `lowerPtr`.
|
|
const error_type = try o.errorIntType();
|
|
|
|
switch (Type.fromInterned(error_union_type.payload_type).classify(zcu)) {
|
|
.fully_comptime => unreachable,
|
|
.no_possible_value, .one_possible_value => return error_type,
|
|
.runtime, .partially_comptime => {},
|
|
}
|
|
|
|
const payload_type = try o.lowerType(.fromInterned(error_union_type.payload_type));
|
|
|
|
const payload_align = Type.fromInterned(error_union_type.payload_type).abiAlignment(zcu);
|
|
const error_align: InternPool.Alignment = .fromByteUnits(std.zig.target.intAlignment(target, zcu.errorSetBits()));
|
|
|
|
const payload_size = Type.fromInterned(error_union_type.payload_type).abiSize(zcu);
|
|
const error_size = std.zig.target.intByteSize(target, zcu.errorSetBits());
|
|
|
|
var fields: [3]Builder.Type = undefined;
|
|
var fields_len: usize = 2;
|
|
const padding_len = if (error_align.compare(.gt, payload_align)) pad: {
|
|
fields[0] = error_type;
|
|
fields[1] = payload_type;
|
|
const payload_end =
|
|
payload_align.forward(error_size) +
|
|
payload_size;
|
|
const abi_size = error_align.forward(payload_end);
|
|
break :pad abi_size - payload_end;
|
|
} else pad: {
|
|
fields[0] = payload_type;
|
|
fields[1] = error_type;
|
|
const error_end =
|
|
error_align.forward(payload_size) +
|
|
error_size;
|
|
const abi_size = payload_align.forward(error_end);
|
|
break :pad abi_size - error_end;
|
|
};
|
|
if (padding_len > 0) {
|
|
fields[2] = try o.builder.arrayType(padding_len, .i8);
|
|
fields_len = 3;
|
|
}
|
|
return o.builder.structType(.normal, fields[0..fields_len]);
|
|
},
|
|
.simple_type => unreachable,
|
|
.struct_type => {
|
|
if (o.type_map.get(t.toIntern())) |value| return value;
|
|
|
|
const struct_type = ip.loadStructType(t.toIntern());
|
|
|
|
if (struct_type.layout == .@"packed") {
|
|
const int_ty = try o.lowerType(.fromInterned(struct_type.packed_backing_int_type));
|
|
try o.type_map.put(o.gpa, t.toIntern(), int_ty);
|
|
return int_ty;
|
|
}
|
|
|
|
assert(struct_type.size > 0);
|
|
|
|
var llvm_field_types: std.ArrayList(Builder.Type) = .empty;
|
|
defer llvm_field_types.deinit(o.gpa);
|
|
// Although we can estimate how much capacity to add, these cannot be
|
|
// relied upon because of the recursive calls to lowerType below.
|
|
try llvm_field_types.ensureUnusedCapacity(o.gpa, struct_type.field_types.len);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var struct_kind: Builder.Type.Structure.Kind = .normal;
|
|
// When we encounter a zero-bit field, we place it here so we know to map it to the next non-zero-bit field (if any).
|
|
var it = struct_type.iterateRuntimeOrder(ip);
|
|
var max_field_ty_align: InternPool.Alignment = .@"1";
|
|
while (it.next()) |field_index| {
|
|
const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
|
|
const field_ty_align = field_ty.abiAlignment(zcu);
|
|
max_field_ty_align = max_field_ty_align.maxStrict(field_ty_align);
|
|
|
|
const prev_offset = offset;
|
|
offset = struct_type.field_offsets.get(ip)[field_index];
|
|
if (@ctz(offset) < field_ty_align.toLog2Units()) {
|
|
struct_kind = .@"packed"; // prevent unexpected padding before this field
|
|
}
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) try llvm_field_types.append(
|
|
o.gpa,
|
|
try o.builder.arrayType(padding_len, .i8),
|
|
);
|
|
|
|
if (!field_ty.hasRuntimeBits(zcu)) continue;
|
|
|
|
try llvm_field_types.append(o.gpa, try o.lowerType(field_ty));
|
|
|
|
offset += field_ty.abiSize(zcu);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = struct_type.alignment.forward(offset);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) try llvm_field_types.append(
|
|
o.gpa,
|
|
try o.builder.arrayType(padding_len, .i8),
|
|
);
|
|
if (@ctz(offset) < max_field_ty_align.toLog2Units()) {
|
|
struct_kind = .@"packed"; // prevent unexpected trailing padding
|
|
}
|
|
}
|
|
|
|
const ty = try o.builder.opaqueType(try o.builder.string(t.containerTypeName(ip).toSlice(ip)));
|
|
try o.type_map.put(o.gpa, t.toIntern(), ty);
|
|
|
|
o.builder.namedTypeSetBody(
|
|
ty,
|
|
try o.builder.structType(struct_kind, llvm_field_types.items),
|
|
);
|
|
return ty;
|
|
},
|
|
.tuple_type => |tuple_type| {
|
|
var llvm_field_types: std.ArrayList(Builder.Type) = .empty;
|
|
defer llvm_field_types.deinit(o.gpa);
|
|
// Although we can estimate how much capacity to add, these cannot be
|
|
// relied upon because of the recursive calls to lowerType below.
|
|
try llvm_field_types.ensureUnusedCapacity(o.gpa, tuple_type.types.len);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var big_align: InternPool.Alignment = .@"1";
|
|
|
|
for (
|
|
tuple_type.types.get(ip),
|
|
tuple_type.values.get(ip),
|
|
) |field_ty, field_val| {
|
|
if (field_val != .none) continue;
|
|
|
|
const field_align = Type.fromInterned(field_ty).abiAlignment(zcu);
|
|
big_align = big_align.max(field_align);
|
|
const prev_offset = offset;
|
|
offset = field_align.forward(offset);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) try llvm_field_types.append(
|
|
o.gpa,
|
|
try o.builder.arrayType(padding_len, .i8),
|
|
);
|
|
if (!Type.fromInterned(field_ty).hasRuntimeBits(zcu)) {
|
|
continue;
|
|
}
|
|
try llvm_field_types.append(o.gpa, try o.lowerType(.fromInterned(field_ty)));
|
|
|
|
offset += Type.fromInterned(field_ty).abiSize(zcu);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = big_align.forward(offset);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) try llvm_field_types.append(
|
|
o.gpa,
|
|
try o.builder.arrayType(padding_len, .i8),
|
|
);
|
|
}
|
|
assert(offset > 0);
|
|
return o.builder.structType(.normal, llvm_field_types.items);
|
|
},
|
|
.union_type => {
|
|
if (o.type_map.get(t.toIntern())) |value| return value;
|
|
|
|
const union_obj = ip.loadUnionType(t.toIntern());
|
|
|
|
if (union_obj.layout == .@"packed") {
|
|
const int_ty = try o.lowerType(.fromInterned(union_obj.packed_backing_int_type));
|
|
try o.type_map.put(o.gpa, t.toIntern(), int_ty);
|
|
return int_ty;
|
|
}
|
|
|
|
assert(union_obj.size > 0);
|
|
|
|
const layout = Type.getUnionLayout(union_obj, zcu);
|
|
|
|
if (layout.payload_size == 0) {
|
|
const enum_tag_ty = try o.lowerType(.fromInterned(union_obj.enum_tag_type));
|
|
try o.type_map.put(o.gpa, t.toIntern(), enum_tag_ty);
|
|
return enum_tag_ty;
|
|
}
|
|
|
|
const aligned_field_ty = Type.fromInterned(union_obj.field_types.get(ip)[layout.most_aligned_field]);
|
|
const aligned_field_llvm_ty = try o.lowerType(aligned_field_ty);
|
|
|
|
const payload_ty = ty: {
|
|
if (layout.most_aligned_field_size == layout.payload_size) {
|
|
break :ty aligned_field_llvm_ty;
|
|
}
|
|
const padding_len = if (layout.tag_size == 0)
|
|
layout.abi_size - layout.most_aligned_field_size
|
|
else
|
|
layout.payload_size - layout.most_aligned_field_size;
|
|
break :ty try o.builder.structType(.@"packed", &.{
|
|
aligned_field_llvm_ty,
|
|
try o.builder.arrayType(padding_len, .i8),
|
|
});
|
|
};
|
|
|
|
if (layout.tag_size == 0) {
|
|
const ty = try o.builder.opaqueType(try o.builder.string(t.containerTypeName(ip).toSlice(ip)));
|
|
try o.type_map.put(o.gpa, t.toIntern(), ty);
|
|
|
|
o.builder.namedTypeSetBody(
|
|
ty,
|
|
try o.builder.structType(.normal, &.{payload_ty}),
|
|
);
|
|
return ty;
|
|
}
|
|
const enum_tag_ty = try o.lowerType(.fromInterned(union_obj.enum_tag_type));
|
|
|
|
// Put the tag before or after the payload depending on which one's
|
|
// alignment is greater.
|
|
var llvm_fields: [3]Builder.Type = undefined;
|
|
var llvm_fields_len: usize = 2;
|
|
|
|
if (layout.tag_align.compare(.gte, layout.payload_align)) {
|
|
llvm_fields = .{ enum_tag_ty, payload_ty, .none };
|
|
} else {
|
|
llvm_fields = .{ payload_ty, enum_tag_ty, .none };
|
|
}
|
|
|
|
// Insert padding to make the LLVM struct ABI size match the Zig union ABI size.
|
|
if (layout.padding != 0) {
|
|
llvm_fields[llvm_fields_len] = try o.builder.arrayType(layout.padding, .i8);
|
|
llvm_fields_len += 1;
|
|
}
|
|
|
|
const ty = try o.builder.opaqueType(try o.builder.string(t.containerTypeName(ip).toSlice(ip)));
|
|
try o.type_map.put(o.gpa, t.toIntern(), ty);
|
|
|
|
o.builder.namedTypeSetBody(
|
|
ty,
|
|
try o.builder.structType(.normal, llvm_fields[0..llvm_fields_len]),
|
|
);
|
|
return ty;
|
|
},
|
|
.opaque_type => unreachable, // no runtime bits
|
|
.enum_type => try o.lowerType(t.intTagType(zcu)),
|
|
.func_type => |func_type| try o.lowerFnType(t, func_type),
|
|
.error_set_type, .inferred_error_set_type => try o.errorIntType(),
|
|
// values, not types
|
|
.undef,
|
|
.simple_value,
|
|
.@"extern",
|
|
.func,
|
|
.int,
|
|
.err,
|
|
.error_union,
|
|
.enum_literal,
|
|
.enum_tag,
|
|
.float,
|
|
.ptr,
|
|
.slice,
|
|
.opt,
|
|
.aggregate,
|
|
.un,
|
|
.bitpack,
|
|
// memoization, not types
|
|
.memoized_call,
|
|
=> unreachable,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn lowerFnType(o: *Object, fn_ty: Type, fn_info: InternPool.Key.FuncType) Allocator.Error!Builder.Type {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const target = zcu.getTarget();
|
|
|
|
assert(fn_ty.fnHasRuntimeBits(zcu));
|
|
|
|
const ret_ty = try lowerFnRetTy(o, fn_info);
|
|
|
|
var llvm_params: std.ArrayList(Builder.Type) = .empty;
|
|
defer llvm_params.deinit(o.gpa);
|
|
|
|
if (firstParamSRet(fn_info, zcu, target)) {
|
|
try llvm_params.append(o.gpa, .ptr);
|
|
}
|
|
|
|
if (fn_info.cc == .auto and zcu.comp.config.any_error_tracing) {
|
|
// First parameter is a pointer to `std.builtin.StackTrace`.
|
|
const llvm_ptr_ty = try o.builder.ptrType(toLlvmAddressSpace(.generic, target));
|
|
try llvm_params.append(o.gpa, llvm_ptr_ty);
|
|
}
|
|
|
|
var it = iterateParamTypes(o, fn_info);
|
|
while (try it.next()) |lowering| switch (lowering) {
|
|
.no_bits => continue,
|
|
.byval => {
|
|
const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
try llvm_params.append(o.gpa, try o.lowerType(param_ty));
|
|
},
|
|
.byref, .byref_mut => {
|
|
try llvm_params.append(o.gpa, .ptr);
|
|
},
|
|
.abi_sized_int => {
|
|
const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
try llvm_params.append(o.gpa, try o.builder.intType(
|
|
@intCast(param_ty.abiSize(zcu) * 8),
|
|
));
|
|
},
|
|
.slice => {
|
|
const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
try llvm_params.appendSlice(o.gpa, &.{
|
|
try o.builder.ptrType(toLlvmAddressSpace(param_ty.ptrAddressSpace(zcu), target)),
|
|
try o.lowerType(.usize),
|
|
});
|
|
},
|
|
.multiple_llvm_types => {
|
|
try llvm_params.appendSlice(o.gpa, it.types_buffer[0..it.types_len]);
|
|
},
|
|
.float_array => |count| {
|
|
const param_ty = Type.fromInterned(fn_info.param_types.get(ip)[it.zig_index - 1]);
|
|
const float_ty = try o.lowerType(aarch64_c_abi.getFloatArrayType(param_ty, zcu).?);
|
|
try llvm_params.append(o.gpa, try o.builder.arrayType(count, float_ty));
|
|
},
|
|
.i32_array, .i64_array => |arr_len| {
|
|
try llvm_params.append(o.gpa, try o.builder.arrayType(arr_len, switch (lowering) {
|
|
.i32_array => .i32,
|
|
.i64_array => .i64,
|
|
else => unreachable,
|
|
}));
|
|
},
|
|
};
|
|
|
|
return o.builder.fnType(
|
|
ret_ty,
|
|
llvm_params.items,
|
|
if (fn_info.is_var_args) .vararg else .normal,
|
|
);
|
|
}
|
|
|
|
pub fn lowerValue(o: *Object, arg_val: InternPool.Index) Allocator.Error!Builder.Constant {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const target = zcu.getTarget();
|
|
|
|
const val: Value = .fromInterned(arg_val);
|
|
const val_key = ip.indexToKey(val.toIntern());
|
|
|
|
const ty: Type = .fromInterned(val_key.typeOf());
|
|
ty.assertHasLayout(zcu);
|
|
assert(ty.hasRuntimeBits(zcu));
|
|
|
|
return switch (val_key) {
|
|
.int_type,
|
|
.ptr_type,
|
|
.array_type,
|
|
.vector_type,
|
|
.opt_type,
|
|
.anyframe_type,
|
|
.error_union_type,
|
|
.simple_type,
|
|
.struct_type,
|
|
.tuple_type,
|
|
.union_type,
|
|
.opaque_type,
|
|
.enum_type,
|
|
.func_type,
|
|
.error_set_type,
|
|
.inferred_error_set_type,
|
|
=> unreachable, // types, not values
|
|
|
|
.undef => return o.builder.undefConst(try o.lowerType(ty)),
|
|
.simple_value => |simple_value| switch (simple_value) {
|
|
.void => unreachable, // non-runtime value
|
|
.null => unreachable, // non-runtime value
|
|
.@"unreachable" => unreachable, // non-runtime value
|
|
|
|
.false => .false,
|
|
.true => .true,
|
|
},
|
|
.enum_literal => unreachable, // non-runtime value
|
|
.@"extern" => unreachable, // non-runtime value
|
|
.func => unreachable, // non-runtime value
|
|
.int => {
|
|
var bigint_space: Value.BigIntSpace = undefined;
|
|
const bigint = val.toBigInt(&bigint_space, zcu);
|
|
const llvm_int_ty = try o.builder.intType(ty.intInfo(zcu).bits);
|
|
return o.builder.bigIntConst(llvm_int_ty, bigint);
|
|
},
|
|
.err => |err| {
|
|
const int = zcu.intern_pool.getErrorValueIfExists(err.name).?;
|
|
return o.builder.intConst(try o.errorIntType(), int);
|
|
},
|
|
.error_union => |error_union| {
|
|
const llvm_error_ty = try o.errorIntType();
|
|
const llvm_error_value = switch (error_union.val) {
|
|
.err_name => |name| try o.builder.intConst(
|
|
llvm_error_ty,
|
|
zcu.intern_pool.getErrorValueIfExists(name).?,
|
|
),
|
|
.payload => try o.builder.intConst(llvm_error_ty, 0),
|
|
};
|
|
|
|
const payload_type = ty.errorUnionPayload(zcu);
|
|
if (!payload_type.hasRuntimeBits(zcu)) {
|
|
// We use the error type directly as the type.
|
|
return llvm_error_value;
|
|
}
|
|
|
|
const payload_align = payload_type.abiAlignment(zcu);
|
|
const error_align = Type.errorAbiAlignment(zcu);
|
|
const llvm_payload_value = switch (error_union.val) {
|
|
.err_name => try o.builder.undefConst(try o.lowerType(payload_type)),
|
|
.payload => |payload| try o.lowerValue(payload),
|
|
};
|
|
|
|
var fields: [3]Builder.Type = undefined;
|
|
var vals: [3]Builder.Constant = undefined;
|
|
if (error_align.compare(.gt, payload_align)) {
|
|
vals[0] = llvm_error_value;
|
|
vals[1] = llvm_payload_value;
|
|
} else {
|
|
vals[0] = llvm_payload_value;
|
|
vals[1] = llvm_error_value;
|
|
}
|
|
fields[0] = vals[0].typeOf(&o.builder);
|
|
fields[1] = vals[1].typeOf(&o.builder);
|
|
|
|
const llvm_ty = try o.lowerType(ty);
|
|
const llvm_ty_fields = llvm_ty.structFields(&o.builder);
|
|
if (llvm_ty_fields.len > 2) {
|
|
assert(llvm_ty_fields.len == 3);
|
|
fields[2] = llvm_ty_fields[2];
|
|
vals[2] = try o.builder.undefConst(fields[2]);
|
|
}
|
|
return o.builder.structConst(try o.builder.structType(
|
|
llvm_ty.structKind(&o.builder),
|
|
fields[0..llvm_ty_fields.len],
|
|
), vals[0..llvm_ty_fields.len]);
|
|
},
|
|
.enum_tag => |enum_tag| o.lowerValue(enum_tag.int),
|
|
.float => switch (ty.floatBits(target)) {
|
|
16 => if (backendSupportsF16(target))
|
|
try o.builder.halfConst(val.toFloat(f16, zcu))
|
|
else
|
|
try o.builder.intConst(.i16, @as(i16, @bitCast(val.toFloat(f16, zcu)))),
|
|
32 => try o.builder.floatConst(val.toFloat(f32, zcu)),
|
|
64 => try o.builder.doubleConst(val.toFloat(f64, zcu)),
|
|
80 => if (backendSupportsF80(target))
|
|
try o.builder.x86_fp80Const(val.toFloat(f80, zcu))
|
|
else
|
|
try o.builder.intConst(.i80, @as(i80, @bitCast(val.toFloat(f80, zcu)))),
|
|
128 => try o.builder.fp128Const(val.toFloat(f128, zcu)),
|
|
else => unreachable,
|
|
},
|
|
.ptr => try o.lowerPtr(arg_val, 0),
|
|
.slice => |slice| return o.builder.structConst(try o.lowerType(ty), &.{
|
|
try o.lowerValue(slice.ptr),
|
|
try o.lowerValue(slice.len),
|
|
}),
|
|
.opt => |opt| {
|
|
comptime assert(optional_layout_version == 3);
|
|
const payload_ty = ty.optionalChild(zcu);
|
|
|
|
const non_null_bit = try o.builder.intConst(.i8, @intFromBool(opt.val != .none));
|
|
if (!payload_ty.hasRuntimeBits(zcu)) {
|
|
return non_null_bit;
|
|
}
|
|
const llvm_ty = try o.lowerType(ty);
|
|
if (ty.optionalReprIsPayload(zcu)) return switch (opt.val) {
|
|
.none => switch (llvm_ty.tag(&o.builder)) {
|
|
.integer => try o.builder.intConst(llvm_ty, 0),
|
|
.pointer => try o.builder.nullConst(llvm_ty),
|
|
.structure => try o.builder.zeroInitConst(llvm_ty),
|
|
else => unreachable,
|
|
},
|
|
else => |payload| try o.lowerValue(payload),
|
|
};
|
|
assert(payload_ty.zigTypeTag(zcu) != .@"fn");
|
|
|
|
var fields: [3]Builder.Type = undefined;
|
|
var vals: [3]Builder.Constant = undefined;
|
|
vals[0] = switch (opt.val) {
|
|
.none => try o.builder.undefConst(try o.lowerType(payload_ty)),
|
|
else => |payload| try o.lowerValue(payload),
|
|
};
|
|
vals[1] = non_null_bit;
|
|
fields[0] = vals[0].typeOf(&o.builder);
|
|
fields[1] = vals[1].typeOf(&o.builder);
|
|
|
|
const llvm_ty_fields = llvm_ty.structFields(&o.builder);
|
|
if (llvm_ty_fields.len > 2) {
|
|
assert(llvm_ty_fields.len == 3);
|
|
fields[2] = llvm_ty_fields[2];
|
|
vals[2] = try o.builder.undefConst(fields[2]);
|
|
}
|
|
return o.builder.structConst(try o.builder.structType(
|
|
llvm_ty.structKind(&o.builder),
|
|
fields[0..llvm_ty_fields.len],
|
|
), vals[0..llvm_ty_fields.len]);
|
|
},
|
|
.bitpack => |bitpack| return o.lowerValue(bitpack.backing_int_val),
|
|
.aggregate => |aggregate| switch (ip.indexToKey(ty.toIntern())) {
|
|
.array_type => |array_type| switch (aggregate.storage) {
|
|
.bytes => |bytes| try o.builder.stringConst(try o.builder.string(
|
|
bytes.toSlice(array_type.lenIncludingSentinel(), ip),
|
|
)),
|
|
.elems => |elems| {
|
|
const array_ty = try o.lowerType(ty);
|
|
const elem_ty = array_ty.childType(&o.builder);
|
|
assert(elems.len == array_ty.aggregateLen(&o.builder));
|
|
|
|
const ExpectedContents = extern struct {
|
|
vals: [Builder.expected_fields_len]Builder.Constant,
|
|
fields: [Builder.expected_fields_len]Builder.Type,
|
|
};
|
|
var bfa_buf: ExpectedContents = undefined;
|
|
var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), o.gpa);
|
|
const allocator = bfa.allocator();
|
|
const vals = try allocator.alloc(Builder.Constant, elems.len);
|
|
defer allocator.free(vals);
|
|
const fields = try allocator.alloc(Builder.Type, elems.len);
|
|
defer allocator.free(fields);
|
|
|
|
var need_unnamed = false;
|
|
for (vals, fields, elems) |*result_val, *result_field, elem| {
|
|
result_val.* = try o.lowerValue(elem);
|
|
result_field.* = result_val.typeOf(&o.builder);
|
|
if (result_field.* != elem_ty) need_unnamed = true;
|
|
}
|
|
return if (need_unnamed) try o.builder.structConst(
|
|
try o.builder.structType(.normal, fields),
|
|
vals,
|
|
) else try o.builder.arrayConst(array_ty, vals);
|
|
},
|
|
.repeated_elem => |elem| {
|
|
const len: usize = @intCast(array_type.len);
|
|
const len_including_sentinel: usize = @intCast(array_type.lenIncludingSentinel());
|
|
const array_ty = try o.lowerType(ty);
|
|
const elem_ty = array_ty.childType(&o.builder);
|
|
|
|
const ExpectedContents = extern struct {
|
|
vals: [Builder.expected_fields_len]Builder.Constant,
|
|
fields: [Builder.expected_fields_len]Builder.Type,
|
|
};
|
|
var bfa_buf: ExpectedContents = undefined;
|
|
var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), o.gpa);
|
|
const allocator = bfa.allocator();
|
|
const vals = try allocator.alloc(Builder.Constant, len_including_sentinel);
|
|
defer allocator.free(vals);
|
|
const fields = try allocator.alloc(Builder.Type, len_including_sentinel);
|
|
defer allocator.free(fields);
|
|
|
|
var need_unnamed = false;
|
|
@memset(vals[0..len], try o.lowerValue(elem));
|
|
@memset(fields[0..len], vals[0].typeOf(&o.builder));
|
|
if (fields[0] != elem_ty) need_unnamed = true;
|
|
|
|
if (array_type.sentinel != .none) {
|
|
vals[len] = try o.lowerValue(array_type.sentinel);
|
|
fields[len] = vals[len].typeOf(&o.builder);
|
|
if (fields[len] != elem_ty) need_unnamed = true;
|
|
}
|
|
|
|
return if (need_unnamed) try o.builder.structConst(
|
|
try o.builder.structType(.@"packed", fields),
|
|
vals,
|
|
) else try o.builder.arrayConst(array_ty, vals);
|
|
},
|
|
},
|
|
.vector_type => |vector_type| {
|
|
const vector_ty = try o.lowerType(ty);
|
|
switch (aggregate.storage) {
|
|
.bytes, .elems => {
|
|
const ExpectedContents = [Builder.expected_fields_len]Builder.Constant;
|
|
var bfa_buf: ExpectedContents = undefined;
|
|
var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), o.gpa);
|
|
const allocator = bfa.allocator();
|
|
const vals = try allocator.alloc(Builder.Constant, vector_type.len);
|
|
defer allocator.free(vals);
|
|
|
|
switch (aggregate.storage) {
|
|
.bytes => |bytes| for (vals, bytes.toSlice(vector_type.len, ip)) |*result_val, byte| {
|
|
result_val.* = try o.builder.intConst(.i8, byte);
|
|
},
|
|
.elems => |elems| for (vals, elems) |*result_val, elem| {
|
|
result_val.* = try o.lowerValue(elem);
|
|
},
|
|
.repeated_elem => unreachable,
|
|
}
|
|
return o.builder.vectorConst(vector_ty, vals);
|
|
},
|
|
.repeated_elem => |elem| return o.builder.splatConst(
|
|
vector_ty,
|
|
try o.lowerValue(elem),
|
|
),
|
|
}
|
|
},
|
|
.tuple_type => |tuple| {
|
|
const struct_ty = try o.lowerType(ty);
|
|
const llvm_len = struct_ty.aggregateLen(&o.builder);
|
|
|
|
const ExpectedContents = extern struct {
|
|
vals: [Builder.expected_fields_len]Builder.Constant,
|
|
fields: [Builder.expected_fields_len]Builder.Type,
|
|
};
|
|
var bfa_buf: ExpectedContents = undefined;
|
|
var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), o.gpa);
|
|
const allocator = bfa.allocator();
|
|
const vals = try allocator.alloc(Builder.Constant, llvm_len);
|
|
defer allocator.free(vals);
|
|
const fields = try allocator.alloc(Builder.Type, llvm_len);
|
|
defer allocator.free(fields);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var llvm_index: usize = 0;
|
|
var offset: u64 = 0;
|
|
var big_align: InternPool.Alignment = .@"1";
|
|
var need_unnamed = false;
|
|
for (
|
|
tuple.types.get(ip),
|
|
tuple.values.get(ip),
|
|
0..,
|
|
) |field_ty, field_comptime_val, field_index| {
|
|
if (field_comptime_val != .none) continue;
|
|
if (!Type.fromInterned(field_ty).hasRuntimeBits(zcu)) continue;
|
|
|
|
const field_align = Type.fromInterned(field_ty).abiAlignment(zcu);
|
|
big_align = big_align.max(field_align);
|
|
const prev_offset = offset;
|
|
offset = field_align.forward(offset);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
// TODO make this and all other padding elsewhere in debug
|
|
// builds be 0xaa not undef.
|
|
fields[llvm_index] = try o.builder.arrayType(padding_len, .i8);
|
|
vals[llvm_index] = try o.builder.undefConst(fields[llvm_index]);
|
|
assert(fields[llvm_index] == struct_ty.structFields(&o.builder)[llvm_index]);
|
|
llvm_index += 1;
|
|
}
|
|
|
|
vals[llvm_index] = switch (aggregate.storage) {
|
|
.bytes => |bytes| try o.builder.intConst(.i8, bytes.at(field_index, ip)),
|
|
.elems => |elems| try o.lowerValue(elems[field_index]),
|
|
.repeated_elem => |elem| try o.lowerValue(elem),
|
|
};
|
|
fields[llvm_index] = vals[llvm_index].typeOf(&o.builder);
|
|
if (fields[llvm_index] != struct_ty.structFields(&o.builder)[llvm_index])
|
|
need_unnamed = true;
|
|
llvm_index += 1;
|
|
|
|
offset += Type.fromInterned(field_ty).abiSize(zcu);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = big_align.forward(offset);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
fields[llvm_index] = try o.builder.arrayType(padding_len, .i8);
|
|
vals[llvm_index] = try o.builder.undefConst(fields[llvm_index]);
|
|
assert(fields[llvm_index] == struct_ty.structFields(&o.builder)[llvm_index]);
|
|
llvm_index += 1;
|
|
}
|
|
}
|
|
assert(llvm_index == llvm_len);
|
|
|
|
return o.builder.structConst(if (need_unnamed)
|
|
try o.builder.structType(struct_ty.structKind(&o.builder), fields)
|
|
else
|
|
struct_ty, vals);
|
|
},
|
|
.struct_type => {
|
|
const struct_type = ip.loadStructType(ty.toIntern());
|
|
const struct_ty = try o.lowerType(ty);
|
|
assert(struct_type.layout != .@"packed");
|
|
const llvm_len = struct_ty.aggregateLen(&o.builder);
|
|
|
|
const ExpectedContents = extern struct {
|
|
vals: [Builder.expected_fields_len]Builder.Constant,
|
|
fields: [Builder.expected_fields_len]Builder.Type,
|
|
};
|
|
var bfa_buf: ExpectedContents = undefined;
|
|
var bfa: std.heap.BufferFirstAllocator = .init(@ptrCast(&bfa_buf), o.gpa);
|
|
const allocator = bfa.allocator();
|
|
const vals = try allocator.alloc(Builder.Constant, llvm_len);
|
|
defer allocator.free(vals);
|
|
const fields = try allocator.alloc(Builder.Type, llvm_len);
|
|
defer allocator.free(fields);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var llvm_index: usize = 0;
|
|
var offset: u64 = 0;
|
|
var need_unnamed = false;
|
|
var field_it = struct_type.iterateRuntimeOrder(ip);
|
|
while (field_it.next()) |field_index| {
|
|
const field_ty = Type.fromInterned(struct_type.field_types.get(ip)[field_index]);
|
|
const prev_offset = offset;
|
|
offset = struct_type.field_offsets.get(ip)[field_index];
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
// TODO make this and all other padding elsewhere in debug
|
|
// builds be 0xaa not undef.
|
|
fields[llvm_index] = try o.builder.arrayType(padding_len, .i8);
|
|
vals[llvm_index] = try o.builder.undefConst(fields[llvm_index]);
|
|
assert(fields[llvm_index] ==
|
|
struct_ty.structFields(&o.builder)[llvm_index]);
|
|
llvm_index += 1;
|
|
}
|
|
|
|
if (!field_ty.hasRuntimeBits(zcu)) {
|
|
// This is a zero-bit field - we only needed it for the alignment.
|
|
continue;
|
|
}
|
|
|
|
vals[llvm_index] = switch (aggregate.storage) {
|
|
.bytes => |bytes| try o.builder.intConst(.i8, bytes.at(field_index, ip)),
|
|
.elems => |elems| try o.lowerValue(elems[field_index]),
|
|
.repeated_elem => |elem| try o.lowerValue(elem),
|
|
};
|
|
fields[llvm_index] = vals[llvm_index].typeOf(&o.builder);
|
|
if (fields[llvm_index] != struct_ty.structFields(&o.builder)[llvm_index])
|
|
need_unnamed = true;
|
|
llvm_index += 1;
|
|
|
|
offset += field_ty.abiSize(zcu);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = struct_type.alignment.forward(offset);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
fields[llvm_index] = try o.builder.arrayType(padding_len, .i8);
|
|
vals[llvm_index] = try o.builder.undefConst(fields[llvm_index]);
|
|
assert(fields[llvm_index] == struct_ty.structFields(&o.builder)[llvm_index]);
|
|
llvm_index += 1;
|
|
}
|
|
}
|
|
assert(llvm_index == llvm_len);
|
|
|
|
return o.builder.structConst(if (need_unnamed)
|
|
try o.builder.structType(struct_ty.structKind(&o.builder), fields)
|
|
else
|
|
struct_ty, vals);
|
|
},
|
|
else => unreachable,
|
|
},
|
|
.un => |un| {
|
|
const union_ty = try o.lowerType(ty);
|
|
const layout = ty.unionGetLayout(zcu);
|
|
if (layout.payload_size == 0) return o.lowerValue(un.tag);
|
|
|
|
const union_obj = zcu.typeToUnion(ty).?;
|
|
const container_layout = union_obj.layout;
|
|
assert(container_layout != .@"packed");
|
|
|
|
var need_unnamed = false;
|
|
const payload = if (un.tag != .none) p: {
|
|
const field_index = zcu.unionTagFieldIndex(union_obj, Value.fromInterned(un.tag)).?;
|
|
const field_ty = Type.fromInterned(union_obj.field_types.get(ip)[field_index]);
|
|
|
|
// Sometimes we must make an unnamed struct because LLVM does
|
|
// not support bitcasting our payload struct to the true union payload type.
|
|
// Instead we use an unnamed struct and every reference to the global
|
|
// must pointer cast to the expected type before accessing the union.
|
|
need_unnamed = layout.most_aligned_field != field_index;
|
|
|
|
if (!field_ty.hasRuntimeBits(zcu)) {
|
|
const padding_len = layout.payload_size;
|
|
break :p try o.builder.undefConst(try o.builder.arrayType(padding_len, .i8));
|
|
}
|
|
const payload = try o.lowerValue(un.val);
|
|
const payload_ty = payload.typeOf(&o.builder);
|
|
if (payload_ty != union_ty.structFields(&o.builder)[
|
|
@intFromBool(layout.tag_size > 0 and layout.tag_align.compare(.gte, layout.payload_align))
|
|
]) need_unnamed = true;
|
|
const field_size = field_ty.abiSize(zcu);
|
|
if (field_size == layout.payload_size) break :p payload;
|
|
const padding_len = layout.payload_size - field_size;
|
|
const padding_ty = try o.builder.arrayType(padding_len, .i8);
|
|
break :p try o.builder.structConst(
|
|
try o.builder.structType(.@"packed", &.{ payload_ty, padding_ty }),
|
|
&.{ payload, try o.builder.undefConst(padding_ty) },
|
|
);
|
|
} else p: {
|
|
assert(layout.tag_size == 0);
|
|
const union_val = try o.lowerValue(un.val);
|
|
need_unnamed = true;
|
|
break :p union_val;
|
|
};
|
|
|
|
const payload_ty = payload.typeOf(&o.builder);
|
|
if (layout.tag_size == 0) return o.builder.structConst(if (need_unnamed)
|
|
try o.builder.structType(union_ty.structKind(&o.builder), &.{payload_ty})
|
|
else
|
|
union_ty, &.{payload});
|
|
const tag = try o.lowerValue(un.tag);
|
|
const tag_ty = tag.typeOf(&o.builder);
|
|
var fields: [3]Builder.Type = undefined;
|
|
var vals: [3]Builder.Constant = undefined;
|
|
var len: usize = 2;
|
|
if (layout.tag_align.compare(.gte, layout.payload_align)) {
|
|
fields = .{ tag_ty, payload_ty, undefined };
|
|
vals = .{ tag, payload, undefined };
|
|
} else {
|
|
fields = .{ payload_ty, tag_ty, undefined };
|
|
vals = .{ payload, tag, undefined };
|
|
}
|
|
if (layout.padding != 0) {
|
|
fields[2] = try o.builder.arrayType(layout.padding, .i8);
|
|
vals[2] = try o.builder.undefConst(fields[2]);
|
|
len = 3;
|
|
}
|
|
return o.builder.structConst(if (need_unnamed)
|
|
try o.builder.structType(union_ty.structKind(&o.builder), fields[0..len])
|
|
else
|
|
union_ty, vals[0..len]);
|
|
},
|
|
.memoized_call => unreachable,
|
|
};
|
|
}
|
|
|
|
fn lowerPtr(
|
|
o: *Object,
|
|
ptr_val: InternPool.Index,
|
|
prev_offset: u64,
|
|
) Allocator.Error!Builder.Constant {
|
|
const zcu = o.zcu;
|
|
const ptr = zcu.intern_pool.indexToKey(ptr_val).ptr;
|
|
const offset: u64 = prev_offset + ptr.byte_offset;
|
|
return switch (ptr.base_addr) {
|
|
.nav => |nav| {
|
|
const base_ptr = try o.lowerNavRef(nav);
|
|
return o.builder.gepConst(.inbounds, .i8, base_ptr, null, &.{
|
|
try o.builder.intConst(.i64, offset),
|
|
});
|
|
},
|
|
.uav => |uav| {
|
|
const orig_ptr_ty: Type = .fromInterned(uav.orig_ty);
|
|
const base_ptr = try o.lowerUavRef(
|
|
uav.val,
|
|
orig_ptr_ty.ptrAlignment(zcu),
|
|
orig_ptr_ty.ptrAddressSpace(zcu),
|
|
);
|
|
return o.builder.gepConst(.inbounds, .i8, base_ptr, null, &.{
|
|
try o.builder.intConst(.i64, offset),
|
|
});
|
|
},
|
|
.int => try o.builder.castConst(
|
|
.inttoptr,
|
|
try o.builder.intConst(try o.lowerType(.usize), offset),
|
|
try o.lowerType(.fromInterned(ptr.ty)),
|
|
),
|
|
.eu_payload => |eu_ptr| try o.lowerPtr(
|
|
eu_ptr,
|
|
offset + codegen.errUnionPayloadOffset(
|
|
Value.fromInterned(eu_ptr).typeOf(zcu).childType(zcu),
|
|
zcu,
|
|
),
|
|
),
|
|
.opt_payload => |opt_ptr| try o.lowerPtr(opt_ptr, offset),
|
|
.field => |field| {
|
|
const agg_ty = Value.fromInterned(field.base).typeOf(zcu).childType(zcu);
|
|
const field_off: u64 = switch (agg_ty.zigTypeTag(zcu)) {
|
|
.pointer => off: {
|
|
assert(agg_ty.isSlice(zcu));
|
|
break :off switch (field.index) {
|
|
Value.slice_ptr_index => 0,
|
|
Value.slice_len_index => @divExact(zcu.getTarget().ptrBitWidth(), 8),
|
|
else => unreachable,
|
|
};
|
|
},
|
|
.@"struct", .@"union" => switch (agg_ty.containerLayout(zcu)) {
|
|
.auto => agg_ty.structFieldOffset(@intCast(field.index), zcu),
|
|
.@"extern", .@"packed" => unreachable,
|
|
},
|
|
else => unreachable,
|
|
};
|
|
return o.lowerPtr(field.base, offset + field_off);
|
|
},
|
|
.arr_elem => |arr_elem| {
|
|
const base_ptr_ty = Value.fromInterned(arr_elem.base).typeOf(zcu);
|
|
assert(base_ptr_ty.ptrSize(zcu) == .many);
|
|
const elem_size = base_ptr_ty.childType(zcu).abiSize(zcu);
|
|
return o.lowerPtr(arr_elem.base, offset + elem_size * arr_elem.index);
|
|
},
|
|
.comptime_field => unreachable,
|
|
.comptime_alloc => unreachable,
|
|
};
|
|
}
|
|
|
|
pub fn lowerPtrToVoid(
|
|
o: *Object,
|
|
/// Must not be `.none`.
|
|
@"align": InternPool.Alignment,
|
|
@"addrspace": std.builtin.AddressSpace,
|
|
) Allocator.Error!Builder.Constant {
|
|
const addr: u64 = @"align".toByteUnits().?;
|
|
const llvm_usize = try o.lowerType(.usize);
|
|
const llvm_addr = try o.builder.intConst(llvm_usize, addr);
|
|
const llvm_ptr_ty = try o.builder.ptrType(toLlvmAddressSpace(@"addrspace", o.zcu.getTarget()));
|
|
return o.builder.castConst(.inttoptr, llvm_addr, llvm_ptr_ty);
|
|
}
|
|
|
|
pub fn lowerUavRef(
|
|
o: *Object,
|
|
uav_val: InternPool.Index,
|
|
/// Must not be `.none`.
|
|
@"align": InternPool.Alignment,
|
|
@"addrspace": std.builtin.AddressSpace,
|
|
) Allocator.Error!Builder.Constant {
|
|
assert(@"align" != .none);
|
|
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const gpa = zcu.comp.gpa;
|
|
|
|
const uav_ty: Type = .fromInterned(ip.typeOf(uav_val));
|
|
|
|
switch (ip.indexToKey(uav_val)) {
|
|
.func => unreachable, // should be using a Nav ref
|
|
.@"extern" => unreachable, // should be using a Nav ref
|
|
else => {},
|
|
}
|
|
|
|
if (!uav_ty.hasRuntimeBits(zcu)) {
|
|
return o.lowerPtrToVoid(@"align", @"addrspace");
|
|
}
|
|
|
|
const llvm_addrspace = toLlvmAddressSpace(@"addrspace", zcu.getTarget());
|
|
|
|
const gop = try o.uav_map.getOrPut(gpa, .{ .val = uav_val, .@"addrspace" = @"addrspace" });
|
|
if (gop.found_existing) {
|
|
// Keep the greater of the two alignments.
|
|
const llvm_variable = gop.value_ptr.*;
|
|
const old_align: InternPool.Alignment = .fromLlvm(llvm_variable.getAlignment(&o.builder));
|
|
llvm_variable.setAlignment(old_align.maxStrict(@"align").toLlvm(), &o.builder);
|
|
return llvm_variable.ptrConst(&o.builder).global.toConst();
|
|
}
|
|
errdefer assert(o.uav_map.remove(.{ .val = uav_val, .@"addrspace" = @"addrspace" }));
|
|
|
|
const llvm_ty = try o.lowerType(uav_ty);
|
|
const llvm_name = try o.builder.strtabStringFmt("__anon_{d}", .{@intFromEnum(uav_val)});
|
|
const llvm_variable = try o.builder.addVariable(llvm_name, llvm_ty, llvm_addrspace);
|
|
gop.value_ptr.* = llvm_variable;
|
|
try llvm_variable.setInitializer(try o.lowerValue(uav_val), &o.builder);
|
|
llvm_variable.setMutability(.constant, &o.builder);
|
|
llvm_variable.setAlignment(@"align".toLlvm(), &o.builder);
|
|
const llvm_global = llvm_variable.ptrConst(&o.builder).global;
|
|
llvm_global.setLinkage(if (o.builder.strip) .private else .internal, &o.builder);
|
|
llvm_global.setUnnamedAddr(.unnamed_addr, &o.builder);
|
|
return llvm_global.toConst();
|
|
}
|
|
|
|
pub fn lowerNavRef(o: *Object, nav_id: InternPool.Nav.Index) Allocator.Error!Builder.Constant {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const gpa = zcu.comp.gpa;
|
|
|
|
const nav = ip.getNav(nav_id);
|
|
const nav_ty: Type = .fromInterned(nav.resolved.?.type);
|
|
if (!nav_ty.isRuntimeFnOrHasRuntimeBits(zcu) and nav.getExtern(ip) == null) {
|
|
const nav_align = switch (nav.resolved.?.@"align") {
|
|
.none => nav_ty.abiAlignment(zcu),
|
|
else => |a| a,
|
|
};
|
|
return o.lowerPtrToVoid(nav_align, nav.resolved.?.@"addrspace");
|
|
}
|
|
|
|
const gop = try o.nav_map.getOrPut(gpa, nav_id);
|
|
if (!gop.found_existing) {
|
|
errdefer assert(o.nav_map.remove(nav_id));
|
|
// The NAV hasn't been lowered yet, so generate a placeholder global whose details will
|
|
// be filled in later.
|
|
const llvm_name = try o.builder.strtabString(nav.fqn.toSlice(ip));
|
|
gop.value_ptr.* = try o.builder.addGlobal(llvm_name, .{
|
|
.type = .void, // placeholder; populated by `updateNav`/`updateFunc`
|
|
.kind = .{ .alias = .none }, // placeholder; populated by `updateNav`/`updateFunc`
|
|
});
|
|
}
|
|
const llvm_global = gop.value_ptr.*;
|
|
|
|
// We need to make sure the global's address space is up to date, because that affects the
|
|
// type of a pointer to this global. But everything else about the global will be populated
|
|
// by `updateNav` or `updateFunc`.
|
|
llvm_global.ptr(&o.builder).addr_space = toLlvmAddressSpace(nav.resolved.?.@"addrspace", zcu.getTarget());
|
|
return llvm_global.toConst();
|
|
}
|
|
|
|
pub fn addByValParamAttrs(
|
|
o: *Object,
|
|
pt: Zcu.PerThread,
|
|
attributes: *Builder.FunctionAttributes.Wip,
|
|
param_ty: Type,
|
|
param_index: u32,
|
|
fn_info: InternPool.Key.FuncType,
|
|
llvm_arg_i: u32,
|
|
) Allocator.Error!void {
|
|
const zcu = o.zcu;
|
|
if (param_ty.isPtrAtRuntime(zcu)) {
|
|
const ptr_info = param_ty.ptrInfo(zcu);
|
|
if (std.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(zcu) and
|
|
!ptr_info.flags.is_allowzero and
|
|
ptr_info.flags.address_space == .generic)
|
|
{
|
|
try attributes.addParamAttr(llvm_arg_i, .nonnull, &o.builder);
|
|
}
|
|
switch (fn_info.cc) {
|
|
else => {},
|
|
.x86_64_interrupt,
|
|
.x86_interrupt,
|
|
=> {
|
|
const child_type = try lowerType(o, Type.fromInterned(ptr_info.child));
|
|
try attributes.addParamAttr(llvm_arg_i, .{ .byval = child_type }, &o.builder);
|
|
},
|
|
}
|
|
if (ptr_info.flags.is_const) {
|
|
try attributes.addParamAttr(llvm_arg_i, .readonly, &o.builder);
|
|
}
|
|
const elem_align: Builder.Alignment.Lazy = switch (ptr_info.flags.alignment) {
|
|
else => |a| .wrap(a.toLlvm()),
|
|
.none => try o.lazyAbiAlignment(pt, .fromInterned(ptr_info.child)),
|
|
};
|
|
try attributes.addParamAttr(llvm_arg_i, .{ .@"align" = elem_align }, &o.builder);
|
|
} else if (ccAbiPromoteInt(fn_info.cc, zcu, 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),
|
|
};
|
|
}
|
|
|
|
pub fn addByRefParamAttrs(
|
|
o: *Object,
|
|
attributes: *Builder.FunctionAttributes.Wip,
|
|
llvm_arg_i: u32,
|
|
alignment: Builder.Alignment,
|
|
byval: 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" = .wrap(alignment) }, &o.builder);
|
|
if (byval) try attributes.addParamAttr(llvm_arg_i, .{ .byval = param_llvm_ty }, &o.builder);
|
|
}
|
|
|
|
pub fn getErrorNameTable(o: *Object) Allocator.Error!Builder.Variable.Index {
|
|
if (o.error_name_table != .none) return o.error_name_table;
|
|
|
|
const name = try o.builder.strtabString("__zig_error_name_table");
|
|
// TODO: Address space
|
|
const variable_index = try o.builder.addVariable(name, .ptr, .default);
|
|
variable_index.setMutability(.constant, &o.builder);
|
|
variable_index.setAlignment(
|
|
Type.slice_const_u8_sentinel_0.abiAlignment(o.zcu).toLlvm(),
|
|
&o.builder,
|
|
);
|
|
const global_index = variable_index.ptrConst(&o.builder).global;
|
|
global_index.setLinkage(.private, &o.builder);
|
|
global_index.setUnnamedAddr(.unnamed_addr, &o.builder);
|
|
|
|
o.error_name_table = variable_index;
|
|
return variable_index;
|
|
}
|
|
|
|
pub fn getErrorsLen(o: *Object) Allocator.Error!Builder.Variable.Index {
|
|
const builder = &o.builder;
|
|
if (o.errors_len_variable == .none) {
|
|
const llvm_err_int_ty = try o.errorIntType();
|
|
const name = try builder.strtabString("__zig_errors_len");
|
|
const variable_index = try builder.addVariable(name, llvm_err_int_ty, .default);
|
|
variable_index.setMutability(.constant, builder);
|
|
variable_index.setAlignment(Type.errorAbiAlignment(o.zcu).toLlvm(), builder);
|
|
const global_index = variable_index.ptrConst(&o.builder).global;
|
|
global_index.setLinkage(.private, builder);
|
|
global_index.setUnnamedAddr(.unnamed_addr, builder);
|
|
o.errors_len_variable = variable_index;
|
|
}
|
|
return o.errors_len_variable;
|
|
}
|
|
|
|
pub fn getEnumTagNameFunction(o: *Object, enum_ty: Type) Allocator.Error!Builder.Function.Index {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
|
|
const gop = try o.enum_tag_name_map.getOrPut(o.gpa, enum_ty.toIntern());
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
errdefer assert(o.enum_tag_name_map.remove(enum_ty.toIntern()));
|
|
const function_index = try o.builder.addFunction(
|
|
// Dummy function type; `updateEnumTagNameFunction` will replace it with the correct type.
|
|
// TODO: change the builder API so we don't need to do this.
|
|
try o.builder.fnType(.void, &.{}, .normal),
|
|
try o.builder.strtabStringFmt("__zig_tag_name_{f}", .{enum_ty.containerTypeName(ip).fmt(ip)}),
|
|
toLlvmAddressSpace(.generic, zcu.getTarget()),
|
|
);
|
|
gop.value_ptr.* = function_index;
|
|
try o.updateEnumTagNameFunction(enum_ty, function_index);
|
|
return function_index;
|
|
}
|
|
fn updateEnumTagNameFunction(
|
|
o: *Object,
|
|
enum_ty: Type,
|
|
function_index: Builder.Function.Index,
|
|
) Allocator.Error!void {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const loaded_enum = ip.loadEnumType(enum_ty.toIntern());
|
|
|
|
const llvm_usize_ty = try o.lowerType(.usize);
|
|
const llvm_ret_ty = try o.lowerType(.slice_const_u8_sentinel_0);
|
|
const llvm_int_ty = try o.lowerType(.fromInterned(loaded_enum.int_tag_type));
|
|
|
|
function_index.ptrConst(&o.builder).global.ptr(&o.builder).type =
|
|
try o.builder.fnType(llvm_ret_ty, &.{llvm_int_ty}, .normal);
|
|
|
|
var attributes: Builder.FunctionAttributes.Wip = .{};
|
|
defer attributes.deinit(&o.builder);
|
|
try o.addCommonFnAttributes(&attributes, zcu.root_mod, zcu.root_mod.omit_frame_pointer);
|
|
|
|
function_index.setLinkage(if (o.builder.strip) .private else .internal, &o.builder);
|
|
function_index.setCallConv(.fastcc, &o.builder);
|
|
function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
|
|
|
|
var wip = try Builder.WipFunction.init(&o.builder, .{
|
|
.function = function_index,
|
|
.strip = true,
|
|
});
|
|
defer wip.deinit();
|
|
wip.cursor = .{ .block = try wip.block(0, "Entry") };
|
|
|
|
const bad_value_block = try wip.block(1, "BadValue");
|
|
const tag_int_value = wip.arg(0);
|
|
var wip_switch = try wip.@"switch"(
|
|
tag_int_value,
|
|
bad_value_block,
|
|
@intCast(loaded_enum.field_names.len),
|
|
.none,
|
|
);
|
|
defer wip_switch.finish(&wip);
|
|
|
|
for (0..loaded_enum.field_names.len) |field_index| {
|
|
const name = try o.builder.stringNull(loaded_enum.field_names.get(ip)[field_index].toSlice(ip));
|
|
const name_init = try o.builder.stringConst(name);
|
|
const name_variable_index = try o.builder.addVariable(.empty, name_init.typeOf(&o.builder), .default);
|
|
try name_variable_index.setInitializer(name_init, &o.builder);
|
|
name_variable_index.setMutability(.constant, &o.builder);
|
|
name_variable_index.setAlignment(comptime Builder.Alignment.fromByteUnits(1), &o.builder);
|
|
const name_global_index = name_variable_index.ptrConst(&o.builder).global;
|
|
name_global_index.setLinkage(.private, &o.builder);
|
|
name_global_index.setUnnamedAddr(.unnamed_addr, &o.builder);
|
|
|
|
const name_val = try o.builder.structValue(llvm_ret_ty, &.{
|
|
name_global_index.toConst(),
|
|
try o.builder.intConst(llvm_usize_ty, name.slice(&o.builder).?.len - 1),
|
|
});
|
|
|
|
const return_block = try wip.block(1, "Name");
|
|
const llvm_tag_val = switch (loaded_enum.field_values.getOrNone(ip, field_index)) {
|
|
.none => try o.builder.intConst(llvm_int_ty, field_index), // auto-numbered
|
|
else => |tag_val_ip| try o.lowerValue(tag_val_ip),
|
|
};
|
|
try wip_switch.addCase(llvm_tag_val, return_block, &wip);
|
|
|
|
wip.cursor = .{ .block = return_block };
|
|
_ = try wip.ret(name_val);
|
|
}
|
|
|
|
wip.cursor = .{ .block = bad_value_block };
|
|
_ = try wip.@"unreachable"();
|
|
|
|
try wip.finish();
|
|
}
|
|
|
|
pub fn lazyAbiAlignment(o: *Object, pt: Zcu.PerThread, ty: Type) Allocator.Error!Builder.Alignment.Lazy {
|
|
const index = try o.type_pool.get(pt, .{ .llvm = o }, ty.toIntern());
|
|
return o.lazy_abi_aligns.items[@intFromEnum(index)];
|
|
}
|
|
|
|
pub fn getIsNamedEnumValueFunction(o: *Object, enum_ty: Type) Allocator.Error!Builder.Function.Index {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
|
|
const gop = try o.named_enum_map.getOrPut(o.gpa, enum_ty.toIntern());
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
errdefer assert(o.named_enum_map.remove(enum_ty.toIntern()));
|
|
const function_index = try o.builder.addFunction(
|
|
// Dummy function type; `updateIsNamedEnumValue` will replace it with the correct type.
|
|
// TODO: change the builder API so we don't need to do this.
|
|
try o.builder.fnType(.void, &.{}, .normal),
|
|
try o.builder.strtabStringFmt("__zig_is_named_enum_value_{f}", .{enum_ty.containerTypeName(ip).fmt(ip)}),
|
|
toLlvmAddressSpace(.generic, zcu.getTarget()),
|
|
);
|
|
gop.value_ptr.* = function_index;
|
|
try o.updateIsNamedEnumValueFunction(enum_ty, function_index);
|
|
return function_index;
|
|
}
|
|
fn updateIsNamedEnumValueFunction(
|
|
o: *Object,
|
|
enum_ty: Type,
|
|
function_index: Builder.Function.Index,
|
|
) Allocator.Error!void {
|
|
const zcu = o.zcu;
|
|
const ip = &zcu.intern_pool;
|
|
const loaded_enum = ip.loadEnumType(enum_ty.toIntern());
|
|
|
|
const llvm_int_ty = try o.lowerType(.fromInterned(loaded_enum.int_tag_type));
|
|
function_index.ptrConst(&o.builder).global.ptr(&o.builder).type =
|
|
try o.builder.fnType(.i1, &.{llvm_int_ty}, .normal);
|
|
|
|
var attributes: Builder.FunctionAttributes.Wip = .{};
|
|
defer attributes.deinit(&o.builder);
|
|
try o.addCommonFnAttributes(&attributes, zcu.root_mod, zcu.root_mod.omit_frame_pointer);
|
|
|
|
function_index.setLinkage(if (o.builder.strip) .private else .internal, &o.builder);
|
|
function_index.setCallConv(.fastcc, &o.builder);
|
|
function_index.setAttributes(try attributes.finish(&o.builder), &o.builder);
|
|
|
|
var wip: Builder.WipFunction = try .init(&o.builder, .{
|
|
.function = function_index,
|
|
.strip = true,
|
|
});
|
|
defer wip.deinit();
|
|
wip.cursor = .{ .block = try wip.block(0, "Entry") };
|
|
|
|
const named_block = try wip.block(@intCast(loaded_enum.field_names.len), "Named");
|
|
const unnamed_block = try wip.block(1, "Unnamed");
|
|
const tag_int_value = wip.arg(0);
|
|
var wip_switch = try wip.@"switch"(tag_int_value, unnamed_block, @intCast(loaded_enum.field_names.len), .none);
|
|
defer wip_switch.finish(&wip);
|
|
|
|
if (loaded_enum.field_values.len > 0) {
|
|
for (loaded_enum.field_values.get(ip)) |tag_val_ip| {
|
|
const llvm_tag_val = try o.lowerValue(tag_val_ip);
|
|
try wip_switch.addCase(llvm_tag_val, named_block, &wip);
|
|
}
|
|
} else {
|
|
// Auto-numbered.
|
|
for (0..loaded_enum.field_names.len) |field_index| {
|
|
const llvm_tag_val = try o.builder.intConst(llvm_int_ty, field_index);
|
|
try wip_switch.addCase(llvm_tag_val, named_block, &wip);
|
|
}
|
|
}
|
|
|
|
wip.cursor = .{ .block = named_block };
|
|
_ = try wip.ret(.true);
|
|
|
|
wip.cursor = .{ .block = unnamed_block };
|
|
_ = try wip.ret(.false);
|
|
|
|
try wip.finish();
|
|
}
|
|
|
|
pub fn getLibcFunction(
|
|
o: *Object,
|
|
fn_name: Builder.StrtabString,
|
|
param_types: []const Builder.Type,
|
|
return_type: Builder.Type,
|
|
) Allocator.Error!Builder.Function.Index {
|
|
if (o.builder.getGlobal(fn_name)) |global| return switch (global.ptrConst(&o.builder).kind) {
|
|
.alias => |alias| alias.getAliasee(&o.builder).ptrConst(&o.builder).kind.function,
|
|
.function => |function| function,
|
|
.variable, .replaced => unreachable,
|
|
};
|
|
return o.builder.addFunction(
|
|
try o.builder.fnType(return_type, param_types, .normal),
|
|
fn_name,
|
|
toLlvmAddressSpace(.generic, o.zcu.getTarget()),
|
|
);
|
|
}
|
|
|
|
pub fn ptraddConst(o: *Object, wip: *Builder.WipFunction, ptr: Builder.Value, offset: u64) Allocator.Error!Builder.Value {
|
|
if (offset == 0) return ptr;
|
|
const llvm_usize_ty = try o.lowerType(.usize);
|
|
const offset_val = try o.builder.intValue(llvm_usize_ty, offset);
|
|
return wip.gep(.inbounds, .i8, ptr, &.{offset_val}, "");
|
|
}
|
|
};
|
|
|
|
const CallingConventionInfo = struct {
|
|
/// The LLVM calling convention to use.
|
|
llvm_cc: Builder.CallConv,
|
|
/// Whether to use an `alignstack` attribute to forcibly re-align the stack pointer in the function's prologue.
|
|
align_stack: bool,
|
|
/// Whether the function needs a `naked` attribute.
|
|
naked: bool,
|
|
/// How many leading parameters to apply the `inreg` attribute to.
|
|
inreg_param_count: u2 = 0,
|
|
};
|
|
|
|
pub fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: *const std.Target) ?CallingConventionInfo {
|
|
const llvm_cc = toLlvmCallConvTag(cc, target) orelse return null;
|
|
const incoming_stack_alignment: ?u64, const register_params: u2 = switch (cc) {
|
|
inline else => |pl| switch (@TypeOf(pl)) {
|
|
void => .{ null, 0 },
|
|
std.builtin.CallingConvention.ArcInterruptOptions,
|
|
std.builtin.CallingConvention.ArmInterruptOptions,
|
|
std.builtin.CallingConvention.RiscvInterruptOptions,
|
|
std.builtin.CallingConvention.ShInterruptOptions,
|
|
std.builtin.CallingConvention.MicroblazeInterruptOptions,
|
|
std.builtin.CallingConvention.MipsInterruptOptions,
|
|
std.builtin.CallingConvention.CommonOptions,
|
|
=> .{ pl.incoming_stack_alignment, 0 },
|
|
std.builtin.CallingConvention.X86RegparmOptions => .{ pl.incoming_stack_alignment, pl.register_params },
|
|
else => @compileError("TODO: toLlvmCallConv" ++ @tagName(pl)),
|
|
},
|
|
};
|
|
return .{
|
|
.llvm_cc = llvm_cc,
|
|
.align_stack = if (incoming_stack_alignment) |a| need_align: {
|
|
const normal_stack_align = target.stackAlignment();
|
|
break :need_align a < normal_stack_align;
|
|
} else false,
|
|
.naked = cc == .naked,
|
|
.inreg_param_count = register_params,
|
|
};
|
|
}
|
|
pub fn toLlvmCallConvTag(cc_tag: std.builtin.CallingConvention.Tag, target: *const std.Target) ?Builder.CallConv {
|
|
if (target.cCallingConvention()) |default_c| {
|
|
if (cc_tag == default_c) {
|
|
return .ccc;
|
|
}
|
|
}
|
|
return switch (cc_tag) {
|
|
.@"inline" => unreachable,
|
|
.auto, .async => .fastcc,
|
|
.naked => .ccc,
|
|
.x86_64_sysv => .x86_64_sysvcc,
|
|
.x86_64_win => .win64cc,
|
|
.x86_64_regcall_v3_sysv => if (target.cpu.arch == .x86_64 and target.os.tag != .windows)
|
|
.x86_regcallcc
|
|
else
|
|
null,
|
|
.x86_64_regcall_v4_win => if (target.cpu.arch == .x86_64 and target.os.tag == .windows)
|
|
.x86_regcallcc // we use the "RegCallv4" module flag to make this correct
|
|
else
|
|
null,
|
|
.x86_64_vectorcall => .x86_vectorcallcc,
|
|
.x86_64_interrupt => .x86_intrcc,
|
|
.x86_stdcall => .x86_stdcallcc,
|
|
.x86_fastcall => .x86_fastcallcc,
|
|
.x86_thiscall => .x86_thiscallcc,
|
|
.x86_regcall_v3 => if (target.cpu.arch == .x86 and target.os.tag != .windows)
|
|
.x86_regcallcc
|
|
else
|
|
null,
|
|
.x86_regcall_v4_win => if (target.cpu.arch == .x86 and target.os.tag == .windows)
|
|
.x86_regcallcc // we use the "RegCallv4" module flag to make this correct
|
|
else
|
|
null,
|
|
.x86_vectorcall => .x86_vectorcallcc,
|
|
.x86_interrupt => .x86_intrcc,
|
|
.aarch64_vfabi => .aarch64_vector_pcs,
|
|
.aarch64_vfabi_sve => .aarch64_sve_vector_pcs,
|
|
.arm_aapcs => .arm_aapcscc,
|
|
.arm_aapcs_vfp => .arm_aapcs_vfpcc,
|
|
.riscv64_lp64_v => .riscv_vectorcallcc,
|
|
.riscv32_ilp32_v => .riscv_vectorcallcc,
|
|
.avr_builtin => .avr_builtincc,
|
|
.avr_signal => .avr_signalcc,
|
|
.avr_interrupt => .avr_intrcc,
|
|
.m68k_rtd => .m68k_rtdcc,
|
|
.m68k_interrupt => .m68k_intrcc,
|
|
.msp430_interrupt => .msp430_intrcc,
|
|
.amdgcn_kernel => .amdgpu_kernel,
|
|
.amdgcn_cs => .amdgpu_cs,
|
|
.nvptx_device => .ptx_device,
|
|
.nvptx_kernel => .ptx_kernel,
|
|
|
|
// Calling conventions which LLVM uses function attributes for.
|
|
.riscv64_interrupt,
|
|
.riscv32_interrupt,
|
|
.arm_interrupt,
|
|
.mips64_interrupt,
|
|
.mips_interrupt,
|
|
.csky_interrupt,
|
|
=> .ccc,
|
|
|
|
// All the calling conventions which LLVM does not have a general representation for.
|
|
// Note that these are often still supported through the `cCallingConvention` path above via `ccc`.
|
|
.x86_16_cdecl,
|
|
.x86_16_stdcall,
|
|
.x86_16_regparmcall,
|
|
.x86_16_interrupt,
|
|
.x86_sysv,
|
|
.x86_win,
|
|
.x86_thiscall_mingw,
|
|
.x86_64_x32,
|
|
.aarch64_aapcs,
|
|
.aarch64_aapcs_darwin,
|
|
.aarch64_aapcs_win,
|
|
.alpha_osf,
|
|
.microblaze_std,
|
|
.microblaze_interrupt,
|
|
.mips64_n64,
|
|
.mips64_n32,
|
|
.mips_o32,
|
|
.riscv64_lp64,
|
|
.riscv32_ilp32,
|
|
.sparc64_sysv,
|
|
.sparc_sysv,
|
|
.powerpc64_elf,
|
|
.powerpc64_elf_altivec,
|
|
.powerpc64_elf_v2,
|
|
.powerpc_sysv,
|
|
.powerpc_sysv_altivec,
|
|
.powerpc_aix,
|
|
.powerpc_aix_altivec,
|
|
.wasm_mvp,
|
|
.arc_sysv,
|
|
.arc_interrupt,
|
|
.avr_gnu,
|
|
.bpf_std,
|
|
.csky_sysv,
|
|
.ez80_cet,
|
|
.ez80_tiflags,
|
|
.hexagon_sysv,
|
|
.hexagon_sysv_hvx,
|
|
.hppa_elf,
|
|
.hppa64_elf,
|
|
.kvx_lp64,
|
|
.kvx_ilp32,
|
|
.lanai_sysv,
|
|
.loongarch64_lp64,
|
|
.loongarch32_ilp32,
|
|
.m68k_sysv,
|
|
.m68k_gnu,
|
|
.msp430_eabi,
|
|
.or1k_sysv,
|
|
.propeller_sysv,
|
|
.s390x_sysv,
|
|
.s390x_sysv_vx,
|
|
.sh_gnu,
|
|
.sh_renesas,
|
|
.sh_interrupt,
|
|
.ve_sysv,
|
|
.xcore_xs1,
|
|
.xcore_xs2,
|
|
.xtensa_call0,
|
|
.xtensa_windowed,
|
|
.amdgcn_device,
|
|
.spirv_device,
|
|
.spirv_kernel,
|
|
.spirv_fragment,
|
|
.spirv_vertex,
|
|
=> null,
|
|
};
|
|
}
|
|
|
|
/// Convert a zig-address space to an llvm address space.
|
|
pub fn toLlvmAddressSpace(address_space: std.builtin.AddressSpace, target: *const std.Target) Builder.AddrSpace {
|
|
for (llvmAddrSpaceInfo(target)) |info| if (info.zig == address_space) return info.llvm;
|
|
unreachable;
|
|
}
|
|
|
|
const AddrSpaceInfo = struct {
|
|
zig: ?std.builtin.AddressSpace,
|
|
llvm: Builder.AddrSpace,
|
|
non_integral: bool = false,
|
|
size: ?u16 = null,
|
|
abi: ?u16 = null,
|
|
pref: ?u16 = null,
|
|
idx: ?u16 = null,
|
|
force_in_data_layout: bool = false,
|
|
};
|
|
fn llvmAddrSpaceInfo(target: *const std.Target) []const AddrSpaceInfo {
|
|
return switch (target.cpu.arch) {
|
|
.x86, .x86_64 => &.{
|
|
.{ .zig = .generic, .llvm = .default },
|
|
.{ .zig = .gs, .llvm = Builder.AddrSpace.x86.gs },
|
|
.{ .zig = .fs, .llvm = Builder.AddrSpace.x86.fs },
|
|
.{ .zig = .ss, .llvm = Builder.AddrSpace.x86.ss },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.x86.ptr32_sptr, .size = 32, .abi = 32, .force_in_data_layout = true },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.x86.ptr32_uptr, .size = 32, .abi = 32, .force_in_data_layout = true },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.x86.ptr64, .size = 64, .abi = 64, .force_in_data_layout = true },
|
|
},
|
|
.nvptx, .nvptx64 => &.{
|
|
.{ .zig = .generic, .llvm = Builder.AddrSpace.nvptx.generic },
|
|
.{ .zig = .global, .llvm = Builder.AddrSpace.nvptx.global },
|
|
.{ .zig = .constant, .llvm = Builder.AddrSpace.nvptx.constant },
|
|
.{ .zig = .param, .llvm = Builder.AddrSpace.nvptx.param },
|
|
.{ .zig = .shared, .llvm = Builder.AddrSpace.nvptx.shared },
|
|
.{ .zig = .local, .llvm = Builder.AddrSpace.nvptx.local },
|
|
},
|
|
.amdgcn => &.{
|
|
.{ .zig = .generic, .llvm = Builder.AddrSpace.amdgpu.flat, .force_in_data_layout = true },
|
|
.{ .zig = .global, .llvm = Builder.AddrSpace.amdgpu.global, .force_in_data_layout = true },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.region, .size = 32, .abi = 32 },
|
|
.{ .zig = .shared, .llvm = Builder.AddrSpace.amdgpu.local, .size = 32, .abi = 32 },
|
|
.{ .zig = .constant, .llvm = Builder.AddrSpace.amdgpu.constant, .force_in_data_layout = true },
|
|
.{ .zig = .local, .llvm = Builder.AddrSpace.amdgpu.private, .size = 32, .abi = 32 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_32bit, .size = 32, .abi = 32 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.buffer_fat_pointer, .non_integral = true, .size = 160, .abi = 256, .idx = 32 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.buffer_resource, .non_integral = true, .size = 128, .abi = 128 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.buffer_strided_pointer, .non_integral = true, .size = 192, .abi = 256, .idx = 32 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_0 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_1 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_2 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_3 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_4 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_5 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_6 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_7 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_8 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_9 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_10 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_11 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_12 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_13 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_14 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.constant_buffer_15 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.amdgpu.streamout_register },
|
|
},
|
|
.avr => &.{
|
|
.{ .zig = .generic, .llvm = Builder.AddrSpace.avr.data, .abi = 8 },
|
|
.{ .zig = .flash, .llvm = Builder.AddrSpace.avr.program, .abi = 8 },
|
|
.{ .zig = .flash1, .llvm = Builder.AddrSpace.avr.program1, .abi = 8 },
|
|
.{ .zig = .flash2, .llvm = Builder.AddrSpace.avr.program2, .abi = 8 },
|
|
.{ .zig = .flash3, .llvm = Builder.AddrSpace.avr.program3, .abi = 8 },
|
|
.{ .zig = .flash4, .llvm = Builder.AddrSpace.avr.program4, .abi = 8 },
|
|
.{ .zig = .flash5, .llvm = Builder.AddrSpace.avr.program5, .abi = 8 },
|
|
},
|
|
.wasm32, .wasm64 => &.{
|
|
.{ .zig = .generic, .llvm = Builder.AddrSpace.wasm.default, .force_in_data_layout = true },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.wasm.variable, .non_integral = true },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.wasm.externref, .non_integral = true, .size = 8, .abi = 8 },
|
|
.{ .zig = null, .llvm = Builder.AddrSpace.wasm.funcref, .non_integral = true, .size = 8, .abi = 8 },
|
|
},
|
|
.m68k => &.{
|
|
.{ .zig = .generic, .llvm = .default, .abi = 16, .pref = 32 },
|
|
},
|
|
else => &.{
|
|
.{ .zig = .generic, .llvm = .default },
|
|
},
|
|
};
|
|
}
|
|
|
|
/// On some targets, global values that are in the generic address space must be generated into a
|
|
/// different address space, and then cast back to the generic address space.
|
|
fn llvmDefaultGlobalAddressSpace(target: *const std.Target) Builder.AddrSpace {
|
|
return switch (target.cpu.arch) {
|
|
// On amdgcn, globals must be explicitly allocated and uploaded so that the program can access
|
|
// them.
|
|
.amdgcn => Builder.AddrSpace.amdgpu.global,
|
|
else => .default,
|
|
};
|
|
}
|
|
|
|
/// Return the actual address space that a value should be stored in if its a global address space.
|
|
/// When a value is placed in the resulting address space, it needs to be cast back into wanted_address_space.
|
|
fn toLlvmGlobalAddressSpace(wanted_address_space: std.builtin.AddressSpace, target: *const std.Target) Builder.AddrSpace {
|
|
return switch (wanted_address_space) {
|
|
.generic => llvmDefaultGlobalAddressSpace(target),
|
|
else => |as| toLlvmAddressSpace(as, target),
|
|
};
|
|
}
|
|
|
|
/// This function returns true if we expect LLVM to lower f16 correctly
|
|
/// and false if we expect LLVM to crash if it encounters an f16 type,
|
|
/// or if it produces miscompilations.
|
|
pub fn backendSupportsF16(target: *const std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.arm,
|
|
.armeb,
|
|
.thumb,
|
|
.thumbeb,
|
|
=> target.abi.float() == .soft or target.cpu.has(.arm, .fullfp16),
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
/// This function returns true if we expect LLVM to lower x86_fp80 correctly
|
|
/// and false if we expect LLVM to crash if it encounters an x86_fp80 type,
|
|
/// or if it produces miscompilations.
|
|
pub fn backendSupportsF80(target: *const std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.x86, .x86_64 => !target.cpu.has(.x86, .soft_float),
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// This function returns true if we expect LLVM to lower f128 correctly,
|
|
/// and false if we expect LLVM to crash if it encounters an f128 type,
|
|
/// or if it produces miscompilations.
|
|
pub fn backendSupportsF128(target: *const std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
// https://github.com/llvm/llvm-project/issues/121122
|
|
.amdgcn,
|
|
=> false,
|
|
.arm,
|
|
.armeb,
|
|
.thumb,
|
|
.thumbeb,
|
|
=> target.abi.float() == .soft or target.cpu.has(.arm, .fp_armv8),
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
/// We need to insert extra padding if LLVM's isn't enough.
|
|
/// However we don't want to ever call LLVMABIAlignmentOfType or
|
|
/// LLVMABISizeOfType because these functions will trip assertions
|
|
/// when using them for self-referential types. So our strategy is
|
|
/// to use non-packed llvm structs but to emit all padding explicitly.
|
|
/// We can do this because for all types, Zig ABI alignment >= LLVM ABI
|
|
/// alignment.
|
|
const struct_layout_version = 2;
|
|
|
|
// TODO: Restore the non_null field to i1 once
|
|
// https://github.com/llvm/llvm-project/issues/56585/ is fixed
|
|
pub const optional_layout_version = 3;
|
|
|
|
pub fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
|
|
switch (arch) {
|
|
.aarch64, .aarch64_be => {
|
|
bindings.LLVMInitializeAArch64Target();
|
|
bindings.LLVMInitializeAArch64TargetInfo();
|
|
bindings.LLVMInitializeAArch64TargetMC();
|
|
bindings.LLVMInitializeAArch64AsmPrinter();
|
|
bindings.LLVMInitializeAArch64AsmParser();
|
|
},
|
|
.amdgcn => {
|
|
bindings.LLVMInitializeAMDGPUTarget();
|
|
bindings.LLVMInitializeAMDGPUTargetInfo();
|
|
bindings.LLVMInitializeAMDGPUTargetMC();
|
|
bindings.LLVMInitializeAMDGPUAsmPrinter();
|
|
bindings.LLVMInitializeAMDGPUAsmParser();
|
|
},
|
|
.thumb, .thumbeb, .arm, .armeb => {
|
|
bindings.LLVMInitializeARMTarget();
|
|
bindings.LLVMInitializeARMTargetInfo();
|
|
bindings.LLVMInitializeARMTargetMC();
|
|
bindings.LLVMInitializeARMAsmPrinter();
|
|
bindings.LLVMInitializeARMAsmParser();
|
|
},
|
|
.avr => {
|
|
bindings.LLVMInitializeAVRTarget();
|
|
bindings.LLVMInitializeAVRTargetInfo();
|
|
bindings.LLVMInitializeAVRTargetMC();
|
|
bindings.LLVMInitializeAVRAsmPrinter();
|
|
bindings.LLVMInitializeAVRAsmParser();
|
|
},
|
|
.bpfel, .bpfeb => {
|
|
bindings.LLVMInitializeBPFTarget();
|
|
bindings.LLVMInitializeBPFTargetInfo();
|
|
bindings.LLVMInitializeBPFTargetMC();
|
|
bindings.LLVMInitializeBPFAsmPrinter();
|
|
bindings.LLVMInitializeBPFAsmParser();
|
|
},
|
|
.hexagon => {
|
|
bindings.LLVMInitializeHexagonTarget();
|
|
bindings.LLVMInitializeHexagonTargetInfo();
|
|
bindings.LLVMInitializeHexagonTargetMC();
|
|
bindings.LLVMInitializeHexagonAsmPrinter();
|
|
bindings.LLVMInitializeHexagonAsmParser();
|
|
},
|
|
.lanai => {
|
|
bindings.LLVMInitializeLanaiTarget();
|
|
bindings.LLVMInitializeLanaiTargetInfo();
|
|
bindings.LLVMInitializeLanaiTargetMC();
|
|
bindings.LLVMInitializeLanaiAsmPrinter();
|
|
bindings.LLVMInitializeLanaiAsmParser();
|
|
},
|
|
.mips, .mipsel, .mips64, .mips64el => {
|
|
bindings.LLVMInitializeMipsTarget();
|
|
bindings.LLVMInitializeMipsTargetInfo();
|
|
bindings.LLVMInitializeMipsTargetMC();
|
|
bindings.LLVMInitializeMipsAsmPrinter();
|
|
bindings.LLVMInitializeMipsAsmParser();
|
|
},
|
|
.msp430 => {
|
|
bindings.LLVMInitializeMSP430Target();
|
|
bindings.LLVMInitializeMSP430TargetInfo();
|
|
bindings.LLVMInitializeMSP430TargetMC();
|
|
bindings.LLVMInitializeMSP430AsmPrinter();
|
|
bindings.LLVMInitializeMSP430AsmParser();
|
|
},
|
|
.nvptx, .nvptx64 => {
|
|
bindings.LLVMInitializeNVPTXTarget();
|
|
bindings.LLVMInitializeNVPTXTargetInfo();
|
|
bindings.LLVMInitializeNVPTXTargetMC();
|
|
bindings.LLVMInitializeNVPTXAsmPrinter();
|
|
// There is no LLVMInitializeNVPTXAsmParser function available.
|
|
},
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => {
|
|
bindings.LLVMInitializePowerPCTarget();
|
|
bindings.LLVMInitializePowerPCTargetInfo();
|
|
bindings.LLVMInitializePowerPCTargetMC();
|
|
bindings.LLVMInitializePowerPCAsmPrinter();
|
|
bindings.LLVMInitializePowerPCAsmParser();
|
|
},
|
|
.riscv32, .riscv32be, .riscv64, .riscv64be => {
|
|
bindings.LLVMInitializeRISCVTarget();
|
|
bindings.LLVMInitializeRISCVTargetInfo();
|
|
bindings.LLVMInitializeRISCVTargetMC();
|
|
bindings.LLVMInitializeRISCVAsmPrinter();
|
|
bindings.LLVMInitializeRISCVAsmParser();
|
|
},
|
|
.sparc, .sparc64 => {
|
|
bindings.LLVMInitializeSparcTarget();
|
|
bindings.LLVMInitializeSparcTargetInfo();
|
|
bindings.LLVMInitializeSparcTargetMC();
|
|
bindings.LLVMInitializeSparcAsmPrinter();
|
|
bindings.LLVMInitializeSparcAsmParser();
|
|
},
|
|
.s390x => {
|
|
bindings.LLVMInitializeSystemZTarget();
|
|
bindings.LLVMInitializeSystemZTargetInfo();
|
|
bindings.LLVMInitializeSystemZTargetMC();
|
|
bindings.LLVMInitializeSystemZAsmPrinter();
|
|
bindings.LLVMInitializeSystemZAsmParser();
|
|
},
|
|
.wasm32, .wasm64 => {
|
|
bindings.LLVMInitializeWebAssemblyTarget();
|
|
bindings.LLVMInitializeWebAssemblyTargetInfo();
|
|
bindings.LLVMInitializeWebAssemblyTargetMC();
|
|
bindings.LLVMInitializeWebAssemblyAsmPrinter();
|
|
bindings.LLVMInitializeWebAssemblyAsmParser();
|
|
},
|
|
.x86, .x86_64 => {
|
|
bindings.LLVMInitializeX86Target();
|
|
bindings.LLVMInitializeX86TargetInfo();
|
|
bindings.LLVMInitializeX86TargetMC();
|
|
bindings.LLVMInitializeX86AsmPrinter();
|
|
bindings.LLVMInitializeX86AsmParser();
|
|
},
|
|
.xtensa => {
|
|
if (build_options.llvm_has_xtensa) {
|
|
bindings.LLVMInitializeXtensaTarget();
|
|
bindings.LLVMInitializeXtensaTargetInfo();
|
|
bindings.LLVMInitializeXtensaTargetMC();
|
|
// There is no LLVMInitializeXtensaAsmPrinter function.
|
|
bindings.LLVMInitializeXtensaAsmParser();
|
|
}
|
|
},
|
|
.xcore => {
|
|
bindings.LLVMInitializeXCoreTarget();
|
|
bindings.LLVMInitializeXCoreTargetInfo();
|
|
bindings.LLVMInitializeXCoreTargetMC();
|
|
bindings.LLVMInitializeXCoreAsmPrinter();
|
|
// There is no LLVMInitializeXCoreAsmParser function.
|
|
},
|
|
.m68k => {
|
|
if (build_options.llvm_has_m68k) {
|
|
bindings.LLVMInitializeM68kTarget();
|
|
bindings.LLVMInitializeM68kTargetInfo();
|
|
bindings.LLVMInitializeM68kTargetMC();
|
|
bindings.LLVMInitializeM68kAsmPrinter();
|
|
bindings.LLVMInitializeM68kAsmParser();
|
|
}
|
|
},
|
|
.csky => {
|
|
if (build_options.llvm_has_csky) {
|
|
bindings.LLVMInitializeCSKYTarget();
|
|
bindings.LLVMInitializeCSKYTargetInfo();
|
|
bindings.LLVMInitializeCSKYTargetMC();
|
|
// There is no LLVMInitializeCSKYAsmPrinter function.
|
|
bindings.LLVMInitializeCSKYAsmParser();
|
|
}
|
|
},
|
|
.ve => {
|
|
bindings.LLVMInitializeVETarget();
|
|
bindings.LLVMInitializeVETargetInfo();
|
|
bindings.LLVMInitializeVETargetMC();
|
|
bindings.LLVMInitializeVEAsmPrinter();
|
|
bindings.LLVMInitializeVEAsmParser();
|
|
},
|
|
.arc => {
|
|
if (build_options.llvm_has_arc) {
|
|
bindings.LLVMInitializeARCTarget();
|
|
bindings.LLVMInitializeARCTargetInfo();
|
|
bindings.LLVMInitializeARCTargetMC();
|
|
bindings.LLVMInitializeARCAsmPrinter();
|
|
// There is no LLVMInitializeARCAsmParser function.
|
|
}
|
|
},
|
|
.loongarch32, .loongarch64 => {
|
|
bindings.LLVMInitializeLoongArchTarget();
|
|
bindings.LLVMInitializeLoongArchTargetInfo();
|
|
bindings.LLVMInitializeLoongArchTargetMC();
|
|
bindings.LLVMInitializeLoongArchAsmPrinter();
|
|
bindings.LLVMInitializeLoongArchAsmParser();
|
|
},
|
|
.spirv32,
|
|
.spirv64,
|
|
=> {
|
|
bindings.LLVMInitializeSPIRVTarget();
|
|
bindings.LLVMInitializeSPIRVTargetInfo();
|
|
bindings.LLVMInitializeSPIRVTargetMC();
|
|
bindings.LLVMInitializeSPIRVAsmPrinter();
|
|
},
|
|
|
|
// LLVM does does not have a backend for these.
|
|
.alpha,
|
|
.arceb,
|
|
.ez80,
|
|
.hppa,
|
|
.hppa64,
|
|
.kalimba,
|
|
.kvx,
|
|
.microblaze,
|
|
.microblazeel,
|
|
.or1k,
|
|
.propeller,
|
|
.sh,
|
|
.sheb,
|
|
.x86_16,
|
|
.xtensaeb,
|
|
=> unreachable,
|
|
}
|
|
}
|