Files
c3c/src/compiler/llvm_codegen_stmt.c

1451 lines
40 KiB
C

// Copyright (c) 2019 Christoffer Lerno. All rights reserved.
// Use of this source code is governed by the GNU LGPLv3.0 license
// a copy of which can be found in the LICENSE file.
#include "llvm_codegen_internal.h"
static void gencontext_emit_switch_body(GenContext *c, BEValue *switch_value, Ast *switch_ast);
static bool ast_is_not_empty(Ast *ast)
{
if (!ast) return false;
if (ast->ast_kind != AST_COMPOUND_STMT) return true;
if (vec_size(ast->compound_stmt.stmts)) return true;
return ast->compound_stmt.defer_list.start != ast->compound_stmt.defer_list.end;
}
void llvm_emit_compound_stmt(GenContext *context, Ast *ast)
{
if (llvm_use_debug(context))
{
llvm_debug_push_lexical_scope(context, ast->span);
}
assert(ast->ast_kind == AST_COMPOUND_STMT);
VECEACH(ast->compound_stmt.stmts, i)
{
llvm_emit_stmt(context, ast->compound_stmt.stmts[i]);
}
llvm_emit_defer(context, ast->compound_stmt.defer_list.start, ast->compound_stmt.defer_list.end);
if (llvm_use_debug(context))
{
llvm_debug_scope_pop(context);
}
}
void gencontext_emit_ct_compound_stmt(GenContext *context, Ast *ast)
{
assert(ast->ast_kind == AST_CT_COMPOUND_STMT);
VECEACH(ast->compound_stmt.stmts, i)
{
llvm_emit_stmt(context, ast->compound_stmt.stmts[i]);
}
}
/**
* This emits a local declaration.
*/
LLVMValueRef llvm_emit_local_decl(GenContext *c, Decl *decl)
{
// 1. Get the declaration and the LLVM type.
Type *var_type = type_lowering(type_no_fail(decl->type));
LLVMTypeRef alloc_type = llvm_get_type(c, var_type);
// 2. In the case we have a static variable,
// then we essentially treat this as a global.
if (decl->var.is_static)
{
void *builder = c->builder;
c->builder = NULL;
decl->backend_ref = LLVMAddGlobal(c->module, alloc_type, "tempglobal");
if (IS_FAILABLE(decl))
{
scratch_buffer_clear();
scratch_buffer_append(decl->external_name);
scratch_buffer_append(".f");
decl->var.failable_ref = LLVMAddGlobal(c->module, llvm_get_type(c, type_anyerr), scratch_buffer_to_string());
}
llvm_emit_global_variable_init(c, decl);
c->builder = builder;
return decl->backend_ref;
}
llvm_emit_local_var_alloca(c, decl);
if (IS_FAILABLE(decl))
{
scratch_buffer_clear();
scratch_buffer_append(decl->name);
scratch_buffer_append(".f");
decl->var.failable_ref = llvm_emit_alloca_aligned(c, type_anyerr, scratch_buffer_to_string());
// Only clear out the result if the assignment isn't a failable.
if (!decl->var.init_expr || !IS_FAILABLE(decl->var.init_expr))
{
LLVMBuildStore(c->builder, LLVMConstNull(llvm_get_type(c, type_anyerr)), decl->var.failable_ref);
}
}
Expr *init = decl->var.init_expr;
if (init)
{
// If we don't have undef, then make an assign.
if (init->expr_kind != EXPR_UNDEF)
{
BEValue value;
llvm_value_set_decl_address(&value, decl);
llvm_emit_assign_expr(c, &value, decl->var.init_expr, decl->var.failable_ref);
}
// TODO trap on undef in debug mode.
}
else
{
Type *type = type_lowering(decl->type);
// Normal case, zero init.
if (type_is_builtin(type->type_kind) || type->type_kind == TYPE_POINTER)
{
llvm_emit_store(c, decl, LLVMConstNull(alloc_type));
}
else
{
BEValue value;
llvm_value_set_decl_address(&value, decl);
llvm_emit_memclear(c, &value);
}
}
return decl->backend_ref;
}
void llvm_emit_decl_expr_list_ignore_result(GenContext *context, Expr *expr)
{
assert(expr->expr_kind == EXPR_COND);
VECEACH(expr->cond_expr, i)
{
BEValue value;
llvm_emit_expr(context, &value, expr->cond_expr[i]);
}
}
void llvm_emit_decl_expr_list(GenContext *context, BEValue *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++)
{
BEValue value;
llvm_emit_expr(context, &value, expr->cond_expr[i]);
}
Expr *last = expr->cond_expr[last_index];
Type *type = last->type;
llvm_emit_expr(context, be_value, last);
if (last->expr_kind == EXPR_DECL)
{
type = last->decl_expr->var.type_info->type;
LLVMValueRef decl_value = llvm_emit_local_decl(context, last->decl_expr);
if (bool_cast && last->decl_expr->var.unwrap)
{
llvm_value_set_bool(be_value, LLVMConstInt(context->bool_type, 1, false));
return;
}
llvm_value_set_address(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);
llvm_emit_cast(context, cast, be_value, type, type_bool);
}
}
}
void llvm_emit_jmp(GenContext *context, LLVMBasicBlockRef block)
{
llvm_emit_br(context, block);
LLVMBasicBlockRef post_jump_block = llvm_basic_block_new(context, "jmp");
llvm_emit_block(context, post_jump_block);
}
static inline void gencontext_emit_return(GenContext *c, Ast *ast)
{
// Ensure we are on a branch that is non empty.
if (!llvm_emit_check_block_branch(c)) return;
bool in_expression_block = c->in_block > 0;
PUSH_ERROR();
LLVMBasicBlockRef error_return_block = NULL;
LLVMValueRef error_out = NULL;
if (in_expression_block)
{
c->error_var = c->block_error_var;
c->catch_block = c->block_failable_exit;
}
else if (IS_FAILABLE(c->cur_func_decl->func_decl.function_signature.rtype))
{
error_return_block = llvm_basic_block_new(c, "err_retblock");
error_out = llvm_emit_alloca_aligned(c, type_anyerr, "reterr");
c->error_var = error_out;
c->catch_block = error_return_block;
}
bool has_return_value = ast->return_stmt.expr != NULL;
BEValue return_value = { 0 };
if (has_return_value)
{
llvm_emit_expr(c, &return_value, ast->return_stmt.expr);
llvm_value_fold_failable(c, &return_value);
}
POP_ERROR();
llvm_emit_defer(c, ast->return_stmt.defer, 0);
// Are we in an expression block?
if (in_expression_block)
{
if (c->return_out)
{
llvm_store_bevalue_aligned(c, c->return_out, &return_value, 0);
}
llvm_emit_jmp(c, c->block_return_exit);
return;
}
if (!has_return_value)
{
llvm_emit_return_implicit(c);
}
else
{
llvm_emit_return_abi(c, &return_value, NULL);
}
c->current_block = NULL;
if (error_return_block && LLVMGetFirstUse(LLVMBasicBlockAsValue(error_return_block)))
{
llvm_emit_block(c, error_return_block);
BEValue value;
llvm_value_set_address(&value, error_out, type_anyerr);
llvm_emit_return_abi(c, NULL, &value);
c->current_block = NULL;
}
LLVMBasicBlockRef post_ret_block = llvm_basic_block_new(c, "ret");
llvm_emit_block(c, post_ret_block);
}
/**
* Emit if (...) { ... } else { ... }
*
* This code is slightly optimized to omit branches when not needed. This is something LLVM
* will optimize as well, but it is convenient to make the code slightly smaller for LLVM to work with:
* 1. If the "then" branch is empty, replace it with "exit".
* 2. If the "else" branch is empty or missing, replace if with "exit".
* 3. If both "else" and "then" branches are empty, replace it with just the condition and remove the "exit"
*/
void llvm_emit_if(GenContext *c, Ast *ast)
{
// We need at least the exit block and the "then" block.
LLVMBasicBlockRef exit_block = llvm_basic_block_new(c, "if.exit");
LLVMBasicBlockRef then_block = exit_block;
LLVMBasicBlockRef else_block = exit_block;
Ast *then_body = ast->if_stmt.then_body;
// Only generate a target if
if (ast_is_not_empty(then_body))
{
then_block = llvm_basic_block_new(c, "if.then");
}
// We have an optional else block.
if (ast_is_not_empty(ast->if_stmt.else_body))
{
else_block = llvm_basic_block_new(c, "if.else");
}
ast->if_stmt.break_block = exit_block;
// Output boolean value and switch.
Decl *label = ast->if_stmt.flow.label;
if (label)
{
label->label.break_target = exit_block;
}
BEValue be_value = { 0 };
bool exit_in_use = true;
if (then_body->ast_kind == AST_IF_CATCH_SWITCH_STMT)
{
llvm_emit_decl_expr_list(c, &be_value, ast->if_stmt.cond, false);
llvm_value_rvalue(c, &be_value);
BEValue comp;
llvm_emit_int_comp_zero(c, &comp, &be_value, BINARYOP_NE);
llvm_emit_cond_br(c, &comp, then_block, else_block);
llvm_emit_br(c, then_block);
llvm_emit_block(c, then_block);
gencontext_emit_switch_body(c, &be_value, then_body);
llvm_emit_br(c, exit_block);
goto EMIT_ELSE;
}
llvm_emit_decl_expr_list(c, &be_value, ast->if_stmt.cond, true);
llvm_value_rvalue(c, &be_value);
assert(llvm_value_is_bool(&be_value));
if (llvm_value_is_const(&be_value) && then_block != else_block)
{
if (LLVMConstIntGetZExtValue(be_value.value))
{
llvm_emit_br(c, then_block);
else_block = exit_block;
}
else
{
llvm_emit_br(c, else_block);
then_block = exit_block;
}
}
else
{
if (then_block != else_block)
{
llvm_emit_cond_br(c, &be_value, then_block, else_block);
}
else
{
exit_in_use = LLVMGetFirstUse(LLVMBasicBlockAsValue(exit_block)) != NULL;
if (exit_in_use) llvm_emit_br(c, exit_block);
}
}
// Emit the 'then' code.
if (then_block != exit_block)
{
llvm_emit_block(c, then_block);
llvm_emit_stmt(c, ast->if_stmt.then_body);
// Jump to exit.
llvm_emit_br(c, exit_block);
}
EMIT_ELSE:
// Emit the 'else' branch if present.
if (else_block != exit_block)
{
llvm_emit_block(c, else_block);
llvm_emit_stmt(c, ast->if_stmt.else_body);
llvm_emit_br(c, exit_block);
}
// And now we just emit the exit block.
if (exit_in_use)
{
llvm_emit_block(c, exit_block);
}
}
void gencontext_emit_for_stmt(GenContext *c, Ast *ast)
{
// First, emit all inits.
if (ast->for_stmt.init) llvm_emit_decl_expr_list_ignore_result(c, ast->for_stmt.init);
// We have 3 optional parts, which makes this code bit complicated.
LLVMBasicBlockRef exit_block = llvm_basic_block_new(c, "for.exit");
LLVMBasicBlockRef inc_block = ast->for_stmt.incr ? llvm_basic_block_new(c, "for.inc") : NULL;
LLVMBasicBlockRef body_block = ast_is_not_empty(ast->for_stmt.body) ? llvm_basic_block_new(c, "for.body") : NULL;
LLVMBasicBlockRef cond_block = ast->for_stmt.cond ? llvm_basic_block_new(c, "for.cond") : NULL;
// Break is simple it always jumps out.
// For continue:
// 1. If there is inc, jump to the condition
// 2. If there is no condition, jump to the body.
LLVMBasicBlockRef continue_block = inc_block ? inc_block : (cond_block ? cond_block : body_block);
ast->for_stmt.continue_block = continue_block;
ast->for_stmt.exit_block = exit_block;
LLVMBasicBlockRef loopback_block = cond_block;
if (cond_block)
{
// Emit cond
llvm_emit_br(c, cond_block);
llvm_emit_block(c, cond_block);
BEValue be_value;
llvm_emit_expr(c, &be_value, ast->for_stmt.cond);
llvm_value_rvalue(c, &be_value);
assert(llvm_value_is_bool(&be_value));
// If we have a body, conditionally jump to it.
if (body_block)
{
llvm_emit_cond_br(c, &be_value, body_block, exit_block);
}
else
{
// Otherwise jump to inc or cond depending on what's available.
llvm_emit_cond_br(c, &be_value, inc_block ? inc_block : cond_block, exit_block);
}
}
if (body_block)
{
if (!cond_block)
{
// We don't have a cond, so we need to unconditionally jump here.
loopback_block = body_block;
llvm_emit_br(c, body_block);
}
llvm_emit_block(c, body_block);
llvm_emit_stmt(c, ast->for_stmt.body);
// IMPROVE handle continue/break.
if (inc_block)
{
llvm_emit_br(c, inc_block);
}
}
if (inc_block)
{
if (!body_block && !cond_block)
{
// We have neither cond nor body, so jump here
loopback_block = inc_block;
llvm_emit_br(c, inc_block);
}
// Emit the block
llvm_emit_block(c, inc_block);
BEValue dummy;
llvm_emit_expr(c, &dummy, ast->for_stmt.incr);
}
if (!loopback_block)
{
loopback_block = llvm_basic_block_new(c, "infiniteloop");
llvm_emit_br(c, loopback_block);
llvm_emit_block(c, loopback_block);
}
// Loop back.
llvm_emit_br(c, loopback_block);
// And insert exit block
llvm_emit_block(c, exit_block);
}
static void llvm_emit_foreach_stmt(GenContext *c, Ast *ast)
{
// First we generate an exit.
LLVMBasicBlockRef exit_block = llvm_basic_block_new(c, "foreach.exit");
PUSH_ERROR();
llvm_set_error_exit(c, exit_block);
// First evaluate the enumerated collection
BEValue enum_value;
llvm_emit_expr(c, &enum_value, ast->foreach_stmt.enumeration);
// Get the length
BEValue len;
llvm_emit_len_for_expr(c, &len, &enum_value);
llvm_value_rvalue(c, &len);
// We pop the error here.
POP_ERROR();
llvm_emit_ptr_from_array(c, &enum_value);
// Create the index and optionally the index var
LLVMTypeRef real_index_type = llvm_get_type(c, type_usize);
BEValue index_var = { 0 };
LLVMTypeRef index_type = ast->foreach_stmt.index ? llvm_get_type(c, ast->foreach_stmt.index->type) : NULL;
BEValue index = { 0 };
bool extend_bits = false;
// In case we have an index, set it up.
if (ast->foreach_stmt.index)
{
llvm_emit_local_var_alloca(c, ast->foreach_stmt.index);
llvm_value_set_address(&index_var, ast->foreach_stmt.index->backend_ref, ast->foreach_stmt.index->type);
// And set it to zero.
llvm_store_bevalue_raw(c, &index_var, llvm_get_zero(c, index_var.type));
index = index_var;
extend_bits = type_size(index_var.type) > type_size(type_isize);
}
// If types don't match (either index has a different type or it doesn't exist)
// then create the address for the internal index and set it to zero.
if (index_type != real_index_type)
{
llvm_value_set_address(&index, llvm_emit_alloca(c, real_index_type, type_abi_alignment(type_isize), "idx"), type_usize);
llvm_store_bevalue_raw(c, &index, llvm_get_zero(c, index.type));
}
Type *actual_type = type_get_indexed_type(ast->foreach_stmt.enumeration->type);
LLVMTypeRef actual_type_llvm = llvm_get_type(c, actual_type);
llvm_emit_local_var_alloca(c, ast->foreach_stmt.variable);
Type *var_type = type_lowering(ast->foreach_stmt.variable->type);
LLVMTypeRef var_type_llvm = llvm_get_type(c, var_type);
BEValue var;
llvm_value_set_address(&var, ast->foreach_stmt.variable->backend_ref, var_type);
LLVMBasicBlockRef inc_block = llvm_basic_block_new(c, "foreach.inc");
LLVMBasicBlockRef body_block = llvm_basic_block_new(c, "foreach.body");
LLVMBasicBlockRef cond_block = llvm_basic_block_new(c, "foreach.cond");
ast->foreach_stmt.continue_block = inc_block;
ast->foreach_stmt.exit_block = exit_block;
// Emit cond
llvm_emit_br(c, cond_block);
llvm_emit_block(c, cond_block);
BEValue index_value;
llvm_value_set(&index_value, llvm_value_rvalue_store(c, &index), type_usize);
BEValue b;
llvm_emit_int_comparison(c, &b, &index_value, &len, BINARYOP_LT);
llvm_emit_cond_br(c, &b, body_block, exit_block);
llvm_emit_block(c, body_block);
// In the case where we have an index that is smaller, we need to do a cast.
if (index_var.value && index.value != index_var.value)
{
LLVMValueRef stored_value;
if (extend_bits)
{
// Note that we zero extend. We never deal in negative indices.
stored_value = LLVMBuildZExt(c->builder, index_value.value, index_type, "");
}
else
{
stored_value = LLVMBuildTrunc(c->builder, index_value.value, index_type, "");
}
llvm_store_bevalue_raw(c, &index_var, stored_value);
}
assert(llvm_value_is_addr(&enum_value));
LLVMValueRef ref_to_element = llvm_emit_pointer_inbounds_gep_raw(c, actual_type_llvm, enum_value.value, index_value.value);
BEValue result;
if (ast->foreach_stmt.value_by_ref)
{
llvm_value_set(&result, ref_to_element, type_get_ptr(actual_type));
LLVMTypeRef pointer_llvm = llvm_get_ptr_type(c, actual_type);
if (pointer_llvm != var_type_llvm)
{
llvm_emit_cast(c, ast->foreach_stmt.cast, &result, var_type, result.type);
}
}
else
{
llvm_value_set_address(&result, ref_to_element, actual_type);
if (var_type_llvm != actual_type_llvm)
{
llvm_emit_cast(c, ast->foreach_stmt.cast, &result, var_type, actual_type);
}
}
llvm_store_bevalue(c, &var, &result);
llvm_emit_stmt(c, ast->foreach_stmt.body);
llvm_emit_br(c, inc_block);
llvm_emit_block(c, inc_block);
index_value.value = LLVMBuildAdd(c->builder, llvm_value_rvalue_store(c, &index), llvm_const_int(c, type_isize, 1), "");
llvm_store_bevalue(c, &index, &index_value);
// Loop back.
llvm_emit_br(c, cond_block);
// And insert exit block
llvm_emit_block(c, exit_block);
}
void gencontext_emit_while_stmt(GenContext *context, Ast *ast)
{
// First, emit all inits.
LLVMBasicBlockRef exit_block = llvm_basic_block_new(context, "while.exit");
LLVMBasicBlockRef begin_block = llvm_basic_block_new(context, "while.begin");
LLVMBasicBlockRef body_block = ast->while_stmt.body->compound_stmt.stmts ? llvm_basic_block_new(context, "while.body") : NULL;
ast->while_stmt.continue_block = begin_block;
ast->while_stmt.break_block = exit_block;
Expr *cond = ast->while_stmt.cond;
bool is_infinite_loop = false;
// Is this while (false) or while (true)
if (cond->expr_kind == EXPR_COND && vec_size(cond->cond_expr) == 1)
{
Expr *expr = cond->cond_expr[0];
if (expr->expr_kind == EXPR_CONST && expr->const_expr.const_kind == CONST_BOOL)
{
is_infinite_loop = expr->const_expr.b;
// This is a NOP
if (!is_infinite_loop) return;
assert(body_block);
}
}
DeferList defers = { 0, 0 };
// Emit cond
llvm_emit_br(context, begin_block);
if (is_infinite_loop)
{
body_block = begin_block;
goto EMIT_BODY;
}
llvm_emit_block(context, begin_block);
if (cond->expr_kind == EXPR_SCOPED_EXPR)
{
defers = cond->expr_scope.defers;
cond = cond->expr_scope.expr;
}
BEValue be_value;
llvm_emit_decl_expr_list(context, &be_value, cond, true);
llvm_value_rvalue(context, &be_value);
// If we have a body, conditionally jump to it.
if (body_block)
{
llvm_emit_cond_br(context, &be_value, body_block, exit_block);
}
else
{
// Emit defers
llvm_emit_defer(context, defers.start, defers.end);
// Otherwise jump to inc or cond depending on what's available.
llvm_emit_cond_br(context, &be_value, begin_block, exit_block);
}
EMIT_BODY:
if (body_block)
{
llvm_emit_block(context, body_block);
llvm_emit_stmt(context, ast->while_stmt.body);
// Emit defers
llvm_emit_defer(context, defers.start, defers.end);
}
// Loop back.
llvm_emit_br(context, begin_block);
// And insert exit block
llvm_emit_block(context, exit_block);
// Emit defers
llvm_emit_defer(context, defers.start, defers.end);
}
void gencontext_emit_do_stmt(GenContext *c, Ast *ast)
{
LLVMBasicBlockRef exit_block = llvm_basic_block_new(c, "do.exit");
LLVMBasicBlockRef cond_block = ast->do_stmt.expr ? llvm_basic_block_new(c, "do.cond") : NULL;
LLVMBasicBlockRef body_block = llvm_basic_block_new(c, "do.body");
// Break is simple it always jumps out.
// For continue: if there is no condition, exit.
LLVMBasicBlockRef cont_block = cond_block ? cond_block : exit_block;
Expr *do_expr = ast->do_stmt.expr;
Ast *do_body = ast->do_stmt.body;
// Overwrite:
ast->do_stmt.break_block = exit_block;
ast->do_stmt.continue_block = cont_block;
// Emit the body
llvm_emit_br(c, body_block);
llvm_emit_block(c, body_block);
llvm_emit_stmt(c, do_body);
if (cond_block)
{
llvm_emit_br(c, cond_block);
llvm_emit_block(c, cond_block);
BEValue be_value = { 0 };
llvm_emit_expr(c, &be_value, do_expr);
llvm_value_rvalue(c, &be_value);
if (llvm_value_is_const(&be_value))
{
unsigned long v = LLVMConstIntGetZExtValue(be_value.value);
llvm_emit_br(c, v ? body_block : exit_block);
}
else
{
llvm_emit_cond_br(c, &be_value, body_block, exit_block);
}
}
else
{
// Branch to the exit
llvm_emit_br(c, exit_block);
}
// Emit the exit block.
llvm_emit_block(c, exit_block);
}
static void llvm_emit_switch_body_if_chain(GenContext *c,
Ast **cases,
Ast *default_case,
BEValue *switch_value,
LLVMBasicBlockRef exit_block)
{
LLVMBasicBlockRef next = NULL;
VECEACH(cases, i)
{
Ast *case_stmt = cases[i];
LLVMBasicBlockRef block = case_stmt->case_stmt.backend_block;
if (case_stmt == default_case) continue;
BEValue be_value;
llvm_emit_expr(c, &be_value, case_stmt->case_stmt.expr);
llvm_value_rvalue(c, &be_value);
BEValue equals;
llvm_emit_comparison(c, &equals, &be_value, switch_value, BINARYOP_EQ);
next = llvm_basic_block_new(c, "next_if");
llvm_emit_cond_br(c, &equals, block, next);
if (case_stmt->case_stmt.body)
{
llvm_emit_block(c, block);
c->current_block_is_target = true;
llvm_emit_stmt(c, case_stmt->case_stmt.body);
llvm_emit_br(c, exit_block);
}
llvm_emit_block(c, next);
}
if (default_case && default_case->case_stmt.body)
{
llvm_emit_br(c, default_case->case_stmt.backend_block);
llvm_emit_block(c, default_case->case_stmt.backend_block);
c->current_block_is_target = true;
llvm_emit_stmt(c, default_case->case_stmt.body);
llvm_emit_br(c, exit_block);
}
else
{
llvm_emit_br(c, exit_block);
}
llvm_emit_block(c, exit_block);
return;
}
static void gencontext_emit_switch_body(GenContext *c, BEValue *switch_value, Ast *switch_ast)
{
bool is_if_chain = switch_ast->switch_stmt.if_chain;
Ast **cases = switch_ast->switch_stmt.cases;
ByteSize case_count = vec_size(cases);
if (!case_count)
{
// No body or default is empty, just exit after the value.
return;
}
Ast *default_case = NULL;
for (unsigned i = 0; i < case_count; i++)
{
Ast *case_stmt = cases[i];
if (!case_stmt->case_stmt.expr)
{
if (case_stmt->case_stmt.body)
{
case_stmt->case_stmt.backend_block = llvm_basic_block_new(c, "switch.default");
}
default_case = case_stmt;
}
else if (case_stmt->case_stmt.body)
{
case_stmt->case_stmt.backend_block = llvm_basic_block_new(c, "switch.case");
}
}
LLVMBasicBlockRef exit_block = llvm_basic_block_new(c, "switch.exit");
LLVMBasicBlockRef switch_block = llvm_basic_block_new(c, "switch.entry");
switch_ast->switch_stmt.codegen.retry_block = switch_block;
switch_ast->switch_stmt.codegen.exit_block = exit_block;
// We will now treat the fallthrough cases:
// switch (i)
// {
// case 1:
// case 2:
// do_something();
// default:
// }
LLVMBasicBlockRef next_block = exit_block;
for (unsigned i = case_count; i > 0; i--)
{
Ast *case_stmt = cases[i - 1];
if (case_stmt->case_stmt.backend_block)
{
next_block = case_stmt->case_stmt.backend_block;
continue;
}
case_stmt->case_stmt.backend_block = next_block;
}
Type *switch_type = switch_ast->ast_kind == AST_IF_CATCH_SWITCH_STMT ? type_lowering(type_anyerr) : switch_ast->switch_stmt.cond->type;
BEValue switch_var;
llvm_value_set_address(&switch_var, llvm_emit_alloca_aligned(c, switch_type, "switch"), switch_type);
switch_ast->switch_stmt.codegen.retry_var = &switch_var;
llvm_store_bevalue(c, &switch_var, switch_value);
llvm_emit_br(c, switch_block);
llvm_emit_block(c, switch_block);
BEValue switch_current_val = switch_var;
llvm_value_rvalue(c, &switch_current_val);
if (is_if_chain)
{
llvm_emit_switch_body_if_chain(c, cases, default_case, &switch_current_val, exit_block);
return;
}
c->current_block = NULL;
LLVMValueRef switch_stmt = LLVMBuildSwitch(c->builder, switch_current_val.value, default_case ? default_case->case_stmt.backend_block : exit_block, case_count);
for (unsigned i = 0; i < case_count; i++)
{
Ast *case_stmt = cases[i];
LLVMBasicBlockRef block = case_stmt->case_stmt.backend_block;
if (case_stmt != default_case)
{
LLVMValueRef case_value;
BEValue be_value;
llvm_emit_expr(c, &be_value, case_stmt->case_stmt.expr);
llvm_value_rvalue(c, &be_value);
case_value = be_value.value;
LLVMAddCase(switch_stmt, case_value, block);
}
// Skip fallthroughs.
if (!case_stmt->case_stmt.body) continue;
llvm_emit_block(c, block);
// IMPORTANT!
c->current_block_is_target = true;
llvm_emit_stmt(c, case_stmt->case_stmt.body);
llvm_emit_br(c, exit_block);
}
llvm_emit_block(c, exit_block);
}
void gencontext_emit_switch(GenContext *context, Ast *ast)
{
BEValue switch_value;
llvm_emit_decl_expr_list(context, &switch_value, ast->switch_stmt.cond, false);
gencontext_emit_switch_body(context, &switch_value, ast);
}
void llvm_emit_defer(GenContext *c, AstId defer_start, AstId defer_end)
{
if (defer_start == defer_end) return;
AstId defer = defer_start;
while (defer && defer != defer_end)
{
Ast *def = astptr(defer);
LLVMBasicBlockRef exit = llvm_basic_block_new(c, "exit");
Ast *body = def->defer_stmt.body;
def->defer_stmt.codegen.exit_block = exit;
llvm_emit_stmt(c, body);
llvm_emit_br(c, exit);
llvm_emit_block(c, exit);
defer = def->defer_stmt.prev_defer;
}
}
void gencontext_emit_break(GenContext *context, Ast *ast)
{
llvm_emit_defer(context, ast->contbreak_stmt.defers.start, ast->contbreak_stmt.defers.end);
Ast *jump_target = astptr(ast->contbreak_stmt.ast);
LLVMBasicBlockRef jump;
switch (jump_target->ast_kind)
{
case AST_IF_STMT:
jump = jump_target->if_stmt.break_block;
break;
case AST_WHILE_STMT:
jump = jump_target->while_stmt.break_block;
break;
case AST_FOREACH_STMT:
jump = jump_target->foreach_stmt.exit_block;
break;
case AST_FOR_STMT:
jump = jump_target->for_stmt.exit_block;
break;
case AST_DO_STMT:
jump = jump_target->do_stmt.break_block;
break;
case AST_IF_CATCH_SWITCH_STMT:
case AST_SWITCH_STMT:
jump = jump_target->switch_stmt.codegen.exit_block;
break;
case AST_DEFER_STMT:
jump = jump_target->defer_stmt.codegen.exit_block;
break;
default:
UNREACHABLE
}
llvm_emit_jmp(context, jump);
}
void gencontext_emit_continue(GenContext *context, Ast *ast)
{
llvm_emit_defer(context, ast->contbreak_stmt.defers.start, ast->contbreak_stmt.defers.end);
Ast *jump_target = astptr(ast->contbreak_stmt.ast);
LLVMBasicBlockRef jump;
switch (jump_target->ast_kind)
{
case AST_IF_STMT:
case AST_SWITCH_STMT:
UNREACHABLE
case AST_WHILE_STMT:
jump = jump_target->while_stmt.continue_block;
break;
case AST_DO_STMT:
jump = jump_target->do_stmt.continue_block;
break;
case AST_FOREACH_STMT:
jump = jump_target->foreach_stmt.continue_block;
break;
case AST_FOR_STMT:
jump = jump_target->for_stmt.continue_block;
break;
default:
UNREACHABLE
}
llvm_emit_jmp(context, jump);
}
void gencontext_emit_next_stmt(GenContext *context, Ast *ast)
{
Ast *jump_target = astptr(ast->next_stmt.case_switch_stmt);
if (jump_target->ast_kind != AST_SWITCH_STMT)
{
llvm_emit_defer(context, ast->next_stmt.defers.start, ast->next_stmt.defers.end);
llvm_emit_jmp(context, jump_target->case_stmt.backend_block);
return;
}
BEValue be_value;
llvm_emit_expr(context, &be_value, ast->next_stmt.switch_expr);
llvm_store_bevalue(context, jump_target->switch_stmt.codegen.retry_var, &be_value);
llvm_emit_defer(context, ast->next_stmt.defers.start, ast->next_stmt.defers.end);
llvm_emit_jmp(context, jump_target->switch_stmt.codegen.retry_block);
}
void gencontext_emit_scoped_stmt(GenContext *context, Ast *ast)
{
llvm_emit_stmt(context, ast->scoped_stmt.stmt);
llvm_emit_defer(context, ast->scoped_stmt.defers.start, ast->scoped_stmt.defers.end);
}
static bool expr_is_pure(Expr *expr)
{
if (!expr) return true;
switch (expr->expr_kind)
{
case EXPR_CONST:
case EXPR_CONST_IDENTIFIER:
case EXPR_IDENTIFIER:
case EXPR_NOP:
return true;
case EXPR_BINARY:
if (expr->binary_expr.operator >= BINARYOP_ASSIGN) return false;
return expr_is_pure(expr->binary_expr.right) && expr_is_pure(expr->binary_expr.left);
case EXPR_UNARY:
switch (expr->unary_expr.operator)
{
case UNARYOP_INC:
case UNARYOP_DEC:
case UNARYOP_TADDR:
return false;
default:
return expr_is_pure(expr->unary_expr.expr);
}
break;
case EXPR_ACCESS:
return expr_is_pure(expr->access_expr.parent);
case EXPR_POISONED:
case EXPR_CT_IDENT:
case EXPR_TYPEID:
case EXPR_CT_CALL:
UNREACHABLE
case EXPR_MACRO_BODY_EXPANSION:
case EXPR_CALL:
case EXPR_CATCH_UNWRAP:
case EXPR_COMPOUND_LITERAL:
case EXPR_COND:
case EXPR_DESIGNATOR:
case EXPR_DECL:
case EXPR_OR_ERROR:
case EXPR_EXPR_BLOCK:
case EXPR_FAILABLE:
case EXPR_RETHROW:
case EXPR_HASH_IDENT:
case EXPR_MACRO_BLOCK:
case EXPR_MACRO_EXPANSION:
case EXPR_FLATPATH:
case EXPR_INITIALIZER_LIST:
case EXPR_DESIGNATED_INITIALIZER_LIST:
case EXPR_PLACEHOLDER:
case EXPR_POST_UNARY:
case EXPR_SCOPED_EXPR:
case EXPR_SLICE_ASSIGN:
case EXPR_TRY_UNWRAP:
case EXPR_TRY_UNWRAP_CHAIN:
case EXPR_UNDEF:
case EXPR_TYPEINFO:
case EXPR_FORCE_UNWRAP:
return false;
case EXPR_CAST:
return expr_is_pure(expr->cast_expr.expr);
case EXPR_EXPRESSION_LIST:
{
VECEACH(expr->expression_list, i)
{
if (!expr_is_pure(expr->expression_list[i])) return false;
}
return true;
}
break;
case EXPR_LEN:
return expr_is_pure(expr->len_expr.inner);
case EXPR_SLICE:
return expr_is_pure(expr->slice_expr.expr)
&& expr_is_pure(expr->slice_expr.start)
&& expr_is_pure(expr->slice_expr.end);
case EXPR_SUBSCRIPT:
return expr_is_pure(expr->subscript_expr.expr)
&& expr_is_pure(expr->subscript_expr.index);
case EXPR_TERNARY:
return expr_is_pure(expr->ternary_expr.cond)
&& expr_is_pure(expr->ternary_expr.else_expr)
&& expr_is_pure(expr->ternary_expr.then_expr);
case EXPR_TRY:
case EXPR_GROUP:
case EXPR_CATCH:
return expr_is_pure(expr->inner_expr);
}
UNREACHABLE
}
static inline void llvm_emit_assume(GenContext *c, Expr *expr)
{
// 1. Convert x > 0 && y > 2 => llvm.assume(x > 0) + llvm.assume(y > 2)
if (expr->expr_kind == EXPR_BINARY && expr->binary_expr.operator == BINARYOP_AND)
{
llvm_emit_assume(c, expr->binary_expr.left);
llvm_emit_assume(c, expr->binary_expr.right);
return;
}
// 2. Convert !(x > 0 || y > 2) => llvm.assume(!(x > 0)) + llvm.assume(!(y > 2))
if (expr->expr_kind == EXPR_UNARY && expr->unary_expr.operator == UNARYOP_NOT)
{
Expr *inner = expr->unary_expr.expr;
if (inner->expr_kind == EXPR_BINARY && inner->binary_expr.operator == BINARYOP_OR)
{
Expr *left = inner->binary_expr.left;
Expr *right = inner->binary_expr.right;
expr->unary_expr.expr = left;
llvm_emit_assume(c, expr);
expr->unary_expr.expr = right;
llvm_emit_assume(c, expr);
return;
}
}
// 3. Check if pure, if so we emit the assume.
if (expr_is_pure(expr))
{
BEValue value;
llvm_emit_expr(c, &value, expr);
llvm_value_rvalue(c, &value);
assert(value.kind == BE_BOOLEAN);
EMIT_LOC(c, expr);
llvm_emit_call_intrinsic(c, intrinsic_id_assume, NULL, 0, &(value.value), 1);
}
}
static inline void llvm_emit_assert_stmt(GenContext *c, Ast *ast)
{
if (active_target.feature.safe_mode)
{
BEValue value;
llvm_emit_expr(c, &value, ast->assert_stmt.expr);
llvm_value_rvalue(c, &value);
LLVMBasicBlockRef on_fail = llvm_basic_block_new(c, "assert_fail");
LLVMBasicBlockRef on_ok = llvm_basic_block_new(c, "assert_ok");
assert(value.kind == BE_BOOLEAN);
llvm_emit_cond_br(c, &value, on_ok, on_fail);
llvm_emit_block(c, on_fail);
SourceLocation *loc = TOKLOC(ast->assert_stmt.expr->span.loc);
const char *error;
if (ast->assert_stmt.message)
{
error = ast->assert_stmt.message->const_expr.string.chars;
}
else
{
error = "Assert violation";
}
llvm_emit_debug_output(c, error, loc->file->name, c->cur_func_decl->name, loc->line);
llvm_emit_call_intrinsic(c, intrinsic_id_trap, NULL, 0, NULL, 0);
llvm_emit_br(c, on_ok);
llvm_emit_block(c, on_ok);
return;
}
llvm_emit_assume(c, ast->assert_stmt.expr);
}
static inline void add_target_clobbers_to_buffer(GenContext *c)
{
switch (platform_target.arch)
{
case ARCH_TYPE_X86_64:
case ARCH_TYPE_X86:
scratch_buffer_append("~{dirflag},~{fpsr},~{flags}");
break;
case ARCH_TYPE_MIPS:
case ARCH_TYPE_MIPS64:
case ARCH_TYPE_MIPS64EL:
case ARCH_TYPE_MIPSEL:
// Currently Clang does this
scratch_buffer_append("~{$1}");
break;
default:
// In Clang no other platform has automatic clobbers
break;
}
}
static inline void llvm_emit_asm_stmt(GenContext *c, Ast *ast)
{
LLVMTypeRef asm_fn_type = LLVMFunctionType(llvm_get_type(c, type_void), NULL, 0, 0);
scratch_buffer_clear();
add_target_clobbers_to_buffer(c);
LLVMValueRef asm_fn = LLVMGetInlineAsm(asm_fn_type,
(char *)ast->asm_stmt.body->const_expr.string.chars,
ast->asm_stmt.body->const_expr.string.len,
scratch_buffer_to_string(), global_context.scratch_buffer_len,
ast->asm_stmt.is_volatile,
true,
LLVMInlineAsmDialectIntel
#if LLVM_VERSION_MAJOR > 12
, /* can throw */ false
#endif
);
LLVMBuildCall2(c->builder, asm_fn_type, asm_fn, NULL, 0, "");
}
static inline void gencontext_emit_unreachable_stmt(GenContext *context, Ast *ast)
{
SourceLocation *loc = TOKLOC(ast->span.loc);
llvm_emit_debug_output(context, "Unreachable statement reached.", loc->file->name, context->cur_func_decl->external_name, loc->line);
llvm_emit_call_intrinsic(context, intrinsic_id_trap, NULL, 0, NULL, 0);
LLVMBuildUnreachable(context->builder);
LLVMBasicBlockRef block = llvm_basic_block_new(context, "unreachable_block");
context->current_block = NULL;
context->current_block_is_target = false;
llvm_emit_block(context, block);
}
void gencontext_emit_expr_stmt(GenContext *c, Ast *ast)
{
BEValue value;
if (IS_FAILABLE(ast->expr_stmt))
{
PUSH_ERROR();
LLVMBasicBlockRef discard_fail = llvm_basic_block_new(c, "voiderr");
c->catch_block = discard_fail;
c->error_var = NULL;
llvm_emit_expr(c, &value, ast->expr_stmt);
llvm_value_rvalue(c, &value);
EMIT_LOC(c, ast);
llvm_emit_br(c, discard_fail);
llvm_emit_block(c, discard_fail);
POP_ERROR();
return;
}
llvm_emit_expr(c, &value, ast->expr_stmt);
llvm_value_rvalue(c, &value);
}
static LLVMValueRef llvm_emit_string(GenContext *c, const char *str)
{
LLVMTypeRef char_type = llvm_get_type(c, type_char);
unsigned len = strlen(str);
LLVMTypeRef char_array_type = LLVMArrayType(char_type, len + 1);
LLVMValueRef global_string = LLVMAddGlobal(c->module, char_array_type, "");
LLVMSetLinkage(global_string, LLVMInternalLinkage);
LLVMSetGlobalConstant(global_string, 1);
LLVMSetInitializer(global_string, LLVMConstStringInContext(c->context, str, len, 0));
AlignSize alignment;
// TODO alignment
LLVMValueRef string = llvm_emit_array_gep_raw(c, global_string, char_array_type, 0,
1, &alignment);
return LLVMBuildBitCast(c->builder, string, LLVMPointerType(char_type, 0), "");
}
void llvm_emit_debug_output(GenContext *c, const char *message, const char *file, const char *func, unsigned line)
{
LLVMTypeRef char_ptr_type = llvm_get_ptr_type(c, type_char);
LLVMTypeRef cint_type = llvm_get_type(c, type_cint());
const char *name;
int file_index;
int line_index;
int expr_index;
int func_index = -1;
switch (platform_target.os)
{
case OS_TYPE_WIN32:
name = "_assert";
expr_index = 0;
file_index = 1;
line_index = 2;
break;
case OS_DARWIN_TYPES:
name = "__assert_rtn";
func_index = 0;
expr_index = 3;
file_index = 1;
line_index = 2;
break;
case OS_TYPE_SOLARIS:
name = "__assert_c99";
expr_index = 0;
file_index = 1;
line_index = 2;
func_index = 3;
break;
case OS_TYPE_LINUX:
name = "__assert_fail";
expr_index = 0;
file_index = 1;
line_index = 2;
func_index = 3;
break;
case OS_TYPE_OPENBSD:
name = "__assert2";
file_index = 0;
line_index = 1;
func_index = 2;
expr_index = 3;
break;
default:
name = "__assert";
expr_index = 0;
file_index = 1;
line_index = 2;
func_index = 3;
break;
}
LLVMValueRef assert_func = LLVMGetNamedFunction(c->module, name);
if (!assert_func)
{
LLVMTypeRef type;
LLVMTypeRef void_type = LLVMVoidTypeInContext(c->context);
switch (platform_target.os)
{
case OS_TYPE_WIN32:
case OS_TYPE_FREE_BSD:
case OS_TYPE_DRAGON_FLY:
{
LLVMTypeRef args[3] = { char_ptr_type, char_ptr_type, cint_type };
type = LLVMFunctionType(void_type, args, 3, false);
break;
}
case OS_DARWIN_TYPES:
case OS_TYPE_LINUX:
case OS_TYPE_SOLARIS:
{
LLVMTypeRef args[4] = { char_ptr_type, char_ptr_type, cint_type, char_ptr_type };
type = LLVMFunctionType(void_type, args, 4, false);
break;
}
case OS_TYPE_OPENBSD:
{
LLVMTypeRef args[4] = { char_ptr_type, cint_type, char_ptr_type, char_ptr_type };
type = LLVMFunctionType(void_type, args, 4, false);
break;
}
case OS_TYPE_NETBSD:
{
LLVMTypeRef args[3] = { char_ptr_type, cint_type, char_ptr_type };
type = LLVMFunctionType(void_type, args, 3, false);
break;
}
default:
{
LLVMTypeRef args[3] = { char_ptr_type, char_ptr_type, cint_type };
type = LLVMFunctionType(void_type, args, 3, false);
break;
}
}
assert_func = LLVMAddFunction(c->module, name, type);
}
LLVMValueRef args[4];
if (func_index == -1)
{
scratch_buffer_clear();
scratch_buffer_append(file);
scratch_buffer_append(" : ");
scratch_buffer_append(func);
file = scratch_buffer_to_string();
}
else
{
args[func_index] = llvm_emit_string(c, func);
}
args[file_index] = llvm_emit_string(c, file);
args[expr_index] = llvm_emit_string(c, message);
args[line_index] = llvm_const_int(c, type_cint(), line);
LLVMBuildCall(c->builder, assert_func, args, func_index > -1 ? 4 : 3, "");
}
void llvm_emit_panic_if_true(GenContext *c, BEValue *value, const char *panic_name, SourceLocation *loc)
{
LLVMBasicBlockRef panic_block = llvm_basic_block_new(c, "panic");
LLVMBasicBlockRef ok_block = llvm_basic_block_new(c, "checkok");
assert(llvm_value_is_bool(value));
llvm_emit_cond_br(c, value, panic_block, ok_block);
llvm_emit_block(c, panic_block);
llvm_emit_debug_output(c, panic_name, loc->file->name, c->cur_func_decl->name, loc->line);
llvm_emit_call_intrinsic(c, intrinsic_id_trap, NULL, 0, NULL, 0);
llvm_emit_br(c, ok_block);
llvm_emit_block(c, ok_block);
}
void llvm_emit_panic_on_true(GenContext *c, LLVMValueRef value, const char *panic_name, SourceLocation *loc)
{
LLVMBasicBlockRef panic_block = llvm_basic_block_new(c, "panic");
LLVMBasicBlockRef ok_block = llvm_basic_block_new(c, "checkok");
BEValue be_value;
llvm_value_set_bool(&be_value, value);
llvm_emit_cond_br(c, &be_value, panic_block, ok_block);
llvm_emit_block(c, panic_block);
llvm_emit_debug_output(c, panic_name, loc->file->name, c->cur_func_decl->name, loc->line);
llvm_emit_call_intrinsic(c, intrinsic_id_trap, NULL, 0, NULL, 0);
llvm_emit_br(c, ok_block);
llvm_emit_block(c, ok_block);
}
void llvm_emit_stmt(GenContext *c, Ast *ast)
{
EMIT_LOC(c, ast);
assert(!c->catch_block && "Did not expect a catch block here.");
switch (ast->ast_kind)
{
case AST_DOCS:
case AST_DOC_DIRECTIVE:
case AST_POISONED:
case AST_VAR_STMT:
case AST_IF_CATCH_SWITCH_STMT:
UNREACHABLE
case AST_SCOPED_STMT:
gencontext_emit_scoped_stmt(c, ast);
break;
case AST_EXPR_STMT:
gencontext_emit_expr_stmt(c, ast);
break;
case AST_DECLARE_STMT:
llvm_emit_local_decl(c, ast->declare_stmt);
break;
case AST_BREAK_STMT:
gencontext_emit_break(c, ast);
break;
case AST_CONTINUE_STMT:
gencontext_emit_continue(c, ast);
break;
case AST_IF_STMT:
llvm_emit_if(c, ast);
break;
case AST_RETURN_STMT:
gencontext_emit_return(c, ast);
break;
case AST_COMPOUND_STMT:
llvm_emit_compound_stmt(c, ast);
break;
case AST_CT_COMPOUND_STMT:
gencontext_emit_ct_compound_stmt(c, ast);
break;
case AST_FOR_STMT:
gencontext_emit_for_stmt(c, ast);
break;
case AST_FOREACH_STMT:
llvm_emit_foreach_stmt(c, ast);
break;
case AST_WHILE_STMT:
gencontext_emit_while_stmt(c, ast);
break;
case AST_DO_STMT:
gencontext_emit_do_stmt(c, ast);
break;
case AST_NEXT_STMT:
gencontext_emit_next_stmt(c, ast);
break;
case AST_DEFER_STMT:
case AST_NOP_STMT:
break;
case AST_ASM_STMT:
llvm_emit_asm_stmt(c, ast);
break;
case AST_ASSERT_STMT:
llvm_emit_assert_stmt(c, ast);
break;;
case AST_CT_ASSERT:
case AST_CT_IF_STMT:
case AST_CT_ELIF_STMT:
case AST_CT_ELSE_STMT:
case AST_CT_FOR_STMT:
case AST_CT_SWITCH_STMT:
case AST_CASE_STMT:
case AST_DEFAULT_STMT:
UNREACHABLE
case AST_SWITCH_STMT:
gencontext_emit_switch(c, ast);
break;
case AST_UNREACHABLE_STMT:
gencontext_emit_unreachable_stmt(c, ast);
break;
case AST_VOLATILE_STMT:
TODO
}
}