zig/src/codegen/llvm.zig
2022-01-08 14:30:11 -05:00

5012 lines
216 KiB
Zig

const std = @import("std");
const builtin = @import("builtin");
const assert = std.debug.assert;
const Allocator = std.mem.Allocator;
const Compilation = @import("../Compilation.zig");
const llvm = @import("llvm/bindings.zig");
const link = @import("../link.zig");
const log = std.log.scoped(.codegen);
const math = std.math;
const native_endian = builtin.cpu.arch.endian();
const build_options = @import("build_options");
const Module = @import("../Module.zig");
const TypedValue = @import("../TypedValue.zig");
const Zir = @import("../Zir.zig");
const Air = @import("../Air.zig");
const Liveness = @import("../Liveness.zig");
const target_util = @import("../target.zig");
const Value = @import("../value.zig").Value;
const Type = @import("../type.zig").Type;
const LazySrcLoc = Module.LazySrcLoc;
const Error = error{ OutOfMemory, CodegenFail };
pub fn targetTriple(allocator: Allocator, target: std.Target) ![:0]u8 {
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",
.hexagon => "hexagon",
.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",
.sparcv9 => "sparcv9",
.sparcel => "sparcel",
.s390x => "s390x",
.tce => "tce",
.tcele => "tcele",
.thumb => "thumb",
.thumbeb => "thumbeb",
.i386 => "i386",
.x86_64 => "x86_64",
.xcore => "xcore",
.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 => return error.@"LLVM backend does not support SPU Mark II",
.spirv32 => return error.@"LLVM backend does not support SPIR-V",
.spirv64 => return error.@"LLVM backend does not support SPIR-V",
};
const llvm_os = switch (target.os.tag) {
.freestanding => "unknown",
.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",
.windows => "windows",
.zos => "zos",
.haiku => "haiku",
.minix => "minix",
.rtems => "rtems",
.nacl => "nacl",
.aix => "aix",
.cuda => "cuda",
.nvcl => "nvcl",
.amdhsa => "amdhsa",
.ps4 => "ps4",
.elfiamcu => "elfiamcu",
.tvos => "tvos",
.watchos => "watchos",
.mesa3d => "mesa3d",
.contiki => "contiki",
.amdpal => "amdpal",
.hermit => "hermit",
.hurd => "hurd",
.wasi => "wasi",
.emscripten => "emscripten",
.uefi => "windows",
.opencl,
.glsl450,
.vulkan,
.plan9,
.other,
=> "unknown",
};
const llvm_abi = switch (target.abi) {
.none => "unknown",
.gnu => "gnu",
.gnuabin32 => "gnuabin32",
.gnuabi64 => "gnuabi64",
.gnueabi => "gnueabi",
.gnueabihf => "gnueabihf",
.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",
};
return std.fmt.allocPrintZ(allocator, "{s}-unknown-{s}-{s}", .{ llvm_arch, llvm_os, llvm_abi });
}
pub const Object = struct {
llvm_module: *const llvm.Module,
context: *const llvm.Context,
target_machine: *const llvm.TargetMachine,
/// 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(*const Module.Decl, *const llvm.Value),
/// Maps Zig types to LLVM types. The table memory itself is backed by the GPA of
/// the compiler, but the Type/Value memory here is backed by `type_map_arena`.
/// TODO we need to remove entries from this map in response to incremental compilation
/// but I think the frontend won't tell us about types that get deleted because
/// hasCodeGenBits() is false for types.
type_map: TypeMap,
/// The backing memory for `type_map`. Periodically garbage collected after flush().
/// The code for doing the periodical GC is not yet implemented.
type_map_arena: std.heap.ArenaAllocator,
/// 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: ?*const llvm.Value,
pub const TypeMap = std.HashMapUnmanaged(
Type,
*const llvm.Type,
Type.HashContext64,
std.hash_map.default_max_load_percentage,
);
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 root_nameZ = try gpa.dupeZ(u8, options.root_name);
defer gpa.free(root_nameZ);
const llvm_module = llvm.Module.createWithName(root_nameZ.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: *const 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;
}
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();
defer target_data.dispose();
llvm_module.setModuleDataLayout(target_data);
return Object{
.llvm_module = llvm_module,
.context = context,
.target_machine = target_machine,
.decl_map = .{},
.type_map = .{},
.type_map_arena = std.heap.ArenaAllocator.init(gpa),
.error_name_table = null,
};
}
pub fn deinit(self: *Object, gpa: Allocator) void {
self.target_machine.dispose();
self.llvm_module.dispose();
self.context.dispose();
self.decl_map.deinit(gpa);
self.type_map.deinit(gpa);
self.type_map_arena.deinit();
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(self: *Object, comp: *Compilation) !void {
// If self.error_name_table is null, there was no instruction that actually referenced the error table.
const error_name_table_ptr_global = self.error_name_table orelse return;
const mod = comp.bin_file.options.module.?;
const target = mod.getTarget();
const llvm_ptr_ty = self.context.intType(8).pointerType(0); // TODO: Address space
const llvm_usize_ty = self.context.intType(target.cpu.arch.ptrBitWidth());
const type_fields = [_]*const llvm.Type{
llvm_ptr_ty,
llvm_usize_ty,
};
const llvm_slice_ty = self.context.structType(&type_fields, type_fields.len, .False);
const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
const slice_alignment = slice_ty.abiAlignment(target);
const error_name_list = mod.error_name_list.items;
const llvm_errors = try comp.gpa.alloc(*const llvm.Value, error_name_list.len);
defer comp.gpa.free(llvm_errors);
llvm_errors[0] = llvm_slice_ty.getUndef();
for (llvm_errors[1..]) |*llvm_error, i| {
const name = error_name_list[1..][i];
const str_init = self.context.constString(name.ptr, @intCast(c_uint, name.len), .False);
const str_global = self.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 = [_]*const llvm.Value{
str_global.constBitCast(llvm_ptr_ty),
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, @intCast(c_uint, error_name_list.len));
const error_name_table_global = self.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.constBitCast(llvm_slice_ty.pointerType(0)); // TODO: Address space
error_name_table_ptr_global.setInitializer(error_name_table_ptr);
}
pub fn flushModule(self: *Object, comp: *Compilation) !void {
try self.genErrorNameTable(comp);
if (comp.verbose_llvm_ir) {
self.llvm_module.dump();
}
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});
@panic("LLVM module verification failed");
}
}
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;
const 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);
const 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,
});
var error_message: [*:0]const u8 = undefined;
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(
self: *Object,
module: *Module,
func: *Module.Fn,
air: Air,
liveness: Liveness,
) !void {
const decl = func.owner_decl;
var dg: DeclGen = .{
.context = self.context,
.object = self,
.module = module,
.decl = decl,
.err_msg = null,
.gpa = module.gpa,
};
const llvm_func = try dg.resolveLlvmFunction(decl);
if (module.align_stack_fns.get(func)) |align_info| {
dg.addFnAttrInt(llvm_func, "alignstack", align_info.alignment);
dg.addFnAttr(llvm_func, "noinline");
} else {
DeclGen.removeFnAttr(llvm_func, "alignstack");
if (!func.is_noinline) DeclGen.removeFnAttr(llvm_func, "noinline");
}
if (func.is_cold) {
dg.addFnAttr(llvm_func, "cold");
} else {
DeclGen.removeFnAttr(llvm_func, "cold");
}
// This gets the LLVM values from the function and stores them in `dg.args`.
const fn_info = decl.ty.fnInfo();
const ret_ty_by_ref = isByRef(fn_info.return_type);
const ret_ptr = if (ret_ty_by_ref) llvm_func.getParam(0) else null;
var args = std.ArrayList(*const llvm.Value).init(dg.gpa);
defer args.deinit();
const param_offset: c_uint = @boolToInt(ret_ptr != null);
for (fn_info.param_types) |param_ty| {
if (!param_ty.hasCodeGenBits()) continue;
const llvm_arg_i = @intCast(c_uint, args.items.len) + param_offset;
try args.append(llvm_func.getParam(llvm_arg_i));
}
// 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 = dg.context.createBuilder();
const entry_block = dg.context.appendBasicBlock(llvm_func, "Entry");
builder.positionBuilderAtEnd(entry_block);
var fg: FuncGen = .{
.gpa = dg.gpa,
.air = air,
.liveness = liveness,
.context = dg.context,
.dg = &dg,
.builder = builder,
.ret_ptr = ret_ptr,
.args = args.toOwnedSlice(),
.arg_index = 0,
.func_inst_table = .{},
.llvm_func = llvm_func,
.blocks = .{},
.single_threaded = module.comp.bin_file.options.single_threaded,
};
defer fg.deinit();
fg.genBody(air.getMainBody()) catch |err| switch (err) {
error.CodegenFail => {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, dg.err_msg.?);
dg.err_msg = null;
return;
},
else => |e| return e,
};
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
try self.updateDeclExports(module, decl, decl_exports);
}
pub fn updateDecl(self: *Object, module: *Module, decl: *Module.Decl) !void {
var dg: DeclGen = .{
.context = self.context,
.object = self,
.module = module,
.decl = decl,
.err_msg = null,
.gpa = module.gpa,
};
dg.genDecl() catch |err| switch (err) {
error.CodegenFail => {
decl.analysis = .codegen_failure;
try module.failed_decls.put(module.gpa, decl, dg.err_msg.?);
dg.err_msg = null;
return;
},
else => |e| return e,
};
const decl_exports = module.decl_exports.get(decl) orelse &[0]*Module.Export{};
try self.updateDeclExports(module, decl, decl_exports);
}
pub fn updateDeclExports(
self: *Object,
module: *const Module,
decl: *const Module.Decl,
exports: []const *Module.Export,
) !void {
// 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) orelse return;
const is_extern = decl.isExtern();
if (is_extern) {
llvm_global.setValueName(decl.name);
llvm_global.setUnnamedAddr(.False);
llvm_global.setLinkage(.External);
} else if (exports.len != 0) {
const exp_name = exports[0].options.name;
llvm_global.setValueName2(exp_name.ptr, exp_name.len);
llvm_global.setUnnamedAddr(.False);
switch (exports[0].options.linkage) {
.Internal => unreachable,
.Strong => llvm_global.setLinkage(.External),
.Weak => llvm_global.setLinkage(.WeakODR),
.LinkOnce => llvm_global.setLinkage(.LinkOnceODR),
}
// 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. We need to
// patch it to support this or figure out how to wrap the C++ API ourselves.
// 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 = try module.gpa.dupeZ(u8, exp.options.name);
defer module.gpa.free(exp_name_z);
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.typeOf(),
llvm_global,
exp_name_z,
);
}
}
} else {
const fqn = try decl.getFullyQualifiedName(module.gpa);
defer module.gpa.free(fqn);
llvm_global.setValueName2(fqn.ptr, fqn.len);
llvm_global.setLinkage(.Internal);
llvm_global.setUnnamedAddr(.True);
}
}
pub fn freeDecl(self: *Object, decl: *Module.Decl) void {
const llvm_value = self.decl_map.get(decl) orelse return;
llvm_value.deleteGlobal();
}
};
pub const DeclGen = struct {
context: *const llvm.Context,
object: *Object,
module: *Module,
decl: *Module.Decl,
gpa: Allocator,
err_msg: ?*Module.ErrorMsg,
fn todo(self: *DeclGen, comptime format: []const u8, args: anytype) Error {
@setCold(true);
assert(self.err_msg == null);
const src_loc = @as(LazySrcLoc, .{ .node_offset = 0 }).toSrcLoc(self.decl);
self.err_msg = try Module.ErrorMsg.create(self.gpa, src_loc, "TODO (LLVM): " ++ format, args);
return error.CodegenFail;
}
fn llvmModule(self: *DeclGen) *const llvm.Module {
return self.object.llvm_module;
}
fn genDecl(dg: *DeclGen) !void {
const decl = dg.decl;
assert(decl.has_tv);
log.debug("gen: {s} type: {}, value: {}", .{ decl.name, decl.ty, decl.val });
if (decl.val.castTag(.function)) |func_payload| {
_ = func_payload;
@panic("TODO llvm backend genDecl function pointer");
} else if (decl.val.castTag(.extern_fn)) |extern_fn| {
_ = try dg.resolveLlvmFunction(extern_fn.data);
} else {
const target = dg.module.getTarget();
const global = try dg.resolveGlobalDecl(decl);
global.setAlignment(decl.getAlignment(target));
assert(decl.has_tv);
const init_val = if (decl.val.castTag(.variable)) |payload| init_val: {
const variable = payload.data;
break :init_val variable.init;
} else init_val: {
global.setGlobalConstant(.True);
break :init_val decl.val;
};
if (init_val.tag() != .unreachable_value) {
const llvm_init = try dg.genTypedValue(.{ .ty = decl.ty, .val = init_val });
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.
const new_global = dg.object.llvm_module.addGlobalInAddressSpace(
llvm_init.typeOf(),
"",
dg.llvmAddressSpace(decl.@"addrspace"),
);
new_global.setLinkage(global.getLinkage());
new_global.setUnnamedAddr(global.getUnnamedAddress());
new_global.setAlignment(global.getAlignment());
new_global.setInitializer(llvm_init);
global.replaceAllUsesWith(new_global);
new_global.takeName(global);
global.deleteGlobal();
}
}
}
}
/// 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(dg: *DeclGen, decl: *Module.Decl) !*const llvm.Value {
const gop = try dg.object.decl_map.getOrPut(dg.gpa, decl);
if (gop.found_existing) return gop.value_ptr.*;
assert(decl.has_tv);
const zig_fn_type = decl.ty;
const fn_info = zig_fn_type.fnInfo();
const target = dg.module.getTarget();
const sret = firstParamSRet(fn_info, target);
const return_type = fn_info.return_type;
const raw_llvm_ret_ty = try dg.llvmType(return_type);
const fn_type = try dg.llvmType(zig_fn_type);
const fqn = try decl.getFullyQualifiedName(dg.gpa);
defer dg.gpa.free(fqn);
const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace");
const llvm_fn = dg.llvmModule().addFunctionInAddressSpace(fqn, fn_type, llvm_addrspace);
gop.value_ptr.* = llvm_fn;
const is_extern = decl.val.tag() == .extern_fn;
if (!is_extern) {
llvm_fn.setLinkage(.Internal);
llvm_fn.setUnnamedAddr(.True);
}
if (sret) {
dg.addArgAttr(llvm_fn, 0, "nonnull"); // Sret pointers must not be address 0
dg.addArgAttr(llvm_fn, 0, "noalias");
llvm_fn.addSretAttr(0, raw_llvm_ret_ty);
}
// Set parameter attributes.
var llvm_param_i: c_uint = @boolToInt(sret);
for (fn_info.param_types) |param_ty| {
if (!param_ty.hasCodeGenBits()) continue;
if (isByRef(param_ty)) {
dg.addArgAttr(llvm_fn, llvm_param_i, "nonnull");
// TODO readonly, noalias, align
}
llvm_param_i += 1;
}
// TODO: more attributes. see codegen.cpp `make_fn_llvm_value`.
if (fn_info.cc == .Naked) {
dg.addFnAttr(llvm_fn, "naked");
} else {
llvm_fn.setFunctionCallConv(toLlvmCallConv(fn_info.cc, target));
}
// Function attributes that are independent of analysis results of the function body.
dg.addCommonFnAttributes(llvm_fn);
if (return_type.isNoReturn()) {
dg.addFnAttr(llvm_fn, "noreturn");
}
return llvm_fn;
}
fn addCommonFnAttributes(dg: *DeclGen, llvm_fn: *const llvm.Value) void {
if (!dg.module.comp.bin_file.options.red_zone) {
dg.addFnAttr(llvm_fn, "noredzone");
}
if (dg.module.comp.bin_file.options.omit_frame_pointer) {
dg.addFnAttrString(llvm_fn, "frame-pointer", "none");
} else {
dg.addFnAttrString(llvm_fn, "frame-pointer", "all");
}
dg.addFnAttr(llvm_fn, "nounwind");
if (dg.module.comp.unwind_tables) {
dg.addFnAttr(llvm_fn, "uwtable");
}
if (dg.module.comp.bin_file.options.skip_linker_dependencies) {
// 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.
dg.addFnAttr(llvm_fn, "nobuiltin");
}
if (dg.module.comp.bin_file.options.optimize_mode == .ReleaseSmall) {
dg.addFnAttr(llvm_fn, "minsize");
dg.addFnAttr(llvm_fn, "optsize");
}
if (dg.module.comp.bin_file.options.tsan) {
dg.addFnAttr(llvm_fn, "sanitize_thread");
}
// TODO add target-cpu and target-features fn attributes
}
fn resolveGlobalDecl(dg: *DeclGen, decl: *Module.Decl) Error!*const llvm.Value {
const gop = try dg.object.decl_map.getOrPut(dg.gpa, decl);
if (gop.found_existing) return gop.value_ptr.*;
errdefer assert(dg.object.decl_map.remove(decl));
const fqn = try decl.getFullyQualifiedName(dg.gpa);
defer dg.gpa.free(fqn);
const llvm_type = try dg.llvmType(decl.ty);
const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace");
const llvm_global = dg.object.llvm_module.addGlobalInAddressSpace(llvm_type, fqn, llvm_addrspace);
gop.value_ptr.* = llvm_global;
const is_extern = decl.val.tag() == .unreachable_value;
if (!is_extern) {
llvm_global.setLinkage(.Internal);
llvm_global.setUnnamedAddr(.True);
}
return llvm_global;
}
fn llvmAddressSpace(self: DeclGen, address_space: std.builtin.AddressSpace) c_uint {
const target = self.module.getTarget();
return switch (target.cpu.arch) {
.i386, .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 => switch (address_space) {
.generic => llvm.address_space.default,
else => unreachable,
},
};
}
fn llvmType(dg: *DeclGen, t: Type) Error!*const llvm.Type {
const gpa = dg.gpa;
switch (t.zigTypeTag()) {
.Void, .NoReturn => return dg.context.voidType(),
.Int => {
const info = t.intInfo(dg.module.getTarget());
return dg.context.intType(info.bits);
},
.Enum => {
var buffer: Type.Payload.Bits = undefined;
const int_ty = t.intTagType(&buffer);
const bit_count = int_ty.intInfo(dg.module.getTarget()).bits;
return dg.context.intType(bit_count);
},
.Float => switch (t.floatBits(dg.module.getTarget())) {
16 => return dg.context.halfType(),
32 => return dg.context.floatType(),
64 => return dg.context.doubleType(),
80 => return dg.context.x86FP80Type(),
128 => return dg.context.fp128Type(),
else => unreachable,
},
.Bool => return dg.context.intType(1),
.Pointer => {
if (t.isSlice()) {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_type = t.slicePtrFieldType(&buf);
const fields: [2]*const llvm.Type = .{
try dg.llvmType(ptr_type),
try dg.llvmType(Type.usize),
};
return dg.context.structType(&fields, fields.len, .False);
}
const llvm_addrspace = dg.llvmAddressSpace(t.ptrAddressSpace());
const elem_ty = t.childType();
const llvm_elem_ty = if (elem_ty.hasCodeGenBits() or elem_ty.zigTypeTag() == .Array)
try dg.llvmType(elem_ty)
else
dg.context.intType(8);
return llvm_elem_ty.pointerType(llvm_addrspace);
},
.Opaque => switch (t.tag()) {
.@"opaque" => {
const gop = try dg.object.type_map.getOrPut(gpa, t);
if (gop.found_existing) return gop.value_ptr.*;
// The Type memory is ephemeral; since we want to store a longer-lived
// reference, we need to copy it here.
gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator());
const opaque_obj = t.castTag(.@"opaque").?.data;
const name = try opaque_obj.getFullyQualifiedName(gpa);
defer gpa.free(name);
const llvm_struct_ty = dg.context.structCreateNamed(name);
gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
return llvm_struct_ty;
},
.anyopaque => return dg.context.intType(8),
else => unreachable,
},
.Array => {
const elem_type = try dg.llvmType(t.childType());
const total_len = t.arrayLen() + @boolToInt(t.sentinel() != null);
return elem_type.arrayType(@intCast(c_uint, total_len));
},
.Vector => {
const elem_type = try dg.llvmType(t.childType());
return elem_type.vectorType(@intCast(c_uint, t.arrayLen()));
},
.Optional => {
var buf: Type.Payload.ElemType = undefined;
const child_type = t.optionalChild(&buf);
if (!child_type.hasCodeGenBits()) {
return dg.context.intType(1);
}
const payload_llvm_ty = try dg.llvmType(child_type);
if (t.isPtrLikeOptional()) {
return payload_llvm_ty;
} else if (!child_type.hasCodeGenBits()) {
return dg.context.intType(1);
}
const fields: [2]*const llvm.Type = .{
payload_llvm_ty, dg.context.intType(1),
};
return dg.context.structType(&fields, fields.len, .False);
},
.ErrorUnion => {
const error_type = t.errorUnionSet();
const payload_type = t.errorUnionPayload();
const llvm_error_type = try dg.llvmType(error_type);
if (!payload_type.hasCodeGenBits()) {
return llvm_error_type;
}
const llvm_payload_type = try dg.llvmType(payload_type);
const fields: [2]*const llvm.Type = .{ llvm_error_type, llvm_payload_type };
return dg.context.structType(&fields, fields.len, .False);
},
.ErrorSet => {
return dg.context.intType(16);
},
.Struct => {
const gop = try dg.object.type_map.getOrPut(gpa, t);
if (gop.found_existing) return gop.value_ptr.*;
// The Type memory is ephemeral; since we want to store a longer-lived
// reference, we need to copy it here.
gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator());
const struct_obj = t.castTag(.@"struct").?.data;
const name = try struct_obj.getFullyQualifiedName(gpa);
defer gpa.free(name);
const llvm_struct_ty = dg.context.structCreateNamed(name);
gop.value_ptr.* = llvm_struct_ty; // must be done before any recursive calls
assert(struct_obj.haveFieldTypes());
var llvm_field_types = try std.ArrayListUnmanaged(*const llvm.Type).initCapacity(gpa, struct_obj.fields.count());
defer llvm_field_types.deinit(gpa);
if (struct_obj.layout == .Packed) {
try llvm_field_types.ensureUnusedCapacity(gpa, struct_obj.fields.count() * 2);
const target = dg.module.getTarget();
comptime assert(Type.packed_struct_layout_version == 1);
var offset: u64 = 0;
var big_align: u32 = 0;
var running_bits: u16 = 0;
for (struct_obj.fields.values()) |field| {
if (!field.ty.hasCodeGenBits()) continue;
const field_align = field.packedAlignment();
if (field_align == 0) {
running_bits += @intCast(u16, field.ty.bitSize(target));
} else {
if (running_bits != 0) {
var int_payload: Type.Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(target);
big_align = @maximum(big_align, int_align);
const llvm_int_ty = try dg.llvmType(int_ty);
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_field_types.appendAssumeCapacity(padding);
}
llvm_field_types.appendAssumeCapacity(llvm_int_ty);
offset += int_ty.abiSize(target);
running_bits = 0;
}
big_align = @maximum(big_align, field_align);
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, field_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_field_types.appendAssumeCapacity(padding);
}
llvm_field_types.appendAssumeCapacity(try dg.llvmType(field.ty));
offset += field.ty.abiSize(target);
}
}
if (running_bits != 0) {
var int_payload: Type.Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(target);
big_align = @maximum(big_align, int_align);
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_field_types.appendAssumeCapacity(padding);
}
const llvm_int_ty = try dg.llvmType(int_ty);
llvm_field_types.appendAssumeCapacity(llvm_int_ty);
}
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, big_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_field_types.appendAssumeCapacity(padding);
}
} else {
for (struct_obj.fields.values()) |field| {
if (!field.ty.hasCodeGenBits()) continue;
llvm_field_types.appendAssumeCapacity(try dg.llvmType(field.ty));
}
}
llvm_struct_ty.structSetBody(
llvm_field_types.items.ptr,
@intCast(c_uint, llvm_field_types.items.len),
.False,
);
return llvm_struct_ty;
},
.Union => {
const gop = try dg.object.type_map.getOrPut(gpa, t);
if (gop.found_existing) return gop.value_ptr.*;
// The Type memory is ephemeral; since we want to store a longer-lived
// reference, we need to copy it here.
gop.key_ptr.* = try t.copy(dg.object.type_map_arena.allocator());
const union_obj = t.cast(Type.Payload.Union).?.data;
const target = dg.module.getTarget();
if (t.unionTagType()) |enum_tag_ty| {
const enum_tag_llvm_ty = try dg.llvmType(enum_tag_ty);
const layout = union_obj.getLayout(target, true);
if (layout.payload_size == 0) {
gop.value_ptr.* = enum_tag_llvm_ty;
return enum_tag_llvm_ty;
}
const name = try union_obj.getFullyQualifiedName(gpa);
defer gpa.free(name);
const llvm_union_ty = dg.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 dg.llvmType(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 = @intCast(c_uint, layout.payload_size - layout.most_aligned_field_size);
const fields: [2]*const llvm.Type = .{
llvm_aligned_field_ty,
dg.context.intType(8).arrayType(padding_len),
};
break :t dg.context.structType(&fields, fields.len, .False);
};
if (layout.tag_size == 0) {
var llvm_fields: [1]*const llvm.Type = .{llvm_payload_ty};
llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False);
return llvm_union_ty;
}
// Put the tag before or after the payload depending on which one's
// alignment is greater.
var llvm_fields: [2]*const llvm.Type = undefined;
if (layout.tag_align >= layout.payload_align) {
llvm_fields[0] = enum_tag_llvm_ty;
llvm_fields[1] = llvm_payload_ty;
} else {
llvm_fields[0] = llvm_payload_ty;
llvm_fields[1] = enum_tag_llvm_ty;
}
llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False);
return llvm_union_ty;
}
// Untagged union
const layout = union_obj.getLayout(target, false);
const name = try union_obj.getFullyQualifiedName(gpa);
defer gpa.free(name);
const llvm_union_ty = dg.context.structCreateNamed(name);
gop.value_ptr.* = llvm_union_ty; // must be done before any recursive calls
const big_field = union_obj.fields.values()[layout.biggest_field];
const llvm_big_field_ty = try dg.llvmType(big_field.ty);
var llvm_fields: [1]*const llvm.Type = .{llvm_big_field_ty};
llvm_union_ty.structSetBody(&llvm_fields, llvm_fields.len, .False);
return llvm_union_ty;
},
.Fn => {
const fn_info = t.fnInfo();
const target = dg.module.getTarget();
const sret = firstParamSRet(fn_info, target);
const return_type = fn_info.return_type;
const raw_llvm_ret_ty = try dg.llvmType(return_type);
const llvm_ret_ty = if (!return_type.hasCodeGenBits() or sret)
dg.context.voidType()
else
raw_llvm_ret_ty;
var llvm_params = std.ArrayList(*const llvm.Type).init(dg.gpa);
defer llvm_params.deinit();
if (sret) {
try llvm_params.append(raw_llvm_ret_ty.pointerType(0));
}
for (fn_info.param_types) |param_ty| {
if (!param_ty.hasCodeGenBits()) continue;
const raw_llvm_ty = try dg.llvmType(param_ty);
const actual_llvm_ty = if (!isByRef(param_ty)) raw_llvm_ty else raw_llvm_ty.pointerType(0);
try llvm_params.append(actual_llvm_ty);
}
return llvm.functionType(
llvm_ret_ty,
llvm_params.items.ptr,
@intCast(c_uint, llvm_params.items.len),
llvm.Bool.fromBool(fn_info.is_var_args),
);
},
.ComptimeInt => unreachable,
.ComptimeFloat => unreachable,
.Type => unreachable,
.Undefined => unreachable,
.Null => unreachable,
.EnumLiteral => unreachable,
.BoundFn => @panic("TODO remove BoundFn from the language"),
.Frame,
.AnyFrame,
=> return dg.todo("implement llvmType for type '{}'", .{t}),
}
}
fn genTypedValue(dg: *DeclGen, tv: TypedValue) Error!*const llvm.Value {
if (tv.val.isUndef()) {
const llvm_type = try dg.llvmType(tv.ty);
return llvm_type.getUndef();
}
switch (tv.ty.zigTypeTag()) {
.Bool => {
const llvm_type = try dg.llvmType(tv.ty);
return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull();
},
.Int => {
var bigint_space: Value.BigIntSpace = undefined;
const bigint = tv.val.toBigInt(&bigint_space);
const target = dg.module.getTarget();
const int_info = tv.ty.intInfo(target);
const llvm_type = dg.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(
@intCast(c_uint, 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;
},
.Enum => {
var int_buffer: Value.Payload.U64 = undefined;
const int_val = tv.enumToInt(&int_buffer);
var bigint_space: Value.BigIntSpace = undefined;
const bigint = int_val.toBigInt(&bigint_space);
const target = dg.module.getTarget();
const int_info = tv.ty.intInfo(target);
const llvm_type = dg.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(
@intCast(c_uint, 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 dg.llvmType(tv.ty);
if (tv.ty.floatBits(dg.module.getTarget()) <= 64) {
return llvm_ty.constReal(tv.val.toFloat(f64));
}
var buf: [2]u64 = @bitCast([2]u64, tv.val.toFloat(f128));
// 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 = dg.context.intType(128).constIntOfArbitraryPrecision(buf.len, &buf);
return int.constBitCast(llvm_ty);
},
.Pointer => switch (tv.val.tag()) {
.decl_ref_mut => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref_mut).?.data.decl),
.decl_ref => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref).?.data),
.variable => {
const decl = tv.val.castTag(.variable).?.data.owner_decl;
dg.markDeclAlive(decl);
const val = try dg.resolveGlobalDecl(decl);
const llvm_var_type = try dg.llvmType(tv.ty);
const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace");
const llvm_type = llvm_var_type.pointerType(llvm_addrspace);
return val.constBitCast(llvm_type);
},
.slice => {
const slice = tv.val.castTag(.slice).?.data;
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const fields: [2]*const llvm.Value = .{
try dg.genTypedValue(.{
.ty = tv.ty.slicePtrFieldType(&buf),
.val = slice.ptr,
}),
try dg.genTypedValue(.{
.ty = Type.usize,
.val = slice.len,
}),
};
return dg.context.constStruct(&fields, fields.len, .False);
},
.int_u64, .one, .int_big_positive => {
const llvm_usize = try dg.llvmType(Type.usize);
const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False);
return llvm_int.constIntToPtr(try dg.llvmType(tv.ty));
},
.field_ptr => {
const field_ptr = tv.val.castTag(.field_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(field_ptr.container_ptr);
const llvm_u32 = dg.context.intType(32);
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(field_ptr.field_index, .False),
};
return parent_ptr.constInBoundsGEP(&indices, indices.len);
},
.elem_ptr => {
const elem_ptr = tv.val.castTag(.elem_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(elem_ptr.array_ptr);
const llvm_usize = try dg.llvmType(Type.usize);
if (parent_ptr.typeOf().getElementType().getTypeKind() == .Array) {
const indices: [2]*const llvm.Value = .{
llvm_usize.constInt(0, .False),
llvm_usize.constInt(elem_ptr.index, .False),
};
return parent_ptr.constInBoundsGEP(&indices, indices.len);
} else {
const indices: [1]*const llvm.Value = .{
llvm_usize.constInt(elem_ptr.index, .False),
};
return parent_ptr.constInBoundsGEP(&indices, indices.len);
}
},
.null_value, .zero => {
const llvm_type = try dg.llvmType(tv.ty);
return llvm_type.constNull();
},
else => |tag| return dg.todo("implement const of pointer type '{}' ({})", .{ tv.ty, tag }),
},
.Array => switch (tv.val.tag()) {
.bytes => {
const bytes = tv.val.castTag(.bytes).?.data;
return dg.context.constString(
bytes.ptr,
@intCast(c_uint, bytes.len),
.True, // don't null terminate. bytes has the sentinel, if any.
);
},
.array => {
const elem_vals = tv.val.castTag(.array).?.data;
const elem_ty = tv.ty.elemType();
const gpa = dg.gpa;
const llvm_elems = try gpa.alloc(*const llvm.Value, elem_vals.len);
defer gpa.free(llvm_elems);
for (elem_vals) |elem_val, i| {
llvm_elems[i] = try dg.genTypedValue(.{ .ty = elem_ty, .val = elem_val });
}
const llvm_elem_ty = try dg.llvmType(elem_ty);
return llvm_elem_ty.constArray(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
);
},
.repeated => {
const val = tv.val.castTag(.repeated).?.data;
const elem_ty = tv.ty.elemType();
const sentinel = tv.ty.sentinel();
const len = @intCast(usize, tv.ty.arrayLen());
const len_including_sent = len + @boolToInt(sentinel != null);
const gpa = dg.gpa;
const llvm_elems = try gpa.alloc(*const llvm.Value, len_including_sent);
defer gpa.free(llvm_elems);
for (llvm_elems[0..len]) |*elem| {
elem.* = try dg.genTypedValue(.{ .ty = elem_ty, .val = val });
}
if (sentinel) |sent| {
llvm_elems[len] = try dg.genTypedValue(.{ .ty = elem_ty, .val = sent });
}
const llvm_elem_ty = try dg.llvmType(elem_ty);
return llvm_elem_ty.constArray(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
);
},
.empty_array_sentinel => {
const elem_ty = tv.ty.elemType();
const sent_val = tv.ty.sentinel().?;
const sentinel = try dg.genTypedValue(.{ .ty = elem_ty, .val = sent_val });
const llvm_elems: [1]*const llvm.Value = .{sentinel};
const llvm_elem_ty = try dg.llvmType(elem_ty);
return llvm_elem_ty.constArray(&llvm_elems, llvm_elems.len);
},
else => unreachable,
},
.Optional => {
var buf: Type.Payload.ElemType = undefined;
const payload_ty = tv.ty.optionalChild(&buf);
const llvm_i1 = dg.context.intType(1);
const is_pl = !tv.val.isNull();
const non_null_bit = if (is_pl) llvm_i1.constAllOnes() else llvm_i1.constNull();
if (!payload_ty.hasCodeGenBits()) {
return non_null_bit;
}
if (tv.ty.isPtrLikeOptional()) {
if (tv.val.castTag(.opt_payload)) |payload| {
return dg.genTypedValue(.{ .ty = payload_ty, .val = payload.data });
} else if (is_pl) {
return dg.genTypedValue(.{ .ty = payload_ty, .val = tv.val });
} else {
const llvm_ty = try dg.llvmType(tv.ty);
return llvm_ty.constNull();
}
}
const fields: [2]*const llvm.Value = .{
try dg.genTypedValue(.{
.ty = payload_ty,
.val = if (tv.val.castTag(.opt_payload)) |pl| pl.data else Value.initTag(.undef),
}),
non_null_bit,
};
return dg.context.constStruct(&fields, fields.len, .False);
},
.Fn => {
const fn_decl = switch (tv.val.tag()) {
.extern_fn => tv.val.castTag(.extern_fn).?.data,
.function => tv.val.castTag(.function).?.data.owner_decl,
else => unreachable,
};
dg.markDeclAlive(fn_decl);
return dg.resolveLlvmFunction(fn_decl);
},
.ErrorSet => {
const llvm_ty = try dg.llvmType(tv.ty);
switch (tv.val.tag()) {
.@"error" => {
const err_name = tv.val.castTag(.@"error").?.data.name;
const kv = try dg.module.getErrorValue(err_name);
return llvm_ty.constInt(kv.value, .False);
},
else => {
// In this case we are rendering an error union which has a 0 bits payload.
return llvm_ty.constNull();
},
}
},
.ErrorUnion => {
const error_type = tv.ty.errorUnionSet();
const payload_type = tv.ty.errorUnionPayload();
const is_pl = tv.val.errorUnionIsPayload();
if (!payload_type.hasCodeGenBits()) {
// We use the error type directly as the type.
const err_val = if (!is_pl) tv.val else Value.initTag(.zero);
return dg.genTypedValue(.{ .ty = error_type, .val = err_val });
}
const fields: [2]*const llvm.Value = .{
try dg.genTypedValue(.{
.ty = error_type,
.val = if (is_pl) Value.initTag(.zero) else tv.val,
}),
try dg.genTypedValue(.{
.ty = payload_type,
.val = if (tv.val.castTag(.eu_payload)) |pl| pl.data else Value.initTag(.undef),
}),
};
return dg.context.constStruct(&fields, fields.len, .False);
},
.Struct => {
const llvm_struct_ty = try dg.llvmType(tv.ty);
const field_vals = tv.val.castTag(.@"struct").?.data;
const gpa = dg.gpa;
const llvm_field_count = llvm_struct_ty.countStructElementTypes();
var llvm_fields = try std.ArrayListUnmanaged(*const llvm.Value).initCapacity(gpa, llvm_field_count);
defer llvm_fields.deinit(gpa);
const struct_obj = tv.ty.castTag(.@"struct").?.data;
if (struct_obj.layout == .Packed) {
const target = dg.module.getTarget();
const fields = struct_obj.fields.values();
comptime assert(Type.packed_struct_layout_version == 1);
var offset: u64 = 0;
var big_align: u32 = 0;
var running_bits: u16 = 0;
var running_int: *const llvm.Value = llvm_struct_ty.structGetTypeAtIndex(0).constNull();
for (field_vals) |field_val, i| {
const field = fields[i];
if (!field.ty.hasCodeGenBits()) continue;
const field_align = field.packedAlignment();
if (field_align == 0) {
const non_int_val = try dg.genTypedValue(.{
.ty = field.ty,
.val = field_val,
});
const ty_bit_size = @intCast(u16, field.ty.bitSize(target));
const small_int_ty = dg.context.intType(ty_bit_size);
const small_int_val = non_int_val.constBitCast(small_int_ty);
const big_int_ty = running_int.typeOf();
const shift_rhs = big_int_ty.constInt(running_bits, .False);
const extended_int_val = small_int_val.constZExt(big_int_ty);
const shifted = extended_int_val.constShl(shift_rhs);
running_int = running_int.constOr(shifted);
running_bits += ty_bit_size;
} else {
big_align = @maximum(big_align, field_align);
if (running_bits != 0) {
var int_payload: Type.Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(target);
big_align = @maximum(big_align, int_align);
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_fields.appendAssumeCapacity(padding.getUndef());
}
llvm_fields.appendAssumeCapacity(running_int);
running_int = llvm_struct_ty.structGetTypeAtIndex(@intCast(c_uint, llvm_fields.items.len)).constNull();
offset += int_ty.abiSize(target);
running_bits = 0;
}
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, field_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_fields.appendAssumeCapacity(padding.getUndef());
}
llvm_fields.appendAssumeCapacity(try dg.genTypedValue(.{
.ty = field.ty,
.val = field_val,
}));
offset += field.ty.abiSize(target);
}
}
if (running_bits != 0) {
var int_payload: Type.Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
const int_align = int_ty.abiAlignment(target);
big_align = @maximum(big_align, int_align);
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_fields.appendAssumeCapacity(padding.getUndef());
}
llvm_fields.appendAssumeCapacity(running_int);
offset += int_ty.abiSize(target);
}
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, big_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
const padding = dg.context.intType(8).arrayType(padding_bytes);
llvm_fields.appendAssumeCapacity(padding.getUndef());
}
} else {
for (field_vals) |field_val, i| {
const field_ty = tv.ty.structFieldType(i);
if (!field_ty.hasCodeGenBits()) continue;
llvm_fields.appendAssumeCapacity(try dg.genTypedValue(.{
.ty = field_ty,
.val = field_val,
}));
}
}
return llvm_struct_ty.constNamedStruct(
llvm_fields.items.ptr,
@intCast(c_uint, llvm_fields.items.len),
);
},
.Union => {
const llvm_union_ty = try dg.llvmType(tv.ty);
const tag_and_val = tv.val.castTag(.@"union").?.data;
const target = dg.module.getTarget();
const layout = tv.ty.unionGetLayout(target);
if (layout.payload_size == 0) {
return genTypedValue(dg, .{
.ty = tv.ty.unionTagType().?,
.val = tag_and_val.tag,
});
}
const union_obj = tv.ty.cast(Type.Payload.Union).?.data;
const field_index = union_obj.tag_ty.enumTagFieldIndex(tag_and_val.tag).?;
assert(union_obj.haveFieldTypes());
const field_ty = union_obj.fields.values()[field_index].ty;
const payload = p: {
if (!field_ty.hasCodeGenBits()) {
const padding_len = @intCast(c_uint, layout.payload_size);
break :p dg.context.intType(8).arrayType(padding_len).getUndef();
}
const field = try genTypedValue(dg, .{ .ty = field_ty, .val = tag_and_val.val });
const field_size = field_ty.abiSize(target);
if (field_size == layout.payload_size) {
break :p field;
}
const padding_len = @intCast(c_uint, layout.payload_size - field_size);
const fields: [2]*const llvm.Value = .{
field, dg.context.intType(8).arrayType(padding_len).getUndef(),
};
break :p dg.context.constStruct(&fields, fields.len, .False);
};
// In this case 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.
const need_unnamed = layout.most_aligned_field != field_index;
if (layout.tag_size == 0) {
const fields: [1]*const llvm.Value = .{payload};
if (need_unnamed) {
return dg.context.constStruct(&fields, fields.len, .False);
} else {
return llvm_union_ty.constNamedStruct(&fields, fields.len);
}
}
const llvm_tag_value = try genTypedValue(dg, .{
.ty = tv.ty.unionTagType().?,
.val = tag_and_val.tag,
});
var fields: [2]*const llvm.Value = undefined;
if (layout.tag_align >= layout.payload_align) {
fields = .{ llvm_tag_value, payload };
} else {
fields = .{ payload, llvm_tag_value };
}
if (need_unnamed) {
return dg.context.constStruct(&fields, fields.len, .False);
} else {
return llvm_union_ty.constNamedStruct(&fields, fields.len);
}
},
.Vector => switch (tv.val.tag()) {
.bytes => {
// Note, sentinel is not stored even if the type has a sentinel.
const bytes = tv.val.castTag(.bytes).?.data;
const vector_len = @intCast(usize, tv.ty.arrayLen());
assert(vector_len == bytes.len or vector_len + 1 == bytes.len);
const elem_ty = tv.ty.elemType();
const llvm_elems = try dg.gpa.alloc(*const llvm.Value, vector_len);
defer dg.gpa.free(llvm_elems);
for (llvm_elems) |*elem, i| {
var byte_payload: Value.Payload.U64 = .{
.base = .{ .tag = .int_u64 },
.data = bytes[i],
};
elem.* = try dg.genTypedValue(.{
.ty = elem_ty,
.val = Value.initPayload(&byte_payload.base),
});
}
return llvm.constVector(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
);
},
.array => {
// Note, sentinel is not stored even if the type has a sentinel.
// The value includes the sentinel in those cases.
const elem_vals = tv.val.castTag(.array).?.data;
const vector_len = @intCast(usize, tv.ty.arrayLen());
assert(vector_len == elem_vals.len or vector_len + 1 == elem_vals.len);
const elem_ty = tv.ty.elemType();
const llvm_elems = try dg.gpa.alloc(*const llvm.Value, vector_len);
defer dg.gpa.free(llvm_elems);
for (llvm_elems) |*elem, i| {
elem.* = try dg.genTypedValue(.{ .ty = elem_ty, .val = elem_vals[i] });
}
return llvm.constVector(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
);
},
.repeated => {
// Note, sentinel is not stored even if the type has a sentinel.
const val = tv.val.castTag(.repeated).?.data;
const elem_ty = tv.ty.elemType();
const len = @intCast(usize, tv.ty.arrayLen());
const llvm_elems = try dg.gpa.alloc(*const llvm.Value, len);
defer dg.gpa.free(llvm_elems);
for (llvm_elems) |*elem| {
elem.* = try dg.genTypedValue(.{ .ty = elem_ty, .val = val });
}
return llvm.constVector(
llvm_elems.ptr,
@intCast(c_uint, llvm_elems.len),
);
},
else => unreachable,
},
.ComptimeInt => unreachable,
.ComptimeFloat => unreachable,
.Type => unreachable,
.EnumLiteral => unreachable,
.Void => unreachable,
.NoReturn => unreachable,
.Undefined => unreachable,
.Null => unreachable,
.BoundFn => unreachable,
.Opaque => unreachable,
.Frame,
.AnyFrame,
=> return dg.todo("implement const of type '{}'", .{tv.ty}),
}
}
const ParentPtr = struct {
ty: Type,
llvm_ptr: *const llvm.Value,
};
fn lowerParentPtrDecl(
dg: *DeclGen,
ptr_val: Value,
decl: *Module.Decl,
) Error!ParentPtr {
dg.markDeclAlive(decl);
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = decl.ty,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
const llvm_ptr = try dg.lowerDeclRefValue(.{ .ty = ptr_ty, .val = ptr_val }, decl);
return ParentPtr{
.llvm_ptr = llvm_ptr,
.ty = decl.ty,
};
}
fn lowerParentPtr(dg: *DeclGen, ptr_val: Value) Error!*const llvm.Value {
switch (ptr_val.tag()) {
.decl_ref_mut => {
const decl = ptr_val.castTag(.decl_ref_mut).?.data.decl;
return (try dg.lowerParentPtrDecl(ptr_val, decl)).llvm_ptr;
},
.decl_ref => {
const decl = ptr_val.castTag(.decl_ref).?.data;
return (try dg.lowerParentPtrDecl(ptr_val, decl)).llvm_ptr;
},
.variable => {
const decl = ptr_val.castTag(.variable).?.data.owner_decl;
return (try dg.lowerParentPtrDecl(ptr_val, decl)).llvm_ptr;
},
.field_ptr => {
const field_ptr = ptr_val.castTag(.field_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(field_ptr.container_ptr);
const llvm_u32 = dg.context.intType(32);
const indices: [2]*const llvm.Value = .{
llvm_u32.constInt(0, .False),
llvm_u32.constInt(field_ptr.field_index, .False),
};
return parent_ptr.constInBoundsGEP(&indices, indices.len);
},
.elem_ptr => {
const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
const parent_ptr = try dg.lowerParentPtr(elem_ptr.array_ptr);
const llvm_usize = try dg.llvmType(Type.usize);
const indices: [2]*const llvm.Value = .{
llvm_usize.constInt(0, .False),
llvm_usize.constInt(elem_ptr.index, .False),
};
return parent_ptr.constInBoundsGEP(&indices, indices.len);
},
.opt_payload_ptr => return dg.todo("implement lowerParentPtr for optional payload", .{}),
.eu_payload_ptr => return dg.todo("implement lowerParentPtr for error union payload", .{}),
else => unreachable,
}
}
fn lowerDeclRefValue(
self: *DeclGen,
tv: TypedValue,
decl: *Module.Decl,
) Error!*const llvm.Value {
if (tv.ty.isSlice()) {
var buf: Type.SlicePtrFieldTypeBuffer = undefined;
const ptr_ty = tv.ty.slicePtrFieldType(&buf);
var slice_len: Value.Payload.U64 = .{
.base = .{ .tag = .int_u64 },
.data = tv.val.sliceLen(),
};
const fields: [2]*const llvm.Value = .{
try self.genTypedValue(.{
.ty = ptr_ty,
.val = tv.val,
}),
try self.genTypedValue(.{
.ty = Type.usize,
.val = Value.initPayload(&slice_len.base),
}),
};
return self.context.constStruct(&fields, fields.len, .False);
}
if (!tv.ty.childType().hasCodeGenBits() or !decl.ty.hasCodeGenBits()) {
return self.lowerPtrToVoid(tv.ty);
}
self.markDeclAlive(decl);
const llvm_val = if (decl.ty.zigTypeTag() == .Fn)
try self.resolveLlvmFunction(decl)
else
try self.resolveGlobalDecl(decl);
const llvm_type = try self.llvmType(tv.ty);
return llvm_val.constBitCast(llvm_type);
}
fn markDeclAlive(dg: *DeclGen, decl: *Module.Decl) void {
if (decl.alive) return;
decl.alive = true;
log.debug("{*} ({s}) marked alive by {*} ({s})", .{
decl, decl.name,
dg.decl, dg.decl.name,
});
// This is the first time we are marking this Decl alive. We must
// therefore recurse into its value and mark any Decl it references
// as also alive, so that any Decl referenced does not get garbage collected.
if (decl.val.pointerDecl()) |pointee| {
return dg.markDeclAlive(pointee);
}
}
fn lowerPtrToVoid(dg: *DeclGen, ptr_ty: Type) !*const llvm.Value {
const target = dg.module.getTarget();
const alignment = ptr_ty.ptrAlignment(target);
// 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 dg.llvmType(Type.usize);
const llvm_ptr_ty = try dg.llvmType(ptr_ty);
if (alignment != 0) {
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 int = switch (target.cpu.arch.ptrBitWidth()) {
32 => llvm_usize.constInt(0xaaaaaaaa, .False),
64 => llvm_usize.constInt(0xaaaaaaaa_aaaaaaaa, .False),
else => unreachable,
};
return int.constIntToPtr(llvm_ptr_ty);
}
fn addAttr(dg: DeclGen, val: *const llvm.Value, index: llvm.AttributeIndex, name: []const u8) void {
return dg.addAttrInt(val, index, name, 0);
}
fn addArgAttr(dg: DeclGen, fn_val: *const llvm.Value, param_index: u32, attr_name: []const u8) void {
return dg.addAttr(fn_val, param_index + 1, attr_name);
}
fn removeAttr(val: *const 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(
dg: DeclGen,
val: *const 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 = dg.context.createEnumAttribute(kind_id, int);
val.addAttributeAtIndex(index, llvm_attr);
}
fn addAttrString(
dg: *DeclGen,
val: *const llvm.Value,
index: llvm.AttributeIndex,
name: []const u8,
value: []const u8,
) void {
const llvm_attr = dg.context.createStringAttribute(
name.ptr,
@intCast(c_uint, name.len),
value.ptr,
@intCast(c_uint, value.len),
);
val.addAttributeAtIndex(index, llvm_attr);
}
fn addFnAttr(dg: DeclGen, val: *const llvm.Value, name: []const u8) void {
dg.addAttr(val, std.math.maxInt(llvm.AttributeIndex), name);
}
fn addFnAttrString(dg: *DeclGen, val: *const llvm.Value, name: []const u8, value: []const u8) void {
dg.addAttrString(val, std.math.maxInt(llvm.AttributeIndex), name, value);
}
fn removeFnAttr(fn_val: *const llvm.Value, name: []const u8) void {
removeAttr(fn_val, std.math.maxInt(llvm.AttributeIndex), name);
}
fn addFnAttrInt(dg: DeclGen, fn_val: *const llvm.Value, name: []const u8, int: u64) void {
return dg.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(dg: *DeclGen, ty: Type, is_rmw_xchg: bool) ?*const llvm.Type {
const target = dg.module.getTarget();
var buffer: Type.Payload.Bits = undefined;
const int_ty = switch (ty.zigTypeTag()) {
.Int => ty,
.Enum => ty.intTagType(&buffer),
.Float => {
if (!is_rmw_xchg) return null;
return dg.context.intType(@intCast(c_uint, ty.abiSize(target) * 8));
},
.Bool => return dg.context.intType(8),
else => return null,
};
const bit_count = int_ty.intInfo(target).bits;
if (!std.math.isPowerOfTwo(bit_count) or (bit_count % 8) != 0) {
return dg.context.intType(@intCast(c_uint, int_ty.abiSize(target) * 8));
} else {
return null;
}
}
};
pub const FuncGen = struct {
gpa: Allocator,
dg: *DeclGen,
air: Air,
liveness: Liveness,
context: *const llvm.Context,
builder: *const llvm.Builder,
/// 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, *const llvm.Value),
/// If the return type isByRef, this is the result pointer. Otherwise null.
ret_ptr: ?*const llvm.Value,
/// 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.
args: []*const llvm.Value,
arg_index: usize,
llvm_func: *const llvm.Value,
/// This data structure is used to implement breaking to blocks.
blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, struct {
parent_bb: *const llvm.BasicBlock,
break_bbs: *BreakBasicBlocks,
break_vals: *BreakValues,
}),
single_threaded: bool,
const BreakBasicBlocks = std.ArrayListUnmanaged(*const llvm.BasicBlock);
const BreakValues = std.ArrayListUnmanaged(*const llvm.Value);
fn deinit(self: *FuncGen) void {
self.builder.dispose();
self.func_inst_table.deinit(self.gpa);
self.gpa.free(self.args);
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 llvmModule(self: *FuncGen) *const llvm.Module {
return self.dg.object.llvm_module;
}
fn resolveInst(self: *FuncGen, inst: Air.Inst.Ref) !*const llvm.Value {
const gop = try self.func_inst_table.getOrPut(self.dg.gpa, inst);
if (gop.found_existing) return gop.value_ptr.*;
const val = self.air.value(inst).?;
const ty = self.air.typeOf(inst);
const llvm_val = try self.dg.genTypedValue(.{ .ty = ty, .val = val });
if (!isByRef(ty)) {
gop.value_ptr.* = llvm_val;
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 = self.dg.module.getTarget();
const global = self.dg.object.llvm_module.addGlobal(llvm_val.typeOf(), "");
global.setInitializer(llvm_val);
global.setLinkage(.Private);
global.setGlobalConstant(.True);
global.setUnnamedAddr(.True);
global.setAlignment(ty.abiAlignment(target));
// Because of LLVM limitations for lowering certain types such as unions,
// the type of global constants might not match the type it is supposed to
// be, and so we must bitcast the pointer at the usage sites.
const wanted_llvm_ty = try self.dg.llvmType(ty);
const wanted_llvm_ptr_ty = wanted_llvm_ty.pointerType(0);
const casted_ptr = global.constBitCast(wanted_llvm_ptr_ty);
gop.value_ptr.* = casted_ptr;
return casted_ptr;
}
fn genBody(self: *FuncGen, body: []const Air.Inst.Index) Error!void {
const air_tags = self.air.instructions.items(.tag);
for (body) |inst| {
const opt_value: ?*const llvm.Value = switch (air_tags[inst]) {
// zig fmt: off
.add => try self.airAdd(inst),
.addwrap => try self.airAddWrap(inst),
.add_sat => try self.airAddSat(inst),
.sub => try self.airSub(inst),
.subwrap => try self.airSubWrap(inst),
.sub_sat => try self.airSubSat(inst),
.mul => try self.airMul(inst),
.mulwrap => try self.airMulWrap(inst),
.mul_sat => try self.airMulSat(inst),
.div_float => try self.airDivFloat(inst),
.div_trunc => try self.airDivTrunc(inst),
.div_floor => try self.airDivFloor(inst),
.div_exact => try self.airDivExact(inst),
.rem => try self.airRem(inst),
.mod => try self.airMod(inst),
.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),
.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),
.cmp_eq => try self.airCmp(inst, .eq),
.cmp_gt => try self.airCmp(inst, .gt),
.cmp_gte => try self.airCmp(inst, .gte),
.cmp_lt => try self.airCmp(inst, .lt),
.cmp_lte => try self.airCmp(inst, .lte),
.cmp_neq => try self.airCmp(inst, .neq),
.is_non_null => try self.airIsNonNull(inst, false, false, .NE),
.is_non_null_ptr => try self.airIsNonNull(inst, true , false, .NE),
.is_null => try self.airIsNonNull(inst, false, true , .EQ),
.is_null_ptr => try self.airIsNonNull(inst, true , 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),
.bool_to_int => try self.airBoolToInt(inst),
.block => try self.airBlock(inst),
.br => try self.airBr(inst),
.switch_br => try self.airSwitchBr(inst),
.breakpoint => try self.airBreakpoint(inst),
.ret_addr => try self.airRetAddr(inst),
.call => try self.airCall(inst),
.cond_br => try self.airCondBr(inst),
.intcast => try self.airIntCast(inst),
.trunc => try self.airTrunc(inst),
.fptrunc => try self.airFptrunc(inst),
.fpext => try self.airFpext(inst),
.ptrtoint => try self.airPtrToInt(inst),
.load => try self.airLoad(inst),
.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),
.assembly => try self.airAssembly(inst),
.slice_ptr => try self.airSliceField(inst, 0),
.slice_len => try self.airSliceField(inst, 1),
.ptr_slice_ptr_ptr => try self.airPtrSliceFieldPtr(inst, 0),
.ptr_slice_len_ptr => try self.airPtrSliceFieldPtr(inst, 1),
.array_to_slice => try self.airArrayToSlice(inst),
.float_to_int => try self.airFloatToInt(inst),
.int_to_float => try self.airIntToFloat(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),
.memcpy => try self.airMemcpy(inst),
.set_union_tag => try self.airSetUnionTag(inst),
.get_union_tag => try self.airGetUnionTag(inst),
.clz => try self.airClzCtz(inst, "ctlz"),
.ctz => try self.airClzCtz(inst, "cttz"),
.popcount => try self.airPopCount(inst, "ctpop"),
.tag_name => try self.airTagName(inst),
.error_name => try self.airErrorName(inst),
.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(inst),
.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),
.array_elem_val => try self.airArrayElemVal(inst),
.slice_elem_val => try self.airSliceElemVal(inst),
.slice_elem_ptr => try self.airSliceElemPtr(inst),
.ptr_elem_val => try self.airPtrElemVal(inst),
.ptr_elem_ptr => try self.airPtrElemPtr(inst),
.optional_payload => try self.airOptionalPayload(inst),
.optional_payload_ptr => try self.airOptionalPayloadPtr(inst),
.optional_payload_ptr_set => try self.airOptionalPayloadPtrSet(inst),
.unwrap_errunion_payload => try self.airErrUnionPayload(inst, false),
.unwrap_errunion_payload_ptr => try self.airErrUnionPayload(inst, true),
.unwrap_errunion_err => try self.airErrUnionErr(inst, false),
.unwrap_errunion_err_ptr => try self.airErrUnionErr(inst, true),
.wrap_optional => try self.airWrapOptional(inst),
.wrap_errunion_payload => try self.airWrapErrUnionPayload(inst),
.wrap_errunion_err => try self.airWrapErrUnionErr(inst),
.constant => unreachable,
.const_ty => unreachable,
.unreach => self.airUnreach(inst),
.dbg_stmt => blk: {
// TODO: implement debug info
break :blk null;
},
// 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) !?*const 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 = @bitCast([]const Air.Inst.Ref, self.air.extra[extra.end..][0..extra.data.args_len]);
const callee_ty = self.air.typeOf(pl_op.operand);
const zig_fn_ty = switch (callee_ty.zigTypeTag()) {
.Fn => callee_ty,
.Pointer => callee_ty.childType(),
else => unreachable,
};
const fn_info = zig_fn_ty.fnInfo();
const return_type = fn_info.return_type;
const llvm_ret_ty = try self.dg.llvmType(return_type);
const llvm_fn = try self.resolveInst(pl_op.operand);
const target = self.dg.module.getTarget();
const sret = firstParamSRet(fn_info, target);
var llvm_args = std.ArrayList(*const llvm.Value).init(self.gpa);
defer llvm_args.deinit();
const ret_ptr = if (!sret) null else blk: {
const ret_ptr = self.buildAlloca(llvm_ret_ty);
ret_ptr.setAlignment(return_type.abiAlignment(target));
try llvm_args.append(ret_ptr);
break :blk ret_ptr;
};
if (fn_info.is_var_args) {
for (args) |arg| {
try llvm_args.append(try self.resolveInst(arg));
}
} else {
for (args) |arg, i| {
const param_ty = fn_info.param_types[i];
if (!param_ty.hasCodeGenBits()) continue;
try llvm_args.append(try self.resolveInst(arg));
}
}
const call = self.builder.buildCall(
llvm_fn,
llvm_args.items.ptr,
@intCast(c_uint, llvm_args.items.len),
toLlvmCallConv(zig_fn_ty.fnCallingConvention(), target),
.Auto,
"",
);
if (return_type.isNoReturn()) {
_ = self.builder.buildUnreachable();
return null;
} else if (self.liveness.isUnused(inst) or !return_type.hasCodeGenBits()) {
return null;
} else if (sret) {
call.setCallSret(llvm_ret_ty);
return ret_ptr;
} else {
return call;
}
}
fn airRet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const ret_ty = self.air.typeOf(un_op);
if (self.ret_ptr) |ret_ptr| {
const operand = try self.resolveInst(un_op);
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = ret_ty,
};
const ptr_ty = Type.initPayload(&ptr_ty_payload.base);
self.store(ret_ptr, ptr_ty, operand, .NotAtomic);
_ = self.builder.buildRetVoid();
return null;
}
if (!ret_ty.hasCodeGenBits()) {
_ = self.builder.buildRetVoid();
return null;
}
const operand = try self.resolveInst(un_op);
_ = self.builder.buildRet(operand);
return null;
}
fn airRetLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const un_op = self.air.instructions.items(.data)[inst].un_op;
const ptr_ty = self.air.typeOf(un_op);
const ret_ty = ptr_ty.childType();
if (!ret_ty.hasCodeGenBits() or isByRef(ret_ty)) {
_ = self.builder.buildRetVoid();
return null;
}
const ptr = try self.resolveInst(un_op);
const loaded = self.builder.buildLoad(ptr, "");
_ = self.builder.buildRet(loaded);
return null;
}
fn airCmp(self: *FuncGen, inst: Air.Inst.Index, op: math.CompareOperator) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOf(bin_op.lhs);
return self.cmp(lhs, rhs, operand_ty, op);
}
fn cmp(
self: *FuncGen,
lhs: *const llvm.Value,
rhs: *const llvm.Value,
operand_ty: Type,
op: math.CompareOperator,
) *const llvm.Value {
var int_buffer: Type.Payload.Bits = undefined;
var opt_buffer: Type.Payload.ElemType = undefined;
const int_ty = switch (operand_ty.zigTypeTag()) {
.Enum => operand_ty.intTagType(&int_buffer),
.Int, .Bool, .Pointer, .ErrorSet => operand_ty,
.Optional => blk: {
const payload_ty = operand_ty.optionalChild(&opt_buffer);
if (!payload_ty.hasCodeGenBits() or operand_ty.isPtrLikeOptional()) {
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(operand_ty);
const lhs_non_null = self.optIsNonNull(lhs, is_by_ref);
const rhs_non_null = self.optIsNonNull(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 = self.optPayloadHandle(lhs, is_by_ref);
const rhs_payload = self.optPayloadHandle(rhs, is_by_ref);
const payload_cmp = 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]*const 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]*const 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 => {
const operation: llvm.RealPredicate = switch (op) {
.eq => .OEQ,
.neq => .UNE,
.lt => .OLT,
.lte => .OLE,
.gt => .OGT,
.gte => .OGE,
};
return self.builder.buildFCmp(operation, lhs, rhs, "");
},
else => unreachable,
};
const is_signed = int_ty.isSignedInt();
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) !?*const llvm.Value {
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.air.typeOfIndex(inst);
const parent_bb = self.context.createBasicBlock("Block");
if (inst_ty.isNoReturn()) {
try self.genBody(body);
return null;
}
var break_bbs: BreakBasicBlocks = .{};
defer break_bbs.deinit(self.gpa);
var break_vals: BreakValues = .{};
defer break_vals.deinit(self.gpa);
try self.blocks.putNoClobber(self.gpa, inst, .{
.parent_bb = parent_bb,
.break_bbs = &break_bbs,
.break_vals = &break_vals,
});
defer assert(self.blocks.remove(inst));
try self.genBody(body);
self.llvm_func.appendExistingBasicBlock(parent_bb);
self.builder.positionBuilderAtEnd(parent_bb);
// If the block does not return a value, we dont have to create a phi node.
if (!inst_ty.hasCodeGenBits()) return null;
const raw_llvm_ty = try self.dg.llvmType(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 (inst_ty.zigTypeTag() == .Fn or isByRef(inst_ty)) {
break :ty raw_llvm_ty.pointerType(0);
}
break :ty raw_llvm_ty;
};
const phi_node = self.builder.buildPhi(llvm_ty, "");
phi_node.addIncoming(
break_vals.items.ptr,
break_bbs.items.ptr,
@intCast(c_uint, break_vals.items.len),
);
return phi_node;
}
fn airBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const branch = self.air.instructions.items(.data)[inst].br;
const block = self.blocks.get(branch.block_inst).?;
// If the break doesn't break a value, then we don't have to add
// the values to the lists.
if (self.air.typeOf(branch.operand).hasCodeGenBits()) {
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.break_bbs.append(self.gpa, self.builder.getInsertBlock());
try block.break_vals.append(self.gpa, val);
}
_ = self.builder.buildBr(block.parent_bb);
return null;
}
fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !?*const 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 airSwitchBr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
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 llvm_switch = self.builder.buildSwitch(cond, 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 = @bitCast([]const Air.Inst.Ref, 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);
llvm_switch.addCase(llvm_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) !?*const llvm.Value {
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.air.typeOfIndex(body[body.len - 1]).isNoReturn()) {
_ = self.builder.buildBr(loop_block);
}
return null;
}
fn airArrayToSlice(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand_ty = self.air.typeOf(ty_op.operand);
const array_ty = operand_ty.childType();
const llvm_usize = try self.dg.llvmType(Type.usize);
const len = llvm_usize.constInt(array_ty.arrayLen(), .False);
const slice_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
if (!array_ty.hasCodeGenBits()) {
return self.builder.buildInsertValue(slice_llvm_ty.getUndef(), len, 1, "");
}
const operand = try self.resolveInst(ty_op.operand);
const indices: [2]*const llvm.Value = .{
llvm_usize.constNull(), llvm_usize.constNull(),
};
const ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
const partial = self.builder.buildInsertValue(slice_llvm_ty.getUndef(), ptr, 0, "");
return self.builder.buildInsertValue(partial, len, 1, "");
}
fn airIntToFloat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const dest_ty = self.air.typeOfIndex(inst);
const dest_llvm_ty = try self.dg.llvmType(dest_ty);
if (dest_ty.isSignedInt()) {
return self.builder.buildSIToFP(operand, dest_llvm_ty, "");
} else {
return self.builder.buildUIToFP(operand, dest_llvm_ty, "");
}
}
fn airFloatToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const dest_ty = self.air.typeOfIndex(inst);
const dest_llvm_ty = try self.dg.llvmType(dest_ty);
// TODO set fast math flag
if (dest_ty.isSignedInt()) {
return self.builder.buildFPToSI(operand, dest_llvm_ty, "");
} else {
return self.builder.buildFPToUI(operand, dest_llvm_ty, "");
}
}
fn airSliceField(self: *FuncGen, inst: Air.Inst.Index, index: c_uint) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const slice_ptr = try self.resolveInst(ty_op.operand);
return self.builder.buildStructGEP(slice_ptr, index, "");
}
fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const slice_ty = self.air.typeOf(bin_op.lhs);
if (!slice_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null;
const slice = try self.resolveInst(bin_op.lhs);
const index = try self.resolveInst(bin_op.rhs);
const ptr = self.sliceElemPtr(slice, index);
return self.load(ptr, slice_ty);
}
fn airSliceElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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 = try self.resolveInst(bin_op.lhs);
const index = try self.resolveInst(bin_op.rhs);
return self.sliceElemPtr(slice, index);
}
fn airArrayElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const array_ty = self.air.typeOf(bin_op.lhs);
const array_llvm_val = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
assert(isByRef(array_ty));
const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
const elem_ptr = self.builder.buildInBoundsGEP(array_llvm_val, &indices, indices.len, "");
const elem_ty = array_ty.childType();
if (isByRef(elem_ty)) {
return elem_ptr;
} else {
return self.builder.buildLoad(elem_ptr, "");
}
}
fn airPtrElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const ptr_ty = self.air.typeOf(bin_op.lhs);
if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst)) return null;
const base_ptr = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
const ptr = if (ptr_ty.isSinglePointer()) ptr: {
// If this is a single-item pointer to an array, we need another index in the GEP.
const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
} else ptr: {
const indices: [1]*const llvm.Value = .{rhs};
break :ptr self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
};
return self.load(ptr, ptr_ty);
}
fn airPtrElemPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOf(bin_op.lhs);
const elem_ty = ptr_ty.childType();
if (!elem_ty.hasCodeGenBits()) return null;
const base_ptr = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
if (ptr_ty.isSinglePointer()) {
// If this is a single-item pointer to an array, we need another index in the GEP.
const indices: [2]*const llvm.Value = .{ self.context.intType(32).constNull(), rhs };
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
} else {
const indices: [1]*const llvm.Value = .{rhs};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
}
}
fn airStructFieldPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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.air.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,
) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOf(ty_op.operand);
return self.fieldPtr(inst, struct_ptr, struct_ptr_ty, field_index);
}
fn airStructFieldVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.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);
if (!field_ty.hasCodeGenBits()) {
return null;
}
assert(isByRef(struct_ty));
const target = self.dg.module.getTarget();
switch (struct_ty.zigTypeTag()) {
.Struct => {
var ptr_ty_buf: Type.Payload.Pointer = undefined;
const llvm_field_index = llvmFieldIndex(struct_ty, field_index, target, &ptr_ty_buf).?;
const field_ptr = self.builder.buildStructGEP(struct_llvm_val, llvm_field_index, "");
const field_ptr_ty = Type.initPayload(&ptr_ty_buf.base);
return self.load(field_ptr, field_ptr_ty);
},
.Union => {
const llvm_field_ty = try self.dg.llvmType(field_ty);
const layout = struct_ty.unionGetLayout(target);
const payload_index = @boolToInt(layout.tag_align >= layout.payload_align);
const union_field_ptr = self.builder.buildStructGEP(struct_llvm_val, payload_index, "");
const field_ptr = self.builder.buildBitCast(union_field_ptr, llvm_field_ty.pointerType(0), "");
if (isByRef(field_ty)) {
return field_ptr;
} else {
return self.builder.buildLoad(field_ptr, "");
}
},
else => unreachable,
}
}
fn airNot(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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) ?*const llvm.Value {
_ = inst;
_ = self.builder.buildUnreachable();
return null;
}
fn airAssembly(self: *FuncGen, inst: Air.Inst.Index) !?*const 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, same
// as stage1.
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const air_asm = self.air.extraData(Air.Asm, ty_pl.payload);
const zir = self.dg.decl.getFileScope().zir;
const extended = zir.instructions.items(.data)[air_asm.data.zir_index].extended;
const is_volatile = @truncate(u1, extended.small >> 15) != 0;
if (!is_volatile and self.liveness.isUnused(inst)) {
return null;
}
const outputs_len = @truncate(u5, extended.small);
if (outputs_len > 1) {
return self.todo("implement llvm codegen for asm with more than 1 output", .{});
}
const args_len = @truncate(u5, extended.small >> 5);
const clobbers_len = @truncate(u5, extended.small >> 10);
const zir_extra = zir.extraData(Zir.Inst.Asm, extended.operand);
const asm_source = zir.nullTerminatedString(zir_extra.data.asm_source);
const args = @bitCast([]const Air.Inst.Ref, self.air.extra[air_asm.end..][0..args_len]);
var extra_i: usize = zir_extra.end;
const output_constraint: ?[]const u8 = out: {
var i: usize = 0;
while (i < outputs_len) : (i += 1) {
const output = zir.extraData(Zir.Inst.Asm.Output, extra_i);
extra_i = output.end;
break :out zir.nullTerminatedString(output.data.constraint);
}
break :out null;
};
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();
const llvm_params_len = args.len;
const llvm_param_types = try arena.alloc(*const llvm.Type, llvm_params_len);
const llvm_param_values = try arena.alloc(*const llvm.Value, llvm_params_len);
var llvm_param_i: usize = 0;
var total_i: usize = 0;
if (output_constraint) |constraint| {
try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1);
if (total_i != 0) {
llvm_constraints.appendAssumeCapacity(',');
}
llvm_constraints.appendAssumeCapacity('=');
llvm_constraints.appendSliceAssumeCapacity(constraint[1..]);
total_i += 1;
}
for (args) |arg| {
const input = zir.extraData(Zir.Inst.Asm.Input, extra_i);
extra_i = input.end;
const constraint = zir.nullTerminatedString(input.data.constraint);
const arg_llvm_value = try self.resolveInst(arg);
llvm_param_values[llvm_param_i] = arg_llvm_value;
llvm_param_types[llvm_param_i] = arg_llvm_value.typeOf();
try llvm_constraints.ensureUnusedCapacity(self.gpa, constraint.len + 1);
if (total_i != 0) {
llvm_constraints.appendAssumeCapacity(',');
}
llvm_constraints.appendSliceAssumeCapacity(constraint);
llvm_param_i += 1;
total_i += 1;
}
const clobbers = zir.extra[extra_i..][0..clobbers_len];
for (clobbers) |clobber_index| {
const clobber = zir.nullTerminatedString(clobber_index);
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;
}
const ret_ty = self.air.typeOfIndex(inst);
const ret_llvm_ty = try self.dg.llvmType(ret_ty);
const llvm_fn_ty = llvm.functionType(
ret_llvm_ty,
llvm_param_types.ptr,
@intCast(c_uint, llvm_param_types.len),
.False,
);
const asm_fn = llvm.getInlineAsm(
llvm_fn_ty,
asm_source.ptr,
asm_source.len,
llvm_constraints.items.ptr,
llvm_constraints.items.len,
llvm.Bool.fromBool(is_volatile),
.False,
.ATT,
.False,
);
return self.builder.buildCall(
asm_fn,
llvm_param_values.ptr,
@intCast(c_uint, llvm_param_values.len),
.C,
.Auto,
"",
);
}
fn airIsNonNull(
self: *FuncGen,
inst: Air.Inst.Index,
operand_is_ptr: bool,
invert: bool,
pred: llvm.IntPredicate,
) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
const operand_ty = self.air.typeOf(un_op);
const optional_ty = if (operand_is_ptr) operand_ty.childType() else operand_ty;
if (optional_ty.isPtrLikeOptional()) {
const optional_llvm_ty = try self.dg.llvmType(optional_ty);
const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand;
return self.builder.buildICmp(pred, loaded, optional_llvm_ty.constNull(), "");
}
var buf: Type.Payload.ElemType = undefined;
const payload_ty = optional_ty.optionalChild(&buf);
if (!payload_ty.hasCodeGenBits()) {
if (invert) {
return self.builder.buildNot(operand, "");
} else {
return operand;
}
}
const is_by_ref = operand_is_ptr or isByRef(optional_ty);
const non_null_bit = self.optIsNonNull(operand, is_by_ref);
if (invert) {
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,
) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
const err_union_ty = self.air.typeOf(un_op);
const payload_ty = err_union_ty.errorUnionPayload();
const err_set_ty = try self.dg.llvmType(Type.initTag(.anyerror));
const zero = err_set_ty.constNull();
if (!payload_ty.hasCodeGenBits()) {
const loaded = if (operand_is_ptr) self.builder.buildLoad(operand, "") else operand;
return self.builder.buildICmp(op, loaded, zero, "");
}
if (operand_is_ptr or isByRef(err_union_ty)) {
const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");
const loaded = self.builder.buildLoad(err_field_ptr, "");
return self.builder.buildICmp(op, loaded, zero, "");
}
const loaded = self.builder.buildExtractValue(operand, 0, "");
return self.builder.buildICmp(op, loaded, zero, "");
}
fn airOptionalPayloadPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.air.typeOf(ty_op.operand).childType();
var buf: Type.Payload.ElemType = undefined;
const payload_ty = optional_ty.optionalChild(&buf);
if (!payload_ty.hasCodeGenBits()) {
// 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.isPtrLikeOptional()) {
// The payload and the optional are the same value.
return operand;
}
const index_type = self.context.intType(32);
const indices: [2]*const llvm.Value = .{
index_type.constNull(), // dereference the pointer
index_type.constNull(), // first field is the payload
};
return self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
}
fn airOptionalPayloadPtrSet(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.air.typeOf(ty_op.operand).childType();
var buf: Type.Payload.ElemType = undefined;
const payload_ty = optional_ty.optionalChild(&buf);
const non_null_bit = self.context.intType(1).constAllOnes();
if (!payload_ty.hasCodeGenBits()) {
// We have a pointer to a i1. 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.isPtrLikeOptional()) {
// The payload and the optional are the same value.
// Setting to non-null will be done when the payload is set.
return operand;
}
const index_type = self.context.intType(32);
{
// First set the non-null bit.
const indices: [2]*const llvm.Value = .{
index_type.constNull(), // dereference the pointer
index_type.constInt(1, .False), // second field is the payload
};
const non_null_ptr = self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
_ = self.builder.buildStore(non_null_bit, non_null_ptr);
}
// Then return the payload pointer.
const indices: [2]*const llvm.Value = .{
index_type.constNull(), // dereference the pointer
index_type.constNull(), // first field is the payload
};
return self.builder.buildInBoundsGEP(operand, &indices, indices.len, "");
}
fn airOptionalPayload(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.air.typeOf(ty_op.operand);
const payload_ty = self.air.typeOfIndex(inst);
if (!payload_ty.hasCodeGenBits()) return null;
if (optional_ty.isPtrLikeOptional()) {
// Payload value is the same as the optional value.
return operand;
}
return self.optPayloadHandle(operand, isByRef(payload_ty));
}
fn airErrUnionPayload(
self: *FuncGen,
inst: Air.Inst.Index,
operand_is_ptr: bool,
) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const err_union_ty = self.air.typeOf(ty_op.operand);
const payload_ty = err_union_ty.errorUnionPayload();
if (!payload_ty.hasCodeGenBits()) return null;
if (operand_is_ptr or isByRef(payload_ty)) {
return self.builder.buildStructGEP(operand, 1, "");
}
return self.builder.buildExtractValue(operand, 1, "");
}
fn airErrUnionErr(
self: *FuncGen,
inst: Air.Inst.Index,
operand_is_ptr: bool,
) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const operand_ty = self.air.typeOf(ty_op.operand);
const payload_ty = operand_ty.errorUnionPayload();
if (!payload_ty.hasCodeGenBits()) {
if (!operand_is_ptr) return operand;
return self.builder.buildLoad(operand, "");
}
if (operand_is_ptr or isByRef(payload_ty)) {
const err_field_ptr = self.builder.buildStructGEP(operand, 0, "");
return self.builder.buildLoad(err_field_ptr, "");
}
return self.builder.buildExtractValue(operand, 0, "");
}
fn airWrapOptional(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const payload_ty = self.air.typeOf(ty_op.operand);
const non_null_bit = self.context.intType(1).constAllOnes();
if (!payload_ty.hasCodeGenBits()) return non_null_bit;
const operand = try self.resolveInst(ty_op.operand);
const optional_ty = self.air.typeOfIndex(inst);
if (optional_ty.isPtrLikeOptional()) return operand;
const llvm_optional_ty = try self.dg.llvmType(optional_ty);
if (isByRef(optional_ty)) {
const optional_ptr = self.buildAlloca(llvm_optional_ty);
const payload_ptr = self.builder.buildStructGEP(optional_ptr, 0, "");
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = payload_ty,
};
const payload_ptr_ty = Type.initPayload(&ptr_ty_payload.base);
self.store(payload_ptr, payload_ptr_ty, operand, .NotAtomic);
const non_null_ptr = self.builder.buildStructGEP(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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const payload_ty = self.air.typeOf(ty_op.operand);
const operand = try self.resolveInst(ty_op.operand);
if (!payload_ty.hasCodeGenBits()) {
return operand;
}
const inst_ty = self.air.typeOfIndex(inst);
const ok_err_code = self.context.intType(16).constNull();
const err_un_llvm_ty = try self.dg.llvmType(inst_ty);
if (isByRef(inst_ty)) {
const result_ptr = self.buildAlloca(err_un_llvm_ty);
const err_ptr = self.builder.buildStructGEP(result_ptr, 0, "");
_ = self.builder.buildStore(ok_err_code, err_ptr);
const payload_ptr = self.builder.buildStructGEP(result_ptr, 1, "");
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = payload_ty,
};
const payload_ptr_ty = Type.initPayload(&ptr_ty_payload.base);
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, 0, "");
return self.builder.buildInsertValue(partial, operand, 1, "");
}
fn airWrapErrUnionErr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const err_un_ty = self.air.typeOfIndex(inst);
const payload_ty = err_un_ty.errorUnionPayload();
const operand = try self.resolveInst(ty_op.operand);
if (!payload_ty.hasCodeGenBits()) {
return operand;
}
const err_un_llvm_ty = try self.dg.llvmType(err_un_ty);
if (isByRef(err_un_ty)) {
const result_ptr = self.buildAlloca(err_un_llvm_ty);
const err_ptr = self.builder.buildStructGEP(result_ptr, 0, "");
_ = self.builder.buildStore(operand, err_ptr);
const payload_ptr = self.builder.buildStructGEP(result_ptr, 1, "");
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = payload_ty,
};
const payload_ptr_ty = Type.initPayload(&ptr_ty_payload.base);
// 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, 0, "");
// TODO set payload bytes to undef
return partial;
}
fn airMin(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst).scalarType();
if (scalar_ty.isAnyFloat()) return self.builder.buildMinNum(lhs, rhs, "");
if (scalar_ty.isSignedInt()) return self.builder.buildSMin(lhs, rhs, "");
return self.builder.buildUMin(lhs, rhs, "");
}
fn airMax(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst).scalarType();
if (scalar_ty.isAnyFloat()) return self.builder.buildMaxNum(lhs, rhs, "");
if (scalar_ty.isSignedInt()) return self.builder.buildSMax(lhs, rhs, "");
return self.builder.buildUMax(lhs, rhs, "");
}
fn airSlice(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
const llvm_slice_ty = try self.dg.llvmType(inst_ty);
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFAdd(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildNSWAdd(lhs, rhs, "");
return self.builder.buildNUWAdd(lhs, rhs, "");
}
fn airAddWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.todo("saturating float add", .{});
if (inst_ty.isSignedInt()) return self.builder.buildSAddSat(lhs, rhs, "");
return self.builder.buildUAddSat(lhs, rhs, "");
}
fn airSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFSub(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildNSWSub(lhs, rhs, "");
return self.builder.buildNUWSub(lhs, rhs, "");
}
fn airSubWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.todo("saturating float sub", .{});
if (inst_ty.isSignedInt()) return self.builder.buildSSubSat(lhs, rhs, "");
return self.builder.buildUSubSat(lhs, rhs, "");
}
fn airMul(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.builder.buildFMul(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildNSWMul(lhs, rhs, "");
return self.builder.buildNUWMul(lhs, rhs, "");
}
fn airMulWrap(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isAnyFloat()) return self.todo("saturating float mul", .{});
if (inst_ty.isSignedInt()) return self.builder.buildSMulFixSat(lhs, rhs, "");
return self.builder.buildUMulFixSat(lhs, rhs, "");
}
fn airDivFloat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.buildFDiv(lhs, rhs, "");
}
fn airDivTrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isRuntimeFloat()) {
const result = self.builder.buildFDiv(lhs, rhs, "");
return self.callTrunc(result, inst_ty);
}
if (inst_ty.isSignedInt()) return self.builder.buildSDiv(lhs, rhs, "");
return self.builder.buildUDiv(lhs, rhs, "");
}
fn airDivFloor(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isRuntimeFloat()) {
const result = self.builder.buildFDiv(lhs, rhs, "");
return try self.callFloor(result, inst_ty);
}
if (inst_ty.isSignedInt()) {
// const d = @divTrunc(a, b);
// const r = @rem(a, b);
// return if (r == 0) d else d - ((a < 0) ^ (b < 0));
const result_llvm_ty = try self.dg.llvmType(inst_ty);
const zero = result_llvm_ty.constNull();
const div_trunc = self.builder.buildSDiv(lhs, rhs, "");
const rem = self.builder.buildSRem(lhs, rhs, "");
const rem_eq_0 = self.builder.buildICmp(.EQ, rem, zero, "");
const a_lt_0 = self.builder.buildICmp(.SLT, lhs, zero, "");
const b_lt_0 = self.builder.buildICmp(.SLT, rhs, zero, "");
const a_b_xor = self.builder.buildXor(a_lt_0, b_lt_0, "");
const a_b_xor_ext = self.builder.buildZExt(a_b_xor, div_trunc.typeOf(), "");
const d_sub_xor = self.builder.buildSub(div_trunc, a_b_xor_ext, "");
return self.builder.buildSelect(rem_eq_0, div_trunc, d_sub_xor, "");
}
return self.builder.buildUDiv(lhs, rhs, "");
}
fn airDivExact(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isRuntimeFloat()) return self.builder.buildFDiv(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildExactSDiv(lhs, rhs, "");
return self.builder.buildExactUDiv(lhs, rhs, "");
}
fn airRem(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
if (inst_ty.isRuntimeFloat()) return self.builder.buildFRem(lhs, rhs, "");
if (inst_ty.isSignedInt()) return self.builder.buildSRem(lhs, rhs, "");
return self.builder.buildURem(lhs, rhs, "");
}
fn airMod(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOfIndex(inst);
const inst_llvm_ty = try self.dg.llvmType(inst_ty);
if (inst_ty.isRuntimeFloat()) {
const a = self.builder.buildFRem(lhs, rhs, "");
const b = self.builder.buildFAdd(a, rhs, "");
const c = self.builder.buildFRem(b, rhs, "");
const zero = inst_llvm_ty.constNull();
const ltz = self.builder.buildFCmp(.OLT, lhs, zero, "");
return self.builder.buildSelect(ltz, c, a, "");
}
if (inst_ty.isSignedInt()) {
const a = self.builder.buildSRem(lhs, rhs, "");
const b = self.builder.buildNSWAdd(a, rhs, "");
const c = self.builder.buildSRem(b, rhs, "");
const zero = inst_llvm_ty.constNull();
const ltz = self.builder.buildICmp(.SLT, lhs, zero, "");
return self.builder.buildSelect(ltz, c, a, "");
}
return self.builder.buildURem(lhs, rhs, "");
}
fn airPtrAdd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const base_ptr = try self.resolveInst(bin_op.lhs);
const offset = try self.resolveInst(bin_op.rhs);
const ptr_ty = self.air.typeOf(bin_op.lhs);
if (ptr_ty.ptrSize() == .One) {
// It's a pointer to an array, so according to LLVM we need an extra GEP index.
const indices: [2]*const llvm.Value = .{
self.context.intType(32).constNull(), offset,
};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
} else {
const indices: [1]*const llvm.Value = .{offset};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
}
}
fn airPtrSub(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const base_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.air.typeOf(bin_op.lhs);
if (ptr_ty.ptrSize() == .One) {
// It's a pointer to an array, so according to LLVM we need an extra GEP index.
const indices: [2]*const llvm.Value = .{
self.context.intType(32).constNull(), negative_offset,
};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
} else {
const indices: [1]*const llvm.Value = .{negative_offset};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
}
}
fn airOverflow(
self: *FuncGen,
inst: Air.Inst.Index,
signed_intrinsic: []const u8,
unsigned_intrinsic: []const u8,
) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
const ptr = try self.resolveInst(pl_op.operand);
const lhs = try self.resolveInst(extra.lhs);
const rhs = try self.resolveInst(extra.rhs);
const ptr_ty = self.air.typeOf(pl_op.operand);
const lhs_ty = self.air.typeOf(extra.lhs);
const intrinsic_name = if (lhs_ty.isSignedInt()) signed_intrinsic else unsigned_intrinsic;
const llvm_lhs_ty = try self.dg.llvmType(lhs_ty);
const llvm_fn = self.getIntrinsic(intrinsic_name, &.{llvm_lhs_ty});
const result_struct = self.builder.buildCall(llvm_fn, &[_]*const llvm.Value{ lhs, rhs }, 2, .Fast, .Auto, "");
const result = self.builder.buildExtractValue(result_struct, 0, "");
const overflow_bit = self.builder.buildExtractValue(result_struct, 1, "");
self.store(ptr, ptr_ty, result, .NotAtomic);
return overflow_bit;
}
fn airShlWithOverflow(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const pl_op = self.air.instructions.items(.data)[inst].pl_op;
const extra = self.air.extraData(Air.Bin, pl_op.payload).data;
const ptr = try self.resolveInst(pl_op.operand);
const lhs = try self.resolveInst(extra.lhs);
const rhs = try self.resolveInst(extra.rhs);
const ptr_ty = self.air.typeOf(pl_op.operand);
const lhs_ty = self.air.typeOf(extra.lhs);
const rhs_ty = self.air.typeOf(extra.rhs);
const tg = self.dg.module.getTarget();
const casted_rhs = if (rhs_ty.bitSize(tg) < lhs_ty.bitSize(tg))
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_ty), "")
else
rhs;
const result = self.builder.buildShl(lhs, casted_rhs, "");
const reconstructed = if (lhs_ty.isSignedInt())
self.builder.buildAShr(result, casted_rhs, "")
else
self.builder.buildLShr(result, casted_rhs, "");
const overflow_bit = self.builder.buildICmp(.NE, lhs, reconstructed, "");
self.store(ptr, ptr_ty, result, .NotAtomic);
return overflow_bit;
}
fn airAnd(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOf(bin_op.lhs);
const tg = self.dg.module.getTarget();
const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
if (lhs_type.isSignedInt()) return self.builder.buildNSWShl(lhs, casted_rhs, "");
return self.builder.buildNUWShl(lhs, casted_rhs, "");
}
fn airShl(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOf(bin_op.lhs);
const tg = self.dg.module.getTarget();
const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
return self.builder.buildShl(lhs, casted_rhs, "");
}
fn airShlSat(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
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.air.typeOf(bin_op.lhs);
const tg = self.dg.module.getTarget();
const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
if (lhs_type.isSignedInt()) return self.builder.buildSShlSat(lhs, casted_rhs, "");
return self.builder.buildUShlSat(lhs, casted_rhs, "");
}
fn airShr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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.air.typeOf(bin_op.lhs);
const tg = self.dg.module.getTarget();
const casted_rhs = if (self.air.typeOf(bin_op.rhs).bitSize(tg) < lhs_type.bitSize(tg))
self.builder.buildZExt(rhs, try self.dg.llvmType(lhs_type), "")
else
rhs;
if (self.air.typeOfIndex(inst).isSignedInt()) {
return self.builder.buildAShr(lhs, casted_rhs, "");
} else {
return self.builder.buildLShr(lhs, casted_rhs, "");
}
}
fn airIntCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const target = self.dg.module.getTarget();
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const dest_ty = self.air.typeOfIndex(inst);
const dest_info = dest_ty.intInfo(target);
const dest_llvm_ty = try self.dg.llvmType(dest_ty);
const operand = try self.resolveInst(ty_op.operand);
const operand_ty = self.air.typeOf(ty_op.operand);
const operand_info = operand_ty.intInfo(target);
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) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
return self.builder.buildTrunc(operand, dest_llvm_ty, "");
}
fn airFptrunc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
return self.builder.buildFPTrunc(operand, dest_llvm_ty, "");
}
fn airFpext(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
return self.builder.buildFPExt(operand, dest_llvm_ty, "");
}
fn airPtrToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
const dest_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
return self.builder.buildPtrToInt(operand, dest_llvm_ty, "");
}
fn airBitCast(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand = try self.resolveInst(ty_op.operand);
const operand_ty = self.air.typeOf(ty_op.operand);
const inst_ty = self.air.typeOfIndex(inst);
const operand_is_ref = isByRef(operand_ty);
const result_is_ref = isByRef(inst_ty);
const llvm_dest_ty = try self.dg.llvmType(inst_ty);
if (operand_is_ref and result_is_ref) {
// They are both pointers; just do a bitcast on the pointers :)
return self.builder.buildBitCast(operand, llvm_dest_ty.pointerType(0), "");
}
if (operand_ty.zigTypeTag() == .Int and inst_ty.zigTypeTag() == .Pointer) {
return self.builder.buildIntToPtr(operand, llvm_dest_ty, "");
}
if (operand_ty.zigTypeTag() == .Vector and inst_ty.zigTypeTag() == .Array) {
const target = self.dg.module.getTarget();
const elem_ty = operand_ty.childType();
if (!result_is_ref) {
return self.dg.todo("implement bitcast vector to non-ref array", .{});
}
const array_ptr = self.buildAlloca(llvm_dest_ty);
const bitcast_ok = elem_ty.bitSize(target) == elem_ty.abiSize(target) * 8;
if (bitcast_ok) {
const llvm_vector_ty = try self.dg.llvmType(operand_ty);
const casted_ptr = self.builder.buildBitCast(array_ptr, llvm_vector_ty.pointerType(0), "");
_ = self.builder.buildStore(operand, casted_ptr);
} 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 self.dg.llvmType(Type.usize);
const llvm_u32 = self.context.intType(32);
const zero = llvm_usize.constNull();
const vector_len = operand_ty.arrayLen();
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]*const llvm.Value = .{ zero, index_usize };
const elem_ptr = self.builder.buildInBoundsGEP(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() == .Array and inst_ty.zigTypeTag() == .Vector) {
const target = self.dg.module.getTarget();
const elem_ty = operand_ty.childType();
const llvm_vector_ty = try self.dg.llvmType(inst_ty);
if (!operand_is_ref) {
return self.dg.todo("implement bitcast non-ref array to vector", .{});
}
const bitcast_ok = elem_ty.bitSize(target) == elem_ty.abiSize(target) * 8;
if (bitcast_ok) {
const llvm_vector_ptr_ty = llvm_vector_ty.pointerType(0);
const casted_ptr = self.builder.buildBitCast(operand, llvm_vector_ptr_ty, "");
const vector = self.builder.buildLoad(casted_ptr, "");
// 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(target));
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 llvm_usize = try self.dg.llvmType(Type.usize);
const llvm_u32 = self.context.intType(32);
const zero = llvm_usize.constNull();
const vector_len = operand_ty.arrayLen();
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]*const llvm.Value = .{ zero, index_usize };
const elem_ptr = self.builder.buildInBoundsGEP(operand, &indexes, indexes.len, "");
const elem = self.builder.buildLoad(elem_ptr, "");
vector = self.builder.buildInsertElement(vector, elem, index_u32, "");
}
return vector;
}
}
if (operand_is_ref) {
// Bitcast the operand pointer, then load.
const casted_ptr = self.builder.buildBitCast(operand, llvm_dest_ty.pointerType(0), "");
return self.builder.buildLoad(casted_ptr, "");
}
if (result_is_ref) {
// Bitcast the result pointer, then store.
const result_ptr = self.buildAlloca(llvm_dest_ty);
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
const casted_ptr = self.builder.buildBitCast(result_ptr, operand_llvm_ty.pointerType(0), "");
_ = self.builder.buildStore(operand, casted_ptr);
return result_ptr;
}
return self.builder.buildBitCast(operand, llvm_dest_ty, "");
}
fn airBoolToInt(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst))
return null;
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) !?*const llvm.Value {
const arg_val = self.args[self.arg_index];
self.arg_index += 1;
const inst_ty = self.air.typeOfIndex(inst);
if (isByRef(inst_ty)) {
// TODO declare debug variable
return arg_val;
} else {
const ptr_val = self.buildAlloca(try self.dg.llvmType(inst_ty));
_ = self.builder.buildStore(arg_val, ptr_val);
// TODO declare debug variable
return arg_val;
}
}
fn airAlloc(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ptr_ty = self.air.typeOfIndex(inst);
const pointee_type = ptr_ty.childType();
if (!pointee_type.hasCodeGenBits()) return self.dg.lowerPtrToVoid(ptr_ty);
const pointee_llvm_ty = try self.dg.llvmType(pointee_type);
const alloca_inst = self.buildAlloca(pointee_llvm_ty);
const target = self.dg.module.getTarget();
const alignment = ptr_ty.ptrAlignment(target);
alloca_inst.setAlignment(alignment);
return alloca_inst;
}
fn airRetPtr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ptr_ty = self.air.typeOfIndex(inst);
const ret_ty = ptr_ty.childType();
if (!ret_ty.hasCodeGenBits()) return null;
if (self.ret_ptr) |ret_ptr| return ret_ptr;
const ret_llvm_ty = try self.dg.llvmType(ret_ty);
const target = self.dg.module.getTarget();
const alloca_inst = self.buildAlloca(ret_llvm_ty);
alloca_inst.setAlignment(ptr_ty.ptrAlignment(target));
return alloca_inst;
}
/// 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: *const llvm.Type) *const llvm.Value {
const prev_block = self.builder.getInsertBlock();
const entry_block = self.llvm_func.getFirstBasicBlock().?;
if (entry_block.getFirstInstruction()) |first_inst| {
self.builder.positionBuilder(entry_block, first_inst);
} else {
self.builder.positionBuilderAtEnd(entry_block);
}
const alloca = self.builder.buildAlloca(llvm_ty, "");
self.builder.positionBuilderAtEnd(prev_block);
return alloca;
}
fn airStore(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const dest_ptr = try self.resolveInst(bin_op.lhs);
const ptr_ty = self.air.typeOf(bin_op.lhs);
// TODO Sema should emit a different instruction when the store should
// possibly do the safety 0xaa bytes for undefined.
const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false;
if (val_is_undef) {
const elem_ty = ptr_ty.childType();
const target = self.dg.module.getTarget();
const elem_size = elem_ty.abiSize(target);
const u8_llvm_ty = self.context.intType(8);
const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0);
const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, "");
const fill_char = u8_llvm_ty.constInt(0xaa, .False);
const dest_ptr_align = ptr_ty.ptrAlignment(target);
const usize_llvm_ty = try self.dg.llvmType(Type.usize);
const len = usize_llvm_ty.constInt(elem_size, .False);
_ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr());
if (self.dg.module.comp.bin_file.options.valgrind) {
// TODO generate valgrind client request to mark byte range as undefined
// see gen_valgrind_undef() in codegen.cpp
}
} else {
const src_operand = try self.resolveInst(bin_op.rhs);
self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic);
}
return null;
}
fn airLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const ptr_ty = self.air.typeOf(ty_op.operand);
if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst))
return null;
const ptr = try self.resolveInst(ty_op.operand);
return self.load(ptr, ptr_ty);
}
fn airBreakpoint(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
_ = inst;
const llvm_fn = self.getIntrinsic("llvm.debugtrap", &.{});
_ = self.builder.buildCall(llvm_fn, undefined, 0, .C, .Auto, "");
return null;
}
fn airRetAddr(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
_ = inst;
const i32_zero = self.context.intType(32).constNull();
const usize_llvm_ty = try self.dg.llvmType(Type.usize);
const llvm_fn = self.getIntrinsic("llvm.returnaddress", &.{});
const ptr_val = self.builder.buildCall(llvm_fn, &[_]*const llvm.Value{i32_zero}, 1, .Fast, .Auto, "");
return self.builder.buildPtrToInt(ptr_val, usize_llvm_ty, "");
}
fn airFence(self: *FuncGen, inst: Air.Inst.Index) !?*const 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) !?*const llvm.Value {
const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
const extra = self.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
var 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.air.typeOf(extra.ptr).elemType();
const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false);
if (opt_abi_ty) |abi_ty| {
// operand needs widening and truncating
ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), "");
if (operand_ty.isSignedInt()) {
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.air.typeOfIndex(inst);
var payload = self.builder.buildExtractValue(result, 0, "");
if (opt_abi_ty != null) {
payload = self.builder.buildTrunc(payload, try self.dg.llvmType(operand_ty), "");
}
const success_bit = self.builder.buildExtractValue(result, 1, "");
if (optional_ty.isPtrLikeOptional()) {
return self.builder.buildSelect(success_bit, payload.typeOf().constNull(), payload, "");
}
const optional_llvm_ty = try self.dg.llvmType(optional_ty);
const non_null_bit = self.builder.buildNot(success_bit, "");
const partial = self.builder.buildInsertValue(optional_llvm_ty.getUndef(), payload, 0, "");
return self.builder.buildInsertValue(partial, non_null_bit, 1, "");
}
fn airAtomicRmw(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
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.air.typeOf(pl_op.operand);
const operand_ty = ptr_ty.elemType();
const operand = try self.resolveInst(extra.operand);
const is_signed_int = operand_ty.isSignedInt();
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 = self.dg.getAtomicAbiType(operand_ty, op == .Xchg);
if (opt_abi_ty) |abi_ty| {
// operand needs widening and truncating or bitcasting.
const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), "");
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,
casted_ptr,
casted_operand,
ordering,
single_threaded,
);
const operand_llvm_ty = try self.dg.llvmType(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 self.dg.llvmType(Type.usize);
const casted_ptr = self.builder.buildBitCast(ptr, usize_llvm_ty.pointerType(0), "");
const casted_operand = self.builder.buildPtrToInt(operand, usize_llvm_ty, "");
const uncasted_result = self.builder.buildAtomicRmw(
op,
casted_ptr,
casted_operand,
ordering,
single_threaded,
);
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
return self.builder.buildIntToPtr(uncasted_result, operand_llvm_ty, "");
}
fn airAtomicLoad(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const atomic_load = self.air.instructions.items(.data)[inst].atomic_load;
const ptr = try self.resolveInst(atomic_load.ptr);
const ptr_ty = self.air.typeOf(atomic_load.ptr);
if (!ptr_ty.isVolatilePtr() and self.liveness.isUnused(inst))
return null;
const ordering = toLlvmAtomicOrdering(atomic_load.order);
const operand_ty = ptr_ty.elemType();
const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false);
if (opt_abi_ty) |abi_ty| {
// operand needs widening and truncating
const casted_ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), "");
const load_inst = (try self.load(casted_ptr, ptr_ty)).?;
load_inst.setOrdering(ordering);
return self.builder.buildTrunc(load_inst, try self.dg.llvmType(operand_ty), "");
}
const load_inst = (try self.load(ptr, ptr_ty)).?;
load_inst.setOrdering(ordering);
return load_inst;
}
fn airAtomicStore(
self: *FuncGen,
inst: Air.Inst.Index,
ordering: llvm.AtomicOrdering,
) !?*const llvm.Value {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const ptr_ty = self.air.typeOf(bin_op.lhs);
const operand_ty = ptr_ty.childType();
if (!operand_ty.hasCodeGenBits()) return null;
var ptr = try self.resolveInst(bin_op.lhs);
var element = try self.resolveInst(bin_op.rhs);
const opt_abi_ty = self.dg.getAtomicAbiType(operand_ty, false);
if (opt_abi_ty) |abi_ty| {
// operand needs widening
ptr = self.builder.buildBitCast(ptr, abi_ty.pointerType(0), "");
if (operand_ty.isSignedInt()) {
element = self.builder.buildSExt(element, abi_ty, "");
} else {
element = self.builder.buildZExt(element, abi_ty, "");
}
}
self.store(ptr, ptr_ty, element, ordering);
return null;
}
fn airMemset(self: *FuncGen, inst: Air.Inst.Index) !?*const 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 dest_ptr = try self.resolveInst(pl_op.operand);
const ptr_ty = self.air.typeOf(pl_op.operand);
const value = try self.resolveInst(extra.lhs);
const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndefDeep() else false;
const len = try self.resolveInst(extra.rhs);
const u8_llvm_ty = self.context.intType(8);
const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0);
const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, "");
const fill_char = if (val_is_undef) u8_llvm_ty.constInt(0xaa, .False) else value;
const target = self.dg.module.getTarget();
const dest_ptr_align = ptr_ty.ptrAlignment(target);
_ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr());
if (val_is_undef and self.dg.module.comp.bin_file.options.valgrind) {
// TODO generate valgrind client request to mark byte range as undefined
// see gen_valgrind_undef() in codegen.cpp
}
return null;
}
fn airMemcpy(self: *FuncGen, inst: Air.Inst.Index) !?*const 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 dest_ptr = try self.resolveInst(pl_op.operand);
const dest_ptr_ty = self.air.typeOf(pl_op.operand);
const src_ptr = try self.resolveInst(extra.lhs);
const src_ptr_ty = self.air.typeOf(extra.lhs);
const len = try self.resolveInst(extra.rhs);
const llvm_ptr_u8 = self.context.intType(8).pointerType(0);
const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, llvm_ptr_u8, "");
const src_ptr_u8 = self.builder.buildBitCast(src_ptr, llvm_ptr_u8, "");
const is_volatile = src_ptr_ty.isVolatilePtr() or dest_ptr_ty.isVolatilePtr();
const target = self.dg.module.getTarget();
_ = self.builder.buildMemCpy(
dest_ptr_u8,
dest_ptr_ty.ptrAlignment(target),
src_ptr_u8,
src_ptr_ty.ptrAlignment(target),
len,
is_volatile,
);
return null;
}
fn airSetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const un_ty = self.air.typeOf(bin_op.lhs).childType();
const target = self.dg.module.getTarget();
const layout = un_ty.unionGetLayout(target);
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) {
_ = self.builder.buildStore(new_tag, union_ptr);
return null;
}
const tag_index = @boolToInt(layout.tag_align < layout.payload_align);
const tag_field_ptr = self.builder.buildStructGEP(union_ptr, tag_index, "");
_ = self.builder.buildStore(new_tag, tag_field_ptr);
return null;
}
fn airGetUnionTag(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const un_ty = self.air.typeOf(ty_op.operand);
const target = self.dg.module.getTarget();
const layout = un_ty.unionGetLayout(target);
if (layout.tag_size == 0) return null;
const union_handle = try self.resolveInst(ty_op.operand);
if (isByRef(un_ty)) {
if (layout.payload_size == 0) {
return self.builder.buildLoad(union_handle, "");
}
const tag_index = @boolToInt(layout.tag_align < layout.payload_align);
const tag_field_ptr = self.builder.buildStructGEP(union_handle, tag_index, "");
return self.builder.buildLoad(tag_field_ptr, "");
} else {
if (layout.payload_size == 0) {
return union_handle;
}
const tag_index = @boolToInt(layout.tag_align < layout.payload_align);
return self.builder.buildExtractValue(union_handle, tag_index, "");
}
}
fn airClzCtz(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand_ty = self.air.typeOf(ty_op.operand);
const operand = try self.resolveInst(ty_op.operand);
const target = self.dg.module.getTarget();
const bits = operand_ty.intInfo(target).bits;
var fn_name_buf: [100]u8 = undefined;
const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
prefix, bits,
}) catch unreachable;
const llvm_i1 = self.context.intType(1);
const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
const param_types = [_]*const llvm.Type{ operand_llvm_ty, llvm_i1 };
const fn_type = llvm.functionType(operand_llvm_ty, &param_types, param_types.len, .False);
break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
};
const params = [_]*const llvm.Value{ operand, llvm_i1.constNull() };
const wrong_size_result = self.builder.buildCall(fn_val, &params, params.len, .C, .Auto, "");
const result_ty = self.air.typeOfIndex(inst);
const result_llvm_ty = try self.dg.llvmType(result_ty);
const result_bits = result_ty.intInfo(target).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 airPopCount(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
const operand_ty = self.air.typeOf(ty_op.operand);
const operand = try self.resolveInst(ty_op.operand);
const target = self.dg.module.getTarget();
const bits = operand_ty.intInfo(target).bits;
var fn_name_buf: [100]u8 = undefined;
const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
prefix, bits,
}) catch unreachable;
const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
const param_types = [_]*const llvm.Type{operand_llvm_ty};
const fn_type = llvm.functionType(operand_llvm_ty, &param_types, param_types.len, .False);
break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
};
const params = [_]*const llvm.Value{operand};
const wrong_size_result = self.builder.buildCall(fn_val, &params, params.len, .C, .Auto, "");
const result_ty = self.air.typeOfIndex(inst);
const result_llvm_ty = try self.dg.llvmType(result_ty);
const result_bits = result_ty.intInfo(target).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 airTagName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
var arena_allocator = std.heap.ArenaAllocator.init(self.gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
const enum_ty = self.air.typeOf(un_op);
const llvm_fn_name = try std.fmt.allocPrintZ(arena, "__zig_tag_name_{s}", .{
try enum_ty.getOwnerDecl().getFullyQualifiedName(arena),
});
const llvm_fn = try self.getEnumTagNameFunction(enum_ty, llvm_fn_name);
const params = [_]*const llvm.Value{operand};
return self.builder.buildCall(llvm_fn, &params, params.len, .Fast, .Auto, "");
}
fn getEnumTagNameFunction(
self: *FuncGen,
enum_ty: Type,
llvm_fn_name: [:0]const u8,
) !*const llvm.Value {
// TODO: detect when the type changes and re-emit this function.
if (self.dg.object.llvm_module.getNamedFunction(llvm_fn_name)) |llvm_fn| {
return llvm_fn;
}
const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
const llvm_ret_ty = try self.dg.llvmType(slice_ty);
const usize_llvm_ty = try self.dg.llvmType(Type.usize);
const target = self.dg.module.getTarget();
const slice_alignment = slice_ty.abiAlignment(target);
var int_tag_type_buffer: Type.Payload.Bits = undefined;
const int_tag_ty = enum_ty.intTagType(&int_tag_type_buffer);
const param_types = [_]*const llvm.Type{try self.dg.llvmType(int_tag_ty)};
const fn_type = llvm.functionType(llvm_ret_ty, &param_types, param_types.len, .False);
const fn_val = self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
fn_val.setLinkage(.Internal);
fn_val.setFunctionCallConv(.Fast);
self.dg.addCommonFnAttributes(fn_val);
const prev_block = self.builder.getInsertBlock();
const prev_debug_location = self.builder.getCurrentDebugLocation2();
defer {
self.builder.positionBuilderAtEnd(prev_block);
if (!self.dg.module.comp.bin_file.options.strip) {
self.builder.setCurrentDebugLocation2(prev_debug_location);
}
}
const entry_block = self.dg.context.appendBasicBlock(fn_val, "Entry");
self.builder.positionBuilderAtEnd(entry_block);
self.builder.clearCurrentDebugLocation();
const fields = enum_ty.enumFields();
const bad_value_block = self.dg.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, @intCast(c_uint, fields.count()));
const array_ptr_indices = [_]*const llvm.Value{
usize_llvm_ty.constNull(), usize_llvm_ty.constNull(),
};
for (fields.keys()) |name, field_index| {
const str_init = self.dg.context.constString(name.ptr, @intCast(c_uint, name.len), .False);
const str_global = self.dg.object.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 = [_]*const llvm.Value{
str_global.constInBoundsGEP(&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 = self.dg.object.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.dg.context.appendBasicBlock(fn_val, "Name");
const this_tag_int_value = int: {
var tag_val_payload: Value.Payload.U32 = .{
.base = .{ .tag = .enum_field_index },
.data = @intCast(u32, field_index),
};
break :int try self.dg.genTypedValue(.{
.ty = enum_ty,
.val = Value.initPayload(&tag_val_payload.base),
});
};
switch_instr.addCase(this_tag_int_value, return_block);
self.builder.positionBuilderAtEnd(return_block);
const loaded = self.builder.buildLoad(slice_global, "");
loaded.setAlignment(slice_alignment);
_ = self.builder.buildRet(loaded);
}
self.builder.positionBuilderAtEnd(bad_value_block);
_ = self.builder.buildUnreachable();
return fn_val;
}
fn airErrorName(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) return null;
const un_op = self.air.instructions.items(.data)[inst].un_op;
const operand = try self.resolveInst(un_op);
const error_name_table_ptr = try self.getErrorNameTable();
const error_name_table = self.builder.buildLoad(error_name_table_ptr, "");
const indices = [_]*const llvm.Value{operand};
const error_name_ptr = self.builder.buildInBoundsGEP(error_name_table, &indices, indices.len, "");
return self.builder.buildLoad(error_name_ptr, "");
}
fn getErrorNameTable(self: *FuncGen) !*const llvm.Value {
if (self.dg.object.error_name_table) |table| {
return table;
}
const slice_ty = Type.initTag(.const_slice_u8_sentinel_0);
const slice_alignment = slice_ty.abiAlignment(self.dg.module.getTarget());
const llvm_slice_ty = try self.dg.llvmType(slice_ty);
const llvm_slice_ptr_ty = llvm_slice_ty.pointerType(0); // TODO: Address space
const error_name_table_global = self.dg.object.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);
self.dg.object.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_handle: *const llvm.Value, is_by_ref: bool) *const llvm.Value {
if (is_by_ref) {
const index_type = self.context.intType(32);
const indices: [2]*const llvm.Value = .{
index_type.constNull(),
index_type.constInt(1, .False),
};
const field_ptr = self.builder.buildInBoundsGEP(opt_handle, &indices, indices.len, "");
return self.builder.buildLoad(field_ptr, "");
}
return self.builder.buildExtractValue(opt_handle, 1, "");
}
/// Assumes the optional is not pointer-like and payload has bits.
fn optPayloadHandle(self: *FuncGen, opt_handle: *const llvm.Value, is_by_ref: bool) *const llvm.Value {
if (is_by_ref) {
// We have a pointer and we need to return a pointer to the first field.
const index_type = self.context.intType(32);
const indices: [2]*const llvm.Value = .{
index_type.constNull(), // dereference the pointer
index_type.constNull(), // first field is the payload
};
return self.builder.buildInBoundsGEP(opt_handle, &indices, indices.len, "");
}
return self.builder.buildExtractValue(opt_handle, 0, "");
}
fn callFloor(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
return self.callFloatUnary(arg, ty, "floor");
}
fn callCeil(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
return self.callFloatUnary(arg, ty, "ceil");
}
fn callTrunc(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
return self.callFloatUnary(arg, ty, "trunc");
}
fn callFloatUnary(self: *FuncGen, arg: *const llvm.Value, ty: Type, name: []const u8) !*const llvm.Value {
const target = self.dg.module.getTarget();
var fn_name_buf: [100]u8 = undefined;
const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.f{d}", .{
name, ty.floatBits(target),
}) catch unreachable;
const llvm_fn = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
const operand_llvm_ty = try self.dg.llvmType(ty);
const param_types = [_]*const llvm.Type{operand_llvm_ty};
const fn_type = llvm.functionType(operand_llvm_ty, &param_types, param_types.len, .False);
break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
};
const args: [1]*const llvm.Value = .{arg};
return self.builder.buildCall(llvm_fn, &args, args.len, .C, .Auto, "");
}
fn fieldPtr(
self: *FuncGen,
inst: Air.Inst.Index,
struct_ptr: *const llvm.Value,
struct_ptr_ty: Type,
field_index: u32,
) !?*const llvm.Value {
const struct_ty = struct_ptr_ty.childType();
switch (struct_ty.zigTypeTag()) {
.Struct => {
const target = self.dg.module.getTarget();
var ty_buf: Type.Payload.Pointer = undefined;
if (llvmFieldIndex(struct_ty, field_index, target, &ty_buf)) |llvm_field_index| {
return self.builder.buildStructGEP(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_usize = try self.dg.llvmType(Type.usize);
const llvm_index = llvm_usize.constInt(1, .False);
const indices: [1]*const llvm.Value = .{llvm_index};
return self.builder.buildInBoundsGEP(struct_ptr, &indices, indices.len, "");
}
},
.Union => return self.unionFieldPtr(inst, struct_ptr, struct_ty, field_index),
else => unreachable,
}
}
fn unionFieldPtr(
self: *FuncGen,
inst: Air.Inst.Index,
union_ptr: *const llvm.Value,
union_ty: Type,
field_index: c_uint,
) !?*const llvm.Value {
const union_obj = union_ty.cast(Type.Payload.Union).?.data;
const field = &union_obj.fields.values()[field_index];
const result_llvm_ty = try self.dg.llvmType(self.air.typeOfIndex(inst));
if (!field.ty.hasCodeGenBits()) {
return null;
}
const target = self.dg.module.getTarget();
const layout = union_ty.unionGetLayout(target);
const payload_index = @boolToInt(layout.tag_align >= layout.payload_align);
const union_field_ptr = self.builder.buildStructGEP(union_ptr, payload_index, "");
return self.builder.buildBitCast(union_field_ptr, result_llvm_ty, "");
}
fn sliceElemPtr(
self: *FuncGen,
slice: *const llvm.Value,
index: *const llvm.Value,
) *const llvm.Value {
const base_ptr = self.builder.buildExtractValue(slice, 0, "");
const indices: [1]*const llvm.Value = .{index};
return self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
}
fn getIntrinsic(self: *FuncGen, name: []const u8, types: []*const llvm.Type) *const llvm.Value {
const id = llvm.lookupIntrinsicID(name.ptr, name.len);
assert(id != 0);
return self.llvmModule().getIntrinsicDeclaration(id, types.ptr, types.len);
}
fn load(self: *FuncGen, ptr: *const llvm.Value, ptr_ty: Type) !?*const llvm.Value {
const info = ptr_ty.ptrInfo().data;
if (!info.pointee_type.hasCodeGenBits()) return null;
const target = self.dg.module.getTarget();
const ptr_alignment = ptr_ty.ptrAlignment(target);
const ptr_volatile = llvm.Bool.fromBool(ptr_ty.isVolatilePtr());
if (info.host_size == 0) {
if (isByRef(info.pointee_type)) return ptr;
const llvm_inst = self.builder.buildLoad(ptr, "");
llvm_inst.setAlignment(ptr_alignment);
llvm_inst.setVolatile(ptr_volatile);
return llvm_inst;
}
const int_ptr_ty = self.context.intType(info.host_size * 8).pointerType(0);
const int_ptr = self.builder.buildBitCast(ptr, int_ptr_ty, "");
const containing_int = self.builder.buildLoad(int_ptr, "");
containing_int.setAlignment(ptr_alignment);
containing_int.setVolatile(ptr_volatile);
const elem_bits = @intCast(c_uint, ptr_ty.elemType().bitSize(target));
const shift_amt = containing_int.typeOf().constInt(info.bit_offset, .False);
const shifted_value = self.builder.buildLShr(containing_int, shift_amt, "");
const elem_llvm_ty = try self.dg.llvmType(info.pointee_type);
if (isByRef(info.pointee_type)) {
const result_align = info.pointee_type.abiAlignment(target);
const result_ptr = self.buildAlloca(elem_llvm_ty);
result_ptr.setAlignment(result_align);
const same_size_int = self.context.intType(elem_bits);
const truncated_int = self.builder.buildTrunc(shifted_value, same_size_int, "");
const bitcasted_ptr = self.builder.buildBitCast(result_ptr, same_size_int.pointerType(0), "");
const store_inst = self.builder.buildStore(truncated_int, bitcasted_ptr);
store_inst.setAlignment(result_align);
return result_ptr;
}
if (info.pointee_type.zigTypeTag() == .Float) {
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, "");
}
return self.builder.buildTrunc(shifted_value, elem_llvm_ty, "");
}
fn store(
self: *FuncGen,
ptr: *const llvm.Value,
ptr_ty: Type,
elem: *const llvm.Value,
ordering: llvm.AtomicOrdering,
) void {
const info = ptr_ty.ptrInfo().data;
const elem_ty = info.pointee_type;
if (!elem_ty.hasCodeGenBits()) {
return;
}
const target = self.dg.module.getTarget();
const ptr_alignment = ptr_ty.ptrAlignment(target);
const ptr_volatile = llvm.Bool.fromBool(info.@"volatile");
if (info.host_size != 0) {
const int_ptr_ty = self.context.intType(info.host_size * 8).pointerType(0);
const int_ptr = self.builder.buildBitCast(ptr, int_ptr_ty, "");
const containing_int = self.builder.buildLoad(int_ptr, "");
assert(ordering == .NotAtomic);
containing_int.setAlignment(ptr_alignment);
containing_int.setVolatile(ptr_volatile);
const elem_bits = @intCast(c_uint, ptr_ty.elemType().bitSize(target));
const containing_int_ty = containing_int.typeOf();
const shift_amt = containing_int_ty.constInt(info.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 = 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, int_ptr);
assert(ordering == .NotAtomic);
store_inst.setAlignment(ptr_alignment);
store_inst.setVolatile(ptr_volatile);
return;
}
if (!isByRef(elem_ty)) {
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 llvm_ptr_u8 = self.context.intType(8).pointerType(0);
const size_bytes = elem_ty.abiSize(target);
_ = self.builder.buildMemCpy(
self.builder.buildBitCast(ptr, llvm_ptr_u8, ""),
ptr_ty.ptrAlignment(target),
self.builder.buildBitCast(elem, llvm_ptr_u8, ""),
elem_ty.abiAlignment(target),
self.context.intType(Type.usize.intInfo(target).bits).constInt(size_bytes, .False),
info.@"volatile",
);
}
};
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, .sparcv9, .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();
},
.i386, .x86_64 => {
llvm.LLVMInitializeX86Target();
llvm.LLVMInitializeX86TargetInfo();
llvm.LLVMInitializeX86TargetMC();
llvm.LLVMInitializeX86AsmPrinter();
llvm.LLVMInitializeX86AsmParser();
},
.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 => {
if (build_options.llvm_has_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,
=> {},
.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) llvm.AtomicRMWBinOp.FAdd else return .Add,
.Sub => if (is_float) llvm.AtomicRMWBinOp.FSub else return .Sub,
.And => .And,
.Nand => .Nand,
.Or => .Or,
.Xor => .Xor,
.Max => if (is_signed) llvm.AtomicRMWBinOp.Max else return .UMax,
.Min => if (is_signed) llvm.AtomicRMWBinOp.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) {
.i386, .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) {
.i386, .x86_64 => .X86_INTR,
.avr => .AVR_INTR,
.msp430 => .MSP430_INTR,
else => unreachable,
},
.Signal => .AVR_SIGNAL,
.SysV => .X86_64_SysV,
};
}
/// Take into account 0 bit fields. Returns null if an llvm field could not be found. This only
/// happends if you want the field index of a zero sized field at the end of the struct.
fn llvmFieldIndex(
ty: Type,
field_index: u32,
target: std.Target,
ptr_pl_buf: *Type.Payload.Pointer,
) ?c_uint {
const struct_obj = ty.castTag(.@"struct").?.data;
if (struct_obj.layout != .Packed) {
var llvm_field_index: c_uint = 0;
for (struct_obj.fields.values()) |field, i| {
if (!field.ty.hasCodeGenBits())
continue;
if (field_index > i) {
llvm_field_index += 1;
continue;
}
ptr_pl_buf.* = .{
.data = .{
.pointee_type = field.ty,
.@"align" = field.normalAlignment(target),
.@"addrspace" = .generic,
},
};
return llvm_field_index;
} else {
// We did not find an llvm field that corrispons to this zig field.
return null;
}
}
// Our job here is to return the host integer field index.
comptime assert(Type.packed_struct_layout_version == 1);
var offset: u64 = 0;
var running_bits: u16 = 0;
var llvm_field_index: c_uint = 0;
for (struct_obj.fields.values()) |field, i| {
if (!field.ty.hasCodeGenBits())
continue;
const field_align = field.packedAlignment();
if (field_align == 0) {
if (i == field_index) {
ptr_pl_buf.* = .{
.data = .{
.pointee_type = field.ty,
.bit_offset = running_bits,
.@"addrspace" = .generic,
},
};
}
running_bits += @intCast(u16, field.ty.bitSize(target));
} else {
if (running_bits != 0) {
var int_payload: Type.Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
if (i > field_index) {
ptr_pl_buf.data.host_size = @intCast(u16, int_ty.abiSize(target));
return llvm_field_index;
}
const int_align = int_ty.abiAlignment(target);
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, int_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
llvm_field_index += 1;
}
llvm_field_index += 1;
offset += int_ty.abiSize(target);
running_bits = 0;
}
const prev_offset = offset;
offset = std.mem.alignForwardGeneric(u64, offset, field_align);
const padding_bytes = @intCast(c_uint, offset - prev_offset);
if (padding_bytes != 0) {
llvm_field_index += 1;
}
if (i == field_index) {
ptr_pl_buf.* = .{
.data = .{
.pointee_type = field.ty,
.@"align" = field_align,
.@"addrspace" = .generic,
},
};
return llvm_field_index;
}
llvm_field_index += 1;
offset += field.ty.abiSize(target);
}
}
assert(running_bits != 0);
var int_payload: Type.Payload.Bits = .{
.base = .{ .tag = .int_unsigned },
.data = running_bits,
};
const int_ty: Type = .{ .ptr_otherwise = &int_payload.base };
ptr_pl_buf.data.host_size = @intCast(u16, int_ty.abiSize(target));
return llvm_field_index;
}
fn firstParamSRet(fn_info: Type.Payload.Function.Data, target: std.Target) bool {
switch (fn_info.cc) {
.Unspecified, .Inline => return isByRef(fn_info.return_type),
.C => {},
else => return false,
}
switch (target.cpu.arch) {
.mips, .mipsel => return false,
.x86_64 => switch (target.os.tag) {
.windows => return @import("../arch/x86_64/abi.zig").classifyWindows(fn_info.return_type, target) == .memory,
else => return @import("../arch/x86_64/abi.zig").classifySystemV(fn_info.return_type, target)[0] == .memory,
},
else => return false, // TODO investigate C ABI for other architectures
}
}
fn isByRef(ty: Type) bool {
switch (ty.zigTypeTag()) {
.Type,
.ComptimeInt,
.ComptimeFloat,
.EnumLiteral,
.Undefined,
.Null,
.BoundFn,
.Opaque,
=> unreachable,
.NoReturn,
.Void,
.Bool,
.Int,
.Float,
.Pointer,
.ErrorSet,
.Fn,
.Enum,
.Vector,
.AnyFrame,
=> return false,
.Array, .Struct, .Frame => return ty.hasCodeGenBits(),
.Union => return ty.hasCodeGenBits(),
.ErrorUnion => return isByRef(ty.errorUnionPayload()),
.Optional => {
var buf: Type.Payload.ElemType = undefined;
return isByRef(ty.optionalChild(&buf));
},
}
}