Files
c3c/src/compiler/tilde_codegen_expr.c
Christoffer Lerno e293c435af 0.6.0: init_new/init_temp removed. LinkedList API rewritten. List "pop" and "remove" function now return Optionals. RingBuffer API rewritten. Allocator interface changed. Deprecated Allocator, DString and mem functions removed. "identity" functions are now constants for Matrix and Complex numbers. @default implementations for interfaces removed. any* => any, same for interfaces. Emit local/private globals as "private" in LLVM, following C "static". Updated enum syntax. Add support [rgba] properties in vectors. Improved checks of aliased "void". Subarray -> slice. Fix of llvm codegen enum check. Improved alignment handling. Add --output-dir #1155. Removed List/Object append. GenericList renamed AnyList. Remove unused "unwrap". Fixes to cond. Optimize output in dead branches. Better checking of operator methods. Disallow any from implementing dynamic methods. Check for operator mismatch. Remove unnecessary bitfield. Remove numbering in --list* commands Old style enum declaration for params/type, but now the type is optional. Add note on #1086. Allow making distinct types out of "void", "typeid", "anyfault" and faults. Remove system linker build options. "Try" expressions must be simple expressions. Add optimized build to Mac tests. Register int. assert(false) only allowed in unused branches or in tests. Compile time failed asserts is a compile time error. Remove current_block_is_target. Bug when assigning an optional from an optional. Remove unused emit_zstring. Simplify phi code. Remove unnecessary unreachable blocks and remove unnecessary current_block NULL assignments. Proper handling of '.' and Win32 '//server' paths. Add "no discard" to expression blocks with a return value. Detect "unsigned >= 0" as errors. Fix issue with distinct void as a member #1147. Improve callstack debug information #1184. Fix issue with absolute output-dir paths. Lambdas were not type checked thoroughly #1185. Fix compilation warning #1187. Request jump table using @jump for switches. Path normalization - fix possible null terminator out of bounds. Improved error messages on inlined macros.
Upgrade of mingw in CI. Fix problems using reflection on interface types #1203. Improved debug information on defer. $foreach doesn't create an implicit syntactic scope.
Error if `@if` depends on `@if`. Updated Linux stacktrace. Fix of default argument stacktrace. Allow linking libraries directly by file path. Improve inlining warning messages. Added `index_of_char_from`. Compiler crash using enum nameof from different module #1205. Removed unused fields in find_msvc. Use vswhere to find msvc. Update tests for LLVM 19
2024-06-12 10:14:26 +02:00

1704 lines
52 KiB
C

