/* analyze.c -- IR analysis functions Copyright © 2023-2024 Samuel Lidén Borell Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include #include "include/csbe.h" #include "csbe_internal.h" #define IS_USED(var) \ (((var)->flags & CSBEVD_INTERN_USED) != 0) #define IS_SINGLE_EBB(var) \ (((var)->flags & CSBEVD_INTERN_SINGLE_EBB) != 0) #define HAS_ADDRESS(var) \ (((var)->flags & CSBEVD_INTERN_HAS_ADDRESS) != 0) #define IS_CROSS_CALL(var) \ (((var)->flags & CSBEVD_INTERN_LIVE_ACROSS_CALL) != 0) #define IS_FUNCPARAM(var) \ (((var)->flags & CSBEVD_INTERN_FUNCPARAM) != 0) /** * Allocates var-lanes to variables that are only used in a single * extened-basic-block (EBB). * * This is simple heuristic to find non-overlapping variables, that can * share var-lanes (and hence: registers). * * \param fb Function body. * \param allow_cross_call * Whether to allocate var-lanes to variables that live across a function * call. This is used to assign different sets var-lanes depending on * this property. This is later used by the codegens to decide whether to * put in a caller-saved or callee-saved register. * \param next_free_varlane * The first unallocated var-lane. Updated by the function. */ static void alloc_single_ebb_varlanes(struct FuncIR *fb, int allow_cross_call, unsigned *next_free_varlane) { struct Var *var = fb->vars; unsigned first_free_varlane = *next_free_varlane; unsigned next_varlane = first_free_varlane; int i; for (i = fb->num_vars; i--; var++) { struct Ebb *ebb; unsigned lane; if (var->lane) continue; assert(!IS_FUNCPARAM(var)); if (HAS_ADDRESS(var) || !IS_USED(var) || !IS_SINGLE_EBB(var)) continue; if (!allow_cross_call && IS_CROSS_CALL(var)) continue; ebb = &fb->ebbs[var->ebb_id]; lane = ebb->next_varlane; /* Local allocation in EBB */ if (!lane) lane = first_free_varlane; /* First variable in EBB */ var->lane = lane++; if (lane > next_varlane) next_varlane = lane; /* Track max value */ ebb->next_varlane = lane; assert(next_varlane > first_free_varlane); } *next_free_varlane = next_varlane; } /** * De-assigns varlanes from variables (typically temporaries) that are used * over a function call. This way, they can be assigned a callee-saved * var-lane instead. */ static void demote_cross_call_vars(struct FuncIR *fb) { struct Var *var = fb->vars; int i; for (i = fb->num_vars; i--; var++) { if (var->lane && IS_CROSS_CALL(var)) { var->lane = 0; } } } enum CsbeErr analyze_ir(struct Csbe *csbe, struct FuncIR *fb) { /* * This is a very crude allocation of "varlanes" (i.e. abstract registers). * EBB flow analyzis and graph coloring could give better results, but * would also require more effort. */ struct Var *var; unsigned next_varlane; /**< Next free varlane for allocation */ unsigned i; (void)csbe; /* Temporaries that are used cross-call lose their varlane, so they get a callee-saved varlane instead */ demote_cross_call_vars(fb); /* Temporary variables will already have been allocated var-lanes at this point */ next_varlane = fb->first_non_temporary_varlane; /* Variables that are only used in a single EBB, and NOT across a function call are allocated the next var-lanes. These use caller-saved registers (since there is no need to save them). This group of var-lanes typically include temporaries, unless they are used across a function call, e.g. y=x+f() */ alloc_single_ebb_varlanes(fb, 0, &next_varlane); fb->first_callee_saved_varlane = next_varlane; /* Single-EBB variables that ARE used across function calls are allocated var-lanes now. These typically use callee-saved registers. */ alloc_single_ebb_varlanes(fb, 1, &next_varlane); /* Assign lanes to all variables that don't need an address */ /* TODO prioritize variables in the likely path */ var = fb->vars; for (i = fb->num_vars; i--; var++) { if (var->lane) continue; if (HAS_ADDRESS(var) || !IS_USED(var)) continue; var->lane = next_varlane++; } /* TODO Possible optimization: Score and sort the varlanes. Variables that are used many times (or in loops) could be prioritized. */ /* For leaf functions, we can use caller-saved regs for multi-EBB variables too */ if (!fb->contains_likely_calls) { fb->first_callee_saved_varlane = next_varlane; } /* Any other variables get the remaining (higher numbered) lanes */ fb->first_addressable_varlane = next_varlane; var = fb->vars; for (i = fb->num_vars; i--; var++) { if (var->lane || !IS_USED(var)) continue; var->lane = next_varlane++; } return CSBEE_OK; }