20210619 Development R7 M2 Kernel Dynamic Binding of Computational Units - orbitalfoundation/wiki GitHub Wiki

Kernel Dynamic Binding of Computational Units

This is a review on future options on how to load and manage computation.

Review

Briefly it is worth reviewing some of the goals for Orbital as they relate to computation:

  1. Orbital is one sense is a typical application running platform that lets users fetch and run rich persistent apps in a sandboxed way.

  2. Orbital is novel however because it lets users compose lightweight apps out of large pieces on the fly, letting them wire pieces together to make new apps at runtime. A good example might be a friend finder app built out of 3 pieces at runtime: a camera module, a face segmentation algorithm module, a display module to draw a box around a friends head when it become visible to the camera. Because Orbital formally exposes units of computation, an app fetched over the wire can be simply a very small description of dependencies and relationships. What makes a "browser" different from say an App Store is that even if an app store has hundreds of thousands of apps in it, there is no possibility of late binding or dynamic composition of logic itself. Apps in app stores are static blobs that treat their users as purely consumers and not creators; they don't expose capabilities to wire apps together. It's not necessarily that all users compose apps, but that any users can compose apps is useful.

  3. Units of computation (or modules, or whatever we call them) are intended to be of intermediate granularity. We're NOT looking to provide a kind of FAAS / Fastly Lucet style functionlets with ultra-low startup latency that handles a single event and then exits. Rather we're thinking of code blobs as persistent, with threading, that can observe state over time, talk to other code modules, have state and present information to the user. For example a camera, a segmenter and a display. Also see https://rise.cs.berkeley.edu/projects/cloudburst/ and https://rise.cs.berkeley.edu/projects/erdos/ and https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/spidermonkey.md .

Common tools and solutions for letting computational units talk to other computational units

  1. CPU Specific Linking. This is typified by source code compiled to specific CPU's and then run inside of full blown operating systems. Kernels tend to manage larger units of computation that are either "applications" or "libraries". We see static linking (the library is bound into the application) and/or dynamic linking (the library binds at runtime). See: https://opensource.com/article/20/6/linux-libraries.

    Security in this approach is historically defined by permissions levels set by the kernel. There is also support for sandboxing such as in OpenBSD: https://sn0int.readthedocs.io/en/stable/sandbox.html . Entire UNIX Operating systems are often treated as single sandboxes because they represent a highly useful and well understood way to manage a group of related pieces: https://en.wikipedia.org/wiki/Docker_(software) .

    Technically, for an application here to rely on a library it needs some kind of "stub" that formally specifies the interfaces for the external resources. For functions this can be a function stub of some kind. The linker then has a few ways to actually bind computational units together, it can physically rewrite the calls, directly gluing a source call invocation to a target piece of code, or, more commonly, it can simply replace an intermediate jump table with new function pointers. Also in Objective C we see message handlers as a glue layer as well.

  2. CPU Agnostic Linking. WASM and tools like WASMTIME are taking the write-once-run-anywhere idea of portable cpu-independent computation popularized by Java and building an open standard not owned by any one party. See: https://emscripten.org/ and https://github.com/WAVM/WAVM

    In this process they're also defining a way to link units of computation to each other and as well adding sandboxing considerations. See https://sendilkumarn.com/blog/wasi-with-wasmtime/ and https://bytecodealliance.org/articles/announcing-the-bytecode-alliance and https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/spidermonkey.md.

    For static linking we see https://emscripten.org/ . For dynamic or embedded runtime linking (such as in an IoT devices) we see proposals such as : https://github.com/WebAssembly/module-linking/blob/master/proposals/module-linking/Explainer.md . Also see WebAssembly Interface types : https://hacks.mozilla.org/2019/08/webassembly-interface-types/ .

    Notably Bytecode Alliance is packaging javascript (in an interpreted mode not compiled mode) as a web assembly module itself:

    https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly

  3. Scripting. Another approach is to use a lightweight scripting language as the intermediary and then to "call down" to larger units of computation on demand. See https://www.secondstate.io/articles/deno-webassembly-rust-wasi/ .

  4. Messaging. This is the "I give up" approach in a sense. We see this pattern often. Often independent applications, processes or threads, perhaps on different machines, are forced to use message queues to communicate with each other. There are issues around marshaling arguments, making sure that parameters are constrained and then dealing with message queues. Inter-application communication in this way is typically much slower and is via some kind of messaging bus; an inter-process communication channel, a piece of shared memory that is passed back and forth, tcp/ip connections and so on. There's a long history of SOAP, XML, JSON, protocol buffers (see https://developers.google.com/protocol-buffers ) for exchanging state and computer science literature is littered with concepts such as REST, RPC and so on.

  5. NPM/Cargo/etc. It is worth noting that package managers and package collections also play a part in defining units of computation. Typically once fetched a package defines its interfaces and then code can transfer flow across these boundaries. See this article for a general critique on function interface declarations: https://jsoverson.medium.com/a-mistake-more-expensive-than-null-6b1c52338014?source=linkShare-2c7238eb9ade-1624124534 . Also see https://www.annehelmond.nl/wordpress/wp-content/uploads/2015/08/Helmond_WebAsPlatform.pdf .

