As a fun hobby project, I'm writing a simple bytecode VM and a compiler from a basic high-level language to the said bytecode.
(Offtopic: the compiler is inspired by Jack Crenshaw's awesome tutorial - Let's Build A Compiler. Really worth a read if you aren't familiar with it).
Writing the VM got me thinking - what is the minimum set of operations the VM has to support for it to allow programming of any application?
I am not talking about Turing completeness - this simply has to do with being able to compute any program.
I am referring to the set of IO operations the VM would have to support - through dedicated bytecode opcodes, through built-in functions or through some other mechanism - for it to allow any kind of IO:
A real application might have to read a file from disk, write to a different one, communicate on TCP or UDP, create complex graphical interfaces on the screen, listen to audio on the microphone, etc.
Surely the developers of, for example, the Java Virtual Machine haven't created dedicated bytecodes or built-in functions for each possible IO operations. Same as to the Python interpreter.
Now that I'm thinking about it - the same can be said about compilers: no compiler writer for a particular language can support every possible IO operation.
So how would a VM (or compiler) designer go about achieving this?