Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

with-kinds #3284

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open

with-kinds #3284

wants to merge 23 commits into from

Conversation

goldfirere
Copy link
Collaborator

@goldfirere goldfirere commented Nov 18, 2024

This adds with-kinds to the compiler.

It is a very preliminary version. Importantly, you should not use an actual with-kind in your code and expect anything to work. Instead, this just provides inferred with-kinds, but that should be sufficient for looking through lists, options, and arrays.

A rebase of this work (at https://github.com/ocaml-flambda/flambda-backend/tree/aspsmith/with-kinds-on-portable-stdlib) has been observed to build Base and Core, and this passes all the tests in the compiler testsuite. It is thus ready for review.

It is strongly suspected not to be performant enough for merging, though this fact has not been adequately assessed. I expect to follow this PR with another one to improve its performance. Search for an internal doc titled "Normalization for with-kinds" for more thoughts on how to do this. But nevertheless, I think this PR is now (Dec 24 '24) ready for correctness review.

Review is roughly by commit, as I enumerate:

And that's everything.

Now to break down the review of the main payload. The high-level overview is that much of this code was actually written by @liam923 and @riaqn in an early version of with-kinds. They should review. I break down where I have taken their code essentially as-is and where I have changed it (necessitating further review). The places where I took the code as-is do not need further review, as I reviewed the code as I adapted it. You can find your previous work at https://github.com/goldfirere/flambda-backend/pull/3/files.

  • In jane/doc: @riaqn. These are updates to the uniqueness documentation in light of with-kinds.
  • In parsing: @liam923. Just some comments.
  • In testsuite: @liam923. You're the most familiar with tests here -- as well as the expected use of with-kinds in base and core. I believe you probably already reviewed with.ml while you were writing new tests.
  • In tools: @liam923. This is a tiny change. (The reason for the eta-expansion is to avoid the value restriction.)
  • ctype.ml/ctype.mli: I think @riaqn. (I'm not sure exactly which of you did which pieces.)
    • instance_parameterized_kind, instance_declaration, jkind_subst, and is_principal are unchanged from your previous work.
    • estimate_type_jkind just has a small tweak compared to previous work (but would benefit from quick review)
    • type_jkind_purely_if_principal is unchanged
    • constrain_type_jkind just uses a different approach to getting type_equal than previously; pretty boring.
    • check_decl_jkind and constrain_decl_jkind have been moved to the bottom of the file so that they could access type_equal (before I made the ref). Maybe this decision should be reversed.
    • Changes in intersect_type_jkind and below are new; please review with care.
  • includecore.ml: @riaqn. The mode-cross stuff could use your expertise (though looks straightforward). The change in type_declarations is harder, but I already went over the basic idea with @ccasin (who agrees the approach is sound).
  • jkind_axis.ml/jkind_axis.mli: @riaqn. This is very directly inspired by previous work, but I added a bunch more functions to Axis_collection.
  • jkind_types.ml/jkind_types.mli: @riaqn. This is mostly boilerplate-y, but there is some stuff with left/right logic that you are the expert in.
  • jkind.ml/jkind.mli/printtyp.ml: Some combination of @liam923 and @riaqn (please work this out).
    • The biggest piece needing careful scrutiny is Bound.reduce_baggage, which is a completely fresh reimplementation of reduce_bound from previous work.
    • Changes to of_user_written_annotation_unchecked_level are very close to prior work; compare the two to reduce review burden.
    • Changes to add_portability_and_contention_crossing were a bit tricky in order to try to get decent behavior from this feature.
    • Changes to for_boxed_record, for_boxed_variant are new and deserve scrutiny. These functions power the inference of with-kinds; an error here could easily lead to unsoundness.
    • The round_up function is new; see usage sites for more context.
    • The new sub_jkind_l function is horrid, but still needs to be sound. It will get improved later yet deserves a careful read-through here, too.
    • Other changes in the file also deserve a read, of course, but they should hopefully be not too hard.
  • mode.ml/mode_intf.mli: @riaqn. This is just some refactoring.
  • predef.ml/predef.mli: Either @liam923 or @riaqn. This is basically all new (but easy).
  • subst.ml: Either @liam923 or @riaqn. This was easier than prior work due to Use constant sorts in transl #3266.
  • typecore.ml: Probably @riaqn. These changes are straightforward adaptations to the new types exposed by Jkind.
  • typedecl.ml/typedecl.mli: Not sure. I recommend either @liam923 or @riaqn take a spin, but if the going gets tough, let's loop in @ccasin. This file was hard, as you will see by the trail of comments I have left.
  • typemode.ml/typemode.mli: Whoever did this the first time around. I basically just adapted the prior work here. Only light review required.
  • misc.ml/misc.mli: Anyone. I guess this is what happens when Jane Street hires a Haskeller.
  • Other files (datarepr.ml, typedecl_separability.ml, typemod.ml, typeopt.ml, types.ml, types.mli): Anyone. These are all small changes.

Thanks, all!

Copy link

github-actions bot commented Nov 18, 2024

Parser Change Checklist

This PR modifies the parser. Please check that the following tests are updated:

  • parsetree/source_jane_street.ml

This test should have examples of every new bit of syntax you are adding. Feel free to just check the box if your PR does not actually change the syntax (because it is refactoring the parser, say).

@goldfirere goldfirere force-pushed the rae/const-sorts-in-transl branch from ab43d23 to 540cc23 Compare November 26, 2024 14:49
@goldfirere goldfirere force-pushed the rae/with-kinds branch 2 times, most recently from 07eb6ed to a0096f3 Compare December 2, 2024 16:38
@goldfirere goldfirere force-pushed the rae/const-sorts-in-transl branch from 540cc23 to 05c3424 Compare December 13, 2024 20:39
Base automatically changed from rae/const-sorts-in-transl to main December 13, 2024 21:14
@goldfirere goldfirere marked this pull request as ready for review December 24, 2024 16:52
@goldfirere
Copy link
Collaborator Author

This is now ready for review. 🎉 🎁 🎄 I have updated the top comment with a review plan, involving @rtjoa, @ccasin, @liam923, and @riaqn (and a tiny bit for @lpw25). @dkalinichenko-js could likely do some review, too, but given the fact that @liam923 and @riaqn have already taken a spin at the implementation here, it seems to make the most sense to have them review. But if the review burden here seems too high, looping in @dkalinichenko-js is a possible way to spread it out.

typing/typedecl.ml Outdated Show resolved Hide resolved
This seems to be what we want; there was a comment here saying we
shouldn't have any baggage but I think that was incorrect. And in fact,
using Ctype.type_equal to check type equality makes a number of tests
pass that we had marked as expecting to pass.
@glittershark
Copy link
Member

glittershark commented Dec 28, 2024

testsuite/tests/typing-jkind-bounds/basics.ml Show resolved Hide resolved
testsuite/tests/typing-jkind-bounds/composite.ml Outdated Show resolved Hide resolved
testsuite/tests/typing-jkind-bounds/composite.ml Outdated Show resolved Hide resolved
testsuite/tests/typing-jkind-bounds/composite.ml Outdated Show resolved Hide resolved
testsuite/tests/typing-jkind-bounds/variants.ml Outdated Show resolved Hide resolved
Comment on lines +1 to +14
(* TEST
readonly_files = "shadow_stdlib.ml bug.ml ref.ml";
setup-ocamlc.byte-build-env;
module = "ref.ml";
flags = "-allow-illegal-crossing";
ocamlc.byte;
module = "shadow_stdlib.ml";
flags = "";
ocamlc.byte;
flags = "-allow-illegal-crossing";
module = "bug.ml";
ocamlc.byte;
check-ocamlc.byte-output;
*)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm surprised that the -allow-illegal-crossing flag has anything to do with this error since illegal crossing isn't actually used anywhere. But I do agree that the error only occurs when the flag is included. Do you know what's going on here? I had thought that maybe the check in Type_decl.check_kind_coherence that happens only when illegal crossing is enabled was responsible, but based on testing, that doesn't seem to be the case. This leads me to the conclusion that add_portability_and_contention_crossing isn't a no-op when no annotation is written, which I didn't expect.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

type_jkind sometimes gets stuff other than an annotation. I don't think it's worth the archaeology here, especially because -allow-illegal-crossing is on the chopping block.

utils/misc.ml Outdated Show resolved Hide resolved
utils/misc.ml Outdated Show resolved Hide resolved
typing/jkind_axis.mli Outdated Show resolved Hide resolved
Copy link
Contributor

@rtjoa rtjoa left a comment

Choose a reason for hiding this comment

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

The changes in 8814e2c and the non-test changes in 25eadb9 look good to me. I'd appreciate if someone more familiar with the guts of the PR could take a second look at the test changes in 25eadb9, which mostly seem to reflect changes from other commits.

Copy link
Contributor

@rtjoa rtjoa left a comment

Choose a reason for hiding this comment

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

6770037 looks good, glad to see type_jkind_deep gone.

typing/jkind.mli Outdated Show resolved Hide resolved
typing/predef.ml Outdated Show resolved Hide resolved
typing/predef.ml Show resolved Hide resolved
@goldfirere
Copy link
Collaborator Author

goldfirere commented Jan 9, 2025

Thanks for the review so far! I've handled the comments above. There is a little more review then:

Thanks!

@goldfirere
Copy link
Collaborator Author

It looks like the checkmarks in my overview might not have gotten noticed. In particular, I believe @riaqn and @rtjoa have some check-marks to click. Thank you!

@liam923
Copy link
Contributor

liam923 commented Jan 10, 2025

@goldfirere Please review:

|}]

type ('a, 'b) t : value mod portable uncontended = { a : 'a; b : 'b }
[%%expect {|
type ('a, 'b) t : value mod uncontended portable = { a : 'a; b : 'b; }
type ('a, 'b) t : immutable_data = { a : 'a; b : 'b; }
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems wrong - t should only cross linearity with 'a and 'b. Unless it's just the printing that's wrong? If it's just the printing that's wrong, could you add a test that demonstrates that?

There's more instances of this in the file, but I'm not going to comment on those.

Comment on lines 12 to 16
type b : value mod portable = { a : int; b : int }
[%%expect {|
type a
type b : value mod portable = { a : int; b : int; }
type b = { a : int; b : int; }
|}]
Copy link
Contributor

Choose a reason for hiding this comment

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

This test is no longer testing the thing it was meant to test. b is meant to have a type that is normally inferred to not be portable. So I think the ints should be replaced with a type that doesn't cross portability.

The same is true for most of the tests in this file. But maybe this isn't worth the effort of fixing, because:

  1. We plan to remove illegal crossing very soon, and I believe @glittershark plans to delete this file rather than incorporating it into the trust-me annotation tests.
  2. I see that you've added some tests where the inferred type does not cross.

|}]
(* CR layouts v2.8: this should be accepted *)
Copy link
Contributor

Choose a reason for hiding this comment

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

It's very satisfying to see these crs go away

type 'a t4_val
type t4 = M4.s t4_val
|}]
(* CR layouts v2.8: The appearance of [immutable_data] there is just wrong. *)
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm pleasantly surprised by this output - I think this is right? In F4, s should have kind immutable_data with X.t. And then after the functor application, X.t is t = T, which is immediate. So M4.s really is immutable_data

