Thank you for sharing.
My ancient ruby code and nodejs code all broke because I didn't pin dependencies. As a result I've got software that is unrunnable.
More software shall be unrunnable as time goes on, I don't know many trends that prevent software from being unbuildable and unrunnable due to change except maybe repeatable builds and hermetic builds.
Given platform toolchains complexity and libc versions and complexity of static Vs dynamic linking, I suspect preserving software is very difficult.
It seems doing + - ×÷ on numbers is not the difficult part of computers but arranging information into the right places in order to do it.
Logistics and package management are difficult to get right.
I think Java got something right. Bytecode is longlasting. Can rewrite the JVM for new platforms and architectures.
I am writing my own language and it is implemented as an assembly interpreter and a compiler for that interpreter. This lets me get development speedier.
What the hard thing I think is more interesting than bytecode or virtual machines is INTEROP.
The Amd64 SysV Binary interface of registers for C calling interface and the System call Interface of Linux.
Mozilla abandoned XPCOM extensions, part of the reason was performance of the interop between JavaScript and C++.
If I could run a virtual machine and interop with modern code that would mean the software was useable for longer.