// Copyright (c) 2019 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the GNU LGPLv3.0 license
// a copy of which can be found in the LICENSE file.

#include "llvm_codegen_internal.h"
#include "compiler_tests/benchmark.h"

#include <llvm-c/Error.h>
#include <llvm-c/Comdat.h>
#include <llvm-c/Linker.h>

typedef struct LLVMOpaquePassBuilderOptions *LLVMPassBuilderOptionsRef;
LLVMErrorRef LLVMRunPasses(LLVMModuleRef M, const char *Passes,
                           LLVMTargetMachineRef TM,
                           LLVMPassBuilderOptionsRef Options);
LLVMPassBuilderOptionsRef LLVMCreatePassBuilderOptions(void);
void LLVMPassBuilderOptionsSetVerifyEach(LLVMPassBuilderOptionsRef Options, LLVMBool VerifyEach);
void LLVMPassBuilderOptionsSetDebugLogging(LLVMPassBuilderOptionsRef Options, LLVMBool DebugLogging);
void LLVMDisposePassBuilderOptions(LLVMPassBuilderOptionsRef Options);

static void llvm_emit_constructors_and_destructors(GenContext *c);
static void llvm_codegen_setup();
static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context);

const char* llvm_version = LLVM_VERSION_STRING;
const char* llvm_target = LLVM_DEFAULT_TARGET_TRIPLE;

static void diagnostics_handler(LLVMDiagnosticInfoRef ref, void *context)
{
	char *message = LLVMGetDiagInfoDescription(ref);
	LLVMDiagnosticSeverity severity = LLVMGetDiagInfoSeverity(ref);
	const char *severity_name;
	switch (severity)
	{
		case LLVMDSError:
			error_exit("LLVM error generating code for %s: %s", ((GenContext *)context)->code_module->name, message);
		case LLVMDSWarning:
			severity_name = "warning";
			break;
		case LLVMDSRemark:
			severity_name = "remark";
			break;
		case LLVMDSNote:
			severity_name = "note";
			break;
		default:
			severity_name = "message";
			break;
	}
#ifdef NDEBUG
	// Avoid warnings when not in debug.
	(void)severity_name; (void)message;
#endif
	DEBUG_LOG("LLVM %s: %s ", severity_name, message);
	LLVMDisposeMessage(message);
}

static void gencontext_init(GenContext *context, Module *module, LLVMContextRef shared_context)
{
	assert(LLVMIsMultithreaded());
	memset(context, 0, sizeof(GenContext));
	if (shared_context)
	{
		context->shared_context = true;
		context->context = shared_context;
	}
	else
	{
		context->context = LLVMContextCreate();
	}
	if (debug_log)
	{
		LLVMContextSetDiagnosticHandler(context->context, &diagnostics_handler, context);
	}
	if (!active_target.emit_llvm && !active_target.test_output) LLVMContextSetDiscardValueNames(context->context, true);
	context->code_module = module;
}

static void gencontext_destroy(GenContext *context)
{
	assert(llvm_is_global_eval(context));
	LLVMDisposeBuilder(context->global_builder);
	if (!context->shared_context) LLVMContextDispose(context->context);
	LLVMDisposeTargetData(context->target_data);
	LLVMDisposeTargetMachine(context->machine);
	free(context);
}

LLVMValueRef llvm_emit_is_no_opt(GenContext *c, LLVMValueRef error_value)
{
	LLVMValueRef compare = LLVMBuildICmp(c->builder, LLVMIntEQ, error_value, llvm_get_zero(c, type_anyfault), "not_err");
	LLVMValueRef values[2] = { compare, LLVMConstInt(c->bool_type, 1, false) };
	return llvm_emit_call_intrinsic(c, intrinsic_id.expect, &c->bool_type, 1, values, 2);
}

LLVMValueRef llvm_emit_memclear_size_align(GenContext *c, LLVMValueRef ptr, uint64_t size, AlignSize align)
{
	return LLVMBuildMemSet(c->builder, ptr, llvm_get_zero(c, type_char), llvm_const_int(c, type_usz, size), align);
}

INLINE void llvm_emit_xtor(GenContext *c, LLVMValueRef *list, const char *name)
{
	if (!list) return;
	unsigned len = vec_size(list);
	LLVMTypeRef type = LLVMTypeOf(list[0]);
	LLVMValueRef array = LLVMConstArray(type, list, len);
	LLVMValueRef global = LLVMAddGlobal(c->module, LLVMTypeOf(array), name);
	LLVMSetLinkage(global, LLVMAppendingLinkage);
	LLVMSetInitializer(global, array);
}
void llvm_emit_constructors_and_destructors(GenContext *c)
{
	llvm_emit_xtor(c, c->constructors, "llvm.global_ctors");
	llvm_emit_xtor(c, c->destructors, "llvm.global_dtors");
}

/**
 * Consider the case when we have int[5] x = { [0] = 1, [1] = 3 }
 * In this case we want this: { i32 0, i32 2, [8 x i32] zeroinitializer }
 * If it's just a single element we don't use [1 x i32] but just i32 0. If it's
 * an array then this is modifying the original type.
 */
static LLVMValueRef llvm_emit_const_array_padding(LLVMTypeRef element_type, IndexDiff diff, bool *modified)
{
	if (diff == 1) return llvm_get_zero_raw(element_type);
	*modified = true;
	return llvm_get_zero_raw(LLVMArrayType(element_type, (unsigned)diff));
}

