Skip to content

Zero alloc annotation for OxCaml#1422

Open
Leonidas-from-XIV wants to merge 13 commits into
ocaml:masterfrom
Leonidas-from-XIV:zero-alloc-annotation
Open

Zero alloc annotation for OxCaml#1422
Leonidas-from-XIV wants to merge 13 commits into
ocaml:masterfrom
Leonidas-from-XIV:zero-alloc-annotation

Conversation

@Leonidas-from-XIV
Copy link
Copy Markdown

This PR aims to take the zero alloc annotation from the source and retain it in the parsed output.

Currently this PR supports the [@@zero_alloc] annotation on signatures. Next step would be to support the [@zero_alloc] annotation on bindings; not sure if it would make sense to add it in this PR or another one.

This is my first PR to Odoc so excuse me for being a bit lost, I'll happily fix up/change stuff.

cc @art-w

Copy link
Copy Markdown
Collaborator

@art-w art-w left a comment

Choose a reason for hiding this comment

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

It looks good! I have a minor nitpick, mostly to be safe because I don't know if the additional O.cut could break existing docs. Do you prefer addressing the remaining TODOs for the cmti in a separate PR?

Comment thread src/document/generator.ml Outdated
Comment thread src/loader/doc_attr.ml Outdated
Copy link
Copy Markdown
Collaborator

@panglesd panglesd left a comment

Choose a reason for hiding this comment

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

Thanks!

I've left a nitpick, a comment about payload support and one about not loading attributes in OxCaml.

Some small comments that probably require no change in this PR:

  • In the oxcaml doc, they use val[@zero_alloc] f : t rather than val f : t [@@zero_alloc] as in this PR (they are different syntax for the same thing). So maybe oxcaml users are more familiar with the former? But keeping as is is fine to me!
  • There is already some logic to extract special attributes (eg @alert and @deprecated). However, they are rendered differently (using odoc @-tags). It's a pity to have two ways to handle attributes, but for zero_alloc (and other oxcaml-specific attributes to come), I think I prefer your approach.

Comment thread src/model/lang.ml
Comment on lines +324 to +327
module Zero_alloc : sig
type t = Assume | Opt | Strict
end
type attr = Zero_alloc of Zero_alloc.t
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

According to the docs, the only allowed payloads are opt, strict and arity <n>.

If I understand correctly "assume" does not correspond to the default. (Assume in a module type would be: do not check that the implementation do not allocate, but use that information in uses outside.)

Moreover, it seems possible to use multiple (valid) payloads.

So I think we have two options in terms of type representation: the "more correct" one

Suggested change
module Zero_alloc : sig
type t = Assume | Opt | Strict
end
type attr = Zero_alloc of Zero_alloc.t
module Zero_alloc : sig
type t = {opt : unit option ; strict: unit option; arity: int option }
end
type attr = Zero_alloc of Zero_alloc.t

or the one that uses the fact that it has already been validated by the oxcaml compiler:

Suggested change
module Zero_alloc : sig
type t = Assume | Opt | Strict
end
type attr = Zero_alloc of Zero_alloc.t
module Zero_alloc : sig
type t = Arity of n | Opt | Strict
end
type attr = Zero_alloc of Zero_alloc.t

Both are fine to me! And the second one is probably less work.

Here are some examples of zero_alloc uses in module types to "validate" the oxcaml doc:

# module type T = sig val[@zero_alloc] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc] end

(** The error says the accepted attributes {e for implementation}, not interface, unfortunately *)

# module type T = sig val[@zero_alloc gloubli-boulga] f : int -> int -> int end ;;
Warning 47 [attribute-payload]: illegal payload for attribute 'zero_alloc'.
It must be either 'assume', 'assume_unless_opt', 'strict', 'opt', 'opt strict', 'assume strict', 'assume never_returns_normally', 'assume never_returns_normally strict', 'assume error', 'ignore', 'arity <int_constant>', 'custom_error_message <string_constant>' or empty

(** assume is not allowed *)

# module type T = sig val[@zero_alloc assume] f : int -> int -> int end ;;
Error: zero_alloc assume attributes are not supported in signatures

(** strict, opt, and arity <n> (and combinations) are allowed *)

# module type T = sig val[@zero_alloc strict] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc strict] end
# module type T = sig val[@zero_alloc opt] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc opt] end
# module type T = sig val[@zero_alloc arity 1] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc arity 1] end
# module type T = sig val[@zero_alloc strict opt] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc strict opt] end
# module type T = sig val[@zero_alloc strict arity 2] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc strict] end
# module type T = sig val[@zero_alloc arity 1 opt strict] f : int -> int -> int end ;;
module type T =
  sig val f : int -> int -> int [@@zero_alloc strict opt arity 1] end
# module type T = sig val[@zero_alloc] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc] end
# module type T = sig val[@zero_alloc] f : int -> int -> int end ;;
module type T = sig val f : int -> int -> int [@@zero_alloc] end

Comment thread src/loader/doc_attr.ml Outdated
Comment thread src/loader/doc_attr.ml
@Leonidas-from-XIV
Copy link
Copy Markdown
Author

In the oxcaml doc, they use val[@zero_alloc] f : t rather than val f : t [@@zero_alloc] as in this PR (they are different syntax for the same thing). So maybe oxcaml users are more familiar with the former? But keeping as is is fine to me!

Very good point. I mostly did it this way as this is how the oxcaml toplevel prints it. So I assumed this is the way to go, but I will at least add a test-case to make sure that the val[@zero_alloc] variant is supported correctly.

@art-w
Copy link
Copy Markdown
Collaborator

art-w commented May 6, 2026

In the oxcaml doc, they use val[@zero_alloc] f : t rather than val f : t [@@zero_alloc] as in this PR (they are different syntax for the same thing).

We received feedback that the later form looks nicer for documentation :)

@Leonidas-from-XIV
Copy link
Copy Markdown
Author

I've rewritten the code to parse the arguments in [@@zero_alloc], with better names and better self-contained. My testing has revealed that ignore, opt, strict, arity and custom_error_message are valid in signatures.

However there are cases where the parser silently ignores errors (e.g. "arity" used but no argument given), in that case I think it would be nice to do something. Probably exiting is too extreme so I didn't want to throw but it would be nice to emit a warning that something went wrong?

Or maybe its ok to silently continue because the OxCaml compiler will complain anyway? WDYT?

@panglesd
Copy link
Copy Markdown
Collaborator

Or maybe its ok to silently continue because the OxCaml compiler will complain anyway? WDYT?

I would say it is ok to silently continue, as I would say those warnings are more the responsibility of the compiler.

But that made me think... Isn't this information already available in the compiler artefact? I have quickly checked in types.mli, and I have found something that looks like it, although I'm not sure (it is in a Wrapped functor that's instantiated and inlined 🫤 ).

If we can avoid the parsing ourself, that would be much better!

@panglesd
Copy link
Copy Markdown
Collaborator

panglesd commented May 11, 2026

After setting up an OxCaml environment 😅 I was able to find that yes, the information is already there!

In cmi files, the "zero alloc" info is stored at vd.val_zero_alloc, and in cmti files, it is stored in vd.val_val.val_zero_alloc. For instance:

    let za = vd.val_val.val_zero_alloc |> Zero_alloc.get in
    let za = match za with
      | Default_zero_alloc -> _
      | Ignore_assert_all -> _
      | Check _ -> _
      | Assume _ -> _
    in
    Value.Zero_alloc za

using this info to create a lang, rather than reparse it from the attribute, seems better! And would remove all the parsing code.

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.

5 participants