I found a bug while looking at #13919 around the compilation of lazy expressions involving unboxed types nested excessively.
Here is a repro:
module type T = sig type t val mk : unit -> t end
module F (X : T) = struct type t = { x : X.t } [@@unboxed] let mk () = { x = X.mk () } end
module F10 (X : T) = F(F(F(F(F(F(F(F(F(F(X))))))))))
module F100 (X : T) = F10(F10(F10(F10(F10(F10(F10(F10(F10(F10(X))))))))))
module B = struct type t = float let mk () = 0. end
module M = F(F100(B))
let x = M.mk ()
let y = lazy x
let a = Array.make 10 y
let z = Lazy.force a.(0)
The issue is that when trying to see if lazy x can be compiled as x or needs to be a (makeforward x), we look at the runtime representation of x given by its type, which has to expand unboxed types. Since it is possible to create recursive unboxed types, this process might not terminate, so we limit the amount of expansions to a very large but finite amount (100 in practice). But when this expansion fails, instead of propagating this all the way to let the classification treat the value as an unknown type, we revert to the initial (unboxed) type and pretend it behaves as a regular (boxed) variant or record type. In the example this means that we assume that x cannot have a lazy or float tag, allowing us to represent it as x. But since it is actually a float, creating an array from it yields a flat float array and reading the values as regular values (as they are supposed to be, given that the type of the elements is something Lazy.t) fails at runtime.
I found a bug while looking at #13919 around the compilation of lazy expressions involving unboxed types nested excessively.
Here is a repro:
The issue is that when trying to see if
lazy xcan be compiled asxor needs to be a(makeforward x), we look at the runtime representation ofxgiven by its type, which has to expand unboxed types. Since it is possible to create recursive unboxed types, this process might not terminate, so we limit the amount of expansions to a very large but finite amount (100 in practice). But when this expansion fails, instead of propagating this all the way to let the classification treat the value as an unknown type, we revert to the initial (unboxed) type and pretend it behaves as a regular (boxed) variant or record type. In the example this means that we assume thatxcannot have a lazy or float tag, allowing us to represent it asx. But since it is actually a float, creating an array from it yields a flat float array and reading the values as regular values (as they are supposed to be, given that the type of the elements issomething Lazy.t) fails at runtime.