LLVMValueRef llvm_emit_const_initializer(GenContext *c, ConstInitializer *const_init)
{
	switch (const_init->kind)
	{
		case CONST_INIT_ZERO:
			return llvm_get_zero(c, const_init->type);
		case CONST_INIT_ARRAY_VALUE:
			UNREACHABLE
		case CONST_INIT_ARRAY_FULL:
		{
			bool was_modified = false;
			Type *array_type = const_init->type;
			Type *element_type = array_type->array.base;
			LLVMTypeRef element_type_llvm = llvm_get_type(c, element_type);
			ConstInitializer **elements = const_init->init_array_full;
			assert(array_type->type_kind == TYPE_ARRAY || array_type->type_kind == TYPE_VECTOR);
			ArraySize size = array_type->array.len;
			assert(size > 0);
			LLVMValueRef *parts = VECNEW(LLVMValueRef, size);
			for (MemberIndex i = 0; i < (MemberIndex)size; i++)
			{
				LLVMValueRef element = llvm_emit_const_initializer(c, elements[i]);
				if (element_type_llvm != LLVMTypeOf(element)) was_modified = true;
				vec_add(parts, element);
			}
			if (array_type->type_kind == TYPE_VECTOR)
			{
				return LLVMConstVector(parts, vec_size(parts));
			}
			if (was_modified)
			{
				return llvm_get_unnamed_struct(c, parts, true);
			}
			return llvm_get_array(element_type_llvm, parts, vec_size(parts));
		}

		case CONST_INIT_ARRAY:
		{
			bool was_modified = false;
			Type *array_type = const_init->type;
			Type *element_type = array_type->array.base;
			LLVMTypeRef element_type_llvm = llvm_get_type(c, element_type);
			AlignSize expected_align = llvm_abi_alignment(c, element_type_llvm);
			ConstInitializer **elements = const_init->init_array.elements;
			unsigned element_count = vec_size(elements);
			assert(element_count > 0 && "Array should always have gotten at least one element.");
			MemberIndex current_index = 0;
			unsigned alignment = 0;
			LLVMValueRef *parts = NULL;
			bool pack = false;
			VECEACH(elements, i)
			{
				ConstInitializer *element = elements[i];
				assert(element->kind == CONST_INIT_ARRAY_VALUE);
				MemberIndex element_index = element->init_array_value.index;
				IndexDiff diff = element_index - current_index;
				if (alignment && expected_align != alignment)
				{
					pack = true;
				}
				alignment = expected_align;
				// Add zeroes
				if (diff > 0)
				{
					vec_add(parts, llvm_emit_const_array_padding(element_type_llvm, diff, &was_modified));
				}
				LLVMValueRef value = llvm_emit_const_initializer(c, element->init_array_value.element);
				if (LLVMTypeOf(value) != element_type_llvm) was_modified = true;
				vec_add(parts, value);
				current_index = element_index + 1;
			}

			IndexDiff end_diff = (MemberIndex)array_type->array.len - current_index;
			if (end_diff > 0)
			{
				vec_add(parts, llvm_emit_const_array_padding(element_type_llvm, end_diff, &was_modified));
			}
			if (was_modified)
			{
				return llvm_get_unnamed_struct(c, parts, pack);
			}
			if (type_flat_is_vector(array_type))
			{
				return LLVMConstVector(parts, vec_size(parts));
			}
			return llvm_get_array(element_type_llvm, parts, vec_size(parts));
		}
		case CONST_INIT_UNION:
		{
			Decl *decl = const_init->type->decl;

			// Emit our value.
			LLVMValueRef result = llvm_emit_const_initializer(c, const_init->init_union.element);
			LLVMTypeRef result_type = LLVMTypeOf(result);

			// Get the union value
			LLVMTypeRef union_type_llvm = llvm_get_type(c, decl->type);

			// Take the first type in the union (note that there may be padding!)
			LLVMTypeRef first_type = LLVMStructGetTypeAtIndex(union_type_llvm, 0);

			// We need to calculate some possible padding.
			TypeSize union_size = type_size(const_init->type);
			TypeSize member_size = llvm_abi_size(c, result_type);

			// Create the resulting values:
			LLVMValueRef values[2] = { result, NULL };
			unsigned value_count = 1;

			// Add possible padding as and i8 array.
			if (union_size > member_size)
			{
				values[1] = llvm_emit_const_padding(c, union_size - member_size);
				value_count = 2;
			}

			// Is this another type than usual for the union?
			if (first_type != result_type)
			{
				// Yes, so the type needs to be modified.
				return llvm_get_struct(c, values, value_count);
			}

			return llvm_get_struct_named(union_type_llvm, values, value_count);
		}
		case CONST_INIT_STRUCT:
		{
			if (const_init->type->type_kind == TYPE_BITSTRUCT)
			{
				return llvm_emit_const_bitstruct(c, const_init);
			}
			Decl *decl = const_init->type->decl;
			Decl **members = decl->strukt.members;
			bool is_packed = decl->is_packed;
			uint32_t count = vec_size(members);
			if (decl->decl_kind == DECL_UNION && count) count = 1;
			LLVMValueRef *entries = NULL;
			bool was_modified = false;
			ByteSize prev_size = 0;
			for (MemberIndex i = 0; i < count; i++)
			{
				if (members[i]->padding)
				{
					vec_add(entries, llvm_emit_const_padding(c, members[i]->padding));
				}
				LLVMTypeRef expected_type = llvm_get_type(c, const_init->init_struct[i]->type);
				LLVMValueRef element = llvm_emit_const_initializer(c, const_init->init_struct[i]);
				LLVMTypeRef element_type = LLVMTypeOf(element);
				assert(LLVMIsConstant(element));
				if (expected_type != element_type)
				{
					was_modified = true;
				}
				// We may need to adjust alignment here due to the lack
				// of LLVM union support (while still being strict about structs)
				if (i > 0 && was_modified)
				{
					// Let's look at the old offset.
					ByteSize old_offset = members[i - 1]->offset;
					// What is the expected offset we would get?
					ByteSize new_offset = is_packed ? old_offset + prev_size : aligned_offset(old_offset + prev_size, llvm_abi_alignment(c, element_type));
					// Add the padding we have built in.
					new_offset += members[i]->padding;
					// If this offset is too small, add const padding.
					if (new_offset < members[i]->offset)
					{
						vec_add(entries, llvm_emit_const_padding(c, members[i]->offset - new_offset));
					}
				}
				prev_size = llvm_abi_size(c, element_type);
				vec_add(entries, element);
			}
			if (decl->strukt.padding)
			{
				vec_add(entries, llvm_emit_const_padding(c, decl->strukt.padding));
			}
			if (was_modified)
			{
				return llvm_get_unnamed_struct(c, entries, decl->is_packed);
			}
			return llvm_get_struct_of_type(c, const_init->type, entries, vec_size(entries));
		}
		case CONST_INIT_VALUE:
		{
			BEValue value;
			llvm_emit_expr(c, &value, const_init->init_value);
			return llvm_load_value_store(c, &value);
		}
	}
	UNREACHABLE
}


void llvm_emit_ptr_from_array(GenContext *c, BEValue *value)
{
	switch (value->type->type_kind)
	{
		case TYPE_POINTER:
			llvm_value_rvalue(c, value);
			value->kind = BE_ADDRESS;
			return;
		case TYPE_ARRAY:
		case TYPE_VECTOR:
		case TYPE_FLEXIBLE_ARRAY:
			return;
		case TYPE_SUBARRAY:
		{
			BEValue member;
			llvm_emit_subarray_pointer(c, value, &member);
			llvm_value_rvalue(c, &member);
			llvm_value_set_address(value,
			                       member.value,
			                       type_get_ptr(value->type->array.base),
			                       type_abi_alignment(value->type->array.base));
			return;
		}
		default:
			UNREACHABLE
	}
}