type 'a t4_val
type ('a : void) t4_void
type t4 = M4.s t4_val
|}]
(* CR layouts v2.8: The appearance of [immutable_data] there is just wrong. *)
Copy link
Contributor

Choose a reason for hiding this comment

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

For the same reason as the test in modules.ml, I think this is right

| No_baggage, b -> b
| b, No_baggage -> b (* CR layouts v2.8: List concatentation is slow. *)
| Baggage (ty1, tys1), Baggage (ty2, tys2) ->
Baggage (ty1, tys1 @ (ty2 :: tys2))
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this a good enough excuse for use to create Misc.Nonempty_list? Feel free to tell me no.

Copy link
Member

Choose a reason for hiding this comment

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

I did so in a child PR


(** Map a function over types in [upper_bounds] *)
val map_type_expr :
(Types.type_expr -> Types.type_expr) -> (allowed * 'r) t -> (allowed * 'r) t
Copy link
Contributor

Choose a reason for hiding this comment

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

The impl is already polymorphic over lr-ness - may as well make the signature be too, even if it's not used.

val round_up :
type_equal:(Types.type_expr -> Types.type_expr -> bool) ->
jkind_of_type:(Types.type_expr -> jkind_l option) ->
(allowed * 'r) t ->
Copy link
Contributor

Choose a reason for hiding this comment

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

The impl doesn't require the input to be a left - maybe remove that constraint from the sig?

Comment on lines +1497 to +1500
(* CR layouts v2.8: This is sad, but I don't know how to account for
existentials in the baggage. See doc named "Existential
baggage". *)
then Builtin.value ~why:Boxed_variant
Copy link
Contributor

Choose a reason for hiding this comment

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

We could probably make some cases happier by only doing this when there are existentials. But I don't think we have a compelling reason to do this yet, so that can be future work.

(fun (lbl : Types.label_declaration) -> add_baggage ~baggage:lbl.ld_type)
lbls jkind

(* CR layouts v2.8: This should take modalities into account. *)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is implicitly making the assumption that modalities can only strengthen a kind, not weaken it. Otherwise it would be unsound to ignore them.

I and @glittershark agree that it is currently true, and seems that it will be true for the foreseeable future. I just want to flag that if that were to ever change, this is unsafe.

let jkind_of_type = type_jkind_purely_if_principal env in
let jkind1 = Jkind.round_up ~type_equal ~jkind_of_type jkind1 in
let jkind2 = Jkind.round_up ~type_equal ~jkind_of_type jkind2 in
Jkind.intersection_or_error ~type_equal ~jkind_of_type ~reason jkind1 jkind2
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't understand what's going on here, but an alarm bell is going off in my head because we're rounding up and then intersecting, which seems like it would accept things that it shouldn't. Is this intentionally permissive?

@liam923
Copy link
Contributor

liam923 commented Jan 13, 2025

As a meta-point, I think someone should review that the review tasks @goldfirere assigned are exhaustive. That is, make sure all changes are actually getting reviewed. I think jkind_intf, for example, did not get assigned anywhere (I have not reviewed that file).

let failure = Error (Violation.of_ (Not_a_subjkind (sub, super))) in
(* First try normal subjkinding, if the right-hand jkind has the right
shape. *)
match try_allow_r super with
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this is redundant with the try_allow_r that happens axis-wise

let baggage1 = Baggage.as_list bound1.baggage in
let baggage2 = Baggage.as_list bound2.baggage in
let modifiers =
Bound_ops.equal bound1.modifier bound2.modifier
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't this be le, not just equal? (although this would only matter in the rare case of checking _ mod external_ with t <= _ mod external64 with t )

Bound_ops.equal bound1.modifier bound2.modifier
in
let baggages =
List.compare_lengths baggage1 baggage2 = 0
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe leave a comment about why you're not using List.compare so that this optimization doesn't accidentally get removed in the future.

| Less | Equal ->
Ok { sub with history = combine_histories Subjkind (Pack sub) (Pack super) }
| Not_le -> Error (Violation.of_ (Not_a_subjkind (sub, super)))
let sub_jkind_l ~type_equal ~jkind_of_type sub super =
Copy link
Contributor

Choose a reason for hiding this comment

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

A special case that I don't think is being handled is when sub is has the min modifier with no baggage, in which case it's always successful. I'm not sure if this case would really get us much, but it seems cheap.

in
let baggages =
List.compare_lengths baggage1 baggage2 = 0
&& List.for_all2 type_equal baggage1 baggage2
Copy link
Contributor

Choose a reason for hiding this comment

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

There's redundant type equality checks happening between axes - avoiding so would presumably help performance. But I presume @glittershark 's upcoming change that transposes the representation will do this, so I guess we can leave it.

Copy link
Member

Choose a reason for hiding this comment

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

yep

Continue
{ t with constr = Path.Map.add p (fuel - 1, args) constr }
else Stop)
| Tvar _ | Tarrow _ | Tunboxed_tuple _ | Tobject _ | Tfield _ | Tnil
Copy link
Contributor

Choose a reason for hiding this comment

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

Should Tunboxed_tuple be treated similarly to Ttuple? Or does it not have the same recursion issue?

@@ -200,21 +174,34 @@ module Axis = struct
| Modal Contention -> "contention"
| Nonmodal Externality -> "externality"
| Nonmodal Nullability -> "nullability"

let is_deep (type a) : a t -> bool = function
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe rename this to is_modal? I.e., our modes are by default deep, and given the current axes as samples, the distinction seems to be between modal vs nonmodal? Do we have imaginary axes violating that?

This is also a good place to insert a CR saying that Externality is in fact Modal and will be made so in the future.

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.

6 participants