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

Contract elision for static types #1671

Merged
merged 11 commits into from
Oct 10, 2023
Merged

Conversation

yannham
Copy link
Member

@yannham yannham commented Oct 9, 2023

Context

As part of #1622, metrics show that the base used for benchmarks is performing an awful lot of array contract applications and their derived subcontracts, in particular polymorphic contracts. This is probably due to the usage of stdlib functions, which are all typed with a polymorphic type.

Indeed, consider std.record.map, whose type is forall a b. (a -> b) -> Array a -> Array b. If we apply it to a 5000 elements array, this will cause the execution of 20 000 small contracts: one to seal all elements of the array argument with a, one to unseal the same elements when f is applied (corresponding to _a_ -> b), one sealing the result of the application to b (the b in a -> _b_), and finally one that will map the unsealing key over the resulting array.

What's sad is that the function is statically typechecked. Thus, under the assumption that the type system is reasonably behaved (blame safety), the polymorphic contract can never blame. In the same spirit, it's not useful to check anything on the return value of the function: the typechecker already proves that it must be Array b.

I discussed contract elision rules in #285, which is a bit different, but related. This PR follows the same idea overall: use the guarantees of the static type system to elide some of the dynamic checks.

Content

This PR applies the following rewriting when generating the contract coming from a static type annotation:

  • forall in positive position are removed, and their bound variable is substituted for Dyn. That is, forall a b. (a -> b) -> Array a -> Array b is rewritten to (Dyn -> Dyn) -> Array Dyn -> Array Dyn.
  • first-order contracts (anything but a function type) in positive position are substituted for Dyn as well. Indeed, according to blame safety, a well-typed function can never be blamed, thus positive occurrences of contracts can never fail. For map, we get after this second transformation (Dyn -> Dyn) -> Array Dyn -> Dyn

Those optimizations are coupled with the following specializations, which aren't restricted to static type annotations:

  • Dyn -> Dyn is specialied to func_dyn, Dyn -> T to func_dom T, etc. That is, the general function contract is specialized when the domain, the codomain or both are Dyn, to eschew useless checks.
  • Similarly, Array Dyn is specialized to a constant-time contract $array_dyn which just checks that its argument is an array (indeed, $array $dyn maps $dyn and is thus linear time - not when applied, thanks to lazy array contracts, but still when the array is finally forced)
  • Similarly, {_ : Dyn} and {_ | Dyn} are specialized to $dict_dyn.

Those two changes combined give even better results: once the type has been rewritten, many specialized version of builtin contracts can be used. On our map example, the final contract becomes $func $func_dyn ($func_dom $array_dyn). This contract only checks that the first argument to map is a function and the second is an array, all in constant time. From 4n contract checks, we are now consistently applying 3 checks (we still check that map is a function, which is useless - but it's not very important and can be improved upon later).

Result

Tested on the codebase of #1622, this gave a consistent 50% reduction in running time, which is encouraging. I will test it on kav (#1667) as well.

@github-actions github-actions bot temporarily deployed to pull request October 9, 2023 18:56 Inactive
@github-actions github-actions bot temporarily deployed to pull request October 9, 2023 19:12 Inactive
core/src/eval/mod.rs Outdated Show resolved Hide resolved
core/src/eval/mod.rs Outdated Show resolved Hide resolved
core/src/typ.rs Outdated Show resolved Hide resolved
core/src/typ.rs Outdated Show resolved Hide resolved
core/stdlib/std.ncl Outdated Show resolved Hide resolved
Base automatically changed from fix/record_insert to master October 10, 2023 07:43
@github-actions github-actions bot temporarily deployed to pull request October 10, 2023 07:46 Inactive
@yannham yannham force-pushed the optimization/contract-elision branch from 7d2622c to 4561e88 Compare October 10, 2023 08:02
@github-actions github-actions bot temporarily deployed to pull request October 10, 2023 08:06 Inactive
@github-actions github-actions bot temporarily deployed to pull request October 10, 2023 09:14 Inactive
@github-actions github-actions bot temporarily deployed to pull request October 10, 2023 09:41 Inactive
@yannham yannham added this pull request to the merge queue Oct 10, 2023
@yannham yannham linked an issue Oct 10, 2023 that may be closed by this pull request
Merged via the queue into master with commit a80c20d Oct 10, 2023
5 checks passed
@yannham yannham deleted the optimization/contract-elision branch October 10, 2023 10:28
@yannham yannham mentioned this pull request Mar 26, 2024
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.

Contract elision for static type annotations
2 participants