// Copyright (c) 2022 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by a LGPLv3.0
// a copy of which can be found in the LICENSE file.
#include "tilde_internal.h"
TB_Reg tilde_get_const_int(TildeContext *c, Type *type, uint64_t i)
{
return type_is_signed(type)
? tb_inst_sint(c->f, tildetype(type), (int64_t)i)
: tb_inst_uint(c->f, tildetype(type), i);
}
TB_Reg tilde_get_const_float(TildeContext *c, Type *type, double d)
{
return type_size(type) == 4 ? tb_inst_float32(c->f, (float)d) : tb_inst_float64(c->f, d);
}
TB_Register tilde_get_zero(TildeContext *c, Type *type)
{
type = type_lowering(type);
TB_DataType data_type = tildetype(type);
switch (data_type.type)
{
case TB_INT:
return type_is_signed(type) ? tb_inst_sint(c->f, data_type, 0) : tb_inst_uint(c->f, data_type, 0);
case TB_FLOAT:
return type->type_kind == TYPE_F32 ? tb_inst_float32(c->f, 0.0f) : tb_inst_float64(c->f, 0.0);
case TB_PTR:
return tb_inst_ptr(c->f, 0);
default:
UNREACHABLE;
}
}
static void tilde_emit_const_expr(TildeContext *c, TBEValue *value, Expr *expr)
{
Type *type = type_reduced_from_expr(expr)->canonical;
switch (expr->const_expr.const_kind)
{
case CONST_BYTES:
TODO
case CONST_INTEGER:
{
Int128 i = expr->const_expr.ixx.i;
switch (expr->const_expr.ixx.type)
{
case TYPE_I128:
case TYPE_U128:
{
uint64_t words[2] = { i.low, i.high };
TODO
}
default:
value_set(value, tilde_get_const_int(c, type, i.low), type);
return;
}
}
case CONST_FLOAT:
value_set(value, tilde_get_const_float(c, type, expr->const_expr.fxx.f), type);
return;
case CONST_POINTER:
if (!expr->const_expr.ptr)
{
value_set(value, tb_inst_ptr(c->f, 0), type);
}
else
{
value_set(value, tb_inst_ptr(c->f, expr->const_expr.ptr), type);
}
return;
case CONST_BOOL:
value_set(value, tb_inst_bool(c->f, expr->const_expr.b), type_bool);
return;
case CONST_STRING:
{
TODO
/*
Type *str_type = type_lowering(expr->type);
bool is_array = type_flat_is_char_array(str_type);
if (llvm_is_local_eval(c) || !is_array)
{
ArraySize strlen = expr->const_expr.bytes.len;
ArraySize size = expr->const_expr.bytes.len + 1;
if (type_flat_is_char_array(expr->type) && type->array.len > size) size = type->array.len;
LLVMValueRef global_name = llvm_add_global_raw(c,
".str",
LLVMArrayType(llvm_get_type(c, type_char), size),
1);
llvm_set_private_linkage(global_name);
LLVMSetUnnamedAddress(global_name, LLVMGlobalUnnamedAddr);
LLVMSetGlobalConstant(global_name, 1);
LLVMValueRef string = llvm_get_zstring(c, expr->const_expr.bytes.ptr, expr->const_expr.bytes.len);
if (size > strlen + 1)
{
LLVMValueRef trailing_zeros = llvm_get_zero_raw(LLVMArrayType(c->byte_type, size - strlen - 1));
LLVMValueRef values[2] = { string, trailing_zeros };
string = llvm_get_packed_struct(c, values, 2);
}
LLVMSetInitializer(global_name, string);
if (is_array)
{
global_name = llvm_emit_bitcast_ptr(c, global_name, type);
llvm_value_set_address(be_value, global_name, type, 1);
}
else
{
global_name = llvm_emit_bitcast(c, global_name, type);
llvm_value_set(be_value, global_name, type);
}
return;
}
ArraySize array_len = type->array.len;
ArraySize size = expr->const_expr.bytes.len + 1;
bool zero_terminate = array_len == size;
LLVMValueRef string;
if (array_len <= size)
{
if (zero_terminate)
{
string = llvm_get_zstring(c, expr->const_expr.bytes.ptr, expr->const_expr.bytes.len);
}
else
{
string = llvm_get_bytes(c, expr->const_expr.bytes.ptr, array_len);
}
}
else
{
char *buffer = ccalloc(1, array_len);
memcpy(buffer, expr->const_expr.bytes.ptr, expr->const_expr.bytes.len);
string = llvm_get_bytes(c, buffer, array_len);
}
llvm_value_set(be_value, string, type);*/
return;
}
case CONST_TYPEID:
TODO
//llvm_emit_typeid(c, be_value, expr->const_expr.typeid);
return;
case CONST_ERR:
{
TODO
/*
Decl *decl = expr->const_expr.enum_err_val;
LLVMValueRef value;
if (decl)
{
value = LLVMBuildPtrToInt(c->builder, llvm_get_ref(c, decl), llvm_get_type(c, type_anyfault), "");
}
else
{
value = llvm_get_zero(c, type_anyfault);
}
llvm_value_set(be_value, value, type_anyfault);*/
return;
}
case CONST_ENUM:
value_set(value, tilde_get_const_int(c, type, expr->const_expr.enum_err_val->enum_constant.ordinal), type);
return;
case CONST_INITIALIZER:
TODO
//llvm_emit_const_initializer_list_expr(c, be_value, expr);
return;
case CONST_MEMBER:
case CONST_UNTYPED_LIST:
UNREACHABLE
}
UNREACHABLE
}
void tilde_emit_parameter(TildeContext *c, TB_Reg *args, unsigned *arg_count_ref, ABIArgInfo *info, TBEValue *be_value, Type *type)
{
type = type_lowering(type);
assert(be_value->type->canonical == type);
switch (info->kind)
{
case ABI_ARG_IGNORE:
// Skip.
return;
case ABI_ARG_INDIRECT:
{
// If we want we could optimize for structs by doing it by reference here.
assert(info->indirect.alignment == type_abi_alignment(type) || info->attributes.realign);
if (info->attributes.by_val && value_is_addr(be_value) && info->indirect.alignment <= be_value->alignment)
{
value_fold_optional(c, be_value);
args[(*arg_count_ref)++] = be_value->reg;
return;
}
TB_Reg indirect = tilde_emit_alloca(c, type, info->indirect.alignment);
tilde_store_to_ptr_aligned(c, indirect, be_value, info->indirect.alignment);
args[(*arg_count_ref)++] = indirect;
return;
}
case ABI_ARG_DIRECT:
args[(*arg_count_ref)++] = tilde_load_value_store(c, be_value);
return;
case ABI_ARG_DIRECT_SPLIT_STRUCT:
{
TODO; /*--
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, info);
assert(coerce_type && coerce_type != llvm_get_type(c, type));
AlignSize target_alignment = llvm_abi_alignment(c, coerce_type);
AlignSize alignment;
LLVMValueRef cast = llvm_emit_coerce_alignment(c, be_value, coerce_type, target_alignment, &alignment);
LLVMTypeRef element = llvm_get_type(c, info->direct_struct_expand.type);
for (unsigned idx = 0; idx < info->direct_struct_expand.elements; idx++)
{
AlignSize load_align;
LLVMValueRef element_ptr = llvm_emit_struct_gep_raw(c, cast, coerce_type, idx, alignment, &load_align);
args[(*arg_count_ref)++] = llvm_load(c, element, element_ptr, load_align, "");
}
return;*/
}
case ABI_ARG_DIRECT_COERCE:
{
TODO; /*
LLVMTypeRef coerce_type = llvm_get_type(c, info->direct_coerce_type);
if (coerce_type == llvm_get_type(c, type))
{
args[(*arg_count_ref)++] = llvm_load_value_store(c, be_value);
return;
}
args[(*arg_count_ref)++] = llvm_emit_coerce(c, coerce_type, be_value, type);
return;--*/
}
case ABI_ARG_DIRECT_COERCE_INT:
{
TODO; /*--
LLVMTypeRef coerce_type = LLVMIntTypeInContext(c->context, type_size(type) * 8);
if (coerce_type == llvm_get_type(c, type))
{
args[(*arg_count_ref)++] = llvm_load_value_store(c, be_value);
return;
}
args[(*arg_count_ref)++] = llvm_emit_coerce(c, coerce_type, be_value, type);
return; --*/
}
case ABI_ARG_DIRECT_PAIR:
{
TODO; /*-
assert(type_flatten(be_value->type) == be_value->type);
LLVMTypeRef original_type = llvm_get_type(c, be_value->type);
LLVMTypeRef struct_type = llvm_get_coerce_type(c, info);
AlignSize alignment;
if (llvm_types_are_similar(original_type, struct_type))
{
// Optimization
assert(LLVMGetTypeKind(original_type) == LLVMStructTypeKind && LLVMCountStructElementTypes(original_type) == 2);
if (llvm_value_is_addr(be_value))
{
LLVMValueRef ptr = llvm_emit_struct_gep_raw(c, be_value->value, original_type, 0, be_value->alignment, &alignment);
args[(*arg_count_ref)++] = llvm_load(c, LLVMStructGetTypeAtIndex(original_type, 0), ptr, alignment, "lo");
ptr = llvm_emit_struct_gep_raw(c, be_value->value, original_type, 1, be_value->alignment, &alignment);
args[(*arg_count_ref)++] = llvm_load(c, LLVMStructGetTypeAtIndex(original_type, 1), ptr, alignment, "hi");
return;
}
LLVMValueRef val = be_value->value;
// Maybe it's just created? Let's optimize codegen.
if (!LLVMGetFirstUse(val) && LLVMIsAInsertValueInst(val) && LLVMIsAInsertValueInst(
LLVMGetPreviousInstruction(val)))
{
LLVMValueRef prev = LLVMGetPreviousInstruction(val);
// Isn't this a second insert?
if (LLVMGetOperand(val, 0) != prev) goto NO_OPT;
// Is it used in between?
if (LLVMGetNextUse(LLVMGetFirstUse(prev))) goto NO_OPT;
// No, then we can replace the instructions with the values.
LLVMValueRef first_val = LLVMGetOperand(prev, 1);
LLVMValueRef second_val = LLVMGetOperand(val, 1);
LLVMInstructionEraseFromParent(val);
LLVMInstructionEraseFromParent(prev);
args[(*arg_count_ref)++] = first_val;
args[(*arg_count_ref)++] = second_val;
return;
}
NO_OPT:
args[(*arg_count_ref)++] = llvm_emit_extract_value(c, be_value->value, 0);
args[(*arg_count_ref)++] = llvm_emit_extract_value(c, be_value->value, 1);
return;
}
llvm_value_addr(c, be_value);
REMINDER("Handle invalid alignment");
// Here we do the following transform:
// struct -> { lo, hi } -> lo, hi
LLVMTypeRef lo = llvm_abi_type(c, info->direct_pair.lo);
LLVMTypeRef hi = llvm_abi_type(c, info->direct_pair.hi);
AlignSize struct_align;
LLVMValueRef cast = llvm_emit_coerce_alignment(c, be_value, struct_type, llvm_abi_alignment(c, struct_type), &struct_align);
// Get the lo value.
LLVMValueRef lo_ptr = llvm_emit_struct_gep_raw(c, cast, struct_type, 0, struct_align, &alignment);
args[(*arg_count_ref)++] = llvm_load(c, lo, lo_ptr, alignment, "lo");
// Get the hi value.
LLVMValueRef hi_ptr = llvm_emit_struct_gep_raw(c, cast, struct_type, 1, struct_align, &alignment);
args[(*arg_count_ref)++] = llvm_load(c, hi, hi_ptr, alignment, "hi");
return;--*/
}
case ABI_ARG_EXPAND_COERCE:
{
TODO
/*--
// Move this to an address (if needed)
llvm_value_addr(c, be_value);
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, info);
AlignSize alignment;
LLVMValueRef temp = llvm_emit_coerce_alignment(c, be_value, coerce_type, llvm_abi_alignment(c, coerce_type), &alignment);
AlignSize align;
LLVMValueRef gep_first = llvm_emit_struct_gep_raw(c, temp, coerce_type, info->coerce_expand.lo_index, alignment, &align);
args[(*arg_count_ref)++] = llvm_load(c, llvm_abi_type(c, info->coerce_expand.lo), gep_first, align, "");
if (abi_type_is_valid(info->coerce_expand.hi))
{
LLVMValueRef gep_second = llvm_emit_struct_gep_raw(c, temp, coerce_type, info->coerce_expand.hi_index, alignment, &align);
args[(*arg_count_ref)++] = llvm_load(c, llvm_abi_type(c, info->coerce_expand.hi), gep_second, align, "");
}
return;--*/
}
case ABI_ARG_EXPAND:
{
// Move this to an address (if needed)
value_addr(c, be_value);
TODO /*--tilde_expand_type_to_args(c, type, be_value.reg, args, arg_count_ref, be_value->alignment);
// Expand the padding here.
if (info->expand.padding_type)
{
args[(*arg_count_ref)++] = TB_NULL_REG;
}
return;--*/
}
}
}
void tilde_emit_raw_call(TildeContext *c, TBEValue *result_value, FunctionPrototype *prototype, TB_FunctionPrototype *func_type,
TB_Function *func, TB_Reg func_ptr, TB_Reg *args, unsigned arg_count, int inline_flag, TB_Reg error_var,
bool sret_return, TBEValue *synthetic_return_param)
{
ABIArgInfo *ret_info = prototype->ret_abi_info;
Type *call_return_type = prototype->abi_ret_type;
TB_Reg call_value;
if (func_ptr)
{
TODO
}
else
{
call_value = tb_inst_call(c->f, tildetype(call_return_type), (TB_Symbol *)func, arg_count, args);
}
switch (inline_flag)
{
case -1:
TODO // llvm_attribute_add_call(c, call_value, attribute_id.noinline, -1, 0);
break;
case 1:
TODO // llvm_attribute_add_call(c, call_value, attribute_id.alwaysinline, -1, 0);
break;
default:
break;
}
assert(!prototype->ret_by_ref || prototype->ret_by_ref_abi_info->kind != ABI_ARG_INDIRECT);
/*
llvm_add_abi_call_attributes(c, call_value, vec_size(prototype->param_types), prototype->abi_args);
if (prototype->abi_varargs)
{
llvm_add_abi_call_attributes(c,
call_value,
vec_size(prototype->varargs),
prototype->abi_varargs);
}*/
// 11. Process the return value.
switch (ret_info->kind)
{
case ABI_ARG_EXPAND:
case ABI_ARG_DIRECT_SPLIT_STRUCT:
UNREACHABLE
case ABI_ARG_IGNORE:
// 12. Basically void returns or empty structs.
// Here we know we don't have an optional or any return value that can be used.
assert(!prototype->is_optional && "Optional should have produced a return value.");
*result_value = (TBEValue) { .type = type_void, .kind = TBE_VALUE };
return;
case ABI_ARG_INDIRECT:
TODO /*
llvm_attribute_add_call_type(c, call_value, attribute_id.sret, 1, llvm_get_type(c, ret_info->indirect.type));
llvm_attribute_add_call(c, call_value, attribute_id.align, 1, ret_info->indirect.alignment);
// 13. Indirect, that is passing the result through an out parameter.
// 13a. In the case of an already present error_var, we don't need to do a load here.
if (error_var || sret_return) break;
// 13b. If not it will be contained in a be_value that is an address
// so we don't need to do anything more.
assert(result_value->kind == BE_ADDRESS);
break; --*/
case ABI_ARG_DIRECT_PAIR:
{
TODO
/*
// 14. A direct pair, in this case the data is stored like { lo, hi }
// For example we might have { int, int, short, short, int },
// this then gets bitcast to { long, long }, so we recover it by loading
// { long, long } into memory, then performing a bitcast to { int, int, short, short, int }
// 14a. Generate the type.
LLVMTypeRef lo = llvm_abi_type(c, ret_info->direct_pair.lo);
LLVMTypeRef hi = llvm_abi_type(c, ret_info->direct_pair.hi);
LLVMTypeRef struct_type = llvm_get_twostruct(c, lo, hi);
// 14b. Use the coerce method to go from the struct to the actual type
// by storing the { lo, hi } struct to memory, then loading it
// again using a bitcast.
llvm_emit_convert_value_from_coerced(c, result_value, struct_type, call_value, call_return_type);
break; --*/
}
case ABI_ARG_EXPAND_COERCE:
{
TODO
/*
// 15. Expand-coerce, this is similar to "direct pair", but looks like this:
// { lo, hi } set into { pad, lo, pad, hi } -> original type.
// 15a. Create memory to hold the return type.
LLVMValueRef ret = llvm_emit_alloca_aligned(c, call_return_type, "");
llvm_value_set_address_abi_aligned(result_value, ret, call_return_type);
// 15b. "Convert" this return type pointer in memory to our coerce type which is { pad, lo, pad, hi }
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, ret_info);
LLVMValueRef coerce = LLVMBuildBitCast(c->builder, ret, coerce_type, "");
// 15d. Find the address to the low value
AlignSize alignment;
LLVMValueRef lo = llvm_emit_struct_gep_raw(c, coerce, coerce_type, ret_info->coerce_expand.lo_index,
type_abi_alignment(call_return_type), &alignment);
// 15e. If there is only a single field, we simply store the value,
// so { lo } set into { pad, lo, pad } -> original type.
if (!abi_type_is_valid(ret_info->coerce_expand.hi))
{
// Here we do a store to call -> lo (leaving the rest undefined)
llvm_store_to_ptr_raw_aligned(c, lo, call_value, alignment);
break;
}
// 15g. We can now extract { lo, hi } to lo_value and hi_value.
LLVMValueRef lo_value = llvm_emit_extract_value(c, call_value, 0);
LLVMValueRef hi_value = llvm_emit_extract_value(c, call_value, 1);
// 15h. Store lo_value into the { pad, lo, pad, hi } struct.
llvm_store_to_ptr_raw_aligned(c, lo, lo_value, alignment);
// 15i. Calculate the address to the high value (like for the low in 15d.
LLVMValueRef hi = llvm_emit_struct_gep_raw(c, coerce, coerce_type, ret_info->coerce_expand.hi_index,
type_abi_alignment(call_return_type), &alignment);
// 15h. Store the high value.
llvm_store_to_ptr_raw_aligned(c, hi, hi_value, alignment);
*/
break;
}
case ABI_ARG_DIRECT:
value_set(result_value, call_value, call_return_type);
break;
case ABI_ARG_DIRECT_COERCE_INT:
{
// 16. A direct coerce, this is basically "call result" bitcast return type.
// 16a. Get the type of the return.
TB_DataType coerce = tilde_get_int_type_of_bytesize(type_size(call_return_type));
// 16b. If we don't have any coerce type, or the actual LLVM types are the same, we're done.
TB_DataType ret_type = tildetype(call_return_type);
if (coerce.raw == ret_type.raw)
{
// 16c. We just set as a value in be_value.
value_set(result_value, call_value, call_return_type);
break;
}
// 16c. We use a normal bitcast coerce.
TODO // tilde_emit_convert_value_from_coerced(c, result_value, coerce, call_value, call_return_type);
break;
}
case ABI_ARG_DIRECT_COERCE:
{
TODO /*---
// 16. A direct coerce, this is basically "call result" bitcast return type.
// 16a. Get the type of the return.
LLVMTypeRef coerce = llvm_get_type(c, ret_info->direct_coerce_type);
// 16b. If we don't have any coerce type, or the actual LLVM types are the same, we're done.
if (coerce == llvm_get_type(c, call_return_type))
{
// 16c. We just set as a value in be_value.
llvm_value_set(result_value, call_value, call_return_type);
break;
}
// 16c. We use a normal bitcast coerce.
llvm_emit_convert_value_from_coerced(c, result_value, coerce, call_value, call_return_type);
break; */
}
}
// 17. Handle optionals.
if (sret_return)
{
*result_value = (TBEValue) { .type = type_void, .kind = TBE_VALUE };
return;
}
if (prototype->is_optional)
{
TBEValue no_err;
// Emit the current stack into the thread local or things will get messed up.
if (c->debug.last_ptr)
tilde_store_to_ptr_raw_aligned(c,
type_voidptr,
c->debug.last_ptr,
c->debug.stack_slot,
type_alloca_alignment(type_voidptr));
// 17a. If we used the error var as the indirect recipient, then that will hold the error.
// otherwise it's whatever value in be_value.
TBEValue error_holder = *result_value;
if (error_var)
{
value_set_address_abi_aligned(&error_holder, c->opt_var, type_anyfault);
}
TB_Reg stored_error;
if (error_var)
{
stored_error = c->opt_var;
c->opt_var = TB_NULL_REG;
}
tilde_emit_jump_to_optional_exit(c, tilde_load_value(c, &error_holder));
if (error_var)
{
c->opt_var = stored_error;
}
// 17g. If void, be_value contents should be skipped.
if (!prototype->ret_by_ref)
{
*result_value = (TBEValue) { .type = type_void, .kind = TBE_VALUE };
return;
}
// 17h. Assign the return param to be_value.
*result_value = *synthetic_return_param;
return;
}
// Emit the current stack into the thread local or things will get messed up.
if (c->debug.last_ptr)
tilde_store_to_ptr_raw_aligned(c,
type_voidptr,
c->debug.last_ptr,
c->debug.stack_slot,
type_alloca_alignment(type_voidptr));
// 17i. The simple case here is where there is a normal return.
// In this case be_value already holds the result
}
static void tilde_emit_call_expr(TildeContext *c, TBEValue *result_value, Expr *expr, TBEValue *target)
{
if (expr->call_expr.is_builtin)
{
TODO // llvm_emit_builtin_call(c, result_value, expr);
return;
}
REMINDER("Debug stack");
/*
if (c->debug.stack_slot_row)
{
llvm_store_to_ptr_raw_aligned(c,
c->debug.stack_slot_row,
llvm_const_int(c, type_uint, expr->span.row),
type_abi_alignment(type_uint));
}*/
TB_FunctionPrototype *func_type;
TB_Function *func = NULL;
TB_Reg func_ptr = TB_NULL_REG;
TBEValue temp_value;
bool always_inline = false;
FunctionPrototype *prototype;
// 1. Call through a pointer.
if (!expr->call_expr.is_func_ref)
{
Expr *function = exprptr(expr->call_expr.function);
// 1a. Find the pointee type for the function pointer:
Type *type = function->type->canonical->pointer;
// 1b. Find the type signature using the underlying pointer.
prototype = type->function.prototype;
// 1c. Evaluate the pointer expression.
TBEValue func_value;
tilde_emit_expr(c, &func_value, function);
// 1d. Load it as a value
func_ptr = tilde_load_value(c, &func_value);
// 1e. Calculate the function type
func_type = tilde_get_func_prototype(c, prototype);
}
else
{
// 2a. Get the function declaration
Decl *function_decl = declptr(expr->call_expr.func_ref);
always_inline = function_decl->func_decl.attr_inline;
// 2b. Set signature, function and function type
prototype = function_decl->type->function.prototype;
func = tilde_get_function(c, function_decl);
assert(func);
func_type = tilde_get_func_prototype(c, prototype);
}
TB_Reg arg_values[512];
unsigned arg_count = 0;
Type **params = prototype->param_types;
ABIArgInfo **abi_args = prototype->abi_args;
unsigned param_count = vec_size(params);
Expr **args = expr->call_expr.arguments;
Expr **varargs = NULL;
Expr *vararg_splat = NULL;
if (prototype->variadic != VARIADIC_NONE)
{
if (expr->call_expr.splat_vararg)
{
vararg_splat = expr->call_expr.splat;
}
else
{
varargs = expr->call_expr.varargs;
}
}
FunctionPrototype copy;
if (prototype->variadic == VARIADIC_RAW)
{
if (varargs || vararg_splat)
{
assert(!vararg_splat);
copy = *prototype;
copy.varargs = NULL;
foreach(Expr*, varargs)
{
vec_add(copy.varargs, type_flatten(val->type));
}
copy.ret_abi_info = NULL;
copy.ret_by_ref_abi_info = NULL;
copy.abi_args = NULL;
c_abi_func_create(&copy);
prototype = &copy;
TB_DataType *params_type = NULL;
tilde_update_prototype_abi(c, prototype, &params_type);
}
}
ABIArgInfo *ret_info = prototype->ret_abi_info;
Type *call_return_type = prototype->abi_ret_type;
// 5. In the case of an optional, the error is replacing the regular return abi.
TB_Reg error_var = TB_NULL_REG;
*result_value = (TBEValue){ .kind = TBE_VALUE, .reg = TB_NULL_REG };
// 6. Generate data for the return value.
bool sret_return = false;
switch (ret_info->kind)
{
case ABI_ARG_INDIRECT:
// 6a. We can use the stored error var if there is no redirect.
if (prototype->is_optional && c->opt_var && !ret_info->attributes.realign)
{
error_var = c->opt_var;
arg_values[arg_count++] = error_var;
break;
}
// 6b. Return true is indirect, in this case we allocate a local, using the desired alignment on the caller side.
assert(ret_info->attributes.realign || ret_info->indirect.alignment == type_abi_alignment(call_return_type));
AlignSize alignment = ret_info->indirect.alignment;
// If we have a target, then use it.
if (target && alignment <= target->alignment)
{
assert(target->kind == TBE_ADDRESS);
arg_values[arg_count++] = target->reg;
sret_return = true;
break;
}
value_set_address(result_value,
tilde_emit_alloca(c, call_return_type, alignment),
call_return_type, alignment);
// 6c. Add the pointer to the list of arguments.
arg_values[arg_count++] = result_value->reg;
break;
case ABI_ARG_EXPAND:
case ABI_ARG_DIRECT_SPLIT_STRUCT:
UNREACHABLE
case ABI_ARG_DIRECT_PAIR:
case ABI_ARG_IGNORE:
case ABI_ARG_DIRECT_COERCE_INT:
case ABI_ARG_DIRECT_COERCE:
case ABI_ARG_DIRECT:
case ABI_ARG_EXPAND_COERCE:
break;
}
// 7. We might have an optional indirect return and a normal return.
// In this case we need to add it by hand.
TBEValue synthetic_return_param = { 0 };
if (prototype->ret_by_ref)
{
// 7b. Create the address to hold the return.
Type *actual_return_type = type_lowering(prototype->ret_by_ref_type);
value_set(&synthetic_return_param, tilde_emit_alloca(c, actual_return_type, 0), type_get_ptr(actual_return_type));
// 7c. Emit it as a parameter as a pointer (will implicitly add it to the value list)
tilde_emit_parameter(c, arg_values, &arg_count, prototype->ret_by_ref_abi_info, &synthetic_return_param, synthetic_return_param.type);
// 7d. Update the be_value to actually be an address.
value_set_address_abi_aligned(&synthetic_return_param, synthetic_return_param.reg, actual_return_type);
}
// 8. Add all other arguments.
for (unsigned i = 0; i < param_count; i++)
{
// 8a. Evaluate the expression.
Expr *arg_expr = args[i];
Type *param = params[i];
ABIArgInfo *info = abi_args[i];
if (arg_expr)
{
tilde_emit_expr(c, &temp_value, arg_expr);
}
else
{
assert(prototype->variadic == VARIADIC_TYPED || prototype->variadic == VARIADIC_ANY);
TODO // tilde_emit_vararg_parameter(c, &temp_value, param, info, varargs, vararg_splat);
}
// 8b. Emit the parameter according to ABI rules.
tilde_emit_parameter(c, arg_values, &arg_count, info, &temp_value, param);
}
// 9. Typed varargs
if (prototype->variadic == VARIADIC_RAW)
{
if (prototype->abi_varargs)
{
// 9. Emit varargs.
unsigned index = 0;
ABIArgInfo **abi_varargs = prototype->abi_varargs;
foreach(Expr*, varargs)
{
tilde_emit_expr(c, &temp_value, val);
ABIArgInfo *info = abi_varargs[index];
tilde_emit_parameter(c, arg_values, &arg_count, info, &temp_value, prototype->varargs[index]);
index++;
}
}
else
{
// 9. Emit varargs.
foreach(Expr*, varargs)
{
tilde_emit_expr(c, &temp_value, val);
REMINDER("Varargs should be expanded correctly");
arg_values[arg_count++] = tilde_load_value(c, &temp_value);
}
}
}
// 10. Create the actual call (remember to emit a loc, because we might have shifted loc emitting the params)
EMIT_LOC(c, expr);
int inline_flag = 0;
if (expr->call_expr.attr_force_noinline)
{
inline_flag = -1;
}
else
{
inline_flag = expr->call_expr.attr_force_inline || always_inline ? 1 : 0;
}
tilde_emit_raw_call(c, result_value, prototype, func_type, func, func_ptr, arg_values, arg_count, inline_flag, error_var, sret_return, &synthetic_return_param);
// Emit the current stack into the thread local or things will get messed up.
if (c->debug.last_ptr)
{
tilde_store_to_ptr_raw_aligned(c, type_voidptr, c->debug.last_ptr, c->debug.stack_slot,
type_alloca_alignment(type_voidptr));
}
// 17i. The simple case here is where there is a normal return.
// In this case be_value already holds the result
return;
}
TBEValue tilde_emit_assign_expr(TildeContext *c, TBEValue *ref, Expr *expr, TB_Reg optional)
{
assert(ref->kind == TBE_ADDRESS || ref->kind == TBE_ADDRESS_OPTIONAL);
assert(optional || !IS_OPTIONAL(expr));
// Special optimization of handling of optional
if (expr->expr_kind == EXPR_OPTIONAL)
{
PUSH_OPT();
c->opt_var = TB_NULL_REG;
c->catch_block = TB_NULL_REG;
TBEValue result;
// Emit the fault type.
tilde_emit_expr(c, &result, expr->inner_expr);
TB_Reg err_val = result.reg;
// Store it in the optional
tilde_store_to_ptr(c, optional, &result);
// Set the result to an undef value
value_set(&result, TB_NULL_REG, ref->type);
POP_OPT();
// If we had a catch block outside then we want to jump to that exit.
if (c->catch_block) tilde_emit_jump_to_optional_exit(c, err_val);
// This return value will not be used.
return result;
}
PUSH_OPT();
TB_Label assign_block = 0;
TB_Label rejump_block = 0;
if (IS_OPTIONAL(expr))
{
assign_block = tb_basic_block_create(c->f);
assert(optional);
if (c->opt_var)
{
c->catch_block = rejump_block = tb_basic_block_create(c->f);
}
else
{
c->catch_block = assign_block;
}
c->opt_var = optional;
}
else
{
c->opt_var = 0;
c->catch_block = 0;
}
TBEValue value;
if (type_flat_is_vector(expr->type))
{
tilde_emit_expr(c, &value, expr);
tilde_store(c, ref, &value);
}
else if (expr_is_const_initializer(expr))
{
TODO
//llvm_emit_const_initialize_reference(c, ref, expr);
value = *ref;
}
else if (expr_is_init_list(expr))
{
TODO
//llvm_emit_initialize_reference(c, ref, expr);
value = *ref;
}
else
{
if (expr->expr_kind == EXPR_CALL)
{
tilde_emit_call_expr(c, &value, expr, ref);
}
else
{
tilde_emit_expr(c, &value, expr);
}
if (value.type != type_void) tilde_store(c, ref, &value);
}
if (optional)
{
tilde_store_to_ptr_raw(c, optional, tilde_get_zero(c, type_anyfault), type_anyfault);
}
POP_OPT();
if (assign_block)
{
tb_inst_goto(c->f, assign_block);
if (rejump_block)
{
tilde_emit_block(c, rejump_block);
TB_Reg error = tilde_load_abi_alignment(c, type_anyfault, optional);
tilde_store_to_ptr_raw(c, c->opt_var, error, type_anyfault);
tb_inst_goto(c->f, c->catch_block);
}
tilde_emit_block(c, assign_block);
}
return value;
}
static inline TB_Reg tilde_emit_add_int(TildeContext *c, Type *type, TB_Reg left, TB_Reg right, SourceSpan loc)
{
if (active_target.feature.trap_on_wrap)
{
REMINDER("Unable to trap on wrap");
TODO /*--
LLVMTypeRef type_to_use = llvm_get_type(c, type->canonical);
LLVMValueRef args[2] = { left, right };
assert(type->canonical == type);
LLVMValueRef add_res;
if (type_is_unsigned(type))
{
add_res = llvm_emit_call_intrinsic(c, intrinsic_id.uadd_overflow, &type_to_use, 1, args, 2);
}
else
{
add_res = llvm_emit_call_intrinsic(c, intrinsic_id.sadd_overflow, &type_to_use, 1, args, 2);
}
LLVMValueRef result = llvm_emit_extract_value(c, add_res, 0);
LLVMValueRef ok = llvm_emit_extract_value(c, add_res, 1);
llvm_emit_panic_on_true(c, ok, "Addition overflow", loc);
return result; --*/
}
return tb_inst_add(c->f, left, right, (TB_ArithmaticBehavior)0);
}
static inline TB_Reg tilde_emit_mult_int(TildeContext *c, Type *type, TB_Reg left, TB_Reg right, SourceSpan loc)
{
if (active_target.feature.trap_on_wrap)
{
TODO /*
LLVMTypeRef type_to_use = llvm_get_type(c, type);
LLVMValueRef args[2] = { left, right };
LLVMTypeRef types[2] = { type_to_use, type_to_use };
unsigned operation = type_is_integer_unsigned(type) ? intrinsic_id.umul_overflow
: intrinsic_id.smul_overflow;
LLVMValueRef call_res = llvm_emit_call_intrinsic(c,
operation,
types,
1,
args,
2);
LLVMValueRef val = llvm_emit_extract_value(c, call_res, 0);
LLVMValueRef ok = llvm_emit_extract_value(c, call_res, 1);
llvm_emit_panic_on_true(c, ok, "Integer multiplication overflow", loc);
return val;*/
}
return tb_inst_mul(c->f, left, right, (TB_ArithmaticBehavior)0);
}
void tilde_emit_int_comp_raw(TildeContext *c, TBEValue *result, Type *lhs_type, Type *rhs_type, TB_Reg lhs_value, TB_Reg rhs_value, BinaryOp binary_op)
{
bool lhs_signed, rhs_signed;
Type *vector_type = type_vector_type(lhs_type);
if (vector_type)
{
lhs_signed = type_is_signed(vector_type);
rhs_signed = type_is_signed(type_vector_type(rhs_type));
}
else
{
assert(type_is_integer_or_bool_kind(lhs_type));
lhs_signed = type_is_signed(lhs_type);
rhs_signed = type_is_signed(rhs_type);
}
if (lhs_signed != rhs_signed)
{
// Swap sides if needed.
if (!lhs_signed)
{
Type *temp = lhs_type;
lhs_type = rhs_type;
rhs_type = temp;
lhs_signed = true;
rhs_signed = false;
TB_Reg temp_val = lhs_value;
lhs_value = rhs_value;
rhs_value = temp_val;
switch (binary_op)
{
case BINARYOP_GE:
binary_op = BINARYOP_LE;
break;
case BINARYOP_GT:
binary_op = BINARYOP_LT;
break;
case BINARYOP_LE:
binary_op = BINARYOP_GE;
break;
case BINARYOP_LT:
binary_op = BINARYOP_GT;
break;
default:
break;
}
}
}
if (!lhs_signed)
{
assert(lhs_signed == rhs_signed);
// Right and left side are both unsigned.
TB_Reg value;
switch (binary_op)
{
case BINARYOP_EQ:
value = tb_inst_cmp_eq(c->f, lhs_value, rhs_value);
break;
case BINARYOP_NE:
value = tb_inst_cmp_ne(c->f, lhs_value, rhs_value);
break;
case BINARYOP_GE:
value = tb_inst_cmp_ige(c->f, lhs_value, rhs_value, false);
break;
case BINARYOP_GT:
value = tb_inst_cmp_igt(c->f, lhs_value, rhs_value, false);
break;
case BINARYOP_LE:
value = tb_inst_cmp_ile(c->f, lhs_value, rhs_value, false);
break;
case BINARYOP_LT:
value = tb_inst_cmp_ilt(c->f, lhs_value, rhs_value, false);
break;
default:
UNREACHABLE
}
if (vector_type)
{
TODO // llvm_convert_vector_comparison(c, result, value, lhs_type, binary_op == BINARYOP_EQ);
return;
}
value_set(result, value, type_bool);
return;
}
// Left side is signed.
TB_Reg comp_value;
TB_Reg check_value;
switch (binary_op)
{
case BINARYOP_EQ:
comp_value = tb_inst_cmp_eq(c->f, lhs_value, rhs_value);
break;
case BINARYOP_NE:
comp_value = tb_inst_cmp_ne(c->f, lhs_value, rhs_value);
break;
case BINARYOP_GE:
comp_value = tb_inst_cmp_ige(c->f, lhs_value, rhs_value, true);
break;
case BINARYOP_GT:
comp_value = tb_inst_cmp_igt(c->f, lhs_value, rhs_value, true);
break;
case BINARYOP_LE:
comp_value = tb_inst_cmp_ile(c->f, lhs_value, rhs_value, true);
break;
case BINARYOP_LT:
comp_value = tb_inst_cmp_ilt(c->f, lhs_value, rhs_value, true);
break;
default:
UNREACHABLE
}
// If right side is also signed then this is fine.
if (rhs_signed)
{
if (vector_type)
{
TODO // tilde_convert_vector_comparison(c, result, comp_value, lhs_type, binary_op == BINARYOP_EQ);
return;
}
value_set(result, comp_value, type_bool);
return;
}
// Otherwise, special handling for left side signed, right side unsigned.
TB_Reg zero = tilde_get_zero(c, lhs_type);
switch (binary_op)
{
case BINARYOP_EQ:
// Only true if lhs >= 0
check_value = tb_inst_cmp_ige(c->f, lhs_value, zero, true);
comp_value = tb_inst_and(c->f, check_value, comp_value);
break;
case BINARYOP_NE:
// Always true if lhs < 0
check_value = tb_inst_cmp_ilt(c->f, lhs_value, zero, true);
comp_value = tb_inst_and(c->f, check_value, comp_value);
break;
case BINARYOP_GE:
// Only true if rhs >= 0 when regarded as a signed integer
check_value = tb_inst_cmp_ige(c->f, rhs_value, zero, true);
comp_value = tb_inst_and(c->f, check_value, comp_value);
break;
case BINARYOP_GT:
// Only true if rhs >= 0 when regarded as a signed integer
check_value = tb_inst_cmp_ige(c->f, rhs_value, zero, true);
comp_value = tb_inst_and(c->f, check_value, comp_value);
break;
case BINARYOP_LE:
// Always true if rhs < 0 when regarded as a signed integer
check_value = tb_inst_cmp_ilt(c->f, rhs_value, zero, true);
comp_value = tb_inst_or(c->f, check_value, comp_value);
break;
case BINARYOP_LT:
// Always true if rhs < 0 when regarded as a signed integer
check_value = tb_inst_cmp_ilt(c->f, rhs_value, zero, true);
comp_value = tb_inst_or(c->f, check_value, comp_value);
break;
default:
UNREACHABLE
}
if (vector_type)
{
TODO // tilde_convert_vector_comparison(c, result, comp_value, lhs_type, BINARYOP_EQ == binary_op);
return;
}
value_set(result, comp_value, type_bool);
}
void tilde_emit_comp(TildeContext *c, TBEValue *result, TBEValue *lhs, TBEValue *rhs, BinaryOp binary_op)
{
assert(binary_op >= BINARYOP_GT && binary_op <= BINARYOP_EQ);
value_rvalue(c, lhs);
value_rvalue(c, rhs);
if (type_is_integer_or_bool_kind(lhs->type))
{
tilde_emit_int_comp_raw(c, result, lhs->type, rhs->type, lhs->reg, rhs->reg, binary_op);
return;
}
if (type_is_pointer(lhs->type))
{
TODO // tilde_emit_ptr_comparison(c, result, lhs, rhs, binary_op);
return;
}
if (type_is_float(lhs->type))
{
TODO // tilde_emit_float_comp(c, result, lhs, rhs, binary_op, NULL);
return;
}
if (lhs->type->type_kind == TYPE_INFO_SLICE)
{
TODO // tilde_emit_subarray_comp(c, result, lhs, rhs, binary_op);
return;
}
if (lhs->type->type_kind == TYPE_VECTOR)
{
Type *type = type_vector_type(lhs->type);
if (type_is_float(type))
{
TODO // tilde_emit_float_comp(c, result, lhs, rhs, binary_op, lhs->type);
}
else
{
TODO // tilde_emit_int_comp_raw(c, result, lhs->type, rhs->type, lhs->reg, rhs->reg, binary_op);
}
return;
}
if (lhs->type->type_kind == TYPE_ARRAY)
{
TODO // tilde_emit_array_comp(c, result, lhs, rhs, binary_op);
return;
}
TODO
}
void tilde_emit_binary(TildeContext *c, TBEValue *be_value, Expr *expr, TBEValue *lhs_loaded, BinaryOp binary_op)
{
// foo ?? bar
if (binary_op == BINARYOP_ELSE)
{
TODO // llvm_emit_else(c, be_value, expr);
return;
}
// foo || bar and foo && bar
if (binary_op == BINARYOP_AND || binary_op == BINARYOP_OR)
{
TODO // llvm_emit_logical_and_or(c, be_value, expr, binary_op);
return;
}
// Load if needed, otherwise use the already loaded.
TBEValue lhs;
if (lhs_loaded)
{
lhs = *lhs_loaded;
}
else
{
if (type_is_float(type_flatten(expr->type)) && (binary_op == BINARYOP_ADD || binary_op == BINARYOP_SUB))
{
TODO // if (tilde_emit_fmuladd_maybe(c, be_value, expr, binary_op)) return;
}
tilde_emit_expr(c, &lhs, exprptr(expr->binary_expr.left));
}
// We need the rvalue.
value_rvalue(c, &lhs);
// Evaluate rhs
TBEValue rhs;
tilde_emit_expr(c, &rhs, exprptr(expr->binary_expr.right));
value_rvalue(c, &rhs);
EMIT_LOC(c, expr);
// Comparison <=>
if (binary_op >= BINARYOP_GT && binary_op <= BINARYOP_EQ)
{
tilde_emit_comp(c, be_value, &lhs, &rhs, binary_op);
return;
}
Type *lhs_type = lhs.type;
Type *rhs_type = rhs.type;
Type *vector_type = lhs_type->type_kind == TYPE_VECTOR ? lhs_type->array.base : NULL;
bool is_float = type_is_float(lhs_type) || (vector_type && type_is_float(vector_type));
TB_Reg val = TB_NULL_REG;
TB_Reg lhs_value = lhs.reg;
TB_Reg rhs_value = rhs.reg;
switch (binary_op)
{
case BINARYOP_ERROR:
UNREACHABLE
case BINARYOP_MULT:
if (is_float)
{
val = tb_inst_fmul(c->f, lhs_value, rhs_value);
break;
}
val = tilde_emit_mult_int(c, lhs_type, lhs_value, rhs_value, expr->span);
break;
case BINARYOP_SUB:
if (lhs_type->type_kind == TYPE_POINTER)
{
TODO /*---
if (lhs_type == rhs_type)
{
LLVMTypeRef int_type = llvm_get_type(c, type_isz);
val = LLVMBuildSub(c->builder, LLVMBuildPtrToInt(c->builder, lhs_value, int_type, ""),
LLVMBuildPtrToInt(c->builder, rhs_value, int_type, ""), "");
val = LLVMBuildExactSDiv(c->builder, val, llvm_const_int(c, type_isz, type_abi_alignment(lhs_type->pointer)), "");
break;
}
rhs_value = LLVMBuildNeg(c->builder, rhs_value, "");
val = llvm_emit_pointer_gep_raw(c, llvm_get_pointee_type(c, lhs_type), lhs_value, rhs_value);
*/
break;
}
if (is_float)
{
val = tb_inst_fsub(c->f, lhs_value, rhs_value);
break;
}
TODO // val = tilde_emit_sub_int(c, lhs_type, lhs_value, rhs_value, expr->span);
break;
case BINARYOP_ADD:
if (lhs_type->type_kind == TYPE_POINTER)
{
assert(type_is_integer(rhs_type));
TODO // val = llvm_emit_pointer_gep_raw(c, llvm_get_pointee_type(c, lhs_type), lhs_value, rhs_value);
break;
}
if (is_float)
{
val = tb_inst_fadd(c->f, lhs_value, rhs_value);
break;
}
val = tilde_emit_add_int(c, lhs_type, lhs_value, rhs_value, expr->span);
break;
case BINARYOP_DIV:
TODO // tilde_emit_trap_zero(c, rhs_type, rhs_value, "Division by zero.", expr->span);
if (is_float)
{
val = tb_inst_fdiv(c->f, lhs_value, rhs_value);
break;
}
val = tb_inst_div(c->f, lhs_value, rhs_value, type_is_signed(lhs_type));
break;
case BINARYOP_MOD:
TODO // tilde_emit_trap_zero(c, rhs_type, rhs_value, "% by zero.", expr->span);
if (type_is_float(lhs_type))
{
TODO // val = LLVMBuildFRem(c->builder, lhs_value, rhs_value, "fmod");
break;
}
val = tb_inst_mod(c->f, lhs_value, rhs_value, type_is_signed(lhs_type));
break;
case BINARYOP_SHR:
TODO /*---
rhs_value = llvm_zext_trunc(c, rhs_value, LLVMTypeOf(lhs_value));
llvm_emit_trap_invalid_shift(c, rhs_value, lhs_type, "Shift amount out of range.", expr->span);
val = type_is_unsigned(lhs_type)
? LLVMBuildLShr(c->builder, lhs_value, rhs_value, "lshr")
: LLVMBuildAShr(c->builder, lhs_value, rhs_value, "ashr");
val = LLVMBuildFreeze(c->builder, val, ""); --*/
break;
case BINARYOP_SHL:
/*---
rhs_value = llvm_zext_trunc(c, rhs_value, LLVMTypeOf(lhs_value));
llvm_emit_trap_invalid_shift(c, rhs_value, lhs_type, "Shift amount out of range.", expr->span);
val = LLVMBuildShl(c->builder, lhs_value, rhs_value, "shl");
val = LLVMBuildFreeze(c->builder, val, ""); --*/
break;
case BINARYOP_BIT_AND:
val = tb_inst_and(c->f, lhs_value, rhs_value);
break;
case BINARYOP_BIT_OR:
val = tb_inst_or(c->f, lhs_value, rhs_value);
break;
case BINARYOP_BIT_XOR:
val = tb_inst_xor(c->f, lhs_value, rhs_value);
break;
case BINARYOP_ELSE:
case BINARYOP_EQ:
case BINARYOP_NE:
case BINARYOP_GE:
case BINARYOP_GT:
case BINARYOP_LE:
case BINARYOP_LT:
case BINARYOP_AND:
case BINARYOP_OR:
case BINARYOP_ASSIGN:
case BINARYOP_MULT_ASSIGN:
case BINARYOP_ADD_ASSIGN:
case BINARYOP_SUB_ASSIGN:
case BINARYOP_DIV_ASSIGN:
case BINARYOP_MOD_ASSIGN:
case BINARYOP_BIT_AND_ASSIGN:
case BINARYOP_BIT_OR_ASSIGN:
case BINARYOP_BIT_XOR_ASSIGN:
case BINARYOP_SHR_ASSIGN:
case BINARYOP_SHL_ASSIGN:
// Handled elsewhere.
UNREACHABLE
}
assert(val);
value_set(be_value, val, expr->type);
}
static void tilde_emit_binary_expr(TildeContext *c, TBEValue *be_value, Expr *expr)
{
BinaryOp binary_op = expr->binary_expr.operator;
if (binary_op >= BINARYOP_ASSIGN && expr_is_vector_index(exprptr(expr->binary_expr.left)))
{
TODO // llvm_emit_vector_assign_expr(c, be_value, expr);
return;
}
if (binary_op > BINARYOP_ASSIGN)
{
BinaryOp base_op = binaryop_assign_base_op(binary_op);
assert(base_op != BINARYOP_ERROR);
TBEValue addr;
tilde_emit_expr(c, &addr, exprptr(expr->binary_expr.left));
value_addr(c, &addr);
tilde_emit_binary(c, be_value, expr, &addr, base_op);
tilde_store(c, &addr, be_value);
return;
}
if (binary_op == BINARYOP_ASSIGN)
{
Expr *left = exprptr(expr->binary_expr.left);
tilde_emit_expr(c, be_value, left);
assert(value_is_addr(be_value));
TB_Reg optional_ref = TB_NULL_REG;
if (left->expr_kind == EXPR_IDENTIFIER)
{
TODO
/*---
optional_ref = decl_optional_ref(left->identifier_expr.decl);
be_value->kind = BE_ADDRESS;*/
}
*be_value = tilde_emit_assign_expr(c, be_value, exprptr(expr->binary_expr.right), optional_ref);
return;
}
tilde_emit_binary(c, be_value, expr, NULL, binary_op);
}
void tilde_emit_expr(TildeContext *c, TBEValue *value, Expr *expr)
{
EMIT_LOC(c, expr);
switch (expr->expr_kind)
{
case NON_RUNTIME_EXPR:
case EXPR_COND:
case EXPR_CT_ARG:
case EXPR_ASM:
case EXPR_VASPLAT:
case EXPR_CT_CHECKS:
case EXPR_BUILTIN:
UNREACHABLE
case EXPR_CONST:
tilde_emit_const_expr(c, value, expr);
return;
case EXPR_BINARY:
tilde_emit_binary_expr(c, value, expr);
return;
case EXPR_NOP:
value_set(value, TB_NULL_REG, type_void);
return;
case EXPR_IDENTIFIER:
value_set_decl(c, value, expr->identifier_expr.decl);
return;
case EXPR_CALL:
tilde_emit_call_expr(c, value, expr, NULL);
return;
default:
break;
}
TODO
}
void tilde_emit_cast(TildeContext *c, CastKind cast_kind, Expr *expr, TBEValue *value, Type *to_type, Type *from_type)
{
Type *to_type_original = to_type;
to_type = type_flatten(to_type);
from_type = type_flatten(from_type);
switch (cast_kind)
{
case CAST_NUMVEC:
TODO // llvm_emit_num_to_vec_cast(c, value, to_type, from_type);
return;
case CAST_BOOLVECINT:
TODO // llvm_emit_bool_to_intvec_cast(c, value, to_type, from_type);
return;
case CAST_ARRVEC:
TODO // llvm_emit_array_to_vector_cast(c, value, to_type, from_type);
return;
case CAST_PTRANY:
{
value_rvalue(c, value);
TODO /*--
LLVMValueRef pointer = llvm_emit_bitcast(c, value->value, type_voidptr);
BEValue typeid;
llvm_emit_typeid(c, &typeid, from_type->pointer);
llvm_value_aggregate_two(c, value, to_type, pointer, typeid.value);
return;--*/
}
case CAST_BSARRY:
TODO
/*--
llvm_value_addr(c, value);
llvm_value_bitcast(c, value, to_type);
llvm_value_rvalue(c, value);--*/
return;
/*
case CAST_BSINT:
llvm_value_addr(c, value);
llvm_value_bitcast(c, value, to_type);
llvm_value_rvalue(c, value);
return;
case CAST_EUINT:
case CAST_ERINT:
to_type = type_lowering(to_type);
from_type = type_lowering(from_type);
llvm_value_rvalue(c, value);
if (type_convert_will_trunc(to_type, from_type))
{
value->value = LLVMBuildTrunc(c->builder, value->value, llvm_get_type(c, to_type), "errinttrunc");
}
else
{
value->value = type_is_signed(to_type)
? LLVMBuildSExt(c->builder, value->value, llvm_get_type(c, to_type), "errsiext")
: LLVMBuildZExt(c->builder, value->value, llvm_get_type(c, to_type), "erruiext");
}
break;
case CAST_ANYPTR:
llvm_emit_any_pointer(c, value, value);
if (llvm_value_is_addr(value))
{
value->value = LLVMBuildBitCast(c->builder, value->value, llvm_get_ptr_type(c, to_type), "");
}
else
{
value->value = LLVMBuildBitCast(c->builder, value->value, llvm_get_type(c, to_type), "");
}
break;
case CAST_XIERR:
to_type = type_lowering(to_type);
llvm_value_rvalue(c, value);
value->value = llvm_zext_trunc(c, value->value, llvm_get_type(c, to_type));
break;
case CAST_ERROR:
UNREACHABLE
case CAST_STRPTR:
case CAST_PTRPTR:
llvm_value_rvalue(c, value);
value->value = LLVMBuildPointerCast(c->builder, value->value, llvm_get_type(c, to_type), "ptrptr");
break;
case CAST_PTRXI:
llvm_value_rvalue(c, value);
value->value = LLVMBuildPtrToInt(c->builder, value->value, llvm_get_type(c, to_type), "ptrxi");
break;
case CAST_APTSA:
llvm_emit_arr_to_subarray_cast(c, value, to_type);
break;
case CAST_SASA:
assert(type_is_pointer(value->type->array.base));
llvm_value_addr(c, value);
llvm_value_bitcast(c, value, to_type);
break;
case CAST_SAPTR:
llvm_emit_subarray_pointer(c, value, value);
if (value->type != to_type)
{
if (llvm_value_is_addr(value))
{
value->value = LLVMBuildPointerCast(c->builder, value->value, llvm_get_ptr_type(c, to_type), "saptr");
}
else
{
value->value = LLVMBuildPointerCast(c->builder, value->value, llvm_get_ptr_type(c, to_type), "saptr");
}
}
break;
case CAST_EREU:
// This is a no op.
assert(type_lowering(to_type) == type_lowering(from_type));
break;
case CAST_VECARR:
llvm_emit_vector_to_array_cast(c, value, to_type, from_type);
break;
case CAST_EUER:
TODO // gencontext_emit_value_bitcast(c, value->value, to_type, from_type);
case CAST_ERBOOL:
case CAST_EUBOOL:
{
BEValue zero;
llvm_value_set_int(c, &zero, type_anyfault, 0);
llvm_emit_int_comp(c, value, value, &zero, BINARYOP_NE);
break;
}
case CAST_PTRBOOL:
llvm_value_rvalue(c, value);
value->value = LLVMBuildIsNotNull(c->builder, value->value, "ptrbool");
value->kind = BE_BOOLEAN;
break;
case CAST_BOOLINT:
llvm_value_rvalue(c, value);
value->value = LLVMBuildZExt(c->builder, value->value, llvm_get_type(c, to_type), "boolsi");
value->kind = BE_VALUE;
break;
case CAST_FPBOOL:
llvm_value_rvalue(c, value);
value->value = LLVMBuildFCmp(c->builder, LLVMRealUNE, value->value, llvm_get_zero(c, from_type), "fpbool");
value->kind = BE_BOOLEAN;
break;
case CAST_BOOLBOOL:
value->value = LLVMBuildTrunc(c->builder, value->value, c->bool_type, "boolbool");
value->kind = BE_BOOLEAN;
break;
case CAST_BOOLFP:
llvm_value_rvalue(c, value);
value->value = LLVMBuildUIToFP(c->builder, value->value, llvm_get_type(c, to_type), "boolfp");
value->kind = BE_VALUE;
break;
case CAST_INTBOOL:
llvm_value_rvalue(c, value);
value->value = LLVMBuildICmp(c->builder, LLVMIntNE, value->value, llvm_get_zero(c, from_type), "intbool");
value->kind = type_kind_is_any_vector(value->type->type_kind) ? BE_BOOLVECTOR : BE_BOOLEAN;
break;
case CAST_FPFP:
llvm_value_rvalue(c, value);
value->value = type_convert_will_trunc(to_type, from_type)
? LLVMBuildFPTrunc(c->builder, value->value, llvm_get_type(c, to_type), "fpfptrunc")
: LLVMBuildFPExt(c->builder, value->value, llvm_get_type(c, to_type), "fpfpext");
break;
case CAST_FPSI:
llvm_value_rvalue(c, value);
value->value = LLVMBuildFPToSI(c->builder, value->value, llvm_get_type(c, to_type), "fpsi");
break;
case CAST_FPUI:
llvm_value_rvalue(c, value);
value->value = LLVMBuildFPToUI(c->builder, value->value, llvm_get_type(c, to_type), "fpui");
break;
case CAST_SISI:
llvm_value_rvalue(c, value);
value->value = type_convert_will_trunc(to_type, from_type)
? LLVMBuildTrunc(c->builder, value->value, llvm_get_type(c, to_type), "sisitrunc")
: LLVMBuildSExt(c->builder, value->value, llvm_get_type(c, to_type), "sisiext");
break;
case CAST_SIUI:
llvm_value_rvalue(c, value);
value->value = type_convert_will_trunc(to_type, from_type)
? LLVMBuildTrunc(c->builder, value->value, llvm_get_type(c, to_type), "siuitrunc")
: LLVMBuildSExt(c->builder, value->value, llvm_get_type(c, to_type), "siuiext");
break;
case CAST_SIFP:
llvm_value_rvalue(c, value);
value->value = LLVMBuildSIToFP(c->builder, value->value, llvm_get_type(c, to_type), "sifp");
break;
case CAST_XIPTR:
llvm_value_rvalue(c, value);
value->value = LLVMBuildIntToPtr(c->builder, value->value, llvm_get_type(c, to_type), "xiptr");
break;
case CAST_UISI:
llvm_value_rvalue(c, value);
value->value = type_convert_will_trunc(to_type, from_type)
? LLVMBuildTrunc(c->builder, value->value, llvm_get_type(c, to_type), "uisitrunc")
: LLVMBuildZExt(c->builder, value->value, llvm_get_type(c, to_type), "uisiext");
break;
case CAST_UIUI:
llvm_value_rvalue(c, value);
value->value = llvm_zext_trunc(c, value->value, llvm_get_type(c, to_type));
break;
case CAST_UIFP:
llvm_value_rvalue(c, value);
value->value = LLVMBuildUIToFP(c->builder, value->value, llvm_get_type(c, to_type), "uifp");
break;
case CAST_ENUMLOW:
llvm_value_rvalue(c, value);
break;
case CAST_STST:
llvm_value_addr(c, value);
value->value = LLVMBuildBitCast(c->builder, value->value, llvm_get_ptr_type(c, to_type), "");
value->type = to_type;
return;
case CAST_INTENUM:
if (active_target.feature.safe_mode && c->builder != c->global_builder)
{
llvm_value_rvalue(c, value);
BEValue check;
Decl *decl = to_type_original->canonical->decl;
unsigned max = vec_size(decl->enums.values);
if (type_is_signed(value->type))
{
scratch_buffer_clear();
scratch_buffer_printf("Conversion to enum '%s' failed - tried to convert a negative value.", decl->name);
llvm_emit_int_comp_zero(c, &check, value, BINARYOP_LT);
llvm_emit_panic_on_true(c, check.value, scratch_buffer_to_string(), expr->span);
}
scratch_buffer_clear();
scratch_buffer_printf("Conversion to enum '%s' failed - the value was greater than %u.", decl->name, max - 1);
LLVMValueRef val = llvm_const_int(c, value->type, max);
llvm_emit_int_comp_raw(c, &check, value->type, value->type, value->value, val, BINARYOP_GE);
llvm_emit_panic_on_true(c, check.value,scratch_buffer_to_string(), expr->span);
}
return;
case CAST_SABOOL:
llvm_value_fold_optional(c, value);
if (llvm_value_is_addr(value))
{
value->value = llvm_emit_struct_gep_raw(c,
value->value,
llvm_get_type(c, value->type),
1,
value->alignment,
&value->alignment);
}
else
{
value->value = llvm_emit_extract_value(c, value->value, 1);
}
value->type = type_usz->canonical;
llvm_value_rvalue(c, value);
llvm_emit_int_comp_zero(c, value, value, BINARYOP_NE);
break;*/
default:
TODO
}
value->type = to_type;
}