From 6ed4ecad41827aa5a7143bc2f47d394320c0efd3 Mon Sep 17 00:00:00 2001 From: Nailen Matschke Date: Thu, 11 Jul 2024 17:42:29 -0400 Subject: [PATCH 1/6] _ --- rfcs/primitive_aliases.md | 87 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 rfcs/primitive_aliases.md diff --git a/rfcs/primitive_aliases.md b/rfcs/primitive_aliases.md new file mode 100644 index 0000000..f42b46a --- /dev/null +++ b/rfcs/primitive_aliases.md @@ -0,0 +1,87 @@ +# Primitive aliases + +## Context + +OCaml supports both a number of built-in primitives as well as the ability to call +functions written in C. Both of these are introduced via the `external` keyword, e.g. + +```ocaml +external magic : 'a -> 'b = "%identity" +external reachable_words : t -> int = "caml_obj_reachable_words" +``` + +Simple examples such as those above behave much like regular OCaml functions, with some +special handling in the compiler. However, as discussed in +[manual section 11](https://ocaml.org/manual/5.2/intfc.html#s:C-cheaper-call) "cheaper C calls", C call +primitives may have more than one symbol associated with them, as well as a number of +attributes on argument and return types, such as with `Float.ldexp`: + +```ocaml +external ldexp + : (float[@unboxed]) + -> (int[@untagged]) + -> (float[@unboxed]) + = "caml_ldexp_float" "caml_ldexp_float_unboxed" +[@@noalloc] +``` + +In order for the compiler to utilize this information and call the unboxed version, the +primitive must be exposed with all attributes and C symbols in both structures and +signatures, though this can be overcome with sufficient inlining. + +## Motivation + +It's often useful to simply re-bind a value (e.g. with a different name), but there is +currently no way to do re-bind a _primitive_ without wrapping it in an OCaml value and +destroying any additional information. Consider a module such as: + +```ocaml +module Float : sig + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + val ( % ) : float -> float -> float +end = struct + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + let ( % ) = modfp +end +``` + +We can only rely on the compiler to pick the unboxed C call (when applicable) for +`Float.( % )`, and not for `Float.O.( % )`, because it is not exposed as a primitive. +Thus, users may prefer to explicitly write out the entire `external` description for every +binding, like so: + +```ocaml +module Float : sig + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external ( % ) : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] +end = struct + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external ( % ) : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] +end +``` + +This is obviously duplicative, verbose, and prone to inconsistency. + +## Proposal + +We will extend the syntax for `external` to support _aliases_, allowing one to re-bind a +primitive without syntactic overhead: + +```ocaml +module Float : sig + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external ( % ) = modfp +end = struct + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external ( % ) = modfp +end +``` + +We do not anticipate any difficulties in parsing this construct. We propose extending the +AST with a new `primitive_description` record, rather than continuing to represent +primitives via `value_description`, and adding a `Psig_primitive` constructor to +`signature_item_desc`. + +Typing such aliases seems similarly straightforward: we can simply look up the primitive +bound to the symbol on the right-hand side in the current environment, recovering all the +same information as if were written out in full. From 66e1387294496d016e64c8f2c46831d0b41f8762 Mon Sep 17 00:00:00 2001 From: Nailen Matschke Date: Thu, 11 Jul 2024 17:50:00 -0400 Subject: [PATCH 2/6] rename --- rfcs/primitive_aliases.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rfcs/primitive_aliases.md b/rfcs/primitive_aliases.md index f42b46a..b3bc05b 100644 --- a/rfcs/primitive_aliases.md +++ b/rfcs/primitive_aliases.md @@ -46,8 +46,8 @@ end ``` We can only rely on the compiler to pick the unboxed C call (when applicable) for -`Float.( % )`, and not for `Float.O.( % )`, because it is not exposed as a primitive. -Thus, users may prefer to explicitly write out the entire `external` description for every +`Float.modfp`, and not for `Float.( % )`, because it is not exposed as a primitive. Thus, +users may prefer to explicitly write out the entire `external` description for every binding, like so: ```ocaml From 529aacfc960f37867d96ef37f0ab1db626cfeee1 Mon Sep 17 00:00:00 2001 From: Nailen Matschke Date: Thu, 11 Jul 2024 17:50:58 -0400 Subject: [PATCH 3/6] format --- rfcs/primitive_aliases.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rfcs/primitive_aliases.md b/rfcs/primitive_aliases.md index b3bc05b..a9e78fe 100644 --- a/rfcs/primitive_aliases.md +++ b/rfcs/primitive_aliases.md @@ -37,10 +37,10 @@ destroying any additional information. Consider a module such as: ```ocaml module Float : sig - external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] val ( % ) : float -> float -> float end = struct - external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] let ( % ) = modfp end ``` @@ -52,11 +52,11 @@ binding, like so: ```ocaml module Float : sig - external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] - external ( % ) : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external ( % ) : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] end = struct - external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] - external ( % ) : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external ( % ) : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] end ``` @@ -69,10 +69,10 @@ primitive without syntactic overhead: ```ocaml module Float : sig - external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] external ( % ) = modfp end = struct - external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] + external modfp : float -> float -> float = "modfp" "modfp_unboxed" [@@unboxed] external ( % ) = modfp end ``` From 6859cf363cbeb298e4715c790fa5ad0e0c496137 Mon Sep 17 00:00:00 2001 From: Nailen Matschke Date: Fri, 12 Jul 2024 11:15:12 -0400 Subject: [PATCH 4/6] typo --- rfcs/primitive_aliases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/primitive_aliases.md b/rfcs/primitive_aliases.md index a9e78fe..debd8dc 100644 --- a/rfcs/primitive_aliases.md +++ b/rfcs/primitive_aliases.md @@ -32,7 +32,7 @@ signatures, though this can be overcome with sufficient inlining. ## Motivation It's often useful to simply re-bind a value (e.g. with a different name), but there is -currently no way to do re-bind a _primitive_ without wrapping it in an OCaml value and +currently no way to re-bind a _primitive_ without wrapping it in an OCaml value and destroying any additional information. Consider a module such as: ```ocaml From ec5ab08f555316f67d2c1024aed7d49e80091a21 Mon Sep 17 00:00:00 2001 From: Nailen Matschke Date: Tue, 23 Jul 2024 11:07:24 -0400 Subject: [PATCH 5/6] small feature request from sdolan --- rfcs/primitive_aliases.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/rfcs/primitive_aliases.md b/rfcs/primitive_aliases.md index debd8dc..250ff39 100644 --- a/rfcs/primitive_aliases.md +++ b/rfcs/primitive_aliases.md @@ -85,3 +85,22 @@ primitives via `value_description`, and adding a `Psig_primitive` constructor to Typing such aliases seems similarly straightforward: we can simply look up the primitive bound to the symbol on the right-hand side in the current environment, recovering all the same information as if were written out in full. + +### Type annotations + +We also intend to support the following form: + +```ocaml +module M : sig + type t + external int_of_t : t -> int = Fun.id +end = struct + type t = int + external int_of_t : t -> int = Fun.id +end +``` + +That is, one will be permitted to ascribe a new type to the aliased primitive, and +criticially the form `external foo : ty = ext` will be acceptable in signatures _even if +`ext` has the wrong type_, with the compatibility check done as usual during module +inclusion. From 723d314f592bced0c34a6ed36ce4408147353d0c Mon Sep 17 00:00:00 2001 From: Nailen Matschke Date: Tue, 23 Jul 2024 11:08:24 -0400 Subject: [PATCH 6/6] typo --- rfcs/primitive_aliases.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rfcs/primitive_aliases.md b/rfcs/primitive_aliases.md index 250ff39..77b348d 100644 --- a/rfcs/primitive_aliases.md +++ b/rfcs/primitive_aliases.md @@ -101,6 +101,6 @@ end ``` That is, one will be permitted to ascribe a new type to the aliased primitive, and -criticially the form `external foo : ty = ext` will be acceptable in signatures _even if +critically the form `external foo : ty = ext` will be acceptable in signatures _even if `ext` has the wrong type_, with the compatibility check done as usual during module inclusion.