Io Implementation Notes


    Conventions

      Io's C implementation uses what you might call an object oriented coding style as described here:

      http://www.dekorte.com/docs/essays/ooc/

      For this reason, sometimes Io's C structures will be referred to as "objects" in this document. It is recommended that these conventions be followed when modifying Io, or adding new addons. The core of Io will be C only (no C++, Objective-C, etc) and addons should avoid using non C languages as much as possible.

      Io specific primitive data structures and addons should be prefixed with "Io".

    Orgnaization

      Io's source code is divided into several areas:

      vm/base/

      Reusable data structure and utility library - code not specific to Io. These files do not reference any external files other than the ANSI C standard library headers. Example objects: List, Hash, Parser

      vm

      Io runtime system, and core primitives. These files only reference those in Io/base and ANSI C standard library headers. Example objects: IoState, IoMessage, IoObject, IoNumber, IoSequence

      bindings

      Additional primitives and extensions to core Io primitives.

      libs

      External libraries which the bindings make use of.

    Runtime

      How code is represented

      Io's base instruction is a message send. When compiled, Io code is simply turned into a message tree. Each node of the tree is an IoMessage structure(object). The structure of an IoMessage looks like this:

      IoMessage
        name (a literal IoString containing the name of the message)
        arguments (list of other IoMessages)
        attachedMessage (another IoMessage or NULL)
        nextMessage (another IoMessage or NULL)
      
      How code is executed

      If you flattened this message tree out in memory and wrote a function that walked through it and maintained a stack, you'd have a traditional bytecode based VM with a single instruction type(send message). But Io doesn't do this(at least not at the moment). Instead a it calls a function on the top message object which gets called recursively to execute the messages.

      A message is executed in a context. A context is made up of:

        message (the current message)
        target (a reference to an Io primitive)
        locals (an IoObject used to store local variables)
      

      These function calls come with a performance hit over bytecode systems, but it greatly simplifies the implementation (a simple recursive call on a small function instead off a giant switch statement). And Io manages to still performs as well (or better) than the popular bytecode based scripting languages.

    Garbage Collection

      You only need to do marks on references to actual Io primitives. Memory allocated within a primitive for internal use is up to the primitive to deallocate. Io uses an incremental garbage collector which means it does a little garbage collection work on each allocation instead of all at once with a large mark and sweep. Io's incremental collector uses a "write-barrier" which means that when an object adds sets a local reference to another object, it needs to tell the collector about it. This is done with the IOREF() macro.

    Coroutines and Exceptions

      Io's coroutines and exceptions use setjmp, longjmp and some related tricks. Be careful when using Io with C++ as C++ can allocate its objects on the stack. If a long jump occurs while C++ has such objects on the stack, their destructor methods may not be called and this will result in a memory leak if their constructors allocate heap memory. See this paper for info on safely using C++ with coroutines.

      As Andrew Cooke put it: "...it's probably not as bad as it sounds. I guess while you're in your own code nothing bad is going to happen. You only have to be careful if you "re-enter" Io. This might be what you are thinking anyway, or what you've said that I've missed, or what the warning you quoted said, when you first posted... so you can probably (i'm guessing here) use Io coroutines and exceptions as long as they are within Io. There should only be a problem if you have a C++ routine which is "in the middle of doing something" when a jump occurs. If a jump occurs before or after calling C++ then there's no problem (bad things might happen if, say, you call C++ from Io which itself calls Io, from inside C++, which then triggers an exception; another example might be calling the C/C++ routine that Io uses to generate exceptions from C++)."

    Performance

      Some tricks Io uses to improve performance:

      • Objects use perfect hashes to store slots
      • protos links are stored in a C array for quick access during lookups