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

Stop Simplif from causing disagreement between runtime arity and syntatic arity of a function #12496

Merged

Conversation

ncik-roberts
Copy link
Contributor

@ncik-roberts ncik-roberts commented Aug 23, 2023

A stated goal of the Syntactic Arity RFC (ocaml/RFCs#32) was for runtime arity to match syntactic arity1. The implementation of syntactic arity in #12236 does not fully implement that goal (unintentionally). For example, even the motivating make_greeter example given in the RFC currently compiles as a function with runtime arity 3, though the spec given in the RFC suggests its runtime arity should match its syntactic arity of 2. This isn't a question of correctness but rather a question of performance: calling a function with runtime arity 2 is optimized at application sites to 2 syntactic arguments.

This PR fixes the implementation to match the RFC.

The piece we missed in #12236: Simplif looks for nested lfunctions and fuses their parameter lists. In order for runtime arity to match syntactic arity, this rewrite shouldn't happen for functions that correspond to fun/function terms that the user wrote.

Functors

Notably, this PR does not change how Simplif's parameter fusion rewrite applies to other constructs that compile to functions, like functors and class functions. The arity of these constructs are out of the scope of the original syntactic arity RFC and PR (though perhaps one day we'll want to follow the RFC's model for these constructs as well). Furthermore, basic experimentation shows that multi-argument functors rely on Simplif's rewrite to be given the "expected" arity; e.g. module Make (X1 : S) (X2 : S) = ... is only given an arity of 2 by virtue of this rewrite and would otherwise be a 1-ary function returning a 1-ary function.

If it's decided that it's not important to preserve this rewrite for functors/class functions, it would simplify this PR to just delete Simplif's rewrite instead of the current approach of excepting fun/function terms.

Review

To review this PR, I'd suggest reading the two commits in order. The first commit adds a regression test demonstrating the discrepancy, and the second commit fixes it and updates the test output.

Footnotes

  1. "Function definitions in OCaml are curried by default, in principle accepting one argument at a time and returning a closure accepting the rest. Since most function applications are saturated (that is, they pass the same number of arguments as the function definition expects), the implementation of functions is optimised to make saturated applications fast. [...] However, this optimisation requires knowing how many arguments a function definition expects. Since this information is not currently tracked in the parse tree, it must be guessed later, and sometimes this guess is wrong. [...] N-ary lambdas would make this issue go away, because the number of parameters of a function is known rather than inferred."

ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Sep 11, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Sep 12, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Sep 13, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Sep 14, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Sep 15, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
@damiendoligez damiendoligez added this to the 5.2 milestone Sep 20, 2023
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Oct 4, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Oct 5, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Oct 5, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Oct 5, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
lambda/lambda.mli Outdated Show resolved Hide resolved
lambda/translcore.ml Show resolved Hide resolved
@lthls
Copy link
Contributor

lthls commented Oct 12, 2023

I'm not a huge fan of using allocations to test arity.
You can get the runtime arity of a function using a bit of Obj:

let runtime_arity (f: _ -> _) =
  let clos_info = Obj.raw_field (Obj.repr f) 1 in
  let raw_arity =
    Nativeint.(to_int (shift_right clos_info (Sys.word_size - 8)))
  in
  if raw_arity < 0 then (* tupled function *) 1 else raw_arity

Do you think you could rewrite the test to use that instead ?

@ncik-roberts ncik-roberts force-pushed the maintain-syntactic-arity-in-simplif branch from 50f6b77 to ad63761 Compare October 16, 2023 16:24
@ncik-roberts
Copy link
Contributor Author

@lthls That seems reasonable enough. I've made that change. I force-pushed it so that any reviewer can still confirm that the test changes output as expected due to the code change.

ncik-roberts added a commit to ncik-roberts/flambda-backend that referenced this pull request Oct 17, 2023
Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)
Copy link
Contributor

@goldfirere goldfirere left a comment

Choose a reason for hiding this comment

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

This is ready to merge from my perspective. Does this need any more review before pulling the trigger?

Copy link
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.

The test file ended up more complex than I expected, but I'm fine with it (modulo a comment that I think is slightly wrong).
I have also looked at the PR itself and haven't found anything suspicious.

(* The "nested arity" of a value is either:
- the empty list, if the value isn't a function
- x :: xs if the value is a function [f], where [x] is [f]'s arity, and
[xs] is the nested arity of the result of applying [f] to a value.
Copy link
Contributor

Choose a reason for hiding this comment

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

When reading this sentence, it looks like fun _ _ -> 0 would have nested arity [Curried 2; Curried 1] (for some values of 2 and 1).
But I think what you actually compute is the expected [Curried 2]. Maybe we could replace to a value by to the number of values corresponding to its arity ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the correction.

@goldfirere goldfirere merged commit e5c693d into ocaml:trunk Oct 17, 2023
9 checks passed
ncik-roberts added a commit to ocaml-flambda/flambda-backend that referenced this pull request Dec 28, 2023
* Newtypes

* Constraint/coercion

* Add map_half_typed_cases

* Implement type-checking/translation

This also promotes tests whose output changes.

* Add upstream tests

Tests from:
  - ocaml/ocaml#12236 (and the corresponding updates to outputs found in ocaml/ocaml#12386 and ocaml/ocaml#12391)
  - ocaml/ocaml#12496 (not merged)

* Fix ocamldoc

* Update chamelon minimizer

* Respond to requested changes to minimizer

* update new test brought in from rebase

* Fix bug in chunking code

* `make bootstrap`

* Add Ast_invariant check

* Fix type-directed disambiguation of optional arg defaults

* Minor comments from review

* Run syntactic-arity test, update output, and fix printing bug

* Remove unnecessary call to escape

* Backport changes from upstream to comparative alloc tests

* Avoid the confusing [Split_function_ty] module

* Comment [split_function_ty] better.

* [contains_gadt] as variant instead of bool

* Calculate is_final_val_param on the fly rather than precomputing indexes

* Note suboptimality

* Get typecore typechecking

* Finish resolving merge conflicts and run tests

* make bootstrap

* Add iteration on / mapping over locations and attributes

* Reduce diff and fix typo in comment:

* promote change to zero-alloc arg structure

* Undo unintentional formatting changes to chamelon

* Fix minimizer

* Minimize diff

* Fix bug with local-returning method

* Fix regression where polymorphic parameters weren't allowed to be used in same parameter list as GADTs

* Fix merge conflicts and make bootstrap

* Apply expected diff to zero-alloc test changed in this PR
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.

None yet

4 participants