Skip to content

Register availability analysis#856

Merged
mshinwell merged 13 commits intoocaml:trunkfrom
mshinwell:reg_availability
Sep 15, 2017
Merged

Register availability analysis#856
mshinwell merged 13 commits intoocaml:trunkfrom
mshinwell:reg_availability

Conversation

@mshinwell
Copy link
Copy Markdown
Contributor

This pull request adds a forwards dataflow pass on Mach code to calculate which registers are available immediately prior to each instruction (that is to say, when the program counter is exactly at the start of that instruction. There is a subtlety to do with call instructions which will be explained in a later pull request---it isn't relevant for the correctness of this pass).

Another way of thinking of this is:

  • The liveness pass calculates the minimal ranges over which registers must be live;
  • The availability pass calculates the maximal ranges over which registers happen to be available.

The output of this pass is annotations on individual Mach instructions. These are collected together into contiguous ranges spanning multiple instructions by a subsequent pass, to be presented. These ranges correspond to statements such as "the value of the variable Foo.bar is to be found in register rax between labels L1234 and L5678". Ranges such as these may be transcribed into DWARF location lists that may be evaluated by the debugger at runtime against a specific value of the program counter. This then determines which variables are available to have their values inspected at that particular point in the program.

This analysis should be textbook, but is full of subtleties.

The availability analysis propagates naming information using a special operation Iname_for_debugger which is analogous to the method used in CompCert (thanks to @xavierleroy for describing this, which helped to simplify the code from the previous version---we no longer have to modify the existing Reg module). Changes to Selectgen, which are not presented here, insert these new operations when it becomes clear what the name to be associated to a particular register will be. The availability analysis then propagates that forwards. Care needs to be taken with Cassign---if the value being assigned to is mutable and is associated to the variable foo, say, then we need to forget about any other ascriptions of the name foo to (other) registers (since the value of foo has now changed, and those registers might contain copies of its old value). Only in the Cassign case will the is_assignment member inside Iname_for_debugger be true.

There is an assertion in this pass that checks the relation between liveness and availability, namely that any live register must also be available. This requires GPR#852 to work properly.

The type of provenance will be changed in due course. Those of you following along with the Flambda pull request #855 should be starting to see how this lines up with the provenance information there.

I will probably make some efficiency improvements and tidy-ups to this code, but it's pretty much in a final form now, and review should commence as it's tricky. @chambart has already looked at the core algorithm before (Pierre, your comments have been marked as XCR where they have been dealt with; please make a pull request against this one to delete them or re-open them as appropriate).

@mshinwell
Copy link
Copy Markdown
Contributor Author

This has been updated for current trunk. @chambart please review again.

@chambart
Copy link
Copy Markdown
Contributor

It looks like a few things could be simplified and clearer if you separated the notion of 'available before' and 'available across'. Of course in general available across wouldn't be observable, but for call and instructions implemented as multiple opcodes this can matter.

Also, it seems like there might be a problem with Ialloc: a register which was not clobbered might still be unavailable due to a collection moving the value. This seems to be handled correctly by functions, but there is the same problem here. If you want to represent values that are probably available, you should add another notion of 'available if not moved or collected'. Most of the time those values would still be available. I don't know if there is a good way to expose that in dwarf.

Otherwise it might not matter, but just for the record, there are two potential performance problems:

  • Finding all available register with in a given location can lead to quadratic situations the 'register with debug information set' type could contain a map of all the register currently living in a given location
  • The fixpoint on Icatch can follow the handlers in the wrong order. It could be mitigated by running availability for an handler only when the input changed.
    This might not be noticeable, but in case this pass is too slow, these might be good culpits.

I'm quite confident that except the Ialloc case everything else is fine.

in
let results =
assert (Array.length instr.arg = Array.length instr.res);
Array.mapi (fun index result_reg ->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is Array.map2

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

(RD.Set.of_array results))
| Iop op ->
if M.operation_can_raise op then begin
augment_availability_at_raise (Ok avail_before)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the instruction can destroy some register, this should be available across rather than available before.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fixed

Printmach.instr ({ instr with M. next = M.end_instr (); })
end;
match instr.desc with
| Iop (Ialloc _) ->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Across an allocation, only live and spilled values are guaranteed to be available.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be fixed now

(* Second: the case of OCaml to OCaml function calls. In this case,
registers always become unavailable unless:
(a) they are "live across" the call; and/or
(b) they hold immediates and are assigned to the stack. *)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment might need to explain why this is the case: the gc can move anything else.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

assert (nfail = nfail');
(* This [available_regs] call may cause [avail_at_exit] to be
updated. *)
available_regs handler ~avail_before:avail_at_top_of_handler
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To avoid useless traversal (with a potential linear speedup if the handlers are in the wrong order), this could check if the argument changed. i.e. if avail_at_top_of_handler is equal to handler.available_before.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check could be done for every instruction to avoid too much recomputing in case of nested fixpoints. But that would require an efficient equality test.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, except you need to get the "available_after" set somehow. I think for the moment I'm going to leave this, I have added a comment in the code referencing these suggestions.

Array.mapi (fun index result_reg ->
let arg_reg = instr.arg.(index) in
match RD.Set.find_reg_exn avail_before arg_reg with
| exception Not_found ->
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case should never happen. It can be an assertion.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've changed this to use Array.map2.

| Unreachable -> Format.fprintf ppf "<unreachable>"
| Ok availability ->
Format.fprintf ppf "{%a}"
(Format.pp_print_list (Reg_with_debug_info.print ~print_reg))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add ~pp_sep:(fun ppf () -> Format.fprintf ppf ",@ ")

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@mshinwell
Copy link
Copy Markdown
Contributor Author

I think I've addressed everything (although deferring the performance concerns). I also fixed a small mistake: names should not be propagated from raise points through to the parameters of exception handlers.

I have added two flags: -davail to dump register availability information when liveness information is requested; and -drunavail to actually cause this pass to be run. The latter requires -g to be in effect too. As discussed with @damiendoligez this will enable us to start exercising this pass on 4.06 compilers pending merging the remainder of the debugging-related patches.

@mshinwell mshinwell added this to the 4.06.0 milestone Sep 14, 2017
Copy link
Copy Markdown
Contributor

@chambart chambart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good for merge to me now. Thanks

@mshinwell mshinwell merged commit b650966 into ocaml:trunk Sep 15, 2017
xavierleroy added a commit to xavierleroy/ocaml that referenced this pull request Apr 18, 2021
The availability analysis was introduced in 2017 as part of GPR ocaml#856,
as a first step towards generating DWARF debugging information.

This project (generating DWARF debug info) was stopped, but the analysis
is still here, even though its results are not used and it is not normally run
(except when the experimental -drunavail flag is given).

This is essentially dead code.  This PR removes it.
mshinwell pushed a commit that referenced this pull request Apr 23, 2021
The availability analysis was introduced in 2017 as part of GPR #856,
as a first step towards generating DWARF debugging information.

This project (generating DWARF debug info) was stopped, but the analysis
is still here, even though its results are not used and it is not normally run
(except when the experimental -drunavail flag is given).

This is essentially dead code.  This PR removes it.
stedolan pushed a commit to stedolan/ocaml that referenced this pull request Oct 25, 2022
EmileTrotignon pushed a commit to EmileTrotignon/ocaml that referenced this pull request Jan 12, 2024
Co-authored-by: Cuihtlauac ALVARADO <cuihtmlauac@tarides.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants