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?

  • 2
    I think you're confusing language implementation with runtime. A runtime will expose APIs that provide for I/O. Some languages have a common runtime, others will need to integrate with the O/S or platforms' APIs individually. The language itself doesn't need to have any native I/O capability. – John Wu Oct 1 '18 at 19:41
  • I am not talking about Turing completeness - this simply has to do with being able to compute any program -- Those two things are the same. – Robert Harvey Oct 1 '18 at 19:44
  • @JohnWu Thanks for the comment. Can you elaborate on that? How is the Runtime different from the Interpreter or Virtual Machine running the code? – Aviv Cohn Oct 1 '18 at 20:59
  • The minimum set of operators is one: Alan Turing's copy, minus, branch on negative. – shawnhcorey Oct 3 '18 at 12:38

Step 1: determine which platform/operating system you are targetting.

Step 2: offer access to the full capabilities of that platform.

For example:

  • If you are developing a language that is supposed to run on a JVM, then you only have to emit JVM bytecode and be able to use existing classes. (These classes might use native implementations to supply their functionality, and are not necessarily limited by the JVM).

  • If you are developing a language that is supposed to run on an Unix variant, then offer access to the syscall interface. Then you can ask the Kernel to do whatever you desire, e.g. send network packets. Go is one of the few languages that does this.

    In practice, you would offer a foreign function interface so that your programs can access the C standard library and other C functions, such as those from POSIX (which includes wrappers for many syscalls) and other libraries.

  • If you are developing a language that is supposed to be used to write web applications, you don't really have a choice aside from transpiling to JavaScript so that you can access all Web APIs. E.g. technologies like WebAssembly allow you to compile code that runs in a browser, but it doesn't offer access to the JS APIs – you have to write bindings in JavaScript to make functions accessible to WebAssembly. This makes WebAssembly highly impractical for general web development. In contrast, TypeScript offers access to all Web APIs because it is transpiled to JavaScript and therefore has full access to the JavaScript platform.

  • Thanks for the comment. Can you elaborate on what you mean by 'foreign function interface'? Do you mean a way to integrate C code into the program, so that the program can just use C syscalls, without having to implement them in the VM itself? – Aviv Cohn Oct 1 '18 at 20:57
  • @AvivCohn In your language implementation, you have a specific protocol for calling functions. A FFI provides such protocols for other languages/implementations. Usually, a FFI allows you to call arbitrary C functions – which also requires that you can deal with pointers and struct layouts. See Python's ctypes library for a straightforward example. – amon Oct 1 '18 at 21:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.