I’ve recently been investigating a problem where the bytecode executable was crashing with a segfault. The reason turned out to be that there were
external declarations using the two name form, but with a function having 5 or less arguments.
external foo_ext : int -> int -> int -> int = "foo_ext_bytecode" "foo_ext_native"
where the correct thing to do would be to skip the
"foo_ext_bytecode" since the declared function type has 3 ≤ 5 arguments.
Couldn’t such invalid declarations be rejected by the compiler simply based on the declared argument count?
I think there are other cases where the two-name form of the external declaration is valid. For example, unboxed float primitives:
external foo : float -> float -> float = "foo_byte" "foo" [@@unboxed]
See section 19.11 of the manual.
Allright, thanks for pointing out these other uses of the two string form. I was only aware of 19.1.2.
I guess what the manual is trying to explain is that the native stub always takes the arguments individually, but that the bytecode stub only does it this way for up to 5 arguments. Hence, the basic rule would be that it’s ok to use the same stub for both native and bytecode as long as there aren’t too many arguments, not that one has to.
For comparison, the incorrect interpretation of 19.1.2, combined with the observation that the two string form isn’t rejected for a low arity function, is that the bytecode stub always takes an argument vector, but that the bytecode compiler can cope with just the native function as long as there are at most 5 arguments. Maybe 19.1.2 could be clarified to rule out this interpretation? Then one wouldn’t need to go all the way to the advanced topics in 19.11 to figure this out.