void llvm_set_global_tls(Decl *decl)
{
	if (!decl->var.is_threadlocal) return;
	LLVMThreadLocalMode thread_local_mode = LLVMGeneralDynamicTLSModel;
	if (!decl->var.is_addr && decl_is_local(decl))
	{
		thread_local_mode = LLVMLocalDynamicTLSModel;
	}
	LLVMSetThreadLocal(decl->backend_ref, true);
	LLVMSetThreadLocalMode(decl->backend_ref, thread_local_mode);
	void *optional_ref = decl->var.optional_ref;
	if (optional_ref)
	{
		LLVMSetThreadLocal(optional_ref, true);
		LLVMSetThreadLocalMode(optional_ref, thread_local_mode);
	}
}
void llvm_set_internal_linkage(LLVMValueRef alloc)
{
	LLVMSetLinkage(alloc, LLVMInternalLinkage);
	LLVMSetVisibility(alloc, LLVMDefaultVisibility);
}
void llvm_set_private_linkage(LLVMValueRef alloc)
{
	LLVMSetLinkage(alloc, LLVMPrivateLinkage);
	LLVMSetVisibility(alloc, LLVMDefaultVisibility);
}
void llvm_emit_global_variable_init(GenContext *c, Decl *decl)
{
	assert(decl->var.kind == VARDECL_GLOBAL || decl->var.kind == VARDECL_CONST || decl->var.is_static);

	// Skip real constants.
	if (!decl->type) return;

	LLVMValueRef init_value;

	Type *var_type = type_lowering(decl->type);

	Expr *init_expr = decl->var.init_expr;
	while (init_expr && init_expr->expr_kind == EXPR_IDENTIFIER
		&& init_expr->identifier_expr.decl->decl_kind == DECL_VAR
		&& init_expr->identifier_expr.decl->var.kind == VARDECL_CONST)
	{
		init_expr = init_expr->identifier_expr.decl->var.init_expr;
	}
	if (init_expr)
	{
		if (init_expr->expr_kind == EXPR_CONST && init_expr->const_expr.const_kind == CONST_INITIALIZER)
		{
			ConstInitializer *list = init_expr->const_expr.initializer;
			init_value = llvm_emit_const_initializer(c, list);
		}
		else
		{
			BEValue value;
			if (init_expr->expr_kind == EXPR_CONST && init_expr->const_expr.const_kind == CONST_BYTES)
			{
				init_value = llvm_get_bytes(c, init_expr->const_expr.bytes.ptr, init_expr->const_expr.bytes.len);
			}
			else
			{
				llvm_emit_expr(c, &value, decl->var.init_expr);
				init_value = llvm_load_value_store(c, &value);
			}
		}
	}
	else
	{
		init_value = decl->var.no_init ? llvm_get_undef(c, var_type) : llvm_get_zero(c, var_type);
	}


	LLVMValueRef old = decl->backend_ref;
	LLVMValueRef global_ref = decl->backend_ref = llvm_add_global_raw(c,
																	  decl_get_extname(decl),
	                                                                  LLVMTypeOf(init_value),
	                                                                  decl->alignment);
	if (decl->var.is_addr)
	{
		LLVMSetUnnamedAddress(global_ref, LLVMNoUnnamedAddr);
	}
	else
	{
		LLVMSetUnnamedAddress(decl->backend_ref,
		                      decl_is_local(decl) ? LLVMGlobalUnnamedAddr : LLVMLocalUnnamedAddr);
	}
	if (decl->section)
	{
		LLVMSetSection(global_ref, decl->section);
	}
	llvm_set_global_tls(decl);

	LLVMValueRef optional_ref = decl->var.optional_ref;
	if (optional_ref)
	{
		llvm_set_alignment(optional_ref, type_alloca_alignment(type_anyfault));
		LLVMSetUnnamedAddress(optional_ref, LLVMGlobalUnnamedAddr);
	}
	if (init_expr && IS_OPTIONAL(init_expr) && init_expr->expr_kind == EXPR_OPTIONAL)
	{
		UNREACHABLE
	}
	if (!decl->is_extern)
	{
		LLVMSetInitializer(decl->backend_ref, init_value);
		if (optional_ref)
		{
			LLVMSetInitializer(optional_ref, llvm_get_zero(c, type_anyfault));
		}
	}

	LLVMSetGlobalConstant(global_ref, decl->var.kind == VARDECL_CONST);

	if (decl->is_extern)
	{
		LLVMSetLinkage(global_ref, LLVMExternalLinkage);
		if (optional_ref) LLVMSetLinkage(optional_ref, LLVMExternalLinkage);
	}
	else if (decl_is_externally_visible(decl))
	{
		LLVMSetVisibility(global_ref, LLVMDefaultVisibility);
		if (optional_ref) LLVMSetVisibility(optional_ref, LLVMDefaultVisibility);
	}
	else
	{
		if (decl->var.kind == VARDECL_CONST || decl->var.kind == VARDECL_GLOBAL)
		{
			LLVMSetVisibility(global_ref, LLVMProtectedVisibility);
			if (optional_ref) LLVMSetVisibility(optional_ref, LLVMProtectedVisibility);
		}
		else
		{
			LLVMSetLinkage(global_ref, LLVMInternalLinkage);
			if (optional_ref) LLVMSetLinkage(optional_ref, LLVMInternalLinkage);
		}
	}

	decl->backend_ref = global_ref;
	LLVMReplaceAllUsesWith(old, global_ref);
	LLVMDeleteGlobal(old);

	// Should we set linkage here?
	if (llvm_use_debug(c))
	{
		llvm_emit_debug_global_var(c, decl);
	}
}
static void gencontext_verify_ir(GenContext *context)
{
	char *error = NULL;
	assert(context->module);
	if (LLVMVerifyModule(context->module, LLVMPrintMessageAction, &error))
	{
		if (*error)
		{
			error_exit("Could not verify IR: %s", error);
		}
		error_exit("Could not verify module IR.");
	}
}


static void gencontext_emit_object_file(GenContext *context)
{
	char *err = "";
	DEBUG_LOG("Target: %s", platform_target.target_triple);
	LLVMSetTarget(context->module, platform_target.target_triple);
	char *layout = LLVMCopyStringRepOfTargetData(context->target_data);
	LLVMSetDataLayout(context->module, layout);
	LLVMDisposeMessage(layout);

	if (context->asm_filename)
	{
		// Generate .s file
		if (LLVMTargetMachineEmitToFile(context->machine, context->module, (char *)context->asm_filename, LLVMAssemblyFile, &err))
		{
			error_exit("Could not emit asm file: %s", err);
		}
	}

	// Generate .o or .obj file
	if (LLVMTargetMachineEmitToFile(context->machine, context->module, (char *)context->object_filename, LLVMObjectFile, &err))
	{
		error_exit("Could not emit object file: %s", err);
	}

}

static void llvm_emit_asm_file(GenContext *context)
{
	char *err = "";
	DEBUG_LOG("Target: %s", platform_target.target_triple);
	LLVMSetTarget(context->module, platform_target.target_triple);
	char *layout = LLVMCopyStringRepOfTargetData(context->target_data);
	LLVMSetDataLayout(context->module, layout);
	LLVMDisposeMessage(layout);

	// Generate .s file
	if (LLVMTargetMachineEmitToFile(context->machine, context->module, (char *)context->asm_filename, LLVMAssemblyFile, &err))
	{
		error_exit("Could not emit asm file: %s", err);
	}
}

void gencontext_print_llvm_ir(GenContext *context)
{
	char *err = NULL;
	if (LLVMPrintModuleToFile(context->module, context->ir_filename, &err))
	{
		error_exit("Could not emit ir to file: %s", err);
	}
}


LLVMValueRef llvm_emit_alloca(GenContext *c, LLVMTypeRef type, unsigned alignment, const char *name)
{
	assert(!llvm_is_global_eval(c));
	assert(alignment > 0);
	LLVMBasicBlockRef current_block = LLVMGetInsertBlock(c->builder);
	LLVMPositionBuilderBefore(c->builder, c->alloca_point);
	assert(LLVMGetTypeContext(type) == c->context);
	LLVMValueRef alloca = LLVMBuildAlloca(c->builder, type, name);
	llvm_set_alignment(alloca, alignment);
	LLVMPositionBuilderAtEnd(c->builder, current_block);
	return alloca;
}

LLVMValueRef llvm_emit_alloca_aligned(GenContext *c, Type *type, const char *name)
{
	return llvm_emit_alloca(c, llvm_get_type(c, type), type_alloca_alignment(type), name);
}

void llvm_emit_and_set_decl_alloca(GenContext *c, Decl *decl)
{
	Type *type = type_lowering(decl->type);
	if (type == type_void) return;
	decl->backend_ref = llvm_emit_alloca(c, llvm_get_type(c, type), decl->alignment, decl->name ? decl->name : ".anon");
}

void llvm_emit_local_var_alloca(GenContext *c, Decl *decl)
{
	assert(!decl->var.is_static);
	llvm_emit_and_set_decl_alloca(c, decl);
	if (llvm_use_debug(c))
	{
		llvm_emit_debug_local_var(c, decl);
	}
}

static inline unsigned lookup_intrinsic(const char *name)
{
	return LLVMLookupIntrinsicID(name, strlen(name));
}

static inline unsigned lookup_attribute(const char *name)
{
	return LLVMGetEnumAttributeKindForName(name, strlen(name));
}

static bool intrinsics_setup = false;
LLVMAttributes attribute_id;
LLVMIntrinsics intrinsic_id;

