Skip to content

Allow value slots (closure environment entries) to be of non-Value kind#924

Closed
mshinwell wants to merge 1 commit intooxcaml:mainfrom
mshinwell:flambda2-value-slots-not-of-kind-value
Closed

Allow value slots (closure environment entries) to be of non-Value kind#924
mshinwell wants to merge 1 commit intooxcaml:mainfrom
mshinwell:flambda2-value-slots-not-of-kind-value

Conversation

@mshinwell
Copy link
Copy Markdown
Collaborator

I quickly sketched this out to see whether there were likely to be any problems inside Flambda 2; it seems ok. We would need something like this for parts of the forthcoming unboxed types proposal (e.g. naked floats being passed to functions, which could cause them to appear inside closures after partial applications).

I have an idea for the closure representation part of this which avoids changing the runtime. It involves segregating value slots into ones of Value kind, and everything else. This is easy now that the Flambda 2 terms give the kinds. Then the non-Value ones would be put in fields after the last code pointers, immediately prior to the startenv point. I think this will avoid disturbing the placement of any code pointers, yet also conceal these non-scannable fields from the GC. (We could also, indeed, put any value slots known to be of kind Value but subkind Tagged_immediate in the non-scannable area.)

So the question is @Gbury : how difficult would it be to adapt Slot_offsets for such a layout?

This would be needed for Closure and Flambda 1 too.

@mshinwell mshinwell added the flambda2 Prerequisite for, or part of, flambda2 label Oct 27, 2022
@mshinwell mshinwell marked this pull request as draft October 27, 2022 14:27
@Gbury
Copy link
Copy Markdown
Contributor

Gbury commented Oct 27, 2022

It should be fairly easy: as it stand we already kind of do something similar with function slots and value slots: we first find offsets for all function slots first (starting with the smallest offsets), and then for all value slots, and we check that no function slots occurs after a value slot. So we would only need to extend that to work with function slots, then value slots of non-value kinds, and then value slots of kind value (and adapt the computation of the start of env, which would be really simple).

@goldfirere
Copy link
Copy Markdown
Collaborator

I'm not well placed to review this, but it's encouraging to see the ease with which this was put together. Thanks, Mark!

@mshinwell mshinwell linked an issue Oct 28, 2022 that may be closed by this pull request
Copy link
Copy Markdown
Contributor

@lthls lthls 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 made a few comments. The main one is that I don't think the changes to the types directory are useful, as we already have kinds in the types themselves.

index
| Project_value_slot { project_from = _; value_slot } ->
| Project_value_slot { project_from = _; value_slot; kind = _ } ->
(* CR mshinwell: could use [kind]? *)
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 could be used either for a consistency check (that the kind of the result type is the expected one), or for adding subkind constraints. However, subkind constraints would better fit at the closure creation time, not on projection.

Simple.pattern_match value_slot
~const:(fun _ -> T.alias_type_of K.value value_slot)
~const:(fun _ ->
T.alias_type_of K.value value_slot, kind_with_subkind)
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.

Suggested change
T.alias_type_of K.value value_slot, kind_with_subkind)
T.alias_type_of kind value_slot, kind_with_subkind)

meet_generic_product env ~components_by_index1 ~components_by_index2
~union:Value_slot.Map.union
meet_generic_product env ~components_by_index1:components_by_index1'
~components_by_index2:components_by_index2' ~union:Value_slot.Map.union
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 instead of splitting off the kinds, you could have just defined a custom union:

let union f m1 m2 =
  Value_slot.Map.union
    (fun value_slot (ty1, kind1) (ty2, kind2) ->
       if not (K.with_subkind.equal kind1 kind2) then Misc.fatal_errorf ...;
       Option.map (fun ty -> ty, kind1) (f value_slot ty1 ty2))
    m1 m2
in ...

let kind =
match component1, component2 with
| Some (_, kind1), Some (_, kind2) ->
(* CR mshinwell: is this too strong? *)
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.

In theory, we could return Bottom too; but I think we can rely on the invariant that a given value slot can only hold values of a single kind (globally).

{ value_slot_components_by_index : t Value_slot.Map.t }
{ value_slot_components_by_index :
(t * Flambda_kind.With_subkind.t) Value_slot.Map.t
}
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's not clear to me why it is useful to add the subkind here. The type already contains a kind.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

From Slack (from @lthls ): ... the layout computation only looks at the set of closure creations, so as long as we have full kinds and subkinds there we should be fine. If we started reifying closures it could be useful, but I'm not convinced it would be necessary even in this case

@stedolan
Copy link
Copy Markdown
Contributor

stedolan commented Nov 1, 2022

This idea makes sense to me! The runtime should be OK with putting non-value data in the code pointer section of closures, except for this part of the compactor - I think that's the only spot where the runtime iterates over the infix code pointers of a closure. (This shouldn't be hard to change, but will need a bit of work. For development, you can just test with compaction disabled)

@mshinwell
Copy link
Copy Markdown
Collaborator Author

@stedolan is proposing to have a magic word prior to the unboxed part of the environment, which can be used for compaction. So from the middle end point of view it's very similar.

@mshinwell
Copy link
Copy Markdown
Collaborator Author

mshinwell commented Nov 1, 2022

I thought about the compactor problem a bit more. Two suggestions:

  1. use a non-grey Infix_tag header value as the magic word, with the Infix_offset_val part set to zero. A small change to the compaction code can then watch out for this.
  2. (maybe better as it doesn't waste space) Take a bit out of the "closinfo" words to indicate whether the current sub-part of a Closure_tag value is the last function slot or not. Especially on 64-bit platforms, the startenv values in the closinfo words are very wide, and reducing by one bit won't harm. (See Add is_last flag to closinfo words #938)

@mshinwell
Copy link
Copy Markdown
Collaborator Author

@Ekdohibs I'm going to close this, but please pull this into #925; we can address Vincent's comments there.

@mshinwell mshinwell closed this Nov 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

flambda2 Prerequisite for, or part of, flambda2 unboxed types

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Closures can have value slots not of kind Value (Closure + Flambda 1 + Flambda 2)

5 participants