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

Remove use of C variable length arrays (VLAs) #11693

Merged
merged 3 commits into from Mar 4, 2023
Merged

Conversation

dra27
Copy link
Member

@dra27 dra27 commented Nov 3, 2022

OCaml 5 adds caml_alloc_1..caml_alloc_9 and the general caml_alloc_N to caml/alloc.h (see
ocaml-multicore/ocaml-multicore#72). These are implemented using C99 variable length arrays (VLAs). VLAs are (if I understand correctly) deprecated in C11 and they're not supported in our favourite MS C compiler (and they have no intention of adding them as part of their C11 and C17 support).

This PR is intended mainly to start discussion - at this stage I've removed caml_alloc_N, but it may be possible to reintroduce it as a macro (cf. CAMLlocalN) if we wanted?

I've reimplemented the 9 allocation functions with the inlined function as a macro, but this needs checking to ensure that the generated code is still correct (cf. the discussion in the original PR and cc @stedolan and @gadmm).

@gasche
Copy link
Member

gasche commented Nov 3, 2022

I can see how the current implementation of caml_alloc_N uses a variable-length array (VLA), so you removed it. But I don't understand what is the issue with the implementation of the caml_alloc_[1-9] functions, in b7a5bb5 you replaced a function by a macro, but I don't see any variable-length array in the code before or after the change? (The do_alloc_small function takes a value* array with an explicit size parameter, which seems fine. Enter_gc uses CAMLlocalN and I don't know how it is implemented, but the same Enter_gc code should be used before and after the patch anyway.)

@gadmm
Copy link
Contributor

gadmm commented Nov 3, 2022

My understanding is that CAMLlocalN uses a VLA when the size argument is not a compile-time constant. By writing do_alloc_small as a macro, we get the constant value in time for avoiding a VLA in CAMLlocalN.

CAMLlocalN is part of the public API so it should itself be fixed, so that it never uses a VLA. How about replacing both uses of VLAs in CAMLlocalN and caml_alloc_N with a call to alloca/_malloca?

(In a second time we can look at the generated code to see if it generates better code after the transformation into a macro, but this is a priori not needed for correctness.)

@dra27
Copy link
Member Author

dra27 commented Nov 3, 2022

It's the use of CAMLlocalN with the wosize argument which creates the VLA. Converting do_alloc_small to a macro means that the constant gets threaded through instead. The configure change proposed here allows users to continue using CAMLlocalN to create VLAs if they wish, it just prohibits our code from doing it.

@gasche
Copy link
Member

gasche commented Nov 3, 2022

The previous caml_alloc_[1-9] functions already had a hairy definition (with a tricky local copy for rooting etc.), and the new version is slightly worse (two macros that depend on each other, etc.). I wonder if we should consider biting the bullet and copy-pasting the definition 9 times. This would also directly avoid CAMLlocalN.

@smuenzel
Copy link
Contributor

smuenzel commented Nov 5, 2022

CAMLlocalN is part of the public API so it should itself be fixed, so that it never uses a VLA. How about replacing both uses of VLAs in CAMLlocalN and caml_alloc_N with a call to alloca/_malloca?

I tried doing this here: trunk...smuenzel:ocaml:no-vla
I think that should pretty much be equivalent, if I'm not missing something

@dra27
Copy link
Member Author

dra27 commented Nov 5, 2022

It does indeed look equivalent, thanks @smuenzel (I think it needs a slight tweak to use _alloca on MSVC, but that's not relevant at the moment). I'm not sure this has to be fixed though - CAMLlocalN doesn't always use a VLA, it only does that if you pass a non-constant argument to it, versus the other functions added which always were using VLAs. Given that alloca is - strangely - not POSIX, it's not totally clear to me that we have to fix it.

I haven't checked, but given that caml_alloc_N is an OCaml 5.0, it's not an impossibility that we simply choose to remove it, rather than re-implement it? Or, alternately, reimplement it as a macro (as with e.g. Alloc_small, taking the result as the first argument instead of it being an inlined function).

@xavierleroy
Copy link
Contributor

alloca is much worse than variable-length arrays, in my opinion. At least VLAs are specified in a standard... No alloca shall enter the OCaml runtime system!

I didn't notice that CAMLlocalN creates a VLA if the number N is not a compile-time constant, but so be it. In the runtime system I see only one use (in otherlibs/runtime_events/runtime_events_consumer.c) with N = 4.

I'm in favor of removing caml_alloc_N. I think caml_alloc_1 to _9 cover all reasonable cases where we want to pass the values of the fields as argument to a function (as opposed to e.g. filling them later by copying from an array or what not).

@dra27
Copy link
Member Author

dra27 commented Nov 7, 2022

We're still just at the point with 5.0 release cycle where caml_alloc_N can be removed - should I open a separate PR just removing caml_alloc_N from 5.0 (and leaving the VLA implementation of caml_alloc_1, etc. to be removed here in 5.1) or shall we add this PR to the 5.0 milestone?

@gasche
Copy link
Member

gasche commented Nov 7, 2022

should I open a separate PR just removing caml_alloc_N from 5.0

This sounds like a good idea to me.

Copy link
Contributor

@gadmm gadmm left a comment

Choose a reason for hiding this comment

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

Following the discussion, this looks essentially good. Both this approach with a macro and the alternative suggested by Garbiel (inlining DO_ALLOC_SMALL) sounds equally good to me.

Changes Outdated
@@ -21,6 +21,10 @@ Working version
- #11691, #11706: use __asm__ instead of asm for strict ISO C conformance
(Xavier Leroy, report by Gregg Reynolds , review by Sadiq Jaffer)

* #11693: Remove use of C99 Variable Length Arrays (VLAs) and therefore the
caml_alloc_N function.
Copy link
Contributor

Choose a reason for hiding this comment

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

This description needs to be updated now that caml_alloc_N never made it into OCaml 5 (esp. no breaking change).

runtime/alloc.c Outdated
Field(v, i) = vals[i];
}
return v;
#define DO_ALLOC_SMALL(wosize, tag, vals) \
Copy link
Contributor

@gadmm gadmm Nov 21, 2022

Choose a reason for hiding this comment

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

Nit-pick. I'd write Do_alloc_small and use parentheses around the occurrences of the arguments, e.g. (tag) < 256.

Can you please add one comment line here to explain to future readers why you had to use a macro?

@gadmm
Copy link
Contributor

gadmm commented Mar 3, 2023

@gasche This looks ready. I am also convinced now that it is better to have a macro than to duplicate code.

(Please credit other reviewers who helped come up with the best version.)

@dra27
Copy link
Member Author

dra27 commented Mar 3, 2023

Actually, it's not quite there - the name vals is fixed (it has to be, for the Enter_gc macro) but it's passed to Do_alloc_small as though it could be altered. I'll push a correction for that (with more comment) shortly. I also found that trying to expand the macros into 9 separate functions gets even messier.

@gasche
Copy link
Member

gasche commented Mar 3, 2023

the name vals is fixed (it has to be, for the Enter_gc macro)

Naming suggestion for that version of the macro: Enter_GC_saving_vals.

Edit: or maybe: Copy_vals_and_enter_GC.

Use a macro to build them instead of a VLA.
Prevent VLAs being accidentally used in future.
@dra27
Copy link
Member Author

dra27 commented Mar 4, 2023

I went with "preserve" - so Enter_gc_preserve_vals - how's the final result looking?

runtime/alloc.c Outdated
value v[3] = {a, b, c};
return do_alloc_small(3, tag, v);
value vals[3] = {a, b, c};
Do_alloc_small(3, tag);
Copy link
Member

Choose a reason for hiding this comment

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

Actually, could this be just

CAMLexport value caml_alloc_3 (tag_t tag, value a, value b, value c)
{
  Do_alloc_small(3, tag, {a, b, c});
}

?

With this form we can bind vals in Do_alloc_small itself, and thus reduce the scope of the code that has to reason about vals as a magical/fixed name.

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it can work - the { ... } is an initialiser syntax and it doesn't work as a bracket for the macro (that gets seen by the preprocessor as a 5-argument macro call)

Copy link
Contributor

Choose a reason for hiding this comment

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

Just add parentheses! Do_alloc_small(3, tag, ({a, b, c})) should pass the preprocessor. But then you could just omit the braces and add them back in the macro.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'd tried that - but then you end up with value vals[3] = ({a, b, c}) which is a syntax error. I hadn't considered passing them as (a, b, c) and then restoring the braces in the macro, but that doesn't appear to work either ("left-hand operand of comma expression has no effect" presumably arising from `value vals[3] = {(a, b, c)}).

It does "work" with __VA_ARGS__, - what do you think??

Copy link
Member

@gasche gasche 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 like a sophisticated monster born out of the best intentions, I love it. Approved.

@gasche gasche merged commit c08807a into ocaml:trunk Mar 4, 2023
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

5 participants