static void llvm_codegen_setup()
{
	if (intrinsics_setup) return;

	intrinsic_id.abs = lookup_intrinsic("llvm.abs");
	intrinsic_id.assume = lookup_intrinsic("llvm.assume");
	intrinsic_id.bitreverse = lookup_intrinsic("llvm.bitreverse");
	intrinsic_id.bswap = lookup_intrinsic("llvm.bswap");
	intrinsic_id.ceil = lookup_intrinsic("llvm.ceil");
	intrinsic_id.convert_from_fp16 = lookup_intrinsic("llvm.convert.from.fp16");
	intrinsic_id.convert_to_fp16 = lookup_intrinsic("llvm.convert.to.fp16");
	intrinsic_id.copysign = lookup_intrinsic("llvm.copysign");
	intrinsic_id.cos = lookup_intrinsic("llvm.cos");
	intrinsic_id.ctlz = lookup_intrinsic("llvm.ctlz");
	intrinsic_id.ctpop = lookup_intrinsic("llvm.ctpop");
	intrinsic_id.cttz = lookup_intrinsic("llvm.cttz");
	intrinsic_id.exp = lookup_intrinsic("llvm.exp");
	intrinsic_id.exp2 = lookup_intrinsic("llvm.exp2");
	intrinsic_id.expect = lookup_intrinsic("llvm.expect");
	intrinsic_id.expect_with_probability = lookup_intrinsic("llvm.expect.with.probability");
	intrinsic_id.fabs = lookup_intrinsic("llvm.fabs");
	intrinsic_id.floor = lookup_intrinsic("llvm.floor");
	intrinsic_id.fma = lookup_intrinsic("llvm.fma");
	intrinsic_id.frameaddress = lookup_intrinsic("llvm.frameaddress");
	intrinsic_id.fshl = lookup_intrinsic("llvm.fshl");
	intrinsic_id.fshr = lookup_intrinsic("llvm.fshr");
#if LLVM_VERSION_MAJOR < 16
	intrinsic_id.get_rounding = lookup_intrinsic("llvm.flt.rounds");
#else
	intrinsic_id.get_rounding = lookup_intrinsic("llvm.get.rounding");
#endif
	intrinsic_id.lifetime_end = lookup_intrinsic("llvm.lifetime.end");
	intrinsic_id.lifetime_start = lookup_intrinsic("llvm.lifetime.start");
	intrinsic_id.llrint = lookup_intrinsic("llvm.llrint");
	intrinsic_id.llround = lookup_intrinsic("llvm.llround");
	intrinsic_id.log = lookup_intrinsic("llvm.log");
	intrinsic_id.log2 = lookup_intrinsic("llvm.log2");
	intrinsic_id.log10 = lookup_intrinsic("llvm.log10");
	intrinsic_id.lrint = lookup_intrinsic("llvm.lrint");
	intrinsic_id.lround = lookup_intrinsic("llvm.lround");
	intrinsic_id.maximum = lookup_intrinsic("llvm.maximum");
	intrinsic_id.maxnum = lookup_intrinsic("llvm.maxnum");
	intrinsic_id.memcpy = lookup_intrinsic("llvm.memcpy");
	intrinsic_id.memcpy_inline = lookup_intrinsic("llvm.memcpy.inline");
	intrinsic_id.memmove = lookup_intrinsic("llvm.memmove");
	intrinsic_id.memset = lookup_intrinsic("llvm.memset");
	intrinsic_id.memset_inline = lookup_intrinsic("llvm.memset.inline");
	intrinsic_id.minimum = lookup_intrinsic("llvm.minimum");
	intrinsic_id.minnum = lookup_intrinsic("llvm.minnum");
	intrinsic_id.fmuladd = lookup_intrinsic("llvm.fmuladd");
	intrinsic_id.nearbyint = lookup_intrinsic("llvm.nearbyint");
	intrinsic_id.pow = lookup_intrinsic("llvm.pow");
	intrinsic_id.powi = lookup_intrinsic("llvm.powi");
	intrinsic_id.prefetch = lookup_intrinsic("llvm.prefetch");
	intrinsic_id.readcyclecounter = lookup_intrinsic("llvm.readcyclecounter");
	intrinsic_id.rint = lookup_intrinsic("llvm.rint");
	intrinsic_id.round = lookup_intrinsic("llvm.round");
	intrinsic_id.roundeven = lookup_intrinsic("llvm.roundeven");
	intrinsic_id.sadd_overflow = lookup_intrinsic("llvm.sadd.with.overflow");
	intrinsic_id.sadd_sat = lookup_intrinsic("llvm.sadd.sat");
	intrinsic_id.set_rounding = lookup_intrinsic("llvm.set.rounding");
	intrinsic_id.sin = lookup_intrinsic("llvm.sin");
	intrinsic_id.sshl_sat = lookup_intrinsic("llvm.sshl.sat");
	intrinsic_id.smax = lookup_intrinsic("llvm.smax");
	intrinsic_id.smin = lookup_intrinsic("llvm.smin");
	intrinsic_id.smul_overflow = lookup_intrinsic("llvm.smul.with.overflow");
	intrinsic_id.sqrt = lookup_intrinsic("llvm.sqrt");
	intrinsic_id.ssub_overflow = lookup_intrinsic("llvm.ssub.with.overflow");
	intrinsic_id.ssub_sat = lookup_intrinsic("llvm.ssub.sat");
	intrinsic_id.trap = lookup_intrinsic("llvm.trap");
	intrinsic_id.trunc = lookup_intrinsic("llvm.trunc");
	intrinsic_id.uadd_overflow = lookup_intrinsic("llvm.uadd.with.overflow");
	intrinsic_id.uadd_sat = lookup_intrinsic("llvm.uadd.sat");
	intrinsic_id.umax = lookup_intrinsic("llvm.umax");
	intrinsic_id.umin = lookup_intrinsic("llvm.umin");
	intrinsic_id.umul_overflow = lookup_intrinsic("llvm.umul.with.overflow");
	intrinsic_id.usub_overflow = lookup_intrinsic("llvm.usub.with.overflow");
	intrinsic_id.ushl_sat = lookup_intrinsic("llvm.ushl.sat");
	intrinsic_id.usub_sat = lookup_intrinsic("llvm.usub.sat");
	intrinsic_id.vector_reduce_fmax = lookup_intrinsic("llvm.vector.reduce.fmax");
	intrinsic_id.vector_reduce_fmin = lookup_intrinsic("llvm.vector.reduce.fmin");
	intrinsic_id.vector_reduce_smax = lookup_intrinsic("llvm.vector.reduce.smax");
	intrinsic_id.vector_reduce_smin = lookup_intrinsic("llvm.vector.reduce.smin");
	intrinsic_id.vector_reduce_umax = lookup_intrinsic("llvm.vector.reduce.umax");
	intrinsic_id.vector_reduce_umin = lookup_intrinsic("llvm.vector.reduce.umin");
	intrinsic_id.vector_reduce_add = lookup_intrinsic("llvm.vector.reduce.add");
	intrinsic_id.vector_reduce_fadd = lookup_intrinsic("llvm.vector.reduce.fadd");
	intrinsic_id.vector_reduce_mul = lookup_intrinsic("llvm.vector.reduce.mul");
	intrinsic_id.vector_reduce_fmul = lookup_intrinsic("llvm.vector.reduce.fmul");
	intrinsic_id.vector_reduce_and = lookup_intrinsic("llvm.vector.reduce.and");
	intrinsic_id.vector_reduce_or = lookup_intrinsic("llvm.vector.reduce.or");
	intrinsic_id.vector_reduce_xor = lookup_intrinsic("llvm.vector.reduce.xor");
	intrinsic_id.wasm_memory_grow = lookup_intrinsic("llvm.wasm.memory.grow");
	intrinsic_id.wasm_memory_size = lookup_intrinsic("llvm.wasm.memory.size");

	attribute_id.align = lookup_attribute("align");
	attribute_id.alwaysinline = lookup_attribute("alwaysinline");
	attribute_id.byval = lookup_attribute("byval");
	attribute_id.elementtype = lookup_attribute("elementtype");
	attribute_id.inlinehint = lookup_attribute("inlinehint");
	attribute_id.inreg = lookup_attribute("inreg");
	attribute_id.naked = lookup_attribute("naked");
	attribute_id.noalias = lookup_attribute("noalias");
	attribute_id.noinline = lookup_attribute("noinline");
	attribute_id.noreturn = lookup_attribute("noreturn");
	attribute_id.nounwind = lookup_attribute("nounwind");
	attribute_id.optnone = lookup_attribute("optnone");
	attribute_id.readonly = lookup_attribute("readonly");
	attribute_id.sext = lookup_attribute("signext");
	attribute_id.sret = lookup_attribute("sret");
	attribute_id.uwtable = lookup_attribute("uwtable");
	attribute_id.writeonly = lookup_attribute("writeonly");
	attribute_id.zext = lookup_attribute("zeroext");
	attribute_id.target_features = lookup_attribute("target-features");
	intrinsics_setup = true;
}

void llvm_set_comdat(GenContext *c, LLVMValueRef global)
{
	if (!platform_target.use_comdat) return;
	LLVMComdatRef comdat = LLVMGetOrInsertComdat(c->module, LLVMGetValueName(global));
	LLVMSetComdatSelectionKind(comdat, LLVMAnyComdatSelectionKind);
	LLVMSetComdat(global, comdat);
}

void llvm_set_linkonce(GenContext *c, LLVMValueRef global)
{
	LLVMSetLinkage(global, LLVMLinkOnceAnyLinkage);
	LLVMSetVisibility(global, LLVMDefaultVisibility);
	llvm_set_comdat(c, global);
}

void llvm_set_weak(GenContext *c, LLVMValueRef global)
{
	LLVMSetLinkage(global, LLVMWeakAnyLinkage);
	LLVMSetVisibility(global, LLVMDefaultVisibility);
	llvm_set_comdat(c, global);
}


void llvm_value_set_int(GenContext *c, BEValue *value, Type *type, uint64_t i)
{
	llvm_value_set(value, llvm_const_int(c, type, i), type);
}

bool llvm_value_is_const(BEValue *value)
{
	return LLVMIsConstant(value->value);
}

void llvm_value_set_decl(GenContext *c, BEValue *value, Decl *decl)
{
	decl = decl_flatten(decl);
	if (decl->is_value)
	{
		llvm_value_set(value, decl->backend_value, decl->type);
		return;
	}
	llvm_value_set_decl_address(c, value, decl);
}


LLVMBasicBlockRef llvm_basic_block_new(GenContext *c, const char *name)
{
	return LLVMCreateBasicBlockInContext(c->context, name);
}

static void llvm_emit_type_decls(GenContext *context, Decl *decl)
{
	switch (decl->decl_kind)
	{
		case DECL_POISONED:
		case DECL_VAR:
		case DECL_ENUM_CONSTANT:
		case DECL_FAULTVALUE:
			UNREACHABLE;
		case DECL_TYPEDEF:
			if (decl->typedef_decl.is_func)
			{
				REMINDER("Emit func typeid");
			}
			break;
		case DECL_FUNC:
			// Never directly invoked.
			UNREACHABLE
		case DECL_DISTINCT:
		case DECL_STRUCT:
		case DECL_UNION:
		case DECL_ENUM:
		case DECL_FAULT:
		case DECL_BITSTRUCT:
			llvm_get_typeid(context, decl->type);
			break;
		case DECL_BODYPARAM:
		case NON_TYPE_DECLS:
			UNREACHABLE
	}
}


static inline void llvm_optimize(GenContext *c)
{
	LLVMPassBuilderOptionsRef options = LLVMCreatePassBuilderOptions();
	LLVMPassBuilderOptionsSetVerifyEach(options, active_target.emit_llvm);
#ifndef NDEBUG
	LLVMPassBuilderOptionsSetDebugLogging(options, debug_log);
#endif
	const char *passes = NULL;
	switch (active_target.size_optimization_level)
	{
		case SIZE_OPTIMIZATION_SMALL:
			passes = "default<Os>";
			break;
		case SIZE_OPTIMIZATION_TINY:
			passes = "default<Oz>";
			break;
		default:
			break;
	}
	switch (active_target.optimization_level)
	{
		case OPTIMIZATION_NONE:
			passes = "default<O0>";
			break;
		case OPTIMIZATION_NOT_SET:
		case OPTIMIZATION_DEFAULT:
			if (!passes) passes = "default<O2>";
			break;
		case OPTIMIZATION_LESS:
			if (!passes) passes = "default<O1>";
			break;
		case OPTIMIZATION_AGGRESSIVE:
			if (!passes) passes = "default<O3>";
			break;
	}
	LLVMErrorRef err = LLVMRunPasses(c->module, passes, c->machine, options);
	if (err)
	{
		error_exit("An error occurred: %s.", LLVMGetErrorMessage(err));
	}
	LLVMDisposePassBuilderOptions(options);
}

const char *llvm_codegen(void *context)
{
	GenContext *c = context;
	llvm_optimize(c);

	// Serialize the LLVM IR, if requested, also verify the IR in this case
	if (active_target.emit_llvm)
	{
		gencontext_print_llvm_ir(c);
		gencontext_verify_ir(c);
	}

	const char *object_name = NULL;
	if (active_target.emit_object_files)
	{
		gencontext_emit_object_file(c);
		object_name = c->object_filename;
	}

	if (active_target.emit_asm)
	{
		llvm_emit_asm_file(context);
	}

	gencontext_end_module(c);
	gencontext_destroy(c);

	return object_name;
}

void llvm_add_global_decl(GenContext *c, Decl *decl)
{
	assert(decl->var.kind == VARDECL_GLOBAL || decl->var.kind == VARDECL_CONST);

	bool same_module = decl->unit->module == c->code_module;
	const char *name = same_module ? "temp_global" : decl_get_extname(decl);
	decl->backend_ref = llvm_add_global(c, name, decl->type, decl->alignment);
	llvm_set_alignment(decl->backend_ref, decl->alignment);
	if (!same_module)
	{
		LLVMSetLinkage(decl->backend_ref, LLVMExternalLinkage);
	}
	if (decl->var.kind == VARDECL_CONST)
	{
		LLVMSetGlobalConstant(decl->backend_ref, true);
	}
	if (IS_OPTIONAL(decl))
	{
		scratch_buffer_clear();
		scratch_buffer_append(decl_get_extname(decl));
		scratch_buffer_append(".f");
		decl->var.optional_ref = llvm_add_global(c, scratch_buffer_to_string(), type_anyfault, 0);
	}
	llvm_set_global_tls(decl);
}

LLVMValueRef llvm_get_opt_ref(GenContext *c, Decl *decl)
{
	llvm_get_ref(c, decl);
	decl = decl_flatten(decl);
	if (decl->decl_kind != DECL_VAR) return NULL;
	return decl->var.optional_ref;
}

