bloat-buster/src/emitter.cpp
2025-05-09 06:14:14 -06:00

2989 lines
117 KiB
C++

#include <compiler.h>
#include <llvm.h>
struct LLVMGlobal
{
String host_triple;
String host_cpu_model;
String host_cpu_features;
};
global_variable LLVMGlobal llvm_global;
fn void llvm_initialize_all_raw()
{
assert(!llvm_initialized);
LLVMInitializeX86TargetInfo();
LLVMInitializeX86Target();
LLVMInitializeX86TargetMC();
LLVMInitializeX86AsmPrinter();
LLVMInitializeX86AsmParser();
LLVMInitializeX86Disassembler();
llvm_global = {
.host_triple = llvm_default_target_triple(),
.host_cpu_model = llvm_host_cpu_name(),
.host_cpu_features = llvm_host_cpu_features(),
};
}
fn void llvm_initialize_all()
{
if (!llvm_initialized)
{
llvm_initialize_all_raw();
}
}
fn bool is_arbitrary_bit_integer(Type* type)
{
switch (type->id)
{
case TypeId::integer: switch (type->integer.bit_count)
{
case 8:
case 16:
case 32:
case 64:
case 128:
return false;
default: return true;
} break;
case TypeId::unresolved: unreachable();
case TypeId::bits: return is_arbitrary_bit_integer(type->bits.backing_type);
case TypeId::enumerator: return is_arbitrary_bit_integer(type->enumerator.backing_type);
default: return false;
}
}
fn u64 integer_max_value(u32 bit_count, bool is_signed)
{
auto max_value = bit_count == 64 ? ~(u64)0 : ((u64)1 << (bit_count - is_signed)) - 1;
return max_value;
}
fn void dump_module(Module* module)
{
print(llvm_module_to_string(module->llvm.module));
}
fn bool type_is_signed(Type* type)
{
switch (type->id)
{
case TypeId::integer:
return type->integer.is_signed;
case TypeId::enumerator:
return type_is_signed(type->enumerator.backing_type);
default: unreachable();
}
}
enum class EvaluationKind
{
scalar,
aggregate,
complex,
};
enum class TypeKind
{
abi,
memory,
};
fn LLVMCallConv llvm_calling_convention(CallingConvention calling_convention)
{
LLVMCallConv cc;
switch (calling_convention)
{
case CallingConvention::c: cc = LLVMCCallConv; break;
case CallingConvention::count: unreachable();
}
return cc;
}
fn Type* resolve_alias(Module* module, Type* type)
{
Type* result_type = 0;
switch (type->id)
{
case TypeId::pointer:
{
auto* element_type = type->pointer.element_type;
auto* resolved_element_type = resolve_alias(module, element_type);
result_type = get_pointer_type(module, resolved_element_type);
} break;
case TypeId::array:
{
auto* element_type = type->array.element_type;
auto element_count = type->array.element_count;
assert(element_count);
auto* resolved_element_type = resolve_alias(module, element_type);
result_type = get_array_type(module, resolved_element_type, element_count);
} break;
case TypeId::integer:
case TypeId::enumerator:
{
result_type = type;
} break;
default: unreachable();
}
assert(result_type);
return result_type;
}
fn EvaluationKind get_evaluation_kind(Type* type)
{
switch (type->id)
{
case TypeId::void_type:
case TypeId::noreturn:
case TypeId::forward_declaration:
case TypeId::unresolved:
case TypeId::function:
case TypeId::alias:
unreachable();
case TypeId::integer:
case TypeId::pointer:
case TypeId::bits:
case TypeId::enumerator:
return EvaluationKind::scalar;
case TypeId::array:
case TypeId::structure:
case TypeId::union_type:
return EvaluationKind::aggregate;
}
}
fn void llvm_initialize(Module* module)
{
llvm_initialize_all();
auto context = LLVMContextCreate();
auto m = llvm_context_create_module(context, module->name);
auto builder = LLVMCreateBuilderInContext(context);
LLVMDIBuilderRef di_builder = 0;
LLVMMetadataRef di_compile_unit = 0;
LLVMMetadataRef di_file = 0;
if (module->has_debug_info)
{
di_builder = LLVMCreateDIBuilder(m);
auto last_slash = string_last_character(module->path, '/');
if (last_slash == string_no_match)
{
report_error();
}
auto directory = module->path(0, last_slash);
auto file_name = module->path(last_slash + 1);
di_file = LLVMDIBuilderCreateFile(di_builder, (char*)file_name.pointer, file_name.length, (char*)directory.pointer, directory.length);
auto producer_name = string_literal("bloat buster");
auto is_optimized = build_mode_is_optimized(module->build_mode);
auto flags = string_literal("");
u32 runtime_version = 0;
auto split_name = string_literal("");
auto sysroot = string_literal("");
auto sdk = string_literal("");
di_compile_unit = LLVMDIBuilderCreateCompileUnit(di_builder, LLVMDWARFSourceLanguageC17, di_file, (char*)producer_name.pointer, producer_name.length, is_optimized, (char*)flags.pointer, flags.length, runtime_version, (char*)split_name.pointer, split_name.length, LLVMDWARFEmissionFull, 0, 0, is_optimized, (char*)sysroot.pointer, sysroot.length, (char*)sdk.pointer, sdk.length);
module->scope.llvm = di_compile_unit;
}
module->llvm = {
.context = context,
.module = m,
.builder = builder,
.di_builder = di_builder,
.file = di_file,
.compile_unit = di_compile_unit,
.pointer_type = LLVMPointerTypeInContext(context, 0),
.void_type = LLVMVoidTypeInContext(context),
};
for (u64 i = 0; i < (u64)IntrinsicIndex::count; i += 1)
{
String name = intrinsic_names[i];
module->llvm.intrinsic_table[i].n = LLVMLookupIntrinsicID((char*)name.pointer, name.length);
}
}
enum class AbiSystemVClass
{
none,
integer,
sse,
sse_up,
x87,
x87_up,
complex_x87,
memory,
};
fn bool contains_no_user_data(Type* type, u64 start, u64 end)
{
unused(end);
if (get_byte_size(type) <= start)
{
return true;
}
else
{
switch (type->id)
{
case TypeId::structure:
{
trap();
} break;
case TypeId::array:
{
trap();
} break;
default: return false;
}
}
}
fn Type* get_integer_type_at_offset(Module* module, Type* type, u32 offset, Type* source_type, u32 source_offset)
{
switch (type->id)
{
case TypeId::integer:
{
auto bit_count = type->integer.bit_count;
switch (bit_count)
{
case 64: return type;
case 32: case 16: case 8:
{
assert(offset == 0);
auto start = source_offset + get_byte_size(type);
auto end = source_offset + 8;
if (contains_no_user_data(source_type, start, end))
{
return type;
}
} break;
default:
{
auto original_byte_count = get_byte_size(type);
assert(original_byte_count != source_offset);
auto byte_count = MIN(original_byte_count - source_offset, 8);
auto bit_count = byte_count * 8;
auto result_type = integer_type(module, { .bit_count = (u32)bit_count, .is_signed = false });
return result_type;
} break;
}
} break;
default: unreachable();
}
auto source_size = get_byte_size(source_type);
auto byte_count = source_size - source_offset;
u32 bit_count = byte_count > 8 ? 64 : byte_count * 8;
auto result = integer_type(module, { .bit_count = bit_count, .is_signed = false });
return result;
}
struct AbiSystemVClassify
{
u64 base_offset;
bool is_variable_argument;
bool is_register_call;
};
struct AbiSystemVClassifyResult
{
AbiSystemVClass r[2];
};
fn AbiSystemVClassifyResult abi_system_v_classify_type(Type* type, AbiSystemVClassify options)
{
AbiSystemVClassifyResult result = {};
auto is_memory = options.base_offset >= 8;
auto current_index = is_memory;
auto not_current_index = !is_memory;
assert(current_index != not_current_index);
result.r[current_index] = AbiSystemVClass::memory;
switch (type->id)
{
case TypeId::void_type:
case TypeId::noreturn:
result.r[current_index] = AbiSystemVClass::none;
break;
case TypeId::bits:
return abi_system_v_classify_type(type->bits.backing_type, options);
case TypeId::enumerator:
return abi_system_v_classify_type(type->enumerator.backing_type, options);
case TypeId::pointer:
result.r[current_index] = AbiSystemVClass::integer;
break;
case TypeId::integer:
{
if (type->integer.bit_count <= 64)
{
result.r[current_index] = AbiSystemVClass::integer;
}
else if (type->integer.bit_count == 128)
{
trap();
}
else
{
report_error();
}
} break;
case TypeId::array:
{
trap();
} break;
case TypeId::structure:
{
trap();
} break;
case TypeId::alias:
return abi_system_v_classify_type(type->alias.type, options);
default: unreachable();
}
return result;
}
fn bool is_integral_or_enumeration_type(Type* type)
{
switch (type->id)
{
case TypeId::alias: return is_integral_or_enumeration_type(type->alias.type);
case TypeId::integer:
case TypeId::bits:
return true;
case TypeId::structure:
return false;
default: unreachable();
}
}
fn bool is_promotable_integer_type_for_abi(Type* type)
{
switch (type->id)
{
case TypeId::integer: return type->integer.bit_count < 32;
case TypeId::bits: return is_promotable_integer_type_for_abi(type->bits.backing_type);
case TypeId::alias: return is_promotable_integer_type_for_abi(type->alias.type);
default: unreachable();
}
}
struct DirectOptions
{
Type* semantic_type;
Type* type;
Type* padding;
u32 offset;
u32 alignment;
bool cannot_be_flattened;
};
fn void resolve_type_in_place_memory(Module* module, Type* type);
fn void resolve_type_in_place_abi(Module* module, Type* type)
{
if (!type->llvm.abi)
{
LLVMTypeRef result = 0;
switch (type->id)
{
case TypeId::void_type:
case TypeId::noreturn:
result = module->llvm.void_type;
break;
case TypeId::integer:
result = LLVMIntTypeInContext(module->llvm.context, type->integer.bit_count);
break;
case TypeId::pointer:
result = module->llvm.pointer_type;
break;
case TypeId::array:
{
auto* element_type = type->array.element_type;
auto element_count = type->array.element_count;
assert(element_count);
resolve_type_in_place_memory(module, element_type);
auto array_type = LLVMArrayType2(element_type->llvm.memory, element_count);
result = array_type;
} break;
case TypeId::enumerator:
{
auto backing_type = type->enumerator.backing_type;
resolve_type_in_place_abi(module, backing_type);
result = backing_type->llvm.abi;
} break;
default: unreachable();
}
assert(result);
type->llvm.abi = result;
}
}
fn void resolve_type_in_place_memory(Module* module, Type* type)
{
if (!type->llvm.memory)
{
resolve_type_in_place_abi(module, type);
LLVMTypeRef result = 0;
switch (type->id)
{
case TypeId::void_type:
case TypeId::noreturn:
case TypeId::pointer:
case TypeId::array:
result = type->llvm.abi;
break;
case TypeId::integer:
{
auto byte_size = get_byte_size(type);
auto bit_count = byte_size * 8;
result = LLVMIntTypeInContext(module->llvm.context, bit_count);
} break;
case TypeId::enumerator:
{
auto backing_type = type->enumerator.backing_type;
resolve_type_in_place_memory(module, backing_type);
result = backing_type->llvm.memory;
} break;
default: unreachable();
}
assert(result);
type->llvm.memory = result;
if (type->id == TypeId::bits)
{
assert(type->llvm.memory == type->llvm.abi);
}
}
}
fn void resolve_type_in_place_debug(Module* module, Type* type)
{
if (module->has_debug_info)
{
if (!type->llvm.debug)
{
LLVMMetadataRef result = 0;
switch (type->id)
{
case TypeId::void_type:
case TypeId::noreturn:
{
result = LLVMDIBuilderCreateBasicType(module->llvm.di_builder, (char*)type->name.pointer, type->name.length, 0, (u32)DwarfType::void_type, type->id == TypeId::noreturn ? LLVMDIFlagNoReturn : LLVMDIFlagZero);
} break;
case TypeId::integer:
{
DwarfType dwarf_type = type->integer.bit_count == 1 ? DwarfType::boolean : (type->integer.is_signed ? DwarfType::signed_type : DwarfType::unsigned_type);
LLVMDIFlags flags = {};
result = LLVMDIBuilderCreateBasicType(module->llvm.di_builder, (char*)type->name.pointer, type->name.length, type->integer.bit_count, (u32)dwarf_type, flags);
} break;
case TypeId::pointer:
{
resolve_type_in_place_debug(module, type->pointer.element_type);
if (type->llvm.debug)
{
trap();
}
else
{
result = LLVMDIBuilderCreatePointerType(module->llvm.di_builder, type->pointer.element_type->llvm.debug, 64, 64, 0, (char*)type->name.pointer, type->name.length);
}
} break;
case TypeId::array:
{
auto array_element_type = type->array.element_type;
auto array_element_count = type->array.element_count;
assert(array_element_count);
resolve_type_in_place_debug(module, array_element_type);
auto bit_alignment = get_byte_alignment(type) * 8;
auto array_type = LLVMDIBuilderCreateArrayType(module->llvm.di_builder, array_element_count, bit_alignment, array_element_type->llvm.debug, 0, 0);
result = array_type;
} break;
case TypeId::enumerator:
{
auto backing_type = type->enumerator.backing_type;
resolve_type_in_place_debug(module, backing_type);
LLVMMetadataRef field_buffer[64];
for (u64 i = 0; i < type->enumerator.fields.length; i += 1)
{
auto& field = type->enumerator.fields[i];
auto enum_field = LLVMDIBuilderCreateEnumerator(module->llvm.di_builder, (char*)field.name.pointer, field.name.length, field.value, type_is_signed(backing_type));
field_buffer[i] = enum_field;
}
result = LLVMDIBuilderCreateEnumerationType(module->llvm.di_builder, module->scope.llvm, (char*)type->name.pointer, type->name.length, module->llvm.file, type->enumerator.line, get_bit_size(type), get_byte_alignment(type) * 8, field_buffer, type->enumerator.fields.length, backing_type->llvm.debug);
} break;
default: unreachable();
}
assert(result);
type->llvm.debug = result;
}
}
}
fn void resolve_type_in_place(Module* module, Type* type)
{
resolve_type_in_place_abi(module, type);
resolve_type_in_place_memory(module, type);
resolve_type_in_place_debug(module, type);
}
fn bool type_is_abi_equal(Module* module, Type* a, Type* b)
{
resolve_type_in_place(module, a);
resolve_type_in_place(module, b);
bool result = a == b;
if (!result)
{
result = a->llvm.abi == b->llvm.abi;
}
return result;
}
fn AbiInformation abi_system_v_get_ignore(Module* module, Type* semantic_type)
{
resolve_type_in_place(module, semantic_type);
return {
.semantic_type = semantic_type,
.flags = {
.kind = AbiKind::ignore,
},
};
}
fn AbiInformation abi_system_v_get_direct(Module* module, DirectOptions direct)
{
AbiInformation result = {
.semantic_type = direct.semantic_type,
.flags = {
.kind = AbiKind::direct,
},
};
resolve_type_in_place(module, direct.semantic_type);
resolve_type_in_place(module, direct.type);
if (unlikely(direct.padding))
{
resolve_type_in_place(module, direct.padding);
}
result.set_coerce_to_type(direct.type);
result.set_padding_type(direct.type);
result.set_direct_offset(direct.offset);
result.set_direct_alignment(direct.alignment);
result.set_can_be_flattened(!direct.cannot_be_flattened);
return result;
}
struct ExtendOptions
{
Type* semantic_type;
Type* type;
bool sign;
};
fn AbiInformation abi_system_v_get_extend(ExtendOptions options)
{
assert(is_integral_or_enumeration_type(options.semantic_type));
AbiInformation result = {
.semantic_type = options.semantic_type,
.flags = {
.kind = AbiKind::extend,
},
};
result.set_coerce_to_type(options.type ? options.type : options.semantic_type);
result.set_padding_type(0);
result.set_direct_offset(0);
result.set_direct_alignment(0);
result.flags.sign_extension = options.sign;
return result;
}
struct AbiSystemVClassifyArgumentTypeOptions
{
u32 available_gpr;
bool is_named_argument;
bool is_reg_call;
};
struct AbiSystemVClassifyArgumentTypeResult
{
AbiInformation abi;
AbiRegisterCountSystemV needed_registers;
};
fn AbiSystemVClassifyArgumentTypeResult abi_system_v_classify_argument_type(Module* module, Type* semantic_argument_type, AbiSystemVClassifyArgumentTypeOptions options)
{
auto classify_result = abi_system_v_classify_type(semantic_argument_type, AbiSystemVClassify{
.base_offset = 0,
.is_variable_argument = !options.is_named_argument,
.is_register_call = options.is_reg_call,
});
auto low_class = classify_result.r[0];
auto high_class = classify_result.r[1];
AbiRegisterCountSystemV needed_registers = {};
Type* low_type = 0;
switch (low_class)
{
case AbiSystemVClass::integer:
{
needed_registers.gpr += 1;
low_type = get_integer_type_at_offset(module, semantic_argument_type, 0, semantic_argument_type, 0);
if (high_class == AbiSystemVClass::none && low_type->id == TypeId::integer)
{
// TODO: if enumerator
if (is_integral_or_enumeration_type(semantic_argument_type) && is_promotable_integer_type_for_abi(semantic_argument_type))
{
return { abi_system_v_get_extend({
.semantic_type = semantic_argument_type,
.sign = type_is_signed(semantic_argument_type),
}), needed_registers };
}
}
trap();
} break;
default: unreachable();
}
trap();
}
struct AbiSystemVClassifyArgumentOptions
{
Type* type;
u16 abi_start;
bool is_reg_call = false;
bool is_named_argument;
};
fn AbiInformation abi_system_v_classify_argument(Module* module, AbiRegisterCountSystemV* available_registers, Slice<LLVMTypeRef> llvm_abi_argument_type_buffer, Slice<Type*> abi_argument_type_buffer, AbiSystemVClassifyArgumentOptions options)
{
auto semantic_argument_type = options.type;
if (options.is_reg_call)
{
trap();
}
auto result = abi_system_v_classify_argument_type(module, semantic_argument_type, {
.available_gpr = available_registers->gpr,
.is_named_argument = options.is_named_argument,
.is_reg_call = options.is_reg_call,
});
auto abi = result.abi;
auto needed_registers = result.needed_registers;
AbiInformation argument_abi;
if (available_registers->gpr >= needed_registers.gpr && available_registers->sse >= needed_registers.sse)
{
available_registers->gpr -= needed_registers.gpr;
available_registers->sse -= needed_registers.sse;
argument_abi = abi;
}
else
{
trap();
}
if (argument_abi.get_padding_type())
{
trap();
}
argument_abi.abi_start = options.abi_start;
u16 count = 0;
switch (argument_abi.flags.kind)
{
case AbiKind::direct:
case AbiKind::extend:
{
auto coerce_to_type = argument_abi.get_coerce_to_type();
resolve_type_in_place(module, coerce_to_type);
auto is_flattened_struct = argument_abi.flags.kind == AbiKind::direct && argument_abi.get_can_be_flattened() && coerce_to_type->id == TypeId::structure;
count = is_flattened_struct ? coerce_to_type->structure.fields.length : 1;
if (is_flattened_struct)
{
trap();
}
else
{
llvm_abi_argument_type_buffer[argument_abi.abi_start] = coerce_to_type->llvm.abi;
abi_argument_type_buffer[argument_abi.abi_start] = coerce_to_type;
}
} break;
default: unreachable();
}
assert(count);
argument_abi.abi_count = count;
return argument_abi;
}
fn AbiInformation abi_system_classify_return_type(Module* module, Type* semantic_return_type)
{
auto type_classes = abi_system_v_classify_type(semantic_return_type, {});
auto low_class = type_classes.r[0];
auto high_class = type_classes.r[1];
assert(high_class != AbiSystemVClass::memory || low_class == AbiSystemVClass::memory);
assert(high_class != AbiSystemVClass::sse_up || low_class == AbiSystemVClass::sse);
Type* low_type = 0;
switch (low_class)
{
case AbiSystemVClass::none:
{
if (high_class == AbiSystemVClass::none)
{
return abi_system_v_get_ignore(module, semantic_return_type);
}
trap();
} break;
case AbiSystemVClass::integer:
{
low_type = get_integer_type_at_offset(module, semantic_return_type, 0, semantic_return_type, 0);
if (high_class == AbiSystemVClass::none && low_type->id == TypeId::integer)
{
if (semantic_return_type->id == TypeId::enumerator)
{
trap();
}
if (is_integral_or_enumeration_type(semantic_return_type) && is_promotable_integer_type_for_abi(semantic_return_type))
{
trap();
}
}
} break;
default: unreachable();
}
Type* high_type = 0;
switch (high_class)
{
case AbiSystemVClass::none:
break;
case AbiSystemVClass::integer:
{
trap();
} break;
default: unreachable();
}
if (high_type)
{
trap();
}
auto result = abi_system_v_get_direct(module, {
.semantic_type = semantic_return_type,
.type = low_type,
});
return result;
}
struct AttributeBuildOptions
{
AbiInformation return_abi;
Slice<AbiInformation> argument_abis;
Slice<Type*> abi_argument_types;
Type* abi_return_type;
FunctionAttributes attributes;
bool call_site;
};
struct AllocaOptions
{
Type* type;
String name = string_literal("");
u32 alignment;
};
fn LLVMValueRef create_alloca(Module* module, AllocaOptions options)
{
auto abi_type = options.type;
resolve_type_in_place(module, abi_type);
u32 alignment;
if (options.alignment)
{
alignment = options.alignment;
}
else
{
alignment = get_byte_alignment(abi_type);
}
auto alloca = llvm_builder_create_alloca(module->llvm.builder, abi_type->llvm.memory, 0, alignment, options.name);
return alloca;
}
struct StoreOptions
{
LLVMValueRef source;
LLVMValueRef destination;
Type* type;
u32 alignment;
};
fn void create_store(Module* module, StoreOptions options)
{
assert(options.source);
assert(options.destination);
assert(options.type);
auto resolved_type = resolve_alias(module, options.type);
resolve_type_in_place(module, resolved_type);
LLVMValueRef source_value;
LLVMTypeRef memory_type = resolved_type->llvm.memory;
if (resolved_type->llvm.abi == memory_type)
{
source_value = options.source;
}
else
{
source_value = LLVMBuildIntCast2(module->llvm.builder, options.source, memory_type, type_is_signed(resolved_type), "");
}
u32 alignment;
if (options.alignment)
{
alignment = options.alignment;
}
else
{
alignment = get_byte_alignment(resolved_type);
}
auto store = LLVMBuildStore(module->llvm.builder, source_value, options.destination);
LLVMSetAlignment(store, alignment);
}
struct LoadOptions
{
Type* type;
LLVMValueRef pointer;
u32 alignment;
TypeKind kind;
};
fn LLVMValueRef create_load(Module* module, LoadOptions options)
{
resolve_type_in_place(module, options.type);
u32 alignment;
if (options.alignment)
{
alignment = options.alignment;
}
else
{
alignment = get_byte_alignment(options.type);
}
auto result = LLVMBuildLoad2(module->llvm.builder, options.type->llvm.memory, options.pointer, "");
LLVMSetAlignment(result, alignment);
switch (options.kind)
{
case TypeKind::abi:
{
if (options.type->llvm.memory == options.type->llvm.abi)
{
break;
}
else
{
result = LLVMBuildIntCast2(module->llvm.builder, result, options.type->llvm.abi, type_is_signed(options.type), "");
}
} break;
case TypeKind::memory: break;
}
return result;
}
struct GEPOptions
{
LLVMTypeRef type;
LLVMValueRef pointer;
Slice<LLVMValueRef> indices;
bool inbounds = true;
};
fn LLVMValueRef create_gep(Module* module, GEPOptions options)
{
auto* gep_function = options.inbounds ? &LLVMBuildInBoundsGEP2 : &LLVMBuildGEP2;
auto gep = gep_function(module->llvm.builder, options.type, options.pointer, options.indices.pointer, (u32)options.indices.length, "");
return gep;
}
fn BBLLVMAttributeList build_attribute_list(Module* module, AttributeBuildOptions options)
{
resolve_type_in_place(module, options.return_abi.semantic_type);
BBLLVMAttributeListOptions attributes = {};
attributes.return_ = {
.semantic_type = options.return_abi.semantic_type->llvm.memory,
.abi_type = options.abi_return_type->llvm.abi,
.dereferenceable_bytes = 0,
.alignment = 0,
.no_alias = false,
.non_null = false,
.no_undef = false,
.sign_extend = options.return_abi.flags.kind == AbiKind::extend and options.return_abi.flags.sign_extension,
.zero_extend = options.return_abi.flags.kind == AbiKind::extend and !options.return_abi.flags.sign_extension,
.in_reg = false,
.no_fp_class = 0, // TODO: this is a struct
.struct_return = false,
.writable = false,
.dead_on_unwind = false,
.in_alloca = false,
.dereferenceable = false,
.dereferenceable_or_null = false,
.nest = false,
.by_value = false,
.by_reference = false,
.no_capture = false,
};
BBLLVMArgumentAttributes argument_attribute_buffer[128];
Slice<BBLLVMArgumentAttributes> argument_attributes = { .pointer = argument_attribute_buffer, .length = options.abi_argument_types.length };
attributes.argument_pointer = argument_attributes.pointer;
attributes.argument_count = argument_attributes.length;
if (options.return_abi.flags.kind == AbiKind::indirect)
{
trap();
}
for (auto& abi: options.argument_abis)
{
for (auto abi_index = abi.abi_start; abi_index < abi.abi_count; abi_index += 1)
{
auto& attributes = argument_attributes[abi_index];
resolve_type_in_place(module, abi.semantic_type);
auto abi_type = options.abi_argument_types[abi_index];
resolve_type_in_place(module, abi_type);
attributes = {
.semantic_type = abi.semantic_type->llvm.memory,
.abi_type = abi_type->llvm.abi,
.dereferenceable_bytes = 0,
.alignment = (u32)(abi.flags.kind == AbiKind::indirect ? 8 : 0),
.no_alias = false,
.non_null = false,
.no_undef = false,
.sign_extend = abi.flags.kind == AbiKind::extend and abi.flags.sign_extension,
.zero_extend = abi.flags.kind == AbiKind::extend and !abi.flags.sign_extension,
.in_reg = abi.flags.in_reg,
.no_fp_class = {},
.struct_return = false,
.writable = false,
.dead_on_unwind = false,
.in_alloca = false,
.dereferenceable = false,
.dereferenceable_or_null = false,
.nest = false,
.by_value = abi.flags.indirect_by_value,
.by_reference = false,
.no_capture = false,
};
}
}
attributes.function = {
.prefer_vector_width = {},
.stack_protector_buffer_size = {},
.definition_probe_stack = {},
.definition_stack_probe_size = {},
.flags0 = {
.noreturn = options.return_abi.semantic_type == noreturn_type(module),
.cmse_ns_call = false,
.nounwind = true,
.returns_twice = false,
.cold = false,
.hot = false,
.no_duplicate = false,
.convergent = false,
.no_merge = false,
.will_return = false,
.no_caller_saved_registers = false,
.no_cf_check = false,
.no_callback = false,
.alloc_size = false, // TODO
.uniform_work_group_size = false,
.aarch64_pstate_sm_body = false,
.aarch64_pstate_sm_enabled = false,
.aarch64_pstate_sm_compatible = false,
.aarch64_preserves_za = false,
.aarch64_in_za = false,
.aarch64_out_za = false,
.aarch64_inout_za = false,
.aarch64_preserves_zt0 = false,
.aarch64_in_zt0 = false,
.aarch64_out_zt0 = false,
.aarch64_inout_zt0 = false,
.optimize_for_size = false,
.min_size = false,
.no_red_zone = false,
.indirect_tls_seg_refs = false,
.no_implicit_floats = false,
.sample_profile_suffix_elision_policy = false,
.memory_none = false,
.memory_readonly = false,
.memory_inaccessible_or_arg_memory_only = false,
.memory_arg_memory_only = false,
.strict_fp = false,
.no_inline = options.attributes.inline_behavior == InlineBehavior::no_inline,
.always_inline = options.attributes.inline_behavior == InlineBehavior::always_inline,
.guard_no_cf = false,
// TODO: branch protection function attributes
// TODO: cpu features
// CALL-SITE ATTRIBUTES
.call_no_builtins = false,
// DEFINITION-SITE ATTRIBUTES
.definition_frame_pointer_kind = module->has_debug_info ? BBLLVMFramePointerKind::all : BBLLVMFramePointerKind::none,
.definition_less_precise_fpmad = false,
.definition_null_pointer_is_valid = false,
.definition_no_trapping_fp_math = false,
.definition_no_infs_fp_math = false,
.definition_no_nans_fp_math = false,
.definition_approx_func_fp_math = false,
.definition_unsafe_fp_math = false,
.definition_use_soft_float = false,
.definition_no_signed_zeroes_fp_math = false,
.definition_stack_realignment = false,
.definition_backchain = false,
.definition_split_stack = false,
.definition_speculative_load_hardening = false,
.definition_zero_call_used_registers = ZeroCallUsedRegsKind::all,
// TODO: denormal builtins
.definition_non_lazy_bind = false,
.definition_cmse_nonsecure_entry = false,
.definition_unwind_table_kind = BBLLVMUWTableKind::None,
},
.flags1 = {
.definition_disable_tail_calls = false,
.definition_stack_protect_strong = false,
.definition_stack_protect = false,
.definition_stack_protect_req = false,
.definition_aarch64_new_za = false,
.definition_aarch64_new_zt0 = false,
.definition_optimize_none = false,
.definition_naked = !options.call_site and options.attributes.naked,
.definition_inline_hint = !options.call_site and options.attributes.inline_behavior == InlineBehavior::inline_hint,
},
};
auto attribute_list = llvm_attribute_list_build(module->llvm.context, &attributes, options.call_site);
return attribute_list;
}
fn void check_types(Module* module, Type* expected, Type* source)
{
assert(expected);
assert(source);
if (expected != source)
{
auto resolved_expected = resolve_alias(module, expected);
auto resolved_source = resolve_alias(module, source);
if (resolved_expected != resolved_source)
{
auto is_dst_p_and_source_int = resolved_expected->id == TypeId::pointer && resolved_source->id == TypeId::integer;
if (!is_dst_p_and_source_int)
{
report_error();
}
}
}
}
fn void typecheck(Module* module, Type* expected, Type* source)
{
if (expected)
{
check_types(module, expected, source);
}
}
fn bool unary_is_boolean(UnaryId id)
{
switch (id)
{
case UnaryId::exclamation:
return true;
case UnaryId::minus:
case UnaryId::plus:
case UnaryId::ampersand:
case UnaryId::tilde:
case UnaryId::enum_name:
case UnaryId::extend:
case UnaryId::truncate:
case UnaryId::pointer_cast:
case UnaryId::int_from_enum:
case UnaryId::int_from_pointer:
case UnaryId::va_end:
case UnaryId::bitwise_not:
case UnaryId::dereference:
return false;
}
}
fn bool binary_is_boolean(BinaryId id)
{
switch (id)
{
case BinaryId::add:
case BinaryId::sub:
case BinaryId::mul:
case BinaryId::div:
case BinaryId::rem:
case BinaryId::bitwise_and:
case BinaryId::bitwise_or:
case BinaryId::bitwise_xor:
case BinaryId::shift_left:
case BinaryId::shift_right:
return false;
case BinaryId::compare_equal:
case BinaryId::compare_not_equal:
case BinaryId::compare_greater:
case BinaryId::compare_less:
case BinaryId::compare_greater_equal:
case BinaryId::compare_less_equal:
case BinaryId::logical_and:
case BinaryId::logical_or:
case BinaryId::logical_and_shortcircuit:
case BinaryId::logical_or_shortcircuit:
return true;
}
}
fn bool binary_is_shortcircuiting(BinaryId id)
{
switch (id)
{
case BinaryId::logical_and_shortcircuit:
case BinaryId::logical_or_shortcircuit:
return true;
default:
return false;
}
}
fn void analyze_type(Module* module, Value* value, Type* expected_type);
fn void analyze_binary_type(Module* module, Value* left, Value* right, bool is_boolean, Type* expected_type)
{
auto left_constant = left->is_constant();
auto right_constant = right->is_constant();
if (!expected_type)
{
if (left_constant && right_constant)
{
if (!left->type && !right->type)
{
auto are_string_literal = left->id == ValueId::string_literal && right->id == ValueId::string_literal;
if (are_string_literal)
{
expected_type = get_slice_type(module, uint8(module));
}
else
{
report_error();
}
}
}
}
if (is_boolean || !expected_type)
{
if (left_constant)
{
analyze_type(module, right, 0);
analyze_type(module, left, right->type);
}
else
{
analyze_type(module, left, 0);
analyze_type(module, right, left->type);
}
}
else if (!is_boolean && expected_type)
{
analyze_type(module, left, expected_type);
analyze_type(module, right, expected_type);
}
else
{
report_error(); // TODO: this might not be an error necessarily?
}
assert(left->type);
assert(right->type);
}
fn void analyze_type(Module* module, Value* value, Type* expected_type)
{
assert(!value->type);
assert(!value->llvm);
if (expected_type && expected_type->id == TypeId::unresolved)
{
trap();
}
Type* value_type = 0;
switch (value->id)
{
case ValueId::constant_integer:
{
if (!expected_type)
{
report_error();
}
resolve_type_in_place(module, expected_type);
auto* resolved_type = resolve_alias(module, expected_type);
switch (resolved_type->id)
{
case TypeId::integer:
{
if (value->constant_integer.is_signed)
{
if (resolved_type->integer.is_signed)
{
report_error();
}
trap();
}
else
{
auto max_value = integer_max_value(resolved_type->integer.bit_count, resolved_type->integer.is_signed);
if (value->constant_integer.value > max_value)
{
report_error();
}
value_type = expected_type;
}
} break;
case TypeId::pointer: value_type = uint64(module); break;
default: trap();
}
typecheck(module, expected_type, value_type);
} break;
case ValueId::unary:
{
auto unary_id = value->unary.id;
auto unary_value = value->unary.value;
switch (unary_id)
{
case UnaryId::extend:
{
if (!expected_type)
{
report_error();
}
auto extended_value = unary_value;
analyze_type(module, extended_value, 0);
auto source = extended_value->type;
assert(source);
auto source_bit_size = get_bit_size(source);
auto expected_bit_size = get_bit_size(expected_type);
if (source_bit_size > expected_bit_size)
{
report_error();
}
else if (source_bit_size == expected_bit_size && type_is_signed(source) == type_is_signed(expected_type))
{
report_error();
}
value_type = expected_type;
} break;
case UnaryId::truncate:
{
if (!expected_type)
{
report_error();
}
analyze_type(module, unary_value, 0);
auto expected_bit_size = get_bit_size(expected_type);
auto source_bit_size = get_bit_size(unary_value->type);
if (expected_bit_size >= source_bit_size)
{
report_error();
}
value_type = expected_type;
} break;
case UnaryId::dereference:
{
analyze_type(module, unary_value, 0);
if (value->kind == ValueKind::left)
{
report_error();
}
auto pointer_type = unary_value->type;
assert(pointer_type->id == TypeId::pointer);
auto dereference_type = pointer_type->pointer.element_type;
typecheck(module, expected_type, dereference_type);
value_type = dereference_type;
} break;
case UnaryId::int_from_enum:
{
analyze_type(module, unary_value, 0);
auto value_enum_type = unary_value->type;
if (value_enum_type->id != TypeId::enumerator)
{
report_error();
}
auto backing_type = value_enum_type->enumerator.backing_type;
typecheck(module, expected_type, backing_type);
value_type = backing_type;
} break;
default:
{
auto is_boolean = unary_is_boolean(unary_id);
if (is_boolean)
{
analyze_type(module, unary_value, 0);
value_type = uint1(module);
}
else
{
analyze_type(module, unary_value, expected_type);
value_type = unary_value->type;
}
typecheck(module, expected_type, value_type);
} break;
}
} break;
case ValueId::unary_type:
{
auto unary_type = value->unary_type.type;
auto unary_type_id = value->unary_type.id;
switch (unary_type_id)
{
case UnaryTypeId::byte_size:
{
if (!expected_type)
{
report_error();
}
trap();
} break;
case UnaryTypeId::integer_max:
{
if (unary_type->id != TypeId::integer)
{
report_error();
}
if (expected_type)
{
if (expected_type->id != TypeId::integer)
{
report_error();
}
}
value_type = expected_type ? expected_type : unary_type;
typecheck(module, expected_type, value_type);
} break;
}
} break;
case ValueId::binary:
{
auto is_boolean = binary_is_boolean(value->binary.id);
analyze_binary_type(module, value->binary.left, value->binary.right, is_boolean, expected_type);
check_types(module, value->binary.left->type, value->binary.right->type);
value_type = is_boolean ? uint1(module) : value->binary.left->type;
} break;
case ValueId::variable_reference:
{
switch (value->kind)
{
case ValueKind::left: value_type = value->variable_reference->storage->type; break;
case ValueKind::right: value_type = value->variable_reference->type; break;
}
assert(value_type);
typecheck(module, expected_type, value_type);
} break;
case ValueId::call:
{
auto call = &value->call;
auto callable = call->callable;
analyze_type(module, callable, 0);
Type* function_type = 0;
switch (callable->id)
{
case ValueId::variable_reference:
{
auto variable_type = callable->variable_reference->type;
switch (variable_type->id)
{
case TypeId::function:
function_type = variable_type; break;
case TypeId::pointer:
{
auto* element_type = variable_type->pointer.element_type;
switch (element_type->id)
{
case TypeId::function: function_type = element_type; break;
default: report_error();
}
} break;
default: report_error();
}
} break;
default:
report_error();
}
assert(function_type);
call->function_type = function_type;
auto semantic_argument_types = function_type->function.semantic_argument_types;
auto call_arguments = call->arguments;
if (function_type->function.is_variable_arguments)
{
if (call_arguments.length < semantic_argument_types.length)
{
report_error();
}
}
else
{
if (call_arguments.length != semantic_argument_types.length)
{
report_error();
}
}
for (u64 i = 0; i < semantic_argument_types.length; i += 1)
{
auto* argument_type = semantic_argument_types[i];
auto* call_argument = call_arguments[i];
unused(argument_type);
unused(call_argument);
trap();
}
for (u64 i = semantic_argument_types.length; i < call_arguments.length; i += 1)
{
trap();
}
auto semantic_return_type = function_type->function.semantic_return_type;
typecheck(module, expected_type, semantic_return_type);
value_type = semantic_return_type;
} break;
case ValueId::array_initialization:
{
if (expected_type)
{
if (expected_type->array.element_count == 0)
{
expected_type->array.element_count = value->array_initialization.values.length;
assert(expected_type->name.equal(string_literal("")));
expected_type->name = array_name(module, expected_type->array.element_type, expected_type->array.element_count);
}
else
{
trap();
}
bool is_constant = true;
auto* element_type = expected_type->array.element_type;
for (auto value : value->array_initialization.values)
{
analyze_type(module, value, element_type);
is_constant = is_constant && value->is_constant();
}
value->array_initialization.is_constant = is_constant;
if (value->kind == ValueKind::left) // TODO: possible?
{
report_error();
}
value_type = expected_type;
}
else
{
trap();
}
} break;
case ValueId::array_expression:
{
analyze_type(module, value->array_expression.index, uint64(module));
auto array_like = value->array_expression.array_like;
array_like->kind = ValueKind::left;
analyze_type(module, array_like, 0);
assert(array_like->kind == ValueKind::left);
auto array_like_type = array_like->type;
if (array_like_type->id != TypeId::pointer)
{
report_error();
}
auto pointer_element_type = array_like_type->pointer.element_type;
Type* element_type = 0;
switch (pointer_element_type->id)
{
case TypeId::array:
{
element_type = pointer_element_type->array.element_type;
} break;
case TypeId::structure:
{
trap();
} break;
case TypeId::pointer:
{
trap();
} break;
default: report_error();
}
assert(element_type);
value_type = element_type;
if (value->kind == ValueKind::left)
{
value_type = get_pointer_type(module, element_type);
}
typecheck(module, expected_type, value_type);
} break;
case ValueId::enum_literal:
{
if (!expected_type)
{
report_error();
}
if (expected_type->id != TypeId::enumerator)
{
report_error();
}
value_type = expected_type;
} break;
default: unreachable();
}
assert(value_type);
value->type = value_type;
}
fn LLVMTypeRef get_llvm_type(Type* type, TypeKind type_kind)
{
switch (type_kind)
{
case TypeKind::abi:
return type->llvm.abi;
case TypeKind::memory:
return type->llvm.memory;
}
}
fn LLVMValueRef emit_call(Module* module, Value* value, LLVMValueRef left_llvm, Type* left_type)
{
unused(left_llvm);
unused(left_type);
switch (value->id)
{
case ValueId::call:
{
auto call = &value->call;
auto raw_function_type = call->function_type;
auto callable = call->callable;
auto call_arguments = call->arguments;
LLVMValueRef llvm_callable = 0;
switch (callable->id)
{
case ValueId::variable_reference:
{
auto variable = callable->variable_reference;
auto variable_type = variable->type;
switch (variable_type->id)
{
case TypeId::pointer:
{
auto element_type = variable_type->pointer.element_type;
switch (element_type->id)
{
case TypeId::function:
{
llvm_callable = create_load(module, LoadOptions{
.type = get_pointer_type(module, raw_function_type),
.pointer = variable->storage->llvm,
});
} break;
default: report_error();
}
} break;
case TypeId::function: llvm_callable = variable->storage->llvm; break;
default: report_error();
}
} break;
default: report_error();
}
assert(llvm_callable);
LLVMValueRef llvm_abi_argument_value_buffer[64];
Type* abi_argument_type_buffer[64];
AbiInformation argument_abi_buffer[64];
u16 abi_argument_count = 0;
bool uses_in_alloca = false;
if (uses_in_alloca)
{
trap();
}
LLVMValueRef llvm_indirect_return_value = 0;
unused(llvm_indirect_return_value);
auto& return_abi = raw_function_type->function.return_abi;
auto return_abi_kind = return_abi.flags.kind;
switch (return_abi_kind)
{
case AbiKind::indirect:
case AbiKind::in_alloca:
case AbiKind::coerce_and_expand:
{
trap();
} break;
default: break;
}
auto available_registers = raw_function_type->function.available_registers;
unused(available_registers);
for (u64 call_argument_index = 0; call_argument_index < call_arguments.length; call_argument_index += 1)
{
trap();
}
auto declaration_abi_argument_count = raw_function_type->function.abi_argument_types.length;
if (raw_function_type->function.is_variable_arguments)
{
assert(abi_argument_count >= declaration_abi_argument_count);
}
else
{
assert(abi_argument_count == declaration_abi_argument_count);
}
assert(raw_function_type->llvm.abi);
auto llvm_call = LLVMBuildCall2(module->llvm.builder, raw_function_type->llvm.abi, llvm_callable, llvm_abi_argument_value_buffer, abi_argument_count, "");
auto attribute_list = build_attribute_list(module, {
.return_abi = return_abi,
.argument_abis = { .pointer = argument_abi_buffer, .length = call_arguments.length },
.abi_argument_types = { .pointer = abi_argument_type_buffer, .length = abi_argument_count },
.abi_return_type = raw_function_type->function.abi_return_type,
.attributes = {},
.call_site = true,
});
LLVMSetInstructionCallConv(llvm_call, llvm_calling_convention(raw_function_type->function.calling_convention));
llvm_call_base_set_attributes(llvm_call, attribute_list);
switch (return_abi_kind)
{
case AbiKind::ignore:
{
trap();
} break;
case AbiKind::direct:
case AbiKind::extend:
{
auto coerce_to_type = return_abi.get_coerce_to_type();
if (type_is_abi_equal(module, return_abi.semantic_type, coerce_to_type) && return_abi.attributes.direct.offset == 0)
{
auto evaluation_kind = get_evaluation_kind(coerce_to_type);
switch (evaluation_kind)
{
case EvaluationKind::scalar: return llvm_call;
case EvaluationKind::aggregate: break;
case EvaluationKind::complex: unreachable();
}
}
trap();
} break;
case AbiKind::indirect:
{
trap();
} break;
default: unreachable();
}
} break;
default: unreachable();
}
}
fn void emit_value(Module* module, Value* value, TypeKind type_kind)
{
assert(value->type);
assert(!value->llvm);
auto resolved_value_type = resolve_alias(module, value->type);
resolve_type_in_place(module, resolved_value_type);
auto must_be_constant = !module->current_function && !module->current_macro_instantiation;
LLVMValueRef llvm_value = 0;
switch (value->id)
{
case ValueId::constant_integer:
{
auto llvm_integer_type = get_llvm_type(resolved_value_type, type_kind);
llvm_value = LLVMConstInt(llvm_integer_type, value->constant_integer.value, value->constant_integer.is_signed);
} break;
case ValueId::unary:
{
auto unary_value = value->unary.value;
assert(!unary_value->llvm);
auto resolved_unary_type = resolve_alias(module, unary_value->type);
emit_value(module, unary_value, type_kind);
auto destination_type = get_llvm_type(resolved_value_type, type_kind);
assert(destination_type);
auto llvm_unary_value = unary_value->llvm;
assert(llvm_unary_value);
switch (value->unary.id)
{
case UnaryId::minus:
{
if (value->unary.value->is_constant())
{
llvm_value = LLVMConstNeg(llvm_unary_value);
}
else
{
llvm_value = LLVMBuildNeg(module->llvm.builder, llvm_unary_value, "");
}
} break;
case UnaryId::plus:
{
trap();
} break;
case UnaryId::ampersand:
{
assert(resolved_value_type == resolved_unary_type);
llvm_value = llvm_unary_value;
} break;
case UnaryId::exclamation:
{
trap();
} break;
case UnaryId::tilde:
{
trap();
} break;
case UnaryId::enum_name:
{
trap();
} break;
case UnaryId::extend:
{
assert(resolved_unary_type->id == TypeId::integer);
if (resolved_unary_type->integer.is_signed)
{
llvm_value = LLVMBuildSExt(module->llvm.builder, llvm_unary_value, destination_type, "");
}
else
{
llvm_value = LLVMBuildZExt(module->llvm.builder, llvm_unary_value, destination_type, "");
}
} break;
case UnaryId::truncate:
{
if (type_kind != TypeKind::abi)
{
assert(resolved_value_type->llvm.abi == resolved_value_type->llvm.memory);
}
llvm_value = LLVMBuildTrunc(module->llvm.builder, llvm_unary_value, destination_type, "");
} break;
case UnaryId::pointer_cast:
{
trap();
} break;
case UnaryId::int_from_enum:
{
llvm_value = llvm_unary_value;
} break;
case UnaryId::int_from_pointer:
{
trap();
} break;
case UnaryId::va_end:
{
trap();
} break;
case UnaryId::bitwise_not:
{
trap();
} break;
case UnaryId::dereference:
{
if (type_kind != TypeKind::memory)
{
trap();
}
switch (value->kind)
{
case ValueKind::right:
{
auto pointer_type = unary_value->type;
assert(pointer_type->id == TypeId::pointer);
auto child_type = pointer_type->pointer.element_type;
assert(child_type == resolved_value_type);
auto load = create_load(module, LoadOptions{
.type = child_type,
.pointer = unary_value->llvm,
});
llvm_value = load;
} break;
case ValueKind::left:
trap();
}
} break;
}
} break;
case ValueId::unary_type:
{
auto unary_type = value->unary_type.type;
auto unary_type_id = value->unary_type.id;
resolve_type_in_place(module, unary_type);
switch (unary_type_id)
{
case UnaryTypeId::byte_size:
{
trap();
} break;
case UnaryTypeId::integer_max:
{
assert(unary_type->id == TypeId::integer);
auto is_signed = unary_type->integer.is_signed;
auto max_value = integer_max_value(unary_type->integer.bit_count, is_signed);
auto constant_integer = LLVMConstInt(resolved_value_type->llvm.abi, max_value, is_signed);
llvm_value = constant_integer;
} break;
}
} break;
case ValueId::binary:
{
bool is_shorcircuiting = binary_is_shortcircuiting(value->binary.id);
if (is_shorcircuiting)
{
trap();
}
else
{
LLVMValueRef llvm_values[2];
Value* values[2] = { value->binary.left, value->binary.right };
for (u64 i = 0; i < array_length(values); i += 1)
{
auto* binary_value = values[i];
if (binary_value->llvm)
{
assert(false); // TODO: check if this if is really necessary
}
else
{
emit_value(module, binary_value, TypeKind::abi);
}
llvm_values[i] = binary_value->llvm;
}
switch (resolved_value_type->id)
{
case TypeId::integer:
{
switch (value->binary.id)
{
case BinaryId::add: llvm_value = LLVMBuildAdd(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::sub: llvm_value = LLVMBuildSub(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::mul: llvm_value = LLVMBuildMul(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::bitwise_and: llvm_value = LLVMBuildAnd(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::bitwise_or: llvm_value = LLVMBuildOr(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::bitwise_xor: llvm_value = LLVMBuildXor(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::shift_left: llvm_value = LLVMBuildShl(module->llvm.builder, llvm_values[0], llvm_values[1], ""); break;
case BinaryId::shift_right:
if (resolved_value_type->integer.is_signed)
{
llvm_value = LLVMBuildAShr(module->llvm.builder, llvm_values[0], llvm_values[1], "");
}
else
{
llvm_value = LLVMBuildLShr(module->llvm.builder, llvm_values[0], llvm_values[1], "");
}
break;
case BinaryId::div:
if (resolved_value_type->integer.is_signed)
{
llvm_value = LLVMBuildSDiv(module->llvm.builder, llvm_values[0], llvm_values[1], "");
}
else
{
llvm_value = LLVMBuildUDiv(module->llvm.builder, llvm_values[0], llvm_values[1], "");
}
break;
case BinaryId::rem:
if (resolved_value_type->integer.is_signed)
{
llvm_value = LLVMBuildSRem(module->llvm.builder, llvm_values[0], llvm_values[1], "");
}
else
{
llvm_value = LLVMBuildURem(module->llvm.builder, llvm_values[0], llvm_values[1], "");
}
break;
case BinaryId::compare_equal:
case BinaryId::compare_not_equal:
case BinaryId::compare_greater:
case BinaryId::compare_less:
case BinaryId::compare_greater_equal:
case BinaryId::compare_less_equal:
{
LLVMIntPredicate predicate;
assert(values[0]->type == values[1]->type);
auto left_signed = type_is_signed(values[0]->type);
auto right_signed = type_is_signed(values[1]->type);
assert(left_signed == right_signed);
auto is_signed = left_signed;
switch (value->binary.id)
{
case BinaryId::compare_equal: predicate = LLVMIntEQ; break;
case BinaryId::compare_not_equal: predicate = LLVMIntNE; break;
case BinaryId::compare_greater: predicate = is_signed ? LLVMIntSGT : LLVMIntUGT;
case BinaryId::compare_less: predicate = is_signed ? LLVMIntSLT : LLVMIntULT;
case BinaryId::compare_greater_equal: predicate = is_signed ? LLVMIntSGE : LLVMIntUGE;
case BinaryId::compare_less_equal: predicate = is_signed ? LLVMIntSLE : LLVMIntULE;
default: unreachable();
}
llvm_value = LLVMBuildICmp(module->llvm.builder, predicate, llvm_values[0], llvm_values[1], "");
} break;
default: unreachable();
}
} break;
default: unreachable();
}
}
} break;
case ValueId::variable_reference:
{
auto* variable = value->variable_reference;
auto resolved_variable_value_type = resolve_alias(module, variable->type);
auto resolved_variable_pointer_type = resolve_alias(module, variable->storage->type);
switch (value->kind)
{
case ValueKind::left:
{
if (resolved_variable_pointer_type == resolved_value_type)
{
llvm_value = variable->storage->llvm;
}
else
{
trap();
}
} break;
case ValueKind::right:
{
if (resolved_variable_value_type != resolved_value_type)
{
report_error();
}
if (must_be_constant)
{
if (variable->scope->kind != ScopeKind::global)
{
report_error();
}
trap();
}
else
{
assert(get_byte_size(resolved_value_type) <= 16);
auto evaluation_kind = get_evaluation_kind(resolved_value_type);
switch (evaluation_kind)
{
case EvaluationKind::scalar:
case EvaluationKind::aggregate:
{
llvm_value = create_load(module, {
.type = resolved_value_type,
.pointer = variable->storage->llvm,
});
} break;
case EvaluationKind::complex:
trap();
}
}
} break;
}
} break;
case ValueId::call:
{
auto call = emit_call(module, value, 0, 0);
llvm_value = call;
} break;
case ValueId::array_initialization:
{
if (value->array_initialization.is_constant)
{
assert(value->kind == ValueKind::right);
auto element_type = resolved_value_type->array.element_type;
auto element_count = value->array_initialization.values.length;
LLVMValueRef value_buffer[64];
resolve_type_in_place(module, element_type);
for (u64 i = 0; i < element_count; i += 1)
{
auto* v = value->array_initialization.values[i];
emit_value(module, v, TypeKind::memory);
value_buffer[i] = v->llvm;
}
auto constant_array = LLVMConstArray2(element_type->llvm.memory, value_buffer, element_count);
llvm_value = constant_array;
}
else
{
trap();
}
} break;
case ValueId::array_expression:
{
auto* array_like = value->array_expression.array_like;
auto* index = value->array_expression.index;
switch (array_like->kind)
{
case ValueKind::left:
{
auto array_like_type = array_like->type;
assert(array_like_type->id == TypeId::pointer);
auto pointer_element_type = array_like_type->pointer.element_type;
switch (pointer_element_type->id)
{
case TypeId::array:
{
auto array_type = pointer_element_type;
emit_value(module, array_like, TypeKind::memory);
emit_value(module, index, TypeKind::memory);
auto uint64_type = uint64(module);
resolve_type_in_place(module, uint64_type);
auto zero_index = LLVMConstNull(uint64_type->llvm.abi);
LLVMValueRef indices[] = { zero_index, index->llvm };
auto gep = create_gep(module, {
.type = array_type->llvm.memory,
.pointer = array_like->llvm,
.indices = array_to_slice(indices),
});
switch (value->kind)
{
case ValueKind::left:
llvm_value = gep;
break;
case ValueKind::right:
llvm_value = create_load(module, LoadOptions{
.type = array_type->array.element_type,
.pointer = gep,
});
break;
}
} break;
case TypeId::structure:
{
trap();
} break;
case TypeId::pointer:
{
trap();
} break;
default: report_error();
}
} break;
case ValueKind::right:
{
trap();
} break;
}
} break;
case ValueId::enum_literal:
{
assert(resolved_value_type->id == TypeId::enumerator);
auto enum_name = value->enum_literal;
bool found = false;
u64 i;
for (i = 0; i < resolved_value_type->enumerator.fields.length; i += 1)
{
auto& field = resolved_value_type->enumerator.fields[i];
if (enum_name.equal(field.name))
{
found = true;
break;
}
}
if (!found)
{
report_error();
}
auto& field = resolved_value_type->enumerator.fields[i];
auto llvm_type = get_llvm_type(resolved_value_type, type_kind);
llvm_value = LLVMConstInt(llvm_type, field.value, type_is_signed(resolved_value_type));
} break;
default: unreachable();
}
assert(llvm_value);
value->llvm = llvm_value;
}
fn void emit_assignment(Module* module, LLVMValueRef left_llvm, Type* left_type, Value* right)
{
assert(!right->llvm);
auto pointer_type = left_type;
auto value_type = right->type;
assert(pointer_type);
assert(value_type);
resolve_type_in_place(module, pointer_type);
resolve_type_in_place(module, value_type);
auto resolved_pointer_type = resolve_alias(module, pointer_type);
auto resolved_value_type = resolve_alias(module, value_type);
assert(resolved_pointer_type->id == TypeId::pointer);
assert(resolved_pointer_type->pointer.element_type == resolved_value_type);
auto type_kind = TypeKind::memory;
auto evaluation_kind = get_evaluation_kind(resolved_value_type);
switch (evaluation_kind)
{
case EvaluationKind::scalar:
{
emit_value(module, right, type_kind);
create_store(module, {
.source = right->llvm,
.destination = left_llvm,
.type = resolved_value_type,
});
} break;
case EvaluationKind::aggregate:
{
switch (right->id)
{
case ValueId::array_initialization:
{
if (right->array_initialization.is_constant)
{
emit_value(module, right, TypeKind::memory);
bool is_constant = true;
LLVMLinkage linkage_type = LLVMInternalLinkage;
LLVMValueRef before = 0;
LLVMThreadLocalMode thread_local_mode = {};
u32 address_space = 0;
bool externally_initialized = false;
auto global = llvm_module_create_global_variable(module->llvm.module, value_type->llvm.memory, is_constant, linkage_type, right->llvm, string_literal("constarray"), before, thread_local_mode, address_space, externally_initialized);
LLVMSetUnnamedAddress(global, LLVMGlobalUnnamedAddr);
auto alignment = get_byte_alignment(resolved_value_type);
LLVMSetAlignment(global, alignment);
auto uint64_type = uint64(module);
resolve_type_in_place(module, uint64_type);
auto element_type = resolved_value_type->array.element_type;
auto element_count = resolved_value_type->array.element_count;
assert(right->array_initialization.values.length == element_count);
u64 memcpy_size = get_byte_size(element_type) * element_count;
LLVMBuildMemCpy(module->llvm.builder, left_llvm, alignment, global, alignment, LLVMConstInt(uint64_type->llvm.abi, memcpy_size, false));
}
else
{
trap();
}
} break;
default: unreachable();
}
} break;
default: unreachable();
}
}
fn void emit_local_storage(Module* module, Variable* variable)
{
assert(!variable->storage);
auto value_type = variable->type;
resolve_type_in_place(module, value_type);
auto pointer_type = get_pointer_type(module, value_type);
auto storage = new_value(module);
auto alloca = create_alloca(module, {
.type = value_type,
.name = variable->name,
});
*storage = Value{
.type = pointer_type,
.id = ValueId::local,
.llvm = alloca,
};
variable->storage = storage;
}
fn void emit_local_variable(Module* module, Local* local)
{
emit_local_storage(module, &local->variable);
assert(local->variable.storage);
if (module->has_debug_info)
{
auto debug_type = local->variable.type->llvm.debug;
assert(debug_type);
bool always_preserve = true;
LLVMDIFlags flags = {};
auto scope = local->variable.scope->llvm;
auto bit_alignment = get_byte_alignment(local->variable.storage->type->pointer.element_type) * 8;
auto local_variable = LLVMDIBuilderCreateAutoVariable(module->llvm.di_builder, scope, (char*)local->variable.name.pointer, local->variable.name.length, module->llvm.file, local->variable.line, debug_type, always_preserve, flags, bit_alignment);
auto debug_location = LLVMDIBuilderCreateDebugLocation(module->llvm.context, local->variable.line, local->variable.column, scope, module->llvm.inlined_at);
LLVMSetCurrentDebugLocation2(module->llvm.builder, debug_location);
auto basic_block = LLVMGetInsertBlock(module->llvm.builder);
assert(basic_block);
LLVMDIBuilderInsertDeclareRecordAtEnd(module->llvm.di_builder, local->variable.storage->llvm, local_variable, LLVMDIBuilderCreateExpression(module->llvm.di_builder, 0, 0), debug_location, basic_block);
}
}
fn void analyze_value(Module* module, Value* value, Type* expected_type, TypeKind type_kind)
{
analyze_type(module, value, expected_type);
emit_value(module, value, type_kind);
}
fn void analyze_statement(Module* module, Scope* scope, Statement* statement, u32* last_line, u32* last_column, LLVMMetadataRef* last_debug_location);
fn void analyze_block(Module* module, Block* block)
{
if (module->has_debug_info)
{
auto lexical_block = LLVMDIBuilderCreateLexicalBlock(module->llvm.di_builder, block->scope.parent->llvm, module->llvm.file, block->scope.line, block->scope.column);
block->scope.llvm = lexical_block;
}
u32 last_line = 0;
u32 last_column = 0;
LLVMMetadataRef last_debug_location = 0;
for (auto* statement = block->first_statement; statement; statement = statement->next)
{
analyze_statement(module, &block->scope, statement, &last_line, &last_column, &last_debug_location);
}
}
fn void analyze_statement(Module* module, Scope* scope, Statement* statement, u32* last_line, u32* last_column, LLVMMetadataRef* last_debug_location)
{
Global* parent_function_global;
if (module->current_function)
{
parent_function_global = module->current_function;
}
else if (module->current_macro_instantiation)
{
parent_function_global = module->current_macro_instantiation->instantiation_function;
}
else
{
report_error();
}
auto* llvm_function = parent_function_global->variable.storage->llvm;
assert(llvm_function);
if (module->has_debug_info)
{
if (statement->line != *last_line || statement->column != *last_column)
{
auto new_location = LLVMDIBuilderCreateDebugLocation(module->llvm.context, statement->line, statement->column, scope->llvm, module->llvm.inlined_at);
*last_debug_location = new_location;
LLVMSetCurrentDebugLocation2(module->llvm.builder, new_location);
*last_line = statement->line;
*last_column = statement->column;
}
}
switch (statement->id)
{
case StatementId::return_st:
{
if (module->current_function)
{
auto& function_type = parent_function_global->variable.storage->type->pointer.element_type->function;
auto& return_abi = function_type.return_abi;
auto return_value = statement->return_st;
switch (return_abi.semantic_type->id)
{
case TypeId::void_type:
{
if (return_value)
{
report_error();
}
} break;
case TypeId::noreturn:
{
report_error();
} break;
default:
{
if (module->has_debug_info)
{
LLVMSetCurrentDebugLocation2(module->llvm.builder, *last_debug_location);
}
auto return_alloca = module->current_function->variable.storage->function.llvm.return_alloca;
if (!return_alloca)
{
report_error();
}
if (!return_value)
{
report_error();
}
analyze_type(module, return_value, return_abi.semantic_type);
auto pointer_type = get_pointer_type(module, return_abi.semantic_type);
emit_assignment(module, return_alloca, pointer_type, return_value);
} break;
}
auto return_block = module->current_function->variable.storage->function.llvm.return_block;
LLVMBuildBr(module->llvm.builder, return_block);
LLVMClearInsertionPosition(module->llvm.builder);
}
else if (module->current_macro_instantiation)
{
trap();
}
else
{
report_error();
}
} break;
case StatementId::local:
{
auto local = statement->local;
auto expected_type = local->variable.type;
assert(!local->variable.storage);
analyze_type(module, local->variable.initial_value, expected_type);
local->variable.type = expected_type ? expected_type : local->variable.initial_value->type;
assert(local->variable.type);
if (expected_type)
{
assert(expected_type == local->variable.type);
}
emit_local_variable(module, local);
emit_assignment(module, local->variable.storage->llvm, local->variable.storage->type, local->variable.initial_value);
} break;
case StatementId::if_st:
{
auto* taken_block = llvm_context_create_basic_block(module->llvm.context, string_literal("if.taken"), llvm_function);
auto* not_taken_block = llvm_context_create_basic_block(module->llvm.context, string_literal("if.not_taken"), llvm_function);
auto* exit_block = llvm_context_create_basic_block(module->llvm.context, string_literal("if.exit"), llvm_function);
auto condition = statement->if_st.condition;
breakpoint();
analyze_value(module, condition, 0, TypeKind::abi);
auto condition_type = condition->type;
LLVMValueRef llvm_condition = 0;
switch (condition_type->id)
{
case TypeId::integer:
{
llvm_condition = condition->llvm;
if (condition_type->integer.bit_count != 1)
{
llvm_condition = LLVMBuildICmp(module->llvm.builder, LLVMIntNE, llvm_condition, LLVMConstNull(condition_type->llvm.abi), "");
}
} break;
default: report_error();
}
assert(llvm_condition);
LLVMBuildCondBr(module->llvm.builder, llvm_condition, taken_block, not_taken_block);
LLVMPositionBuilderAtEnd(module->llvm.builder, taken_block);
analyze_statement(module, scope, statement->if_st.if_statement, last_line, last_column, last_debug_location);
if (LLVMGetInsertBlock(module->llvm.builder))
{
LLVMBuildBr(module->llvm.builder, exit_block);
}
LLVMPositionBuilderAtEnd(module->llvm.builder, not_taken_block);
auto else_statement = statement->if_st.else_statement;
if (else_statement)
{
analyze_statement(module, scope, else_statement, last_line, last_column, last_debug_location);
}
if (LLVMGetInsertBlock(module->llvm.builder))
{
LLVMBuildBr(module->llvm.builder, exit_block);
}
LLVMPositionBuilderAtEnd(module->llvm.builder, exit_block);
} break;
case StatementId::block:
{
analyze_block(module, statement->block);
} break;
default: unreachable();
}
}
fn void emit_debug_argument(Module* module, Argument* argument, LLVMBasicBlockRef basic_block)
{
assert(module->has_debug_info);
resolve_type_in_place(module, argument->variable.type);
bool always_preserve = true;
LLVMDIFlags flags = {};
LLVMMetadataRef scope = argument->variable.scope->llvm;
auto parameter_variable = LLVMDIBuilderCreateParameterVariable(module->llvm.di_builder, scope, (char*)argument->variable.name.pointer, argument->variable.name.length, argument->index, module->llvm.file, argument->variable.line, argument->variable.type->llvm.debug, always_preserve, flags);
auto inlined_at = module->llvm.inlined_at;
auto debug_location = LLVMDIBuilderCreateDebugLocation(module->llvm.context, argument->variable.line, argument->variable.column, scope, inlined_at);
LLVMDIBuilderInsertDeclareRecordAtEnd(module->llvm.di_builder, argument->variable.storage->llvm, parameter_variable, LLVMDIBuilderCreateExpression(module->llvm.di_builder, 0, 0), debug_location, basic_block);
}
void emit(Module* module)
{
llvm_initialize(module);
for (auto* global = module->first_global; global; global = global->next)
{
switch (global->variable.storage->id)
{
case ValueId::function:
case ValueId::external_function:
{
auto function_type = &global->variable.storage->type->pointer.element_type->function;
auto semantic_argument_count = function_type->semantic_argument_types.length;
function_type->argument_abis = arena_allocate<AbiInformation>(module->arena, semantic_argument_count);
auto resolved_calling_convention = resolve_calling_convention(function_type->calling_convention);
auto is_reg_call = resolved_calling_convention == ResolvedCallingConvention::system_v && false; // TODO: regcall calling convention
LLVMTypeRef llvm_abi_argument_type_buffer[64];
switch (resolved_calling_convention)
{
case ResolvedCallingConvention::system_v:
{
function_type->available_registers = {
.system_v = {
.gpr = (u32)(is_reg_call ? 11 : 6),
.sse = (u32)(is_reg_call ? 16 : 8),
},
};
function_type->return_abi = abi_system_classify_return_type(module, function_type->semantic_return_type);
auto return_abi_kind = function_type->return_abi.flags.kind;
Type* abi_argument_type_buffer[64];
u16 abi_argument_type_count = 0;
Type* abi_return_type;
switch (return_abi_kind)
{
case AbiKind::direct:
case AbiKind::extend:
{
abi_return_type = function_type->return_abi.coerce_to_type;
} break;
case AbiKind::ignore:
case AbiKind::indirect:
{
abi_return_type = void_type(module);
} break;
default: unreachable(); // TODO
}
assert(abi_return_type);
function_type->abi_return_type = abi_return_type;
resolve_type_in_place(module, abi_return_type);
if (function_type->return_abi.flags.kind == AbiKind::indirect)
{
trap();
}
for (u64 i = 0; i < semantic_argument_count; i += 1)
{
auto& abi = function_type->argument_abis[i];
auto semantic_argument_type = function_type->semantic_argument_types[i];
auto is_named_argument = i < semantic_argument_count;
assert(is_named_argument);
abi = abi_system_v_classify_argument(module, &function_type->available_registers.system_v, array_to_slice(llvm_abi_argument_type_buffer), array_to_slice(abi_argument_type_buffer), {
.type = semantic_argument_type,
.abi_start = abi_argument_type_count,
.is_named_argument = is_named_argument,
});
abi_argument_type_count += abi.abi_count;
}
auto abi_argument_types = new_type_array(module, abi_argument_type_count);
memcpy(abi_argument_types.pointer, abi_argument_type_buffer, sizeof(abi_argument_type_buffer[0]) * abi_argument_type_count);
function_type->abi_argument_types = abi_argument_types;
} break;
case ResolvedCallingConvention::win64:
{
report_error();
} break;
case ResolvedCallingConvention::count: unreachable();
}
auto llvm_function_type = LLVMFunctionType(function_type->abi_return_type->llvm.abi, llvm_abi_argument_type_buffer, (u32)function_type->abi_argument_types.length, function_type->is_variable_arguments);
LLVMMetadataRef subroutine_type = 0;
if (module->has_debug_info)
{
LLVMMetadataRef debug_argument_type_buffer[64];
Slice<LLVMMetadataRef> debug_argument_types = { .pointer = debug_argument_type_buffer, .length = function_type->argument_abis.length + 1 + function_type->is_variable_arguments };
debug_argument_types[0] = function_type->return_abi.semantic_type->llvm.debug;
assert(debug_argument_types[0]);
auto debug_argument_type_slice = debug_argument_types(1)(0, function_type->argument_abis.length);
for (u64 i = 0; i < function_type->argument_abis.length; i += 1)
{
auto& argument_abi = function_type->argument_abis[i];
auto* debug_argument_type = &debug_argument_type_slice[i];
*debug_argument_type = argument_abi.semantic_type->llvm.debug;
assert(*debug_argument_type);
}
if (function_type->is_variable_arguments)
{
auto void_ty = void_type(module);
assert(void_ty->llvm.debug);
debug_argument_types[function_type->argument_abis.length + 1] = void_ty->llvm.debug;
}
LLVMDIFlags flags = {};
subroutine_type = LLVMDIBuilderCreateSubroutineType(module->llvm.di_builder, module->llvm.file, debug_argument_types.pointer, (u32)debug_argument_types.length, flags);
}
global->variable.storage->type->pointer.element_type->llvm.abi = llvm_function_type;
global->variable.storage->type->pointer.element_type->llvm.debug = subroutine_type;
LLVMLinkage llvm_linkage_type;
switch (global->linkage)
{
case Linkage::internal: llvm_linkage_type = LLVMInternalLinkage; break;
case Linkage::external: llvm_linkage_type = LLVMExternalLinkage; break;
}
unsigned address_space = 0;
auto llvm_function = llvm_module_create_function(module->llvm.module, llvm_function_type, llvm_linkage_type, address_space, global->variable.name);
global->variable.storage->llvm = llvm_function;
LLVMCallConv cc;
switch (function_type->calling_convention)
{
case CallingConvention::c: cc = LLVMCCallConv; break;
case CallingConvention::count: unreachable();
}
LLVMSetFunctionCallConv(llvm_function, cc);
assert(global->variable.storage->id == ValueId::function);
auto attribute_list = build_attribute_list(module, {
.return_abi = function_type->return_abi,
.argument_abis = function_type->argument_abis,
.abi_argument_types = function_type->abi_argument_types,
.abi_return_type = function_type->abi_return_type,
.attributes = global->variable.storage->function.attributes,
.call_site = false,
});
llvm_function_set_attributes(llvm_function, attribute_list);
LLVMMetadataRef subprogram = 0;
auto is_definition = global->variable.storage->id == ValueId::function;
if (module->has_debug_info)
{
auto is_local_to_unit = global->linkage == Linkage::internal;
auto line = global->variable.line;
auto scope_line = line + 1;
LLVMDIFlags flags = {};
auto is_optimized = build_mode_is_optimized(module->build_mode);
subprogram = LLVMDIBuilderCreateFunction(module->llvm.di_builder, module->scope.llvm, (char*)global->variable.name.pointer, global->variable.name.length, (char*)global->variable.name.pointer, global->variable.name.length, module->llvm.file, line, subroutine_type, is_local_to_unit, is_definition, scope_line, flags, is_optimized);
LLVMSetSubprogram(llvm_function, subprogram);
}
if (is_definition)
{
global->variable.storage->function.scope.llvm = subprogram;
module->current_function = global;
LLVMValueRef llvm_abi_argument_buffer[64];
Slice<LLVMValueRef> llvm_abi_arguments = { .pointer = llvm_abi_argument_buffer, .length = function_type->abi_argument_types.length };
LLVMGetParams(llvm_function, llvm_abi_argument_buffer);
auto* entry_block = llvm_context_create_basic_block(module->llvm.context, string_literal("entry"), llvm_function);
auto return_block = llvm_context_create_basic_block(module->llvm.context, string_literal("return_block"), 0);
global->variable.storage->function.llvm.return_block = return_block;
LLVMPositionBuilderAtEnd(module->llvm.builder, entry_block);
LLVMSetCurrentDebugLocation2(module->llvm.builder, 0);
auto return_abi_kind = function_type->return_abi.flags.kind;
switch (return_abi_kind)
{
case AbiKind::indirect:
{
trap();
} break;
case AbiKind::in_alloca:
{
trap();
} break;
default:
{
auto alloca = create_alloca(module, {
.type = function_type->return_abi.semantic_type,
.name = string_literal("retval"),
});
global->variable.storage->function.llvm.return_alloca = alloca;
} break;
case AbiKind::ignore: break;
}
auto arguments = global->variable.storage->function.arguments;
auto argument_abis = function_type->argument_abis;
assert(arguments.length == argument_abis.length);
for (u64 i = 0; i < semantic_argument_count; i += 1)
{
auto* argument = &arguments[i];
auto& argument_abi = argument_abis[i];
auto argument_abi_arguments = llvm_abi_arguments(argument_abi.abi_start)(0, argument_abi.abi_count);
LLVMValueRef semantic_argument_storage = 0;
switch (argument_abi.flags.kind)
{
case AbiKind::direct:
case AbiKind::extend:
{
auto first_argument = argument_abi_arguments[0];
auto coerce_to_type = argument_abi.get_coerce_to_type();
if (coerce_to_type->id != TypeId::structure && type_is_abi_equal(module, coerce_to_type, argument_abi.semantic_type) && argument_abi.attributes.direct.offset == 0)
{
assert(argument_abi.abi_count == 1);
auto is_promoted = false;
auto v = first_argument;
if (coerce_to_type->llvm.abi != LLVMTypeOf(v))
{
trap();
}
if (is_promoted)
{
trap();
}
// TODO: this we can get rid of because we handle all of this inside `create_alloca`, load, stores, etc
if (is_arbitrary_bit_integer(argument_abi.semantic_type))
{
auto bit_count = (u32)get_bit_size(argument_abi.semantic_type);
auto abi_bit_count = align_bit_count(bit_count);
bool is_signed = type_is_signed(argument_abi.semantic_type);
auto destination_type = integer_type(module, { .bit_count = abi_bit_count, .is_signed = is_signed });
auto alloca = create_alloca(module, {
.type = destination_type,
.name = argument->variable.name,
});
LLVMValueRef result;
if (bit_count < abi_bit_count)
{
if (is_signed)
{
result = LLVMBuildSExt(module->llvm.builder, first_argument, destination_type->llvm.memory, "");
}
else
{
result = LLVMBuildZExt(module->llvm.builder, first_argument, destination_type->llvm.memory, "");
}
}
else
{
trap();
}
create_store(module, {
.source = result,
.destination = alloca,
.type = destination_type,
});
semantic_argument_storage = alloca;
}
else
{
trap();
}
}
else
{
trap();
}
} break;
default: unreachable();
}
assert(semantic_argument_storage);
auto storage = new_value(module);
auto value_type = argument->variable.type;
*storage = {
.type = get_pointer_type(module, value_type),
.id = ValueId::argument,
.llvm = semantic_argument_storage,
};
argument->variable.storage = storage;
if (module->has_debug_info)
{
emit_debug_argument(module, argument, entry_block);
}
}
analyze_block(module, global->variable.storage->function.block);
auto* current_basic_block = LLVMGetInsertBlock(module->llvm.builder);
if (current_basic_block)
{
assert(!LLVMGetBasicBlockTerminator(current_basic_block));
if (llvm_basic_block_is_empty(current_basic_block) || llvm_value_use_empty((LLVMValueRef)current_basic_block))
{
LLVMReplaceAllUsesWith((LLVMValueRef)return_block, (LLVMValueRef)current_basic_block);
llvm_basic_block_delete(return_block);
}
else
{
trap();
}
}
else
{
bool is_reachable = false;
if (llvm_value_has_one_use((LLVMValueRef)return_block))
{
auto user = llvm_basic_block_user_begin(return_block);
is_reachable = LLVMIsABranchInst(user) && !LLVMIsConditional(user) && LLVMGetSuccessor(user, 0) == return_block;
if (is_reachable)
{
LLVMPositionBuilderAtEnd(module->llvm.builder, LLVMGetInstructionParent(user));
LLVMInstructionEraseFromParent(user);
llvm_basic_block_delete(return_block);
}
}
if (!is_reachable)
{
trap();
}
}
if (module->has_debug_info)
{
LLVMSetCurrentDebugLocation2(module->llvm.builder, 0);
auto subprogram = LLVMGetSubprogram(llvm_function);
LLVMDIBuilderFinalizeSubprogram(module->llvm.di_builder, subprogram);
}
if (function_type->return_abi.semantic_type == noreturn_type(module) || global->variable.storage->function.attributes.naked)
{
LLVMBuildUnreachable(module->llvm.builder);
}
else if (function_type->return_abi.semantic_type == void_type(module))
{
LLVMBuildRetVoid(module->llvm.builder);
}
else
{
LLVMValueRef return_value = 0;
switch (return_abi_kind)
{
case AbiKind::direct:
case AbiKind::extend:
{
auto return_alloca = global->variable.storage->function.llvm.return_alloca;
auto coerce_to_type = function_type->return_abi.get_coerce_to_type();
auto return_semantic_type = function_type->return_abi.semantic_type;
if (coerce_to_type->id != TypeId::structure && type_is_abi_equal(module, coerce_to_type, return_semantic_type) && function_type->return_abi.attributes.direct.offset == 0)
{
auto store = llvm_find_return_value_dominating_store(module->llvm.builder, return_alloca, return_semantic_type->llvm.abi);
if (store)
{
return_value = LLVMGetOperand(store, 0);
auto alloca = LLVMGetOperand(store, 1);
assert(alloca == return_alloca);
LLVMInstructionEraseFromParent(store);
assert(llvm_value_use_empty(alloca));
LLVMInstructionEraseFromParent(alloca);
}
else
{
return_value = create_load(module, LoadOptions{
.type = return_semantic_type,
.pointer = return_alloca,
});
}
}
else
{
trap();
}
} break;
case AbiKind::indirect:
{
trap();
} break;
default: unreachable();
}
LLVMBuildRet(module->llvm.builder, return_value);
}
// END OF SCOPE
module->current_function = 0;
}
} break;
case ValueId::global:
{
trap();
} break;
default: report_error();
}
}
if (module->has_debug_info)
{
LLVMDIBuilderFinalize(module->llvm.di_builder);
}
String verification_error_message = {};
if (!llvm_module_verify(module->llvm.module, &verification_error_message))
{
dump_module(module);
print(string_literal("\n==========================\nLLVM VERIFICATION ERROR\n==========================\n"));
print(verification_error_message);
fail();
}
if (!module->silent)
{
dump_module(module);
}
}