Zulip Chat Archive
Stream: general
Topic: what happened upgrading from lean4:v4.21.0 to lean4:v4.22.0
Luc Duponcheel (Sep 08 2025 at 21:02):
Hello, I know that, by defining my the unsafe parallelFibonacci I am "driving on the dangerous fast lane", but, in one way or another, by upgrading from lean4:v4.21.0 to lean4:v4.22.0, my #eval tasksSpawningFibonacci 24, using Task, does not work any more. Note that in the attached video, 2^25 threads are spawned, which takes some time, but #eval tasksSpawningFibonacci 5, also crashes with lean4:v4.22.0, so, I think it is not a stack size issue.
simplescreenrecorder-2025-09-08_22.48.30.mkv
Henrik Böving (Sep 08 2025 at 21:15):
Nothing in particular comes to mind, you'll have to provide a #mwe.
Luc Duponcheel (Sep 09 2025 at 10:28):
Henrik,
uncomment the #eval when viewing it in the Lean playground
you can also copy/paste (or download attachment) and run it locally with version 21
Code listing
Luc Duponcheel (Sep 09 2025 at 10:33):
Henrik, see attachment
WME.lean
Henrik Böving (Sep 09 2025 at 11:03):
First things first your definition is not unsafe if massaged sufficiently:
Henrik Böving (Sep 09 2025 at 11:08):
Looking at the coredump of the crash it very much is a stack size issue.
Henrik Böving (Sep 09 2025 at 11:26):
With the version of your code without unsafe (for reference the diff is)
141c141
< unsafe def parallelFibonacci
---
> partial def parallelFibonacci
146c146
< [Parallel program] :
---
> [Parallel program] [Inhabited <| program Nat Nat]:
157a158,160
> instance [Inhabited (computation β)] : Inhabited (FromComputationValuedFunction computation α β) where
> default := { toComputationValuedFunction := fun _ => default }
>
230,231c233
< unsafe def tasksSpawningFibonacci :=
---
> def tasksSpawningFibonacci :=
The issue is that your parallelFibonacci attempts to establish a closure containing itself so that just loops (non tail recursively) forever.
def parallelFibonacci._at_.tasksSpawningFibonacci.spec_0 (x_1 : @& obj) : obj :=
let x_2 : obj := isZero._at_.parallelFibonacci._at_.tasksSpawningFibonacci.spec_0.spec_0;
let x_3 : obj := one._at_.parallelFibonacci._at_.tasksSpawningFibonacci.spec_0.spec_1;
let x_4 : obj := isOne._at_.parallelFibonacci._at_.tasksSpawningFibonacci.spec_0.spec_2;
let x_5 : obj := parallelFibonacci._at_.tasksSpawningFibonacci.spec_0 x_1; <- CALL HERE
inc x_5;
let x_6 : obj := pap parallelFibonacci._at_.tasksSpawningFibonacci.spec_0._lam_0 x_5;
let x_7 : obj := pap parallelFibonacci._at_.tasksSpawningFibonacci.spec_0._lam_1 x_5;
let x_8 : obj := add._at_.parallelFibonacci._at_.tasksSpawningFibonacci.spec_0.spec_7;
let x_9 : obj := pap parallelFibonacci._at_.tasksSpawningFibonacci.spec_0._lam_2 x_6 x_7 x_8;
inc x_3;
let x_10 : obj := if_._at_.parallelFibonacci._at_.tasksSpawningFibonacci.spec_0.spec_8._redArg x_4 x_3 x_9;
let x_11 : obj := if_._at_.parallelFibonacci._at_.tasksSpawningFibonacci.spec_0.spec_8._redArg x_2 x_3 x_10;
ret x_11
The code produced also looks quite similar on v4.21.0 and I get a similar crash there as well, curiously I do indeed not get a crash when I use the unsafe version. I suspect the compiler was holding back on some optimizations in the unsafe case in 21 and for that reason didn't optimize this? Either way it seems to me like this is broken.
Luc Duponcheel (Sep 09 2025 at 14:49):
Henrik,
Thanks for your constructive comments.
parallelFibonacci is not a program, it is a program specification.
The whole goal of my PSBP project
(Program Specification Based Programming)
is to separate program specifications from programs.
A program is a materialization corresponding to instances of the classes in terms of whose members a program specification is written.
Compare this with the painting
"Ceci n'est pas une pipe" of René Magritte
(https://en.wikipedia.org/wiki/The_Treachery_of_Images).
The paining is not a pipe.
I understand that the type system of Lean cannot infer a type for parallelFibonacci. As I already +/- stated, I am
"living on the dangerous fast lane".
Anyway, Lean (v21) could evaluate tasksSpawningFibonacci 10
for the materialization of the Task based instances of the classes involved.
So what do you mean precisely with
"Either way it seems to me like this is broken"?
I mean, what is this in that sentence?
Do you mean that Lean (v21) should not be able to evaluate
tasksSpawningFibonacci 10, or do you mean that Lean (v22) should be able to evaluate tasksSpawningFibonacci 10?
Luc Duponcheel (Sep 09 2025 at 14:59):
I understand that imporant an goal of Lean is to be able to learn Lean what concepts like groups, rings, fields, categories etc. are all about and that Lean is this very demanding (but also very helpful) student that can review your proofs (but can also infer proofs for you). So my goal was to teach Lean what programs are all about. I understand that Lean cannot infer a type for a program specification, but that should, i.m.h.o. not prevent it from interpreting programs and/or generating executable code for programs. And, yes, badly written programs (not badly written program specifications) will, for example, end up looping forever.
Henrik Böving (Sep 09 2025 at 15:10):
parallelFibonacciis not a program, it is a program specification.
Yes it is a program, a program that allocates a lot of closures that eventually get evaluated when your task framework kicks in. Even if it might seem to you like you have some abstract notion of program specification going on this is not what the Lean compiler thinks about your program. If you want a specification and only that instead of this recursive closure allocator you would need to use an inductive for your program specification type.
I understand that the type system of
Leancannot infer a type forparallelFibonacci. As I already +/- stated, I am "living on the dangerous fast lane".
as I demonstrated in my snippet it is perfectly possible to write your Lean file without unsafe.
"Either way it seems to me like this is broken"?
I mean, what is this in that sentence?
parallelFibonacci could always be compiled to an infinite non tail recursion that blows the stack and I am surprised that it ever worked at all.
Luc Duponcheel (Sep 09 2025 at 16:49):
Henrik,
first of all, thx for all your replies
FYI:
I the past, I have written a (more elaborated)
"Program Specification Based Programming" library
in Scala and Haskell.
I switched to Lean because of it's theorem proving capabilities. With the idea of writing an, agreed, somewhat opinionated, programming course.
The code of the course being a programming course for Lean itself.
About:
"... you would need to use an inductive for your program specification type ...
Will using code like below, using a structure, then solve my problem?
BTW:
I did not manage, yet, to use an inductive, maybe you can help?
Code listing
Luc Duponcheel (Sep 09 2025 at 17:29):
Hernik,
About:
... parallelFibonacci could always be compiled to an infinite
non tail recursion that blows the stack ...
- could : yes
- should : no
I mean, given the instance definitions that decrease the
arguments of the two spawned parallelFibonacci's, more
precisely n to n-1 and n-2, Lean could, maybe, be
clever enough to compile to code that does not blow the stack,
(apparently it did with v21) or, at least, offer a compiler option
to not do it.
BTW:
I have had no problems with the unsafe sequential fibonacci,
(both its Active and Reactive implementation). Only the
Task based implementation gave me problems.
Henrik Böving (Sep 11 2025 at 07:43):
I mean, given the
instancedefinitions that decrease the
arguments of the two spawnedparallelFibonacci's, more
preciselynton-1andn-2
This analysis is wrong. The Lean program parallelFibonacci simply calls itself infinitely deep non tail recursively. It doesn't matter what program it specifies, the semantics of Lean dictate that what you are doing is not okay. As I have explained above your program is also broken in both of the versions if it is written out properly without unsafe and instead using partial. As to why the compilation behavior of an unsafe definition changed between 4.21 and 4.22 I don't know but what it is doing now is not wrong.
Note that a trick like you are doing here might work out trivially in a lazy language like Haskell because the closures there would not be evaluated eagerly. This is very much not the case in Lean. If you check the definition of docs#Applicative in Lean you will observe that Lean explicitly introduces a Unit -> thunk in the second part of the sequence in order to avoid evaluating it if it doesn't actually end up being called. You could try to do something similar with your sequencing mechanism.
Luc Duponcheel (Sep 13 2025 at 16:09):
Hello, I found out in the source code that the difference has been introduced in the nightly build of 2026-06-21 in the file CoreM.lean at line 670, setting the new code generator as default code generator to true.
Anyway, I have no problems with my sequential fibonacci only with my Task based parallel fibonacci. So, maybe, it is related to Task code as well (.cpp code because most .lean code involved is extern).
I have raised an issue :
https://github.com/leanprover/lean4/issues/10372
hopefully it will be considered by the compiler guru's.
Ruben Van de Velde (Sep 13 2025 at 16:18):
Impressive crystal ball you've got there!
Last updated: Dec 20 2025 at 21:32 UTC