static void llvm_emit_param_attributes(GenContext *c, LLVMValueRef function, ABIArgInfo *info, bool is_return, int index, int last_index)
{
	assert(last_index == index || info->kind == ABI_ARG_DIRECT_PAIR || info->kind == ABI_ARG_IGNORE
	       || info->kind == ABI_ARG_EXPAND || info->kind == ABI_ARG_DIRECT || info->kind == ABI_ARG_DIRECT_COERCE
	       || info->kind == ABI_ARG_DIRECT_COERCE_INT || info->kind == ABI_ARG_EXPAND_COERCE
	       || info->kind == ABI_ARG_DIRECT_SPLIT_STRUCT_I32);

	if (info->attributes.zeroext)
	{
		// Direct only
		assert(index == last_index);
		llvm_attribute_add(c, function, attribute_id.zext, index);
	}
	if (info->attributes.signext)
	{
		// Direct only
		assert(index == last_index);
		llvm_attribute_add(c, function, attribute_id.sext, index);
	}
	if (info->attributes.by_reg)
	{
		llvm_attribute_add_range(c, function, attribute_id.inreg, index, last_index);
	}
	switch (info->kind)
	{
		case ABI_ARG_EXPAND:
		case ABI_ARG_IGNORE:
		case ABI_ARG_DIRECT_SPLIT_STRUCT_I32:
		case ABI_ARG_DIRECT_COERCE:
		case ABI_ARG_DIRECT_COERCE_INT:
		case ABI_ARG_DIRECT_PAIR:
		case ABI_ARG_DIRECT:
		case ABI_ARG_EXPAND_COERCE:
			break;
		case ABI_ARG_INDIRECT:
			if (is_return)
			{
				assert(info->indirect.type);
				llvm_attribute_add_type(c, function, attribute_id.sret, llvm_get_type(c, info->indirect.type), 1);
				llvm_attribute_add(c, function, attribute_id.noalias, 1);
				llvm_attribute_add_int(c, function, attribute_id.align, info->indirect.alignment, 1);
			}
			else
			{
				if (info->attributes.by_val) llvm_attribute_add_type(c, function, attribute_id.byval, llvm_get_type(c, info->indirect.type), index);
				llvm_attribute_add_int(c, function, attribute_id.align, info->indirect.alignment, index);
			}
			break;
	}

}

void llvm_append_function_attributes(GenContext *c, Decl *decl)
{
	FunctionPrototype *prototype = type_get_resolved_prototype(decl->type);

	LLVMValueRef function = decl->backend_ref;
	ABIArgInfo *ret_abi_info = prototype->ret_abi_info;
	llvm_emit_param_attributes(c, function, ret_abi_info, true, 0, 0);
	unsigned params = vec_size(prototype->param_types);
	if (prototype->ret_by_ref)
	{
		ABIArgInfo *info = prototype->ret_by_ref_abi_info;
		llvm_emit_param_attributes(c, function, prototype->ret_by_ref_abi_info, false, info->param_index_start + 1, info->param_index_end);
	}
	for (unsigned i = 0; i < params; i++)
	{
		ABIArgInfo *info = prototype->abi_args[i];
		llvm_emit_param_attributes(c, function, info, false, info->param_index_start + 1, info->param_index_end);
	}
	// We ignore decl->func_decl.attr_inline and place it in every call instead.
	if (decl->func_decl.attr_noinline)
	{
		llvm_attribute_add(c, function, attribute_id.noinline, -1);
	}
	if (decl->func_decl.signature.attrs.noreturn)
	{
		llvm_attribute_add(c, function, attribute_id.noreturn, -1);
	}
	if (decl->is_export && arch_is_wasm(platform_target.arch))
	{
		if (c->code_module == decl->unit->module)
		{
			llvm_attribute_add_string(c, function, "wasm-export-name", decl_get_extname(decl), -1);
		}
	}
	if (decl->is_extern && arch_is_wasm(platform_target.arch))
	{
		llvm_attribute_add_string(c, function, "wasm-import-name", decl_get_extname(decl), -1);
	}
	if (decl->alignment != type_abi_alignment(decl->type))
	{
		llvm_set_alignment(function, decl->alignment);
	}
	llvm_attribute_add(c, function, attribute_id.nounwind, -1);
	if (decl->func_decl.attr_naked)
	{
		llvm_attribute_add(c, function, attribute_id.naked, -1);
	}
	LLVMSetFunctionCallConv(function, llvm_call_convention_from_call(prototype->call_abi));
}
LLVMValueRef llvm_get_ref(GenContext *c, Decl *decl)
{
	LLVMValueRef backend_ref = decl->backend_ref;
	if (backend_ref)
	{
		if (!LLVMIsAGlobalValue(backend_ref) || LLVMGetGlobalParent(backend_ref) == c->module) return backend_ref;
	}
	switch (decl->decl_kind)
	{
		case DECL_VAR:
			if (decl->var.kind == VARDECL_UNWRAPPED)
			{
				return decl->backend_ref = llvm_get_ref(c, decl->var.alias);
			}
			assert(decl->var.kind == VARDECL_GLOBAL || decl->var.kind == VARDECL_CONST);
			llvm_add_global_decl(c, decl);
			if (decl->is_export && platform_target.os == OS_TYPE_WIN32)
			{
				LLVMSetDLLStorageClass(decl->backend_ref, LLVMDLLExportStorageClass);
			}
			return decl->backend_ref;
		case DECL_FUNC:
			if (decl->func_decl.attr_interface)
			{
				size_t name_len = strlen(decl->name);
				LLVMTypeRef char_array_type = LLVMArrayType(c->byte_type, name_len + 1);
				LLVMValueRef selector = llvm_add_global_raw(c, decl_get_extname(decl), char_array_type, 0);
				LLVMSetGlobalConstant(selector, 1);
				LLVMSetInitializer(selector, llvm_get_zstring(c, decl->name, name_len));
				LLVMSetLinkage(selector, LLVMLinkOnceODRLinkage);
				llvm_set_comdat(c, selector);
				return decl->backend_ref = selector;
			}
			backend_ref = decl->backend_ref = LLVMAddFunction(c->module, decl_get_extname(decl), llvm_get_type(c, decl->type));
			llvm_append_function_attributes(c, decl);
			if (decl->is_export && platform_target.os == OS_TYPE_WIN32 && decl->name != kw_main && decl->name != kw_mainstub)
			{
				LLVMSetDLLStorageClass(backend_ref, LLVMDLLExportStorageClass);
			}
			if (decl_is_local(decl))
			{
				assert(decl->unit->module == c->code_module);
				llvm_set_internal_linkage(backend_ref);
			}
			return backend_ref;
		case DECL_DEFINE:
			if (decl->define_decl.define_kind != DEFINE_TYPE_GENERIC) return llvm_get_ref(c, decl->define_decl.alias);
			UNREACHABLE
		case DECL_FAULTVALUE:
			if (!decl->backend_ref)
			{
				llvm_get_typeid(c, declptr(decl->enum_constant.parent)->type);
			}
			assert(decl->backend_ref);
			return decl->backend_ref;
		case DECL_POISONED:
		case DECL_ATTRIBUTE:
		case DECL_BITSTRUCT:
		case DECL_CT_CASE:
		case DECL_CT_ELIF:
		case DECL_CT_ELSE:
		case DECL_CT_IF:
		case DECL_CT_SWITCH:
		case DECL_CT_ASSERT:
		case DECL_DISTINCT:
		case DECL_ENUM:
		case DECL_ENUM_CONSTANT:
		case DECL_FAULT:
		case DECL_IMPORT:
		case DECL_LABEL:
		case DECL_MACRO:
		case DECL_STRUCT:
		case DECL_TYPEDEF:
		case DECL_UNION:
		case DECL_DECLARRAY:
		case DECL_INITIALIZE:
		case DECL_FINALIZE:
		case DECL_BODYPARAM:
		case DECL_CT_ECHO:
		case DECL_CT_INCLUDE:
		case DECL_GLOBALS:
			UNREACHABLE;
	}
	UNREACHABLE
}

static void llvm_gen_test_main(GenContext *c)
{
	Decl *test_runner = global_context.test_func;
	if (!test_runner)
	{
		error_exit("No test runner found.");
	}
	assert(!global_context.main && "Main should not be set if a test main is generated.");
	global_context.main = test_runner;
	LLVMTypeRef cint = llvm_get_type(c, type_cint);
	LLVMTypeRef main_type = LLVMFunctionType(cint, NULL, 0, true);
	LLVMTypeRef runner_type = LLVMFunctionType(c->byte_type, NULL, 0, true);
	LLVMValueRef func = LLVMAddFunction(c->module, kw_main, main_type);
	LLVMValueRef other_func = LLVMAddFunction(c->module, test_runner->extname, runner_type);
	LLVMBasicBlockRef entry = LLVMAppendBasicBlockInContext(c->context, func, "entry");
	LLVMBuilderRef builder = LLVMCreateBuilderInContext(c->context);
	LLVMPositionBuilderAtEnd(builder, entry);
	LLVMValueRef val = LLVMBuildCall2(builder, runner_type, other_func, NULL, 0, "");
	val = LLVMBuildSelect(builder, LLVMBuildTrunc(builder, val, c->bool_type, ""),
	                      LLVMConstNull(cint), LLVMConstInt(cint, 1, false), "");
	LLVMBuildRet(builder, val);
	LLVMDisposeBuilder(builder);
	gencontext_print_llvm_ir(c);
}
INLINE GenContext *llvm_gen_tests(Module** modules, unsigned module_count, LLVMContextRef shared_context)
{
	Path *test_path = path_create_from_string("$test", 5, INVALID_SPAN);
	Module *test_module = compiler_find_or_create_module(test_path, NULL);

	GenContext *c = cmalloc(sizeof(GenContext));
	active_target.debug_info = DEBUG_INFO_NONE;
	gencontext_init(c, test_module, shared_context);
	gencontext_begin_module(c);

	LLVMValueRef *names = NULL;
	LLVMValueRef *decls = NULL;

	LLVMTypeRef opt_test = LLVMFunctionType(llvm_get_type(c, type_anyfault), NULL, 0, false);
	for (unsigned i = 0; i < module_count; i++)
	{
		Module *module = modules[i];
		FOREACH_BEGIN(Decl *test, module->tests)
			LLVMValueRef ref;
			LLVMTypeRef type = opt_test;
			ref = LLVMAddFunction(c->module, test->extname, type);
			scratch_buffer_clear();
			scratch_buffer_printf("%s::%s", module->name->module, test->name);
			LLVMValueRef name = llvm_emit_string_const(c, scratch_buffer_to_string(), ".test.name");
			vec_add(names, name);
			vec_add(decls, ref);
		FOREACH_END();
	}
	unsigned test_count = vec_size(decls);
	LLVMValueRef name_ref;
	LLVMValueRef decl_ref;

	if (test_count)
	{
		LLVMValueRef array_of_names = LLVMConstArray(c->chars_type, names, test_count);
		LLVMValueRef array_of_decls = LLVMConstArray(c->ptr_type, decls, test_count);
		LLVMTypeRef arr_type = LLVMTypeOf(array_of_names);
		name_ref = llvm_add_global_raw(c, ".test_names", arr_type, 0);
		decl_ref = llvm_add_global_raw(c, ".test_decls", LLVMTypeOf(array_of_decls), 0);
		llvm_set_internal_linkage(name_ref);
		llvm_set_internal_linkage(decl_ref);
		LLVMSetGlobalConstant(name_ref, 1);
		LLVMSetGlobalConstant(decl_ref, 1);
		LLVMSetInitializer(name_ref, array_of_names);
		LLVMSetInitializer(decl_ref, array_of_decls);
	}
	else
	{
		name_ref = LLVMConstNull(c->ptr_type);
		decl_ref = LLVMConstNull(c->ptr_type);
	}
	LLVMValueRef count = llvm_const_int(c, type_usz, test_count);
	Type *chars_array = type_get_subarray(type_chars);
	LLVMValueRef name_list = llvm_add_global(c, test_names_var_name, chars_array, type_alloca_alignment(chars_array));
	LLVMSetGlobalConstant(name_list, 1);
	LLVMSetInitializer(name_list, llvm_emit_aggregate_two(c, chars_array, name_ref, count));
	Type *decls_array_type = type_get_subarray(type_voidptr);
	LLVMValueRef decl_list = llvm_add_global(c, test_fns_var_name, decls_array_type, type_alloca_alignment(decls_array_type));
	LLVMSetGlobalConstant(decl_list, 1);
	LLVMSetInitializer(decl_list, llvm_emit_aggregate_two(c, decls_array_type, decl_ref, count));

	if (active_target.type == TARGET_TYPE_TEST)
	{
		llvm_gen_test_main(c);
	}

	if (llvm_use_debug(c))
	{
		LLVMDIBuilderFinalize(c->debug.builder);
		LLVMDisposeDIBuilder(c->debug.builder);
	}
	return c;
}
void **llvm_gen(Module** modules, unsigned module_count)
{
	if (!module_count) return NULL;
	GenContext **gen_contexts = NULL;
	llvm_codegen_setup();
	if (active_target.single_module)
	{
		GenContext *first_context;
		unsigned first_element;
		LLVMContextRef context = LLVMGetGlobalContext();
		for (int i = 0; i < module_count; i++)
		{
			GenContext *result = llvm_gen_module(modules[i], context);
			if (!result) continue;
			vec_add(gen_contexts, result);
		}
		if (!gen_contexts) return NULL;
		GenContext *first = gen_contexts[0];
		if (active_target.testing)
		{
			vec_add(gen_contexts, llvm_gen_tests(modules, module_count, context));
		}
		unsigned count = vec_size(gen_contexts);
		for (unsigned i = 1; i < count; i++)
		{
			GenContext *other = gen_contexts[i];
			LLVMLinkModules2(first->module, other->module);
			gencontext_destroy(other);
		}
		vec_resize(gen_contexts, 1);
		return (void**)gen_contexts;
	}
	for (unsigned i = 0; i < module_count; i++)
	{
			GenContext *result = llvm_gen_module(modules[i], NULL);
			if (!result) continue;
			vec_add(gen_contexts, result);
	}
	if (active_target.testing)
	{
		vec_add(gen_contexts, llvm_gen_tests(modules, module_count, NULL));
	}
	return (void**)gen_contexts;
}

LLVMMetadataRef llvm_get_debug_file(GenContext *c, FileId file_id)
{
	VECEACH(c->debug.debug_files, i)
	{
		DebugFile *ref = &c->debug.debug_files[i];
		if (ref->file_id == file_id) return ref->debug_file;
	}
	File *f = source_file_by_id(file_id);
	LLVMMetadataRef file = LLVMDIBuilderCreateFile(c->debug.builder,
	                                               f->name,
	                                               strlen(f->name),
	                                               f->dir_path,
	                                               strlen(f->dir_path));
	DebugFile debug_file = { .file_id = file_id, .debug_file = file };
	vec_add(c->debug.debug_files, debug_file);
	return file;
}

