/* * This file is part of libFirm. * Copyright (C) 2012 University of Karlsruhe. */ /** * @file * @brief Main Backend driver. * @author Sebastian Hack * @date 25.11.2004 */ #include "be_t.h" #include "beasm.h" #include "bechordal_t.h" #include "bediagnostic.h" #include "beemitter.h" #include "begnuas.h" #include "beifg.h" #include "beirg.h" #include "belistsched.h" #include "belive.h" #include "belower.h" #include "bemodule.h" #include "benode.h" #include "bera.h" #include "besched.h" #include "bespillutil.h" #include "bessadestr.h" #include "bestack.h" #include "bestat.h" #include "beutil.h" #include "beverify.h" #include "execfreq_t.h" #include "ident_t.h" #include "ircons.h" #include "irdom_t.h" #include "irdump.h" #include "iredges_t.h" #include "irgopt.h" #include "irloop_t.h" #include "iroptimize.h" #include "irprofile.h" #include "irprog.h" #include "irtools.h" #include "irverify.h" #include "lc_opts.h" #include "lc_opts_enum.h" #include "obst.h" #include "statev.h" #include "target_t.h" #include "util.h" #include static struct obstack obst; static be_main_env_t env; /* options visible for anyone */ be_options_t be_options = { .dump_flags = DUMP_NONE, .timing = false, .opt_profile_generate = false, .opt_profile_use = false, .omit_fp = false, .do_verify = true, .ilp_solver = "", .verbose_asm = true, }; /* possible dumping options */ static const lc_opt_enum_mask_items_t dump_items[] = { { "none", DUMP_NONE }, { "initial", DUMP_INITIAL }, { "sched", DUMP_SCHED }, { "prepared", DUMP_PREPARED }, { "regalloc", DUMP_RA }, { "final", DUMP_FINAL }, { "be", DUMP_BE }, { "all", 2 * DUMP_BE - 1 }, { NULL, 0 } }; static lc_opt_enum_mask_var_t dump_var = { &be_options.dump_flags, dump_items }; static const lc_opt_table_entry_t be_main_options[] = { LC_OPT_ENT_ENUM_MASK("dump", "dump irg on several occasions", &dump_var), LC_OPT_ENT_BOOL ("omitfp", "omit frame pointer", &be_options.omit_fp), LC_OPT_ENT_BOOL ("verify", "verify the backend irg", &be_options.do_verify), LC_OPT_ENT_BOOL ("time", "get backend timing statistics", &be_options.timing), LC_OPT_ENT_BOOL ("profilegenerate", "instrument the code for execution count profiling", &be_options.opt_profile_generate), LC_OPT_ENT_BOOL ("profileuse", "use existing profile data", &be_options.opt_profile_use), LC_OPT_ENT_BOOL ("verboseasm", "enable verbose assembler output", &be_options.verbose_asm), LC_OPT_ENT_STR("ilp.solver", "the ilp solver name", &be_options.ilp_solver), LC_OPT_LAST }; asm_constraint_flags_t be_asm_constraint_flags[256]; void be_set_constraint_support(asm_constraint_flags_t const flags, char const *const constraints) { for (char const *i = constraints; *i != '\0'; ++i) { be_asm_constraint_flags[(unsigned char)*i] = flags; } } static void be_init_default_asm_constraint_flags(void) { for (size_t i = 0; i != ARRAY_SIZE(be_asm_constraint_flags); ++i) { be_asm_constraint_flags[i] = ASM_CONSTRAINT_FLAG_INVALID; } be_set_constraint_support(ASM_CONSTRAINT_FLAG_MODIFIER_EARLYCLOBBER, "&"); be_set_constraint_support(ASM_CONSTRAINT_FLAG_SUPPORTS_REGISTER, "0123456789"); /* List of constraints supported by gcc for any machine (or at least * recognized). Mark them as NO_SUPPORT so we can differentiate them * from INVALID. Backends should change the flags they support. */ be_set_constraint_support(ASM_CONSTRAINT_FLAG_NO_SUPPORT, ",<>EFGHIJKLMNOPVXgimoprs"); /* Skip whitespace. * TODO '*' actually penalizes the selection of the next constraint letter. * We do not support this, yet. * TODO '!' and '?' actually penalize an alternative of a multi alternative * constraint. We do not support this, yet. */ be_set_constraint_support(ASM_CONSTRAINT_FLAG_NONE, "\t\n\r !*?"); } bool be_set_arch(char const *const arch) { /* TODO: generalize to all backends */ bool res = false; char buf[64]; snprintf(buf, sizeof(buf), "ia32-arch=%s", arch); res |= ir_target_option(buf); snprintf(buf, sizeof(buf), "sparc-cpu=%s", arch); res |= ir_target_option(buf); return res; } void be_initialize(void) { assert(!ir_target.isa_initialized); be_init_default_asm_constraint_flags(); obstack_init(&obst); } static void finish_isa(void) { assert(ir_target.isa_initialized); ir_target.isa->finish(); obstack_free(&obst, NULL); } asm_constraint_flags_t be_parse_asm_constraints(const char *constraint) { assert(ir_target.isa_initialized); asm_constraint_flags_t flags; char const *c = constraint; switch (*c) { case '=': ++c; flags = ASM_CONSTRAINT_FLAG_MODIFIER_WRITE; break; case '+': ++c; flags = ASM_CONSTRAINT_FLAG_MODIFIER_READ | ASM_CONSTRAINT_FLAG_MODIFIER_WRITE; break; case '%': ++c; /* FALLTHROUGH */ /* TODO stub, should mark this operand as commutative with the next. */ default: flags = ASM_CONSTRAINT_FLAG_MODIFIER_READ; break; } for (; *c != '\0'; ++c) { switch (*c) { case '#': /* text until comma is a comment */ while (*c != 0 && *c != ',') ++c; break; default: flags |= be_asm_constraint_flags[(unsigned char)*c]; break; } } return flags; } int be_is_valid_clobber(const char *clobber) { assert(ir_target.isa_initialized); /* memory is a valid clobber. (the frontend has to detect this case too, * because it has to add memory edges to the asm) */ if (streq(clobber, "memory")) return 1; /* cc (condition code) is always valid */ if (streq(clobber, "cc")) return 1; return be_parse_register_name(clobber) != NULL; } static void be_opt_register(void) { static bool run_once = false; if (run_once) return; run_once = true; lc_opt_entry_t *be_grp = lc_opt_get_grp(firm_opt_get_root(), "be"); lc_opt_add_table(be_grp, be_main_options); } void be_check_verify_result(bool fine, ir_graph *irg) { if (!fine) { be_errorf(NULL, "verifier failed; trying to write assert graph and abort"); dump_ir_graph(irg, "assert"); abort(); } } /* Perform schedule verification if requested. */ static void be_sched_verify(ir_graph *irg) { if (be_options.do_verify) { be_timer_push(T_VERIFY); bool fine = be_verify_schedule(irg); be_check_verify_result(fine, irg); be_timer_pop(T_VERIFY); } } static void be_regalloc_verify(ir_graph *const irg) { if (be_options.do_verify) { be_timer_push(T_VERIFY); bool const fine = be_verify_register_allocation(irg); be_check_verify_result(fine, irg); be_timer_pop(T_VERIFY); } } /* Initialize the Firm backend. Must be run first in init_firm()! */ void firm_be_init(void) { be_opt_register(); be_init_modules(); } static ir_timer_t *bemain_timer; /** * Prepare a backend graph for code generation and initialize its irg */ static void initialize_birg(be_irg_t *birg, ir_graph *irg, be_main_env_t *env) { /* don't duplicate locals in backend when dumping... */ ir_remove_dump_flags(ir_dump_flag_consts_local); be_dump(DUMP_INITIAL, irg, "begin"); assure_irg_properties(irg, IR_GRAPH_PROPERTY_NO_BADS | IR_GRAPH_PROPERTY_NO_UNREACHABLE_CODE | IR_GRAPH_PROPERTY_NO_CRITICAL_EDGES | IR_GRAPH_PROPERTY_MANY_RETURNS); memset(birg, 0, sizeof(*birg)); birg->main_env = env; obstack_init(&birg->obst); irg->be_data = birg; be_info_init_irg(irg); birg->lv = be_liveness_new(irg); /* Verify the initial graph */ if (be_options.do_verify) { be_timer_push(T_VERIFY); bool fine = irg_verify(irg); be_check_verify_result(fine, irg); be_timer_pop(T_VERIFY); } } static ir_graph *be_prepare_profile(const char *const cup_name) { obstack_printf(&obst, "%s.prof", cup_name); obstack_1grow(&obst, '\0'); const char *prof_filename = obstack_finish(&obst); bool have_profile = false; if (be_options.opt_profile_use) { bool res = ir_profile_read(prof_filename); if (!res) { be_warningf(NULL, "could not read profile data '%s'", prof_filename); } else { ir_create_execfreqs_from_profile(); ir_profile_free(); have_profile = true; } } ir_graph *prof_init_irg = NULL; if (be_options.opt_profile_generate) prof_init_irg = ir_profile_instrument(prof_filename); if (!have_profile) { be_timer_push(T_EXECFREQ); foreach_irp_irg(i, irg) { ir_estimate_execfreq(irg); } be_timer_pop(T_EXECFREQ); } return prof_init_irg; } void be_begin(FILE *file_handle, const char *cup_name) { memset(be_asm_constraint_flags, 0, sizeof(be_asm_constraint_flags)); bemain_timer = NULL; if (be_options.timing) { bemain_timer = ir_timer_new(); if (ir_timer_enter_high_priority()) be_warningf(NULL, "could not enter high priority mode"); ir_timer_reset_and_start(bemain_timer); } if (stat_ev_enabled) { const char *dot = strrchr(cup_name, '.'); const char *pos = dot ? dot : cup_name + strlen(cup_name); char *buf = ALLOCAN(char, pos - cup_name + 1); strncpy(buf, cup_name, pos - cup_name); buf[pos - cup_name] = '\0'; stat_ev_ctx_push_str("bemain_compilation_unit", cup_name); } be_timing = be_options.timing; /* perform target lowering if it didn't happen yet */ if (get_irp_n_irgs() > 0 && !irg_is_constrained(get_irp_irg(0), IR_GRAPH_CONSTRAINT_TARGET_LOWERED)) be_lower_for_target(); if (be_timing) { for (be_timer_id_t t = T_FIRST; t < T_LAST+1; ++t) { be_timers[t] = ir_timer_new(); ir_timer_init_parent(be_timers[t]); } } be_emit_init(file_handle); memset(&env, 0, sizeof(env)); env.ent_trampoline_map = pmap_create(); env.pic_trampolines_type = new_type_segment(NEW_IDENT("$PIC_TRAMPOLINE_TYPE"), tf_none); env.ent_pic_symbol_map = pmap_create(); env.pic_symbols_type = new_type_segment(NEW_IDENT("$PIC_SYMBOLS_TYPE"), tf_none); env.cup_name = cup_name; be_info_init(); /* First: initialize all birgs */ size_t num_birgs = 0; /* we might need 1 birg more for instrumentation constructor */ be_irg_t *const birgs = OALLOCN(&obst, be_irg_t, get_irp_n_irgs()+1); foreach_irp_irg(i, irg) { ir_entity *entity = get_irg_entity(irg); if (get_entity_linkage(entity) & IR_LINKAGE_NO_CODEGEN) continue; initialize_birg(&birgs[num_birgs++], irg, &env); if (ir_target.isa->handle_intrinsics) ir_target.isa->handle_intrinsics(irg); be_dump(DUMP_INITIAL, irg, "prepared"); } /* Prepare basicblock profile generation/usage. Note: You should avoid * introducing new control flow after this point or you won't have profile * data for the new basic blocks. */ ir_graph *prof_init_irg = be_prepare_profile(cup_name); if (prof_init_irg != NULL) initialize_birg(&birgs[num_birgs++], prof_init_irg, &env); be_gas_begin_compilation_unit(&env); } void firm_be_finish(void) { finish_isa(); be_quit_modules(); } int be_timing; static const char *get_timer_name(be_timer_id_t id) { switch (id) { case T_ABI: return "abi"; case T_CODEGEN: return "codegen"; case T_RA_PREPARATION: return "ra_preparation"; case T_SCHED: return "sched"; case T_CONSTR: return "constr"; case T_FINISH: return "finish"; case T_EMIT: return "emit"; case T_VERIFY: return "verify"; case T_OTHER: return "other"; case T_HEIGHTS: return "heights"; case T_LIVE: return "live"; case T_EXECFREQ: return "execfreq"; case T_SSA_CONSTR: return "ssa_constr"; case T_RA_EPILOG: return "ra_epilog"; case T_RA_CONSTR: return "ra_constr"; case T_RA_SPILL: return "ra_spill"; case T_RA_SPILL_APPLY: return "ra_spill_apply"; case T_RA_COLOR: return "ra_color"; case T_RA_IFG: return "ra_ifg"; case T_RA_COPYMIN: return "ra_copymin"; case T_RA_SSA: return "ra_ssa"; case T_RA_OTHER: return "ra_other"; } return "unknown"; } ir_timer_t *be_timers[T_LAST+1]; static void dummy_after_transform(ir_graph *irg, const char *name) { (void)irg; (void)name; } after_transform_func be_after_transform = dummy_after_transform; void be_set_after_transform_func(after_transform_func after_transform) { be_after_transform = after_transform; } void be_after_irp_transform(const char *name) { if (be_after_transform == NULL) return; foreach_irp_irg_r(i, irg) { be_after_transform(irg, name); } } void be_lower_for_target(void) { assert(ir_target.isa_initialized); ir_target.isa->lower_for_target(); /* set the phase to low */ foreach_irp_irg_r(i, irg) { assert(!irg_is_constrained(irg, IR_GRAPH_CONSTRAINT_TARGET_LOWERED)); add_irg_constraints(irg, IR_GRAPH_CONSTRAINT_TARGET_LOWERED); } } static int cse_setting; bool be_step_first(ir_graph *irg) { ir_entity *const entity = get_irg_entity(irg); if (get_entity_linkage(entity) & IR_LINKAGE_NO_CODEGEN) return false; be_timer_push(T_OTHER); if (stat_ev_enabled) { stat_ev_ctx_push_fmt("bemain_irg", "%+F", irg); stat_ev_ull("bemain_insns_start", be_count_insns(irg)); stat_ev_ull("bemain_blocks_start", be_count_blocks(irg)); } cse_setting = get_opt_cse(); return true; } void be_step_schedule(ir_graph *irg) { /* We generally disable CSE after scheduling as we now may want to duplicate * operations on purpose, new operations should not merge with existing ones * before they are scheduled. */ set_opt_cse(0); be_timer_push(T_SCHED); be_schedule_graph(irg); be_timer_pop(T_SCHED); be_dump(DUMP_SCHED, irg, "sched"); be_sched_verify(irg); } void be_step_regalloc(ir_graph *irg, const regalloc_if_t *regif) { if (stat_ev_enabled) { stat_ev_dbl("bemain_costs_before_ra", be_estimate_irg_costs(irg)); stat_ev_ull("bemain_insns_before_ra", be_count_insns(irg)); stat_ev_ull("bemain_blocks_before_ra", be_count_blocks(irg)); be_stat_values(irg); } /* Do register allocation */ be_allocate_registers(irg, regif); be_regalloc_verify(irg); if (stat_ev_enabled) { stat_ev_dbl("bemain_costs_after_ra", be_estimate_irg_costs(irg)); stat_ev_ull("bemain_insns_after_ra", be_count_insns(irg)); stat_ev_ull("bemain_blocks_after_ra", be_count_blocks(irg)); } be_dump(DUMP_RA, irg, "ra"); } void be_step_last(ir_graph *irg) { if (stat_ev_enabled) { stat_ev_ull("bemain_insns_finish", be_count_insns(irg)); stat_ev_ull("bemain_blocks_finish", be_count_blocks(irg)); } be_dump(DUMP_FINAL, irg, "final"); be_regalloc_verify(irg); be_timer_pop(T_OTHER); if (be_timing) { if (stat_ev_enabled) { for (be_timer_id_t t = T_FIRST; t < T_LAST+1; ++t) { char buf[128]; snprintf(buf, sizeof(buf), "bemain_time_%s", get_timer_name(t)); stat_ev_dbl(buf, ir_timer_elapsed_usec(be_timers[t])); } } else { printf("==>> IRG %s <<==\n", get_entity_name(get_irg_entity(irg))); for (be_timer_id_t t = T_FIRST; t < T_LAST+1; ++t) { double val = ir_timer_elapsed_usec(be_timers[t]) / 1000.0; printf("%-20s: %10.3f msec\n", get_timer_name(t), val); } } for (be_timer_id_t t = T_FIRST; t < T_LAST+1; ++t) { ir_timer_reset(be_timers[t]); } } be_free_birg(irg); stat_ev_ctx_pop("bemain_irg"); set_opt_cse(cse_setting); } void be_finish(void) { be_gas_end_compilation_unit(&env); if (be_options.timing) { ir_timer_stop(bemain_timer); ir_timer_leave_high_priority(); if (stat_ev_enabled) { stat_ev_dbl("bemain_backend_time", ir_timer_elapsed_msec(bemain_timer)); } else { double val = ir_timer_elapsed_usec(bemain_timer) / 1000.0; printf("%-20s: %10.3f msec\n", "BEMAINLOOP", val); } } if (stat_ev_enabled) { stat_ev_ctx_pop("bemain_compilation_unit"); } be_emit_exit(); be_info_free(); pmap_destroy(env.ent_trampoline_map); pmap_destroy(env.ent_pic_symbol_map); free_type(env.pic_trampolines_type); free_type(env.pic_symbols_type); } void be_main(FILE *file_handle, const char *cup_name) { /* Let the target control how the codegeneration works. */ ir_target.isa->generate_code(file_handle, cup_name); } ir_jit_function_t *be_jit_compile(ir_jit_segment_t *const segment, ir_graph *const irg) { if (ir_target.isa->jit_compile == NULL) return NULL; obstack_init(&obst); ir_entity *entity = get_irg_entity(irg); if (get_entity_linkage(entity) & IR_LINKAGE_NO_CODEGEN) return NULL; be_irg_t *const birg = OALLOCZ(&obst, be_irg_t); initialize_birg(birg, irg, &env); if (ir_target.isa->handle_intrinsics) ir_target.isa->handle_intrinsics(irg); be_dump(DUMP_INITIAL, irg, "prepared"); return ir_target.isa->jit_compile(segment, irg); } void be_emit_function(char *const buffer, ir_jit_function_t *const function) { ir_target.isa->emit_function(buffer, function); }