Log in

View Full Version : A simple AvS+ script with a recursive function makes VD2 crash !


dani75
16th October 2025, 22:42
Hi there !
I wanted to develop a script for AvS+ with a recursive function.
This function should call itself a big number of times, for example 1000 times.
When opening this script with VirtualDub2 in 64 bits mode, this last crashed after a while.
Trying to investigate, I simplified the script and added debug. When the script doesn't make the player crash, it displays a final picture "Fini !". The debug allows to know the number of effective recursions reached.
I notices that the number of recursions requested affects the behaviour : doesn't crash with 500 times for example in any condition. I tested with 32 and 64 bits : with the function calling itself 1000 times, the effective number of calls performed before the crash in 64 bits is around 580. But with 32 bits, the 1000 times are performed without crash. I also tested this script with another player (MPC-HC), and its behaviour was the same.
Considering that a recursive function needs more memory, I added a SetMemoryMax(4096) : nothing changes.

Reading the documentation, I was unable to find where there could be a limit for a recursive function calling itself, especially with AviSynth in 64 bits mode.

Could you try this script and tell me what happens ?
Has anybody a workaround for that, or an explanation ?

Here is my script with 1000 recursions (the RT_Stats libraries should be in the [path] to be customised) :
----------------
Global RepertoireDLL64 = "C:\[path]\Plugins64"
Global RepertoireDLL32 = "C:\[path]\Plugins32"
Global RepertoireDLL26 = "C:\[path]\Plugins26"

Global AviSynth26 = VersionString.LeftStr(13) == "AviSynth 2.60"
Global AviSynth32 = VersionString.RightStr(5).LeftStr(4) == "i386"
Global AviSynth64 = VersionString.RightStr(7).LeftStr(6) == "x86_64"

AviSynth64 ? LoadPlugin(RepertoireDLL64+"RT_Stats_x64.dll") : AviSynth32 ? LoadPlugin(RepertoireDLL32+"RT_Stats_x86.dll") : AviSynth26 ? LoadPlugin(RepertoireDLL26+"RT_Stats_25.dll") : Nop

RT_DebugF("TestRecursion : Debut")
i = Compte(0,1000,1)
RT_DebugF("TestRecursion Fin : %d",i)
Return BlankClip.Subtitle("Fini !")

Function Compte(Int Ind,Int Fin, Int Incr) {
RT_DebugF("TestRecursion : %d",Ind)
Return (Ind < Fin) ? Compte(Ind+Incr,Fin,Incr) : Ind
}