static GenContext *llvm_gen_module(Module *module, LLVMContextRef shared_context)
{
	if (!vec_size(module->units)) return NULL;
	assert(intrinsics_setup);

	bool has_elements = false;
	GenContext *gen_context = cmalloc(sizeof(GenContext));
	gencontext_init(gen_context, module, shared_context);
	gencontext_begin_module(gen_context);

	bool only_used = active_target.strip_unused && !active_target.testing;

	FOREACH_BEGIN(CompilationUnit *unit, module->units)

		gencontext_init_file_emit(gen_context, unit);
		gen_context->debug.compile_unit = unit->llvm.debug_compile_unit;
		gen_context->debug.file = (DebugFile){
				.debug_file = unit->llvm.debug_file,
				.file_id = unit->file->file_id };

		FOREACH_BEGIN(Decl *initializer, unit->xxlizers)
			has_elements = true;
			llvm_emit_xxlizer(gen_context, initializer);
		FOREACH_END();

		FOREACH_BEGIN(Decl *method, unit->methods)
			if (only_used && !method->is_live) continue;
			llvm_emit_function_decl(gen_context, method);
		FOREACH_END();

		FOREACH_BEGIN(Decl *type_decl, unit->types)
			llvm_emit_type_decls(gen_context, type_decl);
		FOREACH_END();

		FOREACH_BEGIN(Decl *enum_decl, unit->enums)
			llvm_emit_type_decls(gen_context, enum_decl);
		FOREACH_END();

		FOREACH_BEGIN(Decl *func, unit->functions)
			if (only_used && !func->is_live) continue;
			if (func->func_decl.attr_test)
			{
				if (!active_target.testing) continue;
				vec_add(module->tests, func);
			}
			llvm_emit_function_decl(gen_context, func);
		FOREACH_END();


		FOREACH_BEGIN(Decl *func, unit->lambdas)
			if (only_used && !func->is_live) continue;
			has_elements = true;
			llvm_emit_function_decl(gen_context, func);
		FOREACH_END();

		if (active_target.type != TARGET_TYPE_TEST && unit->main_function && unit->main_function->is_synthetic)
		{
			has_elements = true;
			llvm_emit_function_decl(gen_context, unit->main_function);
		}

	FOREACH_END();

	FOREACH_BEGIN(CompilationUnit *unit, module->units)

		gen_context->debug.compile_unit = unit->llvm.debug_compile_unit;
		gen_context->debug.file = (DebugFile){
				.debug_file = unit->llvm.debug_file,
				.file_id = unit->file->file_id };

		FOREACH_BEGIN(Decl *var, unit->vars)
			if (only_used && !var->is_live) continue;
			has_elements = true;
			llvm_get_ref(gen_context, var);
		FOREACH_END();

		FOREACH_BEGIN(Decl *var, unit->vars)
			if (only_used && !var->is_live) continue;
			has_elements = true;
			llvm_emit_global_variable_init(gen_context, var);
		FOREACH_END();

		FOREACH_BEGIN(Decl *decl, unit->functions)
			if (decl->func_decl.attr_test && !active_target.testing) continue;
			if (only_used && !decl->is_live) continue;
			if (decl->func_decl.body)
			{
				has_elements = true;
				llvm_emit_function_body(gen_context, decl);
			}
		FOREACH_END();

		FOREACH_BEGIN(Decl *func, unit->lambdas)
			if (only_used && !func->is_live) continue;
			has_elements = true;
			llvm_emit_function_body(gen_context, func);
		FOREACH_END();

		if (active_target.type != TARGET_TYPE_TEST && unit->main_function && unit->main_function->is_synthetic)
		{
			has_elements = true;
			llvm_emit_function_body(gen_context, unit->main_function);
		}

		FOREACH_BEGIN(Decl *decl, unit->methods)
			if (only_used && !decl->is_live) continue;
			if (!decl->func_decl.body) continue;
			has_elements = true;
			llvm_emit_function_body(gen_context, decl);
		FOREACH_END();

		gencontext_end_file_emit(gen_context, unit);

	FOREACH_END();

	llvm_emit_dynamic_functions(gen_context, gen_context->dynamic_functions);

	llvm_emit_constructors_and_destructors(gen_context);

	// EmitDeferred()

	if (llvm_use_debug(gen_context))
	{
		LLVMDIBuilderFinalize(gen_context->debug.builder);
		LLVMDisposeDIBuilder(gen_context->debug.builder);
	}

	// If it's in test, then we want to serialize the IR before it is optimized.
	if (active_target.test_output)
	{
		gencontext_print_llvm_ir(gen_context);
		gencontext_verify_ir(gen_context);
	}
	if (!has_elements) return NULL;
	return gen_context;
}

void llvm_attribute_add_int(GenContext *context, LLVMValueRef value_to_add_attribute_to, unsigned attribute, uint64_t val, int index)
{
	LLVMAttributeRef llvm_attr = LLVMCreateEnumAttribute(context->context, attribute, val);
	LLVMAddAttributeAtIndex(value_to_add_attribute_to, (LLVMAttributeIndex)index, llvm_attr);
}

void llvm_attribute_add_type(GenContext *c, LLVMValueRef value_to_add_attribute_to, unsigned attribute, LLVMTypeRef type, int index)
{
	LLVMAttributeRef llvm_attr = LLVMCreateTypeAttribute(c->context, attribute, type);
	LLVMAddAttributeAtIndex(value_to_add_attribute_to, (LLVMAttributeIndex)index, llvm_attr);
}

void llvm_attribute_add(GenContext *context, LLVMValueRef value_to_add_attribute_to, unsigned attribute, int index)
{
	llvm_attribute_add_int(context, value_to_add_attribute_to, attribute, 0, index);
}

void llvm_attribute_add_call_type(GenContext *c, LLVMValueRef call, unsigned attribute, int index, LLVMTypeRef type)
{
	LLVMAttributeRef llvm_attr = LLVMCreateTypeAttribute(c->context, attribute, type);
	LLVMAddCallSiteAttribute(call, (LLVMAttributeIndex)index, llvm_attr);
}

void llvm_attribute_add_call(GenContext *context, LLVMValueRef call, unsigned attribute, int index, int64_t value)
{
	LLVMAttributeRef llvm_attr = LLVMCreateEnumAttribute(context->context, attribute, (uint64_t)value);
	LLVMAddCallSiteAttribute(call, (LLVMAttributeIndex)index, llvm_attr);
}

void llvm_attribute_add_range(GenContext *context, LLVMValueRef value_to_add_attribute_to, unsigned attribute, int index_start, int index_end)
{
	for (int i = index_start; i <= index_end; i++)
	{
		llvm_attribute_add_int(context, value_to_add_attribute_to, attribute, 0, i);
	}
}

void llvm_attribute_add_string(GenContext *context, LLVMValueRef value_to_add_attribute_to, const char *attribute, const char *value, int index)
{
	LLVMAttributeRef llvm_attr = LLVMCreateStringAttribute(context->context, attribute, (unsigned)strlen(attribute), value, (unsigned)strlen(value));
	LLVMAddAttributeAtIndex(value_to_add_attribute_to, (LLVMAttributeIndex)index, llvm_attr);
}

BitSize llvm_bitsize(GenContext *c, LLVMTypeRef type)
{
	return LLVMSizeOfTypeInBits(c->target_data, type);
}
TypeSize llvm_abi_size(GenContext *c, LLVMTypeRef type)
{
	return (TypeSize)LLVMABISizeOfType(c->target_data, type);
}

AlignSize llvm_abi_alignment(GenContext *c, LLVMTypeRef type)
{
	return (AlignSize)LLVMABIAlignmentOfType(c->target_data, type);
}




void llvm_emit_memcpy(GenContext *c, LLVMValueRef dest, unsigned dest_align, LLVMValueRef source, unsigned src_align, uint64_t len)
{
	assert(dest_align && src_align);
	if (len <= UINT32_MAX)
	{
		LLVMBuildMemCpy(c->builder, dest, dest_align, source, src_align, llvm_const_int(c, type_uint, len));
		return;
	}
	LLVMBuildMemCpy(c->builder, dest, dest_align, source, src_align, llvm_const_int(c, type_ulong, len));
}

void llvm_emit_memcpy_to_decl(GenContext *c, Decl *decl, LLVMValueRef source, unsigned source_alignment)
{
	if (source_alignment == 0) source_alignment = type_abi_alignment(decl->type);
	assert(!decl->is_value);
	llvm_emit_memcpy(c, decl->backend_ref, decl->alignment, source, source_alignment, type_size(decl->type));
}



TypeSize llvm_store_size(GenContext *c, LLVMTypeRef type)
{
	return (TypeSize)LLVMStoreSizeOfType(c->target_data, type);
}

TypeSize llvm_alloc_size(GenContext *c, LLVMTypeRef type)
{
	return (TypeSize)aligned_offset((AlignSize)LLVMStoreSizeOfType(c->target_data, type), llvm_abi_alignment(c, type));
}

