From 9697d89b2f9822065e19cbddb35716c0ac6f14c0 Mon Sep 17 00:00:00 2001 From: Santiago Arranz-Olmos Date: Sat, 13 Jun 2026 22:47:55 +0200 Subject: [PATCH] Register allocation hints --- changes/01-feature/1439-regalloc-hints.md | 8 ++++ compiler/src/regalloc.ml | 39 +++++++++++++++++++ .../arm-m4/force_regalloc_dup.jazz | 8 ++++ .../risc-v/force_regalloc_dup.jazz | 8 ++++ .../x86-64/force_regalloc_dup.jazz | 8 ++++ .../x86-64/force_regalloc_vs_arch.jazz | 11 ++++++ compiler/tests/negative.expected | 19 +++++++-- .../tests/success/arm-m4/force_regalloc.jazz | 8 ++++ .../tests/success/risc-v/force_regalloc.jazz | 8 ++++ .../tests/success/x86-64/force_regalloc.jazz | 10 +++++ 10 files changed, 123 insertions(+), 4 deletions(-) create mode 100644 changes/01-feature/1439-regalloc-hints.md create mode 100644 compiler/tests/fail/register_allocation/arm-m4/force_regalloc_dup.jazz create mode 100644 compiler/tests/fail/register_allocation/risc-v/force_regalloc_dup.jazz create mode 100644 compiler/tests/fail/register_allocation/x86-64/force_regalloc_dup.jazz create mode 100644 compiler/tests/fail/register_allocation/x86-64/force_regalloc_vs_arch.jazz create mode 100644 compiler/tests/success/arm-m4/force_regalloc.jazz create mode 100644 compiler/tests/success/risc-v/force_regalloc.jazz create mode 100644 compiler/tests/success/x86-64/force_regalloc.jazz diff --git a/changes/01-feature/1439-regalloc-hints.md b/changes/01-feature/1439-regalloc-hints.md new file mode 100644 index 0000000000..b09b57d385 --- /dev/null +++ b/changes/01-feature/1439-regalloc-hints.md @@ -0,0 +1,8 @@ +- Experimental register-allocation hints. These hints are best-effort: they are + not part of the compiler correctness of Jasmin. The annotation is + case-sensitive and uses architecture-specific register names: e.g., `RAX` on + x86-64, `r0` on arm-m4, `x10`, `ra` on risc-v. `#[force_regalloc=REG]` +forces a `reg` variable to the named register. For example, on x86-64: + ``` + #[force_regalloc=RAX] reg u64 x; + ``` diff --git a/compiler/src/regalloc.ml b/compiler/src/regalloc.ml index 809bde6574..5ab259cc7d 100644 --- a/compiler/src/regalloc.ml +++ b/compiler/src/regalloc.ml @@ -744,6 +744,43 @@ module Regalloc (Arch : Arch_full.Arch) let cnf = List.fold_left2 (force_constraint var_of_expr) cnf id.i_in es in cnf + let force_regalloc_name = "force_regalloc" + + let parse_force_regalloc x = + let on_name loc _ s = + try List.find (fun r -> r.v_name = s) Arch.all_registers + with Not_found -> + hierror_reg ~loc:(Lone loc) + "unknown register “%s” in %s annotation on variable %a" + s force_regalloc_name (Printer.pp_var ~debug:true) x + in + let on_error loc _ = + hierror_reg ~loc:(Lone loc) + "the “%s” annotation on variable %a requires a register name" + force_regalloc_name (Printer.pp_var ~debug:true) x + in + let on_attr = + Annot.on_attribute ~on_id:on_name ~on_string:on_name on_error + in + Annot.ensure_uniq1 force_regalloc_name on_attr x.v_annot + + let allocate_hints nv vars a cnf = + let allocate_one x i y = + if types_cannot_conflict Arch.reg_size x.v_kind x.v_ty y.v_kind y.v_ty + then hierror_reg ~loc:Lnone + "variable %a (declared at %a with type \"%a\") must be allocated \ + to register %a with an incompatible type" + (Printer.pp_var ~debug:true) x + L.pp_sloc x.v_dloc + PrintCommon.pp_ty x.v_ty + (Printer.pp_var ~debug:false) y; + allocate_one nv vars L.i_dummy cnf x i y a + in + let apply_force_regalloc x i = + parse_force_regalloc x |> Option.may (allocate_one x i) + in + Hv.iter apply_force_regalloc vars; + cnf let stable_call_conv = "stable_call_conv" @@ -1451,6 +1488,8 @@ let global_allocation return_addresses (funcs: ('info, 'asm) func list) : funcs in + let conflicts = allocate_hints nv vars a conflicts in + if !Glob_options.print_liveness then pp_liveness vars liveness_per_callsite liveness_table a; greedy_allocation vars nv conflicts fr a; diff --git a/compiler/tests/fail/register_allocation/arm-m4/force_regalloc_dup.jazz b/compiler/tests/fail/register_allocation/arm-m4/force_regalloc_dup.jazz new file mode 100644 index 0000000000..ee1de481aa --- /dev/null +++ b/compiler/tests/fail/register_allocation/arm-m4/force_regalloc_dup.jazz @@ -0,0 +1,8 @@ +export fn main(reg u32 a) -> reg u32 { + #[force_regalloc=r4, force_regalloc=r5] reg u32 x; + reg u32 r; + x = 1; + x += a; + r = x; + return r; +} diff --git a/compiler/tests/fail/register_allocation/risc-v/force_regalloc_dup.jazz b/compiler/tests/fail/register_allocation/risc-v/force_regalloc_dup.jazz new file mode 100644 index 0000000000..875763acbf --- /dev/null +++ b/compiler/tests/fail/register_allocation/risc-v/force_regalloc_dup.jazz @@ -0,0 +1,8 @@ +export fn main(reg u32 a) -> reg u32 { + #[force_regalloc=x18, force_regalloc=x19] reg u32 x; + reg u32 r; + x = 1; + x += a; + r = x; + return r; +} diff --git a/compiler/tests/fail/register_allocation/x86-64/force_regalloc_dup.jazz b/compiler/tests/fail/register_allocation/x86-64/force_regalloc_dup.jazz new file mode 100644 index 0000000000..78d8c4a123 --- /dev/null +++ b/compiler/tests/fail/register_allocation/x86-64/force_regalloc_dup.jazz @@ -0,0 +1,8 @@ +export fn main(reg u64 a) -> reg u64 { + #[force_regalloc=RAX, force_regalloc=RCX] reg u64 x; + reg u64 r; + x = 1; + x += a; + r = x; + return r; +} diff --git a/compiler/tests/fail/register_allocation/x86-64/force_regalloc_vs_arch.jazz b/compiler/tests/fail/register_allocation/x86-64/force_regalloc_vs_arch.jazz new file mode 100644 index 0000000000..10c2a92aaf --- /dev/null +++ b/compiler/tests/fail/register_allocation/x86-64/force_regalloc_vs_arch.jazz @@ -0,0 +1,11 @@ +export fn main() -> reg u64 { + reg u64 hi lo d; + hi = 0; + lo = 7; + d = 2; + #[force_regalloc=RCX] reg u64 q; + reg u64 r; + ?{RAX=q, RDX=r} = #DIV(hi, lo, d); + q ^= r; + return q; +} diff --git a/compiler/tests/negative.expected b/compiler/tests/negative.expected index a70a4a8dc6..532daf3604 100644 --- a/compiler/tests/negative.expected +++ b/compiler/tests/negative.expected @@ -772,7 +772,9 @@ fail/register_allocation/arm-m4/bug_201.jazz: compilation error in function main: register allocation: too many large parameters according to the ABI (only 0 available on this architecture) -fail/register_allocation/arm-m4/no_mmx.jazz: +fail/register_allocation/arm-m4/force_regalloc_dup.jazz: + +"fail/register_allocation/arm-m4/force_regalloc_dup.jazz", line 2 (4-18): only one of the attribute force_regalloc, force_regalloc is expectedfail/register_allocation/arm-m4/no_mmx.jazz: compilation error: register allocation: unable to allocate tmp (defined at "fail/register_allocation/arm-m4/no_mmx.jazz", line 6 (2-5)): bank “extra (aka mmx)” is empty on this architecture @@ -800,7 +802,9 @@ fail/register_allocation/arm-m4/too_many_ret.jazz: compilation error in function too_many_ret: register allocation: too many return values according to the ABI (only 2 available on this architecture) -fail/register_allocation/x86-64/already_allocated.jazz: +fail/register_allocation/risc-v/force_regalloc_dup.jazz: + +"fail/register_allocation/risc-v/force_regalloc_dup.jazz", line 2 (4-18): only one of the attribute force_regalloc, force_regalloc is expectedfail/register_allocation/x86-64/already_allocated.jazz: "fail/register_allocation/x86-64/already_allocated.jazz", line 10 (0-22): compilation error: @@ -861,6 +865,13 @@ register allocation: conflicting variables “z” and “res” must be merged at "fail/register_allocation/x86-64/conflicting_variables.jazz", line 11 (2-14): res = f(z); +fail/register_allocation/x86-64/force_regalloc_dup.jazz: + +"fail/register_allocation/x86-64/force_regalloc_dup.jazz", line 2 (4-18): only one of the attribute force_regalloc, force_regalloc is expectedfail/register_allocation/x86-64/force_regalloc_vs_arch.jazz: + +compilation error: +register allocation: cannot allocate q into RCX, the variable is already allocated in RAX + fail/register_allocation/x86-64/merge_across_banks.jazz: "fail/register_allocation/x86-64/merge_across_banks.jazz", line 3 (2-12): @@ -1572,8 +1583,8 @@ Allowed args are: [[reg]; [reg; mem (glob allowed)]] Statistics: - Annots: 0 + Annots: 3 Pretyping: 51 Parsing: 4 Typing: 1 - Compile: 194 + Compile: 195 diff --git a/compiler/tests/success/arm-m4/force_regalloc.jazz b/compiler/tests/success/arm-m4/force_regalloc.jazz new file mode 100644 index 0000000000..c735584787 --- /dev/null +++ b/compiler/tests/success/arm-m4/force_regalloc.jazz @@ -0,0 +1,8 @@ +export fn main(reg u32 a) -> reg u32 { + #[force_regalloc=r4] reg u32 x; + reg u32 r; + x = 1; + x += a; + r = x; + return r; +} diff --git a/compiler/tests/success/risc-v/force_regalloc.jazz b/compiler/tests/success/risc-v/force_regalloc.jazz new file mode 100644 index 0000000000..4c0e6912d6 --- /dev/null +++ b/compiler/tests/success/risc-v/force_regalloc.jazz @@ -0,0 +1,8 @@ +export fn main(reg u32 a) -> reg u32 { + #[force_regalloc=x18] reg u32 x; + reg u32 r; + x = 1; + x += a; + r = x; + return r; +} diff --git a/compiler/tests/success/x86-64/force_regalloc.jazz b/compiler/tests/success/x86-64/force_regalloc.jazz new file mode 100644 index 0000000000..3cf93318c5 --- /dev/null +++ b/compiler/tests/success/x86-64/force_regalloc.jazz @@ -0,0 +1,10 @@ +// The compiler inserts self-assignments before and after the body of the +// export function, but they inherit the annotations, so we must pick RAX +// here. + +export fn main(reg u64 a) -> reg u64 { + #[force_regalloc=RAX] reg u64 x; + x = a; + x += 1; + return x; +}