Orbital Approach

  1. Messages and Threads. For now we have a dynamic module loader and runner that treats each module as a separate thread and gives it a message bus. So we are only using a message bus at the moment - we are not doing any static or dynamic direct binding of code. This is admittedly slower at runtime. The plan is to eventually allow for a richer module loading capability with runtime binding of shared libraries using wasm module linking (later). My reasoning here is that for now people can statically link any static libraries, and really the focus should be on how larger units of computation can talk to each other at runtime such as with messages or shared memory (and this is something that will always be needed).

  2. Scripting. We have a javascript module that can load and run javascript. This module provides a javascript sandbox that can actually talk to the rest of the system, so from javascript you can order the system as a whole to load or unload modules and wire modules together. It acts as a scripting glue, making it easier to drive the rest of the system.

  3. Index.js. An "application" in Orbital is specifically a "index.js" file. This acts as a "manifest" which literally just contains a sequence of commands in javascript telling Orbital what modules to load, how to wire them to each other, and what to do overall. It's similar to an index.html in a traditional browser. This is somewhat similar to the idea of scripting; using javascript as a scripting language, however it is not quite the same because we then go a bit further. Orbital does specifically provide a JSON based declarative grammar that can be invoked inside of index.js and that allows users to more easily define complex applications as a series of declarations with wires. The declarative grammar is a cornerstone piece of the project because it's the way that Orbital becomes visible to people who cannot program. Later on the intention is to write a module that presents a visual wireframe editor view (similar to a visual programming language view) to allow people to wire up code modules together at runtime.

  4. UX. Orbital presents the user with a pretty user interface. That interface is itself written as just another module, but it provides high level powers to users. It looks very much like a web-browser or a traditional desktop interface. It lets users type in an URL, fetch whatever is at that URL and then does its best to show whatever is at that URL. If the URL contains an application manifest then the application is loaded and run. The manifest can fetch other modules, wire them together, produce views. And the user always retains a high level executive power to stop an app, or push it to the background (where it can keep running).

  5. Display Module. In traditional browsers the display is accessed via the DOM. We do have a pseudo-dom with a declarative grammar, but ultimately the core binding between modules for our system is pure javascript. So any capability such as WebXR is mostly realized in terms of it being exposed as an API to javascript. There however is some argument that the display module could leverage Servo [ https://github.com/paulrouget/servo-embedding-api ] but we are not doing so at the moment - we are using Bevy for now. Longer term we will build a simpler api like interface to WGPU (Bevy imposes high level patterns we don't feel like being obligated to use) and possibly Flutter or other layout engines. 2D is less important to us however, we mostly just want 3d rendering environment. We do also want to support WebXR and so on - this seems possible by taking https://lib.rs/gh/servo/webxr/webxr and exposing it to a javascript context.