Naïve optimization of named _split_ multiple return valued when defer is never used

This is a naïve optimization but it helps a lot in the general case where callee temporary stack variables
are not allocated to represent the named return values by using that specific memory.

In the future, try to check if a specific named return value is ever used a `defer` within a procedure or not,
or is ever passed to a nested procedure call (e.g. possibly escapes).
This commit is contained in:
gingerBill
2022-11-25 23:57:55 +00:00
parent 615eccb6d1
commit d88b052d2d
9 changed files with 84 additions and 17 deletions

View File

@@ -1544,8 +1544,12 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
// NOTE(bill): Don't err here
}
GB_ASSERT(decl->defer_use_checked == false);
check_stmt_list(ctx, bs->stmts, Stmt_CheckScopeDecls);
decl->defer_use_checked = true;
for_array(i, bs->stmts) {
Ast *stmt = bs->stmts[i];
if (stmt->kind == Ast_ValueDecl) {
@@ -1580,6 +1584,7 @@ void check_proc_body(CheckerContext *ctx_, Token token, DeclInfo *decl, Type *ty
}
}
}
}
check_close_scope(ctx);

View File

@@ -6763,6 +6763,9 @@ ExprKind check_call_expr(CheckerContext *c, Operand *operand, Ast *call, Ast *pr
if (initial_entity != nullptr && initial_entity->kind == Entity_Procedure) {
if (initial_entity->Procedure.deferred_procedure.entity != nullptr) {
call->viral_state_flags |= ViralStateFlag_ContainsDeferredProcedure;
if (c->decl) {
c->decl->defer_used += 1;
}
}
}

View File

@@ -2018,6 +2018,9 @@ void check_stmt_internal(CheckerContext *ctx, Ast *node, u32 flags) {
ctx->in_defer = true;
check_stmt(ctx, ds->stmt, 0);
ctx->in_defer = out_in_defer;
if (ctx->decl) {
ctx->decl->defer_used += 1;
}
}
case_end;

View File

@@ -158,6 +158,8 @@ struct DeclInfo {
bool is_using;
bool where_clauses_evaluated;
bool proc_checked;
isize defer_used;
bool defer_use_checked;
CommentGroup *comment;
CommentGroup *docs;

View File

@@ -1426,7 +1426,9 @@ LB_ABI_INFO(lb_get_abi_info_internal) {
switch (build_context.metrics.arch) {
case TargetArch_amd64:
if (build_context.metrics.os == TargetOs_windows || build_context.metrics.abi == TargetABI_Win64) {
if (build_context.metrics.os == TargetOs_windows) {
return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention);
} else if (build_context.metrics.abi == TargetABI_Win64) {
return lbAbiAmd64Win64::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention);
} else if (build_context.metrics.abi == TargetABI_SysV) {
return lbAbiAmd64SysV::abi_info(c, arg_types, arg_count, return_type, return_is_defined, return_is_tuple, calling_convention);

View File

@@ -398,7 +398,7 @@ lbContextData *lb_push_context_onto_stack_from_implicit_parameter(lbProcedure *p
lbAddr lb_add_global_generated(lbModule *m, Type *type, lbValue value={}, Entity **entity_=nullptr);
lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e=nullptr, bool zero_init=true, i32 param_index=0, bool force_no_init=false);
lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e=nullptr, bool zero_init=true, bool force_no_init=false);
void lb_add_foreign_library_path(lbModule *m, Entity *e);

View File

@@ -134,7 +134,7 @@ lbValue lb_emit_unary_arith(lbProcedure *p, TokenKind op, lbValue x, Type *type)
Type *elem_type = base_array_type(type);
// NOTE(bill): Doesn't need to be zero because it will be initialized in the loops
lbAddr res_addr = lb_add_local(p, type, nullptr, false, 0, true);
lbAddr res_addr = lb_add_local(p, type, nullptr, false, true);
lbValue res = lb_addr_get_ptr(p, res_addr);
bool inline_array_arith = lb_can_try_to_inline_array_arith(type);

View File

@@ -2868,7 +2868,7 @@ lbValue lb_build_cond(lbProcedure *p, Ast *cond, lbBlock *true_block, lbBlock *f
}
lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero_init, i32 param_index, bool force_no_init) {
lbAddr lb_add_local(lbProcedure *p, Type *type, Entity *e, bool zero_init, bool force_no_init) {
GB_ASSERT(p->decl_block != p->curr_block);
LLVMPositionBuilderAtEnd(p->builder, p->decl_block->block);
@@ -2922,7 +2922,7 @@ lbAddr lb_add_local_generated(lbProcedure *p, Type *type, bool zero_init) {
}
lbAddr lb_add_local_generated_temp(lbProcedure *p, Type *type, i64 min_alignment) {
lbAddr res = lb_add_local(p, type, nullptr, false, 0, true);
lbAddr res = lb_add_local(p, type, nullptr, false, true);
lb_try_update_alignment(res.addr, cast(unsigned)min_alignment);
return res;
}

View File

@@ -1,4 +1,3 @@
LLVMValueRef lb_call_intrinsic(lbProcedure *p, const char *name, LLVMValueRef* args, unsigned arg_count, LLVMTypeRef* types, unsigned type_count)
{
unsigned id = LLVMLookupIntrinsicID(name, gb_strlen(name));
@@ -596,16 +595,69 @@ void lb_begin_procedure_body(lbProcedure *p) {
if (e->token.string != "") {
GB_ASSERT(!is_blank_ident(e->token));
// NOTE(bill): Don't even bother trying to optimize this with the return ptr value
// This will violate the defer rules if you do:
// foo :: proc() -> (x, y: T) {
// defer x = ... // defer is executed after the `defer`
// return // the values returned should be zeroed
// }
// NOTE(bill): REALLY, don't even bother.
//
// IMPORTANT NOTE(bill): REALLY, don't even bother!!!!!!
lbAddr res = lb_add_local(p, e->type, e);
lbAddr res = {};
if (p->entity && p->entity->decl_info &&
p->entity->decl_info->defer_use_checked &&
p->entity->decl_info->defer_used == 0) {
// NOTE(bill): this is a bodge to get around the issue of the problem BELOW
// We check to see if we ever use a defer statement ever within a procedure and if it
// if it never happens, see if you can possibly do take the return value pointer
//
// NOTE(bill): this could be buggy in that I have missed a case where `defer` was used
//
// TODO(bill): This could be optimized to check to see where a `defer` only uses
// the variable in question
bool has_return_ptr = p->return_ptr.addr.value != nullptr;
lbValue ptr = {};
if (ft->multiple_return_original_type != nullptr) {
isize the_offset = -1;
if (i+1 < results->variables.count) {
the_offset = cast(isize)param_offset + ft->original_arg_count + i;
} else if (has_return_ptr) {
GB_ASSERT(i+1 == results->variables.count);
the_offset = 0;
}
if (the_offset >= 0) {
lbValue ptr = {};
ptr.value = LLVMGetParam(p->value, cast(unsigned)the_offset);
ptr.type = alloc_type_pointer(e->type);
}
} else if (has_return_ptr) {
lbValue ptr = p->return_ptr.addr;
if (results->variables.count > 1) {
ptr = lb_emit_tuple_ep(p, ptr, cast(i32)i);
}
GB_ASSERT(is_type_pointer(ptr.type));
GB_ASSERT(are_types_identical(type_deref(ptr.type), e->type));
}
if (ptr.value != nullptr) {
lb_add_entity(p->module, e, ptr);
lb_add_debug_local_variable(p, ptr.value, e->type, e->token);
// NOTE(bill): no need to zero on the callee side as it is zeroed on the caller side
res = lb_addr(ptr);
}
}
if (res.addr.type == nullptr) {
// NOTE(bill): Don't even bother trying to optimize this with the return ptr value
// This will violate the defer rules if you do:
// foo :: proc() -> (x, y: T) {
// defer x = ... // defer is executed after the `defer`
// return // the values returned should be zeroed
// }
// NOTE(bill): REALLY, don't even bother.
//
// IMPORTANT NOTE(bill): REALLY, don't even bother!!!!!!
res = lb_add_local(p, e->type, e);
}
if (e->Variable.param_value.kind != ParameterValue_Invalid) {
lbValue c = lb_handle_param_value(p, e->type, e->Variable.param_value, e->token.pos);
@@ -1006,7 +1058,7 @@ lbValue lb_emit_call(lbProcedure *p, lbValue value, Array<lbValue> const &args,
GB_ASSERT(rt->kind == Type_Tuple);
for (isize j = 0; j < rt->Tuple.variables.count-1; j++) {
Type *partial_return_type = rt->Tuple.variables[j]->type;
lbValue partial_return_ptr = lb_add_local_generated(p, partial_return_type, true).addr;
lbValue partial_return_ptr = lb_add_local(p, partial_return_type, nullptr, true, false).addr;
array_add(&processed_args, partial_return_ptr);
}
rt = reduce_tuple_to_single_type(rt->Tuple.variables[rt->Tuple.variables.count-1]->type);