qyot27
17th October 2025, 02:28
With 1000 as per the example, it completes here:
$ fastfetch
./oydmMMMMMMmdyo/.
:smMMMMMMMMMMMhs+:++yhs: -----------------
`omMMMMMMMMMMMN+` `odo` OS: Ubuntu Budgie 25.10 (Questing Quokka) x86_64
/NMMMMMMMMMMMMN- `sN/ Kernel: Linux 6.17.0-5-generic
`hMMMMmhhmMMMMMMh sMh` Uptime: 15 hours, 10 mins
.mMmo- /yMMMMm` `MMm. Packages: 3717 (dpkg), 14 (snap)
mN/ yMMMMMMMd- MMMm Shell: bash 5.2.37
oN- oMMMMMMMMMms+//+o+: :MMMMo Display (VSC342C): 1920x1080 @ 60 Hz in 23" [External] *
m/ +NMMMMMMMMMMMMMMMMm. :NMMMMm Display (BBY0000): 1920x1080 @ 60 Hz in 32" [External]
M` .NMMMMMMMMMMMMMMMNodMMMMMMM DE: Budgie 10.9.3
M- sMMMMMMMMMMMMMMMMMMMMMMMMM WM: Mutter(Budgie) (X11)
mm` mMMMMMMMMMNdhhdNMMMMMMMMMm Theme: QogirBudgie-Light [GTK2/3/4]
oMm/ .dMMMMMMMMh: :dMMMMMMMo Icons: Moka [GTK2/3/4]
mMMNyo/:/sdMMMMMMMMM+ sMMMMMm Font: Noto Sans (10pt) [GTK2/3/4]
.mMMMMMMMMMMMMMMMMMs `NMMMm. Cursor: DMZ-White (24px)
`hMMMMMMMMMMM.oo+. `MMMh` Terminal: tilix 1.9.6
/NMMMMMMMMMo sMN/ Terminal Font: Noto Mono (10pt)
`omMMMMMMMMy. :dmo` CPU: Intel(R) Core(TM) i5-9400 (6) @ 4.10 GHz
:smMMMMMMMh+-` `.:ohs: GPU: Intel Arc A770 @ 2.40 GHz [Discrete]
./oydmMMMMMMdhyo/. Memory: 2.41 GiB / 62.73 GiB (4%)
Swap: 0 B / 8.00 GiB (0%)
Disk (/): 25.20 GiB / 203.08 GiB (12%) - ext4
Disk (/home): 307.07 GiB / 3.67 TiB (8%) - ext4
Locale: en_US.UTF-8



$ cat test.avs
RT_DebugF("TestRecursion : Debut")
i = Compte(0,1000,1)
RT_DebugF("TestRecursion Fin : %d",i)
Return BlankClip(pixel_type="YUV420P8").Subtitle("Fini !")

Function Compte(Int Ind,Int Fin, Int Incr) {
RT_DebugF("TestRecursion : %d",Ind)
Return (Ind < Fin) ? Compte(Ind+Incr,Fin,Incr) : Ind
}

$ mpv test.avs
[osd/libass] Failed to load fonctconfig fonts!
RT_DebugF: TestRecursion : Debut
RT_DebugF: TestRecursion : 0
RT_DebugF: TestRecursion : 1
RT_DebugF: TestRecursion : 2
RT_DebugF: TestRecursion : 3
RT_DebugF: TestRecursion : 4
RT_DebugF: TestRecursion : 5
RT_DebugF: TestRecursion : 6
RT_DebugF: TestRecursion : 7
RT_DebugF: TestRecursion : 8
RT_DebugF: TestRecursion : 9
RT_DebugF: TestRecursion : 10
[...snip...]
RT_DebugF: TestRecursion : 991
RT_DebugF: TestRecursion : 992
RT_DebugF: TestRecursion : 993
RT_DebugF: TestRecursion : 994
RT_DebugF: TestRecursion : 995
RT_DebugF: TestRecursion : 996
RT_DebugF: TestRecursion : 997
RT_DebugF: TestRecursion : 998
RT_DebugF: TestRecursion : 999
RT_DebugF: TestRecursion : 1000
RT_DebugF: TestRecursion Fin : 1000
● Video --vid=1 (rawvideo 640x480 24 fps)
● Audio --aid=1 (pcm_s16le 1ch 44100 Hz 706 kbps)
MESA-INTEL: warning: ../src/intel/vulkan/anv_formats.c:981: FINISHME: support more multi-planar formats with DRM modifiers
AO: [pipewire] 44100Hz mono 1ch s16
[osd/libass] Failed to load fonctconfig fonts!
VO: [gpu-next] 640x480 yuv420p
AV: 00:00:09 / 00:00:10 (99%) A-V: 0.000 DS: 2.5000/0
Exiting... (End of file)


Although when I upped it to 10,000 iterations, it segfaulted at 5077:
RT_DebugF: TestRecursion : 5071
RT_DebugF: TestRecursion : 5072
RT_DebugF: TestRecursion : 5073
RT_DebugF: TestRecursion : 5074
RT_DebugF: TestRecursion : 5075
RT_DebugF: TestRecursion : 5076
RT_DebugF: TestRecursion : 5077

Thread 14 "opener" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0x7fffb77fe6c0 (LWP 31115)]
0x00007ffff186db21 in __printf_buffer (buf=buf@entry=0x7fffb6fff3e0, format=0x7fffa8020ae7 "%d", ap=ap@entry=0x7fffb6fff430, mode_flags=mode_flags@entry=0)
at ./stdio-common/vfprintf-internal.c:601
warning: 601 ./stdio-common/vfprintf-internal.c: No such file or directory
(gdb) bt
#0 0x00007ffff186db21 in __printf_buffer (buf=buf@entry=0x7fffb6fff3e0, format=0x7fffa8020ae7 "%d", ap=ap@entry=0x7fffb6fff430, mode_flags=mode_flags@entry=0)
at ./stdio-common/vfprintf-internal.c:601
#1 0x00007ffff188f52c in __vsprintf_internal
(string=<optimized out>, maxlen=maxlen@entry=18446744073709551615, format=<optimized out>, args=args@entry=0x7fffb6fff430, mode_flags=mode_flags@entry=0)
at ./libio/iovsprintf.c:62
#2 0x00007ffff186a947 in __sprintf (s=<optimized out>, format=<optimized out>) at ./stdio-common/sprintf.c:30
#3 0x00007fffb6fbc297 in RT_DebugF (args=..., user_data=0x0, env=0x7fffa801fdc0) at $HOME/mpv-build-deps/rt_stats/RT_Debug.cpp:201
#4 0x00007fffb69f3977 in FilterConstructor::InstantiateFilter() const () at /usr/local/lib/libavisynth.so
#5 0x00007fffb6a2b0db in ScriptEnvironment::Invoke_(AVSValue*, AVSValue const&, char const*, Function const*, AVSValue const&, char const* const*, InternalEnvironment*, bool) () at /usr/local/lib/libavisynth.so
#6 0x00007fffb6a42330 in ThreadScriptEnvironment::Invoke_(AVSValue*, AVSValue const&, char const*, Function const*, AVSValue const&, char const* const*) ()
at /usr/local/lib/libavisynth.so
#7 0x00007fffb6a72451 in ExpFunctionCall::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#8 0x00007fffb6a701aa in ExpExceptionTranslator::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#9 0x00007fffb6a7020b in ExpLine::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#10 0x00007fffb6a6ff94 in ExpSequence::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#11 0x00007fffb6a6ff0f in ExpRootBlock::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#12 0x00007fffb6a802dc in ScriptFunction::Execute(AVSValue, void*, IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#13 0x00007fffb69f3977 in FilterConstructor::InstantiateFilter() const () at /usr/local/lib/libavisynth.so
#14 0x00007fffb6a2ace2 in ScriptEnvironment::Invoke_(AVSValue*, AVSValue const&, char const*, Function const*, AVSValue const&, char const* const*, InternalEnvironment*, bool) () at /usr/local/lib/libavisynth.so
#15 0x00007fffb6a42330 in ThreadScriptEnvironment::Invoke_(AVSValue*, AVSValue const&, char const*, Function const*, AVSValue const&, char const* const*) ()
at /usr/local/lib/libavisynth.so
#16 0x00007fffb6a72451 in ExpFunctionCall::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
#17 0x00007fffb6a7060c in ExpConditional::Evaluate(IScriptEnvironment*) () at /usr/local/lib/libavisynth.so
--Type <RET> for more, q to quit, c to continue without paging--

dani75
17th October 2025, 08:44
Thank you qyot27.
Following your assumption, therefore there should be a limit in each case, depending of the library used.
On my PC, with the AvS+ 32 bits library (with which my script didn't make VD2 crash previously with 1000 iterations), actually the limit is 1074.
EDIT: I also tested with the old AviSynth 2.6.0 library and the old VirtualDub : crash happens with 1871 iterations, which is better, but insufficient.
I find that this is a weak number and prevents from writing recursive scripts.
Therefore the AviSynth documentation is wrong here :
Block statements
[...]
Features enabling construction of block statements
The list below briefly presents the features making possible the creation of block statements in your script. Listed first are the more obvious ones, followed by those that are somewhat more esoteric and require a little digging inside the Avisynth documentation and experimenting with test cases to discover them.
[...]
. Recursion (ie calling a function from inside that function) can be used for traversing elements of a collection.
Thus, for..next, do..while, do..until loops can be constructed by using recursion

StainlessS
19th October 2025, 02:01
Global RepertoireDLL64 = "C:\[path]\Plugins64"
Global RepertoireDLL32 = "C:\[path]\Plugins32" # Presume this is non v2.6 32 bit plugz
Global RepertoireDLL26 = "C:\[path]\Plugins26"



AviSynth64 ? LoadPlugin(RepertoireDLL64+"RT_Stats_x64.dll") : AviSynth32 ? LoadPlugin(RepertoireDLL32+"RT_Stats_x86.dll") : AviSynth26 ? LoadPlugin(RepertoireDLL26+"RT_Stats_25.dll") : Nop



I think this should be

# Global RepertoireDLL64 = "C:\[path]\Plugins64"
# Global RepertoireDLL32 = "C:\[path]\Plugins32" # Presume this is non v2.6, x86/32-bit plugz
# Global RepertoireDLL26 = "C:\[path]\Plugins26"


AviSynth64 ? LoadPlugin(RepertoireDLL64+"RT_Stats_x64.dll") : AviSynth26 ? LoadPlugin(RepertoireDLL26+"RT_Stats_x86.dll") : LoadPlugin(RepertoireDLL32+"RT_Stats_25.dll") : Nop

dani75
19th October 2025, 08:18
Hi StainlessS, you presume right.
This is a trick to keep the same script while the library installed is AvS+ or the old AvS 2.6.0.
Plugins26 is true when I have Avs 2.6.0 installed, Plugins 32 and respectively Plugins64 is true when I have AvS+ installed according to the player I use to load the script is 32 or 64 bits.

StainlessS
19th October 2025, 12:43
OOps, the final ": Nop" should be chopped off. [it also would throw an error if left as is]
ie, If x64 then x64 else if avs2.6 then avs2.6 else avs2.58

dani75
19th October 2025, 14:52
Hi StainlessS.
The final ":Nop" doesn't throw an error by me. I used a ternary operation, which every 2nd et 3rd members returns nothing.
Anyway I got rid of my problem by founding a workaround.
In the wiki of avisynth, here (http://avisynth.nl/index.php/Control_structures), there is the following explanation :
The fourth element (recursion) is the general tool provided by the syntax for operating on collections and calculating expressions of any complexity. It is also, currently, the only tool.&
This does not mean that there is something that you can't do inside the AviSynth script language; in fact recursion together with assignment can achieve everything an imperative language with looping constructs can do—they just do so in a way that most people without functional programming skills are not familiar.

So, starting from my own requirements, I believe that in most cases, replacing a recursive function by imperative instructions should boil down to 2 cases, considering that this is equivalent to a classic loop:
1) case in which the number of calls is known in advance
2) case in which the number of calls is not known in advance but there is a "Break" condition

In case 1, the recursive call is replaced by a "for" loop. The body of the recursive function is transferred to the body of the loop, and the 2 or 3 arguments of the "for" instruction are used to manage the start and the end of the loop, replacing the initial and end conditions of the recursive function, while the increment controls the equivalent way the new call to the recursive function was done through its new arguments. In my case (in my first post above), here is how the recursion function and its calling function are transformed :
INITIAL SCRIPT
RT_DebugF("TestRecursion : Start")
i = Compte(0,1000,1)
RT_DebugF("TestRecursion, End = %d",i)
Return BlankClip.Subtitle("End !")
Function Loop(Int Ind,Int Fin, Int Incr) {
RT_DebugF("TestRecursion, Count = %d",Ind)
Return (Ind < Fin) ? Loop(Ind+Incr,Fin,Incr) : Ind
}

FINAL SCRIPT
RT_DebugF("TestRecursion : Start")
For(i,1000,1){RT_DebugF("TestRecursion, Count = %d",i)}
RT_DebugF("Test For, End = %d",i)
Return BlankClip.Subtitle("End !")

In AviSynth+, the solution is to use a "For" instruction. In old AviSynth, the same "For" instruction can be used, but within a file that should be loaded through a "GImport" instruction in the upper level file, preceded by a "LoadPlugin("GScript.dll")".

In case 2, a Break instruction is required according to a specific condition, then use a "While loop". For example :
INITIAL SCRIPT
Start = 0
Lim = 1000000
Incr = 1
End = Loop(Start,Lim,Incr)
RT_DebugF("TestRecursion, End = %d",i)
Function Loop(i,j,k){
RT_DebugF("TestRecursion, Count = %d",i)
<body of the function>
BreakCondition = <formula returning a boolean>
if(BreakCondition || i > k){Break}
Return Loop(i+k,j,k)
}

In AviSynth+, the solution is to use a "While" instruction. In old AviSynth, the same "While" instruction cannot be used, because the "Break" instruction is not recognized par the GScript plugin; but it is possible to transfer the break condition inside the "While" argument, and therefore to put the script within a file that should be loaded through a "GImport" instruction in the upper level file, preceded by a "LoadPlugin(GScript.dll" :

FINAL SCRIPT
Start = 0
Lim = 1000000
Incr = 1
BreakCondition = False
While (i <= Lim && !BreakCondition){
RT_DebugF("TestRecursion, Count = %d",i)
<body of the function>
BreakCondition = <formula returning a boolean>
i = i + 1
}
RT_DebugF("TestRecursion, End = %d",i-1)

This worked perfectly by me without any error or crash.