mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-28 14:24:45 +00:00

This commit fixes a long-standing bug wherein bindings to polymorphic, non-function expressions would be lowered at binding site, rather than being specialized at the call site. Concretely, consider the program ``` main = n = 1 idU8 : U8 -> U8 idU8 = \m -> m idU8 n ``` Prior to this commit, we would lower `n = 1` as part of the IR, and the `n` at the call site `idU8 n` would reference the lowered definition. However, at the definition site, `1` has the polymorphic type `Num *` - it is not until the the call site that we are able to refine the type bound by `n`, but at that point it's too late. Since the default layout for `Num *` is a signed 64-bit int, we would generate IR like ``` procedure main(): let App.n : Builtin(Int(I64)) = 1i64; ... let App.5 : Builtin(Int(U8)) = CallByName Add.idU8 App.n; ret App.5; ``` But we know `idU8` expects a `u8`; giving it an `i64` is nonsense. Indeed this would trigger LLVM miscompilations later on. To remedy this, we now keep a sidecar table that maps symbols to the polymorphic expression they reference, when they do so. We then specialize references to symbols on the fly at usage sites, similar to how we specialize function usages. Looking at our example, the definition `n = 1` is now never lowered to the IR directly. We only generate code for `1` at each place `n` is referenced. As a larger example, you can imagine that ``` main = n = 1 asU8 : U8 -> U8 asU32 : U32 -> U8 asU8 n + asU32 n ``` is lowered to the moral equivalent of ``` main = asU8 : U8 -> U8 asU32 : U32 -> U8 asU8 1 + asU32 1 ``` Moreover, transient usages of polymorphic expressions are lowered successfully with this approach. See for example the `monomorphized_tag_with_polymorphic_arg_and_monomorphic_arg` test in this commit, which checks that ``` main = mono : U8 mono = 15 poly = A wrap = Wrapped poly mono useWrap1 : [Wrapped [A] U8, Other] -> U8 useWrap1 = \w -> when w is Wrapped A n -> n Other -> 0 useWrap2 : [Wrapped [A, B] U8] -> U8 useWrap2 = \w -> when w is Wrapped A n -> n Wrapped B _ -> 0 useWrap1 wrap * useWrap2 wrap ``` has proper code generated for it, in the presence of the polymorphic `wrap` which references the polymorphic `poly`. https://github.com/rtfeldman/roc/pull/2347 had a different approach to this - polymorphic expressions would be converted to (possibly capturing) thunks. This has the benefit of reducing code size if there are many polymorphic usages, but may make the generated code slower and makes integration with the existing IR implementation harder. In practice I think the average number of polymorphic usages of an expression will be very small. Closes https://github.com/rtfeldman/roc/issues/2336 Closes https://github.com/rtfeldman/roc/issues/2254 Closes https://github.com/rtfeldman/roc/issues/2344
9 lines
292 B
Text
9 lines
292 B
Text
procedure Test.2 (Test.3, Test.4):
|
|
let Test.8 : Builtin(Int(U64)) = 18i64;
|
|
ret Test.8;
|
|
|
|
procedure Test.0 ():
|
|
let Test.6 : Builtin(Int(U8)) = 100i64;
|
|
let Test.7 : Builtin(Int(U32)) = 100i64;
|
|
let Test.5 : Builtin(Int(U64)) = CallByName Test.2 Test.6 Test.7;
|
|
ret Test.5;
|