The following main.roc
code takes forever to compile, using all my CPU. It's been running for well over 10 minutes, I don't understand why. I tried removing almost everything and adding one piece at a time, and it just gets slower and slower. So I'm guessing the full code will eventually finish compiling one day, but perhaps not today. Any idea what's going on? Sorry if the code is really ugly, I'm a pebble (=a little Roc newbie).
app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.12.0/Lb8EgiejTUzbggO2HVVuPJFkwvvsfW6LojkLR20kTVE.tar.br" }
import cli.Stdout
import cli.Task
import cli.Arg
import cli.File
import cli.Utc
import Day01
import Day02
import Day03
import Day04
import Day05
import Day06
import Day07
import Day08
import Day09
import Day10
import Day11
import Day12
import Day13
import Day14
import Day15
import Day16
import Day17
import Day18
import Day19
import Day20
import Day21
import Day22
import Day23
import Day24
import Day25
dataPath = \day ->
"data/day$(if day < 10 then "0" else "")$(Num.toStr day).txt"
loadData = \day -> day |> dataPath |> File.readUtf8!
solutions = [
(Day01.part1, Day01.part2),
(Day02.part1, Day02.part2),
(Day03.part1, Day03.part2),
(Day04.part1, Day04.part2),
(Day05.part1, Day05.part2),
(Day06.part1, Day06.part2),
(Day07.part1, Day07.part2),
(Day08.part1, Day08.part2),
(Day09.part1, Day09.part2),
(Day10.part1, Day10.part2),
(Day11.part1, Day11.part2),
(Day12.part1, Day12.part2),
(Day13.part1, Day13.part2),
(Day14.part1, Day14.part2),
(Day15.part1, Day15.part2),
(Day16.part1, Day16.part2),
(Day17.part1, Day17.part2),
(Day18.part1, Day18.part2),
(Day19.part1, Day19.part2),
(Day20.part1, Day20.part2),
(Day21.part1, Day21.part2),
(Day22.part1, Day22.part2),
(Day23.part1, Day23.part2),
(Day24.part1, Day24.part2),
(Day25.part1, Day25.part2),
]
runSolution = \solution, index, input ->
Stdout.line! "Part $(Num.toStr index):"
startTime = Utc.now!
result = solution input
endTime = Utc.now!
delta = Utc.deltaAsMillis startTime endTime |> Num.toStr
Stdout.line! "$(result) ($(delta)ms)"
checkDay = \day ->
if day < 1 || day > 25 then Task.err (InvalidDay "Must be between 1 and 25") else Task.ok {}
runDay = \dayArg ->
day = Str.toU64 dayArg |> Task.fromResult!
checkDay! day
Stdout.line! "Day $(Num.toStr day)"
input = loadData! day
(part1, part2) = List.get solutions (day - 1) |> Task.fromResult!
runSolution! part1 1 input
runSolution! part2 2 input
main =
args = Arg.list! {}
daysStr = if List.len args < 2 then List.range { start: At 1, end: At 25 } |> List.map Num.toStr else args |> List.dropFirst 1
_ = daysStr |> List.map runDay |> List.reverse |> Task.sequence!
Task.ok {}
Every DayXX.roc
file contains the same thing:
module [part1, part2]
part1 = \input -> "Part 1 not implemented"
part2 = \input -> "Part 2 not implemented"
Are you importing/ingesting file bytes? i.e. like this https://www.roc-lang.org/examples/IngestFiles/README.html
We have a known LLVM bug that makes that ultra slow -- here is the PR to fix https://github.com/roc-lang/roc/pull/6832
Ah thanks, yes my code does containFile.readUtf8!
, is that what you mean?
No, File.readUtf8
should be fine
My code also uses Utc.now!
, maybe that's related?
I'm a pebble (=a little Roc newbie)
I love this, :smiley:
Basically the code takes some days as command line arguments (e.g., roc main.roc 1 2 3
), and for each day it loads the corresponding data/dayXX.txt
file, and it runs DayXX.part1 and DayXX.part2, times them, and prints the results along with the times.
I'm not seeing anything that looks obviously wrong
So the I/O operations involved are Arg.list
, Stdout.line
, Utc.now
, and File.readUtf8
.
I suspect we have a bug somewhere related to composing a lot of Tasks in a program
Like we a hitting an edge case or something that is blowing up the code gen
It could maybe be related to all the refcounting and other fixes that Brendan fixed recently. All available on the TESTING nightly and basic-cli 0.13.0 if you would like to test that
Luke Boswell said:
I'm not seeing anything that looks obviously wrong
If you want to test this, you can remove all the import DayXX
, and replace the definition of solutions
with this:
solutions = [
(\i -> i, \i -> i),
]
Then run roc main.roc 1
For me, it's still super slow.
Luke Boswell said:
It could maybe be related to all the refcounting and other fixes that Brendan fixed recently. All available on the TESTING nightly and basic-cli 0.13.0 if you would like to test that
Ah yes, thanks, I'll give this a shot.
Ah, ok... so if you run roc check
you will see there are issues
check exits earlier in the compiler pipeline so its giving us the problem reports, whereras build is getting stuck trying to resolve stuff that is definitely broken.
I think you just need to add an import cli.Task exposing [Task]
and that will fix your issue here
Or maybe not...
The only issues reported by roc check
were warnings about the fact that part1 = \input -> "not implemented"
does not use the variable input
. I replaced this with part1 = \_ -> "not implemented"
and now roc check
gives me no warning or errors.
I'm using roc nightly pre-release, built from commit 070d14a on Sat Jul 13 09:01:57 UTC 2024
I also tried to change the import as you said, but it didn't fix the issue. I'll try basic-cli 0.13, as you suggested (probably tomorrow because it's pretty late here in Auckland). Cheers!
So this kind of issue isn't totally uncommon. I find it's almost always trying to do some kind of alias or type analysis on broken code and stuck in a loop. So I find going through and adding type annotations to "pin" the types can help the compiler tell me where the issue is
Yeah, so there's definitely a bug in here somewhere related to composition of Tasks. It may be related to unifying error tags, or the way we are desugaring the !
, or some other bug. I spent a while trying to minify it, but haven't been able to isolate it.
This is what I was using... basically just added type annotations.
app [main] { cli: platform "https://github.com/roc-lang/basic-cli/releases/download/0.13.0/nW9yMRtZuCYf1Oa9vbE5XoirMwzLbtoSgv7NGhUlqYA.tar.br" }
import cli.Stdout
import cli.Task
import cli.Arg
import cli.File
import cli.Utc
import cli.Task exposing [Task]
dataPath : U64 -> Str
dataPath = \day ->
"data/day$(if day < 10 then "0" else "")$(Num.toStr day).txt"
loadData : U64 -> Task Str _
loadData = \day ->
day
|> dataPath
|> File.readUtf8
|> Task.mapErr UnableToReadFile
solutions : List (Str -> Str, Str -> Str)
solutions = [
(\i -> i,\i -> i),
]
runSolution : (Str -> Str), U64, Str -> Task {} []_
runSolution = \solution, index, input ->
Stdout.line! "Part $(Num.toStr index):"
startTime = Utc.now!
result = solution input
endTime = Utc.now!
delta = Utc.deltaAsMillis startTime endTime |> Num.toStr
Stdout.line! "$(result) ($(delta)ms)"
checkDay : U64 -> Task {} [InvalidDay Str]_
checkDay = \day ->
if day < 1 || day > 25 then Task.err (InvalidDay "Must be between 1 and 25") else Task.ok {}
runDay : Str -> Task {} []_
runDay = \dayArg ->
day = Str.toU64 dayArg |> Task.fromResult!
checkDay! day
Stdout.line! "Day $(Num.toStr day)"
input = loadData! day
(part1, part2) = List.get solutions (day - 1) |> Task.fromResult!
runSolution! part1 1 input
runSolution! part2 2 input
main =
args = Arg.list! {}
daysStr = if List.len args < 2 then List.range { start: At 1, end: At 25 } |> List.map Num.toStr else args |> List.dropFirst 1
_ = daysStr |> List.map runDay |> List.reverse |> Task.sequence!
Task.ok {}
Just to clarify this, no matter the result of any extra analysis done here, this is definitely a compiler bug as well.
I'm not sure I found the root of the problem... but I certainly a fix.
I suspected the Task.sequence
as I haven't really seen that used much.
I applied @Brendan Hansknecht's Task.loop
implementation on the Task as builtin branch and this runs really nicely.
I'll see if the same fix also works with current basic-cli.
I'm not sure we want to make another intermediate release, I think I'd rather just upgrade to builtin-task.
Yes also works on basic-cli
Here is a draft PR with the fix https://github.com/roc-lang/basic-cli/pull/236
If anyone would like this, you can build from source by cloning basic-cli on that branch, and then just roc build.roc
will do the rest. Just remember to reference the platform using a local relative path instead of a URL.
I just tested, it works fine indeed, thanks again. Note, I did not have to compile from source, I just copied the definition of sequence
from the PR into my file and used that instead of Task.sequence
. In case anyone else wants to go that route:
sequence = \taskList ->
Task.loop (taskList, List.withCapacity (List.len taskList)) \(tasks, values) ->
when tasks is
[task, .. as rest] ->
value = task!
Task.ok (Step (rest, List.append values value))
[] ->
Task.ok (Done values)
Oh nice. Was that modifying the version in your cache?
I'm sorry, I'm not sure I understand your question. What is the cache you are referring to? I only edited main.roc
and added the sequence
def there. I had to revert to basic-cli 0.12 because I was getting this error with 0.13:
roc_app_binary(18551,0x7ff844aa6fc0) malloc: *** error for object 0x7fb44c905b78: pointer being freed was not allocated
roc_app_binary(18551,0x7ff844aa6fc0) malloc: *** set a breakpoint in malloc_error_break to debug
Oh ok. That also works too.
I was talking about editing the platform implementation in the downloaded files. When you use a URL package roc downloads and put the .roc files into your .cache folder. So, you might also be able to patch it there. I think the check against files hashing correctly is only at the time when it's first downloaded and put in .cache
Last updated: Jul 06 2025 at 12:14 UTC