-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
[4.13 bug] %apply externals require an arrow type as first argument regardless of the actual type #10450
Comments
This was originally detected in the wild in
|
For the record, I find that the new behavior that breaks this program is reasonable: The program could be fixed by defining the |
Yes, significant. The code wasn't inlined without it. With |
For the reference, here is the library that uses the trick
where type
This won't work in the real application. The whole idea is to hide that |
It's not very satisfying, but you can get around the new restriction by using the
I think this should give you the same result as with |
Not sure if it will work. I still need to apply as
Which I presume will work. Will it? |
With external, it is compiled by 4.09.x on amd64 to,
Notice the absence of allocations.
|
If I read the manual correctly (I'm not an expert on this), you would still need explicit coercions everywhere, so I think this doesn't count as working.
|
I tried 4.13.0~alpha1 and got perfect results1 (with no allocations and all functions inlined) when the With that said, it looks like that #10081 changed the way how primitives (externals) work. Before that externals were untyped so that we can write 1)) I am really excited with that! |
Out of curiosity, does using |
I'm also curious to know whether the perfect results were obtained with Flambda, Closure (the non-flambda mode), or both. I'm surprised that Closure would actually perform that much inlining. |
Yes and no. On microbenchmarking yes, hundred times slower, as every addition turned into allocation, instead of two machine instructions. On my blackbox test, which was And I only tried 4.09 and 4.13. I can't say from what version the inlining started to work without having to rely on external.
I think that with flambda we always had good results, since the introduction of bitvec, which was 4.04. The perfect results with 4.13.0 were obtained for 4.13.0+alpha1, which, as far as I can tell doesn't include the flambda option. I can say that inlining appears already on clambda (and rawclamba) level (but not yet present on lambda), (sum8/206
(closure
(fun camlExample__sum8_206 2 x/208 y/209
(let
(prim/232 (field 0 (read_symbol camlExample))
prim/233
(let
(arg/220 y/209
arg/219 x/208
funct/222 (field 74 (read_symbol camlBitvec)))
(closure
(fun camlExample__fun_223 1 arg/221 env/228
(let
(y/229 (field 3 env/228)
x/230 (field 2 env/228)
x/231 (apply* camlZ__add_94 x/230 y/229))
(if
(|| (< (ml_z_sign x/231) 0)
(>= (ml_z_compare x/231 arg/221) 0))
(apply* camlZ__logand_139 x/231 arg/221) x/231)))
arg/219 arg/220 funct/222)))
(apply prim/233 prim/232))) )) |
The output you've shown looks like what I would expect out of Closure. The inner
|
And here is the code I get with my
|
Yep, indeed, I jumped to conclusion too fast, I looked into the closure, which is perfectly inlined itself, but didn't notice that the toplevel function allocates the closure before calling it. Indeed, there's still an allocation happening. Though still better than it was (before that |
Could you elaborate (or point me to your version or send a patch) on this trick, as I can't get it work. |
The initial idea was to apply the following patch both in - external (mod) : 'a m -> modulus -> 'a = "%apply"
+ type 'a mod_result = modulus -> 'a
+ external (mod) : 'a m -> 'a mod_result = "%identity" But with type substitutions (available since 4.08, if I remember correctly), we can get a slightly smaller patch: diff --git a/lib/bitvec/bitvec.ml b/lib/bitvec/bitvec.ml
index 5276462..d2cd5df 100644
--- a/lib/bitvec/bitvec.ml
+++ b/lib/bitvec/bitvec.ml
@@ -63,7 +63,7 @@ let norm {m} x =
inlining (with any other operator definition the vanilla
version of OCaml will create a closure)
*)
-external (mod) : 'a m -> modulus -> 'a = "%apply"
+external (mod) : 'a m -> 'a m = "%identity"
let modulus w = {
m = Z.(one lsl w - one);
diff --git a/lib/bitvec/bitvec.mli b/lib/bitvec/bitvec.mli
index f4495d8..2f522e0 100644
--- a/lib/bitvec/bitvec.mli
+++ b/lib/bitvec/bitvec.mli
@@ -41,7 +41,8 @@ val m64 : modulus
[(x+y) mod m] and won't create any closures, even if [m] is not
known at compile time.
*)
-external (mod) : 'a m -> modulus -> 'a = "%apply"
+type 'a mod_result := modulus -> 'a (* Work around arity checks *)
+external (mod) : 'a m -> 'a mod_result = "%identity"
module type S = sig
(** an abstract representation of an operation modulo some number.*) |
Apparently my trick requires a fairly recent version of OCaml (probably 4.13.0), otherwise |
That's not a problem! For versions before 4.13 we can use the apply primitive and for versions >= 13 we will be using the identity trick. It drives me crazy though that we just change |
In a way, |
If I understand correctly the conclusion, since 4.13 the type ('a,'b) fn
external app: ('a,'b) fn -> 'a -> 'b = "%apply" with type ('a,'b) fn
type ('a,'b) ocaml_fn := 'a -> 'b
external app: ('a,'b) fn -> ('a,'b) ocaml_fn = "%identity" I imagine that we could improve backward compatibility by providing both a |
I feel a bit uneasy about the conclusion (modifying code bases to change If everyone think it's not worth changing this right now, @ivg would it be possible to have a minor release of |
It is in the |
I agree that the conclusion is unsatisfying. I believe that no where else do we insist that a particular primitive be given a particular type. It would seem pretty easy to only have the weird new behaviour when the type matches the expected pattern, and then just fall back to the ordinary behaviour for typing a function application. |
I have a tentative implementation in #10558 which only applies the new typing rule when the type of the primitives matches exactly the standard one. This fixes BAP compatibility issue without introducing new primitives, which seem nice. Are there any objections to the idea or implementation? |
Thanks, Florian, this is awesome! |
#10450: restore support for %apply and %revapply with non-translucid types
The following test case fails with OCaml 4.13 but worked perfectly before:
M.t
is an abstract type but internally a function, which to me should be valid. However OCaml 4.13 somehow treats it as a function and rejects this code with a rather cryptic error message.Looking into the PRs merged in 4.13 so far, it looks like this was introduced in #10081 (cc @alainfrisch @garrigue)
The text was updated successfully, but these errors were encountered: