mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 20:11:17 +00:00
524 lines
14 KiB
C
524 lines
14 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"
|
|
|
|
|
|
bool tilde_emit_goto_if_needed(TildeContext *c, TB_Label jump)
|
|
{
|
|
if (tb_basic_block_is_complete(c->f, tb_inst_get_label(c->f))) return false;
|
|
tb_inst_goto(c->f, jump);
|
|
return true;
|
|
}
|
|
|
|
INLINE void tilde_emit_statement_chain(TildeContext *c, AstId current)
|
|
{
|
|
while (current)
|
|
{
|
|
tilde_emit_stmt(c, ast_next(¤t));
|
|
}
|
|
}
|
|
|
|
INLINE void tilde_emit_compound_stmt(TildeContext *c, Ast *ast)
|
|
{
|
|
// Push debug scope
|
|
tilde_emit_statement_chain(c, ast->compound_stmt.first_stmt);
|
|
// Pop debug scope
|
|
}
|
|
|
|
static void tilde_emit_return_abi(TildeContext *c, TBEValue *return_value, TBEValue *optional)
|
|
{
|
|
FunctionPrototype *prototype = c->cur_func.prototype;
|
|
|
|
// If there is no prototype, this is a static initializer, so bail.
|
|
if (!prototype)
|
|
{
|
|
tb_inst_ret(c->f, TB_NULL_REG);
|
|
return;
|
|
}
|
|
|
|
ABIArgInfo *info = prototype->ret_abi_info;
|
|
|
|
// If we have an optional it's always the return argument, so we need to copy
|
|
// the return value into the return value holder.
|
|
TB_Reg return_out = c->return_out;
|
|
Type *call_return_type = prototype->abi_ret_type;
|
|
|
|
TBEValue no_fail;
|
|
|
|
// In this case we use the optional as the actual return.
|
|
if (prototype->is_optional)
|
|
{
|
|
if (return_value && return_value->type != type_void)
|
|
{
|
|
assert(return_value->type);
|
|
tilde_store_to_ptr_aligned(c, c->return_out, return_value, type_alloca_alignment(return_value->type));
|
|
}
|
|
return_out = c->optional_out;
|
|
if (!optional)
|
|
{
|
|
value_set(&no_fail, tilde_get_zero(c, type_anyfault), type_anyfault);
|
|
optional = &no_fail;
|
|
}
|
|
return_value = optional;
|
|
}
|
|
assert(return_value || info->kind == ABI_ARG_IGNORE);
|
|
|
|
switch (info->kind)
|
|
{
|
|
case ABI_ARG_INDIRECT:
|
|
assert(return_value);
|
|
tilde_store_to_ptr_aligned(c, return_out, return_value, info->indirect.alignment);
|
|
tb_inst_ret(c->f, TB_NULL_REG);
|
|
return;
|
|
case ABI_ARG_IGNORE:
|
|
tb_inst_ret(c->f, TB_NULL_REG);
|
|
return;
|
|
case ABI_ARG_DIRECT_SPLIT_STRUCT:
|
|
case ABI_ARG_EXPAND:
|
|
// Expands to multiple slots -
|
|
// Not applicable to return values.
|
|
UNREACHABLE
|
|
case ABI_ARG_EXPAND_COERCE:
|
|
{
|
|
TODO
|
|
/*
|
|
// Pick the return as an address.
|
|
value_addr(c, return_value);
|
|
// Get the coerce type.
|
|
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, info);
|
|
// Create the new pointer
|
|
assert(return_value);
|
|
LLVMValueRef coerce = LLVMBuildBitCast(c->builder, return_value->value, coerce_type, "");
|
|
// We might have only one value, in that case, build a GEP to that one.
|
|
LLVMValueRef lo_val;
|
|
AlignSize alignment;
|
|
LLVMValueRef lo = llvm_emit_struct_gep_raw(c, coerce, coerce_type, info->coerce_expand.lo_index,
|
|
return_value->alignment, &alignment);
|
|
LLVMTypeRef lo_type = llvm_abi_type(c, info->coerce_expand.lo);
|
|
lo_val = llvm_load(c, lo_type, lo, alignment, "");
|
|
|
|
// We're done if there's a single field.
|
|
if (!abi_type_is_valid(info->coerce_expand.hi))
|
|
{
|
|
llvm_emit_return_value(c, lo_val);
|
|
return;
|
|
}
|
|
|
|
// Let's make a first class aggregate
|
|
LLVMValueRef hi = llvm_emit_struct_gep_raw(c, coerce, coerce_type, info->coerce_expand.hi_index,
|
|
return_value->alignment, &alignment);
|
|
LLVMTypeRef hi_type = llvm_abi_type(c, info->coerce_expand.hi);
|
|
LLVMValueRef hi_val = llvm_load(c, hi_type, hi, alignment, "");
|
|
|
|
LLVMTypeRef unpadded_type = llvm_get_twostruct(c, lo_type, hi_type);
|
|
LLVMValueRef composite = llvm_get_undef_raw(unpadded_type);
|
|
|
|
composite = llvm_emit_insert_value(c, composite, lo_val, 0);
|
|
composite = llvm_emit_insert_value(c, composite, hi_val, 1);
|
|
|
|
// And return that unpadded result
|
|
llvm_emit_return_value(c, composite);
|
|
break;*/
|
|
}
|
|
case ABI_ARG_DIRECT:
|
|
DIRECT_RETURN:
|
|
// The normal return
|
|
tb_inst_ret(c->f, tilde_load_value_store(c, return_value));
|
|
return;
|
|
case ABI_ARG_DIRECT_PAIR:
|
|
{
|
|
TODO
|
|
goto DIRECT_RETURN;
|
|
/*
|
|
LLVMTypeRef coerce_type = llvm_get_coerce_type(c, info);
|
|
if (coerce_type == llvm_get_type(c, call_return_type)) goto DIRECT_RETURN;
|
|
llvm_emit_return_value(c, llvm_emit_coerce(c, coerce_type, return_value, call_return_type));
|
|
return;*/
|
|
}
|
|
case ABI_ARG_DIRECT_COERCE_INT:
|
|
{
|
|
TODO
|
|
/*
|
|
LLVMTypeRef coerce_type = LLVMIntTypeInContext(c->context, type_size(call_return_type) * 8);
|
|
if (coerce_type == llvm_get_type(c, call_return_type)) goto DIRECT_RETURN;
|
|
llvm_emit_return_value(c, llvm_emit_coerce(c, coerce_type, return_value, call_return_type));*/
|
|
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, call_return_type)) goto DIRECT_RETURN;
|
|
llvm_emit_return_value(c, llvm_emit_coerce(c, coerce_type, return_value, call_return_type));*/
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
void tilde_emit_return_implicit(TildeContext *c)
|
|
{
|
|
Type *rtype_real = c->cur_func.prototype ? c->cur_func.prototype->rtype : type_void;
|
|
if (type_lowering(type_no_optional(rtype_real)) != type_void)
|
|
{
|
|
tb_inst_unreachable(c->f);
|
|
return;
|
|
}
|
|
if (type_is_optional(rtype_real))
|
|
{
|
|
tilde_emit_return_abi(c, NULL, NULL);
|
|
return;
|
|
}
|
|
TBEValue value;
|
|
value_set(&value, tb_inst_ptr(c->f, 0), type_anyfault);
|
|
tilde_emit_return_abi(c, NULL, &value);
|
|
}
|
|
|
|
static void tilde_emit_decl_expr_list(TildeContext *c, TBEValue *be_value, Expr *expr, bool bool_cast)
|
|
{
|
|
assert(expr->expr_kind == EXPR_COND);
|
|
ByteSize size = vec_size(expr->cond_expr);
|
|
ByteSize last_index = size - 1;
|
|
for (ByteSize i = 0; i < last_index; i++)
|
|
{
|
|
TBEValue value;
|
|
tilde_emit_expr(c, &value, expr->cond_expr[i]);
|
|
}
|
|
Expr *last = expr->cond_expr[last_index];
|
|
Type *type = last->type;
|
|
tilde_emit_expr(c, be_value, last);
|
|
if (last->expr_kind == EXPR_DECL)
|
|
{
|
|
type = last->decl_expr->var.type_info->type;
|
|
|
|
TB_Reg decl_value = tilde_get_ref(c, last->decl_expr);
|
|
if (bool_cast && last->decl_expr->var.unwrap)
|
|
{
|
|
value_set(be_value, tb_inst_bool(c->f, true), type_bool);
|
|
return;
|
|
}
|
|
value_set_address_abi_aligned(be_value, decl_value, type);
|
|
}
|
|
if (bool_cast)
|
|
{
|
|
type = type_lowering(type);
|
|
if (type->type_kind != TYPE_BOOL)
|
|
{
|
|
CastKind cast = cast_to_bool_kind(type);
|
|
tilde_emit_cast(c, cast, last, be_value, type, type_bool);
|
|
}
|
|
}
|
|
}
|
|
|
|
INLINE void tilde_emit_return_stmt(TildeContext *c, Ast *ast)
|
|
{
|
|
PUSH_OPT();
|
|
|
|
Expr *expr = ast->return_stmt.expr;
|
|
if (expr && expr->expr_kind == EXPR_OPTIONAL)
|
|
{
|
|
TBEValue be_value;
|
|
tilde_emit_expr(c, &be_value, expr->inner_expr);
|
|
tilde_emit_statement_chain(c, ast->return_stmt.cleanup);
|
|
tilde_emit_return_abi(c, NULL, &be_value);
|
|
return;
|
|
}
|
|
|
|
TB_Label error_return_block = 0;
|
|
TB_Reg error_out = TB_NULL_REG;
|
|
if (c->cur_func.prototype && type_is_optional(c->cur_func.prototype->rtype))
|
|
{
|
|
error_return_block = tb_basic_block_create(c->f);
|
|
error_out = tilde_emit_alloca(c, type_anyfault, 0);
|
|
c->opt_var = error_out;
|
|
c->catch_block = error_return_block;
|
|
}
|
|
|
|
bool has_return_value = ast->return_stmt.expr != NULL;
|
|
TBEValue return_value = { 0 };
|
|
if (has_return_value)
|
|
{
|
|
tilde_emit_expr(c, &return_value, ast->return_stmt.expr);
|
|
value_fold_optional(c, &return_value);
|
|
c->retval = return_value;
|
|
}
|
|
|
|
POP_OPT();
|
|
|
|
tilde_emit_statement_chain(c, ast->return_stmt.cleanup);
|
|
|
|
// Are we in an expression block?
|
|
if (!has_return_value)
|
|
{
|
|
tilde_emit_return_implicit(c);
|
|
}
|
|
else
|
|
{
|
|
tilde_emit_return_abi(c, &return_value, NULL);
|
|
}
|
|
if (error_return_block)
|
|
{
|
|
tilde_emit_block(c, error_return_block);
|
|
TBEValue value;
|
|
value_set_address_abi_aligned(&value, error_out, type_anyfault);
|
|
tilde_emit_return_abi(c, NULL, &value);
|
|
}
|
|
}
|
|
|
|
void tilde_emit_local_decl(TildeContext *c, Decl *decl, TBEValue *value)
|
|
{
|
|
// 1. Get the declaration and the LLVM type.
|
|
Type *var_type = type_lowering(type_no_optional(decl->type));
|
|
|
|
// 2. In the case we have a static variable,
|
|
// then we essentially treat this as a global.
|
|
if (decl->var.is_static)
|
|
{
|
|
TODO
|
|
/*
|
|
// In defers we might already have generated this variable.
|
|
if (decl->backend_ref)
|
|
{
|
|
llvm_value_set_decl(c, value, decl);
|
|
return;
|
|
}
|
|
void *builder = c->builder;
|
|
c->builder = c->global_builder;
|
|
decl->backend_ref = llvm_add_global(c, "tempglobal", var_type, decl->alignment);
|
|
if (IS_OPTIONAL(decl))
|
|
{
|
|
scratch_buffer_clear();
|
|
scratch_buffer_append(decl->extname);
|
|
scratch_buffer_append("$f");
|
|
decl->var.optional_ref = llvm_add_global(c, scratch_buffer_to_string(), type_anyfault, 0);
|
|
}
|
|
llvm_emit_global_variable_init(c, decl);
|
|
c->builder = builder;
|
|
llvm_value_set_decl(c, value, decl);
|
|
return;*/
|
|
}
|
|
assert(!decl->backend_ref);
|
|
decl->tb_register = tb_inst_local(c->f, type_size(var_type), type_alloca_alignment(var_type));
|
|
Expr *init = decl->var.init_expr;
|
|
bool is_optional = IS_OPTIONAL(decl);
|
|
if (is_optional)
|
|
{
|
|
scratch_buffer_clear();
|
|
scratch_buffer_append(decl->name);
|
|
scratch_buffer_append(".f");
|
|
decl->var.tb_optional_reg = tb_inst_local(c->f, type_size(type_anyfault), type_alloca_alignment(type_anyfault));
|
|
// Only clear out the result if the assignment isn't an optional.
|
|
}
|
|
|
|
if (init)
|
|
{
|
|
value_set_decl_address(c, value, decl);
|
|
value->kind = TBE_ADDRESS;
|
|
TBEValue val = tilde_emit_assign_expr(c, value, decl->var.init_expr, decl->var.tb_optional_reg);
|
|
if (!is_optional) *value = val;
|
|
}
|
|
else if (decl->var.no_init)
|
|
{
|
|
value_set(value, tb_inst_poison(c->f), decl->type);
|
|
if (decl->var.tb_optional_reg)
|
|
{
|
|
tilde_store_to_ptr_raw(c, decl->var.tb_optional_reg, tb_inst_poison(c->f), type_anyfault);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (decl->var.tb_optional_reg)
|
|
{
|
|
tilde_store_zero(c, type_anyfault, decl->var.tb_optional_reg, 0);
|
|
}
|
|
|
|
Type *type = type_lowering(decl->type);
|
|
// Normal case, zero init.
|
|
if (type_is_builtin(type->type_kind) || type->type_kind == TYPE_POINTER)
|
|
{
|
|
tilde_store_zero(c, type_anyfault, decl->tb_register, decl->alignment);
|
|
}
|
|
else
|
|
{
|
|
tb_inst_memclr(c->f, decl->tb_register, type_bit_size(type), decl->alignment);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
static void tilde_emit_expr_stmt(TildeContext *c, Ast *ast)
|
|
{
|
|
TBEValue value;
|
|
if (IS_OPTIONAL(ast->expr_stmt))
|
|
{
|
|
PUSH_OPT();
|
|
TB_Label discard_fail = tb_basic_block_create(c->f);
|
|
c->catch_block = discard_fail;
|
|
c->opt_var = TB_NULL_REG;
|
|
tilde_emit_expr(c, &value, ast->expr_stmt);
|
|
value_fold_optional(c, &value);
|
|
EMIT_LOC(c, ast);
|
|
tilde_emit_goto_if_needed(c, discard_fail);
|
|
tilde_emit_block(c, discard_fail);
|
|
POP_OPT();
|
|
return;
|
|
}
|
|
|
|
tilde_emit_expr(c, &value, ast->expr_stmt);
|
|
}
|
|
|
|
// See llvm_emit_if_stmt
|
|
static void tilde_emit_if_stmt(TildeContext *c, Ast *ast)
|
|
{
|
|
// We need at least the exit block and the "then" block.
|
|
TB_Label exit_block = tb_basic_block_create(c->f);
|
|
TB_Label then_block = exit_block;
|
|
TB_Label else_block = exit_block;
|
|
|
|
Ast *then_body = astptr(ast->if_stmt.then_body);
|
|
// Only generate a target if
|
|
if (ast_is_not_empty(then_body))
|
|
{
|
|
then_block = tb_basic_block_create(c->f);
|
|
}
|
|
|
|
// We have an optional else block.
|
|
AstId else_id = ast->if_stmt.else_body;
|
|
Ast *else_body = else_id ? astptr(else_id) : NULL;
|
|
if (ast_is_not_empty(else_body))
|
|
{
|
|
else_block = tb_basic_block_create(c->f);
|
|
}
|
|
|
|
Expr *cond = exprptr(ast->if_stmt.cond);
|
|
ast->if_stmt.codegen.tb_break_block = exit_block;
|
|
|
|
// Output boolean value and switch.
|
|
|
|
Decl *label = ast->if_stmt.flow.label;
|
|
if (label)
|
|
{
|
|
label->label.tb_break_target = exit_block;
|
|
}
|
|
|
|
TBEValue be_value = { 0 };
|
|
|
|
bool exit_in_use = true;
|
|
|
|
if (then_body->ast_kind == AST_IF_CATCH_SWITCH_STMT)
|
|
{
|
|
TODO
|
|
/*
|
|
tilde_emit_decl_expr_list(c, &be_value, cond, false);
|
|
value_rvalue(c, &be_value);
|
|
TBEValue comp;
|
|
tilde_emit_int_comp_zero(c, &comp, &be_value, BINARYOP_NE);
|
|
tb_inst_if(c->f, comp.reg, then_block, else_block);
|
|
tb_inst_set_label(c->f, then_block);
|
|
tilde_emit_switch_body(c, &be_value, then_body);
|
|
tb_inst_goto(c->f, exit_block);
|
|
goto EMIT_ELSE;*/
|
|
}
|
|
|
|
tilde_emit_decl_expr_list(c, &be_value, cond, true);
|
|
|
|
value_rvalue(c, &be_value);
|
|
|
|
if (then_block != else_block)
|
|
{
|
|
tb_inst_if(c->f, be_value.reg, then_block, else_block);
|
|
}
|
|
|
|
// Emit the 'then' code.
|
|
if (then_block != exit_block)
|
|
{
|
|
tilde_emit_block(c, then_block);
|
|
tilde_emit_stmt(c, then_body);
|
|
|
|
// Jump to exit.
|
|
tilde_emit_goto_if_needed(c, exit_block);
|
|
}
|
|
|
|
// Emit the 'else' branch if present.
|
|
if (else_block != exit_block)
|
|
{
|
|
tilde_emit_block(c, else_block);
|
|
tilde_emit_stmt(c, else_body);
|
|
tilde_emit_goto_if_needed(c, exit_block);
|
|
}
|
|
|
|
tilde_emit_block(c, exit_block);
|
|
}
|
|
|
|
void tilde_emit_stmt(TildeContext *c, Ast *ast)
|
|
{
|
|
switch (ast->ast_kind)
|
|
{
|
|
case AST_COMPOUND_STMT:
|
|
tilde_emit_compound_stmt(c, ast);
|
|
return;
|
|
case AST_RETURN_STMT:
|
|
tilde_emit_return_stmt(c, ast);
|
|
return;
|
|
case AST_IF_STMT:
|
|
tilde_emit_if_stmt(c, ast);
|
|
return;
|
|
case AST_DECLARE_STMT:
|
|
{
|
|
TBEValue value;
|
|
tilde_emit_local_decl(c, ast->declare_stmt, &value);
|
|
return;
|
|
}
|
|
case AST_EXPR_STMT:
|
|
tilde_emit_expr_stmt(c, ast);
|
|
return;
|
|
default:
|
|
TODO
|
|
}
|
|
}
|
|
|
|
void tilde_emit_jump_to_optional_exit(TildeContext *c, TB_Reg opt_value)
|
|
{
|
|
assert(c->catch_block && "unexpected emit");
|
|
bool is_constant_opt_zero = tb_node_is_constant_zero(c->f, opt_value);
|
|
|
|
// Maybe we don't need to emit anything?
|
|
if (is_constant_opt_zero) return;
|
|
|
|
bool is_constant_opt = false;
|
|
|
|
TB_Label after_block = tb_basic_block_create(c->f);
|
|
|
|
// No error variable
|
|
if (!c->opt_var)
|
|
{
|
|
// No error var and a constant error means jumping to the "catch" block
|
|
if (is_constant_opt)
|
|
{
|
|
tb_inst_goto(c->f, c->catch_block);
|
|
}
|
|
else
|
|
{
|
|
tb_inst_if(c->f, tilde_emit_is_no_opt(c, opt_value), after_block, c->catch_block);
|
|
}
|
|
tilde_emit_block(c, after_block);
|
|
return;
|
|
}
|
|
|
|
// If it's not a constant, then jump conditionally
|
|
if (!is_constant_opt)
|
|
{
|
|
TB_Reg was_ok = tilde_emit_is_no_opt(c, opt_value);
|
|
TB_Label error_block = tb_basic_block_create(c->f);
|
|
tb_inst_if(c->f, was_ok, after_block, error_block);
|
|
tilde_emit_block(c, error_block);
|
|
}
|
|
|
|
tilde_store_to_ptr_raw(c, c->opt_var, opt_value, type_anyfault);
|
|
tb_inst_goto(c->f, c->catch_block);
|
|
tilde_emit_block(c, after_block);
|
|
|
|
}
|