Skip to content

Uniqueness mode#33

Closed
anfelor wants to merge 70 commits intoocaml-flambda:mainfrom
anfelor:uniqueness-mode
Closed

Uniqueness mode#33
anfelor wants to merge 70 commits intoocaml-flambda:mainfrom
anfelor:uniqueness-mode

Conversation

@anfelor
Copy link
Copy Markdown

@anfelor anfelor commented Jul 5, 2022

This PR tracks the ongoing development of uniqueness mode.

Copy link
Copy Markdown
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

The test cases look good

| None -> line i ppf "value_mode <modevar>\n");
( let consts = Btype.Value_mode.check_const x.exp_mode in
line i ppf "value_mode ";
line 0 ppf (match fst consts with
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This is somewhat ugly as I couldn't find a way to combine two format strings (the way '^' acts on normal strings). Is there a nice operator for that?

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.

There is but generally I try to avoid it. I would write this by lifting out these two matches into functions called fmt_locality and fmt_uniqueness and then call them using %a. I'd also avoid using fst and snd by replacing let consts with let locality, uniqueness.

Copy link
Copy Markdown
Contributor

@lpw25 lpw25 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 so far

| None -> line i ppf "value_mode <modevar>\n");
( let consts = Btype.Value_mode.check_const x.exp_mode in
line i ppf "value_mode ";
line 0 ppf (match fst consts with
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.

There is but generally I try to avoid it. I would write this by lifting out these two matches into functions called fmt_locality and fmt_uniqueness and then call them using %a. I'd also avoid using fst and snd by replacing let consts with let locality, uniqueness.

;
fun_binding:
strict_binding
fun_binding_gen(wrap):
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.

I think we should spend a bit more time trying to find a simpler solution to this part. The simplest approach that might work would be to separate out the list of parameters from the final EQUAL seq_expr or type_constraint EQUAL seq_expr in a strict_binding. That would allow the type_constraint to get treated differently depending on context. Probably that will require adding a type like:

type parameter =
  | Ordinary of arg_label * expression option * pattern
  | Type of Longident.t list

to represent the parameters before they are folded onto the body expression.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Flagging this as not implemented as of 0d0c78b

Copy link
Copy Markdown
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

I've looked over all of parsing and typing again

Exp.mk ~loc:Location.none (Pexp_extension(local_ext_loc, PStr []))

let mkexp_stack ~loc exp =
let mkexp_stack ~loc exp =
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.

Looks like an accidental whitespace change

(* the GNU Lesser General Public License version 2.1, with the *)
(* special exception on linking described in the file LICENSE. *)
(* *)
(**************************************************************************)
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.

Leaving it like this is fine.

typing/mode.ml Outdated
let newvar () = Amodevar (fresh ())

let newvar_below = function
| Amode c when L.eq_const c L.min_const -> Amode L.min_const, false
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.

Amode L.min_const is already available as min_mode.

typing/mode.ml Outdated
let newvar_below { locality; uniqueness } =
let l = Locality.newvar_below locality in
let u = Uniqueness.newvar_below uniqueness in
{ locality = fst l; uniqueness = fst u }, snd l || snd u
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.

Better to use patterns to destruct tuples than to use projections

| Some Global -> Olm_global
| Some Local -> Olm_local
| None -> Olm_unknown
and uniqueness = match uniqueness with
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.

Gratuitous and

and the update is not unique *)
let is_unique_with = has_unique_attr_exp sexp in
let expected_mode = if is_unique_with
then mode_unique (mode_subcomponent expected_mode).mode
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 doesn't need the mode_subcomponent. That is needed when we do an allocation, but here we are really doing a mutation.

in
let closed = (opt_sexp = None) in
let rmode = Value_mode.regional_to_global expected_mode.mode in
let rmode = Mode.Value.regional_to_global expected_mode.mode in
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 mode needs to be different if this is a { unique_ ... with ... } expression. The values used for these fields might be escaping an unknown number of regions, just like for a mutation. That means that they will need to be at a global mode.

| Global -> Value_mode.global
| Nonlocal -> Value_mode.local_to_regional rmode
| Global -> Mode.Value.global (* lbl_global==Global also implies Shared *)
| Nonlocal -> Mode.Value.local_to_regional rmode
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.

lbl_global==Nonlocal should also imply Shared

| Nonlocal -> Mode.Value.local_to_regional rmode
| Unrestricted -> rmode
in
let mode, _ = Mode.Value.newvar_above mode in
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.

We shouldn't bother making this new mode variable if the label is global. Maybe we should also only do it if the srecord is an ident or a projection from an ident (or a projection from a projection from ...).

We should also only make a variable for the uniqueness part, rather than for the whole mode.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Maybe we should also only do it if the srecord is an ident or a projection from an ident (or a projection from a projection from ...).

I have not implemented this.

if has_local || has_unique then begin
let mode = match has_local, has_unique with
| true, true -> Mode.Alloc.local_unique
| true, false -> Mode.Alloc.local
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.

I think that this case (and the one below) is too restrictive. It should probably have a variable for its uniqueness. That will allow code like the following (maybe put this in the testsuite):

let foo : local_ unique_ string -> unit = fun (local_ s) -> ()
let bar : local_ unique_ string -> unit = fun (unique_ s) -> ()

The additional variable (and the assert false below) could also be avoided by pulling the equality constraint up into these branches and using more specific equality functions (e.g. using equality on just locality rather than the whole mode).

Copy link
Copy Markdown
Contributor

@lpw25 lpw25 left a comment

Choose a reason for hiding this comment

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

I made a review pass over the middle-end and back-end parts.

They could be Alloc_local, but that would require changes
to pattern typing, as patterns can close over them. *)
Lprim (Pmakeblock (Obj.object_tag, Immutable_unique, None, alloc_heap),
Lprim (Pmakeblock (Obj.object_tag, Immutable, None, alloc_heap_unique),
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.

I think this part should be reverted. For annoying reasons that may go away one day Immutable_unique is not quite the same as Immutable and alloc_heap_unique.

| Alloc_shared

(* CR-soon: This can be private once it is possible to pattern-match on private aliases *)
type alloc_mode = locality_mode * uniqueness_mode
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.

I think that the alloc_modes in Lsend, lfunction and lambda_apply can all be replaced by just locality_modes. This is because they are all related to the allocation mode of closures, and closures can't actually be mutated even if they are unique. I think the uniqueness part of those modes is currently just being ignored.

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.

The one in Assignment can also probably just be a locality_mode. I don't think uniqueness affects the write barrier.

@@ -553,7 +555,7 @@ and transl_exp0 ~in_new_scope ~scopes e =
Lprim (Pmakearray (kind, Immutable, mode), ll,
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.

I think that the mode here should probably always be global_shared. This value can't be local and it will never be used uniquely: it is only used as a template for making new values of mode mode.

let maybe_unique_update_on =
match opt_init_expr with
| None -> None
| Some exp -> match Builtin_attributes.has_unique exp.exp_attributes with
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.

It would be good to have opt_init_expr already have some explicit data saying whether the update is a unique one. It's best if we only actually check for attributes once in the type-checker, and after that use normal types and data.

(fun (_, r) -> match r with
| Reuse_keep -> false
| Reuse_set _ -> true)
(List.combine ll reuses)) in
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 can use List.filter_map to make this simpler.

| Ok false | Error _ -> None
| Ok true -> Some (transl_exp ~scopes exp) in
let is_unique_update_on =
match maybe_unique_update_on with | None -> false | Some _ -> true in
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.

I think it would be better to just have is_unique_update_on and get rid of maybe_unique_update_on.

Lprim(Pmakeblock(0, mut, Some (Pgenval :: shape), mode),
slot :: ll, loc)
end
| Some id ->
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.

I think it would be better to use Lvar init_id in place of id and then bind that to the init expression, just like in the non-unique case.

begin match opt_init_expr with
None -> assert false
begin match opt_init_expr, maybe_unique_update_on with
| None, _ | _, Some _ -> lam
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.

So this case would become just | None -> and not match on maybe_unique_update_on

| Calloc Alloc_heap -> "alloc" ^ location d
| Calloc Alloc_local -> "alloc_local" ^ location d
| Calloc (Alloc_heap, _) -> "alloc" ^ location d
| Calloc (Alloc_local, _) -> "alloc_local" ^ location d
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.

I think that we probably can just use locality_mode rather than alloc_mode for everything in cmm. That's because there is no lifting of allocations after this point, so we only need to know whether to put them on the stack or the heap.

| Ialloc { bytes = n; mode = Alloc_heap, _ } ->
fprintf ppf "alloc %i" n;
| Ialloc { bytes = n; mode = Alloc_local } ->
| Ialloc { bytes = n; mode = Alloc_local, _ } ->
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.

As with cmm, we probably only need locality_mode in the mach IR.

@anfelor
Copy link
Copy Markdown
Author

anfelor commented Sep 9, 2022

To release this the following items should be completed:

  1. There is currently a bug in matching.ml where matches on record fields are bound to a Llet(Alias, _, _, _, _). These let-aliases can be inlined to the place where the bound variable is used if the bound variable is used only once. However, this usage site might come after an overwrite of the record by the { unique_ x with ... } construct. As such, this is unsound and can lead to bugs. Equally, later stages of the compiler might do similar transformations and it should be checked that that is disabled for unique values. To fix the first bug, however, it suffices to make the handling of unique fields in matching.ml more similar to that of mutable fields (grep for lbl_mut).

  2. With uniqueness analysis the order of evaluation matters. In the spirit of point one, consider ({ unique_ x with foo = ... }, x.foo). Thanks to Ocaml's right-to-left evaluation order this is actually sound -- the field foo of x is accessed before it is overwritten for the first tuple entry. However, we should not codify this evaluation order in the uniqueness analysis. As such, all occurrences of fold_right in the current uniqueness analysis should be changed to pass the uenv to a map and later merge.

  3. We currently do not mark borrowed contexts as shared that also contain a unique use of the borrowed variables (e.g. in a separate branch). While this is sound, it can be surprising to users why their code should fail when they delete a branch. See the test borrow_overwritten_by_once in unique.ml as an example. This can be fixed by constraining the current context as global as soon as we see a borrowed use.

@riaqn riaqn mentioned this pull request Feb 3, 2023
antalsz pushed a commit to antalsz/ocaml-jst that referenced this pull request Mar 29, 2023
Extends Lambda.function_kind to track modes of curried functions.
The supported modes have a heap-returning prefix, followed by a
local-returning suffix, both of which may be empty. Functions which
do not fit this pattern cannot be expressed as a single Lfunction,
and must be explicitly curried in Lambda and Clambda. (See the new
Lambda.check_lfunction for the exact invariants)

Most of the changes are straightforward plumbing of the new info.
Some delicate changes are needed to preserve the new invariants, in:

  - simplif.ml: optimisations which combine Lfunctions
  - translcore.ml: transl_curried_function and friends
  - closure.ml: optimisations for partial and over-application
  - cmm_helpers.ml: mode-aware variants of caml_curry

Caveat: build_apply in translcore is wrong
@lpw25
Copy link
Copy Markdown
Contributor

lpw25 commented Aug 18, 2023

Closed in favour of #138, which has now been merged in the other repo. We still need to add borrowing and support for in-place record update.

@lpw25 lpw25 closed this Aug 18, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants