mirror of
https://codeberg.org/ziglang/zig.git
synced 2025-12-06 13:54:21 +00:00
* move inferred error sets into InternPool.
- they are now represented by pointing directly at the corresponding
function body value.
* inferred error set working memory is now in Sema and expires after
the Sema for the function corresponding to the inferred error set is
finished having its body analyzed.
* error sets use a InternPool.Index.Slice rather than an actual slice
to avoid lifetime issues.
11482 lines
501 KiB
Zig
11482 lines
501 KiB
Zig
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const assert = std.debug.assert;
|
|
const Allocator = std.mem.Allocator;
|
|
const log = std.log.scoped(.codegen);
|
|
const math = std.math;
|
|
const native_endian = builtin.cpu.arch.endian();
|
|
const DW = std.dwarf;
|
|
|
|
const llvm = @import("llvm/bindings.zig");
|
|
const link = @import("../link.zig");
|
|
const Compilation = @import("../Compilation.zig");
|
|
const build_options = @import("build_options");
|
|
const Module = @import("../Module.zig");
|
|
const InternPool = @import("../InternPool.zig");
|
|
const Package = @import("../Package.zig");
|
|
const TypedValue = @import("../TypedValue.zig");
|
|
const Air = @import("../Air.zig");
|
|
const Liveness = @import("../Liveness.zig");
|
|
const Value = @import("../value.zig").Value;
|
|
const Type = @import("../type.zig").Type;
|
|
const LazySrcLoc = Module.LazySrcLoc;
|
|
const x86_64_abi = @import("../arch/x86_64/abi.zig");
|
|
const wasm_c_abi = @import("../arch/wasm/abi.zig");
|
|
const aarch64_c_abi = @import("../arch/aarch64/abi.zig");
|
|
const arm_c_abi = @import("../arch/arm/abi.zig");
|
|
const riscv_c_abi = @import("../arch/riscv64/abi.zig");
|
|
|
|
const target_util = @import("../target.zig");
|
|
const libcFloatPrefix = target_util.libcFloatPrefix;
|
|
const libcFloatSuffix = target_util.libcFloatSuffix;
|
|
const compilerRtFloatAbbrev = target_util.compilerRtFloatAbbrev;
|
|
const compilerRtIntAbbrev = target_util.compilerRtIntAbbrev;
|
|
|
|
const Error = error{ OutOfMemory, CodegenFail };
|
|
|
|
pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 {
|
|
var llvm_triple = std.ArrayList(u8).init(allocator);
|
|
defer llvm_triple.deinit();
|
|
|
|
const llvm_arch = switch (target.cpu.arch) {
|
|
.arm => "arm",
|
|
.armeb => "armeb",
|
|
.aarch64 => "aarch64",
|
|
.aarch64_be => "aarch64_be",
|
|
.aarch64_32 => "aarch64_32",
|
|
.arc => "arc",
|
|
.avr => "avr",
|
|
.bpfel => "bpfel",
|
|
.bpfeb => "bpfeb",
|
|
.csky => "csky",
|
|
.dxil => "dxil",
|
|
.hexagon => "hexagon",
|
|
.loongarch32 => "loongarch32",
|
|
.loongarch64 => "loongarch64",
|
|
.m68k => "m68k",
|
|
.mips => "mips",
|
|
.mipsel => "mipsel",
|
|
.mips64 => "mips64",
|
|
.mips64el => "mips64el",
|
|
.msp430 => "msp430",
|
|
.powerpc => "powerpc",
|
|
.powerpcle => "powerpcle",
|
|
.powerpc64 => "powerpc64",
|
|
.powerpc64le => "powerpc64le",
|
|
.r600 => "r600",
|
|
.amdgcn => "amdgcn",
|
|
.riscv32 => "riscv32",
|
|
.riscv64 => "riscv64",
|
|
.sparc => "sparc",
|
|
.sparc64 => "sparc64",
|
|
.sparcel => "sparcel",
|
|
.s390x => "s390x",
|
|
.tce => "tce",
|
|
.tcele => "tcele",
|
|
.thumb => "thumb",
|
|
.thumbeb => "thumbeb",
|
|
.x86 => "i386",
|
|
.x86_64 => "x86_64",
|
|
.xcore => "xcore",
|
|
.xtensa => "xtensa",
|
|
.nvptx => "nvptx",
|
|
.nvptx64 => "nvptx64",
|
|
.le32 => "le32",
|
|
.le64 => "le64",
|
|
.amdil => "amdil",
|
|
.amdil64 => "amdil64",
|
|
.hsail => "hsail",
|
|
.hsail64 => "hsail64",
|
|
.spir => "spir",
|
|
.spir64 => "spir64",
|
|
.spirv32 => "spirv32",
|
|
.spirv64 => "spirv64",
|
|
.kalimba => "kalimba",
|
|
.shave => "shave",
|
|
.lanai => "lanai",
|
|
.wasm32 => "wasm32",
|
|
.wasm64 => "wasm64",
|
|
.renderscript32 => "renderscript32",
|
|
.renderscript64 => "renderscript64",
|
|
.ve => "ve",
|
|
.spu_2 => return error.@"LLVM backend does not support SPU Mark II",
|
|
};
|
|
try llvm_triple.appendSlice(llvm_arch);
|
|
try llvm_triple.appendSlice("-unknown-");
|
|
|
|
const llvm_os = switch (target.os.tag) {
|
|
.freestanding => "unknown",
|
|
.ananas => "ananas",
|
|
.cloudabi => "cloudabi",
|
|
.dragonfly => "dragonfly",
|
|
.freebsd => "freebsd",
|
|
.fuchsia => "fuchsia",
|
|
.kfreebsd => "kfreebsd",
|
|
.linux => "linux",
|
|
.lv2 => "lv2",
|
|
.netbsd => "netbsd",
|
|
.openbsd => "openbsd",
|
|
.solaris => "solaris",
|
|
.windows => "windows",
|
|
.zos => "zos",
|
|
.haiku => "haiku",
|
|
.minix => "minix",
|
|
.rtems => "rtems",
|
|
.nacl => "nacl",
|
|
.aix => "aix",
|
|
.cuda => "cuda",
|
|
.nvcl => "nvcl",
|
|
.amdhsa => "amdhsa",
|
|
.ps4 => "ps4",
|
|
.ps5 => "ps5",
|
|
.elfiamcu => "elfiamcu",
|
|
.mesa3d => "mesa3d",
|
|
.contiki => "contiki",
|
|
.amdpal => "amdpal",
|
|
.hermit => "hermit",
|
|
.hurd => "hurd",
|
|
.wasi => "wasi",
|
|
.emscripten => "emscripten",
|
|
.uefi => "windows",
|
|
.macos => "macosx",
|
|
.ios => "ios",
|
|
.tvos => "tvos",
|
|
.watchos => "watchos",
|
|
.driverkit => "driverkit",
|
|
.shadermodel => "shadermodel",
|
|
.opencl,
|
|
.glsl450,
|
|
.vulkan,
|
|
.plan9,
|
|
.other,
|
|
=> "unknown",
|
|
};
|
|
try llvm_triple.appendSlice(llvm_os);
|
|
|
|
if (target.os.tag.isDarwin()) {
|
|
const min_version = target.os.version_range.semver.min;
|
|
try llvm_triple.writer().print("{d}.{d}.{d}", .{
|
|
min_version.major,
|
|
min_version.minor,
|
|
min_version.patch,
|
|
});
|
|
}
|
|
try llvm_triple.append('-');
|
|
|
|
const llvm_abi = switch (target.abi) {
|
|
.none => "unknown",
|
|
.gnu => "gnu",
|
|
.gnuabin32 => "gnuabin32",
|
|
.gnuabi64 => "gnuabi64",
|
|
.gnueabi => "gnueabi",
|
|
.gnueabihf => "gnueabihf",
|
|
.gnuf32 => "gnuf32",
|
|
.gnuf64 => "gnuf64",
|
|
.gnusf => "gnusf",
|
|
.gnux32 => "gnux32",
|
|
.gnuilp32 => "gnuilp32",
|
|
.code16 => "code16",
|
|
.eabi => "eabi",
|
|
.eabihf => "eabihf",
|
|
.android => "android",
|
|
.musl => "musl",
|
|
.musleabi => "musleabi",
|
|
.musleabihf => "musleabihf",
|
|
.muslx32 => "muslx32",
|
|
.msvc => "msvc",
|
|
.itanium => "itanium",
|
|
.cygnus => "cygnus",
|
|
.coreclr => "coreclr",
|
|
.simulator => "simulator",
|
|
.macabi => "macabi",
|
|
.pixel => "pixel",
|
|
.vertex => "vertex",
|
|
.geometry => "geometry",
|
|
.hull => "hull",
|
|
.domain => "domain",
|
|
.compute => "compute",
|
|
.library => "library",
|
|
.raygeneration => "raygeneration",
|
|
.intersection => "intersection",
|
|
.anyhit => "anyhit",
|
|
.closesthit => "closesthit",
|
|
.miss => "miss",
|
|
.callable => "callable",
|
|
.mesh => "mesh",
|
|
.amplification => "amplification",
|
|
};
|
|
try llvm_triple.appendSlice(llvm_abi);
|
|
|
|
return llvm_triple.toOwnedSliceSentinel(0);
|
|
}
|
|
|
|
pub fn targetOs(os_tag: std.Target.Os.Tag) llvm.OSType {
|
|
return switch (os_tag) {
|
|
.freestanding, .other, .opencl, .glsl450, .vulkan, .plan9 => .UnknownOS,
|
|
.windows, .uefi => .Win32,
|
|
.ananas => .Ananas,
|
|
.cloudabi => .CloudABI,
|
|
.dragonfly => .DragonFly,
|
|
.freebsd => .FreeBSD,
|
|
.fuchsia => .Fuchsia,
|
|
.ios => .IOS,
|
|
.kfreebsd => .KFreeBSD,
|
|
.linux => .Linux,
|
|
.lv2 => .Lv2,
|
|
.macos => .MacOSX,
|
|
.netbsd => .NetBSD,
|
|
.openbsd => .OpenBSD,
|
|
.solaris => .Solaris,
|
|
.zos => .ZOS,
|
|
.haiku => .Haiku,
|
|
.minix => .Minix,
|
|
.rtems => .RTEMS,
|
|
.nacl => .NaCl,
|
|
.aix => .AIX,
|
|
.cuda => .CUDA,
|
|
.nvcl => .NVCL,
|
|
.amdhsa => .AMDHSA,
|
|
.ps4 => .PS4,
|
|
.ps5 => .PS5,
|
|
.elfiamcu => .ELFIAMCU,
|
|
.tvos => .TvOS,
|
|
.watchos => .WatchOS,
|
|
.mesa3d => .Mesa3D,
|
|
.contiki => .Contiki,
|
|
.amdpal => .AMDPAL,
|
|
.hermit => .HermitCore,
|
|
.hurd => .Hurd,
|
|
.wasi => .WASI,
|
|
.emscripten => .Emscripten,
|
|
.driverkit => .DriverKit,
|
|
.shadermodel => .ShaderModel,
|
|
};
|
|
}
|
|
|
|
pub fn targetArch(arch_tag: std.Target.Cpu.Arch) llvm.ArchType {
|
|
return switch (arch_tag) {
|
|
.arm => .arm,
|
|
.armeb => .armeb,
|
|
.aarch64 => .aarch64,
|
|
.aarch64_be => .aarch64_be,
|
|
.aarch64_32 => .aarch64_32,
|
|
.arc => .arc,
|
|
.avr => .avr,
|
|
.bpfel => .bpfel,
|
|
.bpfeb => .bpfeb,
|
|
.csky => .csky,
|
|
.dxil => .dxil,
|
|
.hexagon => .hexagon,
|
|
.loongarch32 => .loongarch32,
|
|
.loongarch64 => .loongarch64,
|
|
.m68k => .m68k,
|
|
.mips => .mips,
|
|
.mipsel => .mipsel,
|
|
.mips64 => .mips64,
|
|
.mips64el => .mips64el,
|
|
.msp430 => .msp430,
|
|
.powerpc => .ppc,
|
|
.powerpcle => .ppcle,
|
|
.powerpc64 => .ppc64,
|
|
.powerpc64le => .ppc64le,
|
|
.r600 => .r600,
|
|
.amdgcn => .amdgcn,
|
|
.riscv32 => .riscv32,
|
|
.riscv64 => .riscv64,
|
|
.sparc => .sparc,
|
|
.sparc64 => .sparcv9, // In LLVM, sparc64 == sparcv9.
|
|
.sparcel => .sparcel,
|
|
.s390x => .systemz,
|
|
.tce => .tce,
|
|
.tcele => .tcele,
|
|
.thumb => .thumb,
|
|
.thumbeb => .thumbeb,
|
|
.x86 => .x86,
|
|
.x86_64 => .x86_64,
|
|
.xcore => .xcore,
|
|
.xtensa => .xtensa,
|
|
.nvptx => .nvptx,
|
|
.nvptx64 => .nvptx64,
|
|
.le32 => .le32,
|
|
.le64 => .le64,
|
|
.amdil => .amdil,
|
|
.amdil64 => .amdil64,
|
|
.hsail => .hsail,
|
|
.hsail64 => .hsail64,
|
|
.spir => .spir,
|
|
.spir64 => .spir64,
|
|
.kalimba => .kalimba,
|
|
.shave => .shave,
|
|
.lanai => .lanai,
|
|
.wasm32 => .wasm32,
|
|
.wasm64 => .wasm64,
|
|
.renderscript32 => .renderscript32,
|
|
.renderscript64 => .renderscript64,
|
|
.ve => .ve,
|
|
.spu_2, .spirv32, .spirv64 => .UnknownArch,
|
|
};
|
|
}
|
|
|
|
pub fn supportsTailCall(target: std.Target) bool {
|
|
switch (target.cpu.arch) {
|
|
.wasm32, .wasm64 => return std.Target.wasm.featureSetHas(target.cpu.features, .tail_call),
|
|
// Although these ISAs support tail calls, LLVM does not support tail calls on them.
|
|
.mips, .mipsel, .mips64, .mips64el => return false,
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => return false,
|
|
else => return true,
|
|
}
|
|
}
|
|
|
|
/// TODO can this be done with simpler logic / different API binding?
|
|
fn deleteLlvmGlobal(llvm_global: *llvm.Value) void {
|
|
if (llvm_global.globalGetValueType().getTypeKind() == .Function) {
|
|
llvm_global.deleteFunction();
|
|
return;
|
|
}
|
|
return llvm_global.deleteGlobal();
|
|
}
|
|
|
|
pub const Object = struct {
|
|
gpa: Allocator,
|
|
module: *Module,
|
|
llvm_module: *llvm.Module,
|
|
di_builder: ?*llvm.DIBuilder,
|
|
/// One of these mappings:
|
|
/// - *Module.File => *DIFile
|
|
/// - *Module.Decl (Fn) => *DISubprogram
|
|
/// - *Module.Decl (Non-Fn) => *DIGlobalVariable
|
|
di_map: std.AutoHashMapUnmanaged(*const anyopaque, *llvm.DINode),
|
|
di_compile_unit: ?*llvm.DICompileUnit,
|
|
context: *llvm.Context,
|
|
target_machine: *llvm.TargetMachine,
|
|
target_data: *llvm.TargetData,
|
|
target: std.Target,
|
|
/// Ideally we would use `llvm_module.getNamedFunction` to go from *Decl to LLVM function,
|
|
/// but that has some downsides:
|
|
/// * we have to compute the fully qualified name every time we want to do the lookup
|
|
/// * for externally linked functions, the name is not fully qualified, but when
|
|
/// a Decl goes from exported to not exported and vice-versa, we would use the wrong
|
|
/// version of the name and incorrectly get function not found in the llvm module.
|
|
/// * it works for functions not all globals.
|
|
/// Therefore, this table keeps track of the mapping.
|
|
decl_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *llvm.Value),
|
|
/// Serves the same purpose as `decl_map` but only used for the `is_named_enum_value` instruction.
|
|
named_enum_map: std.AutoHashMapUnmanaged(Module.Decl.Index, *llvm.Value),
|
|
/// 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,
|
|
di_type_map: DITypeMap,
|
|
/// The LLVM global table which holds the names corresponding to Zig errors.
|
|
/// Note that the values are not added until flushModule, when all errors in
|
|
/// the compilation are known.
|
|
error_name_table: ?*llvm.Value,
|
|
/// This map is usually very close to empty. It tracks only the cases when a
|
|
/// second extern Decl could not be emitted with the correct name due to a
|
|
/// name collision.
|
|
extern_collisions: std.AutoArrayHashMapUnmanaged(Module.Decl.Index, void),
|
|
|
|
/// Memoizes a null `?usize` value.
|
|
null_opt_addr: ?*llvm.Value,
|
|
|
|
pub const TypeMap = std.AutoHashMapUnmanaged(InternPool.Index, *llvm.Type);
|
|
|
|
/// This is an ArrayHashMap as opposed to a HashMap because in `flushModule` we
|
|
/// want to iterate over it while adding entries to it.
|
|
pub const DITypeMap = std.AutoArrayHashMapUnmanaged(InternPool.Index, AnnotatedDITypePtr);
|
|
|
|
pub fn create(gpa: Allocator, options: link.Options) !*Object {
|
|
const obj = try gpa.create(Object);
|
|
errdefer gpa.destroy(obj);
|
|
obj.* = try Object.init(gpa, options);
|
|
return obj;
|
|
}
|
|
|
|
pub fn init(gpa: Allocator, options: link.Options) !Object {
|
|
const context = llvm.Context.create();
|
|
errdefer context.dispose();
|
|
|
|
initializeLLVMTarget(options.target.cpu.arch);
|
|
|
|
const llvm_module = llvm.Module.createWithName(options.root_name.ptr, context);
|
|
errdefer llvm_module.dispose();
|
|
|
|
const llvm_target_triple = try targetTriple(gpa, options.target);
|
|
defer gpa.free(llvm_target_triple);
|
|
|
|
var error_message: [*:0]const u8 = undefined;
|
|
var target: *llvm.Target = undefined;
|
|
if (llvm.Target.getFromTriple(llvm_target_triple.ptr, &target, &error_message).toBool()) {
|
|
defer llvm.disposeMessage(error_message);
|
|
|
|
log.err("LLVM failed to parse '{s}': {s}", .{ llvm_target_triple, error_message });
|
|
return error.InvalidLlvmTriple;
|
|
}
|
|
|
|
llvm_module.setTarget(llvm_target_triple.ptr);
|
|
var opt_di_builder: ?*llvm.DIBuilder = null;
|
|
errdefer if (opt_di_builder) |di_builder| di_builder.dispose();
|
|
|
|
var di_compile_unit: ?*llvm.DICompileUnit = null;
|
|
|
|
if (!options.strip) {
|
|
switch (options.target.ofmt) {
|
|
.coff => llvm_module.addModuleCodeViewFlag(),
|
|
else => llvm_module.addModuleDebugInfoFlag(options.dwarf_format == std.dwarf.Format.@"64"),
|
|
}
|
|
const di_builder = llvm_module.createDIBuilder(true);
|
|
opt_di_builder = di_builder;
|
|
|
|
// Don't use the version string here; LLVM misparses it when it
|
|
// includes the git revision.
|
|
const producer = try std.fmt.allocPrintZ(gpa, "zig {d}.{d}.{d}", .{
|
|
build_options.semver.major,
|
|
build_options.semver.minor,
|
|
build_options.semver.patch,
|
|
});
|
|
defer gpa.free(producer);
|
|
|
|
// 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?
|
|
var buf: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const compile_unit_dir = blk: {
|
|
const path = d: {
|
|
const mod = options.module orelse break :d ".";
|
|
break :d mod.root_pkg.root_src_directory.path orelse ".";
|
|
};
|
|
if (std.fs.path.isAbsolute(path)) break :blk path;
|
|
break :blk std.os.realpath(path, &buf) catch path; // If realpath fails, fallback to whatever path was
|
|
};
|
|
const compile_unit_dir_z = try gpa.dupeZ(u8, compile_unit_dir);
|
|
defer gpa.free(compile_unit_dir_z);
|
|
|
|
di_compile_unit = di_builder.createCompileUnit(
|
|
DW.LANG.C99,
|
|
di_builder.createFile(options.root_name, compile_unit_dir_z),
|
|
producer,
|
|
options.optimize_mode != .Debug,
|
|
"", // flags
|
|
0, // runtime version
|
|
"", // split name
|
|
0, // dwo id
|
|
true, // emit debug info
|
|
);
|
|
}
|
|
|
|
const opt_level: llvm.CodeGenOptLevel = if (options.optimize_mode == .Debug)
|
|
.None
|
|
else
|
|
.Aggressive;
|
|
|
|
const reloc_mode: llvm.RelocMode = if (options.pic)
|
|
.PIC
|
|
else if (options.link_mode == .Dynamic)
|
|
llvm.RelocMode.DynamicNoPIC
|
|
else
|
|
.Static;
|
|
|
|
const code_model: llvm.CodeModel = switch (options.machine_code_model) {
|
|
.default => .Default,
|
|
.tiny => .Tiny,
|
|
.small => .Small,
|
|
.kernel => .Kernel,
|
|
.medium => .Medium,
|
|
.large => .Large,
|
|
};
|
|
|
|
// TODO handle float ABI better- it should depend on the ABI portion of std.Target
|
|
const float_abi: llvm.ABIType = .Default;
|
|
|
|
const target_machine = llvm.TargetMachine.create(
|
|
target,
|
|
llvm_target_triple.ptr,
|
|
if (options.target.cpu.model.llvm_name) |s| s.ptr else null,
|
|
options.llvm_cpu_features,
|
|
opt_level,
|
|
reloc_mode,
|
|
code_model,
|
|
options.function_sections,
|
|
float_abi,
|
|
if (target_util.llvmMachineAbi(options.target)) |s| s.ptr else null,
|
|
);
|
|
errdefer target_machine.dispose();
|
|
|
|
const target_data = target_machine.createTargetDataLayout();
|
|
errdefer target_data.dispose();
|
|
|
|
llvm_module.setModuleDataLayout(target_data);
|
|
|
|
if (options.pic) llvm_module.setModulePICLevel();
|
|
if (options.pie) llvm_module.setModulePIELevel();
|
|
if (code_model != .Default) llvm_module.setModuleCodeModel(code_model);
|
|
|
|
if (options.opt_bisect_limit >= 0) {
|
|
context.setOptBisectLimit(std.math.lossyCast(c_int, options.opt_bisect_limit));
|
|
}
|
|
|
|
return Object{
|
|
.gpa = gpa,
|
|
.module = options.module.?,
|
|
.llvm_module = llvm_module,
|
|
.di_map = .{},
|
|
.di_builder = opt_di_builder,
|
|
.di_compile_unit = di_compile_unit,
|
|
.context = context,
|
|
.target_machine = target_machine,
|
|
.target_data = target_data,
|
|
.target = options.target,
|
|
.decl_map = .{},
|
|
.named_enum_map = .{},
|
|
.type_map = .{},
|
|
.di_type_map = .{},
|
|
.error_name_table = null,
|
|
.extern_collisions = .{},
|
|
.null_opt_addr = null,
|
|
};
|
|
}
|
|
|
|
pub fn deinit(self: *Object, gpa: Allocator) void {
|
|
if (self.di_builder) |dib| {
|
|
dib.dispose();
|
|
self.di_map.deinit(gpa);
|
|
self.di_type_map.deinit(gpa);
|
|
}
|
|
self.target_data.dispose();
|
|
self.target_machine.dispose();
|
|
self.llvm_module.dispose();
|
|
self.context.dispose();
|
|
self.decl_map.deinit(gpa);
|
|
self.named_enum_map.deinit(gpa);
|
|
self.type_map.deinit(gpa);
|
|
self.extern_collisions.deinit(gpa);
|
|
self.* = undefined;
|
|
}
|
|
|
|
pub fn destroy(self: *Object, gpa: Allocator) void {
|
|
self.deinit(gpa);
|
|
gpa.destroy(self);
|
|
}
|
|
|
|
fn locPath(
|
|
arena: Allocator,
|
|
opt_loc: ?Compilation.EmitLoc,
|
|
cache_directory: Compilation.Directory,
|
|
) !?[*:0]u8 {
|
|
const loc = opt_loc orelse return null;
|
|
const directory = loc.directory orelse cache_directory;
|
|
const slice = try directory.joinZ(arena, &[_][]const u8{loc.basename});
|
|
return slice.ptr;
|
|
}
|
|
|
|
fn genErrorNameTable(o: *Object) !void {
|
|
// If o.error_name_table is null, there was no instruction that actually referenced the error table.
|
|
const error_name_table_ptr_global = o.error_name_table orelse return;
|
|
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
|
|
const llvm_ptr_ty = o.context.pointerType(0); // TODO: Address space
|
|
const llvm_usize_ty = o.context.intType(target.ptrBitWidth());
|
|
const type_fields = [_]*llvm.Type{
|
|
llvm_ptr_ty,
|
|
llvm_usize_ty,
|
|
};
|
|
const llvm_slice_ty = o.context.structType(&type_fields, type_fields.len, .False);
|
|
const slice_ty = Type.slice_const_u8_sentinel_0;
|
|
const slice_alignment = slice_ty.abiAlignment(mod);
|
|
|
|
const error_name_list = mod.global_error_set.keys();
|
|
const llvm_errors = try mod.gpa.alloc(*llvm.Value, error_name_list.len);
|
|
defer mod.gpa.free(llvm_errors);
|
|
|
|
llvm_errors[0] = llvm_slice_ty.getUndef();
|
|
for (llvm_errors[1..], error_name_list[1..]) |*llvm_error, name_nts| {
|
|
const name = mod.intern_pool.stringToSlice(name_nts);
|
|
const str_init = o.context.constString(name.ptr, @as(c_uint, @intCast(name.len)), .False);
|
|
const str_global = o.llvm_module.addGlobal(str_init.typeOf(), "");
|
|
str_global.setInitializer(str_init);
|
|
str_global.setLinkage(.Private);
|
|
str_global.setGlobalConstant(.True);
|
|
str_global.setUnnamedAddr(.True);
|
|
str_global.setAlignment(1);
|
|
|
|
const slice_fields = [_]*llvm.Value{
|
|
str_global,
|
|
llvm_usize_ty.constInt(name.len, .False),
|
|
};
|
|
llvm_error.* = llvm_slice_ty.constNamedStruct(&slice_fields, slice_fields.len);
|
|
}
|
|
|
|
const error_name_table_init = llvm_slice_ty.constArray(llvm_errors.ptr, @as(c_uint, @intCast(error_name_list.len)));
|
|
|
|
const error_name_table_global = o.llvm_module.addGlobal(error_name_table_init.typeOf(), "");
|
|
error_name_table_global.setInitializer(error_name_table_init);
|
|
error_name_table_global.setLinkage(.Private);
|
|
error_name_table_global.setGlobalConstant(.True);
|
|
error_name_table_global.setUnnamedAddr(.True);
|
|
error_name_table_global.setAlignment(slice_alignment); // TODO: Dont hardcode
|
|
|
|
const error_name_table_ptr = error_name_table_global;
|
|
error_name_table_ptr_global.setInitializer(error_name_table_ptr);
|
|
}
|
|
|
|
fn genCmpLtErrorsLenFunction(object: *Object) !void {
|
|
// If there is no such function in the module, it means the source code does not need it.
|
|
const llvm_fn = object.llvm_module.getNamedFunction(lt_errors_fn_name) orelse return;
|
|
const mod = object.module;
|
|
const errors_len = mod.global_error_set.count();
|
|
|
|
// Delete previous implementation. We replace it with every flush() because the
|
|
// total number of errors may have changed.
|
|
while (llvm_fn.getFirstBasicBlock()) |bb| {
|
|
bb.deleteBasicBlock();
|
|
}
|
|
|
|
const builder = object.context.createBuilder();
|
|
|
|
const entry_block = object.context.appendBasicBlock(llvm_fn, "Entry");
|
|
builder.positionBuilderAtEnd(entry_block);
|
|
builder.clearCurrentDebugLocation();
|
|
|
|
// Example source of the following LLVM IR:
|
|
// fn __zig_lt_errors_len(index: u16) bool {
|
|
// return index < total_errors_len;
|
|
// }
|
|
|
|
const lhs = llvm_fn.getParam(0);
|
|
const rhs = lhs.typeOf().constInt(errors_len, .False);
|
|
const is_lt = builder.buildICmp(.ULT, lhs, rhs, "");
|
|
_ = builder.buildRet(is_lt);
|
|
}
|
|
|
|
fn genModuleLevelAssembly(object: *Object) !void {
|
|
const mod = object.module;
|
|
if (mod.global_assembly.count() == 0) return;
|
|
var buffer = std.ArrayList(u8).init(mod.gpa);
|
|
defer buffer.deinit();
|
|
var it = mod.global_assembly.iterator();
|
|
while (it.next()) |kv| {
|
|
try buffer.appendSlice(kv.value_ptr.*);
|
|
try buffer.append('\n');
|
|
}
|
|
object.llvm_module.setModuleInlineAsm2(buffer.items.ptr, buffer.items.len - 1);
|
|
}
|
|
|
|
fn resolveExportExternCollisions(object: *Object) !void {
|
|
const mod = object.module;
|
|
|
|
// This map has externs with incorrect symbol names.
|
|
for (object.extern_collisions.keys()) |decl_index| {
|
|
const entry = object.decl_map.getEntry(decl_index) orelse continue;
|
|
const llvm_global = entry.value_ptr.*;
|
|
// Same logic as below but for externs instead of exports.
|
|
const decl = mod.declPtr(decl_index);
|
|
const other_global = object.getLlvmGlobal(mod.intern_pool.stringToSlice(decl.name)) orelse continue;
|
|
if (other_global == llvm_global) continue;
|
|
|
|
llvm_global.replaceAllUsesWith(other_global);
|
|
deleteLlvmGlobal(llvm_global);
|
|
entry.value_ptr.* = other_global;
|
|
}
|
|
object.extern_collisions.clearRetainingCapacity();
|
|
|
|
const export_keys = mod.decl_exports.keys();
|
|
for (mod.decl_exports.values(), 0..) |export_list, i| {
|
|
const decl_index = export_keys[i];
|
|
const llvm_global = object.decl_map.get(decl_index) orelse continue;
|
|
for (export_list.items) |exp| {
|
|
// Detect if the LLVM global has already been created as an extern. In such
|
|
// case, we need to replace all uses of it with this exported global.
|
|
const exp_name = mod.intern_pool.stringToSlice(exp.opts.name);
|
|
|
|
const other_global = object.getLlvmGlobal(exp_name.ptr) orelse continue;
|
|
if (other_global == llvm_global) continue;
|
|
|
|
other_global.replaceAllUsesWith(llvm_global);
|
|
llvm_global.takeName(other_global);
|
|
deleteLlvmGlobal(other_global);
|
|
// Problem: now we need to replace in the decl_map that
|
|
// the extern decl index points to this new global. However we don't
|
|
// know the decl index.
|
|
// Even if we did, a future incremental update to the extern would then
|
|
// treat the LLVM global as an extern rather than an export, so it would
|
|
// need a way to check that.
|
|
// This is a TODO that needs to be solved when making
|
|
// the LLVM backend support incremental compilation.
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn flushModule(self: *Object, comp: *Compilation, prog_node: *std.Progress.Node) !void {
|
|
var sub_prog_node = prog_node.start("LLVM Emit Object", 0);
|
|
sub_prog_node.activate();
|
|
sub_prog_node.context.refresh();
|
|
defer sub_prog_node.end();
|
|
|
|
try self.resolveExportExternCollisions();
|
|
try self.genErrorNameTable();
|
|
try self.genCmpLtErrorsLenFunction();
|
|
try self.genModuleLevelAssembly();
|
|
|
|
if (self.di_builder) |dib| {
|
|
// When lowering debug info for pointers, we emitted the element types as
|
|
// forward decls. Now we must go flesh those out.
|
|
// Here we iterate over a hash map while modifying it but it is OK because
|
|
// we never add or remove entries during this loop.
|
|
var i: usize = 0;
|
|
while (i < self.di_type_map.count()) : (i += 1) {
|
|
const value_ptr = &self.di_type_map.values()[i];
|
|
const annotated = value_ptr.*;
|
|
if (!annotated.isFwdOnly()) continue;
|
|
const entry: Object.DITypeMap.Entry = .{
|
|
.key_ptr = &self.di_type_map.keys()[i],
|
|
.value_ptr = value_ptr,
|
|
};
|
|
_ = try self.lowerDebugTypeImpl(entry, .full, annotated.toDIType());
|
|
}
|
|
|
|
dib.finalize();
|
|
}
|
|
|
|
if (comp.verbose_llvm_ir) |path| {
|
|
if (std.mem.eql(u8, path, "-")) {
|
|
self.llvm_module.dump();
|
|
} else {
|
|
const path_z = try comp.gpa.dupeZ(u8, path);
|
|
defer comp.gpa.free(path_z);
|
|
|
|
var error_message: [*:0]const u8 = undefined;
|
|
|
|
if (self.llvm_module.printModuleToFile(path_z, &error_message).toBool()) {
|
|
defer llvm.disposeMessage(error_message);
|
|
|
|
log.err("dump LLVM module failed ir={s}: {s}", .{
|
|
path, error_message,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
if (comp.verbose_llvm_bc) |path| {
|
|
const path_z = try comp.gpa.dupeZ(u8, path);
|
|
defer comp.gpa.free(path_z);
|
|
|
|
const error_code = self.llvm_module.writeBitcodeToFile(path_z);
|
|
if (error_code != 0) {
|
|
log.err("dump LLVM module failed bc={s}: {d}", .{
|
|
path, error_code,
|
|
});
|
|
}
|
|
}
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
|
|
defer arena_allocator.deinit();
|
|
const arena = arena_allocator.allocator();
|
|
|
|
const mod = comp.bin_file.options.module.?;
|
|
const cache_dir = mod.zig_cache_artifact_directory;
|
|
|
|
if (std.debug.runtime_safety) {
|
|
var error_message: [*:0]const u8 = undefined;
|
|
// verifyModule always allocs the error_message even if there is no error
|
|
defer llvm.disposeMessage(error_message);
|
|
|
|
if (self.llvm_module.verify(.ReturnStatus, &error_message).toBool()) {
|
|
std.debug.print("\n{s}\n", .{error_message});
|
|
|
|
if (try locPath(arena, comp.emit_llvm_ir, cache_dir)) |emit_llvm_ir_path| {
|
|
_ = self.llvm_module.printModuleToFile(emit_llvm_ir_path, &error_message);
|
|
}
|
|
|
|
@panic("LLVM module verification failed");
|
|
}
|
|
}
|
|
|
|
var emit_bin_path: ?[*:0]const u8 = if (comp.bin_file.options.emit) |emit|
|
|
try emit.basenamePath(arena, try arena.dupeZ(u8, comp.bin_file.intermediary_basename.?))
|
|
else
|
|
null;
|
|
|
|
const emit_asm_path = try locPath(arena, comp.emit_asm, cache_dir);
|
|
var emit_llvm_ir_path = try locPath(arena, comp.emit_llvm_ir, cache_dir);
|
|
const emit_llvm_bc_path = try locPath(arena, comp.emit_llvm_bc, cache_dir);
|
|
|
|
const emit_asm_msg = emit_asm_path orelse "(none)";
|
|
const emit_bin_msg = emit_bin_path orelse "(none)";
|
|
const emit_llvm_ir_msg = emit_llvm_ir_path orelse "(none)";
|
|
const emit_llvm_bc_msg = emit_llvm_bc_path orelse "(none)";
|
|
log.debug("emit LLVM object asm={s} bin={s} ir={s} bc={s}", .{
|
|
emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
|
|
});
|
|
|
|
// 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;
|
|
if (emit_asm_path != null and emit_bin_path != null) {
|
|
if (self.target_machine.emitToFile(
|
|
self.llvm_module,
|
|
&error_message,
|
|
comp.bin_file.options.optimize_mode == .Debug,
|
|
comp.bin_file.options.optimize_mode == .ReleaseSmall,
|
|
comp.time_report,
|
|
comp.bin_file.options.tsan,
|
|
comp.bin_file.options.lto,
|
|
null,
|
|
emit_bin_path,
|
|
emit_llvm_ir_path,
|
|
null,
|
|
)) {
|
|
defer llvm.disposeMessage(error_message);
|
|
|
|
log.err("LLVM failed to emit bin={s} ir={s}: {s}", .{
|
|
emit_bin_msg, emit_llvm_ir_msg, error_message,
|
|
});
|
|
return error.FailedToEmit;
|
|
}
|
|
emit_bin_path = null;
|
|
emit_llvm_ir_path = null;
|
|
}
|
|
|
|
if (self.target_machine.emitToFile(
|
|
self.llvm_module,
|
|
&error_message,
|
|
comp.bin_file.options.optimize_mode == .Debug,
|
|
comp.bin_file.options.optimize_mode == .ReleaseSmall,
|
|
comp.time_report,
|
|
comp.bin_file.options.tsan,
|
|
comp.bin_file.options.lto,
|
|
emit_asm_path,
|
|
emit_bin_path,
|
|
emit_llvm_ir_path,
|
|
emit_llvm_bc_path,
|
|
)) {
|
|
defer llvm.disposeMessage(error_message);
|
|
|
|
log.err("LLVM failed to emit asm={s} bin={s} ir={s} bc={s}: {s}", .{
|
|
emit_asm_msg, emit_bin_msg, emit_llvm_ir_msg, emit_llvm_bc_msg,
|
|
error_message,
|
|
});
|
|
return error.FailedToEmit;
|
|
}
|
|
}
|
|
|
|
pub fn updateFunc(
|
|
o: *Object,
|
|
mod: *Module,
|
|
func_index: InternPool.Index,
|
|
air: Air,
|
|
liveness: Liveness,
|
|
) !void {
|
|
const func = mod.funcInfo(func_index);
|
|
const decl_index = func.owner_decl;
|
|
const decl = mod.declPtr(decl_index);
|
|
const target = mod.getTarget();
|
|
const ip = &mod.intern_pool;
|
|
|
|
var dg: DeclGen = .{
|
|
.object = o,
|
|
.decl_index = decl_index,
|
|
.decl = decl,
|
|
.err_msg = null,
|
|
};
|
|
|
|
const llvm_func = try o.resolveLlvmFunction(decl_index);
|
|
|
|
if (func.analysis(ip).is_noinline) {
|
|
o.addFnAttr(llvm_func, "noinline");
|
|
} else {
|
|
Object.removeFnAttr(llvm_func, "noinline");
|
|
}
|
|
|
|
if (func.analysis(ip).stack_alignment.toByteUnitsOptional()) |alignment| {
|
|
o.addFnAttrInt(llvm_func, "alignstack", alignment);
|
|
o.addFnAttr(llvm_func, "noinline");
|
|
} else {
|
|
Object.removeFnAttr(llvm_func, "alignstack");
|
|
}
|
|
|
|
if (func.analysis(ip).is_cold) {
|
|
o.addFnAttr(llvm_func, "cold");
|
|
} else {
|
|
Object.removeFnAttr(llvm_func, "cold");
|
|
}
|
|
|
|
// TODO: disable this if safety is off for the function scope
|
|
const ssp_buf_size = mod.comp.bin_file.options.stack_protector;
|
|
if (ssp_buf_size != 0) {
|
|
var buf: [12]u8 = undefined;
|
|
const arg = std.fmt.bufPrintZ(&buf, "{d}", .{ssp_buf_size}) catch unreachable;
|
|
o.addFnAttr(llvm_func, "sspstrong");
|
|
o.addFnAttrString(llvm_func, "stack-protector-buffer-size", arg);
|
|
}
|
|
|
|
// TODO: disable this if safety is off for the function scope
|
|
if (mod.comp.bin_file.options.stack_check) {
|
|
o.addFnAttrString(llvm_func, "probe-stack", "__zig_probe_stack");
|
|
} else if (target.os.tag == .uefi) {
|
|
o.addFnAttrString(llvm_func, "no-stack-arg-probe", "");
|
|
}
|
|
|
|
if (ip.stringToSliceUnwrap(decl.@"linksection")) |section|
|
|
llvm_func.setSection(section);
|
|
|
|
// Remove all the basic blocks of a function in order to start over, generating
|
|
// LLVM IR from an empty function body.
|
|
while (llvm_func.getFirstBasicBlock()) |bb| {
|
|
bb.deleteBasicBlock();
|
|
}
|
|
|
|
const builder = o.context.createBuilder();
|
|
|
|
const entry_block = o.context.appendBasicBlock(llvm_func, "Entry");
|
|
builder.positionBuilderAtEnd(entry_block);
|
|
|
|
// This gets the LLVM values from the function and stores them in `dg.args`.
|
|
const fn_info = mod.typeToFunc(decl.ty).?;
|
|
const sret = firstParamSRet(fn_info, mod);
|
|
const ret_ptr = if (sret) llvm_func.getParam(0) else null;
|
|
const gpa = o.gpa;
|
|
|
|
if (ccAbiPromoteInt(fn_info.cc, mod, fn_info.return_type.toType())) |s| switch (s) {
|
|
.signed => o.addAttr(llvm_func, 0, "signext"),
|
|
.unsigned => o.addAttr(llvm_func, 0, "zeroext"),
|
|
};
|
|
|
|
const err_return_tracing = fn_info.return_type.toType().isError(mod) and
|
|
mod.comp.bin_file.options.error_return_tracing;
|
|
|
|
const err_ret_trace = if (err_return_tracing)
|
|
llvm_func.getParam(@intFromBool(ret_ptr != null))
|
|
else
|
|
null;
|
|
|
|
// 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(*llvm.Value).init(gpa);
|
|
defer args.deinit();
|
|
|
|
{
|
|
var llvm_arg_i = @as(c_uint, @intFromBool(ret_ptr != null)) + @intFromBool(err_return_tracing);
|
|
var it = iterateParamTypes(o, fn_info);
|
|
while (it.next()) |lowering| switch (lowering) {
|
|
.no_bits => continue,
|
|
.byval => {
|
|
assert(!it.byval_attr);
|
|
const param_index = it.zig_index - 1;
|
|
const param_ty = fn_info.param_types.get(ip)[param_index].toType();
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
try args.ensureUnusedCapacity(1);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const param_llvm_ty = param.typeOf();
|
|
const arg_ptr = buildAllocaInner(o.context, builder, llvm_func, false, param_llvm_ty, alignment, target);
|
|
const store_inst = builder.buildStore(param, arg_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
args.appendAssumeCapacity(param);
|
|
|
|
o.addByValParamAttrs(llvm_func, param_ty, param_index, fn_info, llvm_arg_i);
|
|
}
|
|
llvm_arg_i += 1;
|
|
},
|
|
.byref => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
|
|
o.addByRefParamAttrs(llvm_func, llvm_arg_i, alignment, it.byval_attr, param_llvm_ty);
|
|
llvm_arg_i += 1;
|
|
|
|
try args.ensureUnusedCapacity(1);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
args.appendAssumeCapacity(param);
|
|
} else {
|
|
const load_inst = builder.buildLoad(param_llvm_ty, param, "");
|
|
load_inst.setAlignment(alignment);
|
|
args.appendAssumeCapacity(load_inst);
|
|
}
|
|
},
|
|
.byref_mut => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
|
|
o.addArgAttr(llvm_func, llvm_arg_i, "noundef");
|
|
llvm_arg_i += 1;
|
|
|
|
try args.ensureUnusedCapacity(1);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
args.appendAssumeCapacity(param);
|
|
} else {
|
|
const load_inst = builder.buildLoad(param_llvm_ty, param, "");
|
|
load_inst.setAlignment(alignment);
|
|
args.appendAssumeCapacity(load_inst);
|
|
}
|
|
},
|
|
.abi_sized_int => {
|
|
assert(!it.byval_attr);
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const abi_size = @as(c_uint, @intCast(param_ty.abiSize(mod)));
|
|
const int_llvm_ty = o.context.intType(abi_size * 8);
|
|
const alignment = @max(
|
|
param_ty.abiAlignment(mod),
|
|
o.target_data.abiAlignmentOfType(int_llvm_ty),
|
|
);
|
|
const arg_ptr = buildAllocaInner(o.context, builder, llvm_func, false, param_llvm_ty, alignment, target);
|
|
const store_inst = builder.buildStore(param, arg_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
|
|
try args.ensureUnusedCapacity(1);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
args.appendAssumeCapacity(arg_ptr);
|
|
} else {
|
|
const load_inst = builder.buildLoad(param_llvm_ty, arg_ptr, "");
|
|
load_inst.setAlignment(alignment);
|
|
args.appendAssumeCapacity(load_inst);
|
|
}
|
|
},
|
|
.slice => {
|
|
assert(!it.byval_attr);
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const ptr_info = param_ty.ptrInfo(mod);
|
|
|
|
if (math.cast(u5, it.zig_index - 1)) |i| {
|
|
if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
|
|
o.addArgAttr(llvm_func, llvm_arg_i, "noalias");
|
|
}
|
|
}
|
|
if (param_ty.zigTypeTag(mod) != .Optional) {
|
|
o.addArgAttr(llvm_func, llvm_arg_i, "nonnull");
|
|
}
|
|
if (ptr_info.flags.is_const) {
|
|
o.addArgAttr(llvm_func, llvm_arg_i, "readonly");
|
|
}
|
|
const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse
|
|
@max(ptr_info.child.toType().abiAlignment(mod), 1);
|
|
o.addArgAttrInt(llvm_func, llvm_arg_i, "align", elem_align);
|
|
const ptr_param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
const len_param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
|
|
const slice_llvm_ty = try o.lowerType(param_ty);
|
|
const partial = builder.buildInsertValue(slice_llvm_ty.getUndef(), ptr_param, 0, "");
|
|
const aggregate = builder.buildInsertValue(partial, len_param, 1, "");
|
|
try args.append(aggregate);
|
|
},
|
|
.multiple_llvm_types => {
|
|
assert(!it.byval_attr);
|
|
const field_types = it.llvm_types_buffer[0..it.llvm_types_len];
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param_alignment = param_ty.abiAlignment(mod);
|
|
const arg_ptr = buildAllocaInner(o.context, builder, llvm_func, false, param_llvm_ty, param_alignment, target);
|
|
const llvm_ty = o.context.structType(field_types.ptr, @as(c_uint, @intCast(field_types.len)), .False);
|
|
for (field_types, 0..) |_, field_i_usize| {
|
|
const field_i = @as(c_uint, @intCast(field_i_usize));
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
const field_ptr = builder.buildStructGEP(llvm_ty, arg_ptr, field_i, "");
|
|
const store_inst = builder.buildStore(param, field_ptr);
|
|
store_inst.setAlignment(target.ptrBitWidth() / 8);
|
|
}
|
|
|
|
const is_by_ref = isByRef(param_ty, mod);
|
|
const loaded = if (is_by_ref) arg_ptr else l: {
|
|
const load_inst = builder.buildLoad(param_llvm_ty, arg_ptr, "");
|
|
load_inst.setAlignment(param_alignment);
|
|
break :l load_inst;
|
|
};
|
|
try args.append(loaded);
|
|
},
|
|
.as_u16 => {
|
|
assert(!it.byval_attr);
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
const casted = builder.buildBitCast(param, o.context.halfType(), "");
|
|
try args.ensureUnusedCapacity(1);
|
|
args.appendAssumeCapacity(casted);
|
|
},
|
|
.float_array => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const arg_ptr = buildAllocaInner(o.context, builder, llvm_func, false, param_llvm_ty, alignment, target);
|
|
_ = builder.buildStore(param, arg_ptr);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
try args.append(arg_ptr);
|
|
} else {
|
|
const load_inst = builder.buildLoad(param_llvm_ty, arg_ptr, "");
|
|
load_inst.setAlignment(alignment);
|
|
try args.append(load_inst);
|
|
}
|
|
},
|
|
.i32_array, .i64_array => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const param = llvm_func.getParam(llvm_arg_i);
|
|
llvm_arg_i += 1;
|
|
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const arg_ptr = buildAllocaInner(o.context, builder, llvm_func, false, param_llvm_ty, alignment, target);
|
|
_ = builder.buildStore(param, arg_ptr);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
try args.append(arg_ptr);
|
|
} else {
|
|
const load_inst = builder.buildLoad(param_llvm_ty, arg_ptr, "");
|
|
load_inst.setAlignment(alignment);
|
|
try args.append(load_inst);
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
var di_file: ?*llvm.DIFile = null;
|
|
var di_scope: ?*llvm.DIScope = null;
|
|
|
|
if (o.di_builder) |dib| {
|
|
di_file = try o.getDIFile(gpa, mod.namespacePtr(decl.src_namespace).file_scope);
|
|
|
|
const line_number = decl.src_line + 1;
|
|
const is_internal_linkage = decl.val.getExternFunc(mod) == null and
|
|
!mod.decl_exports.contains(decl_index);
|
|
const noret_bit: c_uint = if (fn_info.return_type == .noreturn_type)
|
|
llvm.DIFlags.NoReturn
|
|
else
|
|
0;
|
|
const decl_di_ty = try o.lowerDebugType(decl.ty, .full);
|
|
const subprogram = dib.createFunction(
|
|
di_file.?.toScope(),
|
|
ip.stringToSlice(decl.name),
|
|
llvm_func.getValueName(),
|
|
di_file.?,
|
|
line_number,
|
|
decl_di_ty,
|
|
is_internal_linkage,
|
|
true, // is definition
|
|
line_number + func.lbrace_line, // scope line
|
|
llvm.DIFlags.StaticMember | noret_bit,
|
|
mod.comp.bin_file.options.optimize_mode != .Debug,
|
|
null, // decl_subprogram
|
|
);
|
|
try o.di_map.put(gpa, decl, subprogram.toNode());
|
|
|
|
llvm_func.fnSetSubprogram(subprogram);
|
|
|
|
di_scope = subprogram.toScope();
|
|
}
|
|
|
|
var fg: FuncGen = .{
|
|
.gpa = gpa,
|
|
.air = air,
|
|
.liveness = liveness,
|
|
.context = o.context,
|
|
.dg = &dg,
|
|
.builder = builder,
|
|
.ret_ptr = ret_ptr,
|
|
.args = args.items,
|
|
.arg_index = 0,
|
|
.func_inst_table = .{},
|
|
.llvm_func = llvm_func,
|
|
.blocks = .{},
|
|
.single_threaded = mod.comp.bin_file.options.single_threaded,
|
|
.di_scope = di_scope,
|
|
.di_file = di_file,
|
|
.base_line = dg.decl.src_line,
|
|
.prev_dbg_line = 0,
|
|
.prev_dbg_column = 0,
|
|
.err_ret_trace = err_ret_trace,
|
|
};
|
|
defer fg.deinit();
|
|
|
|
fg.genBody(air.getMainBody()) catch |err| switch (err) {
|
|
error.CodegenFail => {
|
|
decl.analysis = .codegen_failure;
|
|
try mod.failed_decls.put(mod.gpa, decl_index, dg.err_msg.?);
|
|
dg.err_msg = null;
|
|
return;
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
|
|
try o.updateDeclExports(mod, decl_index, mod.getDeclExports(decl_index));
|
|
}
|
|
|
|
pub fn updateDecl(self: *Object, module: *Module, decl_index: Module.Decl.Index) !void {
|
|
const decl = module.declPtr(decl_index);
|
|
var dg: DeclGen = .{
|
|
.object = self,
|
|
.decl = decl,
|
|
.decl_index = decl_index,
|
|
.err_msg = null,
|
|
};
|
|
dg.genDecl() catch |err| switch (err) {
|
|
error.CodegenFail => {
|
|
decl.analysis = .codegen_failure;
|
|
try module.failed_decls.put(module.gpa, decl_index, dg.err_msg.?);
|
|
dg.err_msg = null;
|
|
return;
|
|
},
|
|
else => |e| return e,
|
|
};
|
|
try self.updateDeclExports(module, decl_index, module.getDeclExports(decl_index));
|
|
}
|
|
|
|
/// TODO replace this with a call to `Module::getNamedValue`. This will require adding
|
|
/// a new wrapper in zig_llvm.h/zig_llvm.cpp.
|
|
fn getLlvmGlobal(o: Object, name: [*:0]const u8) ?*llvm.Value {
|
|
if (o.llvm_module.getNamedFunction(name)) |x| return x;
|
|
if (o.llvm_module.getNamedGlobal(name)) |x| return x;
|
|
return null;
|
|
}
|
|
|
|
pub fn updateDeclExports(
|
|
self: *Object,
|
|
mod: *Module,
|
|
decl_index: Module.Decl.Index,
|
|
exports: []const *Module.Export,
|
|
) !void {
|
|
const gpa = mod.gpa;
|
|
// If the module does not already have the function, we ignore this function call
|
|
// because we call `updateDeclExports` at the end of `updateFunc` and `updateDecl`.
|
|
const llvm_global = self.decl_map.get(decl_index) orelse return;
|
|
const decl = mod.declPtr(decl_index);
|
|
if (decl.isExtern(mod)) {
|
|
var free_decl_name = false;
|
|
const decl_name = decl_name: {
|
|
const decl_name = mod.intern_pool.stringToSlice(decl.name);
|
|
|
|
if (mod.getTarget().isWasm() and try decl.isFunction(mod)) {
|
|
if (mod.intern_pool.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| {
|
|
if (!std.mem.eql(u8, lib_name, "c")) {
|
|
free_decl_name = true;
|
|
break :decl_name try std.fmt.allocPrintZ(gpa, "{s}|{s}", .{
|
|
decl_name, lib_name,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
break :decl_name decl_name;
|
|
};
|
|
defer if (free_decl_name) gpa.free(decl_name);
|
|
|
|
llvm_global.setValueName(decl_name);
|
|
if (self.getLlvmGlobal(decl_name)) |other_global| {
|
|
if (other_global != llvm_global) {
|
|
try self.extern_collisions.put(gpa, decl_index, {});
|
|
}
|
|
}
|
|
llvm_global.setUnnamedAddr(.False);
|
|
llvm_global.setLinkage(.External);
|
|
if (mod.wantDllExports()) llvm_global.setDLLStorageClass(.Default);
|
|
if (self.di_map.get(decl)) |di_node| {
|
|
if (try decl.isFunction(mod)) {
|
|
const di_func = @as(*llvm.DISubprogram, @ptrCast(di_node));
|
|
const linkage_name = llvm.MDString.get(self.context, decl_name.ptr, decl_name.len);
|
|
di_func.replaceLinkageName(linkage_name);
|
|
} else {
|
|
const di_global = @as(*llvm.DIGlobalVariable, @ptrCast(di_node));
|
|
const linkage_name = llvm.MDString.get(self.context, decl_name.ptr, decl_name.len);
|
|
di_global.replaceLinkageName(linkage_name);
|
|
}
|
|
}
|
|
if (decl.val.getVariable(mod)) |variable| {
|
|
if (variable.is_threadlocal) {
|
|
llvm_global.setThreadLocalMode(.GeneralDynamicTLSModel);
|
|
} else {
|
|
llvm_global.setThreadLocalMode(.NotThreadLocal);
|
|
}
|
|
if (variable.is_weak_linkage) {
|
|
llvm_global.setLinkage(.ExternalWeak);
|
|
}
|
|
}
|
|
} else if (exports.len != 0) {
|
|
const exp_name = mod.intern_pool.stringToSlice(exports[0].opts.name);
|
|
llvm_global.setValueName2(exp_name.ptr, exp_name.len);
|
|
llvm_global.setUnnamedAddr(.False);
|
|
if (mod.wantDllExports()) llvm_global.setDLLStorageClass(.DLLExport);
|
|
if (self.di_map.get(decl)) |di_node| {
|
|
if (try decl.isFunction(mod)) {
|
|
const di_func = @as(*llvm.DISubprogram, @ptrCast(di_node));
|
|
const linkage_name = llvm.MDString.get(self.context, exp_name.ptr, exp_name.len);
|
|
di_func.replaceLinkageName(linkage_name);
|
|
} else {
|
|
const di_global = @as(*llvm.DIGlobalVariable, @ptrCast(di_node));
|
|
const linkage_name = llvm.MDString.get(self.context, exp_name.ptr, exp_name.len);
|
|
di_global.replaceLinkageName(linkage_name);
|
|
}
|
|
}
|
|
switch (exports[0].opts.linkage) {
|
|
.Internal => unreachable,
|
|
.Strong => llvm_global.setLinkage(.External),
|
|
.Weak => llvm_global.setLinkage(.WeakODR),
|
|
.LinkOnce => llvm_global.setLinkage(.LinkOnceODR),
|
|
}
|
|
switch (exports[0].opts.visibility) {
|
|
.default => llvm_global.setVisibility(.Default),
|
|
.hidden => llvm_global.setVisibility(.Hidden),
|
|
.protected => llvm_global.setVisibility(.Protected),
|
|
}
|
|
if (mod.intern_pool.stringToSliceUnwrap(exports[0].opts.section)) |section| {
|
|
llvm_global.setSection(section);
|
|
}
|
|
if (decl.val.getVariable(mod)) |variable| {
|
|
if (variable.is_threadlocal) {
|
|
llvm_global.setThreadLocalMode(.GeneralDynamicTLSModel);
|
|
}
|
|
}
|
|
|
|
// If a Decl is exported more than one time (which is rare),
|
|
// we add aliases for all but the first export.
|
|
// TODO LLVM C API does not support deleting aliases.
|
|
// The planned solution to this is https://github.com/ziglang/zig/issues/13265
|
|
// Until then we iterate over existing aliases and make them point
|
|
// to the correct decl, or otherwise add a new alias. Old aliases are leaked.
|
|
for (exports[1..]) |exp| {
|
|
const exp_name_z = mod.intern_pool.stringToSlice(exp.opts.name);
|
|
|
|
if (self.llvm_module.getNamedGlobalAlias(exp_name_z.ptr, exp_name_z.len)) |alias| {
|
|
alias.setAliasee(llvm_global);
|
|
} else {
|
|
_ = self.llvm_module.addAlias(
|
|
llvm_global.globalGetValueType(),
|
|
0,
|
|
llvm_global,
|
|
exp_name_z,
|
|
);
|
|
}
|
|
}
|
|
} else {
|
|
const fqn = mod.intern_pool.stringToSlice(try decl.getFullyQualifiedName(mod));
|
|
llvm_global.setValueName2(fqn.ptr, fqn.len);
|
|
llvm_global.setLinkage(.Internal);
|
|
if (mod.wantDllExports()) llvm_global.setDLLStorageClass(.Default);
|
|
llvm_global.setUnnamedAddr(.True);
|
|
if (decl.val.getVariable(mod)) |variable| {
|
|
const single_threaded = mod.comp.bin_file.options.single_threaded;
|
|
if (variable.is_threadlocal and !single_threaded) {
|
|
llvm_global.setThreadLocalMode(.GeneralDynamicTLSModel);
|
|
} else {
|
|
llvm_global.setThreadLocalMode(.NotThreadLocal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pub fn freeDecl(self: *Object, decl_index: Module.Decl.Index) void {
|
|
const llvm_value = self.decl_map.get(decl_index) orelse return;
|
|
llvm_value.deleteGlobal();
|
|
}
|
|
|
|
fn getDIFile(o: *Object, gpa: Allocator, file: *const Module.File) !*llvm.DIFile {
|
|
const gop = try o.di_map.getOrPut(gpa, file);
|
|
errdefer assert(o.di_map.remove(file));
|
|
if (gop.found_existing) {
|
|
return @as(*llvm.DIFile, @ptrCast(gop.value_ptr.*));
|
|
}
|
|
const dir_path_z = d: {
|
|
var buffer: [std.fs.MAX_PATH_BYTES]u8 = undefined;
|
|
const dir_path = file.pkg.root_src_directory.path orelse ".";
|
|
const resolved_dir_path = if (std.fs.path.isAbsolute(dir_path))
|
|
dir_path
|
|
else
|
|
std.os.realpath(dir_path, &buffer) catch dir_path; // If realpath fails, fallback to whatever dir_path was
|
|
break :d try std.fs.path.joinZ(gpa, &.{
|
|
resolved_dir_path, std.fs.path.dirname(file.sub_file_path) orelse "",
|
|
});
|
|
};
|
|
defer gpa.free(dir_path_z);
|
|
const sub_file_path_z = try gpa.dupeZ(u8, std.fs.path.basename(file.sub_file_path));
|
|
defer gpa.free(sub_file_path_z);
|
|
const di_file = o.di_builder.?.createFile(sub_file_path_z, dir_path_z);
|
|
gop.value_ptr.* = di_file.toNode();
|
|
return di_file;
|
|
}
|
|
|
|
const DebugResolveStatus = enum { fwd, full };
|
|
|
|
/// In the implementation of this function, it is required to store a forward decl
|
|
/// into `gop` before making any recursive calls (even directly).
|
|
fn lowerDebugType(
|
|
o: *Object,
|
|
ty: Type,
|
|
resolve: DebugResolveStatus,
|
|
) Allocator.Error!*llvm.DIType {
|
|
const gpa = o.gpa;
|
|
// Be careful not to reference this `gop` variable after any recursive calls
|
|
// to `lowerDebugType`.
|
|
const gop = try o.di_type_map.getOrPut(gpa, ty.toIntern());
|
|
if (gop.found_existing) {
|
|
const annotated = gop.value_ptr.*;
|
|
const di_type = annotated.toDIType();
|
|
if (!annotated.isFwdOnly() or resolve == .fwd) {
|
|
return di_type;
|
|
}
|
|
const entry: Object.DITypeMap.Entry = .{
|
|
.key_ptr = gop.key_ptr,
|
|
.value_ptr = gop.value_ptr,
|
|
};
|
|
return o.lowerDebugTypeImpl(entry, resolve, di_type);
|
|
}
|
|
errdefer assert(o.di_type_map.orderedRemove(ty.toIntern()));
|
|
const entry: Object.DITypeMap.Entry = .{
|
|
.key_ptr = gop.key_ptr,
|
|
.value_ptr = gop.value_ptr,
|
|
};
|
|
return o.lowerDebugTypeImpl(entry, resolve, null);
|
|
}
|
|
|
|
/// This is a helper function used by `lowerDebugType`.
|
|
fn lowerDebugTypeImpl(
|
|
o: *Object,
|
|
gop: Object.DITypeMap.Entry,
|
|
resolve: DebugResolveStatus,
|
|
opt_fwd_decl: ?*llvm.DIType,
|
|
) Allocator.Error!*llvm.DIType {
|
|
const ty = gop.key_ptr.toType();
|
|
const gpa = o.gpa;
|
|
const target = o.target;
|
|
const dib = o.di_builder.?;
|
|
const mod = o.module;
|
|
const ip = &mod.intern_pool;
|
|
switch (ty.zigTypeTag(mod)) {
|
|
.Void, .NoReturn => {
|
|
const di_type = dib.createBasicType("void", 0, DW.ATE.signed);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
|
|
return di_type;
|
|
},
|
|
.Int => {
|
|
const info = ty.intInfo(mod);
|
|
assert(info.bits != 0);
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const dwarf_encoding: c_uint = switch (info.signedness) {
|
|
.signed => DW.ATE.signed,
|
|
.unsigned => DW.ATE.unsigned,
|
|
};
|
|
const di_bits = ty.abiSize(mod) * 8; // lldb cannot handle non-byte sized types
|
|
const di_type = dib.createBasicType(name, di_bits, dwarf_encoding);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
|
|
return di_type;
|
|
},
|
|
.Enum => {
|
|
const owner_decl_index = ty.getOwnerDecl(mod);
|
|
const owner_decl = o.module.declPtr(owner_decl_index);
|
|
|
|
if (!ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const enum_di_ty = try o.makeEmptyNamespaceDIType(owner_decl_index);
|
|
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
|
|
// means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(enum_di_ty));
|
|
return enum_di_ty;
|
|
}
|
|
|
|
const enum_type = ip.indexToKey(ty.toIntern()).enum_type;
|
|
|
|
const enumerators = try gpa.alloc(*llvm.DIEnumerator, enum_type.names.len);
|
|
defer gpa.free(enumerators);
|
|
|
|
const int_ty = enum_type.tag_ty.toType();
|
|
const int_info = ty.intInfo(mod);
|
|
assert(int_info.bits != 0);
|
|
|
|
for (enum_type.names, 0..) |field_name_ip, i| {
|
|
const field_name_z = ip.stringToSlice(field_name_ip);
|
|
|
|
var bigint_space: Value.BigIntSpace = undefined;
|
|
const bigint = if (enum_type.values.len != 0)
|
|
enum_type.values[i].toValue().toBigInt(&bigint_space, mod)
|
|
else
|
|
std.math.big.int.Mutable.init(&bigint_space.limbs, i).toConst();
|
|
|
|
if (bigint.limbs.len == 1) {
|
|
enumerators[i] = dib.createEnumerator(field_name_z, bigint.limbs[0], int_info.signedness == .unsigned);
|
|
continue;
|
|
}
|
|
if (@sizeOf(usize) == @sizeOf(u64)) {
|
|
enumerators[i] = dib.createEnumerator2(
|
|
field_name_z,
|
|
@intCast(bigint.limbs.len),
|
|
bigint.limbs.ptr,
|
|
int_info.bits,
|
|
int_info.signedness == .unsigned,
|
|
);
|
|
continue;
|
|
}
|
|
@panic("TODO implement bigint debug enumerators to llvm int for 32-bit compiler builds");
|
|
}
|
|
|
|
const di_file = try o.getDIFile(gpa, mod.namespacePtr(owner_decl.src_namespace).file_scope);
|
|
const di_scope = try o.namespaceToDebugScope(owner_decl.src_namespace);
|
|
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
|
|
const enum_di_ty = dib.createEnumerationType(
|
|
di_scope,
|
|
name,
|
|
di_file,
|
|
owner_decl.src_node + 1,
|
|
ty.abiSize(mod) * 8,
|
|
ty.abiAlignment(mod) * 8,
|
|
enumerators.ptr,
|
|
@as(c_int, @intCast(enumerators.len)),
|
|
try o.lowerDebugType(int_ty, .full),
|
|
"",
|
|
);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(enum_di_ty));
|
|
return enum_di_ty;
|
|
},
|
|
.Float => {
|
|
const bits = ty.floatBits(target);
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const di_type = dib.createBasicType(name, bits, DW.ATE.float);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
|
|
return di_type;
|
|
},
|
|
.Bool => {
|
|
const di_bits = 8; // lldb cannot handle non-byte sized types
|
|
const di_type = dib.createBasicType("bool", di_bits, DW.ATE.boolean);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_type);
|
|
return di_type;
|
|
},
|
|
.Pointer => {
|
|
// Normalize everything that the debug info does not represent.
|
|
const ptr_info = ty.ptrInfo(mod);
|
|
|
|
if (ptr_info.sentinel != .none or
|
|
ptr_info.flags.address_space != .generic or
|
|
ptr_info.packed_offset.bit_offset != 0 or
|
|
ptr_info.packed_offset.host_size != 0 or
|
|
ptr_info.flags.vector_index != .none or
|
|
ptr_info.flags.is_allowzero or
|
|
ptr_info.flags.is_const or
|
|
ptr_info.flags.is_volatile or
|
|
ptr_info.flags.size == .Many or ptr_info.flags.size == .C or
|
|
!ptr_info.child.toType().hasRuntimeBitsIgnoreComptime(mod))
|
|
{
|
|
const bland_ptr_ty = try mod.ptrType(.{
|
|
.child = if (!ptr_info.child.toType().hasRuntimeBitsIgnoreComptime(mod))
|
|
.anyopaque_type
|
|
else
|
|
ptr_info.child,
|
|
.flags = .{
|
|
.alignment = ptr_info.flags.alignment,
|
|
.size = switch (ptr_info.flags.size) {
|
|
.Many, .C, .One => .One,
|
|
.Slice => .Slice,
|
|
},
|
|
},
|
|
});
|
|
const ptr_di_ty = try o.lowerDebugType(bland_ptr_ty, resolve);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.init(ptr_di_ty, resolve));
|
|
return ptr_di_ty;
|
|
}
|
|
|
|
if (ty.isSlice(mod)) {
|
|
const ptr_ty = ty.slicePtrFieldType(mod);
|
|
const len_ty = Type.usize;
|
|
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const di_file: ?*llvm.DIFile = null;
|
|
const line = 0;
|
|
const compile_unit_scope = o.di_compile_unit.?.toScope();
|
|
|
|
const fwd_decl = opt_fwd_decl orelse blk: {
|
|
const fwd_decl = dib.createReplaceableCompositeType(
|
|
DW.TAG.structure_type,
|
|
name.ptr,
|
|
compile_unit_scope,
|
|
di_file,
|
|
line,
|
|
);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
|
|
if (resolve == .fwd) return fwd_decl;
|
|
break :blk fwd_decl;
|
|
};
|
|
|
|
const ptr_size = ptr_ty.abiSize(mod);
|
|
const ptr_align = ptr_ty.abiAlignment(mod);
|
|
const len_size = len_ty.abiSize(mod);
|
|
const len_align = len_ty.abiAlignment(mod);
|
|
|
|
var offset: u64 = 0;
|
|
offset += ptr_size;
|
|
offset = std.mem.alignForward(u64, offset, len_align);
|
|
const len_offset = offset;
|
|
|
|
const fields: [2]*llvm.DIType = .{
|
|
dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"ptr",
|
|
di_file,
|
|
line,
|
|
ptr_size * 8, // size in bits
|
|
ptr_align * 8, // align in bits
|
|
0, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(ptr_ty, .full),
|
|
),
|
|
dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"len",
|
|
di_file,
|
|
line,
|
|
len_size * 8, // size in bits
|
|
len_align * 8, // align in bits
|
|
len_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(len_ty, .full),
|
|
),
|
|
};
|
|
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
di_file,
|
|
line,
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
&fields,
|
|
fields.len,
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
}
|
|
|
|
const elem_di_ty = try o.lowerDebugType(ptr_info.child.toType(), .fwd);
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const ptr_di_ty = dib.createPointerType(
|
|
elem_di_ty,
|
|
target.ptrBitWidth(),
|
|
ty.ptrAlignment(mod) * 8,
|
|
name,
|
|
);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(ptr_di_ty));
|
|
return ptr_di_ty;
|
|
},
|
|
.Opaque => {
|
|
if (ty.toIntern() == .anyopaque_type) {
|
|
const di_ty = dib.createBasicType("anyopaque", 0, DW.ATE.signed);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
|
|
return di_ty;
|
|
}
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const owner_decl_index = ty.getOwnerDecl(mod);
|
|
const owner_decl = o.module.declPtr(owner_decl_index);
|
|
const opaque_di_ty = dib.createForwardDeclType(
|
|
DW.TAG.structure_type,
|
|
name,
|
|
try o.namespaceToDebugScope(owner_decl.src_namespace),
|
|
try o.getDIFile(gpa, mod.namespacePtr(owner_decl.src_namespace).file_scope),
|
|
owner_decl.src_node + 1,
|
|
);
|
|
// The recursive call to `lowerDebugType` va `namespaceToDebugScope`
|
|
// means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(opaque_di_ty));
|
|
return opaque_di_ty;
|
|
},
|
|
.Array => {
|
|
const array_di_ty = dib.createArrayType(
|
|
ty.abiSize(mod) * 8,
|
|
ty.abiAlignment(mod) * 8,
|
|
try o.lowerDebugType(ty.childType(mod), .full),
|
|
@as(i64, @intCast(ty.arrayLen(mod))),
|
|
);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(array_di_ty));
|
|
return array_di_ty;
|
|
},
|
|
.Vector => {
|
|
const elem_ty = ty.elemType2(mod);
|
|
// Vector elements cannot be padded since that would make
|
|
// @bitSizOf(elem) * len > @bitSizOf(vec).
|
|
// Neither gdb nor lldb seem to be able to display non-byte sized
|
|
// vectors properly.
|
|
const elem_di_type = switch (elem_ty.zigTypeTag(mod)) {
|
|
.Int => blk: {
|
|
const info = elem_ty.intInfo(mod);
|
|
assert(info.bits != 0);
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const dwarf_encoding: c_uint = switch (info.signedness) {
|
|
.signed => DW.ATE.signed,
|
|
.unsigned => DW.ATE.unsigned,
|
|
};
|
|
break :blk dib.createBasicType(name, info.bits, dwarf_encoding);
|
|
},
|
|
.Bool => dib.createBasicType("bool", 1, DW.ATE.boolean),
|
|
else => try o.lowerDebugType(ty.childType(mod), .full),
|
|
};
|
|
|
|
const vector_di_ty = dib.createVectorType(
|
|
ty.abiSize(mod) * 8,
|
|
ty.abiAlignment(mod) * 8,
|
|
elem_di_type,
|
|
ty.vectorLen(mod),
|
|
);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(vector_di_ty));
|
|
return vector_di_ty;
|
|
},
|
|
.Optional => {
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const child_ty = ty.optionalChild(mod);
|
|
if (!child_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const di_bits = 8; // lldb cannot handle non-byte sized types
|
|
const di_ty = dib.createBasicType(name, di_bits, DW.ATE.boolean);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
|
|
return di_ty;
|
|
}
|
|
if (ty.optionalReprIsPayload(mod)) {
|
|
const ptr_di_ty = try o.lowerDebugType(child_ty, resolve);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.init(ptr_di_ty, resolve));
|
|
return ptr_di_ty;
|
|
}
|
|
|
|
const di_file: ?*llvm.DIFile = null;
|
|
const line = 0;
|
|
const compile_unit_scope = o.di_compile_unit.?.toScope();
|
|
const fwd_decl = opt_fwd_decl orelse blk: {
|
|
const fwd_decl = dib.createReplaceableCompositeType(
|
|
DW.TAG.structure_type,
|
|
name.ptr,
|
|
compile_unit_scope,
|
|
di_file,
|
|
line,
|
|
);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
|
|
if (resolve == .fwd) return fwd_decl;
|
|
break :blk fwd_decl;
|
|
};
|
|
|
|
const non_null_ty = Type.u8;
|
|
const payload_size = child_ty.abiSize(mod);
|
|
const payload_align = child_ty.abiAlignment(mod);
|
|
const non_null_size = non_null_ty.abiSize(mod);
|
|
const non_null_align = non_null_ty.abiAlignment(mod);
|
|
|
|
var offset: u64 = 0;
|
|
offset += payload_size;
|
|
offset = std.mem.alignForward(u64, offset, non_null_align);
|
|
const non_null_offset = offset;
|
|
|
|
const fields: [2]*llvm.DIType = .{
|
|
dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"data",
|
|
di_file,
|
|
line,
|
|
payload_size * 8, // size in bits
|
|
payload_align * 8, // align in bits
|
|
0, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(child_ty, .full),
|
|
),
|
|
dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"some",
|
|
di_file,
|
|
line,
|
|
non_null_size * 8, // size in bits
|
|
non_null_align * 8, // align in bits
|
|
non_null_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(non_null_ty, .full),
|
|
),
|
|
};
|
|
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
di_file,
|
|
line,
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
&fields,
|
|
fields.len,
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
},
|
|
.ErrorUnion => {
|
|
const payload_ty = ty.errorUnionPayload(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const err_set_di_ty = try o.lowerDebugType(Type.anyerror, .full);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(err_set_di_ty));
|
|
return err_set_di_ty;
|
|
}
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
const di_file: ?*llvm.DIFile = null;
|
|
const line = 0;
|
|
const compile_unit_scope = o.di_compile_unit.?.toScope();
|
|
const fwd_decl = opt_fwd_decl orelse blk: {
|
|
const fwd_decl = dib.createReplaceableCompositeType(
|
|
DW.TAG.structure_type,
|
|
name.ptr,
|
|
compile_unit_scope,
|
|
di_file,
|
|
line,
|
|
);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
|
|
if (resolve == .fwd) return fwd_decl;
|
|
break :blk fwd_decl;
|
|
};
|
|
|
|
const error_size = Type.anyerror.abiSize(mod);
|
|
const error_align = Type.anyerror.abiAlignment(mod);
|
|
const payload_size = payload_ty.abiSize(mod);
|
|
const payload_align = payload_ty.abiAlignment(mod);
|
|
|
|
var error_index: u32 = undefined;
|
|
var payload_index: u32 = undefined;
|
|
var error_offset: u64 = undefined;
|
|
var payload_offset: u64 = undefined;
|
|
if (error_align > payload_align) {
|
|
error_index = 0;
|
|
payload_index = 1;
|
|
error_offset = 0;
|
|
payload_offset = std.mem.alignForward(u64, error_size, payload_align);
|
|
} else {
|
|
payload_index = 0;
|
|
error_index = 1;
|
|
payload_offset = 0;
|
|
error_offset = std.mem.alignForward(u64, payload_size, error_align);
|
|
}
|
|
|
|
var fields: [2]*llvm.DIType = undefined;
|
|
fields[error_index] = dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"tag",
|
|
di_file,
|
|
line,
|
|
error_size * 8, // size in bits
|
|
error_align * 8, // align in bits
|
|
error_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(Type.anyerror, .full),
|
|
);
|
|
fields[payload_index] = dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"value",
|
|
di_file,
|
|
line,
|
|
payload_size * 8, // size in bits
|
|
payload_align * 8, // align in bits
|
|
payload_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(payload_ty, .full),
|
|
);
|
|
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
di_file,
|
|
line,
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
&fields,
|
|
fields.len,
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
},
|
|
.ErrorSet => {
|
|
// TODO make this a proper enum with all the error codes in it.
|
|
// will need to consider how to take incremental compilation into account.
|
|
const di_ty = dib.createBasicType("anyerror", 16, DW.ATE.unsigned);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
|
|
return di_ty;
|
|
},
|
|
.Struct => {
|
|
const compile_unit_scope = o.di_compile_unit.?.toScope();
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
|
|
if (mod.typeToStruct(ty)) |struct_obj| {
|
|
if (struct_obj.layout == .Packed and struct_obj.haveFieldTypes()) {
|
|
assert(struct_obj.haveLayout());
|
|
const info = struct_obj.backing_int_ty.intInfo(mod);
|
|
const dwarf_encoding: c_uint = switch (info.signedness) {
|
|
.signed => DW.ATE.signed,
|
|
.unsigned => DW.ATE.unsigned,
|
|
};
|
|
const di_bits = ty.abiSize(mod) * 8; // lldb cannot handle non-byte sized types
|
|
const di_ty = dib.createBasicType(name, di_bits, dwarf_encoding);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFull(di_ty);
|
|
return di_ty;
|
|
}
|
|
}
|
|
|
|
const fwd_decl = opt_fwd_decl orelse blk: {
|
|
const fwd_decl = dib.createReplaceableCompositeType(
|
|
DW.TAG.structure_type,
|
|
name.ptr,
|
|
compile_unit_scope,
|
|
null, // file
|
|
0, // line
|
|
);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
|
|
if (resolve == .fwd) return fwd_decl;
|
|
break :blk fwd_decl;
|
|
};
|
|
|
|
switch (mod.intern_pool.indexToKey(ty.toIntern())) {
|
|
.anon_struct_type => |tuple| {
|
|
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
|
|
defer di_fields.deinit(gpa);
|
|
|
|
try di_fields.ensureUnusedCapacity(gpa, tuple.types.len);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
|
|
for (tuple.types, tuple.values, 0..) |field_ty, field_val, i| {
|
|
if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
|
|
|
|
const field_size = field_ty.toType().abiSize(mod);
|
|
const field_align = field_ty.toType().abiAlignment(mod);
|
|
const field_offset = std.mem.alignForward(u64, offset, field_align);
|
|
offset = field_offset + field_size;
|
|
|
|
const field_name = if (tuple.names.len != 0)
|
|
mod.intern_pool.stringToSlice(tuple.names[i])
|
|
else
|
|
try std.fmt.allocPrintZ(gpa, "{d}", .{i});
|
|
defer if (tuple.names.len == 0) gpa.free(field_name);
|
|
|
|
try di_fields.append(gpa, dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
field_name,
|
|
null, // file
|
|
0, // line
|
|
field_size * 8, // size in bits
|
|
field_align * 8, // align in bits
|
|
field_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(field_ty.toType(), .full),
|
|
));
|
|
}
|
|
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
null, // file
|
|
0, // line
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
di_fields.items.ptr,
|
|
@as(c_int, @intCast(di_fields.items.len)),
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
},
|
|
.struct_type => |struct_type| s: {
|
|
const struct_obj = mod.structPtrUnwrap(struct_type.index) orelse break :s;
|
|
|
|
if (!struct_obj.haveFieldTypes()) {
|
|
// This can happen if a struct type makes it all the way to
|
|
// flush() without ever being instantiated or referenced (even
|
|
// via pointer). The only reason we are hearing about it now is
|
|
// that it is being used as a namespace to put other debug types
|
|
// into. Therefore we can satisfy this by making an empty namespace,
|
|
// rather than changing the frontend to unnecessarily resolve the
|
|
// struct field types.
|
|
const owner_decl_index = ty.getOwnerDecl(mod);
|
|
const struct_di_ty = try o.makeEmptyNamespaceDIType(owner_decl_index);
|
|
dib.replaceTemporary(fwd_decl, struct_di_ty);
|
|
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
|
|
// means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(struct_di_ty));
|
|
return struct_di_ty;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (!ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const owner_decl_index = ty.getOwnerDecl(mod);
|
|
const struct_di_ty = try o.makeEmptyNamespaceDIType(owner_decl_index);
|
|
dib.replaceTemporary(fwd_decl, struct_di_ty);
|
|
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
|
|
// means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(struct_di_ty));
|
|
return struct_di_ty;
|
|
}
|
|
|
|
const fields = ty.structFields(mod);
|
|
const layout = ty.containerLayout(mod);
|
|
|
|
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
|
|
defer di_fields.deinit(gpa);
|
|
|
|
try di_fields.ensureUnusedCapacity(gpa, fields.count());
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
|
|
var it = mod.typeToStruct(ty).?.runtimeFieldIterator(mod);
|
|
while (it.next()) |field_and_index| {
|
|
const field = field_and_index.field;
|
|
const field_size = field.ty.abiSize(mod);
|
|
const field_align = field.alignment(mod, layout);
|
|
const field_offset = std.mem.alignForward(u64, offset, field_align);
|
|
offset = field_offset + field_size;
|
|
|
|
const field_name = mod.intern_pool.stringToSlice(fields.keys()[field_and_index.index]);
|
|
|
|
try di_fields.append(gpa, dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
field_name,
|
|
null, // file
|
|
0, // line
|
|
field_size * 8, // size in bits
|
|
field_align * 8, // align in bits
|
|
field_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(field.ty, .full),
|
|
));
|
|
}
|
|
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
null, // file
|
|
0, // line
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
di_fields.items.ptr,
|
|
@as(c_int, @intCast(di_fields.items.len)),
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
},
|
|
.Union => {
|
|
const compile_unit_scope = o.di_compile_unit.?.toScope();
|
|
const owner_decl_index = ty.getOwnerDecl(mod);
|
|
|
|
const name = try o.allocTypeName(ty);
|
|
defer gpa.free(name);
|
|
|
|
const fwd_decl = opt_fwd_decl orelse blk: {
|
|
const fwd_decl = dib.createReplaceableCompositeType(
|
|
DW.TAG.structure_type,
|
|
name.ptr,
|
|
o.di_compile_unit.?.toScope(),
|
|
null, // file
|
|
0, // line
|
|
);
|
|
gop.value_ptr.* = AnnotatedDITypePtr.initFwd(fwd_decl);
|
|
if (resolve == .fwd) return fwd_decl;
|
|
break :blk fwd_decl;
|
|
};
|
|
|
|
const union_obj = mod.typeToUnion(ty).?;
|
|
if (!union_obj.haveFieldTypes() or !ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const union_di_ty = try o.makeEmptyNamespaceDIType(owner_decl_index);
|
|
dib.replaceTemporary(fwd_decl, union_di_ty);
|
|
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
|
|
// means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(union_di_ty));
|
|
return union_di_ty;
|
|
}
|
|
|
|
const layout = ty.unionGetLayout(mod);
|
|
|
|
if (layout.payload_size == 0) {
|
|
const tag_di_ty = try o.lowerDebugType(union_obj.tag_ty, .full);
|
|
const di_fields = [_]*llvm.DIType{tag_di_ty};
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
null, // file
|
|
0, // line
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
&di_fields,
|
|
di_fields.len,
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` via `makeEmptyNamespaceDIType`
|
|
// means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
}
|
|
|
|
var di_fields: std.ArrayListUnmanaged(*llvm.DIType) = .{};
|
|
defer di_fields.deinit(gpa);
|
|
|
|
try di_fields.ensureUnusedCapacity(gpa, union_obj.fields.count());
|
|
|
|
var it = union_obj.fields.iterator();
|
|
while (it.next()) |kv| {
|
|
const field_name = kv.key_ptr.*;
|
|
const field = kv.value_ptr.*;
|
|
|
|
if (!field.ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
|
|
|
|
const field_size = field.ty.abiSize(mod);
|
|
const field_align = field.normalAlignment(mod);
|
|
|
|
const field_di_ty = try o.lowerDebugType(field.ty, .full);
|
|
di_fields.appendAssumeCapacity(dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
mod.intern_pool.stringToSlice(field_name),
|
|
null, // file
|
|
0, // line
|
|
field_size * 8, // size in bits
|
|
field_align * 8, // align in bits
|
|
0, // offset in bits
|
|
0, // flags
|
|
field_di_ty,
|
|
));
|
|
}
|
|
|
|
var union_name_buf: ?[:0]const u8 = null;
|
|
defer if (union_name_buf) |buf| gpa.free(buf);
|
|
const union_name = if (layout.tag_size == 0) name else name: {
|
|
union_name_buf = try std.fmt.allocPrintZ(gpa, "{s}:Payload", .{name});
|
|
break :name union_name_buf.?;
|
|
};
|
|
|
|
const union_di_ty = dib.createUnionType(
|
|
compile_unit_scope,
|
|
union_name.ptr,
|
|
null, // file
|
|
0, // line
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
di_fields.items.ptr,
|
|
@as(c_int, @intCast(di_fields.items.len)),
|
|
0, // run time lang
|
|
"", // unique id
|
|
);
|
|
|
|
if (layout.tag_size == 0) {
|
|
dib.replaceTemporary(fwd_decl, union_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(union_di_ty));
|
|
return union_di_ty;
|
|
}
|
|
|
|
var tag_offset: u64 = undefined;
|
|
var payload_offset: u64 = undefined;
|
|
if (layout.tag_align >= layout.payload_align) {
|
|
tag_offset = 0;
|
|
payload_offset = std.mem.alignForward(u64, layout.tag_size, layout.payload_align);
|
|
} else {
|
|
payload_offset = 0;
|
|
tag_offset = std.mem.alignForward(u64, layout.payload_size, layout.tag_align);
|
|
}
|
|
|
|
const tag_di = dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"tag",
|
|
null, // file
|
|
0, // line
|
|
layout.tag_size * 8,
|
|
layout.tag_align * 8, // align in bits
|
|
tag_offset * 8, // offset in bits
|
|
0, // flags
|
|
try o.lowerDebugType(union_obj.tag_ty, .full),
|
|
);
|
|
|
|
const payload_di = dib.createMemberType(
|
|
fwd_decl.toScope(),
|
|
"payload",
|
|
null, // file
|
|
0, // line
|
|
layout.payload_size * 8, // size in bits
|
|
layout.payload_align * 8, // align in bits
|
|
payload_offset * 8, // offset in bits
|
|
0, // flags
|
|
union_di_ty,
|
|
);
|
|
|
|
const full_di_fields: [2]*llvm.DIType =
|
|
if (layout.tag_align >= layout.payload_align)
|
|
.{ tag_di, payload_di }
|
|
else
|
|
.{ payload_di, tag_di };
|
|
|
|
const full_di_ty = dib.createStructType(
|
|
compile_unit_scope,
|
|
name.ptr,
|
|
null, // file
|
|
0, // line
|
|
ty.abiSize(mod) * 8, // size in bits
|
|
ty.abiAlignment(mod) * 8, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
&full_di_fields,
|
|
full_di_fields.len,
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
dib.replaceTemporary(fwd_decl, full_di_ty);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(full_di_ty));
|
|
return full_di_ty;
|
|
},
|
|
.Fn => {
|
|
const fn_info = mod.typeToFunc(ty).?;
|
|
|
|
var param_di_types = std.ArrayList(*llvm.DIType).init(gpa);
|
|
defer param_di_types.deinit();
|
|
|
|
// Return type goes first.
|
|
if (fn_info.return_type.toType().hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const sret = firstParamSRet(fn_info, mod);
|
|
const di_ret_ty = if (sret) Type.void else fn_info.return_type.toType();
|
|
try param_di_types.append(try o.lowerDebugType(di_ret_ty, .full));
|
|
|
|
if (sret) {
|
|
const ptr_ty = try mod.singleMutPtrType(fn_info.return_type.toType());
|
|
try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
|
|
}
|
|
} else {
|
|
try param_di_types.append(try o.lowerDebugType(Type.void, .full));
|
|
}
|
|
|
|
if (fn_info.return_type.toType().isError(mod) and
|
|
o.module.comp.bin_file.options.error_return_tracing)
|
|
{
|
|
const ptr_ty = try mod.singleMutPtrType(try o.getStackTraceType());
|
|
try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
|
|
}
|
|
|
|
for (0..fn_info.param_types.len) |i| {
|
|
const param_ty = fn_info.param_types.get(ip)[i].toType();
|
|
if (!param_ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
const ptr_ty = try mod.singleMutPtrType(param_ty);
|
|
try param_di_types.append(try o.lowerDebugType(ptr_ty, .full));
|
|
} else {
|
|
try param_di_types.append(try o.lowerDebugType(param_ty, .full));
|
|
}
|
|
}
|
|
|
|
const fn_di_ty = dib.createSubroutineType(
|
|
param_di_types.items.ptr,
|
|
@as(c_int, @intCast(param_di_types.items.len)),
|
|
0,
|
|
);
|
|
// The recursive call to `lowerDebugType` means we can't use `gop` anymore.
|
|
try o.di_type_map.put(gpa, ty.toIntern(), AnnotatedDITypePtr.initFull(fn_di_ty));
|
|
return fn_di_ty;
|
|
},
|
|
.ComptimeInt => unreachable,
|
|
.ComptimeFloat => unreachable,
|
|
.Type => unreachable,
|
|
.Undefined => unreachable,
|
|
.Null => unreachable,
|
|
.EnumLiteral => unreachable,
|
|
|
|
.Frame => @panic("TODO implement lowerDebugType for Frame types"),
|
|
.AnyFrame => @panic("TODO implement lowerDebugType for AnyFrame types"),
|
|
}
|
|
}
|
|
|
|
fn namespaceToDebugScope(o: *Object, namespace_index: Module.Namespace.Index) !*llvm.DIScope {
|
|
const mod = o.module;
|
|
const namespace = mod.namespacePtr(namespace_index);
|
|
if (namespace.parent == .none) {
|
|
const di_file = try o.getDIFile(o.gpa, namespace.file_scope);
|
|
return di_file.toScope();
|
|
}
|
|
const di_type = try o.lowerDebugType(namespace.ty, .fwd);
|
|
return di_type.toScope();
|
|
}
|
|
|
|
/// This is to be used instead of void for debug info types, to avoid tripping
|
|
/// Assertion `!isa<DIType>(Scope) && "shouldn't make a namespace scope for a type"'
|
|
/// when targeting CodeView (Windows).
|
|
fn makeEmptyNamespaceDIType(o: *Object, decl_index: Module.Decl.Index) !*llvm.DIType {
|
|
const mod = o.module;
|
|
const decl = mod.declPtr(decl_index);
|
|
const fields: [0]*llvm.DIType = .{};
|
|
const di_scope = try o.namespaceToDebugScope(decl.src_namespace);
|
|
return o.di_builder.?.createStructType(
|
|
di_scope,
|
|
mod.intern_pool.stringToSlice(decl.name), // TODO use fully qualified name
|
|
try o.getDIFile(o.gpa, mod.namespacePtr(decl.src_namespace).file_scope),
|
|
decl.src_line + 1,
|
|
0, // size in bits
|
|
0, // align in bits
|
|
0, // flags
|
|
null, // derived from
|
|
undefined, // TODO should be able to pass &fields,
|
|
fields.len,
|
|
0, // run time lang
|
|
null, // vtable holder
|
|
"", // unique id
|
|
);
|
|
}
|
|
|
|
fn getStackTraceType(o: *Object) Allocator.Error!Type {
|
|
const mod = o.module;
|
|
|
|
const std_pkg = mod.main_pkg.table.get("std").?;
|
|
const std_file = (mod.importPkg(std_pkg) catch unreachable).file;
|
|
|
|
const builtin_str = try mod.intern_pool.getOrPutString(mod.gpa, "builtin");
|
|
const std_namespace = mod.namespacePtr(mod.declPtr(std_file.root_decl.unwrap().?).src_namespace);
|
|
const builtin_decl = std_namespace.decls
|
|
.getKeyAdapted(builtin_str, Module.DeclAdapter{ .mod = mod }).?;
|
|
|
|
const stack_trace_str = try mod.intern_pool.getOrPutString(mod.gpa, "StackTrace");
|
|
// buffer is only used for int_type, `builtin` is a struct.
|
|
const builtin_ty = mod.declPtr(builtin_decl).val.toType();
|
|
const builtin_namespace = builtin_ty.getNamespace(mod).?;
|
|
const stack_trace_decl_index = builtin_namespace.decls
|
|
.getKeyAdapted(stack_trace_str, Module.DeclAdapter{ .mod = mod }).?;
|
|
const stack_trace_decl = mod.declPtr(stack_trace_decl_index);
|
|
|
|
// Sema should have ensured that StackTrace was analyzed.
|
|
assert(stack_trace_decl.has_tv);
|
|
return stack_trace_decl.val.toType();
|
|
}
|
|
|
|
fn allocTypeName(o: *Object, ty: Type) Allocator.Error![:0]const u8 {
|
|
var buffer = std.ArrayList(u8).init(o.gpa);
|
|
errdefer buffer.deinit();
|
|
try ty.print(buffer.writer(), o.module);
|
|
return buffer.toOwnedSliceSentinel(0);
|
|
}
|
|
|
|
fn getNullOptAddr(o: *Object) !*llvm.Value {
|
|
if (o.null_opt_addr) |global| return global;
|
|
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
const ty = try mod.intern(.{ .opt_type = .usize_type });
|
|
const null_opt_usize = try mod.intern(.{ .opt = .{
|
|
.ty = ty,
|
|
.val = .none,
|
|
} });
|
|
|
|
const llvm_init = try o.lowerValue(.{
|
|
.ty = ty.toType(),
|
|
.val = null_opt_usize.toValue(),
|
|
});
|
|
const llvm_wanted_addrspace = toLlvmAddressSpace(.generic, target);
|
|
const llvm_actual_addrspace = toLlvmGlobalAddressSpace(.generic, target);
|
|
const global = o.llvm_module.addGlobalInAddressSpace(
|
|
llvm_init.typeOf(),
|
|
"",
|
|
llvm_actual_addrspace,
|
|
);
|
|
global.setLinkage(.Internal);
|
|
global.setUnnamedAddr(.True);
|
|
global.setAlignment(ty.toType().abiAlignment(mod));
|
|
global.setInitializer(llvm_init);
|
|
|
|
const addrspace_casted_global = if (llvm_wanted_addrspace != llvm_actual_addrspace)
|
|
global.constAddrSpaceCast(o.context.pointerType(llvm_wanted_addrspace))
|
|
else
|
|
global;
|
|
|
|
o.null_opt_addr = addrspace_casted_global;
|
|
return addrspace_casted_global;
|
|
}
|
|
|
|
/// If the llvm function does not exist, create it.
|
|
/// Note that this can be called before the function's semantic analysis has
|
|
/// completed, so if any attributes rely on that, they must be done in updateFunc, not here.
|
|
fn resolveLlvmFunction(o: *Object, decl_index: Module.Decl.Index) !*llvm.Value {
|
|
const mod = o.module;
|
|
const gpa = o.gpa;
|
|
const decl = mod.declPtr(decl_index);
|
|
const zig_fn_type = decl.ty;
|
|
const gop = try o.decl_map.getOrPut(gpa, decl_index);
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
|
|
assert(decl.has_tv);
|
|
const fn_info = mod.typeToFunc(zig_fn_type).?;
|
|
const target = mod.getTarget();
|
|
const sret = firstParamSRet(fn_info, mod);
|
|
|
|
const fn_type = try o.lowerType(zig_fn_type);
|
|
|
|
const fqn = try decl.getFullyQualifiedName(mod);
|
|
const ip = &mod.intern_pool;
|
|
|
|
const llvm_addrspace = toLlvmAddressSpace(decl.@"addrspace", target);
|
|
const llvm_fn = o.llvm_module.addFunctionInAddressSpace(ip.stringToSlice(fqn), fn_type, llvm_addrspace);
|
|
gop.value_ptr.* = llvm_fn;
|
|
|
|
const is_extern = decl.isExtern(mod);
|
|
if (!is_extern) {
|
|
llvm_fn.setLinkage(.Internal);
|
|
llvm_fn.setUnnamedAddr(.True);
|
|
} else {
|
|
if (target.isWasm()) {
|
|
o.addFnAttrString(llvm_fn, "wasm-import-name", ip.stringToSlice(decl.name));
|
|
if (ip.stringToSliceUnwrap(decl.getOwnedExternFunc(mod).?.lib_name)) |lib_name| {
|
|
if (!std.mem.eql(u8, lib_name, "c")) {
|
|
o.addFnAttrString(llvm_fn, "wasm-import-module", lib_name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sret) {
|
|
o.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0
|
|
o.addArgAttr(llvm_fn, 0, "noalias");
|
|
|
|
const raw_llvm_ret_ty = try o.lowerType(fn_info.return_type.toType());
|
|
llvm_fn.addSretAttr(raw_llvm_ret_ty);
|
|
}
|
|
|
|
const err_return_tracing = fn_info.return_type.toType().isError(mod) and
|
|
mod.comp.bin_file.options.error_return_tracing;
|
|
|
|
if (err_return_tracing) {
|
|
o.addArgAttr(llvm_fn, @intFromBool(sret), "nonnull");
|
|
}
|
|
|
|
switch (fn_info.cc) {
|
|
.Unspecified, .Inline => {
|
|
llvm_fn.setFunctionCallConv(.Fast);
|
|
},
|
|
.Naked => {
|
|
o.addFnAttr(llvm_fn, "naked");
|
|
},
|
|
.Async => {
|
|
llvm_fn.setFunctionCallConv(.Fast);
|
|
@panic("TODO: LLVM backend lower async function");
|
|
},
|
|
else => {
|
|
llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target));
|
|
},
|
|
}
|
|
|
|
if (fn_info.alignment.toByteUnitsOptional()) |a| {
|
|
llvm_fn.setAlignment(@as(c_uint, @intCast(a)));
|
|
}
|
|
|
|
// Function attributes that are independent of analysis results of the function body.
|
|
o.addCommonFnAttributes(llvm_fn);
|
|
|
|
if (fn_info.return_type == .noreturn_type) {
|
|
o.addFnAttr(llvm_fn, "noreturn");
|
|
}
|
|
|
|
// Add parameter attributes. We handle only the case of extern functions (no body)
|
|
// because functions with bodies are handled in `updateFunc`.
|
|
if (is_extern) {
|
|
var it = iterateParamTypes(o, fn_info);
|
|
it.llvm_index += @intFromBool(sret);
|
|
it.llvm_index += @intFromBool(err_return_tracing);
|
|
while (it.next()) |lowering| switch (lowering) {
|
|
.byval => {
|
|
const param_index = it.zig_index - 1;
|
|
const param_ty = fn_info.param_types.get(ip)[param_index].toType();
|
|
if (!isByRef(param_ty, mod)) {
|
|
o.addByValParamAttrs(llvm_fn, param_ty, param_index, fn_info, it.llvm_index - 1);
|
|
}
|
|
},
|
|
.byref => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1];
|
|
const param_llvm_ty = try o.lowerType(param_ty.toType());
|
|
const alignment = param_ty.toType().abiAlignment(mod);
|
|
o.addByRefParamAttrs(llvm_fn, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
|
|
},
|
|
.byref_mut => {
|
|
o.addArgAttr(llvm_fn, it.llvm_index - 1, "noundef");
|
|
},
|
|
// No attributes needed for these.
|
|
.no_bits,
|
|
.abi_sized_int,
|
|
.multiple_llvm_types,
|
|
.as_u16,
|
|
.float_array,
|
|
.i32_array,
|
|
.i64_array,
|
|
=> continue,
|
|
|
|
.slice => unreachable, // extern functions do not support slice types.
|
|
|
|
};
|
|
}
|
|
|
|
return llvm_fn;
|
|
}
|
|
|
|
fn addCommonFnAttributes(o: *Object, llvm_fn: *llvm.Value) void {
|
|
const comp = o.module.comp;
|
|
|
|
if (!comp.bin_file.options.red_zone) {
|
|
o.addFnAttr(llvm_fn, "noredzone");
|
|
}
|
|
if (comp.bin_file.options.omit_frame_pointer) {
|
|
o.addFnAttrString(llvm_fn, "frame-pointer", "none");
|
|
} else {
|
|
o.addFnAttrString(llvm_fn, "frame-pointer", "all");
|
|
}
|
|
o.addFnAttr(llvm_fn, "nounwind");
|
|
if (comp.unwind_tables) {
|
|
o.addFnAttrInt(llvm_fn, "uwtable", 2);
|
|
}
|
|
if (comp.bin_file.options.skip_linker_dependencies or
|
|
comp.bin_file.options.no_builtin)
|
|
{
|
|
// 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.
|
|
o.addFnAttr(llvm_fn, "nobuiltin");
|
|
}
|
|
if (comp.bin_file.options.optimize_mode == .ReleaseSmall) {
|
|
o.addFnAttr(llvm_fn, "minsize");
|
|
o.addFnAttr(llvm_fn, "optsize");
|
|
}
|
|
if (comp.bin_file.options.tsan) {
|
|
o.addFnAttr(llvm_fn, "sanitize_thread");
|
|
}
|
|
if (comp.getTarget().cpu.model.llvm_name) |s| {
|
|
llvm_fn.addFunctionAttr("target-cpu", s);
|
|
}
|
|
if (comp.bin_file.options.llvm_cpu_features) |s| {
|
|
llvm_fn.addFunctionAttr("target-features", s);
|
|
}
|
|
if (comp.getTarget().cpu.arch.isBpf()) {
|
|
llvm_fn.addFunctionAttr("no-builtins", "");
|
|
}
|
|
}
|
|
|
|
fn resolveGlobalDecl(o: *Object, decl_index: Module.Decl.Index) Error!*llvm.Value {
|
|
const gop = try o.decl_map.getOrPut(o.gpa, decl_index);
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
errdefer assert(o.decl_map.remove(decl_index));
|
|
|
|
const mod = o.module;
|
|
const decl = mod.declPtr(decl_index);
|
|
const fqn = try decl.getFullyQualifiedName(mod);
|
|
|
|
const target = mod.getTarget();
|
|
|
|
const llvm_type = try o.lowerType(decl.ty);
|
|
const llvm_actual_addrspace = toLlvmGlobalAddressSpace(decl.@"addrspace", target);
|
|
|
|
const llvm_global = o.llvm_module.addGlobalInAddressSpace(
|
|
llvm_type,
|
|
mod.intern_pool.stringToSlice(fqn),
|
|
llvm_actual_addrspace,
|
|
);
|
|
gop.value_ptr.* = llvm_global;
|
|
|
|
// This is needed for declarations created by `@extern`.
|
|
if (decl.isExtern(mod)) {
|
|
llvm_global.setValueName(mod.intern_pool.stringToSlice(decl.name));
|
|
llvm_global.setUnnamedAddr(.False);
|
|
llvm_global.setLinkage(.External);
|
|
if (decl.val.getVariable(mod)) |variable| {
|
|
const single_threaded = mod.comp.bin_file.options.single_threaded;
|
|
if (variable.is_threadlocal and !single_threaded) {
|
|
llvm_global.setThreadLocalMode(.GeneralDynamicTLSModel);
|
|
} else {
|
|
llvm_global.setThreadLocalMode(.NotThreadLocal);
|
|
}
|
|
if (variable.is_weak_linkage) llvm_global.setLinkage(.ExternalWeak);
|
|
}
|
|
} else {
|
|
llvm_global.setLinkage(.Internal);
|
|
llvm_global.setUnnamedAddr(.True);
|
|
}
|
|
|
|
return llvm_global;
|
|
}
|
|
|
|
fn isUnnamedType(o: *Object, ty: Type, val: *llvm.Value) bool {
|
|
// Once `lowerType` succeeds, successive calls to it with the same Zig type
|
|
// are guaranteed to succeed. So if a call to `lowerType` fails here it means
|
|
// it is the first time lowering the type, which means the value can't possible
|
|
// have that type.
|
|
const llvm_ty = o.lowerType(ty) catch return true;
|
|
return val.typeOf() != llvm_ty;
|
|
}
|
|
|
|
fn lowerType(o: *Object, t: Type) Allocator.Error!*llvm.Type {
|
|
const llvm_ty = try lowerTypeInner(o, t);
|
|
const mod = o.module;
|
|
if (std.debug.runtime_safety and false) check: {
|
|
if (t.zigTypeTag(mod) == .Opaque) break :check;
|
|
if (!t.hasRuntimeBits(mod)) break :check;
|
|
if (!llvm_ty.isSized().toBool()) break :check;
|
|
|
|
const zig_size = t.abiSize(mod);
|
|
const llvm_size = o.target_data.abiSizeOfType(llvm_ty);
|
|
if (llvm_size != zig_size) {
|
|
log.err("when lowering {}, Zig ABI size = {d} but LLVM ABI size = {d}", .{
|
|
t.fmt(o.module), zig_size, llvm_size,
|
|
});
|
|
}
|
|
}
|
|
return llvm_ty;
|
|
}
|
|
|
|
fn lowerTypeInner(o: *Object, t: Type) Allocator.Error!*llvm.Type {
|
|
const gpa = o.gpa;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
switch (t.zigTypeTag(mod)) {
|
|
.Void, .NoReturn => return o.context.voidType(),
|
|
.Int => {
|
|
const info = t.intInfo(mod);
|
|
assert(info.bits != 0);
|
|
return o.context.intType(info.bits);
|
|
},
|
|
.Enum => {
|
|
const int_ty = t.intTagType(mod);
|
|
const bit_count = int_ty.intInfo(mod).bits;
|
|
assert(bit_count != 0);
|
|
return o.context.intType(bit_count);
|
|
},
|
|
.Float => switch (t.floatBits(target)) {
|
|
16 => return if (backendSupportsF16(target)) o.context.halfType() else o.context.intType(16),
|
|
32 => return o.context.floatType(),
|
|
64 => return o.context.doubleType(),
|
|
80 => return if (backendSupportsF80(target)) o.context.x86FP80Type() else o.context.intType(80),
|
|
128 => return o.context.fp128Type(),
|
|
else => unreachable,
|
|
},
|
|
.Bool => return o.context.intType(1),
|
|
.Pointer => {
|
|
if (t.isSlice(mod)) {
|
|
const ptr_type = t.slicePtrFieldType(mod);
|
|
|
|
const fields: [2]*llvm.Type = .{
|
|
try o.lowerType(ptr_type),
|
|
try o.lowerType(Type.usize),
|
|
};
|
|
return o.context.structType(&fields, fields.len, .False);
|
|
}
|
|
const ptr_info = t.ptrInfo(mod);
|
|
const llvm_addrspace = toLlvmAddressSpace(ptr_info.flags.address_space, target);
|
|
return o.context.pointerType(llvm_addrspace);
|
|
},
|
|
.Opaque => {
|
|
if (t.toIntern() == .anyopaque_type) return o.context.intType(8);
|
|
|
|
const gop = try o.type_map.getOrPut(gpa, t.toIntern());
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
|
|
const opaque_type = mod.intern_pool.indexToKey(t.toIntern()).opaque_type;
|
|
const name = mod.intern_pool.stringToSlice(try mod.opaqueFullyQualifiedName(opaque_type));
|
|
|
|
const llvm_struct_ty = o.context.structCreateNamed(name);
|
|
gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
|
|
return llvm_struct_ty;
|
|
},
|
|
.Array => {
|
|
const elem_ty = t.childType(mod);
|
|
if (std.debug.runtime_safety) assert((try elem_ty.onePossibleValue(mod)) == null);
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
const total_len = t.arrayLen(mod) + @intFromBool(t.sentinel(mod) != null);
|
|
return elem_llvm_ty.arrayType(@as(c_uint, @intCast(total_len)));
|
|
},
|
|
.Vector => {
|
|
const elem_type = try o.lowerType(t.childType(mod));
|
|
return elem_type.vectorType(t.vectorLen(mod));
|
|
},
|
|
.Optional => {
|
|
const child_ty = t.optionalChild(mod);
|
|
if (!child_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return o.context.intType(8);
|
|
}
|
|
const payload_llvm_ty = try o.lowerType(child_ty);
|
|
if (t.optionalReprIsPayload(mod)) {
|
|
return payload_llvm_ty;
|
|
}
|
|
|
|
comptime assert(optional_layout_version == 3);
|
|
var fields_buf: [3]*llvm.Type = .{
|
|
payload_llvm_ty, o.context.intType(8), undefined,
|
|
};
|
|
const offset = child_ty.abiSize(mod) + 1;
|
|
const abi_size = t.abiSize(mod);
|
|
const padding = @as(c_uint, @intCast(abi_size - offset));
|
|
if (padding == 0) {
|
|
return o.context.structType(&fields_buf, 2, .False);
|
|
}
|
|
fields_buf[2] = o.context.intType(8).arrayType(padding);
|
|
return o.context.structType(&fields_buf, 3, .False);
|
|
},
|
|
.ErrorUnion => {
|
|
const payload_ty = t.errorUnionPayload(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return try o.lowerType(Type.anyerror);
|
|
}
|
|
const llvm_error_type = try o.lowerType(Type.anyerror);
|
|
const llvm_payload_type = try o.lowerType(payload_ty);
|
|
|
|
const payload_align = payload_ty.abiAlignment(mod);
|
|
const error_align = Type.anyerror.abiAlignment(mod);
|
|
|
|
const payload_size = payload_ty.abiSize(mod);
|
|
const error_size = Type.anyerror.abiSize(mod);
|
|
|
|
var fields_buf: [3]*llvm.Type = undefined;
|
|
if (error_align > payload_align) {
|
|
fields_buf[0] = llvm_error_type;
|
|
fields_buf[1] = llvm_payload_type;
|
|
const payload_end =
|
|
std.mem.alignForward(u64, error_size, payload_align) +
|
|
payload_size;
|
|
const abi_size = std.mem.alignForward(u64, payload_end, error_align);
|
|
const padding = @as(c_uint, @intCast(abi_size - payload_end));
|
|
if (padding == 0) {
|
|
return o.context.structType(&fields_buf, 2, .False);
|
|
}
|
|
fields_buf[2] = o.context.intType(8).arrayType(padding);
|
|
return o.context.structType(&fields_buf, 3, .False);
|
|
} else {
|
|
fields_buf[0] = llvm_payload_type;
|
|
fields_buf[1] = llvm_error_type;
|
|
const error_end =
|
|
std.mem.alignForward(u64, payload_size, error_align) +
|
|
error_size;
|
|
const abi_size = std.mem.alignForward(u64, error_end, payload_align);
|
|
const padding = @as(c_uint, @intCast(abi_size - error_end));
|
|
if (padding == 0) {
|
|
return o.context.structType(&fields_buf, 2, .False);
|
|
}
|
|
fields_buf[2] = o.context.intType(8).arrayType(padding);
|
|
return o.context.structType(&fields_buf, 3, .False);
|
|
}
|
|
},
|
|
.ErrorSet => return o.context.intType(16),
|
|
.Struct => {
|
|
const gop = try o.type_map.getOrPut(gpa, t.toIntern());
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
|
|
const struct_type = switch (mod.intern_pool.indexToKey(t.toIntern())) {
|
|
.anon_struct_type => |tuple| {
|
|
const llvm_struct_ty = o.context.structCreateNamed("");
|
|
gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
|
|
|
|
var llvm_field_types: std.ArrayListUnmanaged(*llvm.Type) = .{};
|
|
defer llvm_field_types.deinit(gpa);
|
|
|
|
try llvm_field_types.ensureUnusedCapacity(gpa, tuple.types.len);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var big_align: u32 = 0;
|
|
|
|
for (tuple.types, tuple.values) |field_ty, field_val| {
|
|
if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
|
|
|
|
const field_align = field_ty.toType().abiAlignment(mod);
|
|
big_align = @max(big_align, field_align);
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, field_align);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
try llvm_field_types.append(gpa, llvm_array_ty);
|
|
}
|
|
const field_llvm_ty = try o.lowerType(field_ty.toType());
|
|
try llvm_field_types.append(gpa, field_llvm_ty);
|
|
|
|
offset += field_ty.toType().abiSize(mod);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, big_align);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
try llvm_field_types.append(gpa, llvm_array_ty);
|
|
}
|
|
}
|
|
|
|
llvm_struct_ty.structSetBody(
|
|
llvm_field_types.items.ptr,
|
|
@as(c_uint, @intCast(llvm_field_types.items.len)),
|
|
.False,
|
|
);
|
|
|
|
return llvm_struct_ty;
|
|
},
|
|
.struct_type => |struct_type| struct_type,
|
|
else => unreachable,
|
|
};
|
|
|
|
const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
|
|
|
|
if (struct_obj.layout == .Packed) {
|
|
assert(struct_obj.haveLayout());
|
|
const int_llvm_ty = try o.lowerType(struct_obj.backing_int_ty);
|
|
gop.value_ptr.* = int_llvm_ty;
|
|
return int_llvm_ty;
|
|
}
|
|
|
|
const name = mod.intern_pool.stringToSlice(try struct_obj.getFullyQualifiedName(mod));
|
|
|
|
const llvm_struct_ty = o.context.structCreateNamed(name);
|
|
gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
|
|
|
|
assert(struct_obj.haveFieldTypes());
|
|
|
|
var llvm_field_types: std.ArrayListUnmanaged(*llvm.Type) = .{};
|
|
defer llvm_field_types.deinit(gpa);
|
|
|
|
try llvm_field_types.ensureUnusedCapacity(gpa, struct_obj.fields.count());
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var big_align: u32 = 1;
|
|
var any_underaligned_fields = false;
|
|
|
|
var it = struct_obj.runtimeFieldIterator(mod);
|
|
while (it.next()) |field_and_index| {
|
|
const field = field_and_index.field;
|
|
const field_align = field.alignment(mod, struct_obj.layout);
|
|
const field_ty_align = field.ty.abiAlignment(mod);
|
|
any_underaligned_fields = any_underaligned_fields or
|
|
field_align < field_ty_align;
|
|
big_align = @max(big_align, field_align);
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, field_align);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
try llvm_field_types.append(gpa, llvm_array_ty);
|
|
}
|
|
const field_llvm_ty = try o.lowerType(field.ty);
|
|
try llvm_field_types.append(gpa, field_llvm_ty);
|
|
|
|
offset += field.ty.abiSize(mod);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, big_align);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
try llvm_field_types.append(gpa, llvm_array_ty);
|
|
}
|
|
}
|
|
|
|
llvm_struct_ty.structSetBody(
|
|
llvm_field_types.items.ptr,
|
|
@as(c_uint, @intCast(llvm_field_types.items.len)),
|
|
llvm.Bool.fromBool(any_underaligned_fields),
|
|
);
|
|
|
|
return llvm_struct_ty;
|
|
},
|
|
.Union => {
|
|
const gop = try o.type_map.getOrPut(gpa, t.toIntern());
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
|
|
const layout = t.unionGetLayout(mod);
|
|
const union_obj = mod.typeToUnion(t).?;
|
|
|
|
if (union_obj.layout == .Packed) {
|
|
const bitsize = @as(c_uint, @intCast(t.bitSize(mod)));
|
|
const int_llvm_ty = o.context.intType(bitsize);
|
|
gop.value_ptr.* = int_llvm_ty;
|
|
return int_llvm_ty;
|
|
}
|
|
|
|
if (layout.payload_size == 0) {
|
|
const enum_tag_llvm_ty = try o.lowerType(union_obj.tag_ty);
|
|
gop.value_ptr.* = enum_tag_llvm_ty;
|
|
return enum_tag_llvm_ty;
|
|
}
|
|
|
|
const name = mod.intern_pool.stringToSlice(try union_obj.getFullyQualifiedName(mod));
|
|
|
|
const llvm_union_ty = o.context.structCreateNamed(name);
|
|
gop.value_ptr.* = llvm_union_ty; // must be done before any recursive calls
|
|
|
|
const aligned_field = union_obj.fields.values()[layout.most_aligned_field];
|
|
const llvm_aligned_field_ty = try o.lowerType(aligned_field.ty);
|
|
|
|
const llvm_payload_ty = t: {
|
|
if (layout.most_aligned_field_size == layout.payload_size) {
|
|
break :t llvm_aligned_field_ty;
|
|
}
|
|
const padding_len = if (layout.tag_size == 0)
|
|
@as(c_uint, @intCast(layout.abi_size - layout.most_aligned_field_size))
|
|
else
|
|
@as(c_uint, @intCast(layout.payload_size - layout.most_aligned_field_size));
|
|
const fields: [2]*llvm.Type = .{
|
|
llvm_aligned_field_ty,
|
|
o.context.intType(8).arrayType(padding_len),
|
|
};
|
|
break :t o.context.structType(&fields, fields.len, .True);
|
|
};
|
|
|
|
if (layout.tag_size == 0) {
|
|
var llvm_fields: [1]*llvm.Type = .{llvm_payload_ty};
|
|
llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False);
|
|
return llvm_union_ty;
|
|
}
|
|
const enum_tag_llvm_ty = try o.lowerType(union_obj.tag_ty);
|
|
|
|
// Put the tag before or after the payload depending on which one's
|
|
// alignment is greater.
|
|
var llvm_fields: [3]*llvm.Type = undefined;
|
|
var llvm_fields_len: c_uint = 2;
|
|
|
|
if (layout.tag_align >= layout.payload_align) {
|
|
llvm_fields = .{ enum_tag_llvm_ty, llvm_payload_ty, undefined };
|
|
} else {
|
|
llvm_fields = .{ llvm_payload_ty, enum_tag_llvm_ty, undefined };
|
|
}
|
|
|
|
// Insert padding to make the LLVM struct ABI size match the Zig union ABI size.
|
|
if (layout.padding != 0) {
|
|
llvm_fields[2] = o.context.intType(8).arrayType(layout.padding);
|
|
llvm_fields_len = 3;
|
|
}
|
|
|
|
llvm_union_ty.structSetBody(&llvm_fields, llvm_fields_len, .False);
|
|
return llvm_union_ty;
|
|
},
|
|
.Fn => return lowerTypeFn(o, t),
|
|
.ComptimeInt => unreachable,
|
|
.ComptimeFloat => unreachable,
|
|
.Type => unreachable,
|
|
.Undefined => unreachable,
|
|
.Null => unreachable,
|
|
.EnumLiteral => unreachable,
|
|
|
|
.Frame => @panic("TODO implement llvmType for Frame types"),
|
|
.AnyFrame => @panic("TODO implement llvmType for AnyFrame types"),
|
|
}
|
|
}
|
|
|
|
fn lowerTypeFn(o: *Object, fn_ty: Type) Allocator.Error!*llvm.Type {
|
|
const mod = o.module;
|
|
const ip = &mod.intern_pool;
|
|
const fn_info = mod.typeToFunc(fn_ty).?;
|
|
const llvm_ret_ty = try lowerFnRetTy(o, fn_info);
|
|
|
|
var llvm_params = std.ArrayList(*llvm.Type).init(o.gpa);
|
|
defer llvm_params.deinit();
|
|
|
|
if (firstParamSRet(fn_info, mod)) {
|
|
try llvm_params.append(o.context.pointerType(0));
|
|
}
|
|
|
|
if (fn_info.return_type.toType().isError(mod) and
|
|
mod.comp.bin_file.options.error_return_tracing)
|
|
{
|
|
const ptr_ty = try mod.singleMutPtrType(try o.getStackTraceType());
|
|
try llvm_params.append(try o.lowerType(ptr_ty));
|
|
}
|
|
|
|
var it = iterateParamTypes(o, fn_info);
|
|
while (it.next()) |lowering| switch (lowering) {
|
|
.no_bits => continue,
|
|
.byval => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
try llvm_params.append(try o.lowerType(param_ty));
|
|
},
|
|
.byref, .byref_mut => {
|
|
try llvm_params.append(o.context.pointerType(0));
|
|
},
|
|
.abi_sized_int => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const abi_size = @as(c_uint, @intCast(param_ty.abiSize(mod)));
|
|
try llvm_params.append(o.context.intType(abi_size * 8));
|
|
},
|
|
.slice => {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const ptr_ty = if (param_ty.zigTypeTag(mod) == .Optional)
|
|
param_ty.optionalChild(mod).slicePtrFieldType(mod)
|
|
else
|
|
param_ty.slicePtrFieldType(mod);
|
|
const ptr_llvm_ty = try o.lowerType(ptr_ty);
|
|
const len_llvm_ty = try o.lowerType(Type.usize);
|
|
|
|
try llvm_params.ensureUnusedCapacity(2);
|
|
llvm_params.appendAssumeCapacity(ptr_llvm_ty);
|
|
llvm_params.appendAssumeCapacity(len_llvm_ty);
|
|
},
|
|
.multiple_llvm_types => {
|
|
try llvm_params.appendSlice(it.llvm_types_buffer[0..it.llvm_types_len]);
|
|
},
|
|
.as_u16 => {
|
|
try llvm_params.append(o.context.intType(16));
|
|
},
|
|
.float_array => |count| {
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const float_ty = try o.lowerType(aarch64_c_abi.getFloatArrayType(param_ty, mod).?);
|
|
const field_count = @as(c_uint, @intCast(count));
|
|
const arr_ty = float_ty.arrayType(field_count);
|
|
try llvm_params.append(arr_ty);
|
|
},
|
|
.i32_array, .i64_array => |arr_len| {
|
|
const elem_size: u8 = if (lowering == .i32_array) 32 else 64;
|
|
const arr_ty = o.context.intType(elem_size).arrayType(arr_len);
|
|
try llvm_params.append(arr_ty);
|
|
},
|
|
};
|
|
|
|
return llvm.functionType(
|
|
llvm_ret_ty,
|
|
llvm_params.items.ptr,
|
|
@as(c_uint, @intCast(llvm_params.items.len)),
|
|
llvm.Bool.fromBool(fn_info.is_var_args),
|
|
);
|
|
}
|
|
|
|
/// Use this instead of lowerType when you want to handle correctly the case of elem_ty
|
|
/// being a zero bit type, but it should still be lowered as an i8 in such case.
|
|
/// There are other similar cases handled here as well.
|
|
fn lowerPtrElemTy(o: *Object, elem_ty: Type) Allocator.Error!*llvm.Type {
|
|
const mod = o.module;
|
|
const lower_elem_ty = switch (elem_ty.zigTypeTag(mod)) {
|
|
.Opaque => true,
|
|
.Fn => !mod.typeToFunc(elem_ty).?.is_generic,
|
|
.Array => elem_ty.childType(mod).hasRuntimeBitsIgnoreComptime(mod),
|
|
else => elem_ty.hasRuntimeBitsIgnoreComptime(mod),
|
|
};
|
|
const llvm_elem_ty = if (lower_elem_ty)
|
|
try o.lowerType(elem_ty)
|
|
else
|
|
o.context.intType(8);
|
|
|
|
return llvm_elem_ty;
|
|
}
|
|
|
|
fn lowerValue(o: *Object, arg_tv: TypedValue) Error!*llvm.Value {
|
|
const mod = o.module;
|
|
const gpa = o.gpa;
|
|
const target = mod.getTarget();
|
|
var tv = arg_tv;
|
|
switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
|
|
.runtime_value => |rt| tv.val = rt.val.toValue(),
|
|
else => {},
|
|
}
|
|
if (tv.val.isUndefDeep(mod)) {
|
|
const llvm_type = try o.lowerType(tv.ty);
|
|
return llvm_type.getUndef();
|
|
}
|
|
|
|
switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
|
|
.int_type,
|
|
.ptr_type,
|
|
.array_type,
|
|
.vector_type,
|
|
.opt_type,
|
|
.anyframe_type,
|
|
.error_union_type,
|
|
.simple_type,
|
|
.struct_type,
|
|
.anon_struct_type,
|
|
.union_type,
|
|
.opaque_type,
|
|
.enum_type,
|
|
.func_type,
|
|
.error_set_type,
|
|
.inferred_error_set_type,
|
|
=> unreachable, // types, not values
|
|
|
|
.undef, .runtime_value => unreachable, // handled above
|
|
.simple_value => |simple_value| switch (simple_value) {
|
|
.undefined,
|
|
.void,
|
|
.null,
|
|
.empty_struct,
|
|
.@"unreachable",
|
|
.generic_poison,
|
|
=> unreachable, // non-runtime values
|
|
.false, .true => {
|
|
const llvm_type = try o.lowerType(tv.ty);
|
|
return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull();
|
|
},
|
|
},
|
|
.variable,
|
|
.enum_literal,
|
|
.empty_enum_value,
|
|
=> unreachable, // non-runtime values
|
|
.extern_func => |extern_func| {
|
|
const fn_decl_index = extern_func.decl;
|
|
const fn_decl = mod.declPtr(fn_decl_index);
|
|
try mod.markDeclAlive(fn_decl);
|
|
return o.resolveLlvmFunction(fn_decl_index);
|
|
},
|
|
.func => |func| {
|
|
const fn_decl_index = func.owner_decl;
|
|
const fn_decl = mod.declPtr(fn_decl_index);
|
|
try mod.markDeclAlive(fn_decl);
|
|
return o.resolveLlvmFunction(fn_decl_index);
|
|
},
|
|
.int => {
|
|
var bigint_space: Value.BigIntSpace = undefined;
|
|
const bigint = tv.val.toBigInt(&bigint_space, mod);
|
|
return lowerBigInt(o, tv.ty, bigint);
|
|
},
|
|
.err => |err| {
|
|
const llvm_ty = try o.lowerType(Type.anyerror);
|
|
const int = try mod.getErrorValue(err.name);
|
|
return llvm_ty.constInt(int, .False);
|
|
},
|
|
.error_union => |error_union| {
|
|
const err_tv: TypedValue = switch (error_union.val) {
|
|
.err_name => |err_name| .{
|
|
.ty = tv.ty.errorUnionSet(mod),
|
|
.val = (try mod.intern(.{ .err = .{
|
|
.ty = tv.ty.errorUnionSet(mod).toIntern(),
|
|
.name = err_name,
|
|
} })).toValue(),
|
|
},
|
|
.payload => .{
|
|
.ty = Type.err_int,
|
|
.val = try mod.intValue(Type.err_int, 0),
|
|
},
|
|
};
|
|
const payload_type = tv.ty.errorUnionPayload(mod);
|
|
if (!payload_type.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
// We use the error type directly as the type.
|
|
return o.lowerValue(err_tv);
|
|
}
|
|
|
|
const payload_align = payload_type.abiAlignment(mod);
|
|
const error_align = err_tv.ty.abiAlignment(mod);
|
|
const llvm_error_value = try o.lowerValue(err_tv);
|
|
const llvm_payload_value = try o.lowerValue(.{
|
|
.ty = payload_type,
|
|
.val = switch (error_union.val) {
|
|
.err_name => try mod.intern(.{ .undef = payload_type.toIntern() }),
|
|
.payload => |payload| payload,
|
|
}.toValue(),
|
|
});
|
|
var fields_buf: [3]*llvm.Value = undefined;
|
|
|
|
const llvm_ty = try o.lowerType(tv.ty);
|
|
const llvm_field_count = llvm_ty.countStructElementTypes();
|
|
if (llvm_field_count > 2) {
|
|
assert(llvm_field_count == 3);
|
|
fields_buf[2] = llvm_ty.structGetTypeAtIndex(2).getUndef();
|
|
}
|
|
|
|
if (error_align > payload_align) {
|
|
fields_buf[0] = llvm_error_value;
|
|
fields_buf[1] = llvm_payload_value;
|
|
return o.context.constStruct(&fields_buf, llvm_field_count, .False);
|
|
} else {
|
|
fields_buf[0] = llvm_payload_value;
|
|
fields_buf[1] = llvm_error_value;
|
|
return o.context.constStruct(&fields_buf, llvm_field_count, .False);
|
|
}
|
|
},
|
|
.enum_tag => {
|
|
const int_val = try tv.intFromEnum(mod);
|
|
|
|
var bigint_space: Value.BigIntSpace = undefined;
|
|
const bigint = int_val.toBigInt(&bigint_space, mod);
|
|
|
|
const int_info = tv.ty.intInfo(mod);
|
|
const llvm_type = o.context.intType(int_info.bits);
|
|
|
|
const unsigned_val = v: {
|
|
if (bigint.limbs.len == 1) {
|
|
break :v llvm_type.constInt(bigint.limbs[0], .False);
|
|
}
|
|
if (@sizeOf(usize) == @sizeOf(u64)) {
|
|
break :v llvm_type.constIntOfArbitraryPrecision(
|
|
@as(c_uint, @intCast(bigint.limbs.len)),
|
|
bigint.limbs.ptr,
|
|
);
|
|
}
|
|
@panic("TODO implement bigint to llvm int for 32-bit compiler builds");
|
|
};
|
|
if (!bigint.positive) {
|
|
return llvm.constNeg(unsigned_val);
|
|
}
|
|
return unsigned_val;
|
|
},
|
|
.float => {
|
|
const llvm_ty = try o.lowerType(tv.ty);
|
|
switch (tv.ty.floatBits(target)) {
|
|
16 => {
|
|
const repr = @as(u16, @bitCast(tv.val.toFloat(f16, mod)));
|
|
const llvm_i16 = o.context.intType(16);
|
|
const int = llvm_i16.constInt(repr, .False);
|
|
return int.constBitCast(llvm_ty);
|
|
},
|
|
32 => {
|
|
const repr = @as(u32, @bitCast(tv.val.toFloat(f32, mod)));
|
|
const llvm_i32 = o.context.intType(32);
|
|
const int = llvm_i32.constInt(repr, .False);
|
|
return int.constBitCast(llvm_ty);
|
|
},
|
|
64 => {
|
|
const repr = @as(u64, @bitCast(tv.val.toFloat(f64, mod)));
|
|
const llvm_i64 = o.context.intType(64);
|
|
const int = llvm_i64.constInt(repr, .False);
|
|
return int.constBitCast(llvm_ty);
|
|
},
|
|
80 => {
|
|
const float = tv.val.toFloat(f80, mod);
|
|
const repr = std.math.break_f80(float);
|
|
const llvm_i80 = o.context.intType(80);
|
|
var x = llvm_i80.constInt(repr.exp, .False);
|
|
x = x.constShl(llvm_i80.constInt(64, .False));
|
|
x = x.constOr(llvm_i80.constInt(repr.fraction, .False));
|
|
if (backendSupportsF80(target)) {
|
|
return x.constBitCast(llvm_ty);
|
|
} else {
|
|
return x;
|
|
}
|
|
},
|
|
128 => {
|
|
var buf: [2]u64 = @as([2]u64, @bitCast(tv.val.toFloat(f128, mod)));
|
|
// LLVM seems to require that the lower half of the f128 be placed first
|
|
// in the buffer.
|
|
if (native_endian == .Big) {
|
|
std.mem.swap(u64, &buf[0], &buf[1]);
|
|
}
|
|
const int = o.context.intType(128).constIntOfArbitraryPrecision(buf.len, &buf);
|
|
return int.constBitCast(llvm_ty);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
.ptr => |ptr| {
|
|
const ptr_tv: TypedValue = switch (ptr.len) {
|
|
.none => tv,
|
|
else => .{ .ty = tv.ty.slicePtrFieldType(mod), .val = tv.val.slicePtr(mod) },
|
|
};
|
|
const llvm_ptr_val = switch (ptr.addr) {
|
|
.decl => |decl| try o.lowerDeclRefValue(ptr_tv, decl),
|
|
.mut_decl => |mut_decl| try o.lowerDeclRefValue(ptr_tv, mut_decl.decl),
|
|
.int => |int| try o.lowerIntAsPtr(int.toValue()),
|
|
.eu_payload,
|
|
.opt_payload,
|
|
.elem,
|
|
.field,
|
|
=> try o.lowerParentPtr(ptr_tv.val, ptr_tv.ty.ptrInfo(mod).packed_offset.bit_offset % 8 == 0),
|
|
.comptime_field => unreachable,
|
|
};
|
|
switch (ptr.len) {
|
|
.none => return llvm_ptr_val,
|
|
else => {
|
|
const fields: [2]*llvm.Value = .{
|
|
llvm_ptr_val,
|
|
try o.lowerValue(.{ .ty = Type.usize, .val = ptr.len.toValue() }),
|
|
};
|
|
return o.context.constStruct(&fields, fields.len, .False);
|
|
},
|
|
}
|
|
},
|
|
.opt => |opt| {
|
|
comptime assert(optional_layout_version == 3);
|
|
const payload_ty = tv.ty.optionalChild(mod);
|
|
|
|
const llvm_i8 = o.context.intType(8);
|
|
const non_null_bit = switch (opt.val) {
|
|
.none => llvm_i8.constNull(),
|
|
else => llvm_i8.constInt(1, .False),
|
|
};
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return non_null_bit;
|
|
}
|
|
const llvm_ty = try o.lowerType(tv.ty);
|
|
if (tv.ty.optionalReprIsPayload(mod)) return switch (opt.val) {
|
|
.none => llvm_ty.constNull(),
|
|
else => |payload| o.lowerValue(.{ .ty = payload_ty, .val = payload.toValue() }),
|
|
};
|
|
assert(payload_ty.zigTypeTag(mod) != .Fn);
|
|
|
|
const llvm_field_count = llvm_ty.countStructElementTypes();
|
|
var fields_buf: [3]*llvm.Value = undefined;
|
|
fields_buf[0] = try o.lowerValue(.{
|
|
.ty = payload_ty,
|
|
.val = switch (opt.val) {
|
|
.none => try mod.intern(.{ .undef = payload_ty.toIntern() }),
|
|
else => |payload| payload,
|
|
}.toValue(),
|
|
});
|
|
fields_buf[1] = non_null_bit;
|
|
if (llvm_field_count > 2) {
|
|
assert(llvm_field_count == 3);
|
|
fields_buf[2] = llvm_ty.structGetTypeAtIndex(2).getUndef();
|
|
}
|
|
return o.context.constStruct(&fields_buf, llvm_field_count, .False);
|
|
},
|
|
.aggregate => |aggregate| switch (mod.intern_pool.indexToKey(tv.ty.toIntern())) {
|
|
.array_type => switch (aggregate.storage) {
|
|
.bytes => |bytes| return o.context.constString(
|
|
bytes.ptr,
|
|
@as(c_uint, @intCast(tv.ty.arrayLenIncludingSentinel(mod))),
|
|
.True, // Don't null terminate. Bytes has the sentinel, if any.
|
|
),
|
|
.elems => |elem_vals| {
|
|
const elem_ty = tv.ty.childType(mod);
|
|
const llvm_elems = try gpa.alloc(*llvm.Value, elem_vals.len);
|
|
defer gpa.free(llvm_elems);
|
|
var need_unnamed = false;
|
|
for (elem_vals, 0..) |elem_val, i| {
|
|
llvm_elems[i] = try o.lowerValue(.{ .ty = elem_ty, .val = elem_val.toValue() });
|
|
need_unnamed = need_unnamed or o.isUnnamedType(elem_ty, llvm_elems[i]);
|
|
}
|
|
if (need_unnamed) {
|
|
return o.context.constStruct(
|
|
llvm_elems.ptr,
|
|
@as(c_uint, @intCast(llvm_elems.len)),
|
|
.True,
|
|
);
|
|
} else {
|
|
const llvm_elem_ty = try o.lowerType(elem_ty);
|
|
return llvm_elem_ty.constArray(
|
|
llvm_elems.ptr,
|
|
@as(c_uint, @intCast(llvm_elems.len)),
|
|
);
|
|
}
|
|
},
|
|
.repeated_elem => |val| {
|
|
const elem_ty = tv.ty.childType(mod);
|
|
const sentinel = tv.ty.sentinel(mod);
|
|
const len = @as(usize, @intCast(tv.ty.arrayLen(mod)));
|
|
const len_including_sent = len + @intFromBool(sentinel != null);
|
|
const llvm_elems = try gpa.alloc(*llvm.Value, len_including_sent);
|
|
defer gpa.free(llvm_elems);
|
|
|
|
var need_unnamed = false;
|
|
if (len != 0) {
|
|
for (llvm_elems[0..len]) |*elem| {
|
|
elem.* = try o.lowerValue(.{ .ty = elem_ty, .val = val.toValue() });
|
|
}
|
|
need_unnamed = need_unnamed or o.isUnnamedType(elem_ty, llvm_elems[0]);
|
|
}
|
|
|
|
if (sentinel) |sent| {
|
|
llvm_elems[len] = try o.lowerValue(.{ .ty = elem_ty, .val = sent });
|
|
need_unnamed = need_unnamed or o.isUnnamedType(elem_ty, llvm_elems[len]);
|
|
}
|
|
|
|
if (need_unnamed) {
|
|
return o.context.constStruct(
|
|
llvm_elems.ptr,
|
|
@as(c_uint, @intCast(llvm_elems.len)),
|
|
.True,
|
|
);
|
|
} else {
|
|
const llvm_elem_ty = try o.lowerType(elem_ty);
|
|
return llvm_elem_ty.constArray(
|
|
llvm_elems.ptr,
|
|
@as(c_uint, @intCast(llvm_elems.len)),
|
|
);
|
|
}
|
|
},
|
|
},
|
|
.vector_type => |vector_type| {
|
|
const elem_ty = vector_type.child.toType();
|
|
const llvm_elems = try gpa.alloc(*llvm.Value, vector_type.len);
|
|
defer gpa.free(llvm_elems);
|
|
const llvm_i8 = o.context.intType(8);
|
|
for (llvm_elems, 0..) |*llvm_elem, i| {
|
|
llvm_elem.* = switch (aggregate.storage) {
|
|
.bytes => |bytes| llvm_i8.constInt(bytes[i], .False),
|
|
.elems => |elems| try o.lowerValue(.{
|
|
.ty = elem_ty,
|
|
.val = elems[i].toValue(),
|
|
}),
|
|
.repeated_elem => |elem| try o.lowerValue(.{
|
|
.ty = elem_ty,
|
|
.val = elem.toValue(),
|
|
}),
|
|
};
|
|
}
|
|
return llvm.constVector(
|
|
llvm_elems.ptr,
|
|
@as(c_uint, @intCast(llvm_elems.len)),
|
|
);
|
|
},
|
|
.anon_struct_type => |tuple| {
|
|
var llvm_fields: std.ArrayListUnmanaged(*llvm.Value) = .{};
|
|
defer llvm_fields.deinit(gpa);
|
|
|
|
try llvm_fields.ensureUnusedCapacity(gpa, tuple.types.len);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var big_align: u32 = 0;
|
|
var need_unnamed = false;
|
|
|
|
for (tuple.types, tuple.values, 0..) |field_ty, field_val, i| {
|
|
if (field_val != .none) continue;
|
|
if (!field_ty.toType().hasRuntimeBitsIgnoreComptime(mod)) continue;
|
|
|
|
const field_align = field_ty.toType().abiAlignment(mod);
|
|
big_align = @max(big_align, field_align);
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, field_align);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
// TODO make this and all other padding elsewhere in debug
|
|
// builds be 0xaa not undef.
|
|
llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
|
|
}
|
|
|
|
const field_llvm_val = try o.lowerValue(.{
|
|
.ty = field_ty.toType(),
|
|
.val = try tv.val.fieldValue(mod, i),
|
|
});
|
|
|
|
need_unnamed = need_unnamed or o.isUnnamedType(field_ty.toType(), field_llvm_val);
|
|
|
|
llvm_fields.appendAssumeCapacity(field_llvm_val);
|
|
|
|
offset += field_ty.toType().abiSize(mod);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, big_align);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
|
|
}
|
|
}
|
|
|
|
if (need_unnamed) {
|
|
return o.context.constStruct(
|
|
llvm_fields.items.ptr,
|
|
@as(c_uint, @intCast(llvm_fields.items.len)),
|
|
.False,
|
|
);
|
|
} else {
|
|
const llvm_struct_ty = try o.lowerType(tv.ty);
|
|
return llvm_struct_ty.constNamedStruct(
|
|
llvm_fields.items.ptr,
|
|
@as(c_uint, @intCast(llvm_fields.items.len)),
|
|
);
|
|
}
|
|
},
|
|
.struct_type => |struct_type| {
|
|
const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
|
|
const llvm_struct_ty = try o.lowerType(tv.ty);
|
|
|
|
if (struct_obj.layout == .Packed) {
|
|
assert(struct_obj.haveLayout());
|
|
const big_bits = struct_obj.backing_int_ty.bitSize(mod);
|
|
const int_llvm_ty = o.context.intType(@as(c_uint, @intCast(big_bits)));
|
|
const fields = struct_obj.fields.values();
|
|
comptime assert(Type.packed_struct_layout_version == 2);
|
|
var running_int: *llvm.Value = int_llvm_ty.constNull();
|
|
var running_bits: u16 = 0;
|
|
for (fields, 0..) |field, i| {
|
|
if (!field.ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
|
|
|
|
const non_int_val = try o.lowerValue(.{
|
|
.ty = field.ty,
|
|
.val = try tv.val.fieldValue(mod, i),
|
|
});
|
|
const ty_bit_size = @as(u16, @intCast(field.ty.bitSize(mod)));
|
|
const small_int_ty = o.context.intType(ty_bit_size);
|
|
const small_int_val = if (field.ty.isPtrAtRuntime(mod))
|
|
non_int_val.constPtrToInt(small_int_ty)
|
|
else
|
|
non_int_val.constBitCast(small_int_ty);
|
|
const shift_rhs = int_llvm_ty.constInt(running_bits, .False);
|
|
// If the field is as large as the entire packed struct, this
|
|
// zext would go from, e.g. i16 to i16. This is legal with
|
|
// constZExtOrBitCast but not legal with constZExt.
|
|
const extended_int_val = small_int_val.constZExtOrBitCast(int_llvm_ty);
|
|
const shifted = extended_int_val.constShl(shift_rhs);
|
|
running_int = running_int.constOr(shifted);
|
|
running_bits += ty_bit_size;
|
|
}
|
|
return running_int;
|
|
}
|
|
|
|
const llvm_field_count = llvm_struct_ty.countStructElementTypes();
|
|
var llvm_fields = try std.ArrayListUnmanaged(*llvm.Value).initCapacity(gpa, llvm_field_count);
|
|
defer llvm_fields.deinit(gpa);
|
|
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var big_align: u32 = 0;
|
|
var need_unnamed = false;
|
|
|
|
var it = struct_obj.runtimeFieldIterator(mod);
|
|
while (it.next()) |field_and_index| {
|
|
const field = field_and_index.field;
|
|
const field_align = field.alignment(mod, struct_obj.layout);
|
|
big_align = @max(big_align, field_align);
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, field_align);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
// TODO make this and all other padding elsewhere in debug
|
|
// builds be 0xaa not undef.
|
|
llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
|
|
}
|
|
|
|
const field_llvm_val = try o.lowerValue(.{
|
|
.ty = field.ty,
|
|
.val = try tv.val.fieldValue(mod, field_and_index.index),
|
|
});
|
|
|
|
need_unnamed = need_unnamed or o.isUnnamedType(field.ty, field_llvm_val);
|
|
|
|
llvm_fields.appendAssumeCapacity(field_llvm_val);
|
|
|
|
offset += field.ty.abiSize(mod);
|
|
}
|
|
{
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, big_align);
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
const llvm_array_ty = o.context.intType(8).arrayType(@as(c_uint, @intCast(padding_len)));
|
|
llvm_fields.appendAssumeCapacity(llvm_array_ty.getUndef());
|
|
}
|
|
}
|
|
|
|
if (need_unnamed) {
|
|
return o.context.constStruct(
|
|
llvm_fields.items.ptr,
|
|
@as(c_uint, @intCast(llvm_fields.items.len)),
|
|
.False,
|
|
);
|
|
} else {
|
|
return llvm_struct_ty.constNamedStruct(
|
|
llvm_fields.items.ptr,
|
|
@as(c_uint, @intCast(llvm_fields.items.len)),
|
|
);
|
|
}
|
|
},
|
|
else => unreachable,
|
|
},
|
|
.un => {
|
|
const llvm_union_ty = try o.lowerType(tv.ty);
|
|
const tag_and_val: Value.Payload.Union.Data = switch (tv.val.toIntern()) {
|
|
.none => tv.val.castTag(.@"union").?.data,
|
|
else => switch (mod.intern_pool.indexToKey(tv.val.toIntern())) {
|
|
.un => |un| .{ .tag = un.tag.toValue(), .val = un.val.toValue() },
|
|
else => unreachable,
|
|
},
|
|
};
|
|
|
|
const layout = tv.ty.unionGetLayout(mod);
|
|
|
|
if (layout.payload_size == 0) {
|
|
return lowerValue(o, .{
|
|
.ty = tv.ty.unionTagTypeSafety(mod).?,
|
|
.val = tag_and_val.tag,
|
|
});
|
|
}
|
|
const union_obj = mod.typeToUnion(tv.ty).?;
|
|
const field_index = tv.ty.unionTagFieldIndex(tag_and_val.tag, o.module).?;
|
|
assert(union_obj.haveFieldTypes());
|
|
|
|
const field_ty = union_obj.fields.values()[field_index].ty;
|
|
if (union_obj.layout == .Packed) {
|
|
if (!field_ty.hasRuntimeBits(mod))
|
|
return llvm_union_ty.constNull();
|
|
const non_int_val = try lowerValue(o, .{ .ty = field_ty, .val = tag_and_val.val });
|
|
const ty_bit_size = @as(u16, @intCast(field_ty.bitSize(mod)));
|
|
const small_int_ty = o.context.intType(ty_bit_size);
|
|
const small_int_val = if (field_ty.isPtrAtRuntime(mod))
|
|
non_int_val.constPtrToInt(small_int_ty)
|
|
else
|
|
non_int_val.constBitCast(small_int_ty);
|
|
return small_int_val.constZExtOrBitCast(llvm_union_ty);
|
|
}
|
|
|
|
// 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.
|
|
var need_unnamed: bool = layout.most_aligned_field != field_index;
|
|
const payload = p: {
|
|
if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const padding_len = @as(c_uint, @intCast(layout.payload_size));
|
|
break :p o.context.intType(8).arrayType(padding_len).getUndef();
|
|
}
|
|
const field = try lowerValue(o, .{ .ty = field_ty, .val = tag_and_val.val });
|
|
need_unnamed = need_unnamed or o.isUnnamedType(field_ty, field);
|
|
const field_size = field_ty.abiSize(mod);
|
|
if (field_size == layout.payload_size) {
|
|
break :p field;
|
|
}
|
|
const padding_len = @as(c_uint, @intCast(layout.payload_size - field_size));
|
|
const fields: [2]*llvm.Value = .{
|
|
field, o.context.intType(8).arrayType(padding_len).getUndef(),
|
|
};
|
|
break :p o.context.constStruct(&fields, fields.len, .True);
|
|
};
|
|
|
|
if (layout.tag_size == 0) {
|
|
const fields: [1]*llvm.Value = .{payload};
|
|
if (need_unnamed) {
|
|
return o.context.constStruct(&fields, fields.len, .False);
|
|
} else {
|
|
return llvm_union_ty.constNamedStruct(&fields, fields.len);
|
|
}
|
|
}
|
|
const llvm_tag_value = try lowerValue(o, .{
|
|
.ty = tv.ty.unionTagTypeSafety(mod).?,
|
|
.val = tag_and_val.tag,
|
|
});
|
|
var fields: [3]*llvm.Value = undefined;
|
|
var fields_len: c_uint = 2;
|
|
if (layout.tag_align >= layout.payload_align) {
|
|
fields = .{ llvm_tag_value, payload, undefined };
|
|
} else {
|
|
fields = .{ payload, llvm_tag_value, undefined };
|
|
}
|
|
if (layout.padding != 0) {
|
|
fields[2] = o.context.intType(8).arrayType(layout.padding).getUndef();
|
|
fields_len = 3;
|
|
}
|
|
if (need_unnamed) {
|
|
return o.context.constStruct(&fields, fields_len, .False);
|
|
} else {
|
|
return llvm_union_ty.constNamedStruct(&fields, fields_len);
|
|
}
|
|
},
|
|
.memoized_call => unreachable,
|
|
}
|
|
}
|
|
|
|
fn lowerIntAsPtr(o: *Object, val: Value) Error!*llvm.Value {
|
|
const mod = o.module;
|
|
switch (mod.intern_pool.indexToKey(val.toIntern())) {
|
|
.undef => return o.context.pointerType(0).getUndef(),
|
|
.int => {
|
|
var bigint_space: Value.BigIntSpace = undefined;
|
|
const bigint = val.toBigInt(&bigint_space, mod);
|
|
const llvm_int = lowerBigInt(o, Type.usize, bigint);
|
|
return llvm_int.constIntToPtr(o.context.pointerType(0));
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn lowerBigInt(o: *Object, ty: Type, bigint: std.math.big.int.Const) *llvm.Value {
|
|
const mod = o.module;
|
|
const int_info = ty.intInfo(mod);
|
|
assert(int_info.bits != 0);
|
|
const llvm_type = o.context.intType(int_info.bits);
|
|
|
|
const unsigned_val = v: {
|
|
if (bigint.limbs.len == 1) {
|
|
break :v llvm_type.constInt(bigint.limbs[0], .False);
|
|
}
|
|
if (@sizeOf(usize) == @sizeOf(u64)) {
|
|
break :v llvm_type.constIntOfArbitraryPrecision(
|
|
@as(c_uint, @intCast(bigint.limbs.len)),
|
|
bigint.limbs.ptr,
|
|
);
|
|
}
|
|
@panic("TODO implement bigint to llvm int for 32-bit compiler builds");
|
|
};
|
|
if (!bigint.positive) {
|
|
return llvm.constNeg(unsigned_val);
|
|
}
|
|
return unsigned_val;
|
|
}
|
|
|
|
const ParentPtr = struct {
|
|
ty: Type,
|
|
llvm_ptr: *llvm.Value,
|
|
};
|
|
|
|
fn lowerParentPtrDecl(
|
|
o: *Object,
|
|
ptr_val: Value,
|
|
decl_index: Module.Decl.Index,
|
|
) Error!*llvm.Value {
|
|
const mod = o.module;
|
|
const decl = mod.declPtr(decl_index);
|
|
try mod.markDeclAlive(decl);
|
|
const ptr_ty = try mod.singleMutPtrType(decl.ty);
|
|
return try o.lowerDeclRefValue(.{ .ty = ptr_ty, .val = ptr_val }, decl_index);
|
|
}
|
|
|
|
fn lowerParentPtr(o: *Object, ptr_val: Value, byte_aligned: bool) Error!*llvm.Value {
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
return switch (mod.intern_pool.indexToKey(ptr_val.toIntern()).ptr.addr) {
|
|
.decl => |decl| o.lowerParentPtrDecl(ptr_val, decl),
|
|
.mut_decl => |mut_decl| o.lowerParentPtrDecl(ptr_val, mut_decl.decl),
|
|
.int => |int| o.lowerIntAsPtr(int.toValue()),
|
|
.eu_payload => |eu_ptr| {
|
|
const parent_llvm_ptr = try o.lowerParentPtr(eu_ptr.toValue(), true);
|
|
|
|
const eu_ty = mod.intern_pool.typeOf(eu_ptr).toType().childType(mod);
|
|
const payload_ty = eu_ty.errorUnionPayload(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
// In this case, we represent pointer to error union the same as pointer
|
|
// to the payload.
|
|
return parent_llvm_ptr;
|
|
}
|
|
|
|
const payload_offset: u8 = if (payload_ty.abiAlignment(mod) > Type.anyerror.abiSize(mod)) 2 else 1;
|
|
const llvm_u32 = o.context.intType(32);
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_u32.constInt(0, .False),
|
|
llvm_u32.constInt(payload_offset, .False),
|
|
};
|
|
const eu_llvm_ty = try o.lowerType(eu_ty);
|
|
return eu_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
},
|
|
.opt_payload => |opt_ptr| {
|
|
const parent_llvm_ptr = try o.lowerParentPtr(opt_ptr.toValue(), true);
|
|
|
|
const opt_ty = mod.intern_pool.typeOf(opt_ptr).toType().childType(mod);
|
|
const payload_ty = opt_ty.optionalChild(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod) or
|
|
payload_ty.optionalReprIsPayload(mod))
|
|
{
|
|
// In this case, we represent pointer to optional the same as pointer
|
|
// to the payload.
|
|
return parent_llvm_ptr;
|
|
}
|
|
|
|
const llvm_u32 = o.context.intType(32);
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_u32.constInt(0, .False),
|
|
llvm_u32.constInt(0, .False),
|
|
};
|
|
const opt_llvm_ty = try o.lowerType(opt_ty);
|
|
return opt_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
},
|
|
.comptime_field => unreachable,
|
|
.elem => |elem_ptr| {
|
|
const parent_llvm_ptr = try o.lowerParentPtr(elem_ptr.base.toValue(), true);
|
|
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const indices: [1]*llvm.Value = .{
|
|
llvm_usize.constInt(elem_ptr.index, .False),
|
|
};
|
|
const elem_ty = mod.intern_pool.typeOf(elem_ptr.base).toType().elemType2(mod);
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
return elem_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
},
|
|
.field => |field_ptr| {
|
|
const parent_llvm_ptr = try o.lowerParentPtr(field_ptr.base.toValue(), byte_aligned);
|
|
const parent_ty = mod.intern_pool.typeOf(field_ptr.base).toType().childType(mod);
|
|
|
|
const field_index = @as(u32, @intCast(field_ptr.index));
|
|
const llvm_u32 = o.context.intType(32);
|
|
switch (parent_ty.zigTypeTag(mod)) {
|
|
.Union => {
|
|
if (parent_ty.containerLayout(mod) == .Packed) {
|
|
return parent_llvm_ptr;
|
|
}
|
|
|
|
const layout = parent_ty.unionGetLayout(mod);
|
|
if (layout.payload_size == 0) {
|
|
// In this case a pointer to the union and a pointer to any
|
|
// (void) payload is the same.
|
|
return parent_llvm_ptr;
|
|
}
|
|
const llvm_pl_index = if (layout.tag_size == 0)
|
|
0
|
|
else
|
|
@intFromBool(layout.tag_align >= layout.payload_align);
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_u32.constInt(0, .False),
|
|
llvm_u32.constInt(llvm_pl_index, .False),
|
|
};
|
|
const parent_llvm_ty = try o.lowerType(parent_ty);
|
|
return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
},
|
|
.Struct => {
|
|
if (parent_ty.containerLayout(mod) == .Packed) {
|
|
if (!byte_aligned) return parent_llvm_ptr;
|
|
const llvm_usize = o.context.intType(target.ptrBitWidth());
|
|
const base_addr = parent_llvm_ptr.constPtrToInt(llvm_usize);
|
|
// count bits of fields before this one
|
|
const prev_bits = b: {
|
|
var b: usize = 0;
|
|
for (parent_ty.structFields(mod).values()[0..field_index]) |field| {
|
|
if (field.is_comptime or !field.ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
|
|
b += @as(usize, @intCast(field.ty.bitSize(mod)));
|
|
}
|
|
break :b b;
|
|
};
|
|
const byte_offset = llvm_usize.constInt(prev_bits / 8, .False);
|
|
const field_addr = base_addr.constAdd(byte_offset);
|
|
const final_llvm_ty = o.context.pointerType(0);
|
|
return field_addr.constIntToPtr(final_llvm_ty);
|
|
}
|
|
|
|
const parent_llvm_ty = try o.lowerType(parent_ty);
|
|
if (llvmField(parent_ty, field_index, mod)) |llvm_field| {
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_u32.constInt(0, .False),
|
|
llvm_u32.constInt(llvm_field.index, .False),
|
|
};
|
|
return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
} else {
|
|
const llvm_index = llvm_u32.constInt(@intFromBool(parent_ty.hasRuntimeBitsIgnoreComptime(mod)), .False);
|
|
const indices: [1]*llvm.Value = .{llvm_index};
|
|
return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
}
|
|
},
|
|
.Pointer => {
|
|
assert(parent_ty.isSlice(mod));
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_u32.constInt(0, .False),
|
|
llvm_u32.constInt(field_index, .False),
|
|
};
|
|
const parent_llvm_ty = try o.lowerType(parent_ty);
|
|
return parent_llvm_ty.constInBoundsGEP(parent_llvm_ptr, &indices, indices.len);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
};
|
|
}
|
|
|
|
fn lowerDeclRefValue(
|
|
o: *Object,
|
|
tv: TypedValue,
|
|
decl_index: Module.Decl.Index,
|
|
) Error!*llvm.Value {
|
|
const mod = o.module;
|
|
|
|
// In the case of something like:
|
|
// fn foo() void {}
|
|
// const bar = foo;
|
|
// ... &bar;
|
|
// `bar` is just an alias and we actually want to lower a reference to `foo`.
|
|
const decl = mod.declPtr(decl_index);
|
|
if (decl.val.getFunction(mod)) |func| {
|
|
if (func.owner_decl != decl_index) {
|
|
return o.lowerDeclRefValue(tv, func.owner_decl);
|
|
}
|
|
} else if (decl.val.getExternFunc(mod)) |func| {
|
|
if (func.decl != decl_index) {
|
|
return o.lowerDeclRefValue(tv, func.decl);
|
|
}
|
|
}
|
|
|
|
const is_fn_body = decl.ty.zigTypeTag(mod) == .Fn;
|
|
if ((!is_fn_body and !decl.ty.hasRuntimeBits(mod)) or
|
|
(is_fn_body and mod.typeToFunc(decl.ty).?.is_generic))
|
|
{
|
|
return o.lowerPtrToVoid(tv.ty);
|
|
}
|
|
|
|
try mod.markDeclAlive(decl);
|
|
|
|
const llvm_decl_val = if (is_fn_body)
|
|
try o.resolveLlvmFunction(decl_index)
|
|
else
|
|
try o.resolveGlobalDecl(decl_index);
|
|
|
|
const target = mod.getTarget();
|
|
const llvm_wanted_addrspace = toLlvmAddressSpace(decl.@"addrspace", target);
|
|
const llvm_actual_addrspace = toLlvmGlobalAddressSpace(decl.@"addrspace", target);
|
|
const llvm_val = if (llvm_wanted_addrspace != llvm_actual_addrspace) blk: {
|
|
const llvm_decl_wanted_ptr_ty = o.context.pointerType(llvm_wanted_addrspace);
|
|
break :blk llvm_decl_val.constAddrSpaceCast(llvm_decl_wanted_ptr_ty);
|
|
} else llvm_decl_val;
|
|
|
|
const llvm_type = try o.lowerType(tv.ty);
|
|
if (tv.ty.zigTypeTag(mod) == .Int) {
|
|
return llvm_val.constPtrToInt(llvm_type);
|
|
} else {
|
|
return llvm_val.constBitCast(llvm_type);
|
|
}
|
|
}
|
|
|
|
fn lowerPtrToVoid(o: *Object, ptr_ty: Type) !*llvm.Value {
|
|
const mod = o.module;
|
|
// Even though we are pointing at something which has zero bits (e.g. `void`),
|
|
// Pointers are defined to have bits. So we must return something here.
|
|
// The value cannot be undefined, because we use the `nonnull` annotation
|
|
// for non-optional pointers. We also need to respect the alignment, even though
|
|
// the address will never be dereferenced.
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const llvm_ptr_ty = try o.lowerType(ptr_ty);
|
|
if (ptr_ty.ptrInfo(mod).flags.alignment.toByteUnitsOptional()) |alignment| {
|
|
return llvm_usize.constInt(alignment, .False).constIntToPtr(llvm_ptr_ty);
|
|
}
|
|
// Note that these 0xaa values are appropriate even in release-optimized builds
|
|
// because we need a well-defined value that is not null, and LLVM does not
|
|
// have an "undef_but_not_null" attribute. As an example, if this `alloc` AIR
|
|
// instruction is followed by a `wrap_optional`, it will return this value
|
|
// verbatim, and the result should test as non-null.
|
|
const target = mod.getTarget();
|
|
const int = switch (target.ptrBitWidth()) {
|
|
16 => llvm_usize.constInt(0xaaaa, .False),
|
|
32 => llvm_usize.constInt(0xaaaaaaaa, .False),
|
|
64 => llvm_usize.constInt(0xaaaaaaaa_aaaaaaaa, .False),
|
|
else => unreachable,
|
|
};
|
|
return int.constIntToPtr(llvm_ptr_ty);
|
|
}
|
|
|
|
fn addAttr(o: *Object, val: *llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
|
|
return o.addAttrInt(val, index, name, 0);
|
|
}
|
|
|
|
fn addArgAttr(o: *Object, fn_val: *llvm.Value, param_index: u32, attr_name: []const u8) void {
|
|
return o.addAttr(fn_val, param_index + 1, attr_name);
|
|
}
|
|
|
|
fn addArgAttrInt(o: *Object, fn_val: *llvm.Value, param_index: u32, attr_name: []const u8, int: u64) void {
|
|
return o.addAttrInt(fn_val, param_index + 1, attr_name, int);
|
|
}
|
|
|
|
fn removeAttr(val: *llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
|
|
const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len);
|
|
assert(kind_id != 0);
|
|
val.removeEnumAttributeAtIndex(index, kind_id);
|
|
}
|
|
|
|
fn addAttrInt(
|
|
o: *Object,
|
|
val: *llvm.Value,
|
|
index: llvm.AttributeIndex,
|
|
name: []const u8,
|
|
int: u64,
|
|
) void {
|
|
const kind_id = llvm.getEnumAttributeKindForName(name.ptr, name.len);
|
|
assert(kind_id != 0);
|
|
const llvm_attr = o.context.createEnumAttribute(kind_id, int);
|
|
val.addAttributeAtIndex(index, llvm_attr);
|
|
}
|
|
|
|
fn addAttrString(
|
|
o: *Object,
|
|
val: *llvm.Value,
|
|
index: llvm.AttributeIndex,
|
|
name: []const u8,
|
|
value: []const u8,
|
|
) void {
|
|
const llvm_attr = o.context.createStringAttribute(
|
|
name.ptr,
|
|
@as(c_uint, @intCast(name.len)),
|
|
value.ptr,
|
|
@as(c_uint, @intCast(value.len)),
|
|
);
|
|
val.addAttributeAtIndex(index, llvm_attr);
|
|
}
|
|
|
|
fn addFnAttr(o: *Object, val: *llvm.Value, name: []const u8) void {
|
|
o.addAttr(val, std.math.maxInt(llvm.AttributeIndex), name);
|
|
}
|
|
|
|
fn addFnAttrString(o: *Object, val: *llvm.Value, name: []const u8, value: []const u8) void {
|
|
o.addAttrString(val, std.math.maxInt(llvm.AttributeIndex), name, value);
|
|
}
|
|
|
|
fn removeFnAttr(fn_val: *llvm.Value, name: []const u8) void {
|
|
removeAttr(fn_val, std.math.maxInt(llvm.AttributeIndex), name);
|
|
}
|
|
|
|
fn addFnAttrInt(o: *Object, fn_val: *llvm.Value, name: []const u8, int: u64) void {
|
|
return o.addAttrInt(fn_val, std.math.maxInt(llvm.AttributeIndex), name, int);
|
|
}
|
|
|
|
/// If the operand type of an atomic operation is not byte sized we need to
|
|
/// widen it before using it and then truncate the result.
|
|
/// RMW exchange of floating-point values is bitcasted to same-sized integer
|
|
/// types to work around a LLVM deficiency when targeting ARM/AArch64.
|
|
fn getAtomicAbiType(o: *Object, ty: Type, is_rmw_xchg: bool) ?*llvm.Type {
|
|
const mod = o.module;
|
|
const int_ty = switch (ty.zigTypeTag(mod)) {
|
|
.Int => ty,
|
|
.Enum => ty.intTagType(mod),
|
|
.Float => {
|
|
if (!is_rmw_xchg) return null;
|
|
return o.context.intType(@as(c_uint, @intCast(ty.abiSize(mod) * 8)));
|
|
},
|
|
.Bool => return o.context.intType(8),
|
|
else => return null,
|
|
};
|
|
const bit_count = int_ty.intInfo(mod).bits;
|
|
if (!std.math.isPowerOfTwo(bit_count) or (bit_count % 8) != 0) {
|
|
return o.context.intType(@as(c_uint, @intCast(int_ty.abiSize(mod) * 8)));
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
fn addByValParamAttrs(
|
|
o: *Object,
|
|
llvm_fn: *llvm.Value,
|
|
param_ty: Type,
|
|
param_index: u32,
|
|
fn_info: InternPool.Key.FuncType,
|
|
llvm_arg_i: u32,
|
|
) void {
|
|
const mod = o.module;
|
|
if (param_ty.isPtrAtRuntime(mod)) {
|
|
const ptr_info = param_ty.ptrInfo(mod);
|
|
if (math.cast(u5, param_index)) |i| {
|
|
if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
|
|
o.addArgAttr(llvm_fn, llvm_arg_i, "noalias");
|
|
}
|
|
}
|
|
if (!param_ty.isPtrLikeOptional(mod) and !ptr_info.flags.is_allowzero) {
|
|
o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull");
|
|
}
|
|
if (ptr_info.flags.is_const) {
|
|
o.addArgAttr(llvm_fn, llvm_arg_i, "readonly");
|
|
}
|
|
const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse
|
|
@max(ptr_info.child.toType().abiAlignment(mod), 1);
|
|
o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", elem_align);
|
|
} else if (ccAbiPromoteInt(fn_info.cc, mod, param_ty)) |s| switch (s) {
|
|
.signed => o.addArgAttr(llvm_fn, llvm_arg_i, "signext"),
|
|
.unsigned => o.addArgAttr(llvm_fn, llvm_arg_i, "zeroext"),
|
|
};
|
|
}
|
|
|
|
fn addByRefParamAttrs(
|
|
o: *Object,
|
|
llvm_fn: *llvm.Value,
|
|
llvm_arg_i: u32,
|
|
alignment: u32,
|
|
byval_attr: bool,
|
|
param_llvm_ty: *llvm.Type,
|
|
) void {
|
|
o.addArgAttr(llvm_fn, llvm_arg_i, "nonnull");
|
|
o.addArgAttr(llvm_fn, llvm_arg_i, "readonly");
|
|
o.addArgAttrInt(llvm_fn, llvm_arg_i, "align", alignment);
|
|
if (byval_attr) {
|
|
llvm_fn.addByValAttr(llvm_arg_i, param_llvm_ty);
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const DeclGen = struct {
|
|
object: *Object,
|
|
decl: *Module.Decl,
|
|
decl_index: Module.Decl.Index,
|
|
err_msg: ?*Module.ErrorMsg,
|
|
|
|
fn todo(dg: *DeclGen, comptime format: []const u8, args: anytype) Error {
|
|
@setCold(true);
|
|
assert(dg.err_msg == null);
|
|
const o = dg.object;
|
|
const gpa = o.gpa;
|
|
const mod = o.module;
|
|
const src_loc = LazySrcLoc.nodeOffset(0).toSrcLoc(dg.decl, mod);
|
|
dg.err_msg = try Module.ErrorMsg.create(gpa, src_loc, "TODO (LLVM): " ++ format, args);
|
|
return error.CodegenFail;
|
|
}
|
|
|
|
fn genDecl(dg: *DeclGen) !void {
|
|
const o = dg.object;
|
|
const mod = o.module;
|
|
const decl = dg.decl;
|
|
const decl_index = dg.decl_index;
|
|
assert(decl.has_tv);
|
|
|
|
if (decl.val.getExternFunc(mod)) |extern_func| {
|
|
_ = try o.resolveLlvmFunction(extern_func.decl);
|
|
} else {
|
|
const target = mod.getTarget();
|
|
var global = try o.resolveGlobalDecl(decl_index);
|
|
global.setAlignment(decl.getAlignment(mod));
|
|
if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |s| global.setSection(s);
|
|
assert(decl.has_tv);
|
|
const init_val = if (decl.val.getVariable(mod)) |variable| init_val: {
|
|
break :init_val variable.init;
|
|
} else init_val: {
|
|
global.setGlobalConstant(.True);
|
|
break :init_val decl.val.toIntern();
|
|
};
|
|
if (init_val != .none) {
|
|
const llvm_init = try o.lowerValue(.{ .ty = decl.ty, .val = init_val.toValue() });
|
|
if (global.globalGetValueType() == llvm_init.typeOf()) {
|
|
global.setInitializer(llvm_init);
|
|
} else {
|
|
// LLVM does not allow us to change the type of globals. So we must
|
|
// create a new global with the correct type, copy all its attributes,
|
|
// and then update all references to point to the new global,
|
|
// delete the original, and rename the new one to the old one's name.
|
|
// This is necessary because LLVM does not support const bitcasting
|
|
// a struct with padding bytes, which is needed to lower a const union value
|
|
// to LLVM, when a field other than the most-aligned is active. Instead,
|
|
// we must lower to an unnamed struct, and pointer cast at usage sites
|
|
// of the global. Such an unnamed struct is the cause of the global type
|
|
// mismatch, because we don't have the LLVM type until the *value* is created,
|
|
// whereas the global needs to be created based on the type alone, because
|
|
// lowering the value may reference the global as a pointer.
|
|
// Related: https://github.com/ziglang/zig/issues/13265
|
|
const llvm_global_addrspace = toLlvmGlobalAddressSpace(decl.@"addrspace", target);
|
|
const new_global = o.llvm_module.addGlobalInAddressSpace(
|
|
llvm_init.typeOf(),
|
|
"",
|
|
llvm_global_addrspace,
|
|
);
|
|
new_global.setLinkage(global.getLinkage());
|
|
new_global.setUnnamedAddr(global.getUnnamedAddress());
|
|
new_global.setAlignment(global.getAlignment());
|
|
if (mod.intern_pool.stringToSliceUnwrap(decl.@"linksection")) |s|
|
|
new_global.setSection(s);
|
|
new_global.setInitializer(llvm_init);
|
|
// TODO: How should this work then the address space of a global changed?
|
|
global.replaceAllUsesWith(new_global);
|
|
o.decl_map.putAssumeCapacity(decl_index, new_global);
|
|
new_global.takeName(global);
|
|
global.deleteGlobal();
|
|
global = new_global;
|
|
}
|
|
}
|
|
|
|
if (o.di_builder) |dib| {
|
|
const di_file = try o.getDIFile(o.gpa, mod.namespacePtr(decl.src_namespace).file_scope);
|
|
|
|
const line_number = decl.src_line + 1;
|
|
const is_internal_linkage = !o.module.decl_exports.contains(decl_index);
|
|
const di_global = dib.createGlobalVariableExpression(
|
|
di_file.toScope(),
|
|
mod.intern_pool.stringToSlice(decl.name),
|
|
global.getValueName(),
|
|
di_file,
|
|
line_number,
|
|
try o.lowerDebugType(decl.ty, .full),
|
|
is_internal_linkage,
|
|
);
|
|
|
|
try o.di_map.put(o.gpa, dg.decl, di_global.getVariable().toNode());
|
|
if (!is_internal_linkage or decl.isExtern(mod)) global.attachMetaData(di_global);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
pub const FuncGen = struct {
|
|
gpa: Allocator,
|
|
dg: *DeclGen,
|
|
air: Air,
|
|
liveness: Liveness,
|
|
context: *llvm.Context,
|
|
builder: *llvm.Builder,
|
|
di_scope: ?*llvm.DIScope,
|
|
di_file: ?*llvm.DIFile,
|
|
base_line: u32,
|
|
prev_dbg_line: c_uint,
|
|
prev_dbg_column: c_uint,
|
|
|
|
/// Stack of locations where a call was inlined.
|
|
dbg_inlined: std.ArrayListUnmanaged(DbgState) = .{},
|
|
|
|
/// Stack of `DILexicalBlock`s. dbg_block instructions cannot happend accross
|
|
/// dbg_inline instructions so no special handling there is required.
|
|
dbg_block_stack: std.ArrayListUnmanaged(*llvm.DIScope) = .{},
|
|
|
|
/// This stores the LLVM values used in a function, such that they can be referred to
|
|
/// in other instructions. This table is cleared before every function is generated.
|
|
func_inst_table: std.AutoHashMapUnmanaged(Air.Inst.Ref, *llvm.Value),
|
|
|
|
/// If the return type is sret, this is the result pointer. Otherwise null.
|
|
/// Note that this can disagree with isByRef for the return type in the case
|
|
/// of C ABI functions.
|
|
ret_ptr: ?*llvm.Value,
|
|
/// Any function that needs to perform Valgrind client requests needs an array alloca
|
|
/// instruction, however a maximum of one per function is needed.
|
|
valgrind_client_request_array: ?*llvm.Value = null,
|
|
/// These fields are used to refer to the LLVM value of the function parameters
|
|
/// in an Arg instruction.
|
|
/// This list may be shorter than the list according to the zig type system;
|
|
/// it omits 0-bit types. If the function uses sret as the first parameter,
|
|
/// this slice does not include it.
|
|
args: []const *llvm.Value,
|
|
arg_index: c_uint,
|
|
|
|
llvm_func: *llvm.Value,
|
|
|
|
err_ret_trace: ?*llvm.Value = null,
|
|
|
|
/// This data structure is used to implement breaking to blocks.
|
|
blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
|
|
parent_bb: *llvm.BasicBlock,
|
|
breaks: *BreakList,
|
|
}),
|
|
|
|
single_threaded: bool,
|
|
|
|
const DbgState = struct { loc: *llvm.DILocation, scope: *llvm.DIScope, base_line: u32 };
|
|
const BreakList = std.MultiArrayList(struct {
|
|
bb: *llvm.BasicBlock,
|
|
val: *llvm.Value,
|
|
});
|
|
|
|
fn deinit(self: *FuncGen) void {
|
|
self.builder.dispose();
|
|
self.dbg_inlined.deinit(self.gpa);
|
|
self.dbg_block_stack.deinit(self.gpa);
|
|
self.func_inst_table.deinit(self.gpa);
|
|
self.blocks.deinit(self.gpa);
|
|
}
|
|
|
|
fn todo(self: *FuncGen, comptime format: []const u8, args: anytype) Error {
|
|
@setCold(true);
|
|
return self.dg.todo(format, args);
|
|
}
|
|
|
|
fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*llvm.Value {
|
|
const gpa = self.gpa;
|
|
const gop = try self.func_inst_table.getOrPut(gpa, inst);
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const llvm_val = try self.resolveValue(.{
|
|
.ty = self.typeOf(inst),
|
|
.val = (try self.air.value(inst, mod)).?,
|
|
});
|
|
gop.value_ptr.* = llvm_val;
|
|
return llvm_val;
|
|
}
|
|
|
|
fn resolveValue(self: *FuncGen, tv: TypedValue) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const llvm_val = try o.lowerValue(tv);
|
|
if (!isByRef(tv.ty, mod)) return llvm_val;
|
|
|
|
// We have an LLVM value but we need to create a global constant and
|
|
// set the value as its initializer, and then return a pointer to the global.
|
|
const target = mod.getTarget();
|
|
const llvm_wanted_addrspace = toLlvmAddressSpace(.generic, target);
|
|
const llvm_actual_addrspace = toLlvmGlobalAddressSpace(.generic, target);
|
|
const global = o.llvm_module.addGlobalInAddressSpace(llvm_val.typeOf(), "", llvm_actual_addrspace);
|
|
global.setInitializer(llvm_val);
|
|
global.setLinkage(.Private);
|
|
global.setGlobalConstant(.True);
|
|
global.setUnnamedAddr(.True);
|
|
global.setAlignment(tv.ty.abiAlignment(mod));
|
|
const addrspace_casted_ptr = if (llvm_actual_addrspace != llvm_wanted_addrspace)
|
|
global.constAddrSpaceCast(self.context.pointerType(llvm_wanted_addrspace))
|
|
else
|
|
global;
|
|
return addrspace_casted_ptr;
|
|
}
|
|
|
|
fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ip = &mod.intern_pool;
|
|
const air_tags = self.air.instructions.items(.tag);
|
|
for (body, 0..) |inst, i| {
|
|
if (self.liveness.isUnused(inst) and !self.air.mustLower(inst, ip))
|
|
continue;
|
|
|
|
const opt_value: ?*llvm.Value = switch (air_tags[inst]) {
|
|
// zig fmt: off
|
|
.add => try self.airAdd(inst, false),
|
|
.add_optimized => try self.airAdd(inst, true),
|
|
.add_wrap => try self.airAddWrap(inst),
|
|
.add_sat => try self.airAddSat(inst),
|
|
|
|
.sub => try self.airSub(inst, false),
|
|
.sub_optimized => try self.airSub(inst, true),
|
|
.sub_wrap => try self.airSubWrap(inst),
|
|
.sub_sat => try self.airSubSat(inst),
|
|
|
|
.mul => try self.airMul(inst, false),
|
|
.mul_optimized => try self.airMul(inst, true),
|
|
.mul_wrap => try self.airMulWrap(inst),
|
|
.mul_sat => try self.airMulSat(inst),
|
|
|
|
.add_safe => try self.airSafeArithmetic(inst, "llvm.sadd.with.overflow", "llvm.uadd.with.overflow"),
|
|
.sub_safe => try self.airSafeArithmetic(inst, "llvm.ssub.with.overflow", "llvm.usub.with.overflow"),
|
|
.mul_safe => try self.airSafeArithmetic(inst, "llvm.smul.with.overflow", "llvm.umul.with.overflow"),
|
|
|
|
.div_float => try self.airDivFloat(inst, false),
|
|
.div_trunc => try self.airDivTrunc(inst, false),
|
|
.div_floor => try self.airDivFloor(inst, false),
|
|
.div_exact => try self.airDivExact(inst, false),
|
|
.rem => try self.airRem(inst, false),
|
|
.mod => try self.airMod(inst, false),
|
|
.ptr_add => try self.airPtrAdd(inst),
|
|
.ptr_sub => try self.airPtrSub(inst),
|
|
.shl => try self.airShl(inst),
|
|
.shl_sat => try self.airShlSat(inst),
|
|
.shl_exact => try self.airShlExact(inst),
|
|
.min => try self.airMin(inst),
|
|
.max => try self.airMax(inst),
|
|
.slice => try self.airSlice(inst),
|
|
.mul_add => try self.airMulAdd(inst),
|
|
|
|
.div_float_optimized => try self.airDivFloat(inst, true),
|
|
.div_trunc_optimized => try self.airDivTrunc(inst, true),
|
|
.div_floor_optimized => try self.airDivFloor(inst, true),
|
|
.div_exact_optimized => try self.airDivExact(inst, true),
|
|
.rem_optimized => try self.airRem(inst, true),
|
|
.mod_optimized => try self.airMod(inst, true),
|
|
|
|
.add_with_overflow => try self.airOverflow(inst, "llvm.sadd.with.overflow", "llvm.uadd.with.overflow"),
|
|
.sub_with_overflow => try self.airOverflow(inst, "llvm.ssub.with.overflow", "llvm.usub.with.overflow"),
|
|
.mul_with_overflow => try self.airOverflow(inst, "llvm.smul.with.overflow", "llvm.umul.with.overflow"),
|
|
.shl_with_overflow => try self.airShlWithOverflow(inst),
|
|
|
|
.bit_and, .bool_and => try self.airAnd(inst),
|
|
.bit_or, .bool_or => try self.airOr(inst),
|
|
.xor => try self.airXor(inst),
|
|
.shr => try self.airShr(inst, false),
|
|
.shr_exact => try self.airShr(inst, true),
|
|
|
|
.sqrt => try self.airUnaryOp(inst, .sqrt),
|
|
.sin => try self.airUnaryOp(inst, .sin),
|
|
.cos => try self.airUnaryOp(inst, .cos),
|
|
.tan => try self.airUnaryOp(inst, .tan),
|
|
.exp => try self.airUnaryOp(inst, .exp),
|
|
.exp2 => try self.airUnaryOp(inst, .exp2),
|
|
.log => try self.airUnaryOp(inst, .log),
|
|
.log2 => try self.airUnaryOp(inst, .log2),
|
|
.log10 => try self.airUnaryOp(inst, .log10),
|
|
.fabs => try self.airUnaryOp(inst, .fabs),
|
|
.floor => try self.airUnaryOp(inst, .floor),
|
|
.ceil => try self.airUnaryOp(inst, .ceil),
|
|
.round => try self.airUnaryOp(inst, .round),
|
|
.trunc_float => try self.airUnaryOp(inst, .trunc),
|
|
|
|
.neg => try self.airNeg(inst, false),
|
|
.neg_optimized => try self.airNeg(inst, true),
|
|
|
|
.cmp_eq => try self.airCmp(inst, .eq, false),
|
|
.cmp_gt => try self.airCmp(inst, .gt, false),
|
|
.cmp_gte => try self.airCmp(inst, .gte, false),
|
|
.cmp_lt => try self.airCmp(inst, .lt, false),
|
|
.cmp_lte => try self.airCmp(inst, .lte, false),
|
|
.cmp_neq => try self.airCmp(inst, .neq, false),
|
|
|
|
.cmp_eq_optimized => try self.airCmp(inst, .eq, true),
|
|
.cmp_gt_optimized => try self.airCmp(inst, .gt, true),
|
|
.cmp_gte_optimized => try self.airCmp(inst, .gte, true),
|
|
.cmp_lt_optimized => try self.airCmp(inst, .lt, true),
|
|
.cmp_lte_optimized => try self.airCmp(inst, .lte, true),
|
|
.cmp_neq_optimized => try self.airCmp(inst, .neq, true),
|
|
|
|
.cmp_vector => try self.airCmpVector(inst, false),
|
|
.cmp_vector_optimized => try self.airCmpVector(inst, true),
|
|
.cmp_lt_errors_len => try self.airCmpLtErrorsLen(inst),
|
|
|
|
.is_non_null => try self.airIsNonNull(inst, false, .NE),
|
|
.is_non_null_ptr => try self.airIsNonNull(inst, true , .NE),
|
|
.is_null => try self.airIsNonNull(inst, false, .EQ),
|
|
.is_null_ptr => try self.airIsNonNull(inst, true , .EQ),
|
|
|
|
.is_non_err => try self.airIsErr(inst, .EQ, false),
|
|
.is_non_err_ptr => try self.airIsErr(inst, .EQ, true),
|
|
.is_err => try self.airIsErr(inst, .NE, false),
|
|
.is_err_ptr => try self.airIsErr(inst, .NE, true),
|
|
|
|
.alloc => try self.airAlloc(inst),
|
|
.ret_ptr => try self.airRetPtr(inst),
|
|
.arg => try self.airArg(inst),
|
|
.bitcast => try self.airBitCast(inst),
|
|
.int_from_bool => try self.airIntFromBool(inst),
|
|
.block => try self.airBlock(inst),
|
|
.br => try self.airBr(inst),
|
|
.switch_br => try self.airSwitchBr(inst),
|
|
.trap => try self.airTrap(inst),
|
|
.breakpoint => try self.airBreakpoint(inst),
|
|
.ret_addr => try self.airRetAddr(inst),
|
|
.frame_addr => try self.airFrameAddress(inst),
|
|
.cond_br => try self.airCondBr(inst),
|
|
.@"try" => try self.airTry(body[i..]),
|
|
.try_ptr => try self.airTryPtr(inst),
|
|
.intcast => try self.airIntCast(inst),
|
|
.trunc => try self.airTrunc(inst),
|
|
.fptrunc => try self.airFptrunc(inst),
|
|
.fpext => try self.airFpext(inst),
|
|
.int_from_ptr => try self.airIntFromPtr(inst),
|
|
.load => try self.airLoad(body[i..]),
|
|
.loop => try self.airLoop(inst),
|
|
.not => try self.airNot(inst),
|
|
.ret => try self.airRet(inst),
|
|
.ret_load => try self.airRetLoad(inst),
|
|
.store => try self.airStore(inst, false),
|
|
.store_safe => try self.airStore(inst, true),
|
|
.assembly => try self.airAssembly(inst),
|
|
.slice_ptr => try self.airSliceField(inst, 0),
|
|
.slice_len => try self.airSliceField(inst, 1),
|
|
|
|
.call => try self.airCall(inst, .Auto),
|
|
.call_always_tail => try self.airCall(inst, .AlwaysTail),
|
|
.call_never_tail => try self.airCall(inst, .NeverTail),
|
|
.call_never_inline => try self.airCall(inst, .NeverInline),
|
|
|
|
.ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0),
|
|
.ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1),
|
|
|
|
.int_from_float => try self.airIntFromFloat(inst, false),
|
|
.int_from_float_optimized => try self.airIntFromFloat(inst, true),
|
|
|
|
.array_to_slice => try self.airArrayToSlice(inst),
|
|
.float_from_int => try self.airFloatFromInt(inst),
|
|
.cmpxchg_weak => try self.airCmpxchg(inst, true),
|
|
.cmpxchg_strong => try self.airCmpxchg(inst, false),
|
|
.fence => try self.airFence(inst),
|
|
.atomic_rmw => try self.airAtomicRmw(inst),
|
|
.atomic_load => try self.airAtomicLoad(inst),
|
|
.memset => try self.airMemset(inst, false),
|
|
.memset_safe => try self.airMemset(inst, true),
|
|
.memcpy => try self.airMemcpy(inst),
|
|
.set_union_tag => try self.airSetUnionTag(inst),
|
|
.get_union_tag => try self.airGetUnionTag(inst),
|
|
.clz => try self.airClzCtz(inst, "llvm.ctlz"),
|
|
.ctz => try self.airClzCtz(inst, "llvm.cttz"),
|
|
.popcount => try self.airBitOp(inst, "llvm.ctpop"),
|
|
.byte_swap => try self.airByteSwap(inst, "llvm.bswap"),
|
|
.bit_reverse => try self.airBitOp(inst, "llvm.bitreverse"),
|
|
.tag_name => try self.airTagName(inst),
|
|
.error_name => try self.airErrorName(inst),
|
|
.splat => try self.airSplat(inst),
|
|
.select => try self.airSelect(inst),
|
|
.shuffle => try self.airShuffle(inst),
|
|
.aggregate_init => try self.airAggregateInit(inst),
|
|
.union_init => try self.airUnionInit(inst),
|
|
.prefetch => try self.airPrefetch(inst),
|
|
.addrspace_cast => try self.airAddrSpaceCast(inst),
|
|
|
|
.is_named_enum_value => try self.airIsNamedEnumValue(inst),
|
|
.error_set_has_value => try self.airErrorSetHasValue(inst),
|
|
|
|
.reduce => try self.airReduce(inst, false),
|
|
.reduce_optimized => try self.airReduce(inst, true),
|
|
|
|
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
|
|
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
|
|
.atomic_store_release => try self.airAtomicStore(inst, .Release),
|
|
.atomic_store_seq_cst => try self.airAtomicStore(inst, .SequentiallyConsistent),
|
|
|
|
.struct_field_ptr => try self.airStructFieldPtr(inst),
|
|
.struct_field_val => try self.airStructFieldVal(body[i..]),
|
|
|
|
.struct_field_ptr_index_0 => try self.airStructFieldPtrIndex(inst, 0),
|
|
.struct_field_ptr_index_1 => try self.airStructFieldPtrIndex(inst, 1),
|
|
.struct_field_ptr_index_2 => try self.airStructFieldPtrIndex(inst, 2),
|
|
.struct_field_ptr_index_3 => try self.airStructFieldPtrIndex(inst, 3),
|
|
|
|
.field_parent_ptr => try self.airFieldParentPtr(inst),
|
|
|
|
.array_elem_val => try self.airArrayElemVal(body[i..]),
|
|
.slice_elem_val => try self.airSliceElemVal(body[i..]),
|
|
.slice_elem_ptr => try self.airSliceElemPtr(inst),
|
|
.ptr_elem_val => try self.airPtrElemVal(body[i..]),
|
|
.ptr_elem_ptr => try self.airPtrElemPtr(inst),
|
|
|
|
.optional_payload => try self.airOptionalPayload(body[i..]),
|
|
.optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
|
|
.optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
|
|
|
|
.unwrap_errunion_payload => try self.airErrUnionPayload(body[i..], false),
|
|
.unwrap_errunion_payload_ptr => try self.airErrUnionPayload(body[i..], true),
|
|
.unwrap_errunion_err => try self.airErrUnionErr(inst, false),
|
|
.unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true),
|
|
.errunion_payload_ptr_set => try self.airErrUnionPayloadPtrSet(inst),
|
|
.err_return_trace => try self.airErrReturnTrace(inst),
|
|
.set_err_return_trace => try self.airSetErrReturnTrace(inst),
|
|
.save_err_return_trace_index => try self.airSaveErrReturnTraceIndex(inst),
|
|
|
|
.wrap_optional => try self.airWrapOptional(inst),
|
|
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
|
|
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
|
|
|
|
.wasm_memory_size => try self.airWasmMemorySize(inst),
|
|
.wasm_memory_grow => try self.airWasmMemoryGrow(inst),
|
|
|
|
.vector_store_elem => try self.airVectorStoreElem(inst),
|
|
|
|
.inferred_alloc, .inferred_alloc_comptime => unreachable,
|
|
|
|
.unreach => self.airUnreach(inst),
|
|
.dbg_stmt => self.airDbgStmt(inst),
|
|
.dbg_inline_begin => try self.airDbgInlineBegin(inst),
|
|
.dbg_inline_end => try self.airDbgInlineEnd(inst),
|
|
.dbg_block_begin => try self.airDbgBlockBegin(),
|
|
.dbg_block_end => try self.airDbgBlockEnd(),
|
|
.dbg_var_ptr => try self.airDbgVarPtr(inst),
|
|
.dbg_var_val => try self.airDbgVarVal(inst),
|
|
|
|
.c_va_arg => try self.airCVaArg(inst),
|
|
.c_va_copy => try self.airCVaCopy(inst),
|
|
.c_va_end => try self.airCVaEnd(inst),
|
|
.c_va_start => try self.airCVaStart(inst),
|
|
|
|
.work_item_id => try self.airWorkItemId(inst),
|
|
.work_group_size => try self.airWorkGroupSize(inst),
|
|
.work_group_id => try self.airWorkGroupId(inst),
|
|
// zig fmt: on
|
|
};
|
|
if (opt_value) |val| {
|
|
const ref = Air.indexToRef(inst);
|
|
try self.func_inst_table.putNoClobber(self.gpa, ref, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn airCall(self: *FuncGen, inst: Air.Inst.Index, attr: llvm.CallAttr) !?*llvm.Value {
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const extra = self.air.extraData(Air.Call, pl_op.payload);
|
|
const args = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[extra.end..][0..extra.data.args_len]));
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ip = &mod.intern_pool;
|
|
const callee_ty = self.typeOf(pl_op.operand);
|
|
const zig_fn_ty = switch (callee_ty.zigTypeTag(mod)) {
|
|
.Fn => callee_ty,
|
|
.Pointer => callee_ty.childType(mod),
|
|
else => unreachable,
|
|
};
|
|
const fn_info = mod.typeToFunc(zig_fn_ty).?;
|
|
const return_type = fn_info.return_type.toType();
|
|
const llvm_fn = try self.resolveInst(pl_op.operand);
|
|
const target = mod.getTarget();
|
|
const sret = firstParamSRet(fn_info, mod);
|
|
|
|
var llvm_args = std.ArrayList(*llvm.Value).init(self.gpa);
|
|
defer llvm_args.deinit();
|
|
|
|
const ret_ptr = if (!sret) null else blk: {
|
|
const llvm_ret_ty = try o.lowerType(return_type);
|
|
const ret_ptr = self.buildAlloca(llvm_ret_ty, return_type.abiAlignment(mod));
|
|
try llvm_args.append(ret_ptr);
|
|
break :blk ret_ptr;
|
|
};
|
|
|
|
const err_return_tracing = return_type.isError(mod) and
|
|
o.module.comp.bin_file.options.error_return_tracing;
|
|
if (err_return_tracing) {
|
|
try llvm_args.append(self.err_ret_trace.?);
|
|
}
|
|
|
|
var it = iterateParamTypes(o, fn_info);
|
|
while (it.nextCall(self, args)) |lowering| switch (lowering) {
|
|
.no_bits => continue,
|
|
.byval => {
|
|
const arg = args[it.zig_index - 1];
|
|
const param_ty = self.typeOf(arg);
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
const llvm_param_ty = try o.lowerType(param_ty);
|
|
if (isByRef(param_ty, mod)) {
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const load_inst = self.builder.buildLoad(llvm_param_ty, llvm_arg, "");
|
|
load_inst.setAlignment(alignment);
|
|
try llvm_args.append(load_inst);
|
|
} else {
|
|
try llvm_args.append(llvm_arg);
|
|
}
|
|
},
|
|
.byref => {
|
|
const arg = args[it.zig_index - 1];
|
|
const param_ty = self.typeOf(arg);
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
if (isByRef(param_ty, mod)) {
|
|
try llvm_args.append(llvm_arg);
|
|
} else {
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const param_llvm_ty = llvm_arg.typeOf();
|
|
const arg_ptr = self.buildAlloca(param_llvm_ty, alignment);
|
|
const store_inst = self.builder.buildStore(llvm_arg, arg_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
try llvm_args.append(arg_ptr);
|
|
}
|
|
},
|
|
.byref_mut => {
|
|
const arg = args[it.zig_index - 1];
|
|
const param_ty = self.typeOf(arg);
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const arg_ptr = self.buildAlloca(param_llvm_ty, alignment);
|
|
if (isByRef(param_ty, mod)) {
|
|
const load_inst = self.builder.buildLoad(param_llvm_ty, llvm_arg, "");
|
|
load_inst.setAlignment(alignment);
|
|
|
|
const store_inst = self.builder.buildStore(load_inst, arg_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
try llvm_args.append(arg_ptr);
|
|
} else {
|
|
const store_inst = self.builder.buildStore(llvm_arg, arg_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
try llvm_args.append(arg_ptr);
|
|
}
|
|
},
|
|
.abi_sized_int => {
|
|
const arg = args[it.zig_index - 1];
|
|
const param_ty = self.typeOf(arg);
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
const abi_size = @as(c_uint, @intCast(param_ty.abiSize(mod)));
|
|
const int_llvm_ty = self.context.intType(abi_size * 8);
|
|
|
|
if (isByRef(param_ty, mod)) {
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
const load_inst = self.builder.buildLoad(int_llvm_ty, llvm_arg, "");
|
|
load_inst.setAlignment(alignment);
|
|
try llvm_args.append(load_inst);
|
|
} else {
|
|
// LLVM does not allow bitcasting structs so we must allocate
|
|
// a local, store as one type, and then load as another type.
|
|
const alignment = @max(
|
|
param_ty.abiAlignment(mod),
|
|
o.target_data.abiAlignmentOfType(int_llvm_ty),
|
|
);
|
|
const int_ptr = self.buildAlloca(int_llvm_ty, alignment);
|
|
const store_inst = self.builder.buildStore(llvm_arg, int_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
const load_inst = self.builder.buildLoad(int_llvm_ty, int_ptr, "");
|
|
load_inst.setAlignment(alignment);
|
|
try llvm_args.append(load_inst);
|
|
}
|
|
},
|
|
.slice => {
|
|
const arg = args[it.zig_index - 1];
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
const ptr = self.builder.buildExtractValue(llvm_arg, 0, "");
|
|
const len = self.builder.buildExtractValue(llvm_arg, 1, "");
|
|
try llvm_args.ensureUnusedCapacity(2);
|
|
llvm_args.appendAssumeCapacity(ptr);
|
|
llvm_args.appendAssumeCapacity(len);
|
|
},
|
|
.multiple_llvm_types => {
|
|
const arg = args[it.zig_index - 1];
|
|
const param_ty = self.typeOf(arg);
|
|
const llvm_types = it.llvm_types_buffer[0..it.llvm_types_len];
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
const is_by_ref = isByRef(param_ty, mod);
|
|
const arg_ptr = if (is_by_ref) llvm_arg else p: {
|
|
const p = self.buildAlloca(llvm_arg.typeOf(), null);
|
|
const store_inst = self.builder.buildStore(llvm_arg, p);
|
|
store_inst.setAlignment(param_ty.abiAlignment(mod));
|
|
break :p p;
|
|
};
|
|
|
|
const llvm_ty = self.context.structType(llvm_types.ptr, @as(c_uint, @intCast(llvm_types.len)), .False);
|
|
try llvm_args.ensureUnusedCapacity(it.llvm_types_len);
|
|
for (llvm_types, 0..) |field_ty, i_usize| {
|
|
const i = @as(c_uint, @intCast(i_usize));
|
|
const field_ptr = self.builder.buildStructGEP(llvm_ty, arg_ptr, i, "");
|
|
const load_inst = self.builder.buildLoad(field_ty, field_ptr, "");
|
|
load_inst.setAlignment(target.ptrBitWidth() / 8);
|
|
llvm_args.appendAssumeCapacity(load_inst);
|
|
}
|
|
},
|
|
.as_u16 => {
|
|
const arg = args[it.zig_index - 1];
|
|
const llvm_arg = try self.resolveInst(arg);
|
|
const casted = self.builder.buildBitCast(llvm_arg, self.context.intType(16), "");
|
|
try llvm_args.append(casted);
|
|
},
|
|
.float_array => |count| {
|
|
const arg = args[it.zig_index - 1];
|
|
const arg_ty = self.typeOf(arg);
|
|
var llvm_arg = try self.resolveInst(arg);
|
|
if (!isByRef(arg_ty, mod)) {
|
|
const p = self.buildAlloca(llvm_arg.typeOf(), null);
|
|
const store_inst = self.builder.buildStore(llvm_arg, p);
|
|
store_inst.setAlignment(arg_ty.abiAlignment(mod));
|
|
llvm_arg = store_inst;
|
|
}
|
|
|
|
const float_ty = try o.lowerType(aarch64_c_abi.getFloatArrayType(arg_ty, mod).?);
|
|
const array_llvm_ty = float_ty.arrayType(count);
|
|
|
|
const alignment = arg_ty.abiAlignment(mod);
|
|
const load_inst = self.builder.buildLoad(array_llvm_ty, llvm_arg, "");
|
|
load_inst.setAlignment(alignment);
|
|
try llvm_args.append(load_inst);
|
|
},
|
|
.i32_array, .i64_array => |arr_len| {
|
|
const elem_size: u8 = if (lowering == .i32_array) 32 else 64;
|
|
const arg = args[it.zig_index - 1];
|
|
const arg_ty = self.typeOf(arg);
|
|
var llvm_arg = try self.resolveInst(arg);
|
|
if (!isByRef(arg_ty, mod)) {
|
|
const p = self.buildAlloca(llvm_arg.typeOf(), null);
|
|
const store_inst = self.builder.buildStore(llvm_arg, p);
|
|
store_inst.setAlignment(arg_ty.abiAlignment(mod));
|
|
llvm_arg = store_inst;
|
|
}
|
|
|
|
const array_llvm_ty = self.context.intType(elem_size).arrayType(arr_len);
|
|
const alignment = arg_ty.abiAlignment(mod);
|
|
const load_inst = self.builder.buildLoad(array_llvm_ty, llvm_arg, "");
|
|
load_inst.setAlignment(alignment);
|
|
try llvm_args.append(load_inst);
|
|
},
|
|
};
|
|
|
|
const call = self.builder.buildCall(
|
|
try o.lowerType(zig_fn_ty),
|
|
llvm_fn,
|
|
llvm_args.items.ptr,
|
|
@as(c_uint, @intCast(llvm_args.items.len)),
|
|
toLlvmCallConv(fn_info.cc, target),
|
|
attr,
|
|
"",
|
|
);
|
|
|
|
if (callee_ty.zigTypeTag(mod) == .Pointer) {
|
|
// Add argument attributes for function pointer calls.
|
|
it = iterateParamTypes(o, fn_info);
|
|
it.llvm_index += @intFromBool(sret);
|
|
it.llvm_index += @intFromBool(err_return_tracing);
|
|
while (it.next()) |lowering| switch (lowering) {
|
|
.byval => {
|
|
const param_index = it.zig_index - 1;
|
|
const param_ty = fn_info.param_types.get(ip)[param_index].toType();
|
|
if (!isByRef(param_ty, mod)) {
|
|
o.addByValParamAttrs(call, param_ty, param_index, fn_info, it.llvm_index - 1);
|
|
}
|
|
},
|
|
.byref => {
|
|
const param_index = it.zig_index - 1;
|
|
const param_ty = fn_info.param_types.get(ip)[param_index].toType();
|
|
const param_llvm_ty = try o.lowerType(param_ty);
|
|
const alignment = param_ty.abiAlignment(mod);
|
|
o.addByRefParamAttrs(call, it.llvm_index - 1, alignment, it.byval_attr, param_llvm_ty);
|
|
},
|
|
.byref_mut => {
|
|
o.addArgAttr(call, it.llvm_index - 1, "noundef");
|
|
},
|
|
// No attributes needed for these.
|
|
.no_bits,
|
|
.abi_sized_int,
|
|
.multiple_llvm_types,
|
|
.as_u16,
|
|
.float_array,
|
|
.i32_array,
|
|
.i64_array,
|
|
=> continue,
|
|
|
|
.slice => {
|
|
assert(!it.byval_attr);
|
|
const param_ty = fn_info.param_types.get(ip)[it.zig_index - 1].toType();
|
|
const ptr_info = param_ty.ptrInfo(mod);
|
|
const llvm_arg_i = it.llvm_index - 2;
|
|
|
|
if (math.cast(u5, it.zig_index - 1)) |i| {
|
|
if (@as(u1, @truncate(fn_info.noalias_bits >> i)) != 0) {
|
|
o.addArgAttr(call, llvm_arg_i, "noalias");
|
|
}
|
|
}
|
|
if (param_ty.zigTypeTag(mod) != .Optional) {
|
|
o.addArgAttr(call, llvm_arg_i, "nonnull");
|
|
}
|
|
if (ptr_info.flags.is_const) {
|
|
o.addArgAttr(call, llvm_arg_i, "readonly");
|
|
}
|
|
const elem_align = ptr_info.flags.alignment.toByteUnitsOptional() orelse
|
|
@max(ptr_info.child.toType().abiAlignment(mod), 1);
|
|
o.addArgAttrInt(call, llvm_arg_i, "align", elem_align);
|
|
},
|
|
};
|
|
}
|
|
|
|
if (fn_info.return_type == .noreturn_type and attr != .AlwaysTail) {
|
|
return null;
|
|
}
|
|
|
|
if (self.liveness.isUnused(inst) or !return_type.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return null;
|
|
}
|
|
|
|
const llvm_ret_ty = try o.lowerType(return_type);
|
|
|
|
if (ret_ptr) |rp| {
|
|
call.setCallSret(llvm_ret_ty);
|
|
if (isByRef(return_type, mod)) {
|
|
return rp;
|
|
} else {
|
|
// our by-ref status disagrees with sret so we must load.
|
|
const loaded = self.builder.buildLoad(llvm_ret_ty, rp, "");
|
|
loaded.setAlignment(return_type.abiAlignment(mod));
|
|
return loaded;
|
|
}
|
|
}
|
|
|
|
const abi_ret_ty = try lowerFnRetTy(o, fn_info);
|
|
|
|
if (abi_ret_ty != llvm_ret_ty) {
|
|
// In this case the function return type is honoring the calling convention by having
|
|
// a different LLVM type than the usual one. We solve this here at the callsite
|
|
// by using our canonical type, then loading it if necessary.
|
|
const alignment = o.target_data.abiAlignmentOfType(abi_ret_ty);
|
|
const rp = self.buildAlloca(llvm_ret_ty, alignment);
|
|
const store_inst = self.builder.buildStore(call, rp);
|
|
store_inst.setAlignment(alignment);
|
|
if (isByRef(return_type, mod)) {
|
|
return rp;
|
|
} else {
|
|
const load_inst = self.builder.buildLoad(llvm_ret_ty, rp, "");
|
|
load_inst.setAlignment(alignment);
|
|
return load_inst;
|
|
}
|
|
}
|
|
|
|
if (isByRef(return_type, mod)) {
|
|
// our by-ref status disagrees with sret so we must allocate, store,
|
|
// and return the allocation pointer.
|
|
const alignment = return_type.abiAlignment(mod);
|
|
const rp = self.buildAlloca(llvm_ret_ty, alignment);
|
|
const store_inst = self.builder.buildStore(call, rp);
|
|
store_inst.setAlignment(alignment);
|
|
return rp;
|
|
} else {
|
|
return call;
|
|
}
|
|
}
|
|
|
|
fn buildSimplePanic(fg: *FuncGen, panic_id: Module.PanicId) !void {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const msg_decl_index = mod.panic_messages[@intFromEnum(panic_id)].unwrap().?;
|
|
const msg_decl = mod.declPtr(msg_decl_index);
|
|
const msg_len = msg_decl.ty.childType(mod).arrayLen(mod);
|
|
const msg_ptr = try o.lowerValue(.{
|
|
.ty = msg_decl.ty,
|
|
.val = msg_decl.val,
|
|
});
|
|
const null_opt_addr_global = try o.getNullOptAddr();
|
|
const target = mod.getTarget();
|
|
const llvm_usize = fg.context.intType(target.ptrBitWidth());
|
|
// example:
|
|
// call fastcc void @test2.panic(
|
|
// ptr @builtin.panic_messages.integer_overflow__anon_987, ; msg.ptr
|
|
// i64 16, ; msg.len
|
|
// ptr null, ; stack trace
|
|
// ptr @2, ; addr (null ?usize)
|
|
// )
|
|
const args = [4]*llvm.Value{
|
|
msg_ptr,
|
|
llvm_usize.constInt(msg_len, .False),
|
|
fg.context.pointerType(0).constNull(),
|
|
null_opt_addr_global,
|
|
};
|
|
const panic_func = mod.funcInfo(mod.panic_func_index);
|
|
const panic_decl = mod.declPtr(panic_func.owner_decl);
|
|
const fn_info = mod.typeToFunc(panic_decl.ty).?;
|
|
const panic_global = try o.resolveLlvmFunction(panic_func.owner_decl);
|
|
_ = fg.builder.buildCall(
|
|
try o.lowerType(panic_decl.ty),
|
|
panic_global,
|
|
&args,
|
|
args.len,
|
|
toLlvmCallConv(fn_info.cc, target),
|
|
.Auto,
|
|
"",
|
|
);
|
|
_ = fg.builder.buildUnreachable();
|
|
}
|
|
|
|
fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const ret_ty = self.typeOf(un_op);
|
|
if (self.ret_ptr) |ret_ptr| {
|
|
const operand = try self.resolveInst(un_op);
|
|
const ptr_ty = try mod.singleMutPtrType(ret_ty);
|
|
try self.store(ret_ptr, ptr_ty, operand, .NotAtomic);
|
|
_ = self.builder.buildRetVoid();
|
|
return null;
|
|
}
|
|
const fn_info = mod.typeToFunc(self.dg.decl.ty).?;
|
|
if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
if (fn_info.return_type.toType().isError(mod)) {
|
|
// Functions with an empty error set are emitted with an error code
|
|
// return type and return zero so they can be function pointers coerced
|
|
// to functions that return anyerror.
|
|
const err_int = try o.lowerType(Type.anyerror);
|
|
_ = self.builder.buildRet(err_int.constInt(0, .False));
|
|
} else {
|
|
_ = self.builder.buildRetVoid();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const abi_ret_ty = try lowerFnRetTy(o, fn_info);
|
|
const operand = try self.resolveInst(un_op);
|
|
const alignment = ret_ty.abiAlignment(mod);
|
|
|
|
if (isByRef(ret_ty, mod)) {
|
|
// operand is a pointer however self.ret_ptr is null so that means
|
|
// we need to return a value.
|
|
const load_inst = self.builder.buildLoad(abi_ret_ty, operand, "");
|
|
load_inst.setAlignment(alignment);
|
|
_ = self.builder.buildRet(load_inst);
|
|
return null;
|
|
}
|
|
|
|
const llvm_ret_ty = operand.typeOf();
|
|
if (abi_ret_ty == llvm_ret_ty) {
|
|
_ = self.builder.buildRet(operand);
|
|
return null;
|
|
}
|
|
|
|
const rp = self.buildAlloca(llvm_ret_ty, alignment);
|
|
const store_inst = self.builder.buildStore(operand, rp);
|
|
store_inst.setAlignment(alignment);
|
|
const load_inst = self.builder.buildLoad(abi_ret_ty, rp, "");
|
|
load_inst.setAlignment(alignment);
|
|
_ = self.builder.buildRet(load_inst);
|
|
return null;
|
|
}
|
|
|
|
fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const ptr_ty = self.typeOf(un_op);
|
|
const ret_ty = ptr_ty.childType(mod);
|
|
const fn_info = mod.typeToFunc(self.dg.decl.ty).?;
|
|
if (!ret_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
if (fn_info.return_type.toType().isError(mod)) {
|
|
// Functions with an empty error set are emitted with an error code
|
|
// return type and return zero so they can be function pointers coerced
|
|
// to functions that return anyerror.
|
|
const err_int = try o.lowerType(Type.anyerror);
|
|
_ = self.builder.buildRet(err_int.constInt(0, .False));
|
|
} else {
|
|
_ = self.builder.buildRetVoid();
|
|
}
|
|
return null;
|
|
}
|
|
if (self.ret_ptr != null) {
|
|
_ = self.builder.buildRetVoid();
|
|
return null;
|
|
}
|
|
const ptr = try self.resolveInst(un_op);
|
|
const abi_ret_ty = try lowerFnRetTy(o, fn_info);
|
|
const loaded = self.builder.buildLoad(abi_ret_ty, ptr, "");
|
|
loaded.setAlignment(ret_ty.abiAlignment(mod));
|
|
_ = self.builder.buildRet(loaded);
|
|
return null;
|
|
}
|
|
|
|
fn airCVaArg(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const list = try self.resolveInst(ty_op.operand);
|
|
const arg_ty = self.air.getRefType(ty_op.ty);
|
|
const llvm_arg_ty = try o.lowerType(arg_ty);
|
|
|
|
return self.builder.buildVAArg(list, llvm_arg_ty, "");
|
|
}
|
|
|
|
fn airCVaCopy(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const src_list = try self.resolveInst(ty_op.operand);
|
|
const va_list_ty = self.air.getRefType(ty_op.ty);
|
|
const llvm_va_list_ty = try o.lowerType(va_list_ty);
|
|
const mod = o.module;
|
|
|
|
const result_alignment = va_list_ty.abiAlignment(mod);
|
|
const dest_list = self.buildAlloca(llvm_va_list_ty, result_alignment);
|
|
|
|
const llvm_fn_name = "llvm.va_copy";
|
|
const llvm_fn = o.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
|
|
const param_types = [_]*llvm.Type{
|
|
self.context.pointerType(0),
|
|
self.context.pointerType(0),
|
|
};
|
|
const fn_type = llvm.functionType(self.context.voidType(), ¶m_types, param_types.len, .False);
|
|
break :blk o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
};
|
|
|
|
const args: [2]*llvm.Value = .{ dest_list, src_list };
|
|
_ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
|
|
if (isByRef(va_list_ty, mod)) {
|
|
return dest_list;
|
|
} else {
|
|
const loaded = self.builder.buildLoad(llvm_va_list_ty, dest_list, "");
|
|
loaded.setAlignment(result_alignment);
|
|
return loaded;
|
|
}
|
|
}
|
|
|
|
fn airCVaEnd(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const list = try self.resolveInst(un_op);
|
|
|
|
const llvm_fn_name = "llvm.va_end";
|
|
const llvm_fn = o.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
|
|
const param_types = [_]*llvm.Type{self.context.pointerType(0)};
|
|
const fn_type = llvm.functionType(self.context.voidType(), ¶m_types, param_types.len, .False);
|
|
break :blk o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
};
|
|
const args: [1]*llvm.Value = .{list};
|
|
_ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
return null;
|
|
}
|
|
|
|
fn airCVaStart(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const va_list_ty = self.typeOfIndex(inst);
|
|
const llvm_va_list_ty = try o.lowerType(va_list_ty);
|
|
|
|
const result_alignment = va_list_ty.abiAlignment(mod);
|
|
const list = self.buildAlloca(llvm_va_list_ty, result_alignment);
|
|
|
|
const llvm_fn_name = "llvm.va_start";
|
|
const llvm_fn = o.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
|
|
const param_types = [_]*llvm.Type{self.context.pointerType(0)};
|
|
const fn_type = llvm.functionType(self.context.voidType(), ¶m_types, param_types.len, .False);
|
|
break :blk o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
};
|
|
const args: [1]*llvm.Value = .{list};
|
|
_ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
|
|
if (isByRef(va_list_ty, mod)) {
|
|
return list;
|
|
} else {
|
|
const loaded = self.builder.buildLoad(llvm_va_list_ty, list, "");
|
|
loaded.setAlignment(result_alignment);
|
|
return loaded;
|
|
}
|
|
}
|
|
|
|
fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const operand_ty = self.typeOf(bin_op.lhs);
|
|
|
|
return self.cmp(lhs, rhs, operand_ty, op);
|
|
}
|
|
|
|
fn airCmpVector(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.VectorCmp, ty_pl.payload).data;
|
|
|
|
const lhs = try self.resolveInst(extra.lhs);
|
|
const rhs = try self.resolveInst(extra.rhs);
|
|
const vec_ty = self.typeOf(extra.lhs);
|
|
const cmp_op = extra.compareOperator();
|
|
|
|
return self.cmp(lhs, rhs, vec_ty, cmp_op);
|
|
}
|
|
|
|
fn airCmpLtErrorsLen(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const llvm_fn = try self.getCmpLtErrorsLenFunction();
|
|
const args: [1]*llvm.Value = .{operand};
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
}
|
|
|
|
fn cmp(
|
|
self: *FuncGen,
|
|
lhs: *llvm.Value,
|
|
rhs: *llvm.Value,
|
|
operand_ty: Type,
|
|
op: math.CompareOperator,
|
|
) Allocator.Error!*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const scalar_ty = operand_ty.scalarType(mod);
|
|
const int_ty = switch (scalar_ty.zigTypeTag(mod)) {
|
|
.Enum => scalar_ty.intTagType(mod),
|
|
.Int, .Bool, .Pointer, .ErrorSet => scalar_ty,
|
|
.Optional => blk: {
|
|
const payload_ty = operand_ty.optionalChild(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod) or
|
|
operand_ty.optionalReprIsPayload(mod))
|
|
{
|
|
break :blk operand_ty;
|
|
}
|
|
// We need to emit instructions to check for equality/inequality
|
|
// of optionals that are not pointers.
|
|
const is_by_ref = isByRef(scalar_ty, mod);
|
|
const opt_llvm_ty = try o.lowerType(scalar_ty);
|
|
const lhs_non_null = self.optIsNonNull(opt_llvm_ty, lhs, is_by_ref);
|
|
const rhs_non_null = self.optIsNonNull(opt_llvm_ty, rhs, is_by_ref);
|
|
const llvm_i2 = self.context.intType(2);
|
|
const lhs_non_null_i2 = self.builder.buildZExt(lhs_non_null, llvm_i2, "");
|
|
const rhs_non_null_i2 = self.builder.buildZExt(rhs_non_null, llvm_i2, "");
|
|
const lhs_shifted = self.builder.buildShl(lhs_non_null_i2, llvm_i2.constInt(1, .False), "");
|
|
const lhs_rhs_ored = self.builder.buildOr(lhs_shifted, rhs_non_null_i2, "");
|
|
const both_null_block = self.context.appendBasicBlock(self.llvm_func, "BothNull");
|
|
const mixed_block = self.context.appendBasicBlock(self.llvm_func, "Mixed");
|
|
const both_pl_block = self.context.appendBasicBlock(self.llvm_func, "BothNonNull");
|
|
const end_block = self.context.appendBasicBlock(self.llvm_func, "End");
|
|
const llvm_switch = self.builder.buildSwitch(lhs_rhs_ored, mixed_block, 2);
|
|
const llvm_i2_00 = llvm_i2.constInt(0b00, .False);
|
|
const llvm_i2_11 = llvm_i2.constInt(0b11, .False);
|
|
llvm_switch.addCase(llvm_i2_00, both_null_block);
|
|
llvm_switch.addCase(llvm_i2_11, both_pl_block);
|
|
|
|
self.builder.positionBuilderAtEnd(both_null_block);
|
|
_ = self.builder.buildBr(end_block);
|
|
|
|
self.builder.positionBuilderAtEnd(mixed_block);
|
|
_ = self.builder.buildBr(end_block);
|
|
|
|
self.builder.positionBuilderAtEnd(both_pl_block);
|
|
const lhs_payload = try self.optPayloadHandle(opt_llvm_ty, lhs, scalar_ty, true);
|
|
const rhs_payload = try self.optPayloadHandle(opt_llvm_ty, rhs, scalar_ty, true);
|
|
const payload_cmp = try self.cmp(lhs_payload, rhs_payload, payload_ty, op);
|
|
_ = self.builder.buildBr(end_block);
|
|
const both_pl_block_end = self.builder.getInsertBlock();
|
|
|
|
self.builder.positionBuilderAtEnd(end_block);
|
|
const incoming_blocks: [3]*llvm.BasicBlock = .{
|
|
both_null_block,
|
|
mixed_block,
|
|
both_pl_block_end,
|
|
};
|
|
const llvm_i1 = self.context.intType(1);
|
|
const llvm_i1_0 = llvm_i1.constInt(0, .False);
|
|
const llvm_i1_1 = llvm_i1.constInt(1, .False);
|
|
const incoming_values: [3]*llvm.Value = .{
|
|
switch (op) {
|
|
.eq => llvm_i1_1,
|
|
.neq => llvm_i1_0,
|
|
else => unreachable,
|
|
},
|
|
switch (op) {
|
|
.eq => llvm_i1_0,
|
|
.neq => llvm_i1_1,
|
|
else => unreachable,
|
|
},
|
|
payload_cmp,
|
|
};
|
|
|
|
const phi_node = self.builder.buildPhi(llvm_i1, "");
|
|
comptime assert(incoming_values.len == incoming_blocks.len);
|
|
phi_node.addIncoming(
|
|
&incoming_values,
|
|
&incoming_blocks,
|
|
incoming_values.len,
|
|
);
|
|
return phi_node;
|
|
},
|
|
.Float => return self.buildFloatCmp(op, operand_ty, .{ lhs, rhs }),
|
|
else => unreachable,
|
|
};
|
|
const is_signed = int_ty.isSignedInt(mod);
|
|
const operation: llvm.IntPredicate = switch (op) {
|
|
.eq => .EQ,
|
|
.neq => .NE,
|
|
.lt => if (is_signed) llvm.IntPredicate.SLT else .ULT,
|
|
.lte => if (is_signed) llvm.IntPredicate.SLE else .ULE,
|
|
.gt => if (is_signed) llvm.IntPredicate.SGT else .UGT,
|
|
.gte => if (is_signed) llvm.IntPredicate.SGE else .UGE,
|
|
};
|
|
return self.builder.buildICmp(operation, lhs, rhs, "");
|
|
}
|
|
|
|
fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.Block, ty_pl.payload);
|
|
const body = self.air.extra[extra.end..][0..extra.data.body_len];
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const parent_bb = self.context.createBasicBlock("Block");
|
|
|
|
if (inst_ty.isNoReturn(mod)) {
|
|
try self.genBody(body);
|
|
return null;
|
|
}
|
|
|
|
var breaks: BreakList = .{};
|
|
defer breaks.deinit(self.gpa);
|
|
|
|
try self.blocks.putNoClobber(self.gpa, inst, .{
|
|
.parent_bb = parent_bb,
|
|
.breaks = &breaks,
|
|
});
|
|
defer assert(self.blocks.remove(inst));
|
|
|
|
try self.genBody(body);
|
|
|
|
self.llvm_func.appendExistingBasicBlock(parent_bb);
|
|
self.builder.positionBuilderAtEnd(parent_bb);
|
|
|
|
// Create a phi node only if the block returns a value.
|
|
const is_body = inst_ty.zigTypeTag(mod) == .Fn;
|
|
if (!is_body and !inst_ty.hasRuntimeBitsIgnoreComptime(mod)) return null;
|
|
|
|
const raw_llvm_ty = try o.lowerType(inst_ty);
|
|
|
|
const llvm_ty = ty: {
|
|
// If the zig tag type is a function, this represents an actual function body; not
|
|
// a pointer to it. LLVM IR allows the call instruction to use function bodies instead
|
|
// of function pointers, however the phi makes it a runtime value and therefore
|
|
// the LLVM type has to be wrapped in a pointer.
|
|
if (is_body or isByRef(inst_ty, mod)) {
|
|
break :ty self.context.pointerType(0);
|
|
}
|
|
break :ty raw_llvm_ty;
|
|
};
|
|
|
|
const phi_node = self.builder.buildPhi(llvm_ty, "");
|
|
phi_node.addIncoming(
|
|
breaks.items(.val).ptr,
|
|
breaks.items(.bb).ptr,
|
|
@as(c_uint, @intCast(breaks.len)),
|
|
);
|
|
return phi_node;
|
|
}
|
|
|
|
fn airBr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const branch = self.air.instructions.items(.data)[inst].br;
|
|
const block = self.blocks.get(branch.block_inst).?;
|
|
|
|
// Add the values to the lists only if the break provides a value.
|
|
const operand_ty = self.typeOf(branch.operand);
|
|
const mod = o.module;
|
|
if (operand_ty.hasRuntimeBitsIgnoreComptime(mod) or operand_ty.zigTypeTag(mod) == .Fn) {
|
|
const val = try self.resolveInst(branch.operand);
|
|
|
|
// For the phi node, we need the basic blocks and the values of the
|
|
// break instructions.
|
|
try block.breaks.append(self.gpa, .{
|
|
.bb = self.builder.getInsertBlock(),
|
|
.val = val,
|
|
});
|
|
}
|
|
_ = self.builder.buildBr(block.parent_bb);
|
|
return null;
|
|
}
|
|
|
|
fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const cond = try self.resolveInst(pl_op.operand);
|
|
const extra = self.air.extraData(Air.CondBr, pl_op.payload);
|
|
const then_body = self.air.extra[extra.end..][0..extra.data.then_body_len];
|
|
const else_body = self.air.extra[extra.end + then_body.len ..][0..extra.data.else_body_len];
|
|
|
|
const then_block = self.context.appendBasicBlock(self.llvm_func, "Then");
|
|
const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
|
|
_ = self.builder.buildCondBr(cond, then_block, else_block);
|
|
|
|
self.builder.positionBuilderAtEnd(then_block);
|
|
try self.genBody(then_body);
|
|
|
|
self.builder.positionBuilderAtEnd(else_block);
|
|
try self.genBody(else_body);
|
|
|
|
// No need to reset the insert cursor since this instruction is noreturn.
|
|
return null;
|
|
}
|
|
|
|
fn airTry(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const err_union = try self.resolveInst(pl_op.operand);
|
|
const extra = self.air.extraData(Air.Try, pl_op.payload);
|
|
const body = self.air.extra[extra.end..][0..extra.data.body_len];
|
|
const err_union_ty = self.typeOf(pl_op.operand);
|
|
const payload_ty = self.typeOfIndex(inst);
|
|
const can_elide_load = if (isByRef(payload_ty, mod)) self.canElideLoad(body_tail) else false;
|
|
const is_unused = self.liveness.isUnused(inst);
|
|
return lowerTry(self, err_union, body, err_union_ty, false, can_elide_load, is_unused);
|
|
}
|
|
|
|
fn airTryPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.TryPtr, ty_pl.payload);
|
|
const err_union_ptr = try self.resolveInst(extra.data.ptr);
|
|
const body = self.air.extra[extra.end..][0..extra.data.body_len];
|
|
const err_union_ty = self.typeOf(extra.data.ptr).childType(mod);
|
|
const is_unused = self.liveness.isUnused(inst);
|
|
return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, is_unused);
|
|
}
|
|
|
|
fn lowerTry(
|
|
fg: *FuncGen,
|
|
err_union: *llvm.Value,
|
|
body: []const Air.Inst.Index,
|
|
err_union_ty: Type,
|
|
operand_is_ptr: bool,
|
|
can_elide_load: bool,
|
|
is_unused: bool,
|
|
) !?*llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const payload_ty = err_union_ty.errorUnionPayload(mod);
|
|
const payload_has_bits = payload_ty.hasRuntimeBitsIgnoreComptime(mod);
|
|
const err_union_llvm_ty = try o.lowerType(err_union_ty);
|
|
|
|
if (!err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
|
|
const is_err = err: {
|
|
const err_set_ty = try o.lowerType(Type.anyerror);
|
|
const zero = err_set_ty.constNull();
|
|
if (!payload_has_bits) {
|
|
// TODO add alignment to this load
|
|
const loaded = if (operand_is_ptr)
|
|
fg.builder.buildLoad(err_set_ty, err_union, "")
|
|
else
|
|
err_union;
|
|
break :err fg.builder.buildICmp(.NE, loaded, zero, "");
|
|
}
|
|
const err_field_index = errUnionErrorOffset(payload_ty, mod);
|
|
if (operand_is_ptr or isByRef(err_union_ty, mod)) {
|
|
const err_field_ptr = fg.builder.buildStructGEP(err_union_llvm_ty, err_union, err_field_index, "");
|
|
// TODO add alignment to this load
|
|
const loaded = fg.builder.buildLoad(err_set_ty, err_field_ptr, "");
|
|
break :err fg.builder.buildICmp(.NE, loaded, zero, "");
|
|
}
|
|
const loaded = fg.builder.buildExtractValue(err_union, err_field_index, "");
|
|
break :err fg.builder.buildICmp(.NE, loaded, zero, "");
|
|
};
|
|
|
|
const return_block = fg.context.appendBasicBlock(fg.llvm_func, "TryRet");
|
|
const continue_block = fg.context.appendBasicBlock(fg.llvm_func, "TryCont");
|
|
_ = fg.builder.buildCondBr(is_err, return_block, continue_block);
|
|
|
|
fg.builder.positionBuilderAtEnd(return_block);
|
|
try fg.genBody(body);
|
|
|
|
fg.builder.positionBuilderAtEnd(continue_block);
|
|
}
|
|
if (is_unused) {
|
|
return null;
|
|
}
|
|
if (!payload_has_bits) {
|
|
return if (operand_is_ptr) err_union else null;
|
|
}
|
|
const offset = errUnionPayloadOffset(payload_ty, mod);
|
|
if (operand_is_ptr) {
|
|
return fg.builder.buildStructGEP(err_union_llvm_ty, err_union, offset, "");
|
|
} else if (isByRef(err_union_ty, mod)) {
|
|
const payload_ptr = fg.builder.buildStructGEP(err_union_llvm_ty, err_union, offset, "");
|
|
if (isByRef(payload_ty, mod)) {
|
|
if (can_elide_load)
|
|
return payload_ptr;
|
|
|
|
return fg.loadByRef(payload_ptr, payload_ty, payload_ty.abiAlignment(mod), false);
|
|
}
|
|
const load_inst = fg.builder.buildLoad(err_union_llvm_ty.structGetTypeAtIndex(offset), payload_ptr, "");
|
|
load_inst.setAlignment(payload_ty.abiAlignment(mod));
|
|
return load_inst;
|
|
}
|
|
return fg.builder.buildExtractValue(err_union, offset, "");
|
|
}
|
|
|
|
fn airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const cond = try self.resolveInst(pl_op.operand);
|
|
const switch_br = self.air.extraData(Air.SwitchBr, pl_op.payload);
|
|
const else_block = self.context.appendBasicBlock(self.llvm_func, "Else");
|
|
const target = mod.getTarget();
|
|
const llvm_usize = self.context.intType(target.ptrBitWidth());
|
|
const cond_int = if (cond.typeOf().getTypeKind() == .Pointer)
|
|
self.builder.buildPtrToInt(cond, llvm_usize, "")
|
|
else
|
|
cond;
|
|
const llvm_switch = self.builder.buildSwitch(cond_int, else_block, switch_br.data.cases_len);
|
|
|
|
var extra_index: usize = switch_br.end;
|
|
var case_i: u32 = 0;
|
|
|
|
while (case_i < switch_br.data.cases_len) : (case_i += 1) {
|
|
const case = self.air.extraData(Air.SwitchBr.Case, extra_index);
|
|
const items = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[case.end..][0..case.data.items_len]));
|
|
const case_body = self.air.extra[case.end + items.len ..][0..case.data.body_len];
|
|
extra_index = case.end + case.data.items_len + case_body.len;
|
|
|
|
const case_block = self.context.appendBasicBlock(self.llvm_func, "Case");
|
|
|
|
for (items) |item| {
|
|
const llvm_item = try self.resolveInst(item);
|
|
const llvm_int_item = if (llvm_item.typeOf().getTypeKind() == .Pointer)
|
|
llvm_item.constPtrToInt(llvm_usize)
|
|
else
|
|
llvm_item;
|
|
llvm_switch.addCase(llvm_int_item, case_block);
|
|
}
|
|
|
|
self.builder.positionBuilderAtEnd(case_block);
|
|
try self.genBody(case_body);
|
|
}
|
|
|
|
self.builder.positionBuilderAtEnd(else_block);
|
|
const else_body = self.air.extra[extra_index..][0..switch_br.data.else_body_len];
|
|
if (else_body.len != 0) {
|
|
try self.genBody(else_body);
|
|
} else {
|
|
_ = self.builder.buildUnreachable();
|
|
}
|
|
|
|
// No need to reset the insert cursor since this instruction is noreturn.
|
|
return null;
|
|
}
|
|
|
|
fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const loop = self.air.extraData(Air.Block, ty_pl.payload);
|
|
const body = self.air.extra[loop.end..][0..loop.data.body_len];
|
|
const loop_block = self.context.appendBasicBlock(self.llvm_func, "Loop");
|
|
_ = self.builder.buildBr(loop_block);
|
|
|
|
self.builder.positionBuilderAtEnd(loop_block);
|
|
try self.genBody(body);
|
|
|
|
// TODO instead of this logic, change AIR to have the property that
|
|
// every block is guaranteed to end with a noreturn instruction.
|
|
// Then we can simply rely on the fact that a repeat or break instruction
|
|
// would have been emitted already. Also the main loop in genBody can
|
|
// be while(true) instead of for(body), which will eliminate 1 branch on
|
|
// a hot path.
|
|
if (body.len == 0 or !self.typeOfIndex(body[body.len - 1]).isNoReturn(mod)) {
|
|
_ = self.builder.buildBr(loop_block);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn airArrayToSlice(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const array_ty = operand_ty.childType(mod);
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const len = llvm_usize.constInt(array_ty.arrayLen(mod), .False);
|
|
const slice_llvm_ty = try o.lowerType(self.typeOfIndex(inst));
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
if (!array_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const partial = self.builder.buildInsertValue(slice_llvm_ty.getUndef(), operand, 0, "");
|
|
return self.builder.buildInsertValue(partial, len, 1, "");
|
|
}
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_usize.constNull(), llvm_usize.constNull(),
|
|
};
|
|
const array_llvm_ty = try o.lowerType(array_ty);
|
|
const ptr = self.builder.buildInBoundsGEP(array_llvm_ty, operand, &indices, indices.len, "");
|
|
const partial = self.builder.buildInsertValue(slice_llvm_ty.getUndef(), ptr, 0, "");
|
|
return self.builder.buildInsertValue(partial, len, 1, "");
|
|
}
|
|
|
|
fn airFloatFromInt(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const operand_scalar_ty = operand_ty.scalarType(mod);
|
|
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
const dest_scalar_ty = dest_ty.scalarType(mod);
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
const target = mod.getTarget();
|
|
|
|
if (intrinsicsAllowed(dest_scalar_ty, target)) {
|
|
if (operand_scalar_ty.isSignedInt(mod)) {
|
|
return self.builder.buildSIToFP(operand, dest_llvm_ty, "");
|
|
} else {
|
|
return self.builder.buildUIToFP(operand, dest_llvm_ty, "");
|
|
}
|
|
}
|
|
|
|
const operand_bits = @as(u16, @intCast(operand_scalar_ty.bitSize(mod)));
|
|
const rt_int_bits = compilerRtIntBits(operand_bits);
|
|
const rt_int_ty = self.context.intType(rt_int_bits);
|
|
var extended = e: {
|
|
if (operand_scalar_ty.isSignedInt(mod)) {
|
|
break :e self.builder.buildSExtOrBitCast(operand, rt_int_ty, "");
|
|
} else {
|
|
break :e self.builder.buildZExtOrBitCast(operand, rt_int_ty, "");
|
|
}
|
|
};
|
|
const dest_bits = dest_scalar_ty.floatBits(target);
|
|
const compiler_rt_operand_abbrev = compilerRtIntAbbrev(rt_int_bits);
|
|
const compiler_rt_dest_abbrev = compilerRtFloatAbbrev(dest_bits);
|
|
const sign_prefix = if (operand_scalar_ty.isSignedInt(mod)) "" else "un";
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const fn_name = std.fmt.bufPrintZ(&fn_name_buf, "__float{s}{s}i{s}f", .{
|
|
sign_prefix,
|
|
compiler_rt_operand_abbrev,
|
|
compiler_rt_dest_abbrev,
|
|
}) catch unreachable;
|
|
|
|
var param_types = [1]*llvm.Type{rt_int_ty};
|
|
if (rt_int_bits == 128 and (target.os.tag == .windows and target.cpu.arch == .x86_64)) {
|
|
// On Windows x86-64, "ti" functions must use Vector(2, u64) instead of the standard
|
|
// i128 calling convention to adhere to the ABI that LLVM expects compiler-rt to have.
|
|
const v2i64 = self.context.intType(64).vectorType(2);
|
|
extended = self.builder.buildBitCast(extended, v2i64, "");
|
|
param_types = [1]*llvm.Type{v2i64};
|
|
}
|
|
|
|
const libc_fn = self.getLibcFunction(fn_name, ¶m_types, dest_llvm_ty);
|
|
const params = [1]*llvm.Value{extended};
|
|
|
|
return self.builder.buildCall(libc_fn.globalGetValueType(), libc_fn, ¶ms, params.len, .C, .Auto, "");
|
|
}
|
|
|
|
fn airIntFromFloat(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const operand_scalar_ty = operand_ty.scalarType(mod);
|
|
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
const dest_scalar_ty = dest_ty.scalarType(mod);
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
|
|
if (intrinsicsAllowed(operand_scalar_ty, target)) {
|
|
// TODO set fast math flag
|
|
if (dest_scalar_ty.isSignedInt(mod)) {
|
|
return self.builder.buildFPToSI(operand, dest_llvm_ty, "");
|
|
} else {
|
|
return self.builder.buildFPToUI(operand, dest_llvm_ty, "");
|
|
}
|
|
}
|
|
|
|
const rt_int_bits = compilerRtIntBits(@as(u16, @intCast(dest_scalar_ty.bitSize(mod))));
|
|
const ret_ty = self.context.intType(rt_int_bits);
|
|
const libc_ret_ty = if (rt_int_bits == 128 and (target.os.tag == .windows and target.cpu.arch == .x86_64)) b: {
|
|
// On Windows x86-64, "ti" functions must use Vector(2, u64) instead of the standard
|
|
// i128 calling convention to adhere to the ABI that LLVM expects compiler-rt to have.
|
|
break :b self.context.intType(64).vectorType(2);
|
|
} else ret_ty;
|
|
|
|
const operand_bits = operand_scalar_ty.floatBits(target);
|
|
const compiler_rt_operand_abbrev = compilerRtFloatAbbrev(operand_bits);
|
|
|
|
const compiler_rt_dest_abbrev = compilerRtIntAbbrev(rt_int_bits);
|
|
const sign_prefix = if (dest_scalar_ty.isSignedInt(mod)) "" else "uns";
|
|
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const fn_name = std.fmt.bufPrintZ(&fn_name_buf, "__fix{s}{s}f{s}i", .{
|
|
sign_prefix,
|
|
compiler_rt_operand_abbrev,
|
|
compiler_rt_dest_abbrev,
|
|
}) catch unreachable;
|
|
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
const param_types = [1]*llvm.Type{operand_llvm_ty};
|
|
const libc_fn = self.getLibcFunction(fn_name, ¶m_types, libc_ret_ty);
|
|
const params = [1]*llvm.Value{operand};
|
|
|
|
var result = self.builder.buildCall(libc_fn.globalGetValueType(), libc_fn, ¶ms, params.len, .C, .Auto, "");
|
|
|
|
if (libc_ret_ty != ret_ty) result = self.builder.buildBitCast(result, ret_ty, "");
|
|
if (ret_ty != dest_llvm_ty) result = self.builder.buildTrunc(result, dest_llvm_ty, "");
|
|
return result;
|
|
}
|
|
|
|
fn sliceOrArrayPtr(fg: *FuncGen, ptr: *llvm.Value, ty: Type) *llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
if (ty.isSlice(mod)) {
|
|
return fg.builder.buildExtractValue(ptr, 0, "");
|
|
} else {
|
|
return ptr;
|
|
}
|
|
}
|
|
|
|
fn sliceOrArrayLenInBytes(fg: *FuncGen, ptr: *llvm.Value, ty: Type) *llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
const llvm_usize_ty = fg.context.intType(target.ptrBitWidth());
|
|
switch (ty.ptrSize(mod)) {
|
|
.Slice => {
|
|
const len = fg.builder.buildExtractValue(ptr, 1, "");
|
|
const elem_ty = ty.childType(mod);
|
|
const abi_size = elem_ty.abiSize(mod);
|
|
if (abi_size == 1) return len;
|
|
const abi_size_llvm_val = llvm_usize_ty.constInt(abi_size, .False);
|
|
return fg.builder.buildMul(len, abi_size_llvm_val, "");
|
|
},
|
|
.One => {
|
|
const array_ty = ty.childType(mod);
|
|
const elem_ty = array_ty.childType(mod);
|
|
const abi_size = elem_ty.abiSize(mod);
|
|
return llvm_usize_ty.constInt(array_ty.arrayLen(mod) * abi_size, .False);
|
|
},
|
|
.Many, .C => unreachable,
|
|
}
|
|
}
|
|
|
|
fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*llvm.Value {
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
return self.builder.buildExtractValue(operand, index, "");
|
|
}
|
|
|
|
fn airPtrSliceFieldPtr(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const slice_ptr = try self.resolveInst(ty_op.operand);
|
|
const slice_ptr_ty = self.typeOf(ty_op.operand);
|
|
const slice_llvm_ty = try o.lowerPtrElemTy(slice_ptr_ty.childType(mod));
|
|
|
|
return self.builder.buildStructGEP(slice_llvm_ty, slice_ptr, index, "");
|
|
}
|
|
|
|
fn airSliceElemVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const slice_ty = self.typeOf(bin_op.lhs);
|
|
const slice = try self.resolveInst(bin_op.lhs);
|
|
const index = try self.resolveInst(bin_op.rhs);
|
|
const elem_ty = slice_ty.childType(mod);
|
|
const llvm_elem_ty = try o.lowerPtrElemTy(elem_ty);
|
|
const base_ptr = self.builder.buildExtractValue(slice, 0, "");
|
|
const indices: [1]*llvm.Value = .{index};
|
|
const ptr = self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, "");
|
|
if (isByRef(elem_ty, mod)) {
|
|
if (self.canElideLoad(body_tail))
|
|
return ptr;
|
|
|
|
return self.loadByRef(ptr, elem_ty, elem_ty.abiAlignment(mod), false);
|
|
}
|
|
|
|
return self.load(ptr, slice_ty);
|
|
}
|
|
|
|
fn airSliceElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
const slice_ty = self.typeOf(bin_op.lhs);
|
|
|
|
const slice = try self.resolveInst(bin_op.lhs);
|
|
const index = try self.resolveInst(bin_op.rhs);
|
|
const llvm_elem_ty = try o.lowerPtrElemTy(slice_ty.childType(mod));
|
|
const base_ptr = self.builder.buildExtractValue(slice, 0, "");
|
|
const indices: [1]*llvm.Value = .{index};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, "");
|
|
}
|
|
|
|
fn airArrayElemVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const array_ty = self.typeOf(bin_op.lhs);
|
|
const array_llvm_val = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const array_llvm_ty = try o.lowerType(array_ty);
|
|
const elem_ty = array_ty.childType(mod);
|
|
if (isByRef(array_ty, mod)) {
|
|
const indices: [2]*llvm.Value = .{ self.context.intType(32).constNull(), rhs };
|
|
if (isByRef(elem_ty, mod)) {
|
|
const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_ty, array_llvm_val, &indices, indices.len, "");
|
|
if (canElideLoad(self, body_tail))
|
|
return elem_ptr;
|
|
|
|
return self.loadByRef(elem_ptr, elem_ty, elem_ty.abiAlignment(mod), false);
|
|
} else {
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
if (Air.refToIndex(bin_op.lhs)) |lhs_index| {
|
|
if (self.air.instructions.items(.tag)[lhs_index] == .load) {
|
|
const load_data = self.air.instructions.items(.data)[lhs_index];
|
|
const load_ptr = load_data.ty_op.operand;
|
|
if (Air.refToIndex(load_ptr)) |load_ptr_index| {
|
|
const load_ptr_tag = self.air.instructions.items(.tag)[load_ptr_index];
|
|
switch (load_ptr_tag) {
|
|
.struct_field_ptr, .struct_field_ptr_index_0, .struct_field_ptr_index_1, .struct_field_ptr_index_2, .struct_field_ptr_index_3 => {
|
|
const load_ptr_inst = try self.resolveInst(load_ptr);
|
|
const gep = self.builder.buildInBoundsGEP(array_llvm_ty, load_ptr_inst, &indices, indices.len, "");
|
|
return self.builder.buildLoad(elem_llvm_ty, gep, "");
|
|
},
|
|
else => {},
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_ty, array_llvm_val, &indices, indices.len, "");
|
|
return self.builder.buildLoad(elem_llvm_ty, elem_ptr, "");
|
|
}
|
|
}
|
|
|
|
// This branch can be reached for vectors, which are always by-value.
|
|
return self.builder.buildExtractElement(array_llvm_val, rhs, "");
|
|
}
|
|
|
|
fn airPtrElemVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const elem_ty = ptr_ty.childType(mod);
|
|
const llvm_elem_ty = try o.lowerPtrElemTy(elem_ty);
|
|
const base_ptr = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
// TODO: when we go fully opaque pointers in LLVM 16 we can remove this branch
|
|
const ptr = if (ptr_ty.isSinglePointer(mod)) ptr: {
|
|
// If this is a single-item pointer to an array, we need another index in the GEP.
|
|
const indices: [2]*llvm.Value = .{ self.context.intType(32).constNull(), rhs };
|
|
break :ptr self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, "");
|
|
} else ptr: {
|
|
const indices: [1]*llvm.Value = .{rhs};
|
|
break :ptr self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, "");
|
|
};
|
|
if (isByRef(elem_ty, mod)) {
|
|
if (self.canElideLoad(body_tail))
|
|
return ptr;
|
|
|
|
return self.loadByRef(ptr, elem_ty, elem_ty.abiAlignment(mod), false);
|
|
}
|
|
|
|
return self.load(ptr, ptr_ty);
|
|
}
|
|
|
|
fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const elem_ty = ptr_ty.childType(mod);
|
|
if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) return o.lowerPtrToVoid(ptr_ty);
|
|
|
|
const base_ptr = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
const elem_ptr = self.air.getRefType(ty_pl.ty);
|
|
if (elem_ptr.ptrInfo(mod).flags.vector_index != .none) return base_ptr;
|
|
|
|
const llvm_elem_ty = try o.lowerPtrElemTy(elem_ty);
|
|
if (ptr_ty.isSinglePointer(mod)) {
|
|
// If this is a single-item pointer to an array, we need another index in the GEP.
|
|
const indices: [2]*llvm.Value = .{ self.context.intType(32).constNull(), rhs };
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, "");
|
|
} else {
|
|
const indices: [1]*llvm.Value = .{rhs};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, base_ptr, &indices, indices.len, "");
|
|
}
|
|
}
|
|
|
|
fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
|
|
const struct_ptr = try self.resolveInst(struct_field.struct_operand);
|
|
const struct_ptr_ty = self.typeOf(struct_field.struct_operand);
|
|
return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, struct_field.field_index);
|
|
}
|
|
|
|
fn airStructFieldPtrIndex(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
field_index: u32,
|
|
) !?*llvm.Value {
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const struct_ptr = try self.resolveInst(ty_op.operand);
|
|
const struct_ptr_ty = self.typeOf(ty_op.operand);
|
|
return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
|
|
}
|
|
|
|
fn airStructFieldVal(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const struct_field = self.air.extraData(Air.StructField, ty_pl.payload).data;
|
|
const struct_ty = self.typeOf(struct_field.struct_operand);
|
|
const struct_llvm_val = try self.resolveInst(struct_field.struct_operand);
|
|
const field_index = struct_field.field_index;
|
|
const field_ty = struct_ty.structFieldType(field_index, mod);
|
|
if (!field_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return null;
|
|
}
|
|
|
|
if (!isByRef(struct_ty, mod)) {
|
|
assert(!isByRef(field_ty, mod));
|
|
switch (struct_ty.zigTypeTag(mod)) {
|
|
.Struct => switch (struct_ty.containerLayout(mod)) {
|
|
.Packed => {
|
|
const struct_obj = mod.typeToStruct(struct_ty).?;
|
|
const bit_offset = struct_obj.packedFieldBitOffset(mod, field_index);
|
|
const containing_int = struct_llvm_val;
|
|
const shift_amt = containing_int.typeOf().constInt(bit_offset, .False);
|
|
const shifted_value = self.builder.buildLShr(containing_int, shift_amt, "");
|
|
const elem_llvm_ty = try o.lowerType(field_ty);
|
|
if (field_ty.zigTypeTag(mod) == .Float or field_ty.zigTypeTag(mod) == .Vector) {
|
|
const elem_bits = @as(c_uint, @intCast(field_ty.bitSize(mod)));
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, "");
|
|
return self.builder.buildBitCast(truncated_int, elem_llvm_ty, "");
|
|
} else if (field_ty.isPtrAtRuntime(mod)) {
|
|
const elem_bits = @as(c_uint, @intCast(field_ty.bitSize(mod)));
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, "");
|
|
return self.builder.buildIntToPtr(truncated_int, elem_llvm_ty, "");
|
|
}
|
|
return self.builder.buildTrunc(shifted_value, elem_llvm_ty, "");
|
|
},
|
|
else => {
|
|
const llvm_field_index = llvmField(struct_ty, field_index, mod).?.index;
|
|
return self.builder.buildExtractValue(struct_llvm_val, llvm_field_index, "");
|
|
},
|
|
},
|
|
.Union => {
|
|
assert(struct_ty.containerLayout(mod) == .Packed);
|
|
const containing_int = struct_llvm_val;
|
|
const elem_llvm_ty = try o.lowerType(field_ty);
|
|
if (field_ty.zigTypeTag(mod) == .Float or field_ty.zigTypeTag(mod) == .Vector) {
|
|
const elem_bits = @as(c_uint, @intCast(field_ty.bitSize(mod)));
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(containing_int, same_size_int, "");
|
|
return self.builder.buildBitCast(truncated_int, elem_llvm_ty, "");
|
|
} else if (field_ty.isPtrAtRuntime(mod)) {
|
|
const elem_bits = @as(c_uint, @intCast(field_ty.bitSize(mod)));
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(containing_int, same_size_int, "");
|
|
return self.builder.buildIntToPtr(truncated_int, elem_llvm_ty, "");
|
|
}
|
|
return self.builder.buildTrunc(containing_int, elem_llvm_ty, "");
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
switch (struct_ty.zigTypeTag(mod)) {
|
|
.Struct => {
|
|
assert(struct_ty.containerLayout(mod) != .Packed);
|
|
const llvm_field = llvmField(struct_ty, field_index, mod).?;
|
|
const struct_llvm_ty = try o.lowerType(struct_ty);
|
|
const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, struct_llvm_val, llvm_field.index, "");
|
|
const field_ptr_ty = try mod.ptrType(.{
|
|
.child = llvm_field.ty.toIntern(),
|
|
.flags = .{
|
|
.alignment = InternPool.Alignment.fromNonzeroByteUnits(llvm_field.alignment),
|
|
},
|
|
});
|
|
if (isByRef(field_ty, mod)) {
|
|
if (canElideLoad(self, body_tail))
|
|
return field_ptr;
|
|
|
|
assert(llvm_field.alignment != 0);
|
|
return self.loadByRef(field_ptr, field_ty, llvm_field.alignment, false);
|
|
} else {
|
|
return self.load(field_ptr, field_ptr_ty);
|
|
}
|
|
},
|
|
.Union => {
|
|
const union_llvm_ty = try o.lowerType(struct_ty);
|
|
const layout = struct_ty.unionGetLayout(mod);
|
|
const payload_index = @intFromBool(layout.tag_align >= layout.payload_align);
|
|
const field_ptr = self.builder.buildStructGEP(union_llvm_ty, struct_llvm_val, payload_index, "");
|
|
const llvm_field_ty = try o.lowerType(field_ty);
|
|
if (isByRef(field_ty, mod)) {
|
|
if (canElideLoad(self, body_tail))
|
|
return field_ptr;
|
|
|
|
return self.loadByRef(field_ptr, field_ty, layout.payload_align, false);
|
|
} else {
|
|
return self.builder.buildLoad(llvm_field_ty, field_ptr, "");
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn airFieldParentPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.FieldParentPtr, ty_pl.payload).data;
|
|
|
|
const field_ptr = try self.resolveInst(extra.field_ptr);
|
|
|
|
const target = o.module.getTarget();
|
|
const parent_ty = self.air.getRefType(ty_pl.ty).childType(mod);
|
|
const field_offset = parent_ty.structFieldOffset(extra.field_index, mod);
|
|
|
|
const res_ty = try o.lowerType(self.air.getRefType(ty_pl.ty));
|
|
if (field_offset == 0) {
|
|
return field_ptr;
|
|
}
|
|
const llvm_usize_ty = self.context.intType(target.ptrBitWidth());
|
|
|
|
const field_ptr_int = self.builder.buildPtrToInt(field_ptr, llvm_usize_ty, "");
|
|
const base_ptr_int = self.builder.buildNUWSub(field_ptr_int, llvm_usize_ty.constInt(field_offset, .False), "");
|
|
return self.builder.buildIntToPtr(base_ptr_int, res_ty, "");
|
|
}
|
|
|
|
fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
|
|
return self.builder.buildNot(operand, "");
|
|
}
|
|
|
|
fn airUnreach(self: *FuncGen, inst: Air.Inst.Index) ?*llvm.Value {
|
|
_ = inst;
|
|
_ = self.builder.buildUnreachable();
|
|
return null;
|
|
}
|
|
|
|
fn airDbgStmt(self: *FuncGen, inst: Air.Inst.Index) ?*llvm.Value {
|
|
const di_scope = self.di_scope orelse return null;
|
|
const dbg_stmt = self.air.instructions.items(.data)[inst].dbg_stmt;
|
|
self.prev_dbg_line = @as(c_uint, @intCast(self.base_line + dbg_stmt.line + 1));
|
|
self.prev_dbg_column = @as(c_uint, @intCast(dbg_stmt.column + 1));
|
|
const inlined_at = if (self.dbg_inlined.items.len > 0)
|
|
self.dbg_inlined.items[self.dbg_inlined.items.len - 1].loc
|
|
else
|
|
null;
|
|
self.builder.setCurrentDebugLocation(self.prev_dbg_line, self.prev_dbg_column, di_scope, inlined_at);
|
|
return null;
|
|
}
|
|
|
|
fn airDbgInlineBegin(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const dib = o.di_builder orelse return null;
|
|
const ty_fn = self.air.instructions.items(.data)[inst].ty_fn;
|
|
|
|
const mod = o.module;
|
|
const func = mod.funcInfo(ty_fn.func);
|
|
const decl_index = func.owner_decl;
|
|
const decl = mod.declPtr(decl_index);
|
|
const di_file = try o.getDIFile(self.gpa, mod.namespacePtr(decl.src_namespace).file_scope);
|
|
self.di_file = di_file;
|
|
const line_number = decl.src_line + 1;
|
|
const cur_debug_location = self.builder.getCurrentDebugLocation2();
|
|
|
|
try self.dbg_inlined.append(self.gpa, .{
|
|
.loc = @ptrCast(cur_debug_location),
|
|
.scope = self.di_scope.?,
|
|
.base_line = self.base_line,
|
|
});
|
|
|
|
const fqn = try decl.getFullyQualifiedName(mod);
|
|
|
|
const is_internal_linkage = !mod.decl_exports.contains(decl_index);
|
|
const fn_ty = try mod.funcType(.{
|
|
.param_types = &.{},
|
|
.return_type = .void_type,
|
|
.alignment = .none,
|
|
.noalias_bits = 0,
|
|
.comptime_bits = 0,
|
|
.cc = .Unspecified,
|
|
.is_var_args = false,
|
|
.is_generic = false,
|
|
.is_noinline = false,
|
|
.section_is_generic = false,
|
|
.addrspace_is_generic = false,
|
|
});
|
|
const fn_di_ty = try o.lowerDebugType(fn_ty, .full);
|
|
const subprogram = dib.createFunction(
|
|
di_file.toScope(),
|
|
mod.intern_pool.stringToSlice(decl.name),
|
|
mod.intern_pool.stringToSlice(fqn),
|
|
di_file,
|
|
line_number,
|
|
fn_di_ty,
|
|
is_internal_linkage,
|
|
true, // is definition
|
|
line_number + func.lbrace_line, // scope line
|
|
llvm.DIFlags.StaticMember,
|
|
mod.comp.bin_file.options.optimize_mode != .Debug,
|
|
null, // decl_subprogram
|
|
);
|
|
|
|
const lexical_block = dib.createLexicalBlock(subprogram.toScope(), di_file, line_number, 1);
|
|
self.di_scope = lexical_block.toScope();
|
|
self.base_line = decl.src_line;
|
|
return null;
|
|
}
|
|
|
|
fn airDbgInlineEnd(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
if (o.di_builder == null) return null;
|
|
const ty_fn = self.air.instructions.items(.data)[inst].ty_fn;
|
|
|
|
const mod = o.module;
|
|
const decl = mod.funcOwnerDeclPtr(ty_fn.func);
|
|
const di_file = try o.getDIFile(self.gpa, mod.namespacePtr(decl.src_namespace).file_scope);
|
|
self.di_file = di_file;
|
|
const old = self.dbg_inlined.pop();
|
|
self.di_scope = old.scope;
|
|
self.base_line = old.base_line;
|
|
return null;
|
|
}
|
|
|
|
fn airDbgBlockBegin(self: *FuncGen) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const dib = o.di_builder orelse return null;
|
|
const old_scope = self.di_scope.?;
|
|
try self.dbg_block_stack.append(self.gpa, old_scope);
|
|
const lexical_block = dib.createLexicalBlock(old_scope, self.di_file.?, self.prev_dbg_line, self.prev_dbg_column);
|
|
self.di_scope = lexical_block.toScope();
|
|
return null;
|
|
}
|
|
|
|
fn airDbgBlockEnd(self: *FuncGen) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
if (o.di_builder == null) return null;
|
|
self.di_scope = self.dbg_block_stack.pop();
|
|
return null;
|
|
}
|
|
|
|
fn airDbgVarPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const dib = o.di_builder orelse return null;
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const operand = try self.resolveInst(pl_op.operand);
|
|
const name = self.air.nullTerminatedString(pl_op.payload);
|
|
const ptr_ty = self.typeOf(pl_op.operand);
|
|
|
|
const di_local_var = dib.createAutoVariable(
|
|
self.di_scope.?,
|
|
name.ptr,
|
|
self.di_file.?,
|
|
self.prev_dbg_line,
|
|
try o.lowerDebugType(ptr_ty.childType(mod), .full),
|
|
true, // always preserve
|
|
0, // flags
|
|
);
|
|
const inlined_at = if (self.dbg_inlined.items.len > 0)
|
|
self.dbg_inlined.items[self.dbg_inlined.items.len - 1].loc
|
|
else
|
|
null;
|
|
const debug_loc = llvm.getDebugLoc(self.prev_dbg_line, self.prev_dbg_column, self.di_scope.?, inlined_at);
|
|
const insert_block = self.builder.getInsertBlock();
|
|
_ = dib.insertDeclareAtEnd(operand, di_local_var, debug_loc, insert_block);
|
|
return null;
|
|
}
|
|
|
|
fn airDbgVarVal(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const dib = o.di_builder orelse return null;
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const operand = try self.resolveInst(pl_op.operand);
|
|
const operand_ty = self.typeOf(pl_op.operand);
|
|
const name = self.air.nullTerminatedString(pl_op.payload);
|
|
|
|
if (needDbgVarWorkaround(o)) {
|
|
return null;
|
|
}
|
|
|
|
const di_local_var = dib.createAutoVariable(
|
|
self.di_scope.?,
|
|
name.ptr,
|
|
self.di_file.?,
|
|
self.prev_dbg_line,
|
|
try o.lowerDebugType(operand_ty, .full),
|
|
true, // always preserve
|
|
0, // flags
|
|
);
|
|
const inlined_at = if (self.dbg_inlined.items.len > 0)
|
|
self.dbg_inlined.items[self.dbg_inlined.items.len - 1].loc
|
|
else
|
|
null;
|
|
const debug_loc = llvm.getDebugLoc(self.prev_dbg_line, self.prev_dbg_column, self.di_scope.?, inlined_at);
|
|
const insert_block = self.builder.getInsertBlock();
|
|
const mod = o.module;
|
|
if (isByRef(operand_ty, mod)) {
|
|
_ = dib.insertDeclareAtEnd(operand, di_local_var, debug_loc, insert_block);
|
|
} else if (o.module.comp.bin_file.options.optimize_mode == .Debug) {
|
|
const alignment = operand_ty.abiAlignment(mod);
|
|
const alloca = self.buildAlloca(operand.typeOf(), alignment);
|
|
const store_inst = self.builder.buildStore(operand, alloca);
|
|
store_inst.setAlignment(alignment);
|
|
_ = dib.insertDeclareAtEnd(alloca, di_local_var, debug_loc, insert_block);
|
|
} else {
|
|
_ = dib.insertDbgValueIntrinsicAtEnd(operand, di_local_var, debug_loc, insert_block);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
fn airAssembly(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
// Eventually, the Zig compiler needs to be reworked to have inline
|
|
// assembly go through the same parsing code regardless of backend, and
|
|
// have LLVM-flavored inline assembly be *output* from that assembler.
|
|
// We don't have such an assembler implemented yet though. For now,
|
|
// this implementation feeds the inline assembly code directly to LLVM.
|
|
|
|
const o = self.dg.object;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.Asm, ty_pl.payload);
|
|
const is_volatile = @as(u1, @truncate(extra.data.flags >> 31)) != 0;
|
|
const clobbers_len = @as(u31, @truncate(extra.data.flags));
|
|
var extra_i: usize = extra.end;
|
|
|
|
const outputs = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[extra_i..][0..extra.data.outputs_len]));
|
|
extra_i += outputs.len;
|
|
const inputs = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[extra_i..][0..extra.data.inputs_len]));
|
|
extra_i += inputs.len;
|
|
|
|
var llvm_constraints: std.ArrayListUnmanaged(u8) = .{};
|
|
defer llvm_constraints.deinit(self.gpa);
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
|
|
defer arena_allocator.deinit();
|
|
const arena = arena_allocator.allocator();
|
|
|
|
// The exact number of return / parameter values depends on which output values
|
|
// are passed by reference as indirect outputs (determined below).
|
|
const max_return_count = outputs.len;
|
|
const llvm_ret_types = try arena.alloc(*llvm.Type, max_return_count);
|
|
const llvm_ret_indirect = try arena.alloc(bool, max_return_count);
|
|
|
|
const max_param_count = inputs.len + outputs.len;
|
|
const llvm_param_types = try arena.alloc(*llvm.Type, max_param_count);
|
|
const llvm_param_values = try arena.alloc(*llvm.Value, max_param_count);
|
|
// This stores whether we need to add an elementtype attribute and
|
|
// if so, the element type itself.
|
|
const llvm_param_attrs = try arena.alloc(?*llvm.Type, max_param_count);
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
|
|
var llvm_ret_i: usize = 0;
|
|
var llvm_param_i: usize = 0;
|
|
var total_i: u16 = 0;
|
|
|
|
var name_map: std.StringArrayHashMapUnmanaged(u16) = .{};
|
|
try name_map.ensureUnusedCapacity(arena, max_param_count);
|
|
|
|
for (outputs, 0..) |output, i| {
|
|
const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
|
|
const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
|
|
const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
|
|
// This equation accounts for the fact that even if we have exactly 4 bytes
|
|
// for the string, we still use the next u32 for the null terminator.
|
|
extra_i += (constraint.len + name.len + (2 + 3)) / 4;
|
|
|
|
try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 3);
|
|
if (total_i != 0) {
|
|
llvm_constraints.appendAssumeCapacity(',');
|
|
}
|
|
llvm_constraints.appendAssumeCapacity('=');
|
|
|
|
// Pass any non-return outputs indirectly, if the constraint accepts a memory location
|
|
llvm_ret_indirect[i] = (output != .none) and constraintAllowsMemory(constraint);
|
|
if (output != .none) {
|
|
const output_inst = try self.resolveInst(output);
|
|
const output_ty = self.typeOf(output);
|
|
assert(output_ty.zigTypeTag(mod) == .Pointer);
|
|
const elem_llvm_ty = try o.lowerPtrElemTy(output_ty.childType(mod));
|
|
|
|
if (llvm_ret_indirect[i]) {
|
|
// Pass the result by reference as an indirect output (e.g. "=*m")
|
|
llvm_constraints.appendAssumeCapacity('*');
|
|
|
|
llvm_param_values[llvm_param_i] = output_inst;
|
|
llvm_param_types[llvm_param_i] = output_inst.typeOf();
|
|
llvm_param_attrs[llvm_param_i] = elem_llvm_ty;
|
|
llvm_param_i += 1;
|
|
} else {
|
|
// Pass the result directly (e.g. "=r")
|
|
llvm_ret_types[llvm_ret_i] = elem_llvm_ty;
|
|
llvm_ret_i += 1;
|
|
}
|
|
} else {
|
|
const ret_ty = self.typeOfIndex(inst);
|
|
llvm_ret_types[llvm_ret_i] = try o.lowerType(ret_ty);
|
|
llvm_ret_i += 1;
|
|
}
|
|
|
|
// LLVM uses commas internally to separate different constraints,
|
|
// alternative constraints are achieved with pipes.
|
|
// We still allow the user to use commas in a way that is similar
|
|
// to GCC's inline assembly.
|
|
// http://llvm.org/docs/LangRef.html#constraint-codes
|
|
for (constraint[1..]) |byte| {
|
|
switch (byte) {
|
|
',' => llvm_constraints.appendAssumeCapacity('|'),
|
|
'*' => {}, // Indirect outputs are handled above
|
|
else => llvm_constraints.appendAssumeCapacity(byte),
|
|
}
|
|
}
|
|
|
|
if (!std.mem.eql(u8, name, "_")) {
|
|
const gop = name_map.getOrPutAssumeCapacity(name);
|
|
if (gop.found_existing) return self.todo("duplicate asm output name '{s}'", .{name});
|
|
gop.value_ptr.* = total_i;
|
|
}
|
|
total_i += 1;
|
|
}
|
|
|
|
for (inputs) |input| {
|
|
const extra_bytes = std.mem.sliceAsBytes(self.air.extra[extra_i..]);
|
|
const constraint = std.mem.sliceTo(extra_bytes, 0);
|
|
const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0);
|
|
// This equation accounts for the fact that even if we have exactly 4 bytes
|
|
// for the string, we still use the next u32 for the null terminator.
|
|
extra_i += (constraint.len + name.len + (2 + 3)) / 4;
|
|
|
|
const arg_llvm_value = try self.resolveInst(input);
|
|
const arg_ty = self.typeOf(input);
|
|
var llvm_elem_ty: ?*llvm.Type = null;
|
|
if (isByRef(arg_ty, mod)) {
|
|
llvm_elem_ty = try o.lowerPtrElemTy(arg_ty);
|
|
if (constraintAllowsMemory(constraint)) {
|
|
llvm_param_values[llvm_param_i] = arg_llvm_value;
|
|
llvm_param_types[llvm_param_i] = arg_llvm_value.typeOf();
|
|
} else {
|
|
const alignment = arg_ty.abiAlignment(mod);
|
|
const arg_llvm_ty = try o.lowerType(arg_ty);
|
|
const load_inst = self.builder.buildLoad(arg_llvm_ty, arg_llvm_value, "");
|
|
load_inst.setAlignment(alignment);
|
|
llvm_param_values[llvm_param_i] = load_inst;
|
|
llvm_param_types[llvm_param_i] = arg_llvm_ty;
|
|
}
|
|
} else {
|
|
if (constraintAllowsRegister(constraint)) {
|
|
llvm_param_values[llvm_param_i] = arg_llvm_value;
|
|
llvm_param_types[llvm_param_i] = arg_llvm_value.typeOf();
|
|
} else {
|
|
const alignment = arg_ty.abiAlignment(mod);
|
|
const arg_ptr = self.buildAlloca(arg_llvm_value.typeOf(), alignment);
|
|
const store_inst = self.builder.buildStore(arg_llvm_value, arg_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
llvm_param_values[llvm_param_i] = arg_ptr;
|
|
llvm_param_types[llvm_param_i] = arg_ptr.typeOf();
|
|
}
|
|
}
|
|
|
|
try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1);
|
|
if (total_i != 0) {
|
|
llvm_constraints.appendAssumeCapacity(',');
|
|
}
|
|
for (constraint) |byte| {
|
|
llvm_constraints.appendAssumeCapacity(switch (byte) {
|
|
',' => '|',
|
|
else => byte,
|
|
});
|
|
}
|
|
|
|
if (!std.mem.eql(u8, name, "_")) {
|
|
const gop = name_map.getOrPutAssumeCapacity(name);
|
|
if (gop.found_existing) return self.todo("duplicate asm input name '{s}'", .{name});
|
|
gop.value_ptr.* = total_i;
|
|
}
|
|
|
|
// In the case of indirect inputs, LLVM requires the callsite to have
|
|
// an elementtype(<ty>) attribute.
|
|
if (constraint[0] == '*') {
|
|
llvm_param_attrs[llvm_param_i] = llvm_elem_ty orelse
|
|
try o.lowerPtrElemTy(arg_ty.childType(mod));
|
|
} else {
|
|
llvm_param_attrs[llvm_param_i] = null;
|
|
}
|
|
|
|
llvm_param_i += 1;
|
|
total_i += 1;
|
|
}
|
|
|
|
{
|
|
var clobber_i: u32 = 0;
|
|
while (clobber_i < clobbers_len) : (clobber_i += 1) {
|
|
const clobber = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra[extra_i..]), 0);
|
|
// This equation accounts for the fact that even if we have exactly 4 bytes
|
|
// for the string, we still use the next u32 for the null terminator.
|
|
extra_i += clobber.len / 4 + 1;
|
|
|
|
try llvm_constraints.ensureUnusedCapacity(self.gpa, clobber.len + 4);
|
|
if (total_i != 0) {
|
|
llvm_constraints.appendAssumeCapacity(',');
|
|
}
|
|
llvm_constraints.appendSliceAssumeCapacity("~{");
|
|
llvm_constraints.appendSliceAssumeCapacity(clobber);
|
|
llvm_constraints.appendSliceAssumeCapacity("}");
|
|
|
|
total_i += 1;
|
|
}
|
|
}
|
|
|
|
// We have finished scanning through all inputs/outputs, so the number of
|
|
// parameters and return values is known.
|
|
const param_count = llvm_param_i;
|
|
const return_count = llvm_ret_i;
|
|
|
|
// For some targets, Clang unconditionally adds some clobbers to all inline assembly.
|
|
// While this is probably not strictly necessary, if we don't follow Clang's lead
|
|
// here then we may risk tripping LLVM bugs since anything not used by Clang tends
|
|
// to be buggy and regress often.
|
|
switch (target.cpu.arch) {
|
|
.x86_64, .x86 => {
|
|
if (total_i != 0) try llvm_constraints.append(self.gpa, ',');
|
|
try llvm_constraints.appendSlice(self.gpa, "~{dirflag},~{fpsr},~{flags}");
|
|
total_i += 3;
|
|
},
|
|
.mips, .mipsel, .mips64, .mips64el => {
|
|
if (total_i != 0) try llvm_constraints.append(self.gpa, ',');
|
|
try llvm_constraints.appendSlice(self.gpa, "~{$1}");
|
|
total_i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
const asm_source = std.mem.sliceAsBytes(self.air.extra[extra_i..])[0..extra.data.source_len];
|
|
|
|
// hackety hacks until stage2 has proper inline asm in the frontend.
|
|
var rendered_template = std.ArrayList(u8).init(self.gpa);
|
|
defer rendered_template.deinit();
|
|
|
|
const State = enum { start, percent, input, modifier };
|
|
|
|
var state: State = .start;
|
|
|
|
var name_start: usize = undefined;
|
|
var modifier_start: usize = undefined;
|
|
for (asm_source, 0..) |byte, i| {
|
|
switch (state) {
|
|
.start => switch (byte) {
|
|
'%' => state = .percent,
|
|
'$' => try rendered_template.appendSlice("$$"),
|
|
else => try rendered_template.append(byte),
|
|
},
|
|
.percent => switch (byte) {
|
|
'%' => {
|
|
try rendered_template.append('%');
|
|
state = .start;
|
|
},
|
|
'[' => {
|
|
try rendered_template.append('$');
|
|
try rendered_template.append('{');
|
|
name_start = i + 1;
|
|
state = .input;
|
|
},
|
|
else => {
|
|
try rendered_template.append('%');
|
|
try rendered_template.append(byte);
|
|
state = .start;
|
|
},
|
|
},
|
|
.input => switch (byte) {
|
|
']', ':' => {
|
|
const name = asm_source[name_start..i];
|
|
|
|
const index = name_map.get(name) orelse {
|
|
// we should validate the assembly in Sema; by now it is too late
|
|
return self.todo("unknown input or output name: '{s}'", .{name});
|
|
};
|
|
try rendered_template.writer().print("{d}", .{index});
|
|
if (byte == ':') {
|
|
try rendered_template.append(':');
|
|
modifier_start = i + 1;
|
|
state = .modifier;
|
|
} else {
|
|
try rendered_template.append('}');
|
|
state = .start;
|
|
}
|
|
},
|
|
else => {},
|
|
},
|
|
.modifier => switch (byte) {
|
|
']' => {
|
|
try rendered_template.appendSlice(asm_source[modifier_start..i]);
|
|
try rendered_template.append('}');
|
|
state = .start;
|
|
},
|
|
else => {},
|
|
},
|
|
}
|
|
}
|
|
|
|
const ret_llvm_ty = switch (return_count) {
|
|
0 => self.context.voidType(),
|
|
1 => llvm_ret_types[0],
|
|
else => self.context.structType(
|
|
llvm_ret_types.ptr,
|
|
@as(c_uint, @intCast(return_count)),
|
|
.False,
|
|
),
|
|
};
|
|
|
|
const llvm_fn_ty = llvm.functionType(
|
|
ret_llvm_ty,
|
|
llvm_param_types.ptr,
|
|
@as(c_uint, @intCast(param_count)),
|
|
.False,
|
|
);
|
|
const asm_fn = llvm.getInlineAsm(
|
|
llvm_fn_ty,
|
|
rendered_template.items.ptr,
|
|
rendered_template.items.len,
|
|
llvm_constraints.items.ptr,
|
|
llvm_constraints.items.len,
|
|
llvm.Bool.fromBool(is_volatile),
|
|
.False,
|
|
.ATT,
|
|
.False,
|
|
);
|
|
const call = self.builder.buildCall(
|
|
llvm_fn_ty,
|
|
asm_fn,
|
|
llvm_param_values.ptr,
|
|
@as(c_uint, @intCast(param_count)),
|
|
.C,
|
|
.Auto,
|
|
"",
|
|
);
|
|
for (llvm_param_attrs[0..param_count], 0..) |llvm_elem_ty, i| {
|
|
if (llvm_elem_ty) |llvm_ty| {
|
|
llvm.setCallElemTypeAttr(call, i, llvm_ty);
|
|
}
|
|
}
|
|
|
|
var ret_val = call;
|
|
llvm_ret_i = 0;
|
|
for (outputs, 0..) |output, i| {
|
|
if (llvm_ret_indirect[i]) continue;
|
|
|
|
const output_value = if (return_count > 1) b: {
|
|
break :b self.builder.buildExtractValue(call, @as(c_uint, @intCast(llvm_ret_i)), "");
|
|
} else call;
|
|
|
|
if (output != .none) {
|
|
const output_ptr = try self.resolveInst(output);
|
|
const output_ptr_ty = self.typeOf(output);
|
|
|
|
const store_inst = self.builder.buildStore(output_value, output_ptr);
|
|
store_inst.setAlignment(output_ptr_ty.ptrAlignment(mod));
|
|
} else {
|
|
ret_val = output_value;
|
|
}
|
|
llvm_ret_i += 1;
|
|
}
|
|
|
|
return ret_val;
|
|
}
|
|
|
|
fn airIsNonNull(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
operand_is_ptr: bool,
|
|
pred: llvm.IntPredicate,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const operand_ty = self.typeOf(un_op);
|
|
const optional_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
|
|
const optional_llvm_ty = try o.lowerType(optional_ty);
|
|
const payload_ty = optional_ty.optionalChild(mod);
|
|
if (optional_ty.optionalReprIsPayload(mod)) {
|
|
const loaded = if (operand_is_ptr)
|
|
self.builder.buildLoad(optional_llvm_ty, operand, "")
|
|
else
|
|
operand;
|
|
if (payload_ty.isSlice(mod)) {
|
|
const slice_ptr = self.builder.buildExtractValue(loaded, 0, "");
|
|
const ptr_ty = try o.lowerType(payload_ty.slicePtrFieldType(mod));
|
|
return self.builder.buildICmp(pred, slice_ptr, ptr_ty.constNull(), "");
|
|
}
|
|
return self.builder.buildICmp(pred, loaded, optional_llvm_ty.constNull(), "");
|
|
}
|
|
|
|
comptime assert(optional_layout_version == 3);
|
|
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const loaded = if (operand_is_ptr)
|
|
self.builder.buildLoad(optional_llvm_ty, operand, "")
|
|
else
|
|
operand;
|
|
const llvm_i8 = self.context.intType(8);
|
|
return self.builder.buildICmp(pred, loaded, llvm_i8.constNull(), "");
|
|
}
|
|
|
|
const is_by_ref = operand_is_ptr or isByRef(optional_ty, mod);
|
|
const non_null_bit = self.optIsNonNull(optional_llvm_ty, operand, is_by_ref);
|
|
if (pred == .EQ) {
|
|
return self.builder.buildNot(non_null_bit, "");
|
|
} else {
|
|
return non_null_bit;
|
|
}
|
|
}
|
|
|
|
fn airIsErr(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
op: llvm.IntPredicate,
|
|
operand_is_ptr: bool,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const operand_ty = self.typeOf(un_op);
|
|
const err_union_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
|
|
const payload_ty = err_union_ty.errorUnionPayload(mod);
|
|
const err_set_ty = try o.lowerType(Type.anyerror);
|
|
const zero = err_set_ty.constNull();
|
|
|
|
if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
|
|
const llvm_i1 = self.context.intType(1);
|
|
switch (op) {
|
|
.EQ => return llvm_i1.constInt(1, .False), // 0 == 0
|
|
.NE => return llvm_i1.constInt(0, .False), // 0 != 0
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const loaded = if (operand_is_ptr)
|
|
self.builder.buildLoad(try o.lowerType(err_union_ty), operand, "")
|
|
else
|
|
operand;
|
|
return self.builder.buildICmp(op, loaded, zero, "");
|
|
}
|
|
|
|
const err_field_index = errUnionErrorOffset(payload_ty, mod);
|
|
|
|
if (operand_is_ptr or isByRef(err_union_ty, mod)) {
|
|
const err_union_llvm_ty = try o.lowerType(err_union_ty);
|
|
const err_field_ptr = self.builder.buildStructGEP(err_union_llvm_ty, operand, err_field_index, "");
|
|
const loaded = self.builder.buildLoad(err_set_ty, err_field_ptr, "");
|
|
return self.builder.buildICmp(op, loaded, zero, "");
|
|
}
|
|
|
|
const loaded = self.builder.buildExtractValue(operand, err_field_index, "");
|
|
return self.builder.buildICmp(op, loaded, zero, "");
|
|
}
|
|
|
|
fn airOptionalPayloadPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const optional_ty = self.typeOf(ty_op.operand).childType(mod);
|
|
const payload_ty = optional_ty.optionalChild(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
// We have a pointer to a zero-bit value and we need to return
|
|
// a pointer to a zero-bit value.
|
|
return operand;
|
|
}
|
|
if (optional_ty.optionalReprIsPayload(mod)) {
|
|
// The payload and the optional are the same value.
|
|
return operand;
|
|
}
|
|
const optional_llvm_ty = try o.lowerType(optional_ty);
|
|
return self.builder.buildStructGEP(optional_llvm_ty, operand, 0, "");
|
|
}
|
|
|
|
fn airOptionalPayloadPtrSet(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
comptime assert(optional_layout_version == 3);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const optional_ty = self.typeOf(ty_op.operand).childType(mod);
|
|
const payload_ty = optional_ty.optionalChild(mod);
|
|
const non_null_bit = self.context.intType(8).constInt(1, .False);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
// We have a pointer to a i8. We need to set it to 1 and then return the same pointer.
|
|
_ = self.builder.buildStore(non_null_bit, operand);
|
|
return operand;
|
|
}
|
|
if (optional_ty.optionalReprIsPayload(mod)) {
|
|
// The payload and the optional are the same value.
|
|
// Setting to non-null will be done when the payload is set.
|
|
return operand;
|
|
}
|
|
|
|
// First set the non-null bit.
|
|
const optional_llvm_ty = try o.lowerType(optional_ty);
|
|
const non_null_ptr = self.builder.buildStructGEP(optional_llvm_ty, operand, 1, "");
|
|
// TODO set alignment on this store
|
|
_ = self.builder.buildStore(non_null_bit, non_null_ptr);
|
|
|
|
// Then return the payload pointer (only if it's used).
|
|
if (self.liveness.isUnused(inst))
|
|
return null;
|
|
|
|
return self.builder.buildStructGEP(optional_llvm_ty, operand, 0, "");
|
|
}
|
|
|
|
fn airOptionalPayload(self: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const optional_ty = self.typeOf(ty_op.operand);
|
|
const payload_ty = self.typeOfIndex(inst);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) return null;
|
|
|
|
if (optional_ty.optionalReprIsPayload(mod)) {
|
|
// Payload value is the same as the optional value.
|
|
return operand;
|
|
}
|
|
|
|
const opt_llvm_ty = try o.lowerType(optional_ty);
|
|
const can_elide_load = if (isByRef(payload_ty, mod)) self.canElideLoad(body_tail) else false;
|
|
return self.optPayloadHandle(opt_llvm_ty, operand, optional_ty, can_elide_load);
|
|
}
|
|
|
|
fn airErrUnionPayload(
|
|
self: *FuncGen,
|
|
body_tail: []const Air.Inst.Index,
|
|
operand_is_ptr: bool,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const err_union_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
|
|
const result_ty = self.typeOfIndex(inst);
|
|
const payload_ty = if (operand_is_ptr) result_ty.childType(mod) else result_ty;
|
|
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return if (operand_is_ptr) operand else null;
|
|
}
|
|
const offset = errUnionPayloadOffset(payload_ty, mod);
|
|
const err_union_llvm_ty = try o.lowerType(err_union_ty);
|
|
if (operand_is_ptr) {
|
|
return self.builder.buildStructGEP(err_union_llvm_ty, operand, offset, "");
|
|
} else if (isByRef(err_union_ty, mod)) {
|
|
const payload_ptr = self.builder.buildStructGEP(err_union_llvm_ty, operand, offset, "");
|
|
if (isByRef(payload_ty, mod)) {
|
|
if (self.canElideLoad(body_tail))
|
|
return payload_ptr;
|
|
|
|
return self.loadByRef(payload_ptr, payload_ty, payload_ty.abiAlignment(mod), false);
|
|
}
|
|
const load_inst = self.builder.buildLoad(err_union_llvm_ty.structGetTypeAtIndex(offset), payload_ptr, "");
|
|
load_inst.setAlignment(payload_ty.abiAlignment(mod));
|
|
return load_inst;
|
|
}
|
|
return self.builder.buildExtractValue(operand, offset, "");
|
|
}
|
|
|
|
fn airErrUnionErr(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
operand_is_ptr: bool,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const err_union_ty = if (operand_is_ptr) operand_ty.childType(mod) else operand_ty;
|
|
if (err_union_ty.errorUnionSet(mod).errorSetIsEmpty(mod)) {
|
|
const err_llvm_ty = try o.lowerType(Type.anyerror);
|
|
if (operand_is_ptr) {
|
|
return operand;
|
|
} else {
|
|
return err_llvm_ty.constInt(0, .False);
|
|
}
|
|
}
|
|
|
|
const err_set_llvm_ty = try o.lowerType(Type.anyerror);
|
|
|
|
const payload_ty = err_union_ty.errorUnionPayload(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
if (!operand_is_ptr) return operand;
|
|
return self.builder.buildLoad(err_set_llvm_ty, operand, "");
|
|
}
|
|
|
|
const offset = errUnionErrorOffset(payload_ty, mod);
|
|
|
|
if (operand_is_ptr or isByRef(err_union_ty, mod)) {
|
|
const err_union_llvm_ty = try o.lowerType(err_union_ty);
|
|
const err_field_ptr = self.builder.buildStructGEP(err_union_llvm_ty, operand, offset, "");
|
|
return self.builder.buildLoad(err_set_llvm_ty, err_field_ptr, "");
|
|
}
|
|
|
|
return self.builder.buildExtractValue(operand, offset, "");
|
|
}
|
|
|
|
fn airErrUnionPayloadPtrSet(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const err_union_ty = self.typeOf(ty_op.operand).childType(mod);
|
|
|
|
const payload_ty = err_union_ty.errorUnionPayload(mod);
|
|
const non_error_val = try o.lowerValue(.{ .ty = Type.anyerror, .val = try mod.intValue(Type.err_int, 0) });
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
_ = self.builder.buildStore(non_error_val, operand);
|
|
return operand;
|
|
}
|
|
const err_union_llvm_ty = try o.lowerType(err_union_ty);
|
|
{
|
|
const error_offset = errUnionErrorOffset(payload_ty, mod);
|
|
// First set the non-error value.
|
|
const non_null_ptr = self.builder.buildStructGEP(err_union_llvm_ty, operand, error_offset, "");
|
|
const store_inst = self.builder.buildStore(non_error_val, non_null_ptr);
|
|
store_inst.setAlignment(Type.anyerror.abiAlignment(mod));
|
|
}
|
|
// Then return the payload pointer (only if it is used).
|
|
if (self.liveness.isUnused(inst))
|
|
return null;
|
|
|
|
const payload_offset = errUnionPayloadOffset(payload_ty, mod);
|
|
return self.builder.buildStructGEP(err_union_llvm_ty, operand, payload_offset, "");
|
|
}
|
|
|
|
fn airErrReturnTrace(self: *FuncGen, _: Air.Inst.Index) !?*llvm.Value {
|
|
return self.err_ret_trace.?;
|
|
}
|
|
|
|
fn airSetErrReturnTrace(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
self.err_ret_trace = operand;
|
|
return null;
|
|
}
|
|
|
|
fn airSaveErrReturnTraceIndex(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
//const struct_ty = try self.resolveInst(ty_pl.ty);
|
|
const struct_ty = self.air.getRefType(ty_pl.ty);
|
|
const field_index = ty_pl.payload;
|
|
|
|
const mod = o.module;
|
|
const llvm_field = llvmField(struct_ty, field_index, mod).?;
|
|
const struct_llvm_ty = try o.lowerType(struct_ty);
|
|
const field_ptr = self.builder.buildStructGEP(struct_llvm_ty, self.err_ret_trace.?, llvm_field.index, "");
|
|
const field_ptr_ty = try mod.ptrType(.{
|
|
.child = llvm_field.ty.toIntern(),
|
|
.flags = .{
|
|
.alignment = InternPool.Alignment.fromNonzeroByteUnits(llvm_field.alignment),
|
|
},
|
|
});
|
|
return self.load(field_ptr, field_ptr_ty);
|
|
}
|
|
|
|
fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const payload_ty = self.typeOf(ty_op.operand);
|
|
const non_null_bit = self.context.intType(8).constInt(1, .False);
|
|
comptime assert(optional_layout_version == 3);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) return non_null_bit;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const optional_ty = self.typeOfIndex(inst);
|
|
if (optional_ty.optionalReprIsPayload(mod)) {
|
|
return operand;
|
|
}
|
|
const llvm_optional_ty = try o.lowerType(optional_ty);
|
|
if (isByRef(optional_ty, mod)) {
|
|
const optional_ptr = self.buildAlloca(llvm_optional_ty, optional_ty.abiAlignment(mod));
|
|
const payload_ptr = self.builder.buildStructGEP(llvm_optional_ty, optional_ptr, 0, "");
|
|
const payload_ptr_ty = try mod.singleMutPtrType(payload_ty);
|
|
try self.store(payload_ptr, payload_ptr_ty, operand, .NotAtomic);
|
|
const non_null_ptr = self.builder.buildStructGEP(llvm_optional_ty, optional_ptr, 1, "");
|
|
_ = self.builder.buildStore(non_null_bit, non_null_ptr);
|
|
return optional_ptr;
|
|
}
|
|
const partial = self.builder.buildInsertValue(llvm_optional_ty.getUndef(), operand, 0, "");
|
|
return self.builder.buildInsertValue(partial, non_null_bit, 1, "");
|
|
}
|
|
|
|
fn airWrapErrUnionPayload(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const err_un_ty = self.typeOfIndex(inst);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const payload_ty = self.typeOf(ty_op.operand);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return operand;
|
|
}
|
|
const ok_err_code = (try o.lowerType(Type.anyerror)).constNull();
|
|
const err_un_llvm_ty = try o.lowerType(err_un_ty);
|
|
|
|
const payload_offset = errUnionPayloadOffset(payload_ty, mod);
|
|
const error_offset = errUnionErrorOffset(payload_ty, mod);
|
|
if (isByRef(err_un_ty, mod)) {
|
|
const result_ptr = self.buildAlloca(err_un_llvm_ty, err_un_ty.abiAlignment(mod));
|
|
const err_ptr = self.builder.buildStructGEP(err_un_llvm_ty, result_ptr, error_offset, "");
|
|
const store_inst = self.builder.buildStore(ok_err_code, err_ptr);
|
|
store_inst.setAlignment(Type.anyerror.abiAlignment(mod));
|
|
const payload_ptr = self.builder.buildStructGEP(err_un_llvm_ty, result_ptr, payload_offset, "");
|
|
const payload_ptr_ty = try mod.singleMutPtrType(payload_ty);
|
|
try self.store(payload_ptr, payload_ptr_ty, operand, .NotAtomic);
|
|
return result_ptr;
|
|
}
|
|
|
|
const partial = self.builder.buildInsertValue(err_un_llvm_ty.getUndef(), ok_err_code, error_offset, "");
|
|
return self.builder.buildInsertValue(partial, operand, payload_offset, "");
|
|
}
|
|
|
|
fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const err_un_ty = self.typeOfIndex(inst);
|
|
const payload_ty = err_un_ty.errorUnionPayload(mod);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return operand;
|
|
}
|
|
const err_un_llvm_ty = try o.lowerType(err_un_ty);
|
|
|
|
const payload_offset = errUnionPayloadOffset(payload_ty, mod);
|
|
const error_offset = errUnionErrorOffset(payload_ty, mod);
|
|
if (isByRef(err_un_ty, mod)) {
|
|
const result_ptr = self.buildAlloca(err_un_llvm_ty, err_un_ty.abiAlignment(mod));
|
|
const err_ptr = self.builder.buildStructGEP(err_un_llvm_ty, result_ptr, error_offset, "");
|
|
const store_inst = self.builder.buildStore(operand, err_ptr);
|
|
store_inst.setAlignment(Type.anyerror.abiAlignment(mod));
|
|
const payload_ptr = self.builder.buildStructGEP(err_un_llvm_ty, result_ptr, payload_offset, "");
|
|
const payload_ptr_ty = try mod.singleMutPtrType(payload_ty);
|
|
// TODO store undef to payload_ptr
|
|
_ = payload_ptr;
|
|
_ = payload_ptr_ty;
|
|
return result_ptr;
|
|
}
|
|
|
|
const partial = self.builder.buildInsertValue(err_un_llvm_ty.getUndef(), operand, error_offset, "");
|
|
// TODO set payload bytes to undef
|
|
return partial;
|
|
}
|
|
|
|
fn airWasmMemorySize(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const index = pl_op.payload;
|
|
const llvm_u32 = self.context.intType(32);
|
|
const llvm_fn = self.getIntrinsic("llvm.wasm.memory.size", &.{llvm_u32});
|
|
const args: [1]*llvm.Value = .{llvm_u32.constInt(index, .False)};
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
}
|
|
|
|
fn airWasmMemoryGrow(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const index = pl_op.payload;
|
|
const operand = try self.resolveInst(pl_op.operand);
|
|
const llvm_u32 = self.context.intType(32);
|
|
const llvm_fn = self.getIntrinsic("llvm.wasm.memory.grow", &.{llvm_u32});
|
|
const args: [2]*llvm.Value = .{
|
|
llvm_u32.constInt(index, .False),
|
|
operand,
|
|
};
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
}
|
|
|
|
fn airVectorStoreElem(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const data = self.air.instructions.items(.data)[inst].vector_store_elem;
|
|
const extra = self.air.extraData(Air.Bin, data.payload).data;
|
|
|
|
const vector_ptr = try self.resolveInst(data.vector_ptr);
|
|
const vector_ptr_ty = self.typeOf(data.vector_ptr);
|
|
const index = try self.resolveInst(extra.lhs);
|
|
const operand = try self.resolveInst(extra.rhs);
|
|
|
|
const loaded_vector = blk: {
|
|
const elem_llvm_ty = try o.lowerType(vector_ptr_ty.childType(mod));
|
|
const load_inst = self.builder.buildLoad(elem_llvm_ty, vector_ptr, "");
|
|
load_inst.setAlignment(vector_ptr_ty.ptrAlignment(mod));
|
|
load_inst.setVolatile(llvm.Bool.fromBool(vector_ptr_ty.isVolatilePtr(mod)));
|
|
break :blk load_inst;
|
|
};
|
|
const modified_vector = self.builder.buildInsertElement(loaded_vector, operand, index, "");
|
|
try self.store(vector_ptr, vector_ptr_ty, modified_vector, .NotAtomic);
|
|
return null;
|
|
}
|
|
|
|
fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const scalar_ty = self.typeOfIndex(inst).scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.buildFloatOp(.fmin, scalar_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSMin(lhs, rhs, "");
|
|
return self.builder.buildUMin(lhs, rhs, "");
|
|
}
|
|
|
|
fn airMax(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const scalar_ty = self.typeOfIndex(inst).scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.buildFloatOp(.fmax, scalar_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSMax(lhs, rhs, "");
|
|
return self.builder.buildUMax(lhs, rhs, "");
|
|
}
|
|
|
|
fn airSlice(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
const ptr = try self.resolveInst(bin_op.lhs);
|
|
const len = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const llvm_slice_ty = try o.lowerType(inst_ty);
|
|
|
|
// In case of slicing a global, the result type looks something like `{ i8*, i64 }`
|
|
// but `ptr` is pointing to the global directly.
|
|
const partial = self.builder.buildInsertValue(llvm_slice_ty.getUndef(), ptr, 0, "");
|
|
return self.builder.buildInsertValue(partial, len, 1, "");
|
|
}
|
|
|
|
fn airAdd(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.buildFloatOp(.add, inst_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildNSWAdd(lhs, rhs, "");
|
|
return self.builder.buildNUWAdd(lhs, rhs, "");
|
|
}
|
|
|
|
fn airSafeArithmetic(
|
|
fg: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
signed_intrinsic: []const u8,
|
|
unsigned_intrinsic: []const u8,
|
|
) !?*llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
|
|
const bin_op = fg.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try fg.resolveInst(bin_op.lhs);
|
|
const rhs = try fg.resolveInst(bin_op.rhs);
|
|
const inst_ty = fg.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
const is_scalar = scalar_ty.ip_index == inst_ty.ip_index;
|
|
|
|
const intrinsic_name = switch (scalar_ty.isSignedInt(mod)) {
|
|
true => signed_intrinsic,
|
|
false => unsigned_intrinsic,
|
|
};
|
|
const llvm_inst_ty = try o.lowerType(inst_ty);
|
|
const llvm_fn = fg.getIntrinsic(intrinsic_name, &.{llvm_inst_ty});
|
|
const result_struct = fg.builder.buildCall(
|
|
llvm_fn.globalGetValueType(),
|
|
llvm_fn,
|
|
&[_]*llvm.Value{ lhs, rhs },
|
|
2,
|
|
.Fast,
|
|
.Auto,
|
|
"",
|
|
);
|
|
const overflow_bit = fg.builder.buildExtractValue(result_struct, 1, "");
|
|
const scalar_overflow_bit = switch (is_scalar) {
|
|
true => overflow_bit,
|
|
false => fg.builder.buildOrReduce(overflow_bit),
|
|
};
|
|
|
|
const fail_block = fg.context.appendBasicBlock(fg.llvm_func, "OverflowFail");
|
|
const ok_block = fg.context.appendBasicBlock(fg.llvm_func, "OverflowOk");
|
|
_ = fg.builder.buildCondBr(scalar_overflow_bit, fail_block, ok_block);
|
|
|
|
fg.builder.positionBuilderAtEnd(fail_block);
|
|
try fg.buildSimplePanic(.integer_overflow);
|
|
|
|
fg.builder.positionBuilderAtEnd(ok_block);
|
|
return fg.builder.buildExtractValue(result_struct, 0, "");
|
|
}
|
|
|
|
fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
return self.builder.buildAdd(lhs, rhs, "");
|
|
}
|
|
|
|
fn airAddSat(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.todo("saturating float add", .{});
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSAddSat(lhs, rhs, "");
|
|
|
|
return self.builder.buildUAddSat(lhs, rhs, "");
|
|
}
|
|
|
|
fn airSub(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.buildFloatOp(.sub, inst_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildNSWSub(lhs, rhs, "");
|
|
return self.builder.buildNUWSub(lhs, rhs, "");
|
|
}
|
|
|
|
fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
return self.builder.buildSub(lhs, rhs, "");
|
|
}
|
|
|
|
fn airSubSat(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.todo("saturating float sub", .{});
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSSubSat(lhs, rhs, "");
|
|
return self.builder.buildUSubSat(lhs, rhs, "");
|
|
}
|
|
|
|
fn airMul(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.buildFloatOp(.mul, inst_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildNSWMul(lhs, rhs, "");
|
|
return self.builder.buildNUWMul(lhs, rhs, "");
|
|
}
|
|
|
|
fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
return self.builder.buildMul(lhs, rhs, "");
|
|
}
|
|
|
|
fn airMulSat(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isAnyFloat()) return self.todo("saturating float mul", .{});
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSMulFixSat(lhs, rhs, "");
|
|
return self.builder.buildUMulFixSat(lhs, rhs, "");
|
|
}
|
|
|
|
fn airDivFloat(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
|
|
return self.buildFloatOp(.div, inst_ty, 2, .{ lhs, rhs });
|
|
}
|
|
|
|
fn airDivTrunc(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isRuntimeFloat()) {
|
|
const result = try self.buildFloatOp(.div, inst_ty, 2, .{ lhs, rhs });
|
|
return self.buildFloatOp(.trunc, inst_ty, 1, .{result});
|
|
}
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSDiv(lhs, rhs, "");
|
|
return self.builder.buildUDiv(lhs, rhs, "");
|
|
}
|
|
|
|
fn airDivFloor(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isRuntimeFloat()) {
|
|
const result = try self.buildFloatOp(.div, inst_ty, 2, .{ lhs, rhs });
|
|
return self.buildFloatOp(.floor, inst_ty, 1, .{result});
|
|
}
|
|
if (scalar_ty.isSignedInt(mod)) {
|
|
const inst_llvm_ty = try o.lowerType(inst_ty);
|
|
const scalar_bit_size_minus_one = scalar_ty.bitSize(mod) - 1;
|
|
const bit_size_minus_one = if (inst_ty.zigTypeTag(mod) == .Vector) const_vector: {
|
|
const vec_len = inst_ty.vectorLen(mod);
|
|
const scalar_llvm_ty = try o.lowerType(scalar_ty);
|
|
|
|
const shifts = try self.gpa.alloc(*llvm.Value, vec_len);
|
|
defer self.gpa.free(shifts);
|
|
|
|
@memset(shifts, scalar_llvm_ty.constInt(scalar_bit_size_minus_one, .False));
|
|
break :const_vector llvm.constVector(shifts.ptr, vec_len);
|
|
} else inst_llvm_ty.constInt(scalar_bit_size_minus_one, .False);
|
|
|
|
const div = self.builder.buildSDiv(lhs, rhs, "");
|
|
const rem = self.builder.buildSRem(lhs, rhs, "");
|
|
const div_sign = self.builder.buildXor(lhs, rhs, "");
|
|
const div_sign_mask = self.builder.buildAShr(div_sign, bit_size_minus_one, "");
|
|
const zero = inst_llvm_ty.constNull();
|
|
const rem_nonzero = self.builder.buildICmp(.NE, rem, zero, "");
|
|
const correction = self.builder.buildSelect(rem_nonzero, div_sign_mask, zero, "");
|
|
return self.builder.buildNSWAdd(div, correction, "");
|
|
}
|
|
return self.builder.buildUDiv(lhs, rhs, "");
|
|
}
|
|
|
|
fn airDivExact(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isRuntimeFloat()) return self.buildFloatOp(.div, inst_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildExactSDiv(lhs, rhs, "");
|
|
return self.builder.buildExactUDiv(lhs, rhs, "");
|
|
}
|
|
|
|
fn airRem(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isRuntimeFloat()) return self.buildFloatOp(.fmod, inst_ty, 2, .{ lhs, rhs });
|
|
if (scalar_ty.isSignedInt(mod)) return self.builder.buildSRem(lhs, rhs, "");
|
|
return self.builder.buildURem(lhs, rhs, "");
|
|
}
|
|
|
|
fn airMod(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const inst_llvm_ty = try o.lowerType(inst_ty);
|
|
const scalar_ty = inst_ty.scalarType(mod);
|
|
|
|
if (scalar_ty.isRuntimeFloat()) {
|
|
const a = try self.buildFloatOp(.fmod, inst_ty, 2, .{ lhs, rhs });
|
|
const b = try self.buildFloatOp(.add, inst_ty, 2, .{ a, rhs });
|
|
const c = try self.buildFloatOp(.fmod, inst_ty, 2, .{ b, rhs });
|
|
const zero = inst_llvm_ty.constNull();
|
|
const ltz = try self.buildFloatCmp(.lt, inst_ty, .{ lhs, zero });
|
|
return self.builder.buildSelect(ltz, c, a, "");
|
|
}
|
|
if (scalar_ty.isSignedInt(mod)) {
|
|
const scalar_bit_size_minus_one = scalar_ty.bitSize(mod) - 1;
|
|
const bit_size_minus_one = if (inst_ty.zigTypeTag(mod) == .Vector) const_vector: {
|
|
const vec_len = inst_ty.vectorLen(mod);
|
|
const scalar_llvm_ty = try o.lowerType(scalar_ty);
|
|
|
|
const shifts = try self.gpa.alloc(*llvm.Value, vec_len);
|
|
defer self.gpa.free(shifts);
|
|
|
|
@memset(shifts, scalar_llvm_ty.constInt(scalar_bit_size_minus_one, .False));
|
|
break :const_vector llvm.constVector(shifts.ptr, vec_len);
|
|
} else inst_llvm_ty.constInt(scalar_bit_size_minus_one, .False);
|
|
|
|
const rem = self.builder.buildSRem(lhs, rhs, "");
|
|
const div_sign = self.builder.buildXor(lhs, rhs, "");
|
|
const div_sign_mask = self.builder.buildAShr(div_sign, bit_size_minus_one, "");
|
|
const rhs_masked = self.builder.buildAnd(rhs, div_sign_mask, "");
|
|
const zero = inst_llvm_ty.constNull();
|
|
const rem_nonzero = self.builder.buildICmp(.NE, rem, zero, "");
|
|
const correction = self.builder.buildSelect(rem_nonzero, rhs_masked, zero, "");
|
|
return self.builder.buildNSWAdd(rem, correction, "");
|
|
}
|
|
return self.builder.buildURem(lhs, rhs, "");
|
|
}
|
|
|
|
fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
const ptr = try self.resolveInst(bin_op.lhs);
|
|
const offset = try self.resolveInst(bin_op.rhs);
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const llvm_elem_ty = try o.lowerPtrElemTy(ptr_ty.childType(mod));
|
|
switch (ptr_ty.ptrSize(mod)) {
|
|
.One => {
|
|
// It's a pointer to an array, so according to LLVM we need an extra GEP index.
|
|
const indices: [2]*llvm.Value = .{ self.context.intType(32).constNull(), offset };
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, ptr, &indices, indices.len, "");
|
|
},
|
|
.C, .Many => {
|
|
const indices: [1]*llvm.Value = .{offset};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, ptr, &indices, indices.len, "");
|
|
},
|
|
.Slice => {
|
|
const base = self.builder.buildExtractValue(ptr, 0, "");
|
|
const indices: [1]*llvm.Value = .{offset};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, base, &indices, indices.len, "");
|
|
},
|
|
}
|
|
}
|
|
|
|
fn airPtrSub(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const bin_op = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
const ptr = try self.resolveInst(bin_op.lhs);
|
|
const offset = try self.resolveInst(bin_op.rhs);
|
|
const negative_offset = self.builder.buildNeg(offset, "");
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const llvm_elem_ty = try o.lowerPtrElemTy(ptr_ty.childType(mod));
|
|
switch (ptr_ty.ptrSize(mod)) {
|
|
.One => {
|
|
// It's a pointer to an array, so according to LLVM we need an extra GEP index.
|
|
const indices: [2]*llvm.Value = .{
|
|
self.context.intType(32).constNull(), negative_offset,
|
|
};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, ptr, &indices, indices.len, "");
|
|
},
|
|
.C, .Many => {
|
|
const indices: [1]*llvm.Value = .{negative_offset};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, ptr, &indices, indices.len, "");
|
|
},
|
|
.Slice => {
|
|
const base = self.builder.buildExtractValue(ptr, 0, "");
|
|
const indices: [1]*llvm.Value = .{negative_offset};
|
|
return self.builder.buildInBoundsGEP(llvm_elem_ty, base, &indices, indices.len, "");
|
|
},
|
|
}
|
|
}
|
|
|
|
fn airOverflow(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
signed_intrinsic: []const u8,
|
|
unsigned_intrinsic: []const u8,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
|
|
const lhs = try self.resolveInst(extra.lhs);
|
|
const rhs = try self.resolveInst(extra.rhs);
|
|
|
|
const lhs_ty = self.typeOf(extra.lhs);
|
|
const scalar_ty = lhs_ty.scalarType(mod);
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
|
|
const intrinsic_name = if (scalar_ty.isSignedInt(mod)) signed_intrinsic else unsigned_intrinsic;
|
|
|
|
const llvm_lhs_ty = try o.lowerType(lhs_ty);
|
|
const llvm_dest_ty = try o.lowerType(dest_ty);
|
|
|
|
const llvm_fn = self.getIntrinsic(intrinsic_name, &.{llvm_lhs_ty});
|
|
const result_struct = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &[_]*llvm.Value{ lhs, rhs }, 2, .Fast, .Auto, "");
|
|
|
|
const result = self.builder.buildExtractValue(result_struct, 0, "");
|
|
const overflow_bit = self.builder.buildExtractValue(result_struct, 1, "");
|
|
|
|
const result_index = llvmField(dest_ty, 0, mod).?.index;
|
|
const overflow_index = llvmField(dest_ty, 1, mod).?.index;
|
|
|
|
if (isByRef(dest_ty, mod)) {
|
|
const result_alignment = dest_ty.abiAlignment(mod);
|
|
const alloca_inst = self.buildAlloca(llvm_dest_ty, result_alignment);
|
|
{
|
|
const field_ptr = self.builder.buildStructGEP(llvm_dest_ty, alloca_inst, result_index, "");
|
|
const store_inst = self.builder.buildStore(result, field_ptr);
|
|
store_inst.setAlignment(result_alignment);
|
|
}
|
|
{
|
|
const field_ptr = self.builder.buildStructGEP(llvm_dest_ty, alloca_inst, overflow_index, "");
|
|
const store_inst = self.builder.buildStore(overflow_bit, field_ptr);
|
|
store_inst.setAlignment(1);
|
|
}
|
|
|
|
return alloca_inst;
|
|
}
|
|
|
|
const partial = self.builder.buildInsertValue(llvm_dest_ty.getUndef(), result, result_index, "");
|
|
return self.builder.buildInsertValue(partial, overflow_bit, overflow_index, "");
|
|
}
|
|
|
|
fn buildElementwiseCall(
|
|
self: *FuncGen,
|
|
llvm_fn: *llvm.Value,
|
|
args_vectors: []const *llvm.Value,
|
|
result_vector: *llvm.Value,
|
|
vector_len: usize,
|
|
) !*llvm.Value {
|
|
const args_len = @as(c_uint, @intCast(args_vectors.len));
|
|
const llvm_i32 = self.context.intType(32);
|
|
assert(args_len <= 3);
|
|
|
|
var i: usize = 0;
|
|
var result = result_vector;
|
|
while (i < vector_len) : (i += 1) {
|
|
const index_i32 = llvm_i32.constInt(i, .False);
|
|
|
|
var args: [3]*llvm.Value = undefined;
|
|
for (args_vectors, 0..) |arg_vector, k| {
|
|
args[k] = self.builder.buildExtractElement(arg_vector, index_i32, "");
|
|
}
|
|
const result_elem = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args_len, .C, .Auto, "");
|
|
result = self.builder.buildInsertElement(result, result_elem, index_i32, "");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
fn getLibcFunction(
|
|
self: *FuncGen,
|
|
fn_name: [:0]const u8,
|
|
param_types: []const *llvm.Type,
|
|
return_type: *llvm.Type,
|
|
) *llvm.Value {
|
|
const o = self.dg.object;
|
|
return o.llvm_module.getNamedFunction(fn_name.ptr) orelse b: {
|
|
const alias = o.llvm_module.getNamedGlobalAlias(fn_name.ptr, fn_name.len);
|
|
break :b if (alias) |a| a.getAliasee() else null;
|
|
} orelse b: {
|
|
const params_len = @as(c_uint, @intCast(param_types.len));
|
|
const fn_type = llvm.functionType(return_type, param_types.ptr, params_len, .False);
|
|
const f = o.llvm_module.addFunction(fn_name, fn_type);
|
|
break :b f;
|
|
};
|
|
}
|
|
|
|
/// Creates a floating point comparison by lowering to the appropriate
|
|
/// hardware instruction or softfloat routine for the target
|
|
fn buildFloatCmp(
|
|
self: *FuncGen,
|
|
pred: math.CompareOperator,
|
|
ty: Type,
|
|
params: [2]*llvm.Value,
|
|
) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const target = o.module.getTarget();
|
|
const scalar_ty = ty.scalarType(mod);
|
|
const scalar_llvm_ty = try o.lowerType(scalar_ty);
|
|
|
|
if (intrinsicsAllowed(scalar_ty, target)) {
|
|
const llvm_predicate: llvm.RealPredicate = switch (pred) {
|
|
.eq => .OEQ,
|
|
.neq => .UNE,
|
|
.lt => .OLT,
|
|
.lte => .OLE,
|
|
.gt => .OGT,
|
|
.gte => .OGE,
|
|
};
|
|
return self.builder.buildFCmp(llvm_predicate, params[0], params[1], "");
|
|
}
|
|
|
|
const float_bits = scalar_ty.floatBits(target);
|
|
const compiler_rt_float_abbrev = compilerRtFloatAbbrev(float_bits);
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const fn_base_name = switch (pred) {
|
|
.neq => "ne",
|
|
.eq => "eq",
|
|
.lt => "lt",
|
|
.lte => "le",
|
|
.gt => "gt",
|
|
.gte => "ge",
|
|
};
|
|
const fn_name = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f2", .{
|
|
fn_base_name, compiler_rt_float_abbrev,
|
|
}) catch unreachable;
|
|
|
|
const param_types = [2]*llvm.Type{ scalar_llvm_ty, scalar_llvm_ty };
|
|
const llvm_i32 = self.context.intType(32);
|
|
const libc_fn = self.getLibcFunction(fn_name, param_types[0..], llvm_i32);
|
|
|
|
const zero = llvm_i32.constInt(0, .False);
|
|
const int_pred: llvm.IntPredicate = switch (pred) {
|
|
.eq => .EQ,
|
|
.neq => .NE,
|
|
.lt => .SLT,
|
|
.lte => .SLE,
|
|
.gt => .SGT,
|
|
.gte => .SGE,
|
|
};
|
|
|
|
if (ty.zigTypeTag(mod) == .Vector) {
|
|
const vec_len = ty.vectorLen(mod);
|
|
const vector_result_ty = llvm_i32.vectorType(vec_len);
|
|
|
|
var result = vector_result_ty.getUndef();
|
|
result = try self.buildElementwiseCall(libc_fn, ¶ms, result, vec_len);
|
|
|
|
const zero_vector = self.builder.buildVectorSplat(vec_len, zero, "");
|
|
return self.builder.buildICmp(int_pred, result, zero_vector, "");
|
|
}
|
|
|
|
const result = self.builder.buildCall(libc_fn.globalGetValueType(), libc_fn, ¶ms, params.len, .C, .Auto, "");
|
|
return self.builder.buildICmp(int_pred, result, zero, "");
|
|
}
|
|
|
|
const FloatOp = enum {
|
|
add,
|
|
ceil,
|
|
cos,
|
|
div,
|
|
exp,
|
|
exp2,
|
|
fabs,
|
|
floor,
|
|
fma,
|
|
fmax,
|
|
fmin,
|
|
fmod,
|
|
log,
|
|
log10,
|
|
log2,
|
|
mul,
|
|
neg,
|
|
round,
|
|
sin,
|
|
sqrt,
|
|
sub,
|
|
tan,
|
|
trunc,
|
|
};
|
|
|
|
const FloatOpStrat = union(enum) {
|
|
intrinsic: []const u8,
|
|
libc: [:0]const u8,
|
|
};
|
|
|
|
/// Creates a floating point operation (add, sub, fma, sqrt, exp, etc.)
|
|
/// by lowering to the appropriate hardware instruction or softfloat
|
|
/// routine for the target
|
|
fn buildFloatOp(
|
|
self: *FuncGen,
|
|
comptime op: FloatOp,
|
|
ty: Type,
|
|
comptime params_len: usize,
|
|
params: [params_len]*llvm.Value,
|
|
) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
const scalar_ty = ty.scalarType(mod);
|
|
const llvm_ty = try o.lowerType(ty);
|
|
const scalar_llvm_ty = try o.lowerType(scalar_ty);
|
|
|
|
const intrinsics_allowed = op != .tan and intrinsicsAllowed(scalar_ty, target);
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const strat: FloatOpStrat = if (intrinsics_allowed) switch (op) {
|
|
// Some operations are dedicated LLVM instructions, not available as intrinsics
|
|
.neg => return self.builder.buildFNeg(params[0], ""),
|
|
.add => return self.builder.buildFAdd(params[0], params[1], ""),
|
|
.sub => return self.builder.buildFSub(params[0], params[1], ""),
|
|
.mul => return self.builder.buildFMul(params[0], params[1], ""),
|
|
.div => return self.builder.buildFDiv(params[0], params[1], ""),
|
|
.fmod => return self.builder.buildFRem(params[0], params[1], ""),
|
|
.fmax => return self.builder.buildMaxNum(params[0], params[1], ""),
|
|
.fmin => return self.builder.buildMinNum(params[0], params[1], ""),
|
|
else => .{ .intrinsic = "llvm." ++ @tagName(op) },
|
|
} else b: {
|
|
const float_bits = scalar_ty.floatBits(target);
|
|
break :b switch (op) {
|
|
.neg => {
|
|
// In this case we can generate a softfloat negation by XORing the
|
|
// bits with a constant.
|
|
const int_llvm_ty = self.context.intType(float_bits);
|
|
const one = int_llvm_ty.constInt(1, .False);
|
|
const shift_amt = int_llvm_ty.constInt(float_bits - 1, .False);
|
|
const sign_mask = one.constShl(shift_amt);
|
|
const result = if (ty.zigTypeTag(mod) == .Vector) blk: {
|
|
const splat_sign_mask = self.builder.buildVectorSplat(ty.vectorLen(mod), sign_mask, "");
|
|
const cast_ty = int_llvm_ty.vectorType(ty.vectorLen(mod));
|
|
const bitcasted_operand = self.builder.buildBitCast(params[0], cast_ty, "");
|
|
break :blk self.builder.buildXor(bitcasted_operand, splat_sign_mask, "");
|
|
} else blk: {
|
|
const bitcasted_operand = self.builder.buildBitCast(params[0], int_llvm_ty, "");
|
|
break :blk self.builder.buildXor(bitcasted_operand, sign_mask, "");
|
|
};
|
|
return self.builder.buildBitCast(result, llvm_ty, "");
|
|
},
|
|
.add, .sub, .div, .mul => FloatOpStrat{
|
|
.libc = std.fmt.bufPrintZ(&fn_name_buf, "__{s}{s}f3", .{
|
|
@tagName(op), compilerRtFloatAbbrev(float_bits),
|
|
}) catch unreachable,
|
|
},
|
|
.ceil,
|
|
.cos,
|
|
.exp,
|
|
.exp2,
|
|
.fabs,
|
|
.floor,
|
|
.fma,
|
|
.fmax,
|
|
.fmin,
|
|
.fmod,
|
|
.log,
|
|
.log10,
|
|
.log2,
|
|
.round,
|
|
.sin,
|
|
.sqrt,
|
|
.tan,
|
|
.trunc,
|
|
=> FloatOpStrat{
|
|
.libc = std.fmt.bufPrintZ(&fn_name_buf, "{s}{s}{s}", .{
|
|
libcFloatPrefix(float_bits), @tagName(op), libcFloatSuffix(float_bits),
|
|
}) catch unreachable,
|
|
},
|
|
};
|
|
};
|
|
|
|
const llvm_fn: *llvm.Value = switch (strat) {
|
|
.intrinsic => |fn_name| self.getIntrinsic(fn_name, &.{llvm_ty}),
|
|
.libc => |fn_name| b: {
|
|
const param_types = [3]*llvm.Type{ scalar_llvm_ty, scalar_llvm_ty, scalar_llvm_ty };
|
|
const libc_fn = self.getLibcFunction(fn_name, param_types[0..params.len], scalar_llvm_ty);
|
|
if (ty.zigTypeTag(mod) == .Vector) {
|
|
const result = llvm_ty.getUndef();
|
|
return self.buildElementwiseCall(libc_fn, ¶ms, result, ty.vectorLen(mod));
|
|
}
|
|
|
|
break :b libc_fn;
|
|
},
|
|
};
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params_len, .C, .Auto, "");
|
|
}
|
|
|
|
fn airMulAdd(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
|
|
|
|
const mulend1 = try self.resolveInst(extra.lhs);
|
|
const mulend2 = try self.resolveInst(extra.rhs);
|
|
const addend = try self.resolveInst(pl_op.operand);
|
|
|
|
const ty = self.typeOfIndex(inst);
|
|
return self.buildFloatOp(.fma, ty, 3, .{ mulend1, mulend2, addend });
|
|
}
|
|
|
|
fn airShlWithOverflow(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.Bin, ty_pl.payload).data;
|
|
|
|
const lhs = try self.resolveInst(extra.lhs);
|
|
const rhs = try self.resolveInst(extra.rhs);
|
|
|
|
const lhs_ty = self.typeOf(extra.lhs);
|
|
const rhs_ty = self.typeOf(extra.rhs);
|
|
const lhs_scalar_ty = lhs_ty.scalarType(mod);
|
|
const rhs_scalar_ty = rhs_ty.scalarType(mod);
|
|
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
const llvm_dest_ty = try o.lowerType(dest_ty);
|
|
|
|
const casted_rhs = if (rhs_scalar_ty.bitSize(mod) < lhs_scalar_ty.bitSize(mod))
|
|
self.builder.buildZExt(rhs, try o.lowerType(lhs_ty), "")
|
|
else
|
|
rhs;
|
|
|
|
const result = self.builder.buildShl(lhs, casted_rhs, "");
|
|
const reconstructed = if (lhs_scalar_ty.isSignedInt(mod))
|
|
self.builder.buildAShr(result, casted_rhs, "")
|
|
else
|
|
self.builder.buildLShr(result, casted_rhs, "");
|
|
|
|
const overflow_bit = self.builder.buildICmp(.NE, lhs, reconstructed, "");
|
|
|
|
const result_index = llvmField(dest_ty, 0, mod).?.index;
|
|
const overflow_index = llvmField(dest_ty, 1, mod).?.index;
|
|
|
|
if (isByRef(dest_ty, mod)) {
|
|
const result_alignment = dest_ty.abiAlignment(mod);
|
|
const alloca_inst = self.buildAlloca(llvm_dest_ty, result_alignment);
|
|
{
|
|
const field_ptr = self.builder.buildStructGEP(llvm_dest_ty, alloca_inst, result_index, "");
|
|
const store_inst = self.builder.buildStore(result, field_ptr);
|
|
store_inst.setAlignment(result_alignment);
|
|
}
|
|
{
|
|
const field_ptr = self.builder.buildStructGEP(llvm_dest_ty, alloca_inst, overflow_index, "");
|
|
const store_inst = self.builder.buildStore(overflow_bit, field_ptr);
|
|
store_inst.setAlignment(1);
|
|
}
|
|
|
|
return alloca_inst;
|
|
}
|
|
|
|
const partial = self.builder.buildInsertValue(llvm_dest_ty.getUndef(), result, result_index, "");
|
|
return self.builder.buildInsertValue(partial, overflow_bit, overflow_index, "");
|
|
}
|
|
|
|
fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
return self.builder.buildAnd(lhs, rhs, "");
|
|
}
|
|
|
|
fn airOr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
return self.builder.buildOr(lhs, rhs, "");
|
|
}
|
|
|
|
fn airXor(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
return self.builder.buildXor(lhs, rhs, "");
|
|
}
|
|
|
|
fn airShlExact(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
const lhs_ty = self.typeOf(bin_op.lhs);
|
|
const rhs_ty = self.typeOf(bin_op.rhs);
|
|
const lhs_scalar_ty = lhs_ty.scalarType(mod);
|
|
const rhs_scalar_ty = rhs_ty.scalarType(mod);
|
|
|
|
const casted_rhs = if (rhs_scalar_ty.bitSize(mod) < lhs_scalar_ty.bitSize(mod))
|
|
self.builder.buildZExt(rhs, try o.lowerType(lhs_ty), "")
|
|
else
|
|
rhs;
|
|
if (lhs_scalar_ty.isSignedInt(mod)) return self.builder.buildNSWShl(lhs, casted_rhs, "");
|
|
return self.builder.buildNUWShl(lhs, casted_rhs, "");
|
|
}
|
|
|
|
fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
const lhs_type = self.typeOf(bin_op.lhs);
|
|
const rhs_type = self.typeOf(bin_op.rhs);
|
|
const lhs_scalar_ty = lhs_type.scalarType(mod);
|
|
const rhs_scalar_ty = rhs_type.scalarType(mod);
|
|
|
|
const casted_rhs = if (rhs_scalar_ty.bitSize(mod) < lhs_scalar_ty.bitSize(mod))
|
|
self.builder.buildZExt(rhs, try o.lowerType(lhs_type), "")
|
|
else
|
|
rhs;
|
|
return self.builder.buildShl(lhs, casted_rhs, "");
|
|
}
|
|
|
|
fn airShlSat(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
const lhs_ty = self.typeOf(bin_op.lhs);
|
|
const rhs_ty = self.typeOf(bin_op.rhs);
|
|
const lhs_scalar_ty = lhs_ty.scalarType(mod);
|
|
const rhs_scalar_ty = rhs_ty.scalarType(mod);
|
|
const lhs_bits = lhs_scalar_ty.bitSize(mod);
|
|
|
|
const casted_rhs = if (rhs_scalar_ty.bitSize(mod) < lhs_bits)
|
|
self.builder.buildZExt(rhs, lhs.typeOf(), "")
|
|
else
|
|
rhs;
|
|
|
|
const result = if (lhs_scalar_ty.isSignedInt(mod))
|
|
self.builder.buildSShlSat(lhs, casted_rhs, "")
|
|
else
|
|
self.builder.buildUShlSat(lhs, casted_rhs, "");
|
|
|
|
// LLVM langref says "If b is (statically or dynamically) equal to or
|
|
// larger than the integer bit width of the arguments, the result is a
|
|
// poison value."
|
|
// However Zig semantics says that saturating shift left can never produce
|
|
// undefined; instead it saturates.
|
|
const lhs_scalar_llvm_ty = try o.lowerType(lhs_scalar_ty);
|
|
const bits = lhs_scalar_llvm_ty.constInt(lhs_bits, .False);
|
|
const lhs_max = lhs_scalar_llvm_ty.constAllOnes();
|
|
if (rhs_ty.zigTypeTag(mod) == .Vector) {
|
|
const vec_len = rhs_ty.vectorLen(mod);
|
|
const bits_vec = self.builder.buildVectorSplat(vec_len, bits, "");
|
|
const lhs_max_vec = self.builder.buildVectorSplat(vec_len, lhs_max, "");
|
|
const in_range = self.builder.buildICmp(.ULT, rhs, bits_vec, "");
|
|
return self.builder.buildSelect(in_range, result, lhs_max_vec, "");
|
|
} else {
|
|
const in_range = self.builder.buildICmp(.ULT, rhs, bits, "");
|
|
return self.builder.buildSelect(in_range, result, lhs_max, "");
|
|
}
|
|
}
|
|
|
|
fn airShr(self: *FuncGen, inst: Air.Inst.Index, is_exact: bool) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
|
|
const lhs = try self.resolveInst(bin_op.lhs);
|
|
const rhs = try self.resolveInst(bin_op.rhs);
|
|
|
|
const lhs_ty = self.typeOf(bin_op.lhs);
|
|
const rhs_ty = self.typeOf(bin_op.rhs);
|
|
const lhs_scalar_ty = lhs_ty.scalarType(mod);
|
|
const rhs_scalar_ty = rhs_ty.scalarType(mod);
|
|
|
|
const casted_rhs = if (rhs_scalar_ty.bitSize(mod) < lhs_scalar_ty.bitSize(mod))
|
|
self.builder.buildZExt(rhs, try o.lowerType(lhs_ty), "")
|
|
else
|
|
rhs;
|
|
const is_signed_int = lhs_scalar_ty.isSignedInt(mod);
|
|
|
|
if (is_exact) {
|
|
if (is_signed_int) {
|
|
return self.builder.buildAShrExact(lhs, casted_rhs, "");
|
|
} else {
|
|
return self.builder.buildLShrExact(lhs, casted_rhs, "");
|
|
}
|
|
} else {
|
|
if (is_signed_int) {
|
|
return self.builder.buildAShr(lhs, casted_rhs, "");
|
|
} else {
|
|
return self.builder.buildLShr(lhs, casted_rhs, "");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
const dest_info = dest_ty.intInfo(mod);
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const operand_info = operand_ty.intInfo(mod);
|
|
|
|
if (operand_info.bits < dest_info.bits) {
|
|
switch (operand_info.signedness) {
|
|
.signed => return self.builder.buildSExt(operand, dest_llvm_ty, ""),
|
|
.unsigned => return self.builder.buildZExt(operand, dest_llvm_ty, ""),
|
|
}
|
|
} else if (operand_info.bits > dest_info.bits) {
|
|
return self.builder.buildTrunc(operand, dest_llvm_ty, "");
|
|
} else {
|
|
return operand;
|
|
}
|
|
}
|
|
|
|
fn airTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const dest_llvm_ty = try o.lowerType(self.typeOfIndex(inst));
|
|
return self.builder.buildTrunc(operand, dest_llvm_ty, "");
|
|
}
|
|
|
|
fn airFptrunc(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
const target = mod.getTarget();
|
|
const dest_bits = dest_ty.floatBits(target);
|
|
const src_bits = operand_ty.floatBits(target);
|
|
|
|
if (intrinsicsAllowed(dest_ty, target) and intrinsicsAllowed(operand_ty, target)) {
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
return self.builder.buildFPTrunc(operand, dest_llvm_ty, "");
|
|
} else {
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const fn_name = std.fmt.bufPrintZ(&fn_name_buf, "__trunc{s}f{s}f2", .{
|
|
compilerRtFloatAbbrev(src_bits), compilerRtFloatAbbrev(dest_bits),
|
|
}) catch unreachable;
|
|
|
|
const params = [1]*llvm.Value{operand};
|
|
const param_types = [1]*llvm.Type{operand_llvm_ty};
|
|
const llvm_fn = self.getLibcFunction(fn_name, ¶m_types, dest_llvm_ty);
|
|
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .C, .Auto, "");
|
|
}
|
|
}
|
|
|
|
fn airFpext(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const dest_ty = self.typeOfIndex(inst);
|
|
const target = mod.getTarget();
|
|
const dest_bits = dest_ty.floatBits(target);
|
|
const src_bits = operand_ty.floatBits(target);
|
|
|
|
if (intrinsicsAllowed(dest_ty, target) and intrinsicsAllowed(operand_ty, target)) {
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
return self.builder.buildFPExt(operand, dest_llvm_ty, "");
|
|
} else {
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
const dest_llvm_ty = try o.lowerType(dest_ty);
|
|
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const fn_name = std.fmt.bufPrintZ(&fn_name_buf, "__extend{s}f{s}f2", .{
|
|
compilerRtFloatAbbrev(src_bits), compilerRtFloatAbbrev(dest_bits),
|
|
}) catch unreachable;
|
|
|
|
const params = [1]*llvm.Value{operand};
|
|
const param_types = [1]*llvm.Type{operand_llvm_ty};
|
|
const llvm_fn = self.getLibcFunction(fn_name, ¶m_types, dest_llvm_ty);
|
|
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .C, .Auto, "");
|
|
}
|
|
}
|
|
|
|
fn airIntFromPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const ptr_ty = self.typeOf(un_op);
|
|
const operand_ptr = self.sliceOrArrayPtr(operand, ptr_ty);
|
|
const dest_llvm_ty = try o.lowerType(self.typeOfIndex(inst));
|
|
return self.builder.buildPtrToInt(operand_ptr, dest_llvm_ty, "");
|
|
}
|
|
|
|
fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !*llvm.Value {
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
return self.bitCast(operand, operand_ty, inst_ty);
|
|
}
|
|
|
|
fn bitCast(self: *FuncGen, operand: *llvm.Value, operand_ty: Type, inst_ty: Type) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const operand_is_ref = isByRef(operand_ty, mod);
|
|
const result_is_ref = isByRef(inst_ty, mod);
|
|
const llvm_dest_ty = try o.lowerType(inst_ty);
|
|
|
|
if (operand_is_ref and result_is_ref) {
|
|
// They are both pointers, so just return the same opaque pointer :)
|
|
return operand;
|
|
}
|
|
|
|
if (llvm_dest_ty.getTypeKind() == .Integer and
|
|
operand.typeOf().getTypeKind() == .Integer)
|
|
{
|
|
return self.builder.buildZExtOrBitCast(operand, llvm_dest_ty, "");
|
|
}
|
|
|
|
if (operand_ty.zigTypeTag(mod) == .Int and inst_ty.isPtrAtRuntime(mod)) {
|
|
return self.builder.buildIntToPtr(operand, llvm_dest_ty, "");
|
|
}
|
|
|
|
if (operand_ty.zigTypeTag(mod) == .Vector and inst_ty.zigTypeTag(mod) == .Array) {
|
|
const elem_ty = operand_ty.childType(mod);
|
|
if (!result_is_ref) {
|
|
return self.dg.todo("implement bitcast vector to non-ref array", .{});
|
|
}
|
|
const array_ptr = self.buildAlloca(llvm_dest_ty, null);
|
|
const bitcast_ok = elem_ty.bitSize(mod) == elem_ty.abiSize(mod) * 8;
|
|
if (bitcast_ok) {
|
|
const llvm_store = self.builder.buildStore(operand, array_ptr);
|
|
llvm_store.setAlignment(inst_ty.abiAlignment(mod));
|
|
} else {
|
|
// If the ABI size of the element type is not evenly divisible by size in bits;
|
|
// a simple bitcast will not work, and we fall back to extractelement.
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const llvm_u32 = self.context.intType(32);
|
|
const zero = llvm_usize.constNull();
|
|
const vector_len = operand_ty.arrayLen(mod);
|
|
var i: u64 = 0;
|
|
while (i < vector_len) : (i += 1) {
|
|
const index_usize = llvm_usize.constInt(i, .False);
|
|
const index_u32 = llvm_u32.constInt(i, .False);
|
|
const indexes: [2]*llvm.Value = .{ zero, index_usize };
|
|
const elem_ptr = self.builder.buildInBoundsGEP(llvm_dest_ty, array_ptr, &indexes, indexes.len, "");
|
|
const elem = self.builder.buildExtractElement(operand, index_u32, "");
|
|
_ = self.builder.buildStore(elem, elem_ptr);
|
|
}
|
|
}
|
|
return array_ptr;
|
|
} else if (operand_ty.zigTypeTag(mod) == .Array and inst_ty.zigTypeTag(mod) == .Vector) {
|
|
const elem_ty = operand_ty.childType(mod);
|
|
const llvm_vector_ty = try o.lowerType(inst_ty);
|
|
if (!operand_is_ref) {
|
|
return self.dg.todo("implement bitcast non-ref array to vector", .{});
|
|
}
|
|
|
|
const bitcast_ok = elem_ty.bitSize(mod) == elem_ty.abiSize(mod) * 8;
|
|
if (bitcast_ok) {
|
|
const vector = self.builder.buildLoad(llvm_vector_ty, operand, "");
|
|
// The array is aligned to the element's alignment, while the vector might have a completely
|
|
// different alignment. This means we need to enforce the alignment of this load.
|
|
vector.setAlignment(elem_ty.abiAlignment(mod));
|
|
return vector;
|
|
} else {
|
|
// If the ABI size of the element type is not evenly divisible by size in bits;
|
|
// a simple bitcast will not work, and we fall back to extractelement.
|
|
const array_llvm_ty = try o.lowerType(operand_ty);
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const llvm_u32 = self.context.intType(32);
|
|
const zero = llvm_usize.constNull();
|
|
const vector_len = operand_ty.arrayLen(mod);
|
|
var vector = llvm_vector_ty.getUndef();
|
|
var i: u64 = 0;
|
|
while (i < vector_len) : (i += 1) {
|
|
const index_usize = llvm_usize.constInt(i, .False);
|
|
const index_u32 = llvm_u32.constInt(i, .False);
|
|
const indexes: [2]*llvm.Value = .{ zero, index_usize };
|
|
const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_ty, operand, &indexes, indexes.len, "");
|
|
const elem = self.builder.buildLoad(elem_llvm_ty, elem_ptr, "");
|
|
vector = self.builder.buildInsertElement(vector, elem, index_u32, "");
|
|
}
|
|
|
|
return vector;
|
|
}
|
|
}
|
|
|
|
if (operand_is_ref) {
|
|
const load_inst = self.builder.buildLoad(llvm_dest_ty, operand, "");
|
|
load_inst.setAlignment(operand_ty.abiAlignment(mod));
|
|
return load_inst;
|
|
}
|
|
|
|
if (result_is_ref) {
|
|
const alignment = @max(operand_ty.abiAlignment(mod), inst_ty.abiAlignment(mod));
|
|
const result_ptr = self.buildAlloca(llvm_dest_ty, alignment);
|
|
const store_inst = self.builder.buildStore(operand, result_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
return result_ptr;
|
|
}
|
|
|
|
if (llvm_dest_ty.getTypeKind() == .Struct) {
|
|
// Both our operand and our result are values, not pointers,
|
|
// but LLVM won't let us bitcast struct values.
|
|
// Therefore, we store operand to alloca, then load for result.
|
|
const alignment = @max(operand_ty.abiAlignment(mod), inst_ty.abiAlignment(mod));
|
|
const result_ptr = self.buildAlloca(llvm_dest_ty, alignment);
|
|
const store_inst = self.builder.buildStore(operand, result_ptr);
|
|
store_inst.setAlignment(alignment);
|
|
const load_inst = self.builder.buildLoad(llvm_dest_ty, result_ptr, "");
|
|
load_inst.setAlignment(alignment);
|
|
return load_inst;
|
|
}
|
|
|
|
return self.builder.buildBitCast(operand, llvm_dest_ty, "");
|
|
}
|
|
|
|
fn airIntFromBool(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
return operand;
|
|
}
|
|
|
|
fn airArg(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const arg_val = self.args[self.arg_index];
|
|
self.arg_index += 1;
|
|
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
if (o.di_builder) |dib| {
|
|
if (needDbgVarWorkaround(o)) {
|
|
return arg_val;
|
|
}
|
|
|
|
const src_index = self.air.instructions.items(.data)[inst].arg.src_index;
|
|
const func_index = self.dg.decl.getOwnedFunctionIndex();
|
|
const func = mod.funcInfo(func_index);
|
|
const lbrace_line = mod.declPtr(func.owner_decl).src_line + func.lbrace_line + 1;
|
|
const lbrace_col = func.lbrace_column + 1;
|
|
const di_local_var = dib.createParameterVariable(
|
|
self.di_scope.?,
|
|
mod.getParamName(func_index, src_index).ptr, // TODO test 0 bit args
|
|
self.di_file.?,
|
|
lbrace_line,
|
|
try o.lowerDebugType(inst_ty, .full),
|
|
true, // always preserve
|
|
0, // flags
|
|
self.arg_index, // includes +1 because 0 is return type
|
|
);
|
|
|
|
const debug_loc = llvm.getDebugLoc(lbrace_line, lbrace_col, self.di_scope.?, null);
|
|
const insert_block = self.builder.getInsertBlock();
|
|
if (isByRef(inst_ty, mod)) {
|
|
_ = dib.insertDeclareAtEnd(arg_val, di_local_var, debug_loc, insert_block);
|
|
} else if (o.module.comp.bin_file.options.optimize_mode == .Debug) {
|
|
const alignment = inst_ty.abiAlignment(mod);
|
|
const alloca = self.buildAlloca(arg_val.typeOf(), alignment);
|
|
const store_inst = self.builder.buildStore(arg_val, alloca);
|
|
store_inst.setAlignment(alignment);
|
|
_ = dib.insertDeclareAtEnd(alloca, di_local_var, debug_loc, insert_block);
|
|
} else {
|
|
_ = dib.insertDbgValueIntrinsicAtEnd(arg_val, di_local_var, debug_loc, insert_block);
|
|
}
|
|
}
|
|
|
|
return arg_val;
|
|
}
|
|
|
|
fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ptr_ty = self.typeOfIndex(inst);
|
|
const pointee_type = ptr_ty.childType(mod);
|
|
if (!pointee_type.isFnOrHasRuntimeBitsIgnoreComptime(mod))
|
|
return o.lowerPtrToVoid(ptr_ty);
|
|
|
|
const pointee_llvm_ty = try o.lowerType(pointee_type);
|
|
const alignment = ptr_ty.ptrAlignment(mod);
|
|
return self.buildAlloca(pointee_llvm_ty, alignment);
|
|
}
|
|
|
|
fn airRetPtr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ptr_ty = self.typeOfIndex(inst);
|
|
const ret_ty = ptr_ty.childType(mod);
|
|
if (!ret_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) return o.lowerPtrToVoid(ptr_ty);
|
|
if (self.ret_ptr) |ret_ptr| return ret_ptr;
|
|
const ret_llvm_ty = try o.lowerType(ret_ty);
|
|
return self.buildAlloca(ret_llvm_ty, ptr_ty.ptrAlignment(mod));
|
|
}
|
|
|
|
/// Use this instead of builder.buildAlloca, because this function makes sure to
|
|
/// put the alloca instruction at the top of the function!
|
|
fn buildAlloca(self: *FuncGen, llvm_ty: *llvm.Type, alignment: ?c_uint) *llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
return buildAllocaInner(self.context, self.builder, self.llvm_func, self.di_scope != null, llvm_ty, alignment, target);
|
|
}
|
|
|
|
fn airStore(self: *FuncGen, inst: Air.Inst.Index, safety: bool) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const dest_ptr = try self.resolveInst(bin_op.lhs);
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const operand_ty = ptr_ty.childType(mod);
|
|
|
|
const val_is_undef = if (try self.air.value(bin_op.rhs, mod)) |val| val.isUndefDeep(mod) else false;
|
|
if (val_is_undef) {
|
|
// Even if safety is disabled, we still emit a memset to undefined since it conveys
|
|
// extra information to LLVM. However, safety makes the difference between using
|
|
// 0xaa or actual undefined for the fill byte.
|
|
const u8_llvm_ty = self.context.intType(8);
|
|
const fill_byte = if (safety)
|
|
u8_llvm_ty.constInt(0xaa, .False)
|
|
else
|
|
u8_llvm_ty.getUndef();
|
|
const operand_size = operand_ty.abiSize(mod);
|
|
const usize_llvm_ty = try o.lowerType(Type.usize);
|
|
const len = usize_llvm_ty.constInt(operand_size, .False);
|
|
const dest_ptr_align = ptr_ty.ptrAlignment(mod);
|
|
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, ptr_ty.isVolatilePtr(mod));
|
|
if (safety and mod.comp.bin_file.options.valgrind) {
|
|
self.valgrindMarkUndef(dest_ptr, len);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
const src_operand = try self.resolveInst(bin_op.rhs);
|
|
try self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic);
|
|
return null;
|
|
}
|
|
|
|
/// As an optimization, we want to avoid unnecessary copies of isByRef=true
|
|
/// types. Here, we scan forward in the current block, looking to see if
|
|
/// this load dies before any side effects occur. In such case, we can
|
|
/// safely return the operand without making a copy.
|
|
///
|
|
/// The first instruction of `body_tail` is the one whose copy we want to elide.
|
|
fn canElideLoad(fg: *FuncGen, body_tail: []const Air.Inst.Index) bool {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const ip = &mod.intern_pool;
|
|
for (body_tail[1..]) |body_inst| {
|
|
switch (fg.liveness.categorizeOperand(fg.air, body_inst, body_tail[0], ip)) {
|
|
.none => continue,
|
|
.write, .noret, .complex => return false,
|
|
.tomb => return true,
|
|
}
|
|
}
|
|
// The only way to get here is to hit the end of a loop instruction
|
|
// (implicit repeat).
|
|
return false;
|
|
}
|
|
|
|
fn airLoad(fg: *FuncGen, body_tail: []const Air.Inst.Index) !?*llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const inst = body_tail[0];
|
|
const ty_op = fg.air.instructions.items(.data)[inst].ty_op;
|
|
const ptr_ty = fg.typeOf(ty_op.operand);
|
|
const ptr_info = ptr_ty.ptrInfo(mod);
|
|
const ptr = try fg.resolveInst(ty_op.operand);
|
|
|
|
elide: {
|
|
if (!isByRef(ptr_info.child.toType(), mod)) break :elide;
|
|
if (!canElideLoad(fg, body_tail)) break :elide;
|
|
return ptr;
|
|
}
|
|
return fg.load(ptr, ptr_ty);
|
|
}
|
|
|
|
fn airTrap(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
_ = inst;
|
|
const llvm_fn = self.getIntrinsic("llvm.trap", &.{});
|
|
_ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, undefined, 0, .Cold, .Auto, "");
|
|
_ = self.builder.buildUnreachable();
|
|
return null;
|
|
}
|
|
|
|
fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
_ = inst;
|
|
const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{});
|
|
_ = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, undefined, 0, .C, .Auto, "");
|
|
return null;
|
|
}
|
|
|
|
fn airRetAddr(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
_ = inst;
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const target = mod.getTarget();
|
|
if (!target_util.supportsReturnAddress(target)) {
|
|
// https://github.com/ziglang/zig/issues/11946
|
|
return llvm_usize.constNull();
|
|
}
|
|
|
|
const llvm_i32 = self.context.intType(32);
|
|
const llvm_fn = self.getIntrinsic("llvm.returnaddress", &.{});
|
|
const params = [_]*llvm.Value{llvm_i32.constNull()};
|
|
const ptr_val = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .Fast, .Auto, "");
|
|
return self.builder.buildPtrToInt(ptr_val, llvm_usize, "");
|
|
}
|
|
|
|
fn airFrameAddress(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
_ = inst;
|
|
const o = self.dg.object;
|
|
const llvm_i32 = self.context.intType(32);
|
|
const llvm_fn_name = "llvm.frameaddress.p0";
|
|
const llvm_fn = o.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
|
|
const llvm_p0i8 = self.context.pointerType(0);
|
|
const param_types = [_]*llvm.Type{llvm_i32};
|
|
const fn_type = llvm.functionType(llvm_p0i8, ¶m_types, param_types.len, .False);
|
|
break :blk o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
};
|
|
|
|
const params = [_]*llvm.Value{llvm_i32.constNull()};
|
|
const ptr_val = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .Fast, .Auto, "");
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
return self.builder.buildPtrToInt(ptr_val, llvm_usize, "");
|
|
}
|
|
|
|
fn airFence(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const atomic_order = self.air.instructions.items(.data)[inst].fence;
|
|
const llvm_memory_order = toLlvmAtomicOrdering(atomic_order);
|
|
const single_threaded = llvm.Bool.fromBool(self.single_threaded);
|
|
_ = self.builder.buildFence(llvm_memory_order, single_threaded, "");
|
|
return null;
|
|
}
|
|
|
|
fn airCmpxchg(self: *FuncGen, inst: Air.Inst.Index, is_weak: bool) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
|
|
const ptr = try self.resolveInst(extra.ptr);
|
|
var expected_value = try self.resolveInst(extra.expected_value);
|
|
var new_value = try self.resolveInst(extra.new_value);
|
|
const operand_ty = self.typeOf(extra.ptr).childType(mod);
|
|
const opt_abi_ty = o.getAtomicAbiType(operand_ty, false);
|
|
if (opt_abi_ty) |abi_ty| {
|
|
// operand needs widening and truncating
|
|
if (operand_ty.isSignedInt(mod)) {
|
|
expected_value = self.builder.buildSExt(expected_value, abi_ty, "");
|
|
new_value = self.builder.buildSExt(new_value, abi_ty, "");
|
|
} else {
|
|
expected_value = self.builder.buildZExt(expected_value, abi_ty, "");
|
|
new_value = self.builder.buildZExt(new_value, abi_ty, "");
|
|
}
|
|
}
|
|
const result = self.builder.buildAtomicCmpXchg(
|
|
ptr,
|
|
expected_value,
|
|
new_value,
|
|
toLlvmAtomicOrdering(extra.successOrder()),
|
|
toLlvmAtomicOrdering(extra.failureOrder()),
|
|
llvm.Bool.fromBool(self.single_threaded),
|
|
);
|
|
result.setWeak(llvm.Bool.fromBool(is_weak));
|
|
|
|
const optional_ty = self.typeOfIndex(inst);
|
|
|
|
var payload = self.builder.buildExtractValue(result, 0, "");
|
|
if (opt_abi_ty != null) {
|
|
payload = self.builder.buildTrunc(payload, try o.lowerType(operand_ty), "");
|
|
}
|
|
const success_bit = self.builder.buildExtractValue(result, 1, "");
|
|
|
|
if (optional_ty.optionalReprIsPayload(mod)) {
|
|
return self.builder.buildSelect(success_bit, payload.typeOf().constNull(), payload, "");
|
|
}
|
|
|
|
comptime assert(optional_layout_version == 3);
|
|
|
|
const non_null_bit = self.builder.buildNot(success_bit, "");
|
|
return buildOptional(self, optional_ty, payload, non_null_bit);
|
|
}
|
|
|
|
fn airAtomicRmw(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const extra = self.air.extraData(Air.AtomicRmw, pl_op.payload).data;
|
|
const ptr = try self.resolveInst(pl_op.operand);
|
|
const ptr_ty = self.typeOf(pl_op.operand);
|
|
const operand_ty = ptr_ty.childType(mod);
|
|
const operand = try self.resolveInst(extra.operand);
|
|
const is_signed_int = operand_ty.isSignedInt(mod);
|
|
const is_float = operand_ty.isRuntimeFloat();
|
|
const op = toLlvmAtomicRmwBinOp(extra.op(), is_signed_int, is_float);
|
|
const ordering = toLlvmAtomicOrdering(extra.ordering());
|
|
const single_threaded = llvm.Bool.fromBool(self.single_threaded);
|
|
const opt_abi_ty = o.getAtomicAbiType(operand_ty, op == .Xchg);
|
|
if (opt_abi_ty) |abi_ty| {
|
|
// operand needs widening and truncating or bitcasting.
|
|
const casted_operand = if (is_float)
|
|
self.builder.buildBitCast(operand, abi_ty, "")
|
|
else if (is_signed_int)
|
|
self.builder.buildSExt(operand, abi_ty, "")
|
|
else
|
|
self.builder.buildZExt(operand, abi_ty, "");
|
|
|
|
const uncasted_result = self.builder.buildAtomicRmw(
|
|
op,
|
|
ptr,
|
|
casted_operand,
|
|
ordering,
|
|
single_threaded,
|
|
);
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
if (is_float) {
|
|
return self.builder.buildBitCast(uncasted_result, operand_llvm_ty, "");
|
|
} else {
|
|
return self.builder.buildTrunc(uncasted_result, operand_llvm_ty, "");
|
|
}
|
|
}
|
|
|
|
if (operand.typeOf().getTypeKind() != .Pointer) {
|
|
return self.builder.buildAtomicRmw(op, ptr, operand, ordering, single_threaded);
|
|
}
|
|
|
|
// It's a pointer but we need to treat it as an int.
|
|
const usize_llvm_ty = try o.lowerType(Type.usize);
|
|
const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, "");
|
|
const uncasted_result = self.builder.buildAtomicRmw(
|
|
op,
|
|
ptr,
|
|
casted_operand,
|
|
ordering,
|
|
single_threaded,
|
|
);
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
return self.builder.buildIntToPtr(uncasted_result, operand_llvm_ty, "");
|
|
}
|
|
|
|
fn airAtomicLoad(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const atomic_load = self.air.instructions.items(.data)[inst].atomic_load;
|
|
const ptr = try self.resolveInst(atomic_load.ptr);
|
|
const ptr_ty = self.typeOf(atomic_load.ptr);
|
|
const ptr_info = ptr_ty.ptrInfo(mod);
|
|
const elem_ty = ptr_info.child.toType();
|
|
if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod))
|
|
return null;
|
|
const ordering = toLlvmAtomicOrdering(atomic_load.order);
|
|
const opt_abi_llvm_ty = o.getAtomicAbiType(elem_ty, false);
|
|
const ptr_alignment = @as(u32, @intCast(ptr_info.flags.alignment.toByteUnitsOptional() orelse
|
|
ptr_info.child.toType().abiAlignment(mod)));
|
|
const ptr_volatile = llvm.Bool.fromBool(ptr_info.flags.is_volatile);
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
|
|
if (opt_abi_llvm_ty) |abi_llvm_ty| {
|
|
// operand needs widening and truncating
|
|
const load_inst = self.builder.buildLoad(abi_llvm_ty, ptr, "");
|
|
load_inst.setAlignment(ptr_alignment);
|
|
load_inst.setVolatile(ptr_volatile);
|
|
load_inst.setOrdering(ordering);
|
|
return self.builder.buildTrunc(load_inst, elem_llvm_ty, "");
|
|
}
|
|
const load_inst = self.builder.buildLoad(elem_llvm_ty, ptr, "");
|
|
load_inst.setAlignment(ptr_alignment);
|
|
load_inst.setVolatile(ptr_volatile);
|
|
load_inst.setOrdering(ordering);
|
|
return load_inst;
|
|
}
|
|
|
|
fn airAtomicStore(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
ordering: llvm.AtomicOrdering,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const operand_ty = ptr_ty.childType(mod);
|
|
if (!operand_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) return null;
|
|
const ptr = try self.resolveInst(bin_op.lhs);
|
|
var element = try self.resolveInst(bin_op.rhs);
|
|
const opt_abi_ty = o.getAtomicAbiType(operand_ty, false);
|
|
|
|
if (opt_abi_ty) |abi_ty| {
|
|
// operand needs widening
|
|
if (operand_ty.isSignedInt(mod)) {
|
|
element = self.builder.buildSExt(element, abi_ty, "");
|
|
} else {
|
|
element = self.builder.buildZExt(element, abi_ty, "");
|
|
}
|
|
}
|
|
try self.store(ptr, ptr_ty, element, ordering);
|
|
return null;
|
|
}
|
|
|
|
fn airMemset(self: *FuncGen, inst: Air.Inst.Index, safety: bool) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const dest_slice = try self.resolveInst(bin_op.lhs);
|
|
const ptr_ty = self.typeOf(bin_op.lhs);
|
|
const elem_ty = self.typeOf(bin_op.rhs);
|
|
const target = mod.getTarget();
|
|
const dest_ptr_align = ptr_ty.ptrAlignment(mod);
|
|
const u8_llvm_ty = self.context.intType(8);
|
|
const dest_ptr = self.sliceOrArrayPtr(dest_slice, ptr_ty);
|
|
const is_volatile = ptr_ty.isVolatilePtr(mod);
|
|
|
|
// Any WebAssembly runtime will trap when the destination pointer is out-of-bounds, regardless
|
|
// of the length. This means we need to emit a check where we skip the memset when the length
|
|
// is 0 as we allow for undefined pointers in 0-sized slices.
|
|
// This logic can be removed once https://github.com/ziglang/zig/issues/16360 is done.
|
|
const intrinsic_len0_traps = o.target.isWasm() and
|
|
ptr_ty.isSlice(mod) and
|
|
std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory);
|
|
|
|
if (try self.air.value(bin_op.rhs, mod)) |elem_val| {
|
|
if (elem_val.isUndefDeep(mod)) {
|
|
// Even if safety is disabled, we still emit a memset to undefined since it conveys
|
|
// extra information to LLVM. However, safety makes the difference between using
|
|
// 0xaa or actual undefined for the fill byte.
|
|
const fill_byte = if (safety)
|
|
u8_llvm_ty.constInt(0xaa, .False)
|
|
else
|
|
u8_llvm_ty.getUndef();
|
|
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
|
|
if (intrinsic_len0_traps) {
|
|
try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
} else {
|
|
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
}
|
|
|
|
if (safety and mod.comp.bin_file.options.valgrind) {
|
|
self.valgrindMarkUndef(dest_ptr, len);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Test if the element value is compile-time known to be a
|
|
// repeating byte pattern, for example, `@as(u64, 0)` has a
|
|
// repeating byte pattern of 0 bytes. In such case, the memset
|
|
// intrinsic can be used.
|
|
if (try elem_val.hasRepeatedByteRepr(elem_ty, mod)) |byte_val| {
|
|
const fill_byte = try self.resolveValue(.{
|
|
.ty = Type.u8,
|
|
.val = byte_val,
|
|
});
|
|
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
|
|
|
|
if (intrinsic_len0_traps) {
|
|
try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
} else {
|
|
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
const value = try self.resolveInst(bin_op.rhs);
|
|
const elem_abi_size = elem_ty.abiSize(mod);
|
|
|
|
if (elem_abi_size == 1) {
|
|
// In this case we can take advantage of LLVM's intrinsic.
|
|
const fill_byte = try self.bitCast(value, elem_ty, Type.u8);
|
|
const len = self.sliceOrArrayLenInBytes(dest_slice, ptr_ty);
|
|
|
|
if (intrinsic_len0_traps) {
|
|
try self.safeWasmMemset(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
} else {
|
|
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// non-byte-sized element. lower with a loop. something like this:
|
|
|
|
// entry:
|
|
// ...
|
|
// %end_ptr = getelementptr %ptr, %len
|
|
// br loop
|
|
// loop:
|
|
// %it_ptr = phi body %next_ptr, entry %ptr
|
|
// %end = cmp eq %it_ptr, %end_ptr
|
|
// cond_br %end body, end
|
|
// body:
|
|
// store %it_ptr, %value
|
|
// %next_ptr = getelementptr %it_ptr, 1
|
|
// br loop
|
|
// end:
|
|
// ...
|
|
const entry_block = self.builder.getInsertBlock();
|
|
const loop_block = self.context.appendBasicBlock(self.llvm_func, "InlineMemsetLoop");
|
|
const body_block = self.context.appendBasicBlock(self.llvm_func, "InlineMemsetBody");
|
|
const end_block = self.context.appendBasicBlock(self.llvm_func, "InlineMemsetEnd");
|
|
|
|
const llvm_usize_ty = self.context.intType(target.ptrBitWidth());
|
|
const len = switch (ptr_ty.ptrSize(mod)) {
|
|
.Slice => self.builder.buildExtractValue(dest_slice, 1, ""),
|
|
.One => llvm_usize_ty.constInt(ptr_ty.childType(mod).arrayLen(mod), .False),
|
|
.Many, .C => unreachable,
|
|
};
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
const len_gep = [_]*llvm.Value{len};
|
|
const end_ptr = self.builder.buildInBoundsGEP(elem_llvm_ty, dest_ptr, &len_gep, len_gep.len, "");
|
|
_ = self.builder.buildBr(loop_block);
|
|
|
|
self.builder.positionBuilderAtEnd(loop_block);
|
|
const it_ptr = self.builder.buildPhi(self.context.pointerType(0), "");
|
|
const end = self.builder.buildICmp(.NE, it_ptr, end_ptr, "");
|
|
_ = self.builder.buildCondBr(end, body_block, end_block);
|
|
|
|
self.builder.positionBuilderAtEnd(body_block);
|
|
const elem_abi_alignment = elem_ty.abiAlignment(mod);
|
|
const it_ptr_alignment = @min(elem_abi_alignment, dest_ptr_align);
|
|
if (isByRef(elem_ty, mod)) {
|
|
_ = self.builder.buildMemCpy(
|
|
it_ptr,
|
|
it_ptr_alignment,
|
|
value,
|
|
elem_abi_alignment,
|
|
llvm_usize_ty.constInt(elem_abi_size, .False),
|
|
is_volatile,
|
|
);
|
|
} else {
|
|
const store_inst = self.builder.buildStore(value, it_ptr);
|
|
store_inst.setAlignment(it_ptr_alignment);
|
|
store_inst.setVolatile(llvm.Bool.fromBool(is_volatile));
|
|
}
|
|
const one_gep = [_]*llvm.Value{llvm_usize_ty.constInt(1, .False)};
|
|
const next_ptr = self.builder.buildInBoundsGEP(elem_llvm_ty, it_ptr, &one_gep, one_gep.len, "");
|
|
_ = self.builder.buildBr(loop_block);
|
|
|
|
self.builder.positionBuilderAtEnd(end_block);
|
|
|
|
const incoming_values: [2]*llvm.Value = .{ next_ptr, dest_ptr };
|
|
const incoming_blocks: [2]*llvm.BasicBlock = .{ body_block, entry_block };
|
|
it_ptr.addIncoming(&incoming_values, &incoming_blocks, 2);
|
|
|
|
return null;
|
|
}
|
|
|
|
fn safeWasmMemset(
|
|
self: *FuncGen,
|
|
dest_ptr: *llvm.Value,
|
|
fill_byte: *llvm.Value,
|
|
len: *llvm.Value,
|
|
dest_ptr_align: u32,
|
|
is_volatile: bool,
|
|
) !void {
|
|
const llvm_usize_ty = self.context.intType(self.dg.object.target.ptrBitWidth());
|
|
const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .neq);
|
|
const memset_block = self.context.appendBasicBlock(self.llvm_func, "MemsetTrapSkip");
|
|
const end_block = self.context.appendBasicBlock(self.llvm_func, "MemsetTrapEnd");
|
|
_ = self.builder.buildCondBr(cond, memset_block, end_block);
|
|
self.builder.positionBuilderAtEnd(memset_block);
|
|
_ = self.builder.buildMemSet(dest_ptr, fill_byte, len, dest_ptr_align, is_volatile);
|
|
_ = self.builder.buildBr(end_block);
|
|
self.builder.positionBuilderAtEnd(end_block);
|
|
}
|
|
|
|
fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const dest_slice = try self.resolveInst(bin_op.lhs);
|
|
const dest_ptr_ty = self.typeOf(bin_op.lhs);
|
|
const src_slice = try self.resolveInst(bin_op.rhs);
|
|
const src_ptr_ty = self.typeOf(bin_op.rhs);
|
|
const src_ptr = self.sliceOrArrayPtr(src_slice, src_ptr_ty);
|
|
const len = self.sliceOrArrayLenInBytes(dest_slice, dest_ptr_ty);
|
|
const dest_ptr = self.sliceOrArrayPtr(dest_slice, dest_ptr_ty);
|
|
const is_volatile = src_ptr_ty.isVolatilePtr(mod) or dest_ptr_ty.isVolatilePtr(mod);
|
|
|
|
// When bulk-memory is enabled, this will be lowered to WebAssembly's memory.copy instruction.
|
|
// This instruction will trap on an invalid address, regardless of the length.
|
|
// For this reason we must add a check for 0-sized slices as its pointer field can be undefined.
|
|
// We only have to do this for slices as arrays will have a valid pointer.
|
|
// This logic can be removed once https://github.com/ziglang/zig/issues/16360 is done.
|
|
if (o.target.isWasm() and
|
|
std.Target.wasm.featureSetHas(o.target.cpu.features, .bulk_memory) and
|
|
dest_ptr_ty.isSlice(mod))
|
|
{
|
|
const llvm_usize_ty = self.context.intType(self.dg.object.target.ptrBitWidth());
|
|
const cond = try self.cmp(len, llvm_usize_ty.constInt(0, .False), Type.usize, .neq);
|
|
const memcpy_block = self.context.appendBasicBlock(self.llvm_func, "MemcpyTrapSkip");
|
|
const end_block = self.context.appendBasicBlock(self.llvm_func, "MemcpyTrapEnd");
|
|
_ = self.builder.buildCondBr(cond, memcpy_block, end_block);
|
|
self.builder.positionBuilderAtEnd(memcpy_block);
|
|
_ = self.builder.buildMemCpy(
|
|
dest_ptr,
|
|
dest_ptr_ty.ptrAlignment(mod),
|
|
src_ptr,
|
|
src_ptr_ty.ptrAlignment(mod),
|
|
len,
|
|
is_volatile,
|
|
);
|
|
_ = self.builder.buildBr(end_block);
|
|
self.builder.positionBuilderAtEnd(end_block);
|
|
return null;
|
|
}
|
|
|
|
_ = self.builder.buildMemCpy(
|
|
dest_ptr,
|
|
dest_ptr_ty.ptrAlignment(mod),
|
|
src_ptr,
|
|
src_ptr_ty.ptrAlignment(mod),
|
|
len,
|
|
is_volatile,
|
|
);
|
|
return null;
|
|
}
|
|
|
|
fn airSetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
|
|
const un_ty = self.typeOf(bin_op.lhs).childType(mod);
|
|
const layout = un_ty.unionGetLayout(mod);
|
|
if (layout.tag_size == 0) return null;
|
|
const union_ptr = try self.resolveInst(bin_op.lhs);
|
|
const new_tag = try self.resolveInst(bin_op.rhs);
|
|
if (layout.payload_size == 0) {
|
|
// TODO alignment on this store
|
|
_ = self.builder.buildStore(new_tag, union_ptr);
|
|
return null;
|
|
}
|
|
const un_llvm_ty = try o.lowerType(un_ty);
|
|
const tag_index = @intFromBool(layout.tag_align < layout.payload_align);
|
|
const tag_field_ptr = self.builder.buildStructGEP(un_llvm_ty, union_ptr, tag_index, "");
|
|
// TODO alignment on this store
|
|
_ = self.builder.buildStore(new_tag, tag_field_ptr);
|
|
return null;
|
|
}
|
|
|
|
fn airGetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const un_ty = self.typeOf(ty_op.operand);
|
|
const layout = un_ty.unionGetLayout(mod);
|
|
if (layout.tag_size == 0) return null;
|
|
const union_handle = try self.resolveInst(ty_op.operand);
|
|
if (isByRef(un_ty, mod)) {
|
|
const llvm_un_ty = try o.lowerType(un_ty);
|
|
if (layout.payload_size == 0) {
|
|
return self.builder.buildLoad(llvm_un_ty, union_handle, "");
|
|
}
|
|
const tag_index = @intFromBool(layout.tag_align < layout.payload_align);
|
|
const tag_field_ptr = self.builder.buildStructGEP(llvm_un_ty, union_handle, tag_index, "");
|
|
return self.builder.buildLoad(llvm_un_ty.structGetTypeAtIndex(tag_index), tag_field_ptr, "");
|
|
} else {
|
|
if (layout.payload_size == 0) {
|
|
return union_handle;
|
|
}
|
|
const tag_index = @intFromBool(layout.tag_align < layout.payload_align);
|
|
return self.builder.buildExtractValue(union_handle, tag_index, "");
|
|
}
|
|
}
|
|
|
|
fn airUnaryOp(self: *FuncGen, inst: Air.Inst.Index, comptime op: FloatOp) !?*llvm.Value {
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const operand_ty = self.typeOf(un_op);
|
|
|
|
return self.buildFloatOp(op, operand_ty, 1, .{operand});
|
|
}
|
|
|
|
fn airNeg(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const operand_ty = self.typeOf(un_op);
|
|
|
|
return self.buildFloatOp(.neg, operand_ty, 1, .{operand});
|
|
}
|
|
|
|
fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, llvm_fn_name: []const u8) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
|
|
const llvm_i1 = self.context.intType(1);
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
const fn_val = self.getIntrinsic(llvm_fn_name, &.{operand_llvm_ty});
|
|
|
|
const params = [_]*llvm.Value{ operand, llvm_i1.constNull() };
|
|
const wrong_size_result = self.builder.buildCall(fn_val.globalGetValueType(), fn_val, ¶ms, params.len, .C, .Auto, "");
|
|
const result_ty = self.typeOfIndex(inst);
|
|
const result_llvm_ty = try o.lowerType(result_ty);
|
|
|
|
const bits = operand_ty.intInfo(mod).bits;
|
|
const result_bits = result_ty.intInfo(mod).bits;
|
|
if (bits > result_bits) {
|
|
return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, "");
|
|
} else if (bits < result_bits) {
|
|
return self.builder.buildZExt(wrong_size_result, result_llvm_ty, "");
|
|
} else {
|
|
return wrong_size_result;
|
|
}
|
|
}
|
|
|
|
fn airBitOp(self: *FuncGen, inst: Air.Inst.Index, llvm_fn_name: []const u8) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
|
|
const params = [_]*llvm.Value{operand};
|
|
const operand_llvm_ty = try o.lowerType(operand_ty);
|
|
const fn_val = self.getIntrinsic(llvm_fn_name, &.{operand_llvm_ty});
|
|
|
|
const wrong_size_result = self.builder.buildCall(fn_val.globalGetValueType(), fn_val, ¶ms, params.len, .C, .Auto, "");
|
|
const result_ty = self.typeOfIndex(inst);
|
|
const result_llvm_ty = try o.lowerType(result_ty);
|
|
|
|
const bits = operand_ty.intInfo(mod).bits;
|
|
const result_bits = result_ty.intInfo(mod).bits;
|
|
if (bits > result_bits) {
|
|
return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, "");
|
|
} else if (bits < result_bits) {
|
|
return self.builder.buildZExt(wrong_size_result, result_llvm_ty, "");
|
|
} else {
|
|
return wrong_size_result;
|
|
}
|
|
}
|
|
|
|
fn airByteSwap(self: *FuncGen, inst: Air.Inst.Index, llvm_fn_name: []const u8) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand_ty = self.typeOf(ty_op.operand);
|
|
var bits = operand_ty.intInfo(mod).bits;
|
|
assert(bits % 8 == 0);
|
|
|
|
var operand = try self.resolveInst(ty_op.operand);
|
|
var operand_llvm_ty = try o.lowerType(operand_ty);
|
|
|
|
if (bits % 16 == 8) {
|
|
// If not an even byte-multiple, we need zero-extend + shift-left 1 byte
|
|
// The truncated result at the end will be the correct bswap
|
|
const scalar_llvm_ty = self.context.intType(bits + 8);
|
|
if (operand_ty.zigTypeTag(mod) == .Vector) {
|
|
const vec_len = operand_ty.vectorLen(mod);
|
|
operand_llvm_ty = scalar_llvm_ty.vectorType(vec_len);
|
|
|
|
const shifts = try self.gpa.alloc(*llvm.Value, vec_len);
|
|
defer self.gpa.free(shifts);
|
|
|
|
for (shifts) |*elem| {
|
|
elem.* = scalar_llvm_ty.constInt(8, .False);
|
|
}
|
|
const shift_vec = llvm.constVector(shifts.ptr, vec_len);
|
|
|
|
const extended = self.builder.buildZExt(operand, operand_llvm_ty, "");
|
|
operand = self.builder.buildShl(extended, shift_vec, "");
|
|
} else {
|
|
const extended = self.builder.buildZExt(operand, scalar_llvm_ty, "");
|
|
operand = self.builder.buildShl(extended, scalar_llvm_ty.constInt(8, .False), "");
|
|
operand_llvm_ty = scalar_llvm_ty;
|
|
}
|
|
bits = bits + 8;
|
|
}
|
|
|
|
const params = [_]*llvm.Value{operand};
|
|
const fn_val = self.getIntrinsic(llvm_fn_name, &.{operand_llvm_ty});
|
|
|
|
const wrong_size_result = self.builder.buildCall(fn_val.globalGetValueType(), fn_val, ¶ms, params.len, .C, .Auto, "");
|
|
|
|
const result_ty = self.typeOfIndex(inst);
|
|
const result_llvm_ty = try o.lowerType(result_ty);
|
|
const result_bits = result_ty.intInfo(mod).bits;
|
|
if (bits > result_bits) {
|
|
return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, "");
|
|
} else if (bits < result_bits) {
|
|
return self.builder.buildZExt(wrong_size_result, result_llvm_ty, "");
|
|
} else {
|
|
return wrong_size_result;
|
|
}
|
|
}
|
|
|
|
fn airErrorSetHasValue(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
const error_set_ty = self.air.getRefType(ty_op.ty);
|
|
|
|
const names = error_set_ty.errorSetNames(mod);
|
|
const valid_block = self.context.appendBasicBlock(self.llvm_func, "Valid");
|
|
const invalid_block = self.context.appendBasicBlock(self.llvm_func, "Invalid");
|
|
const end_block = self.context.appendBasicBlock(self.llvm_func, "End");
|
|
const switch_instr = self.builder.buildSwitch(operand, invalid_block, @as(c_uint, @intCast(names.len)));
|
|
|
|
for (names) |name| {
|
|
const err_int = @as(Module.ErrorInt, @intCast(mod.global_error_set.getIndex(name).?));
|
|
const this_tag_int_value = try o.lowerValue(.{
|
|
.ty = Type.err_int,
|
|
.val = try mod.intValue(Type.err_int, err_int),
|
|
});
|
|
switch_instr.addCase(this_tag_int_value, valid_block);
|
|
}
|
|
self.builder.positionBuilderAtEnd(valid_block);
|
|
_ = self.builder.buildBr(end_block);
|
|
|
|
self.builder.positionBuilderAtEnd(invalid_block);
|
|
_ = self.builder.buildBr(end_block);
|
|
|
|
self.builder.positionBuilderAtEnd(end_block);
|
|
|
|
const llvm_type = self.context.intType(1);
|
|
const incoming_values: [2]*llvm.Value = .{
|
|
llvm_type.constInt(1, .False), llvm_type.constInt(0, .False),
|
|
};
|
|
const incoming_blocks: [2]*llvm.BasicBlock = .{
|
|
valid_block, invalid_block,
|
|
};
|
|
const phi_node = self.builder.buildPhi(llvm_type, "");
|
|
phi_node.addIncoming(&incoming_values, &incoming_blocks, 2);
|
|
return phi_node;
|
|
}
|
|
|
|
fn airIsNamedEnumValue(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const enum_ty = self.typeOf(un_op);
|
|
|
|
const llvm_fn = try self.getIsNamedEnumValueFunction(enum_ty);
|
|
const params = [_]*llvm.Value{operand};
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .Fast, .Auto, "");
|
|
}
|
|
|
|
fn getIsNamedEnumValueFunction(self: *FuncGen, enum_ty: Type) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const enum_type = mod.intern_pool.indexToKey(enum_ty.toIntern()).enum_type;
|
|
|
|
// TODO: detect when the type changes and re-emit this function.
|
|
const gop = try o.named_enum_map.getOrPut(o.gpa, enum_type.decl);
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
errdefer assert(o.named_enum_map.remove(enum_type.decl));
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
|
|
defer arena_allocator.deinit();
|
|
const arena = arena_allocator.allocator();
|
|
|
|
const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
|
|
const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_is_named_enum_value_{}", .{fqn.fmt(&mod.intern_pool)});
|
|
|
|
const param_types = [_]*llvm.Type{try o.lowerType(enum_type.tag_ty.toType())};
|
|
|
|
const llvm_ret_ty = try o.lowerType(Type.bool);
|
|
const fn_type = llvm.functionType(llvm_ret_ty, ¶m_types, param_types.len, .False);
|
|
const fn_val = o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
fn_val.setLinkage(.Internal);
|
|
fn_val.setFunctionCallConv(.Fast);
|
|
o.addCommonFnAttributes(fn_val);
|
|
gop.value_ptr.* = fn_val;
|
|
|
|
const prev_block = self.builder.getInsertBlock();
|
|
const prev_debug_location = self.builder.getCurrentDebugLocation2();
|
|
defer {
|
|
self.builder.positionBuilderAtEnd(prev_block);
|
|
if (self.di_scope != null) {
|
|
self.builder.setCurrentDebugLocation2(prev_debug_location);
|
|
}
|
|
}
|
|
|
|
const entry_block = self.context.appendBasicBlock(fn_val, "Entry");
|
|
self.builder.positionBuilderAtEnd(entry_block);
|
|
self.builder.clearCurrentDebugLocation();
|
|
|
|
const named_block = self.context.appendBasicBlock(fn_val, "Named");
|
|
const unnamed_block = self.context.appendBasicBlock(fn_val, "Unnamed");
|
|
const tag_int_value = fn_val.getParam(0);
|
|
const switch_instr = self.builder.buildSwitch(tag_int_value, unnamed_block, @as(c_uint, @intCast(enum_type.names.len)));
|
|
|
|
for (enum_type.names, 0..) |_, field_index_usize| {
|
|
const field_index = @as(u32, @intCast(field_index_usize));
|
|
const this_tag_int_value = int: {
|
|
break :int try o.lowerValue(.{
|
|
.ty = enum_ty,
|
|
.val = try mod.enumValueFieldIndex(enum_ty, field_index),
|
|
});
|
|
};
|
|
switch_instr.addCase(this_tag_int_value, named_block);
|
|
}
|
|
self.builder.positionBuilderAtEnd(named_block);
|
|
_ = self.builder.buildRet(self.context.intType(1).constInt(1, .False));
|
|
|
|
self.builder.positionBuilderAtEnd(unnamed_block);
|
|
_ = self.builder.buildRet(self.context.intType(1).constInt(0, .False));
|
|
return fn_val;
|
|
}
|
|
|
|
fn airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const enum_ty = self.typeOf(un_op);
|
|
|
|
const llvm_fn = try self.getEnumTagNameFunction(enum_ty);
|
|
const params = [_]*llvm.Value{operand};
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .Fast, .Auto, "");
|
|
}
|
|
|
|
fn getEnumTagNameFunction(self: *FuncGen, enum_ty: Type) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const enum_type = mod.intern_pool.indexToKey(enum_ty.toIntern()).enum_type;
|
|
|
|
// TODO: detect when the type changes and re-emit this function.
|
|
const gop = try o.decl_map.getOrPut(o.gpa, enum_type.decl);
|
|
if (gop.found_existing) return gop.value_ptr.*;
|
|
errdefer assert(o.decl_map.remove(enum_type.decl));
|
|
|
|
var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
|
|
defer arena_allocator.deinit();
|
|
const arena = arena_allocator.allocator();
|
|
|
|
const fqn = try mod.declPtr(enum_type.decl).getFullyQualifiedName(mod);
|
|
const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{}", .{fqn.fmt(&mod.intern_pool)});
|
|
|
|
const slice_ty = Type.slice_const_u8_sentinel_0;
|
|
const llvm_ret_ty = try o.lowerType(slice_ty);
|
|
const usize_llvm_ty = try o.lowerType(Type.usize);
|
|
const slice_alignment = slice_ty.abiAlignment(mod);
|
|
|
|
const param_types = [_]*llvm.Type{try o.lowerType(enum_type.tag_ty.toType())};
|
|
|
|
const fn_type = llvm.functionType(llvm_ret_ty, ¶m_types, param_types.len, .False);
|
|
const fn_val = o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
fn_val.setLinkage(.Internal);
|
|
fn_val.setFunctionCallConv(.Fast);
|
|
o.addCommonFnAttributes(fn_val);
|
|
gop.value_ptr.* = fn_val;
|
|
|
|
const prev_block = self.builder.getInsertBlock();
|
|
const prev_debug_location = self.builder.getCurrentDebugLocation2();
|
|
defer {
|
|
self.builder.positionBuilderAtEnd(prev_block);
|
|
if (self.di_scope != null) {
|
|
self.builder.setCurrentDebugLocation2(prev_debug_location);
|
|
}
|
|
}
|
|
|
|
const entry_block = self.context.appendBasicBlock(fn_val, "Entry");
|
|
self.builder.positionBuilderAtEnd(entry_block);
|
|
self.builder.clearCurrentDebugLocation();
|
|
|
|
const bad_value_block = self.context.appendBasicBlock(fn_val, "BadValue");
|
|
const tag_int_value = fn_val.getParam(0);
|
|
const switch_instr = self.builder.buildSwitch(tag_int_value, bad_value_block, @as(c_uint, @intCast(enum_type.names.len)));
|
|
|
|
const array_ptr_indices = [_]*llvm.Value{
|
|
usize_llvm_ty.constNull(), usize_llvm_ty.constNull(),
|
|
};
|
|
|
|
for (enum_type.names, 0..) |name_ip, field_index_usize| {
|
|
const field_index = @as(u32, @intCast(field_index_usize));
|
|
const name = mod.intern_pool.stringToSlice(name_ip);
|
|
const str_init = self.context.constString(name.ptr, @as(c_uint, @intCast(name.len)), .False);
|
|
const str_init_llvm_ty = str_init.typeOf();
|
|
const str_global = o.llvm_module.addGlobal(str_init_llvm_ty, "");
|
|
str_global.setInitializer(str_init);
|
|
str_global.setLinkage(.Private);
|
|
str_global.setGlobalConstant(.True);
|
|
str_global.setUnnamedAddr(.True);
|
|
str_global.setAlignment(1);
|
|
|
|
const slice_fields = [_]*llvm.Value{
|
|
str_init_llvm_ty.constInBoundsGEP(str_global, &array_ptr_indices, array_ptr_indices.len),
|
|
usize_llvm_ty.constInt(name.len, .False),
|
|
};
|
|
const slice_init = llvm_ret_ty.constNamedStruct(&slice_fields, slice_fields.len);
|
|
const slice_global = o.llvm_module.addGlobal(slice_init.typeOf(), "");
|
|
slice_global.setInitializer(slice_init);
|
|
slice_global.setLinkage(.Private);
|
|
slice_global.setGlobalConstant(.True);
|
|
slice_global.setUnnamedAddr(.True);
|
|
slice_global.setAlignment(slice_alignment);
|
|
|
|
const return_block = self.context.appendBasicBlock(fn_val, "Name");
|
|
const this_tag_int_value = try o.lowerValue(.{
|
|
.ty = enum_ty,
|
|
.val = try mod.enumValueFieldIndex(enum_ty, field_index),
|
|
});
|
|
switch_instr.addCase(this_tag_int_value, return_block);
|
|
|
|
self.builder.positionBuilderAtEnd(return_block);
|
|
const loaded = self.builder.buildLoad(llvm_ret_ty, slice_global, "");
|
|
loaded.setAlignment(slice_alignment);
|
|
_ = self.builder.buildRet(loaded);
|
|
}
|
|
|
|
self.builder.positionBuilderAtEnd(bad_value_block);
|
|
_ = self.builder.buildUnreachable();
|
|
return fn_val;
|
|
}
|
|
|
|
fn getCmpLtErrorsLenFunction(self: *FuncGen) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
|
|
if (o.llvm_module.getNamedFunction(lt_errors_fn_name)) |llvm_fn| {
|
|
return llvm_fn;
|
|
}
|
|
|
|
// Function signature: fn (anyerror) bool
|
|
|
|
const ret_llvm_ty = try o.lowerType(Type.bool);
|
|
const anyerror_llvm_ty = try o.lowerType(Type.anyerror);
|
|
const param_types = [_]*llvm.Type{anyerror_llvm_ty};
|
|
|
|
const fn_type = llvm.functionType(ret_llvm_ty, ¶m_types, param_types.len, .False);
|
|
const llvm_fn = o.llvm_module.addFunction(lt_errors_fn_name, fn_type);
|
|
llvm_fn.setLinkage(.Internal);
|
|
llvm_fn.setFunctionCallConv(.Fast);
|
|
o.addCommonFnAttributes(llvm_fn);
|
|
return llvm_fn;
|
|
}
|
|
|
|
fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const un_op = self.air.instructions.items(.data)[inst].un_op;
|
|
const operand = try self.resolveInst(un_op);
|
|
const slice_ty = self.typeOfIndex(inst);
|
|
const slice_llvm_ty = try o.lowerType(slice_ty);
|
|
|
|
const error_name_table_ptr = try self.getErrorNameTable();
|
|
const ptr_slice_llvm_ty = self.context.pointerType(0);
|
|
const error_name_table = self.builder.buildLoad(ptr_slice_llvm_ty, error_name_table_ptr, "");
|
|
const indices = [_]*llvm.Value{operand};
|
|
const error_name_ptr = self.builder.buildInBoundsGEP(slice_llvm_ty, error_name_table, &indices, indices.len, "");
|
|
return self.builder.buildLoad(slice_llvm_ty, error_name_ptr, "");
|
|
}
|
|
|
|
fn airSplat(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const scalar = try self.resolveInst(ty_op.operand);
|
|
const vector_ty = self.typeOfIndex(inst);
|
|
const len = vector_ty.vectorLen(mod);
|
|
return self.builder.buildVectorSplat(len, scalar, "");
|
|
}
|
|
|
|
fn airSelect(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
|
|
const pred = try self.resolveInst(pl_op.operand);
|
|
const a = try self.resolveInst(extra.lhs);
|
|
const b = try self.resolveInst(extra.rhs);
|
|
|
|
return self.builder.buildSelect(pred, a, b, "");
|
|
}
|
|
|
|
fn airShuffle(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.Shuffle, ty_pl.payload).data;
|
|
const a = try self.resolveInst(extra.a);
|
|
const b = try self.resolveInst(extra.b);
|
|
const mask = extra.mask.toValue();
|
|
const mask_len = extra.mask_len;
|
|
const a_len = self.typeOf(extra.a).vectorLen(mod);
|
|
|
|
// LLVM uses integers larger than the length of the first array to
|
|
// index into the second array. This was deemed unnecessarily fragile
|
|
// when changing code, so Zig uses negative numbers to index the
|
|
// second vector. These start at -1 and go down, and are easiest to use
|
|
// with the ~ operator. Here we convert between the two formats.
|
|
const values = try self.gpa.alloc(*llvm.Value, mask_len);
|
|
defer self.gpa.free(values);
|
|
|
|
const llvm_i32 = self.context.intType(32);
|
|
|
|
for (values, 0..) |*val, i| {
|
|
const elem = try mask.elemValue(mod, i);
|
|
if (elem.isUndef(mod)) {
|
|
val.* = llvm_i32.getUndef();
|
|
} else {
|
|
const int = elem.toSignedInt(mod);
|
|
const unsigned = if (int >= 0) @as(u32, @intCast(int)) else @as(u32, @intCast(~int + a_len));
|
|
val.* = llvm_i32.constInt(unsigned, .False);
|
|
}
|
|
}
|
|
|
|
const llvm_mask_value = llvm.constVector(values.ptr, mask_len);
|
|
return self.builder.buildShuffleVector(a, b, llvm_mask_value, "");
|
|
}
|
|
|
|
/// Reduce a vector by repeatedly applying `llvm_fn` to produce an accumulated result.
|
|
///
|
|
/// Equivalent to:
|
|
/// reduce: {
|
|
/// var i: usize = 0;
|
|
/// var accum: T = init;
|
|
/// while (i < vec.len) : (i += 1) {
|
|
/// accum = llvm_fn(accum, vec[i]);
|
|
/// }
|
|
/// break :reduce accum;
|
|
/// }
|
|
///
|
|
fn buildReducedCall(
|
|
self: *FuncGen,
|
|
llvm_fn: *llvm.Value,
|
|
operand_vector: *llvm.Value,
|
|
vector_len: usize,
|
|
accum_init: *llvm.Value,
|
|
) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
const llvm_usize_ty = try o.lowerType(Type.usize);
|
|
const llvm_vector_len = llvm_usize_ty.constInt(vector_len, .False);
|
|
const llvm_result_ty = accum_init.typeOf();
|
|
|
|
// Allocate and initialize our mutable variables
|
|
const i_ptr = self.buildAlloca(llvm_usize_ty, null);
|
|
_ = self.builder.buildStore(llvm_usize_ty.constInt(0, .False), i_ptr);
|
|
const accum_ptr = self.buildAlloca(llvm_result_ty, null);
|
|
_ = self.builder.buildStore(accum_init, accum_ptr);
|
|
|
|
// Setup the loop
|
|
const loop = self.context.appendBasicBlock(self.llvm_func, "ReduceLoop");
|
|
const loop_exit = self.context.appendBasicBlock(self.llvm_func, "AfterReduce");
|
|
_ = self.builder.buildBr(loop);
|
|
{
|
|
self.builder.positionBuilderAtEnd(loop);
|
|
|
|
// while (i < vec.len)
|
|
const i = self.builder.buildLoad(llvm_usize_ty, i_ptr, "");
|
|
const cond = self.builder.buildICmp(.ULT, i, llvm_vector_len, "");
|
|
const loop_then = self.context.appendBasicBlock(self.llvm_func, "ReduceLoopThen");
|
|
|
|
_ = self.builder.buildCondBr(cond, loop_then, loop_exit);
|
|
|
|
{
|
|
self.builder.positionBuilderAtEnd(loop_then);
|
|
|
|
// accum = f(accum, vec[i]);
|
|
const accum = self.builder.buildLoad(llvm_result_ty, accum_ptr, "");
|
|
const element = self.builder.buildExtractElement(operand_vector, i, "");
|
|
const params = [2]*llvm.Value{ accum, element };
|
|
const new_accum = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, ¶ms, params.len, .C, .Auto, "");
|
|
_ = self.builder.buildStore(new_accum, accum_ptr);
|
|
|
|
// i += 1
|
|
const new_i = self.builder.buildAdd(i, llvm_usize_ty.constInt(1, .False), "");
|
|
_ = self.builder.buildStore(new_i, i_ptr);
|
|
_ = self.builder.buildBr(loop);
|
|
}
|
|
}
|
|
|
|
self.builder.positionBuilderAtEnd(loop_exit);
|
|
return self.builder.buildLoad(llvm_result_ty, accum_ptr, "");
|
|
}
|
|
|
|
fn airReduce(self: *FuncGen, inst: Air.Inst.Index, want_fast_math: bool) !?*llvm.Value {
|
|
self.builder.setFastMath(want_fast_math);
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
|
|
const reduce = self.air.instructions.items(.data)[inst].reduce;
|
|
const operand = try self.resolveInst(reduce.operand);
|
|
const operand_ty = self.typeOf(reduce.operand);
|
|
const scalar_ty = self.typeOfIndex(inst);
|
|
|
|
switch (reduce.operation) {
|
|
.And => return self.builder.buildAndReduce(operand),
|
|
.Or => return self.builder.buildOrReduce(operand),
|
|
.Xor => return self.builder.buildXorReduce(operand),
|
|
.Min => switch (scalar_ty.zigTypeTag(mod)) {
|
|
.Int => return self.builder.buildIntMinReduce(operand, scalar_ty.isSignedInt(mod)),
|
|
.Float => if (intrinsicsAllowed(scalar_ty, target)) {
|
|
return self.builder.buildFPMinReduce(operand);
|
|
},
|
|
else => unreachable,
|
|
},
|
|
.Max => switch (scalar_ty.zigTypeTag(mod)) {
|
|
.Int => return self.builder.buildIntMaxReduce(operand, scalar_ty.isSignedInt(mod)),
|
|
.Float => if (intrinsicsAllowed(scalar_ty, target)) {
|
|
return self.builder.buildFPMaxReduce(operand);
|
|
},
|
|
else => unreachable,
|
|
},
|
|
.Add => switch (scalar_ty.zigTypeTag(mod)) {
|
|
.Int => return self.builder.buildAddReduce(operand),
|
|
.Float => if (intrinsicsAllowed(scalar_ty, target)) {
|
|
const scalar_llvm_ty = try o.lowerType(scalar_ty);
|
|
const neutral_value = scalar_llvm_ty.constReal(-0.0);
|
|
return self.builder.buildFPAddReduce(neutral_value, operand);
|
|
},
|
|
else => unreachable,
|
|
},
|
|
.Mul => switch (scalar_ty.zigTypeTag(mod)) {
|
|
.Int => return self.builder.buildMulReduce(operand),
|
|
.Float => if (intrinsicsAllowed(scalar_ty, target)) {
|
|
const scalar_llvm_ty = try o.lowerType(scalar_ty);
|
|
const neutral_value = scalar_llvm_ty.constReal(1.0);
|
|
return self.builder.buildFPMulReduce(neutral_value, operand);
|
|
},
|
|
else => unreachable,
|
|
},
|
|
}
|
|
|
|
// Reduction could not be performed with intrinsics.
|
|
// Use a manual loop over a softfloat call instead.
|
|
var fn_name_buf: [64]u8 = undefined;
|
|
const float_bits = scalar_ty.floatBits(target);
|
|
const fn_name = switch (reduce.operation) {
|
|
.Min => std.fmt.bufPrintZ(&fn_name_buf, "{s}fmin{s}", .{
|
|
libcFloatPrefix(float_bits), libcFloatSuffix(float_bits),
|
|
}) catch unreachable,
|
|
.Max => std.fmt.bufPrintZ(&fn_name_buf, "{s}fmax{s}", .{
|
|
libcFloatPrefix(float_bits), libcFloatSuffix(float_bits),
|
|
}) catch unreachable,
|
|
.Add => std.fmt.bufPrintZ(&fn_name_buf, "__add{s}f3", .{
|
|
compilerRtFloatAbbrev(float_bits),
|
|
}) catch unreachable,
|
|
.Mul => std.fmt.bufPrintZ(&fn_name_buf, "__mul{s}f3", .{
|
|
compilerRtFloatAbbrev(float_bits),
|
|
}) catch unreachable,
|
|
else => unreachable,
|
|
};
|
|
|
|
const param_llvm_ty = try o.lowerType(scalar_ty);
|
|
const param_types = [2]*llvm.Type{ param_llvm_ty, param_llvm_ty };
|
|
const libc_fn = self.getLibcFunction(fn_name, ¶m_types, param_llvm_ty);
|
|
const init_value = try o.lowerValue(.{
|
|
.ty = scalar_ty,
|
|
.val = try mod.floatValue(scalar_ty, switch (reduce.operation) {
|
|
.Min => std.math.nan(f32),
|
|
.Max => std.math.nan(f32),
|
|
.Add => -0.0,
|
|
.Mul => 1.0,
|
|
else => unreachable,
|
|
}),
|
|
});
|
|
return self.buildReducedCall(libc_fn, operand, operand_ty.vectorLen(mod), init_value);
|
|
}
|
|
|
|
fn airAggregateInit(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const result_ty = self.typeOfIndex(inst);
|
|
const len = @as(usize, @intCast(result_ty.arrayLen(mod)));
|
|
const elements = @as([]const Air.Inst.Ref, @ptrCast(self.air.extra[ty_pl.payload..][0..len]));
|
|
const llvm_result_ty = try o.lowerType(result_ty);
|
|
|
|
switch (result_ty.zigTypeTag(mod)) {
|
|
.Vector => {
|
|
const llvm_u32 = self.context.intType(32);
|
|
|
|
var vector = llvm_result_ty.getUndef();
|
|
for (elements, 0..) |elem, i| {
|
|
const index_u32 = llvm_u32.constInt(i, .False);
|
|
const llvm_elem = try self.resolveInst(elem);
|
|
vector = self.builder.buildInsertElement(vector, llvm_elem, index_u32, "");
|
|
}
|
|
return vector;
|
|
},
|
|
.Struct => {
|
|
if (result_ty.containerLayout(mod) == .Packed) {
|
|
const struct_obj = mod.typeToStruct(result_ty).?;
|
|
assert(struct_obj.haveLayout());
|
|
const big_bits = struct_obj.backing_int_ty.bitSize(mod);
|
|
const int_llvm_ty = self.context.intType(@as(c_uint, @intCast(big_bits)));
|
|
const fields = struct_obj.fields.values();
|
|
comptime assert(Type.packed_struct_layout_version == 2);
|
|
var running_int: *llvm.Value = int_llvm_ty.constNull();
|
|
var running_bits: u16 = 0;
|
|
for (elements, 0..) |elem, i| {
|
|
const field = fields[i];
|
|
if (!field.ty.hasRuntimeBitsIgnoreComptime(mod)) continue;
|
|
|
|
const non_int_val = try self.resolveInst(elem);
|
|
const ty_bit_size = @as(u16, @intCast(field.ty.bitSize(mod)));
|
|
const small_int_ty = self.context.intType(ty_bit_size);
|
|
const small_int_val = if (field.ty.isPtrAtRuntime(mod))
|
|
self.builder.buildPtrToInt(non_int_val, small_int_ty, "")
|
|
else
|
|
self.builder.buildBitCast(non_int_val, small_int_ty, "");
|
|
const shift_rhs = int_llvm_ty.constInt(running_bits, .False);
|
|
// If the field is as large as the entire packed struct, this
|
|
// zext would go from, e.g. i16 to i16. This is legal with
|
|
// constZExtOrBitCast but not legal with constZExt.
|
|
const extended_int_val = self.builder.buildZExtOrBitCast(small_int_val, int_llvm_ty, "");
|
|
const shifted = self.builder.buildShl(extended_int_val, shift_rhs, "");
|
|
running_int = self.builder.buildOr(running_int, shifted, "");
|
|
running_bits += ty_bit_size;
|
|
}
|
|
return running_int;
|
|
}
|
|
|
|
if (isByRef(result_ty, mod)) {
|
|
const llvm_u32 = self.context.intType(32);
|
|
// TODO in debug builds init to undef so that the padding will be 0xaa
|
|
// even if we fully populate the fields.
|
|
const alloca_inst = self.buildAlloca(llvm_result_ty, result_ty.abiAlignment(mod));
|
|
|
|
var indices: [2]*llvm.Value = .{ llvm_u32.constNull(), undefined };
|
|
for (elements, 0..) |elem, i| {
|
|
if ((try result_ty.structFieldValueComptime(mod, i)) != null) continue;
|
|
|
|
const llvm_elem = try self.resolveInst(elem);
|
|
const llvm_i = llvmField(result_ty, i, mod).?.index;
|
|
indices[1] = llvm_u32.constInt(llvm_i, .False);
|
|
const field_ptr = self.builder.buildInBoundsGEP(llvm_result_ty, alloca_inst, &indices, indices.len, "");
|
|
const field_ptr_ty = try mod.ptrType(.{
|
|
.child = self.typeOf(elem).toIntern(),
|
|
.flags = .{
|
|
.alignment = InternPool.Alignment.fromNonzeroByteUnits(
|
|
result_ty.structFieldAlign(i, mod),
|
|
),
|
|
},
|
|
});
|
|
try self.store(field_ptr, field_ptr_ty, llvm_elem, .NotAtomic);
|
|
}
|
|
|
|
return alloca_inst;
|
|
} else {
|
|
var result = llvm_result_ty.getUndef();
|
|
for (elements, 0..) |elem, i| {
|
|
if ((try result_ty.structFieldValueComptime(mod, i)) != null) continue;
|
|
|
|
const llvm_elem = try self.resolveInst(elem);
|
|
const llvm_i = llvmField(result_ty, i, mod).?.index;
|
|
result = self.builder.buildInsertValue(result, llvm_elem, llvm_i, "");
|
|
}
|
|
return result;
|
|
}
|
|
},
|
|
.Array => {
|
|
assert(isByRef(result_ty, mod));
|
|
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const alloca_inst = self.buildAlloca(llvm_result_ty, result_ty.abiAlignment(mod));
|
|
|
|
const array_info = result_ty.arrayInfo(mod);
|
|
const elem_ptr_ty = try mod.ptrType(.{
|
|
.child = array_info.elem_type.toIntern(),
|
|
});
|
|
|
|
for (elements, 0..) |elem, i| {
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_usize.constNull(),
|
|
llvm_usize.constInt(@as(c_uint, @intCast(i)), .False),
|
|
};
|
|
const elem_ptr = self.builder.buildInBoundsGEP(llvm_result_ty, alloca_inst, &indices, indices.len, "");
|
|
const llvm_elem = try self.resolveInst(elem);
|
|
try self.store(elem_ptr, elem_ptr_ty, llvm_elem, .NotAtomic);
|
|
}
|
|
if (array_info.sentinel) |sent_val| {
|
|
const indices: [2]*llvm.Value = .{
|
|
llvm_usize.constNull(),
|
|
llvm_usize.constInt(@as(c_uint, @intCast(array_info.len)), .False),
|
|
};
|
|
const elem_ptr = self.builder.buildInBoundsGEP(llvm_result_ty, alloca_inst, &indices, indices.len, "");
|
|
const llvm_elem = try self.resolveValue(.{
|
|
.ty = array_info.elem_type,
|
|
.val = sent_val,
|
|
});
|
|
|
|
try self.store(elem_ptr, elem_ptr_ty, llvm_elem, .NotAtomic);
|
|
}
|
|
|
|
return alloca_inst;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn airUnionInit(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
|
|
const extra = self.air.extraData(Air.UnionInit, ty_pl.payload).data;
|
|
const union_ty = self.typeOfIndex(inst);
|
|
const union_llvm_ty = try o.lowerType(union_ty);
|
|
const layout = union_ty.unionGetLayout(mod);
|
|
const union_obj = mod.typeToUnion(union_ty).?;
|
|
|
|
if (union_obj.layout == .Packed) {
|
|
const big_bits = union_ty.bitSize(mod);
|
|
const int_llvm_ty = self.context.intType(@as(c_uint, @intCast(big_bits)));
|
|
const field = union_obj.fields.values()[extra.field_index];
|
|
const non_int_val = try self.resolveInst(extra.init);
|
|
const ty_bit_size = @as(u16, @intCast(field.ty.bitSize(mod)));
|
|
const small_int_ty = self.context.intType(ty_bit_size);
|
|
const small_int_val = if (field.ty.isPtrAtRuntime(mod))
|
|
self.builder.buildPtrToInt(non_int_val, small_int_ty, "")
|
|
else
|
|
self.builder.buildBitCast(non_int_val, small_int_ty, "");
|
|
return self.builder.buildZExtOrBitCast(small_int_val, int_llvm_ty, "");
|
|
}
|
|
|
|
const tag_int = blk: {
|
|
const tag_ty = union_ty.unionTagTypeHypothetical(mod);
|
|
const union_field_name = union_obj.fields.keys()[extra.field_index];
|
|
const enum_field_index = tag_ty.enumFieldIndex(union_field_name, mod).?;
|
|
const tag_val = try mod.enumValueFieldIndex(tag_ty, enum_field_index);
|
|
const tag_int_val = try tag_val.intFromEnum(tag_ty, mod);
|
|
break :blk tag_int_val.toUnsignedInt(mod);
|
|
};
|
|
if (layout.payload_size == 0) {
|
|
if (layout.tag_size == 0) {
|
|
return null;
|
|
}
|
|
assert(!isByRef(union_ty, mod));
|
|
return union_llvm_ty.constInt(tag_int, .False);
|
|
}
|
|
assert(isByRef(union_ty, mod));
|
|
// The llvm type of the alloca will be the named LLVM union type, and will not
|
|
// necessarily match the format that we need, depending on which tag is active.
|
|
// We must construct the correct unnamed struct type here, in order to then set
|
|
// the fields appropriately.
|
|
const result_ptr = self.buildAlloca(union_llvm_ty, layout.abi_align);
|
|
const llvm_payload = try self.resolveInst(extra.init);
|
|
assert(union_obj.haveFieldTypes());
|
|
const field = union_obj.fields.values()[extra.field_index];
|
|
const field_llvm_ty = try o.lowerType(field.ty);
|
|
const field_size = field.ty.abiSize(mod);
|
|
const field_align = field.normalAlignment(mod);
|
|
|
|
const llvm_union_ty = t: {
|
|
const payload = p: {
|
|
if (!field.ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
const padding_len = @as(c_uint, @intCast(layout.payload_size));
|
|
break :p self.context.intType(8).arrayType(padding_len);
|
|
}
|
|
if (field_size == layout.payload_size) {
|
|
break :p field_llvm_ty;
|
|
}
|
|
const padding_len = @as(c_uint, @intCast(layout.payload_size - field_size));
|
|
const fields: [2]*llvm.Type = .{
|
|
field_llvm_ty, self.context.intType(8).arrayType(padding_len),
|
|
};
|
|
break :p self.context.structType(&fields, fields.len, .True);
|
|
};
|
|
if (layout.tag_size == 0) {
|
|
const fields: [1]*llvm.Type = .{payload};
|
|
break :t self.context.structType(&fields, fields.len, .False);
|
|
}
|
|
const tag_llvm_ty = try o.lowerType(union_obj.tag_ty);
|
|
var fields: [3]*llvm.Type = undefined;
|
|
var fields_len: c_uint = 2;
|
|
if (layout.tag_align >= layout.payload_align) {
|
|
fields = .{ tag_llvm_ty, payload, undefined };
|
|
} else {
|
|
fields = .{ payload, tag_llvm_ty, undefined };
|
|
}
|
|
if (layout.padding != 0) {
|
|
fields[2] = self.context.intType(8).arrayType(layout.padding);
|
|
fields_len = 3;
|
|
}
|
|
break :t self.context.structType(&fields, fields_len, .False);
|
|
};
|
|
|
|
// Now we follow the layout as expressed above with GEP instructions to set the
|
|
// tag and the payload.
|
|
const index_type = self.context.intType(32);
|
|
|
|
const field_ptr_ty = try mod.ptrType(.{
|
|
.child = field.ty.toIntern(),
|
|
.flags = .{
|
|
.alignment = InternPool.Alignment.fromNonzeroByteUnits(field_align),
|
|
},
|
|
});
|
|
if (layout.tag_size == 0) {
|
|
const indices: [3]*llvm.Value = .{
|
|
index_type.constNull(),
|
|
index_type.constNull(),
|
|
index_type.constNull(),
|
|
};
|
|
const len: c_uint = if (field_size == layout.payload_size) 2 else 3;
|
|
const field_ptr = self.builder.buildInBoundsGEP(llvm_union_ty, result_ptr, &indices, len, "");
|
|
try self.store(field_ptr, field_ptr_ty, llvm_payload, .NotAtomic);
|
|
return result_ptr;
|
|
}
|
|
|
|
{
|
|
const indices: [3]*llvm.Value = .{
|
|
index_type.constNull(),
|
|
index_type.constInt(@intFromBool(layout.tag_align >= layout.payload_align), .False),
|
|
index_type.constNull(),
|
|
};
|
|
const len: c_uint = if (field_size == layout.payload_size) 2 else 3;
|
|
const field_ptr = self.builder.buildInBoundsGEP(llvm_union_ty, result_ptr, &indices, len, "");
|
|
try self.store(field_ptr, field_ptr_ty, llvm_payload, .NotAtomic);
|
|
}
|
|
{
|
|
const indices: [2]*llvm.Value = .{
|
|
index_type.constNull(),
|
|
index_type.constInt(@intFromBool(layout.tag_align < layout.payload_align), .False),
|
|
};
|
|
const field_ptr = self.builder.buildInBoundsGEP(llvm_union_ty, result_ptr, &indices, indices.len, "");
|
|
const tag_llvm_ty = try o.lowerType(union_obj.tag_ty);
|
|
const llvm_tag = tag_llvm_ty.constInt(tag_int, .False);
|
|
const store_inst = self.builder.buildStore(llvm_tag, field_ptr);
|
|
store_inst.setAlignment(union_obj.tag_ty.abiAlignment(mod));
|
|
}
|
|
|
|
return result_ptr;
|
|
}
|
|
|
|
fn airPrefetch(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const prefetch = self.air.instructions.items(.data)[inst].prefetch;
|
|
|
|
comptime assert(@intFromEnum(std.builtin.PrefetchOptions.Rw.read) == 0);
|
|
comptime assert(@intFromEnum(std.builtin.PrefetchOptions.Rw.write) == 1);
|
|
|
|
// TODO these two asserts should be able to be comptime because the type is a u2
|
|
assert(prefetch.locality >= 0);
|
|
assert(prefetch.locality <= 3);
|
|
|
|
comptime assert(@intFromEnum(std.builtin.PrefetchOptions.Cache.instruction) == 0);
|
|
comptime assert(@intFromEnum(std.builtin.PrefetchOptions.Cache.data) == 1);
|
|
|
|
// LLVM fails during codegen of instruction cache prefetchs for these architectures.
|
|
// This is an LLVM bug as the prefetch intrinsic should be a noop if not supported
|
|
// by the target.
|
|
// To work around this, don't emit llvm.prefetch in this case.
|
|
// See https://bugs.llvm.org/show_bug.cgi?id=21037
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
switch (prefetch.cache) {
|
|
.instruction => switch (target.cpu.arch) {
|
|
.x86_64,
|
|
.x86,
|
|
.powerpc,
|
|
.powerpcle,
|
|
.powerpc64,
|
|
.powerpc64le,
|
|
=> return null,
|
|
.arm, .armeb, .thumb, .thumbeb => {
|
|
switch (prefetch.rw) {
|
|
.write => return null,
|
|
else => {},
|
|
}
|
|
},
|
|
else => {},
|
|
},
|
|
.data => {},
|
|
}
|
|
|
|
const llvm_ptr_u8 = self.context.pointerType(0);
|
|
const llvm_u32 = self.context.intType(32);
|
|
|
|
const llvm_fn_name = "llvm.prefetch.p0";
|
|
const fn_val = o.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
|
|
// declare void @llvm.prefetch(i8*, i32, i32, i32)
|
|
const llvm_void = self.context.voidType();
|
|
const param_types = [_]*llvm.Type{
|
|
llvm_ptr_u8, llvm_u32, llvm_u32, llvm_u32,
|
|
};
|
|
const fn_type = llvm.functionType(llvm_void, ¶m_types, param_types.len, .False);
|
|
break :blk o.llvm_module.addFunction(llvm_fn_name, fn_type);
|
|
};
|
|
|
|
const ptr = try self.resolveInst(prefetch.ptr);
|
|
|
|
const params = [_]*llvm.Value{
|
|
ptr,
|
|
llvm_u32.constInt(@intFromEnum(prefetch.rw), .False),
|
|
llvm_u32.constInt(prefetch.locality, .False),
|
|
llvm_u32.constInt(@intFromEnum(prefetch.cache), .False),
|
|
};
|
|
_ = self.builder.buildCall(fn_val.globalGetValueType(), fn_val, ¶ms, params.len, .C, .Auto, "");
|
|
return null;
|
|
}
|
|
|
|
fn airAddrSpaceCast(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
|
|
const inst_ty = self.typeOfIndex(inst);
|
|
const operand = try self.resolveInst(ty_op.operand);
|
|
|
|
const llvm_dest_ty = try o.lowerType(inst_ty);
|
|
return self.builder.buildAddrSpaceCast(operand, llvm_dest_ty, "");
|
|
}
|
|
|
|
fn amdgcnWorkIntrinsic(self: *FuncGen, dimension: u32, default: u32, comptime basename: []const u8) !?*llvm.Value {
|
|
const llvm_u32 = self.context.intType(32);
|
|
|
|
const llvm_fn_name = switch (dimension) {
|
|
0 => basename ++ ".x",
|
|
1 => basename ++ ".y",
|
|
2 => basename ++ ".z",
|
|
else => return llvm_u32.constInt(default, .False),
|
|
};
|
|
|
|
const args: [0]*llvm.Value = .{};
|
|
const llvm_fn = self.getIntrinsic(llvm_fn_name, &.{});
|
|
return self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
}
|
|
|
|
fn airWorkItemId(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const target = o.module.getTarget();
|
|
assert(target.cpu.arch == .amdgcn); // TODO is to port this function to other GPU architectures
|
|
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const dimension = pl_op.payload;
|
|
return self.amdgcnWorkIntrinsic(dimension, 0, "llvm.amdgcn.workitem.id");
|
|
}
|
|
|
|
fn airWorkGroupSize(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const target = o.module.getTarget();
|
|
assert(target.cpu.arch == .amdgcn); // TODO is to port this function to other GPU architectures
|
|
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const dimension = pl_op.payload;
|
|
const llvm_u32 = self.context.intType(32);
|
|
if (dimension >= 3) {
|
|
return llvm_u32.constInt(1, .False);
|
|
}
|
|
|
|
// Fetch the dispatch pointer, which points to this structure:
|
|
// https://github.com/RadeonOpenCompute/ROCR-Runtime/blob/adae6c61e10d371f7cbc3d0e94ae2c070cab18a4/src/inc/hsa.h#L2913
|
|
const llvm_fn = self.getIntrinsic("llvm.amdgcn.dispatch.ptr", &.{});
|
|
const args: [0]*llvm.Value = .{};
|
|
const dispatch_ptr = self.builder.buildCall(llvm_fn.globalGetValueType(), llvm_fn, &args, args.len, .Fast, .Auto, "");
|
|
dispatch_ptr.setAlignment(4);
|
|
|
|
// Load the work_group_* member from the struct as u16.
|
|
// Just treat the dispatch pointer as an array of u16 to keep things simple.
|
|
const offset = 2 + dimension;
|
|
const index = [_]*llvm.Value{llvm_u32.constInt(offset, .False)};
|
|
const llvm_u16 = self.context.intType(16);
|
|
const workgroup_size_ptr = self.builder.buildInBoundsGEP(llvm_u16, dispatch_ptr, &index, index.len, "");
|
|
const workgroup_size = self.builder.buildLoad(llvm_u16, workgroup_size_ptr, "");
|
|
workgroup_size.setAlignment(2);
|
|
return workgroup_size;
|
|
}
|
|
|
|
fn airWorkGroupId(self: *FuncGen, inst: Air.Inst.Index) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const target = o.module.getTarget();
|
|
assert(target.cpu.arch == .amdgcn); // TODO is to port this function to other GPU architectures
|
|
|
|
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
|
|
const dimension = pl_op.payload;
|
|
return self.amdgcnWorkIntrinsic(dimension, 0, "llvm.amdgcn.workgroup.id");
|
|
}
|
|
|
|
fn getErrorNameTable(self: *FuncGen) !*llvm.Value {
|
|
const o = self.dg.object;
|
|
if (o.error_name_table) |table| {
|
|
return table;
|
|
}
|
|
|
|
const mod = o.module;
|
|
const slice_ty = Type.slice_const_u8_sentinel_0;
|
|
const slice_alignment = slice_ty.abiAlignment(mod);
|
|
const llvm_slice_ptr_ty = self.context.pointerType(0); // TODO: Address space
|
|
|
|
const error_name_table_global = o.llvm_module.addGlobal(llvm_slice_ptr_ty, "__zig_err_name_table");
|
|
error_name_table_global.setInitializer(llvm_slice_ptr_ty.getUndef());
|
|
error_name_table_global.setLinkage(.Private);
|
|
error_name_table_global.setGlobalConstant(.True);
|
|
error_name_table_global.setUnnamedAddr(.True);
|
|
error_name_table_global.setAlignment(slice_alignment);
|
|
|
|
o.error_name_table = error_name_table_global;
|
|
return error_name_table_global;
|
|
}
|
|
|
|
/// Assumes the optional is not pointer-like and payload has bits.
|
|
fn optIsNonNull(
|
|
self: *FuncGen,
|
|
opt_llvm_ty: *llvm.Type,
|
|
opt_handle: *llvm.Value,
|
|
is_by_ref: bool,
|
|
) *llvm.Value {
|
|
const non_null_llvm_ty = self.context.intType(8);
|
|
const field = b: {
|
|
if (is_by_ref) {
|
|
const field_ptr = self.builder.buildStructGEP(opt_llvm_ty, opt_handle, 1, "");
|
|
break :b self.builder.buildLoad(non_null_llvm_ty, field_ptr, "");
|
|
}
|
|
break :b self.builder.buildExtractValue(opt_handle, 1, "");
|
|
};
|
|
comptime assert(optional_layout_version == 3);
|
|
|
|
return self.builder.buildICmp(.NE, field, non_null_llvm_ty.constInt(0, .False), "");
|
|
}
|
|
|
|
/// Assumes the optional is not pointer-like and payload has bits.
|
|
fn optPayloadHandle(
|
|
fg: *FuncGen,
|
|
opt_llvm_ty: *llvm.Type,
|
|
opt_handle: *llvm.Value,
|
|
opt_ty: Type,
|
|
can_elide_load: bool,
|
|
) !*llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const payload_ty = opt_ty.optionalChild(mod);
|
|
|
|
if (isByRef(opt_ty, mod)) {
|
|
// We have a pointer and we need to return a pointer to the first field.
|
|
const payload_ptr = fg.builder.buildStructGEP(opt_llvm_ty, opt_handle, 0, "");
|
|
|
|
const payload_alignment = payload_ty.abiAlignment(mod);
|
|
if (isByRef(payload_ty, mod)) {
|
|
if (can_elide_load)
|
|
return payload_ptr;
|
|
|
|
return fg.loadByRef(payload_ptr, payload_ty, payload_alignment, false);
|
|
}
|
|
const payload_llvm_ty = try o.lowerType(payload_ty);
|
|
const load_inst = fg.builder.buildLoad(payload_llvm_ty, payload_ptr, "");
|
|
load_inst.setAlignment(payload_alignment);
|
|
return load_inst;
|
|
}
|
|
|
|
assert(!isByRef(payload_ty, mod));
|
|
return fg.builder.buildExtractValue(opt_handle, 0, "");
|
|
}
|
|
|
|
fn buildOptional(
|
|
self: *FuncGen,
|
|
optional_ty: Type,
|
|
payload: *llvm.Value,
|
|
non_null_bit: *llvm.Value,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const optional_llvm_ty = try o.lowerType(optional_ty);
|
|
const non_null_field = self.builder.buildZExt(non_null_bit, self.context.intType(8), "");
|
|
const mod = o.module;
|
|
|
|
if (isByRef(optional_ty, mod)) {
|
|
const payload_alignment = optional_ty.abiAlignment(mod);
|
|
const alloca_inst = self.buildAlloca(optional_llvm_ty, payload_alignment);
|
|
|
|
{
|
|
const field_ptr = self.builder.buildStructGEP(optional_llvm_ty, alloca_inst, 0, "");
|
|
const store_inst = self.builder.buildStore(payload, field_ptr);
|
|
store_inst.setAlignment(payload_alignment);
|
|
}
|
|
{
|
|
const field_ptr = self.builder.buildStructGEP(optional_llvm_ty, alloca_inst, 1, "");
|
|
const store_inst = self.builder.buildStore(non_null_field, field_ptr);
|
|
store_inst.setAlignment(1);
|
|
}
|
|
|
|
return alloca_inst;
|
|
}
|
|
|
|
const partial = self.builder.buildInsertValue(optional_llvm_ty.getUndef(), payload, 0, "");
|
|
return self.builder.buildInsertValue(partial, non_null_field, 1, "");
|
|
}
|
|
|
|
fn fieldPtr(
|
|
self: *FuncGen,
|
|
inst: Air.Inst.Index,
|
|
struct_ptr: *llvm.Value,
|
|
struct_ptr_ty: Type,
|
|
field_index: u32,
|
|
) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const struct_ty = struct_ptr_ty.childType(mod);
|
|
switch (struct_ty.zigTypeTag(mod)) {
|
|
.Struct => switch (struct_ty.containerLayout(mod)) {
|
|
.Packed => {
|
|
const result_ty = self.typeOfIndex(inst);
|
|
const result_ty_info = result_ty.ptrInfo(mod);
|
|
|
|
if (result_ty_info.packed_offset.host_size != 0) {
|
|
// From LLVM's perspective, a pointer to a packed struct and a pointer
|
|
// to a field of a packed struct are the same. The difference is in the
|
|
// Zig pointer type which provides information for how to mask and shift
|
|
// out the relevant bits when accessing the pointee.
|
|
return struct_ptr;
|
|
}
|
|
|
|
// We have a pointer to a packed struct field that happens to be byte-aligned.
|
|
// Offset our operand pointer by the correct number of bytes.
|
|
const byte_offset = struct_ty.packedStructFieldByteOffset(field_index, mod);
|
|
if (byte_offset == 0) return struct_ptr;
|
|
const byte_llvm_ty = self.context.intType(8);
|
|
const llvm_usize = try o.lowerType(Type.usize);
|
|
const llvm_index = llvm_usize.constInt(byte_offset, .False);
|
|
const indices: [1]*llvm.Value = .{llvm_index};
|
|
return self.builder.buildInBoundsGEP(byte_llvm_ty, struct_ptr, &indices, indices.len, "");
|
|
},
|
|
else => {
|
|
const struct_llvm_ty = try o.lowerPtrElemTy(struct_ty);
|
|
|
|
if (llvmField(struct_ty, field_index, mod)) |llvm_field| {
|
|
return self.builder.buildStructGEP(struct_llvm_ty, struct_ptr, llvm_field.index, "");
|
|
} else {
|
|
// If we found no index then this means this is a zero sized field at the
|
|
// end of the struct. Treat our struct pointer as an array of two and get
|
|
// the index to the element at index `1` to get a pointer to the end of
|
|
// the struct.
|
|
const llvm_u32 = self.context.intType(32);
|
|
const llvm_index = llvm_u32.constInt(@intFromBool(struct_ty.hasRuntimeBitsIgnoreComptime(mod)), .False);
|
|
const indices: [1]*llvm.Value = .{llvm_index};
|
|
return self.builder.buildInBoundsGEP(struct_llvm_ty, struct_ptr, &indices, indices.len, "");
|
|
}
|
|
},
|
|
},
|
|
.Union => {
|
|
const layout = struct_ty.unionGetLayout(mod);
|
|
if (layout.payload_size == 0 or struct_ty.containerLayout(mod) == .Packed) return struct_ptr;
|
|
const payload_index = @intFromBool(layout.tag_align >= layout.payload_align);
|
|
const union_llvm_ty = try o.lowerType(struct_ty);
|
|
const union_field_ptr = self.builder.buildStructGEP(union_llvm_ty, struct_ptr, payload_index, "");
|
|
return union_field_ptr;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn getIntrinsic(fg: *FuncGen, name: []const u8, types: []const *llvm.Type) *llvm.Value {
|
|
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
|
|
assert(id != 0);
|
|
const o = fg.dg.object;
|
|
return o.llvm_module.getIntrinsicDeclaration(id, types.ptr, types.len);
|
|
}
|
|
|
|
/// Load a by-ref type by constructing a new alloca and performing a memcpy.
|
|
fn loadByRef(
|
|
fg: *FuncGen,
|
|
ptr: *llvm.Value,
|
|
pointee_type: Type,
|
|
ptr_alignment: u32,
|
|
is_volatile: bool,
|
|
) !*llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const pointee_llvm_ty = try o.lowerType(pointee_type);
|
|
const result_align = @max(ptr_alignment, pointee_type.abiAlignment(mod));
|
|
const result_ptr = fg.buildAlloca(pointee_llvm_ty, result_align);
|
|
const llvm_usize = fg.context.intType(Type.usize.intInfo(mod).bits);
|
|
const size_bytes = pointee_type.abiSize(mod);
|
|
_ = fg.builder.buildMemCpy(
|
|
result_ptr,
|
|
result_align,
|
|
ptr,
|
|
ptr_alignment,
|
|
llvm_usize.constInt(size_bytes, .False),
|
|
is_volatile,
|
|
);
|
|
return result_ptr;
|
|
}
|
|
|
|
/// This function always performs a copy. For isByRef=true types, it creates a new
|
|
/// alloca and copies the value into it, then returns the alloca instruction.
|
|
/// For isByRef=false types, it creates a load instruction and returns it.
|
|
fn load(self: *FuncGen, ptr: *llvm.Value, ptr_ty: Type) !?*llvm.Value {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const info = ptr_ty.ptrInfo(mod);
|
|
const elem_ty = info.child.toType();
|
|
if (!elem_ty.hasRuntimeBitsIgnoreComptime(mod)) return null;
|
|
|
|
const ptr_alignment = @as(u32, @intCast(info.flags.alignment.toByteUnitsOptional() orelse
|
|
elem_ty.abiAlignment(mod)));
|
|
const ptr_volatile = llvm.Bool.fromBool(info.flags.is_volatile);
|
|
|
|
assert(info.flags.vector_index != .runtime);
|
|
if (info.flags.vector_index != .none) {
|
|
const index_u32 = self.context.intType(32).constInt(@intFromEnum(info.flags.vector_index), .False);
|
|
const vec_elem_ty = try o.lowerType(elem_ty);
|
|
const vec_ty = vec_elem_ty.vectorType(info.packed_offset.host_size);
|
|
|
|
const loaded_vector = self.builder.buildLoad(vec_ty, ptr, "");
|
|
loaded_vector.setAlignment(ptr_alignment);
|
|
loaded_vector.setVolatile(ptr_volatile);
|
|
|
|
return self.builder.buildExtractElement(loaded_vector, index_u32, "");
|
|
}
|
|
|
|
if (info.packed_offset.host_size == 0) {
|
|
if (isByRef(elem_ty, mod)) {
|
|
return self.loadByRef(ptr, elem_ty, ptr_alignment, info.flags.is_volatile);
|
|
}
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
const llvm_inst = self.builder.buildLoad(elem_llvm_ty, ptr, "");
|
|
llvm_inst.setAlignment(ptr_alignment);
|
|
llvm_inst.setVolatile(ptr_volatile);
|
|
return llvm_inst;
|
|
}
|
|
|
|
const int_elem_ty = self.context.intType(info.packed_offset.host_size * 8);
|
|
const containing_int = self.builder.buildLoad(int_elem_ty, ptr, "");
|
|
containing_int.setAlignment(ptr_alignment);
|
|
containing_int.setVolatile(ptr_volatile);
|
|
|
|
const elem_bits = @as(c_uint, @intCast(ptr_ty.childType(mod).bitSize(mod)));
|
|
const shift_amt = containing_int.typeOf().constInt(info.packed_offset.bit_offset, .False);
|
|
const shifted_value = self.builder.buildLShr(containing_int, shift_amt, "");
|
|
const elem_llvm_ty = try o.lowerType(elem_ty);
|
|
|
|
if (isByRef(elem_ty, mod)) {
|
|
const result_align = elem_ty.abiAlignment(mod);
|
|
const result_ptr = self.buildAlloca(elem_llvm_ty, result_align);
|
|
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, "");
|
|
const store_inst = self.builder.buildStore(truncated_int, result_ptr);
|
|
store_inst.setAlignment(result_align);
|
|
return result_ptr;
|
|
}
|
|
|
|
if (elem_ty.zigTypeTag(mod) == .Float or elem_ty.zigTypeTag(mod) == .Vector) {
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, "");
|
|
return self.builder.buildBitCast(truncated_int, elem_llvm_ty, "");
|
|
}
|
|
|
|
if (elem_ty.isPtrAtRuntime(mod)) {
|
|
const same_size_int = self.context.intType(elem_bits);
|
|
const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, "");
|
|
return self.builder.buildIntToPtr(truncated_int, elem_llvm_ty, "");
|
|
}
|
|
|
|
return self.builder.buildTrunc(shifted_value, elem_llvm_ty, "");
|
|
}
|
|
|
|
fn store(
|
|
self: *FuncGen,
|
|
ptr: *llvm.Value,
|
|
ptr_ty: Type,
|
|
elem: *llvm.Value,
|
|
ordering: llvm.AtomicOrdering,
|
|
) !void {
|
|
const o = self.dg.object;
|
|
const mod = o.module;
|
|
const info = ptr_ty.ptrInfo(mod);
|
|
const elem_ty = info.child.toType();
|
|
if (!elem_ty.isFnOrHasRuntimeBitsIgnoreComptime(mod)) {
|
|
return;
|
|
}
|
|
const ptr_alignment = ptr_ty.ptrAlignment(mod);
|
|
const ptr_volatile = llvm.Bool.fromBool(info.flags.is_volatile);
|
|
|
|
assert(info.flags.vector_index != .runtime);
|
|
if (info.flags.vector_index != .none) {
|
|
const index_u32 = self.context.intType(32).constInt(@intFromEnum(info.flags.vector_index), .False);
|
|
const vec_elem_ty = try o.lowerType(elem_ty);
|
|
const vec_ty = vec_elem_ty.vectorType(info.packed_offset.host_size);
|
|
|
|
const loaded_vector = self.builder.buildLoad(vec_ty, ptr, "");
|
|
loaded_vector.setAlignment(ptr_alignment);
|
|
loaded_vector.setVolatile(ptr_volatile);
|
|
|
|
const modified_vector = self.builder.buildInsertElement(loaded_vector, elem, index_u32, "");
|
|
|
|
const store_inst = self.builder.buildStore(modified_vector, ptr);
|
|
assert(ordering == .NotAtomic);
|
|
store_inst.setAlignment(ptr_alignment);
|
|
store_inst.setVolatile(ptr_volatile);
|
|
return;
|
|
}
|
|
|
|
if (info.packed_offset.host_size != 0) {
|
|
const int_elem_ty = self.context.intType(info.packed_offset.host_size * 8);
|
|
const containing_int = self.builder.buildLoad(int_elem_ty, ptr, "");
|
|
assert(ordering == .NotAtomic);
|
|
containing_int.setAlignment(ptr_alignment);
|
|
containing_int.setVolatile(ptr_volatile);
|
|
const elem_bits = @as(c_uint, @intCast(ptr_ty.childType(mod).bitSize(mod)));
|
|
const containing_int_ty = containing_int.typeOf();
|
|
const shift_amt = containing_int_ty.constInt(info.packed_offset.bit_offset, .False);
|
|
// Convert to equally-sized integer type in order to perform the bit
|
|
// operations on the value to store
|
|
const value_bits_type = self.context.intType(elem_bits);
|
|
const value_bits = if (elem_ty.isPtrAtRuntime(mod))
|
|
self.builder.buildPtrToInt(elem, value_bits_type, "")
|
|
else
|
|
self.builder.buildBitCast(elem, value_bits_type, "");
|
|
|
|
var mask_val = value_bits_type.constAllOnes();
|
|
mask_val = mask_val.constZExt(containing_int_ty);
|
|
mask_val = mask_val.constShl(shift_amt);
|
|
mask_val = mask_val.constNot();
|
|
|
|
const anded_containing_int = self.builder.buildAnd(containing_int, mask_val, "");
|
|
const extended_value = self.builder.buildZExt(value_bits, containing_int_ty, "");
|
|
const shifted_value = self.builder.buildShl(extended_value, shift_amt, "");
|
|
const ored_value = self.builder.buildOr(shifted_value, anded_containing_int, "");
|
|
|
|
const store_inst = self.builder.buildStore(ored_value, ptr);
|
|
assert(ordering == .NotAtomic);
|
|
store_inst.setAlignment(ptr_alignment);
|
|
store_inst.setVolatile(ptr_volatile);
|
|
return;
|
|
}
|
|
if (!isByRef(elem_ty, mod)) {
|
|
const store_inst = self.builder.buildStore(elem, ptr);
|
|
store_inst.setOrdering(ordering);
|
|
store_inst.setAlignment(ptr_alignment);
|
|
store_inst.setVolatile(ptr_volatile);
|
|
return;
|
|
}
|
|
assert(ordering == .NotAtomic);
|
|
const size_bytes = elem_ty.abiSize(mod);
|
|
_ = self.builder.buildMemCpy(
|
|
ptr,
|
|
ptr_alignment,
|
|
elem,
|
|
elem_ty.abiAlignment(mod),
|
|
self.context.intType(Type.usize.intInfo(mod).bits).constInt(size_bytes, .False),
|
|
info.flags.is_volatile,
|
|
);
|
|
}
|
|
|
|
fn valgrindMarkUndef(fg: *FuncGen, ptr: *llvm.Value, len: *llvm.Value) void {
|
|
const VG_USERREQ__MAKE_MEM_UNDEFINED = 1296236545;
|
|
const o = fg.dg.object;
|
|
const target = o.module.getTarget();
|
|
const usize_llvm_ty = fg.context.intType(target.ptrBitWidth());
|
|
const zero = usize_llvm_ty.constInt(0, .False);
|
|
const req = usize_llvm_ty.constInt(VG_USERREQ__MAKE_MEM_UNDEFINED, .False);
|
|
const ptr_as_usize = fg.builder.buildPtrToInt(ptr, usize_llvm_ty, "");
|
|
_ = valgrindClientRequest(fg, zero, req, ptr_as_usize, len, zero, zero, zero);
|
|
}
|
|
|
|
fn valgrindClientRequest(
|
|
fg: *FuncGen,
|
|
default_value: *llvm.Value,
|
|
request: *llvm.Value,
|
|
a1: *llvm.Value,
|
|
a2: *llvm.Value,
|
|
a3: *llvm.Value,
|
|
a4: *llvm.Value,
|
|
a5: *llvm.Value,
|
|
) *llvm.Value {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
const target = mod.getTarget();
|
|
if (!target_util.hasValgrindSupport(target)) return default_value;
|
|
|
|
const usize_llvm_ty = fg.context.intType(target.ptrBitWidth());
|
|
const usize_alignment = @as(c_uint, @intCast(Type.usize.abiSize(mod)));
|
|
|
|
const array_llvm_ty = usize_llvm_ty.arrayType(6);
|
|
const array_ptr = fg.valgrind_client_request_array orelse a: {
|
|
const array_ptr = fg.buildAlloca(array_llvm_ty, usize_alignment);
|
|
fg.valgrind_client_request_array = array_ptr;
|
|
break :a array_ptr;
|
|
};
|
|
const array_elements = [_]*llvm.Value{ request, a1, a2, a3, a4, a5 };
|
|
const zero = usize_llvm_ty.constInt(0, .False);
|
|
for (array_elements, 0..) |elem, i| {
|
|
const indexes = [_]*llvm.Value{
|
|
zero, usize_llvm_ty.constInt(@as(c_uint, @intCast(i)), .False),
|
|
};
|
|
const elem_ptr = fg.builder.buildInBoundsGEP(array_llvm_ty, array_ptr, &indexes, indexes.len, "");
|
|
const store_inst = fg.builder.buildStore(elem, elem_ptr);
|
|
store_inst.setAlignment(usize_alignment);
|
|
}
|
|
|
|
const arch_specific: struct {
|
|
template: [:0]const u8,
|
|
constraints: [:0]const u8,
|
|
} = switch (target.cpu.arch) {
|
|
.x86 => .{
|
|
.template =
|
|
\\roll $$3, %edi ; roll $$13, %edi
|
|
\\roll $$61, %edi ; roll $$51, %edi
|
|
\\xchgl %ebx,%ebx
|
|
,
|
|
.constraints = "={edx},{eax},0,~{cc},~{memory}",
|
|
},
|
|
.x86_64 => .{
|
|
.template =
|
|
\\rolq $$3, %rdi ; rolq $$13, %rdi
|
|
\\rolq $$61, %rdi ; rolq $$51, %rdi
|
|
\\xchgq %rbx,%rbx
|
|
,
|
|
.constraints = "={rdx},{rax},0,~{cc},~{memory}",
|
|
},
|
|
.aarch64, .aarch64_32, .aarch64_be => .{
|
|
.template =
|
|
\\ror x12, x12, #3 ; ror x12, x12, #13
|
|
\\ror x12, x12, #51 ; ror x12, x12, #61
|
|
\\orr x10, x10, x10
|
|
,
|
|
.constraints = "={x3},{x4},0,~{cc},~{memory}",
|
|
},
|
|
else => unreachable,
|
|
};
|
|
|
|
const array_ptr_as_usize = fg.builder.buildPtrToInt(array_ptr, usize_llvm_ty, "");
|
|
const args = [_]*llvm.Value{ array_ptr_as_usize, default_value };
|
|
const param_types = [_]*llvm.Type{ usize_llvm_ty, usize_llvm_ty };
|
|
const fn_llvm_ty = llvm.functionType(usize_llvm_ty, ¶m_types, args.len, .False);
|
|
const asm_fn = llvm.getInlineAsm(
|
|
fn_llvm_ty,
|
|
arch_specific.template.ptr,
|
|
arch_specific.template.len,
|
|
arch_specific.constraints.ptr,
|
|
arch_specific.constraints.len,
|
|
.True, // has side effects
|
|
.False, // alignstack
|
|
.ATT,
|
|
.False, // can throw
|
|
);
|
|
|
|
const call = fg.builder.buildCall(
|
|
fn_llvm_ty,
|
|
asm_fn,
|
|
&args,
|
|
args.len,
|
|
.C,
|
|
.Auto,
|
|
"",
|
|
);
|
|
return call;
|
|
}
|
|
|
|
fn typeOf(fg: *FuncGen, inst: Air.Inst.Ref) Type {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
return fg.air.typeOf(inst, &mod.intern_pool);
|
|
}
|
|
|
|
fn typeOfIndex(fg: *FuncGen, inst: Air.Inst.Index) Type {
|
|
const o = fg.dg.object;
|
|
const mod = o.module;
|
|
return fg.air.typeOfIndex(inst, &mod.intern_pool);
|
|
}
|
|
};
|
|
|
|
fn initializeLLVMTarget(arch: std.Target.Cpu.Arch) void {
|
|
switch (arch) {
|
|
.aarch64, .aarch64_be, .aarch64_32 => {
|
|
llvm.LLVMInitializeAArch64Target();
|
|
llvm.LLVMInitializeAArch64TargetInfo();
|
|
llvm.LLVMInitializeAArch64TargetMC();
|
|
llvm.LLVMInitializeAArch64AsmPrinter();
|
|
llvm.LLVMInitializeAArch64AsmParser();
|
|
},
|
|
.amdgcn => {
|
|
llvm.LLVMInitializeAMDGPUTarget();
|
|
llvm.LLVMInitializeAMDGPUTargetInfo();
|
|
llvm.LLVMInitializeAMDGPUTargetMC();
|
|
llvm.LLVMInitializeAMDGPUAsmPrinter();
|
|
llvm.LLVMInitializeAMDGPUAsmParser();
|
|
},
|
|
.thumb, .thumbeb, .arm, .armeb => {
|
|
llvm.LLVMInitializeARMTarget();
|
|
llvm.LLVMInitializeARMTargetInfo();
|
|
llvm.LLVMInitializeARMTargetMC();
|
|
llvm.LLVMInitializeARMAsmPrinter();
|
|
llvm.LLVMInitializeARMAsmParser();
|
|
},
|
|
.avr => {
|
|
llvm.LLVMInitializeAVRTarget();
|
|
llvm.LLVMInitializeAVRTargetInfo();
|
|
llvm.LLVMInitializeAVRTargetMC();
|
|
llvm.LLVMInitializeAVRAsmPrinter();
|
|
llvm.LLVMInitializeAVRAsmParser();
|
|
},
|
|
.bpfel, .bpfeb => {
|
|
llvm.LLVMInitializeBPFTarget();
|
|
llvm.LLVMInitializeBPFTargetInfo();
|
|
llvm.LLVMInitializeBPFTargetMC();
|
|
llvm.LLVMInitializeBPFAsmPrinter();
|
|
llvm.LLVMInitializeBPFAsmParser();
|
|
},
|
|
.hexagon => {
|
|
llvm.LLVMInitializeHexagonTarget();
|
|
llvm.LLVMInitializeHexagonTargetInfo();
|
|
llvm.LLVMInitializeHexagonTargetMC();
|
|
llvm.LLVMInitializeHexagonAsmPrinter();
|
|
llvm.LLVMInitializeHexagonAsmParser();
|
|
},
|
|
.lanai => {
|
|
llvm.LLVMInitializeLanaiTarget();
|
|
llvm.LLVMInitializeLanaiTargetInfo();
|
|
llvm.LLVMInitializeLanaiTargetMC();
|
|
llvm.LLVMInitializeLanaiAsmPrinter();
|
|
llvm.LLVMInitializeLanaiAsmParser();
|
|
},
|
|
.mips, .mipsel, .mips64, .mips64el => {
|
|
llvm.LLVMInitializeMipsTarget();
|
|
llvm.LLVMInitializeMipsTargetInfo();
|
|
llvm.LLVMInitializeMipsTargetMC();
|
|
llvm.LLVMInitializeMipsAsmPrinter();
|
|
llvm.LLVMInitializeMipsAsmParser();
|
|
},
|
|
.msp430 => {
|
|
llvm.LLVMInitializeMSP430Target();
|
|
llvm.LLVMInitializeMSP430TargetInfo();
|
|
llvm.LLVMInitializeMSP430TargetMC();
|
|
llvm.LLVMInitializeMSP430AsmPrinter();
|
|
llvm.LLVMInitializeMSP430AsmParser();
|
|
},
|
|
.nvptx, .nvptx64 => {
|
|
llvm.LLVMInitializeNVPTXTarget();
|
|
llvm.LLVMInitializeNVPTXTargetInfo();
|
|
llvm.LLVMInitializeNVPTXTargetMC();
|
|
llvm.LLVMInitializeNVPTXAsmPrinter();
|
|
// There is no LLVMInitializeNVPTXAsmParser function available.
|
|
},
|
|
.powerpc, .powerpcle, .powerpc64, .powerpc64le => {
|
|
llvm.LLVMInitializePowerPCTarget();
|
|
llvm.LLVMInitializePowerPCTargetInfo();
|
|
llvm.LLVMInitializePowerPCTargetMC();
|
|
llvm.LLVMInitializePowerPCAsmPrinter();
|
|
llvm.LLVMInitializePowerPCAsmParser();
|
|
},
|
|
.riscv32, .riscv64 => {
|
|
llvm.LLVMInitializeRISCVTarget();
|
|
llvm.LLVMInitializeRISCVTargetInfo();
|
|
llvm.LLVMInitializeRISCVTargetMC();
|
|
llvm.LLVMInitializeRISCVAsmPrinter();
|
|
llvm.LLVMInitializeRISCVAsmParser();
|
|
},
|
|
.sparc, .sparc64, .sparcel => {
|
|
llvm.LLVMInitializeSparcTarget();
|
|
llvm.LLVMInitializeSparcTargetInfo();
|
|
llvm.LLVMInitializeSparcTargetMC();
|
|
llvm.LLVMInitializeSparcAsmPrinter();
|
|
llvm.LLVMInitializeSparcAsmParser();
|
|
},
|
|
.s390x => {
|
|
llvm.LLVMInitializeSystemZTarget();
|
|
llvm.LLVMInitializeSystemZTargetInfo();
|
|
llvm.LLVMInitializeSystemZTargetMC();
|
|
llvm.LLVMInitializeSystemZAsmPrinter();
|
|
llvm.LLVMInitializeSystemZAsmParser();
|
|
},
|
|
.wasm32, .wasm64 => {
|
|
llvm.LLVMInitializeWebAssemblyTarget();
|
|
llvm.LLVMInitializeWebAssemblyTargetInfo();
|
|
llvm.LLVMInitializeWebAssemblyTargetMC();
|
|
llvm.LLVMInitializeWebAssemblyAsmPrinter();
|
|
llvm.LLVMInitializeWebAssemblyAsmParser();
|
|
},
|
|
.x86, .x86_64 => {
|
|
llvm.LLVMInitializeX86Target();
|
|
llvm.LLVMInitializeX86TargetInfo();
|
|
llvm.LLVMInitializeX86TargetMC();
|
|
llvm.LLVMInitializeX86AsmPrinter();
|
|
llvm.LLVMInitializeX86AsmParser();
|
|
},
|
|
.xtensa => {
|
|
if (build_options.llvm_has_xtensa) {
|
|
llvm.LLVMInitializeXtensaTarget();
|
|
llvm.LLVMInitializeXtensaTargetInfo();
|
|
llvm.LLVMInitializeXtensaTargetMC();
|
|
llvm.LLVMInitializeXtensaAsmPrinter();
|
|
llvm.LLVMInitializeXtensaAsmParser();
|
|
}
|
|
},
|
|
.xcore => {
|
|
llvm.LLVMInitializeXCoreTarget();
|
|
llvm.LLVMInitializeXCoreTargetInfo();
|
|
llvm.LLVMInitializeXCoreTargetMC();
|
|
llvm.LLVMInitializeXCoreAsmPrinter();
|
|
// There is no LLVMInitializeXCoreAsmParser function.
|
|
},
|
|
.m68k => {
|
|
if (build_options.llvm_has_m68k) {
|
|
llvm.LLVMInitializeM68kTarget();
|
|
llvm.LLVMInitializeM68kTargetInfo();
|
|
llvm.LLVMInitializeM68kTargetMC();
|
|
llvm.LLVMInitializeM68kAsmPrinter();
|
|
llvm.LLVMInitializeM68kAsmParser();
|
|
}
|
|
},
|
|
.csky => {
|
|
if (build_options.llvm_has_csky) {
|
|
llvm.LLVMInitializeCSKYTarget();
|
|
llvm.LLVMInitializeCSKYTargetInfo();
|
|
llvm.LLVMInitializeCSKYTargetMC();
|
|
// There is no LLVMInitializeCSKYAsmPrinter function.
|
|
llvm.LLVMInitializeCSKYAsmParser();
|
|
}
|
|
},
|
|
.ve => {
|
|
llvm.LLVMInitializeVETarget();
|
|
llvm.LLVMInitializeVETargetInfo();
|
|
llvm.LLVMInitializeVETargetMC();
|
|
llvm.LLVMInitializeVEAsmPrinter();
|
|
llvm.LLVMInitializeVEAsmParser();
|
|
},
|
|
.arc => {
|
|
if (build_options.llvm_has_arc) {
|
|
llvm.LLVMInitializeARCTarget();
|
|
llvm.LLVMInitializeARCTargetInfo();
|
|
llvm.LLVMInitializeARCTargetMC();
|
|
llvm.LLVMInitializeARCAsmPrinter();
|
|
// There is no LLVMInitializeARCAsmParser function.
|
|
}
|
|
},
|
|
|
|
// LLVM backends that have no initialization functions.
|
|
.tce,
|
|
.tcele,
|
|
.r600,
|
|
.le32,
|
|
.le64,
|
|
.amdil,
|
|
.amdil64,
|
|
.hsail,
|
|
.hsail64,
|
|
.shave,
|
|
.spir,
|
|
.spir64,
|
|
.kalimba,
|
|
.renderscript32,
|
|
.renderscript64,
|
|
.dxil,
|
|
.loongarch32,
|
|
.loongarch64,
|
|
=> {},
|
|
|
|
.spu_2 => unreachable, // LLVM does not support this backend
|
|
.spirv32 => unreachable, // LLVM does not support this backend
|
|
.spirv64 => unreachable, // LLVM does not support this backend
|
|
}
|
|
}
|
|
|
|
fn toLlvmAtomicOrdering(atomic_order: std.builtin.AtomicOrder) llvm.AtomicOrdering {
|
|
return switch (atomic_order) {
|
|
.Unordered => .Unordered,
|
|
.Monotonic => .Monotonic,
|
|
.Acquire => .Acquire,
|
|
.Release => .Release,
|
|
.AcqRel => .AcquireRelease,
|
|
.SeqCst => .SequentiallyConsistent,
|
|
};
|
|
}
|
|
|
|
fn toLlvmAtomicRmwBinOp(
|
|
op: std.builtin.AtomicRmwOp,
|
|
is_signed: bool,
|
|
is_float: bool,
|
|
) llvm.AtomicRMWBinOp {
|
|
return switch (op) {
|
|
.Xchg => .Xchg,
|
|
.Add => if (is_float) .FAdd else return .Add,
|
|
.Sub => if (is_float) .FSub else return .Sub,
|
|
.And => .And,
|
|
.Nand => .Nand,
|
|
.Or => .Or,
|
|
.Xor => .Xor,
|
|
.Max => if (is_float) .FMax else if (is_signed) .Max else return .UMax,
|
|
.Min => if (is_float) .FMin else if (is_signed) .Min else return .UMin,
|
|
};
|
|
}
|
|
|
|
fn toLlvmCallConv(cc: std.builtin.CallingConvention, target: std.Target) llvm.CallConv {
|
|
return switch (cc) {
|
|
.Unspecified, .Inline, .Async => .Fast,
|
|
.C, .Naked => .C,
|
|
.Stdcall => .X86_StdCall,
|
|
.Fastcall => .X86_FastCall,
|
|
.Vectorcall => return switch (target.cpu.arch) {
|
|
.x86, .x86_64 => .X86_VectorCall,
|
|
.aarch64, .aarch64_be, .aarch64_32 => .AArch64_VectorCall,
|
|
else => unreachable,
|
|
},
|
|
.Thiscall => .X86_ThisCall,
|
|
.APCS => .ARM_APCS,
|
|
.AAPCS => .ARM_AAPCS,
|
|
.AAPCSVFP => .ARM_AAPCS_VFP,
|
|
.Interrupt => return switch (target.cpu.arch) {
|
|
.x86, .x86_64 => .X86_INTR,
|
|
.avr => .AVR_INTR,
|
|
.msp430 => .MSP430_INTR,
|
|
else => unreachable,
|
|
},
|
|
.Signal => .AVR_SIGNAL,
|
|
.SysV => .X86_64_SysV,
|
|
.Win64 => .Win64,
|
|
.Kernel => return switch (target.cpu.arch) {
|
|
.nvptx, .nvptx64 => .PTX_Kernel,
|
|
.amdgcn => .AMDGPU_KERNEL,
|
|
else => unreachable,
|
|
},
|
|
};
|
|
}
|
|
|
|
/// Convert a zig-address space to an llvm address space.
|
|
fn toLlvmAddressSpace(address_space: std.builtin.AddressSpace, target: std.Target) c_uint {
|
|
return switch (target.cpu.arch) {
|
|
.x86, .x86_64 => switch (address_space) {
|
|
.generic => llvm.address_space.default,
|
|
.gs => llvm.address_space.x86.gs,
|
|
.fs => llvm.address_space.x86.fs,
|
|
.ss => llvm.address_space.x86.ss,
|
|
else => unreachable,
|
|
},
|
|
.nvptx, .nvptx64 => switch (address_space) {
|
|
.generic => llvm.address_space.default,
|
|
.global => llvm.address_space.nvptx.global,
|
|
.constant => llvm.address_space.nvptx.constant,
|
|
.param => llvm.address_space.nvptx.param,
|
|
.shared => llvm.address_space.nvptx.shared,
|
|
.local => llvm.address_space.nvptx.local,
|
|
else => unreachable,
|
|
},
|
|
.amdgcn => switch (address_space) {
|
|
.generic => llvm.address_space.amdgpu.flat,
|
|
.global => llvm.address_space.amdgpu.global,
|
|
.constant => llvm.address_space.amdgpu.constant,
|
|
.shared => llvm.address_space.amdgpu.local,
|
|
.local => llvm.address_space.amdgpu.private,
|
|
else => unreachable,
|
|
},
|
|
.avr => switch (address_space) {
|
|
.generic => llvm.address_space.default,
|
|
.flash => llvm.address_space.avr.flash,
|
|
.flash1 => llvm.address_space.avr.flash1,
|
|
.flash2 => llvm.address_space.avr.flash2,
|
|
.flash3 => llvm.address_space.avr.flash3,
|
|
.flash4 => llvm.address_space.avr.flash4,
|
|
.flash5 => llvm.address_space.avr.flash5,
|
|
else => unreachable,
|
|
},
|
|
else => switch (address_space) {
|
|
.generic => llvm.address_space.default,
|
|
else => unreachable,
|
|
},
|
|
};
|
|
}
|
|
|
|
/// On some targets, local 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.
|
|
/// For example, on GPUs local variable declarations must be generated into the local address space.
|
|
/// This function returns the address space local values should be generated into.
|
|
fn llvmAllocaAddressSpace(target: std.Target) c_uint {
|
|
return switch (target.cpu.arch) {
|
|
// On amdgcn, locals should be generated into the private address space.
|
|
// To make Zig not impossible to use, these are then converted to addresses in the
|
|
// generic address space and treates as regular pointers. This is the way that HIP also does it.
|
|
.amdgcn => llvm.address_space.amdgpu.private,
|
|
else => llvm.address_space.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: std.Target) c_uint {
|
|
return switch (target.cpu.arch) {
|
|
// On amdgcn, globals must be explicitly allocated and uploaded so that the program can access
|
|
// them.
|
|
.amdgcn => llvm.address_space.amdgpu.global,
|
|
else => llvm.address_space.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: std.Target) c_uint {
|
|
return switch (wanted_address_space) {
|
|
.generic => llvmDefaultGlobalAddressSpace(target),
|
|
else => |as| toLlvmAddressSpace(as, target),
|
|
};
|
|
}
|
|
|
|
const LlvmField = struct {
|
|
index: c_uint,
|
|
ty: Type,
|
|
alignment: u32,
|
|
};
|
|
|
|
/// Take into account 0 bit fields and padding. Returns null if an llvm
|
|
/// field could not be found.
|
|
/// This only happens if you want the field index of a zero sized field at
|
|
/// the end of the struct.
|
|
fn llvmField(ty: Type, field_index: usize, mod: *Module) ?LlvmField {
|
|
// Detects where we inserted extra padding fields so that we can skip
|
|
// over them in this function.
|
|
comptime assert(struct_layout_version == 2);
|
|
var offset: u64 = 0;
|
|
var big_align: u32 = 0;
|
|
|
|
const struct_type = switch (mod.intern_pool.indexToKey(ty.toIntern())) {
|
|
.anon_struct_type => |tuple| {
|
|
var llvm_field_index: c_uint = 0;
|
|
for (tuple.types, tuple.values, 0..) |field_ty, field_val, i| {
|
|
if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
|
|
|
|
const field_align = field_ty.toType().abiAlignment(mod);
|
|
big_align = @max(big_align, field_align);
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, field_align);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
llvm_field_index += 1;
|
|
}
|
|
|
|
if (field_index <= i) {
|
|
return .{
|
|
.index = llvm_field_index,
|
|
.ty = field_ty.toType(),
|
|
.alignment = field_align,
|
|
};
|
|
}
|
|
|
|
llvm_field_index += 1;
|
|
offset += field_ty.toType().abiSize(mod);
|
|
}
|
|
return null;
|
|
},
|
|
.struct_type => |s| s,
|
|
else => unreachable,
|
|
};
|
|
const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
|
|
const layout = struct_obj.layout;
|
|
assert(layout != .Packed);
|
|
|
|
var llvm_field_index: c_uint = 0;
|
|
var it = struct_obj.runtimeFieldIterator(mod);
|
|
while (it.next()) |field_and_index| {
|
|
const field = field_and_index.field;
|
|
const field_align = field.alignment(mod, layout);
|
|
big_align = @max(big_align, field_align);
|
|
const prev_offset = offset;
|
|
offset = std.mem.alignForward(u64, offset, field_align);
|
|
|
|
const padding_len = offset - prev_offset;
|
|
if (padding_len > 0) {
|
|
llvm_field_index += 1;
|
|
}
|
|
|
|
if (field_index == field_and_index.index) {
|
|
return .{
|
|
.index = llvm_field_index,
|
|
.ty = field.ty,
|
|
.alignment = field_align,
|
|
};
|
|
}
|
|
|
|
llvm_field_index += 1;
|
|
offset += field.ty.abiSize(mod);
|
|
} else {
|
|
// We did not find an llvm field that corresponds to this zig field.
|
|
return null;
|
|
}
|
|
}
|
|
|
|
fn firstParamSRet(fn_info: InternPool.Key.FuncType, mod: *Module) bool {
|
|
const return_type = fn_info.return_type.toType();
|
|
if (!return_type.hasRuntimeBitsIgnoreComptime(mod)) return false;
|
|
|
|
const target = mod.getTarget();
|
|
switch (fn_info.cc) {
|
|
.Unspecified, .Inline => return isByRef(return_type, mod),
|
|
.C => switch (target.cpu.arch) {
|
|
.mips, .mipsel => return false,
|
|
.x86_64 => switch (target.os.tag) {
|
|
.windows => return x86_64_abi.classifyWindows(return_type, mod) == .memory,
|
|
else => return firstParamSRetSystemV(return_type, mod),
|
|
},
|
|
.wasm32 => return wasm_c_abi.classifyType(return_type, mod)[0] == .indirect,
|
|
.aarch64, .aarch64_be => return aarch64_c_abi.classifyType(return_type, mod) == .memory,
|
|
.arm, .armeb => switch (arm_c_abi.classifyType(return_type, mod, .ret)) {
|
|
.memory, .i64_array => return true,
|
|
.i32_array => |size| return size != 1,
|
|
.byval => return false,
|
|
},
|
|
.riscv32, .riscv64 => return riscv_c_abi.classifyType(return_type, mod) == .memory,
|
|
else => return false, // TODO investigate C ABI for other architectures
|
|
},
|
|
.SysV => return firstParamSRetSystemV(return_type, mod),
|
|
.Win64 => return x86_64_abi.classifyWindows(return_type, mod) == .memory,
|
|
.Stdcall => return !isScalar(mod, return_type),
|
|
else => return false,
|
|
}
|
|
}
|
|
|
|
fn firstParamSRetSystemV(ty: Type, mod: *Module) bool {
|
|
const class = x86_64_abi.classifySystemV(ty, mod, .ret);
|
|
if (class[0] == .memory) return true;
|
|
if (class[0] == .x87 and class[2] != .none) return true;
|
|
return false;
|
|
}
|
|
|
|
/// In order to support the C calling convention, some return types need to be lowered
|
|
/// completely differently in the function prototype to honor the C ABI, and then
|
|
/// be effectively bitcasted to the actual return type.
|
|
fn lowerFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) !*llvm.Type {
|
|
const mod = o.module;
|
|
const return_type = fn_info.return_type.toType();
|
|
if (!return_type.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
// If the return type is an error set or an error union, then we make this
|
|
// anyerror return type instead, so that it can be coerced into a function
|
|
// pointer type which has anyerror as the return type.
|
|
if (return_type.isError(mod)) {
|
|
return o.lowerType(Type.anyerror);
|
|
} else {
|
|
return o.context.voidType();
|
|
}
|
|
}
|
|
const target = mod.getTarget();
|
|
switch (fn_info.cc) {
|
|
.Unspecified, .Inline => {
|
|
if (isByRef(return_type, mod)) {
|
|
return o.context.voidType();
|
|
} else {
|
|
return o.lowerType(return_type);
|
|
}
|
|
},
|
|
.C => {
|
|
switch (target.cpu.arch) {
|
|
.mips, .mipsel => return o.lowerType(return_type),
|
|
.x86_64 => switch (target.os.tag) {
|
|
.windows => return lowerWin64FnRetTy(o, fn_info),
|
|
else => return lowerSystemVFnRetTy(o, fn_info),
|
|
},
|
|
.wasm32 => {
|
|
if (isScalar(mod, return_type)) {
|
|
return o.lowerType(return_type);
|
|
}
|
|
const classes = wasm_c_abi.classifyType(return_type, mod);
|
|
if (classes[0] == .indirect or classes[0] == .none) {
|
|
return o.context.voidType();
|
|
}
|
|
|
|
assert(classes[0] == .direct and classes[1] == .none);
|
|
const scalar_type = wasm_c_abi.scalarType(return_type, mod);
|
|
const abi_size = scalar_type.abiSize(mod);
|
|
return o.context.intType(@as(c_uint, @intCast(abi_size * 8)));
|
|
},
|
|
.aarch64, .aarch64_be => {
|
|
switch (aarch64_c_abi.classifyType(return_type, mod)) {
|
|
.memory => return o.context.voidType(),
|
|
.float_array => return o.lowerType(return_type),
|
|
.byval => return o.lowerType(return_type),
|
|
.integer => {
|
|
const bit_size = return_type.bitSize(mod);
|
|
return o.context.intType(@as(c_uint, @intCast(bit_size)));
|
|
},
|
|
.double_integer => return o.context.intType(64).arrayType(2),
|
|
}
|
|
},
|
|
.arm, .armeb => {
|
|
switch (arm_c_abi.classifyType(return_type, mod, .ret)) {
|
|
.memory, .i64_array => return o.context.voidType(),
|
|
.i32_array => |len| if (len == 1) {
|
|
return o.context.intType(32);
|
|
} else {
|
|
return o.context.voidType();
|
|
},
|
|
.byval => return o.lowerType(return_type),
|
|
}
|
|
},
|
|
.riscv32, .riscv64 => {
|
|
switch (riscv_c_abi.classifyType(return_type, mod)) {
|
|
.memory => return o.context.voidType(),
|
|
.integer => {
|
|
const bit_size = return_type.bitSize(mod);
|
|
return o.context.intType(@as(c_uint, @intCast(bit_size)));
|
|
},
|
|
.double_integer => {
|
|
var llvm_types_buffer: [2]*llvm.Type = .{
|
|
o.context.intType(64),
|
|
o.context.intType(64),
|
|
};
|
|
return o.context.structType(&llvm_types_buffer, 2, .False);
|
|
},
|
|
.byval => return o.lowerType(return_type),
|
|
}
|
|
},
|
|
// TODO investigate C ABI for other architectures
|
|
else => return o.lowerType(return_type),
|
|
}
|
|
},
|
|
.Win64 => return lowerWin64FnRetTy(o, fn_info),
|
|
.SysV => return lowerSystemVFnRetTy(o, fn_info),
|
|
.Stdcall => {
|
|
if (isScalar(mod, return_type)) {
|
|
return o.lowerType(return_type);
|
|
} else {
|
|
return o.context.voidType();
|
|
}
|
|
},
|
|
else => return o.lowerType(return_type),
|
|
}
|
|
}
|
|
|
|
fn lowerWin64FnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) !*llvm.Type {
|
|
const mod = o.module;
|
|
const return_type = fn_info.return_type.toType();
|
|
switch (x86_64_abi.classifyWindows(return_type, mod)) {
|
|
.integer => {
|
|
if (isScalar(mod, return_type)) {
|
|
return o.lowerType(return_type);
|
|
} else {
|
|
const abi_size = return_type.abiSize(mod);
|
|
return o.context.intType(@as(c_uint, @intCast(abi_size * 8)));
|
|
}
|
|
},
|
|
.win_i128 => return o.context.intType(64).vectorType(2),
|
|
.memory => return o.context.voidType(),
|
|
.sse => return o.lowerType(return_type),
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn lowerSystemVFnRetTy(o: *Object, fn_info: InternPool.Key.FuncType) !*llvm.Type {
|
|
const mod = o.module;
|
|
const return_type = fn_info.return_type.toType();
|
|
if (isScalar(mod, return_type)) {
|
|
return o.lowerType(return_type);
|
|
}
|
|
const classes = x86_64_abi.classifySystemV(return_type, mod, .ret);
|
|
if (classes[0] == .memory) {
|
|
return o.context.voidType();
|
|
}
|
|
var llvm_types_buffer: [8]*llvm.Type = undefined;
|
|
var llvm_types_index: u32 = 0;
|
|
for (classes) |class| {
|
|
switch (class) {
|
|
.integer => {
|
|
llvm_types_buffer[llvm_types_index] = o.context.intType(64);
|
|
llvm_types_index += 1;
|
|
},
|
|
.sse, .sseup => {
|
|
llvm_types_buffer[llvm_types_index] = o.context.doubleType();
|
|
llvm_types_index += 1;
|
|
},
|
|
.float => {
|
|
llvm_types_buffer[llvm_types_index] = o.context.floatType();
|
|
llvm_types_index += 1;
|
|
},
|
|
.float_combine => {
|
|
llvm_types_buffer[llvm_types_index] = o.context.floatType().vectorType(2);
|
|
llvm_types_index += 1;
|
|
},
|
|
.x87 => {
|
|
if (llvm_types_index != 0 or classes[2] != .none) {
|
|
return o.context.voidType();
|
|
}
|
|
llvm_types_buffer[llvm_types_index] = o.context.x86FP80Type();
|
|
llvm_types_index += 1;
|
|
},
|
|
.x87up => continue,
|
|
.complex_x87 => {
|
|
@panic("TODO");
|
|
},
|
|
.memory => unreachable, // handled above
|
|
.win_i128 => unreachable, // windows only
|
|
.none => break,
|
|
}
|
|
}
|
|
if (classes[0] == .integer and classes[1] == .none) {
|
|
const abi_size = return_type.abiSize(mod);
|
|
return o.context.intType(@as(c_uint, @intCast(abi_size * 8)));
|
|
}
|
|
return o.context.structType(&llvm_types_buffer, llvm_types_index, .False);
|
|
}
|
|
|
|
const ParamTypeIterator = struct {
|
|
object: *Object,
|
|
fn_info: InternPool.Key.FuncType,
|
|
zig_index: u32,
|
|
llvm_index: u32,
|
|
llvm_types_len: u32,
|
|
llvm_types_buffer: [8]*llvm.Type,
|
|
byval_attr: bool,
|
|
|
|
const Lowering = union(enum) {
|
|
no_bits,
|
|
byval,
|
|
byref,
|
|
byref_mut,
|
|
abi_sized_int,
|
|
multiple_llvm_types,
|
|
slice,
|
|
as_u16,
|
|
float_array: u8,
|
|
i32_array: u8,
|
|
i64_array: u8,
|
|
};
|
|
|
|
pub fn next(it: *ParamTypeIterator) ?Lowering {
|
|
if (it.zig_index >= it.fn_info.param_types.len) return null;
|
|
const mod = it.object.module;
|
|
const ip = &mod.intern_pool;
|
|
const ty = it.fn_info.param_types.get(ip)[it.zig_index];
|
|
it.byval_attr = false;
|
|
return nextInner(it, ty.toType());
|
|
}
|
|
|
|
/// `airCall` uses this instead of `next` so that it can take into account variadic functions.
|
|
pub fn nextCall(it: *ParamTypeIterator, fg: *FuncGen, args: []const Air.Inst.Ref) ?Lowering {
|
|
const mod = it.object.module;
|
|
const ip = &mod.intern_pool;
|
|
if (it.zig_index >= it.fn_info.param_types.len) {
|
|
if (it.zig_index >= args.len) {
|
|
return null;
|
|
} else {
|
|
return nextInner(it, fg.typeOf(args[it.zig_index]));
|
|
}
|
|
} else {
|
|
return nextInner(it, it.fn_info.param_types.get(ip)[it.zig_index].toType());
|
|
}
|
|
}
|
|
|
|
fn nextInner(it: *ParamTypeIterator, ty: Type) ?Lowering {
|
|
const mod = it.object.module;
|
|
const target = mod.getTarget();
|
|
|
|
if (!ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
it.zig_index += 1;
|
|
return .no_bits;
|
|
}
|
|
switch (it.fn_info.cc) {
|
|
.Unspecified, .Inline => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
if (ty.isSlice(mod) or (ty.zigTypeTag(mod) == .Optional and ty.optionalChild(mod).isSlice(mod))) {
|
|
it.llvm_index += 1;
|
|
return .slice;
|
|
} else if (isByRef(ty, mod)) {
|
|
return .byref;
|
|
} else {
|
|
return .byval;
|
|
}
|
|
},
|
|
.Async => {
|
|
@panic("TODO implement async function lowering in the LLVM backend");
|
|
},
|
|
.C => {
|
|
switch (target.cpu.arch) {
|
|
.mips, .mipsel => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byval;
|
|
},
|
|
.x86_64 => switch (target.os.tag) {
|
|
.windows => return it.nextWin64(ty),
|
|
else => return it.nextSystemV(ty),
|
|
},
|
|
.wasm32 => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
if (isScalar(mod, ty)) {
|
|
return .byval;
|
|
}
|
|
const classes = wasm_c_abi.classifyType(ty, mod);
|
|
if (classes[0] == .indirect) {
|
|
return .byref;
|
|
}
|
|
return .abi_sized_int;
|
|
},
|
|
.aarch64, .aarch64_be => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
switch (aarch64_c_abi.classifyType(ty, mod)) {
|
|
.memory => return .byref_mut,
|
|
.float_array => |len| return Lowering{ .float_array = len },
|
|
.byval => return .byval,
|
|
.integer => {
|
|
it.llvm_types_len = 1;
|
|
it.llvm_types_buffer[0] = it.object.context.intType(64);
|
|
return .multiple_llvm_types;
|
|
},
|
|
.double_integer => return Lowering{ .i64_array = 2 },
|
|
}
|
|
},
|
|
.arm, .armeb => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
switch (arm_c_abi.classifyType(ty, mod, .arg)) {
|
|
.memory => {
|
|
it.byval_attr = true;
|
|
return .byref;
|
|
},
|
|
.byval => return .byval,
|
|
.i32_array => |size| return Lowering{ .i32_array = size },
|
|
.i64_array => |size| return Lowering{ .i64_array = size },
|
|
}
|
|
},
|
|
.riscv32, .riscv64 => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
if (ty.toIntern() == .f16_type) {
|
|
return .as_u16;
|
|
}
|
|
switch (riscv_c_abi.classifyType(ty, mod)) {
|
|
.memory => return .byref_mut,
|
|
.byval => return .byval,
|
|
.integer => return .abi_sized_int,
|
|
.double_integer => return Lowering{ .i64_array = 2 },
|
|
}
|
|
},
|
|
// TODO investigate C ABI for other architectures
|
|
else => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byval;
|
|
},
|
|
}
|
|
},
|
|
.Win64 => return it.nextWin64(ty),
|
|
.SysV => return it.nextSystemV(ty),
|
|
.Stdcall => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
|
|
if (isScalar(mod, ty)) {
|
|
return .byval;
|
|
} else {
|
|
it.byval_attr = true;
|
|
return .byref;
|
|
}
|
|
},
|
|
else => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byval;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn nextWin64(it: *ParamTypeIterator, ty: Type) ?Lowering {
|
|
const mod = it.object.module;
|
|
switch (x86_64_abi.classifyWindows(ty, mod)) {
|
|
.integer => {
|
|
if (isScalar(mod, ty)) {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byval;
|
|
} else {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .abi_sized_int;
|
|
}
|
|
},
|
|
.win_i128 => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byref;
|
|
},
|
|
.memory => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byref_mut;
|
|
},
|
|
.sse => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byval;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
fn nextSystemV(it: *ParamTypeIterator, ty: Type) ?Lowering {
|
|
const mod = it.object.module;
|
|
const classes = x86_64_abi.classifySystemV(ty, mod, .arg);
|
|
if (classes[0] == .memory) {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
it.byval_attr = true;
|
|
return .byref;
|
|
}
|
|
if (isScalar(mod, ty)) {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .byval;
|
|
}
|
|
var llvm_types_buffer: [8]*llvm.Type = undefined;
|
|
var llvm_types_index: u32 = 0;
|
|
for (classes) |class| {
|
|
switch (class) {
|
|
.integer => {
|
|
llvm_types_buffer[llvm_types_index] = it.object.context.intType(64);
|
|
llvm_types_index += 1;
|
|
},
|
|
.sse, .sseup => {
|
|
llvm_types_buffer[llvm_types_index] = it.object.context.doubleType();
|
|
llvm_types_index += 1;
|
|
},
|
|
.float => {
|
|
llvm_types_buffer[llvm_types_index] = it.object.context.floatType();
|
|
llvm_types_index += 1;
|
|
},
|
|
.float_combine => {
|
|
llvm_types_buffer[llvm_types_index] = it.object.context.floatType().vectorType(2);
|
|
llvm_types_index += 1;
|
|
},
|
|
.x87 => {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
it.byval_attr = true;
|
|
return .byref;
|
|
},
|
|
.x87up => unreachable,
|
|
.complex_x87 => {
|
|
@panic("TODO");
|
|
},
|
|
.memory => unreachable, // handled above
|
|
.win_i128 => unreachable, // windows only
|
|
.none => break,
|
|
}
|
|
}
|
|
if (classes[0] == .integer and classes[1] == .none) {
|
|
it.zig_index += 1;
|
|
it.llvm_index += 1;
|
|
return .abi_sized_int;
|
|
}
|
|
it.llvm_types_buffer = llvm_types_buffer;
|
|
it.llvm_types_len = llvm_types_index;
|
|
it.llvm_index += llvm_types_index;
|
|
it.zig_index += 1;
|
|
return .multiple_llvm_types;
|
|
}
|
|
};
|
|
|
|
fn iterateParamTypes(object: *Object, fn_info: InternPool.Key.FuncType) ParamTypeIterator {
|
|
return .{
|
|
.object = object,
|
|
.fn_info = fn_info,
|
|
.zig_index = 0,
|
|
.llvm_index = 0,
|
|
.llvm_types_buffer = undefined,
|
|
.llvm_types_len = 0,
|
|
.byval_attr = false,
|
|
};
|
|
}
|
|
|
|
fn ccAbiPromoteInt(
|
|
cc: std.builtin.CallingConvention,
|
|
mod: *Module,
|
|
ty: Type,
|
|
) ?std.builtin.Signedness {
|
|
const target = mod.getTarget();
|
|
switch (cc) {
|
|
.Unspecified, .Inline, .Async => return null,
|
|
else => {},
|
|
}
|
|
const int_info = switch (ty.zigTypeTag(mod)) {
|
|
.Bool => Type.u1.intInfo(mod),
|
|
.Int, .Enum, .ErrorSet => ty.intInfo(mod),
|
|
else => return null,
|
|
};
|
|
if (int_info.bits <= 16) return int_info.signedness;
|
|
switch (target.cpu.arch) {
|
|
.riscv64 => {
|
|
if (int_info.bits == 32) {
|
|
// LLVM always signextends 32 bit ints, unsure if bug.
|
|
return .signed;
|
|
}
|
|
if (int_info.bits < 64) {
|
|
return int_info.signedness;
|
|
}
|
|
},
|
|
.sparc64,
|
|
.powerpc64,
|
|
.powerpc64le,
|
|
=> {
|
|
if (int_info.bits < 64) {
|
|
return int_info.signedness;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// This is the one source of truth for whether a type is passed around as an LLVM pointer,
|
|
/// or as an LLVM value.
|
|
fn isByRef(ty: Type, mod: *Module) bool {
|
|
// For tuples and structs, if there are more than this many non-void
|
|
// fields, then we make it byref, otherwise byval.
|
|
const max_fields_byval = 0;
|
|
|
|
switch (ty.zigTypeTag(mod)) {
|
|
.Type,
|
|
.ComptimeInt,
|
|
.ComptimeFloat,
|
|
.EnumLiteral,
|
|
.Undefined,
|
|
.Null,
|
|
.Opaque,
|
|
=> unreachable,
|
|
|
|
.NoReturn,
|
|
.Void,
|
|
.Bool,
|
|
.Int,
|
|
.Float,
|
|
.Pointer,
|
|
.ErrorSet,
|
|
.Fn,
|
|
.Enum,
|
|
.Vector,
|
|
.AnyFrame,
|
|
=> return false,
|
|
|
|
.Array, .Frame => return ty.hasRuntimeBits(mod),
|
|
.Struct => {
|
|
// Packed structs are represented to LLVM as integers.
|
|
if (ty.containerLayout(mod) == .Packed) return false;
|
|
const struct_type = switch (mod.intern_pool.indexToKey(ty.toIntern())) {
|
|
.anon_struct_type => |tuple| {
|
|
var count: usize = 0;
|
|
for (tuple.types, tuple.values) |field_ty, field_val| {
|
|
if (field_val != .none or !field_ty.toType().hasRuntimeBits(mod)) continue;
|
|
|
|
count += 1;
|
|
if (count > max_fields_byval) return true;
|
|
if (isByRef(field_ty.toType(), mod)) return true;
|
|
}
|
|
return false;
|
|
},
|
|
.struct_type => |s| s,
|
|
else => unreachable,
|
|
};
|
|
const struct_obj = mod.structPtrUnwrap(struct_type.index).?;
|
|
var count: usize = 0;
|
|
for (struct_obj.fields.values()) |field| {
|
|
if (field.is_comptime or !field.ty.hasRuntimeBits(mod)) continue;
|
|
|
|
count += 1;
|
|
if (count > max_fields_byval) return true;
|
|
if (isByRef(field.ty, mod)) return true;
|
|
}
|
|
return false;
|
|
},
|
|
.Union => switch (ty.containerLayout(mod)) {
|
|
.Packed => return false,
|
|
else => return ty.hasRuntimeBits(mod),
|
|
},
|
|
.ErrorUnion => {
|
|
const payload_ty = ty.errorUnionPayload(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
.Optional => {
|
|
const payload_ty = ty.optionalChild(mod);
|
|
if (!payload_ty.hasRuntimeBitsIgnoreComptime(mod)) {
|
|
return false;
|
|
}
|
|
if (ty.optionalReprIsPayload(mod)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn isScalar(mod: *Module, ty: Type) bool {
|
|
return switch (ty.zigTypeTag(mod)) {
|
|
.Void,
|
|
.Bool,
|
|
.NoReturn,
|
|
.Int,
|
|
.Float,
|
|
.Pointer,
|
|
.Optional,
|
|
.ErrorSet,
|
|
.Enum,
|
|
.AnyFrame,
|
|
.Vector,
|
|
=> true,
|
|
|
|
.Struct => ty.containerLayout(mod) == .Packed,
|
|
.Union => ty.containerLayout(mod) == .Packed,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// This function returns true if we expect LLVM to lower x86_fp80 correctly
|
|
/// and false if we expect LLVM to crash if it counters an x86_fp80 type.
|
|
fn backendSupportsF80(target: std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.x86_64, .x86 => !std.Target.x86.featureSetHas(target.cpu.features, .soft_float),
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
/// This function returns true if we expect LLVM to lower f16 correctly
|
|
/// and false if we expect LLVM to crash if it counters an f16 type or
|
|
/// if it produces miscompilations.
|
|
fn backendSupportsF16(target: std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.powerpc,
|
|
.powerpcle,
|
|
.powerpc64,
|
|
.powerpc64le,
|
|
.wasm32,
|
|
.wasm64,
|
|
.mips,
|
|
.mipsel,
|
|
.mips64,
|
|
.mips64el,
|
|
=> false,
|
|
.aarch64 => std.Target.aarch64.featureSetHas(target.cpu.features, .fp_armv8),
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
/// This function returns true if we expect LLVM to lower f128 correctly,
|
|
/// and false if we expect LLVm to crash if it encounters and f128 type
|
|
/// or if it produces miscompilations.
|
|
fn backendSupportsF128(target: std.Target) bool {
|
|
return switch (target.cpu.arch) {
|
|
.amdgcn => false,
|
|
.aarch64 => std.Target.aarch64.featureSetHas(target.cpu.features, .fp_armv8),
|
|
else => true,
|
|
};
|
|
}
|
|
|
|
/// LLVM does not support all relevant intrinsics for all targets, so we
|
|
/// may need to manually generate a libc call
|
|
fn intrinsicsAllowed(scalar_ty: Type, target: std.Target) bool {
|
|
return switch (scalar_ty.toIntern()) {
|
|
.f16_type => backendSupportsF16(target),
|
|
.f80_type => (target.c_type_bit_size(.longdouble) == 80) and backendSupportsF80(target),
|
|
.f128_type => (target.c_type_bit_size(.longdouble) == 128) and backendSupportsF128(target),
|
|
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
|
|
const optional_layout_version = 3;
|
|
|
|
/// We use the least significant bit of the pointer address to tell us
|
|
/// whether the type is fully resolved. Types that are only fwd declared
|
|
/// have the LSB flipped to a 1.
|
|
const AnnotatedDITypePtr = enum(usize) {
|
|
_,
|
|
|
|
fn initFwd(di_type: *llvm.DIType) AnnotatedDITypePtr {
|
|
const addr = @intFromPtr(di_type);
|
|
assert(@as(u1, @truncate(addr)) == 0);
|
|
return @as(AnnotatedDITypePtr, @enumFromInt(addr | 1));
|
|
}
|
|
|
|
fn initFull(di_type: *llvm.DIType) AnnotatedDITypePtr {
|
|
const addr = @intFromPtr(di_type);
|
|
return @as(AnnotatedDITypePtr, @enumFromInt(addr));
|
|
}
|
|
|
|
fn init(di_type: *llvm.DIType, resolve: Object.DebugResolveStatus) AnnotatedDITypePtr {
|
|
const addr = @intFromPtr(di_type);
|
|
const bit = @intFromBool(resolve == .fwd);
|
|
return @as(AnnotatedDITypePtr, @enumFromInt(addr | bit));
|
|
}
|
|
|
|
fn toDIType(self: AnnotatedDITypePtr) *llvm.DIType {
|
|
const fixed_addr = @intFromEnum(self) & ~@as(usize, 1);
|
|
return @as(*llvm.DIType, @ptrFromInt(fixed_addr));
|
|
}
|
|
|
|
fn isFwdOnly(self: AnnotatedDITypePtr) bool {
|
|
return @as(u1, @truncate(@intFromEnum(self))) != 0;
|
|
}
|
|
};
|
|
|
|
const lt_errors_fn_name = "__zig_lt_errors_len";
|
|
|
|
/// Without this workaround, LLVM crashes with "unknown codeview register H1"
|
|
/// https://github.com/llvm/llvm-project/issues/56484
|
|
fn needDbgVarWorkaround(o: *Object) bool {
|
|
const target = o.module.getTarget();
|
|
if (target.os.tag == .windows and target.cpu.arch == .aarch64) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn compilerRtIntBits(bits: u16) u16 {
|
|
inline for (.{ 32, 64, 128 }) |b| {
|
|
if (bits <= b) {
|
|
return b;
|
|
}
|
|
}
|
|
return bits;
|
|
}
|
|
|
|
fn buildAllocaInner(
|
|
context: *llvm.Context,
|
|
builder: *llvm.Builder,
|
|
llvm_func: *llvm.Value,
|
|
di_scope_non_null: bool,
|
|
llvm_ty: *llvm.Type,
|
|
maybe_alignment: ?c_uint,
|
|
target: std.Target,
|
|
) *llvm.Value {
|
|
const address_space = llvmAllocaAddressSpace(target);
|
|
|
|
const alloca = blk: {
|
|
const prev_block = builder.getInsertBlock();
|
|
const prev_debug_location = builder.getCurrentDebugLocation2();
|
|
defer {
|
|
builder.positionBuilderAtEnd(prev_block);
|
|
if (di_scope_non_null) {
|
|
builder.setCurrentDebugLocation2(prev_debug_location);
|
|
}
|
|
}
|
|
|
|
const entry_block = llvm_func.getFirstBasicBlock().?;
|
|
if (entry_block.getFirstInstruction()) |first_inst| {
|
|
builder.positionBuilder(entry_block, first_inst);
|
|
} else {
|
|
builder.positionBuilderAtEnd(entry_block);
|
|
}
|
|
builder.clearCurrentDebugLocation();
|
|
|
|
break :blk builder.buildAllocaInAddressSpace(llvm_ty, address_space, "");
|
|
};
|
|
|
|
if (maybe_alignment) |alignment| {
|
|
alloca.setAlignment(alignment);
|
|
}
|
|
|
|
// The pointer returned from this function should have the generic address space,
|
|
// if this isn't the case then cast it to the generic address space.
|
|
if (address_space != llvm.address_space.default) {
|
|
return builder.buildAddrSpaceCast(alloca, context.pointerType(llvm.address_space.default), "");
|
|
}
|
|
|
|
return alloca;
|
|
}
|
|
|
|
fn errUnionPayloadOffset(payload_ty: Type, mod: *Module) u1 {
|
|
return @intFromBool(Type.anyerror.abiAlignment(mod) > payload_ty.abiAlignment(mod));
|
|
}
|
|
|
|
fn errUnionErrorOffset(payload_ty: Type, mod: *Module) u1 {
|
|
return @intFromBool(Type.anyerror.abiAlignment(mod) <= payload_ty.abiAlignment(mod));
|
|
}
|
|
|
|
/// Returns true for asm constraint (e.g. "=*m", "=r") if it accepts a memory location
|
|
///
|
|
/// See also TargetInfo::validateOutputConstraint, AArch64TargetInfo::validateAsmConstraint, etc. in Clang
|
|
fn constraintAllowsMemory(constraint: []const u8) bool {
|
|
// TODO: This implementation is woefully incomplete.
|
|
for (constraint) |byte| {
|
|
switch (byte) {
|
|
'=', '*', ',', '&' => {},
|
|
'm', 'o', 'X', 'g' => return true,
|
|
else => {},
|
|
}
|
|
} else return false;
|
|
}
|
|
|
|
/// Returns true for asm constraint (e.g. "=*m", "=r") if it accepts a register
|
|
///
|
|
/// See also TargetInfo::validateOutputConstraint, AArch64TargetInfo::validateAsmConstraint, etc. in Clang
|
|
fn constraintAllowsRegister(constraint: []const u8) bool {
|
|
// TODO: This implementation is woefully incomplete.
|
|
for (constraint) |byte| {
|
|
switch (byte) {
|
|
'=', '*', ',', '&' => {},
|
|
'm', 'o' => {},
|
|
else => return true,
|
|
}
|
|
} else return false;
|
|
}
|