uArt attempts to provide a minimal run-time environment for actors. The goal is to create a bridge between the Actor Model of computation and the realities of modern computer hardware architecture. In particular, we assume a CPU (possibly multi-core) accessing data and executing instructions from a linearly addressable memory.
The primitive actions defined by the Actor Model are:
- Create a new actor with some initial behavior
- Send an asynchronous message to an actor
- Become a new behavior for processing subsequent messages
Resource control is an important issue in an actor implementation. The model abstracts away resource limitations, but a practical implementation must deal with this issue. Our approach to resource control involves the concept of a Sponsor for an actor computation.
We observe that all actor computation occurs in the context of receiving an Event. It seems logical the the Sponsor for a computation should either be the Event or be reachable through the Event. Thus, the Event provides the context for a computation.
Each Create action produces a new uniquely identifiable actor. We use a distinct memory address to identify each actor, assuming a memory-safe computational environment to maintain the security of each actor. Of course, the implementation kernel has privileged access to all actors. Kernel code is "meta" to the actor semantics, and should be kept to an absolute minimum.
At a minimum, an actor consists of a sequence of CPU instructions representing the actor's behavior. The actor's identity corresponds to the memory address where their behavior starts. Thus, dispatching an Event to an actor is accomplished by preparing an environment and simply jumping to the behavior. We can avoid using a call-stack by making direct jumps rather a "subroutine" call and return. An actor behavior executes as an "atomic" transaction. The behavior indicates completion by simply jumping back to the kernel. We expect to have separate jump addresses to distinguish between successful completion and signaling some kind of exception.
The execution environment for an actor's behavior consists of at least these critical components:
- The unique Event being delivered and processed
- The unique identity of the target actor
- The code constituting the Behavior of the actor
- The data constituting the State (if any) parameterizing the Behavior
- The data constituting the Message to be delivered
- The Sponsor that controls resources available during processing
Note that these may, in fact, all be components of a composite Event.
Event
+-> Sponsor
+-> Actor
| +-- Behavior
+-- Message
Each Send action produces a new uniquely identifiable Event with a specified Message and a target actor address. The same actor may (of course) be the target of several Events, thus the actor must be referenced, not contained, by the Event. The Sponsor is also likely to be shared, and therefore referenced.
An important distinction can be made between actors (as described above) and value-actors. Value-actors, or simply Values, do not have distinct identities. They represent immutable values. They may be duplicated and/or locally recreated when crossing memory domains. This is an important optimization because it avoids having to route messages back the "the original" in cases where the behavior of the local value is indistinguishable from any/all other copies. Values are still actors, in the sense that they can receive messages. However, they cannot use the Become primitive, so their behavior never changes.
Within a memory domain, only one actor can occupy a particular address. This implies that we can determine matching actor identities by simply comparing memory addresses. We can extend this to a small set of pre-defined Values by encoding them directly in the bits of a "register". These are effectively synthetic actor addresses. The type of the value (and thus it's immutable behavior) and the value itself are both encoded in the address. The least-significant bits indicate the type of data stored.
2#..xxxxx1 -- Integer ring
2#..xxx000 -- String/Symbol
2#..xxx010 -- Structure
2#..xxx100 -- Reference
2#..000100 -- null (a pre-defined actor reference)
2#..001100 -- true (a pre-defined actor reference)
2#..010100 -- false (a pre-defined actor reference)
2#..xxx110 -- Label/Address
The main resource controlled by the Sponsor is memory. Since each Send and each Create consumes memory, an actor cannot get very much work done without support from a Sponsor. We anticipate some sort of "watch-dog" timer to prevent run-away CPU consumption, but expect most behaviors to be well-behaved by virtue of having been created by a trusted compiler and working within a resource-safe language.
We don't want to be tied to any particular assembly language, so we define an abstract assembly language in terms of basic operations on actor-visible resources. Translating this abstract assembly language into assembly (or machine instructions) for a particular CPU should be straight-forward. LLVM is our immediate target for translation.
In the following descriptions,
reg
indicates a string which names a logical "register"
whose value may be read/written by the instruction.
We assume the assembler can do liveness analysis
and assign logical registers to physical registers on a given CPU.
Several registers are pre-defined to contain components of the current Event. This provides the execution environment for the target actor's behavior.
"_sponsor"
"_self"
"_message"
Registers may contain values or references. The least-significant bits of the register encodes the data-type, as described previously.
{ "action":"literal", "value":value, "result":reg }
The "literal"
action stores a literal value
into the register named by "result"
.
Literal values are limited to numbers, strings, true
, false
, and null
.
All other actions get their values indirectly,
from registers named by strings,
so this is the only way to provide an "immediate" parameter.
Structures are collections of bindings from keys to values. The keys can be of any type, although strings are most common.
{ "action":"new", "type":reg, "result":reg }
The "new"
action creates a new structure value.
The "type"
register contains a reference to the structure type.
A reference to the new structure is stored
into the register named by "result"
.
{ "action":"load", "struct":reg, "key":reg, "result":reg }
The "load"
action looks up "key"
in "struct"
and stores the corresponding value into "result"
.
If "key"
is not found in "struct"
, then the value is null
.
{ "action":"store", "struct":reg, "key":reg, "value":reg }
The "store"
action binds "key"
to "value"
in "struct"
.
If value is null
, then "key"
is effectively deleted,
since null
is the "not found" value for "load"
.
Arrays are a special kind of Structure.
They are indexed by numeric keys, starting with zero.
The value of the "length"
key is always one larger
than the largest index under which a value is stored.
Thus, appending to the array is accomplished
by storing the new element at the current value of "length"
.
{ "action":"split", "value":reg, "at":reg, "head":reg, "tail":reg }
The "split"
action create two new arrays from an existing array "value"
.
The array stored in "head"
contains the elements
from the beginning of the array up to, but not including, "at"
.
The array stored in "tail"
contains the elements
starting from "at"
and continuing through the end of the array.
Note that "head"
and/or "tail"
could be empty,
depending on the value of "at"
,
and both have their own "length"
.
The "split"
action also acts on other types of "value"
.
A string is interpreted as an array of integer code-points (for each character).
A number is interpreted as an array of boolean bit-values (most-significant first).
A boolean ignores "at"
, stores itself in "head"
and null
in "tail"
.
A non-array structure separates the value "at"
the specified key
(stored in "head"
) from the rest of the structure (stored in "tail"
).
If "at"
is not directly bound, then the "head"
structure will be empty.
{ "action":"join", "head":reg, "tail":reg, "result":reg }
The "join"
action concatenates two array values.
It is the inverse of "split"
.
It also acts on other types in a corresponding way.
If the types of "head"
and "tail"
are incompatible, the action fails.
{ "action":"compare", "this":reg, "that":reg,
"equal":reg, "less":reg, "more":reg }
{ "action":"add", "this":reg, "that":reg,
"result":reg, "overflow":reg, "zero":reg, "pos":reg, "neg":reg }
{ "action":"sub", "this":reg, "that":reg,
"result":reg, "underflow":reg, "zero":reg, "pos":reg, "neg":reg }
{ "action":"mul", "this":reg, "that":reg,
"result":reg, "overflow":reg, "zero":reg, "pos":reg, "neg":reg }
{ "action":"div", "this":reg, "that":reg,
"result":reg, "modulus":reg, "zero":reg, "pos":reg, "neg":reg }
Actor addresses behave like other values,
except that there is no "literal" format.
They can only obtained through "create"
,
although they can be included in messages
and other structures.
{ "action":"create", "behavior":reg, "result":reg }
{ "action":"send", "target":reg, "message":reg }
An actor is literally the structure
resulting from "create"
and referenced by _self
when a behavior executes.
All "store"
instructions into _self
are effectively partial "become" primitives.
An actor's current behavior
is stored under the "_behavior"
key in _self
.
Normally, instructions are executed in sequence.
When there are no more instructions in the sequence,
control returns to the kernel.
If an instruction fails,
control transfers to the kernel exception handler.
The "fail"
action forces failure.
The "label"
action stores the address of the next instruction
into a named register.
Control can be redirected to a labelled location
by absolute or conditional jumps.
{ "action":"fail" }
{ "action":"label", "name":label }
{ "action":"if", "condition":reg, "true":label, "false":label }
{ "action":"jump", "label":label }
The sufficiency of our abstract instruction set is illustrated by showing how various actor idioms may be implemented.
Reaching the end of a list of instructions implies successful completion,
thus []
will "succeed" without taking any further action.
We will use square-brackets as syntactic sugar for Array creation.
A transparent forwarder simply sends any message it receives to a delegate.
"forward":[
{ "action":"literal", "value":"delegate", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"delegate" },
{ "action":"send", "target":"delegate", "message":"_message" }
]
A label is just a forwarder that wraps the message in an envelope.
"label":[
{ "action":"literal", "value":"delegate", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"delegate" },
{ "action":"literal", "value":"label", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"label" },
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"envelope" },
{ "action":"literal", "value":"label", "result":"key" },
{ "action":"store", "struct":"envelope", "key":"key", "value":"label" },
{ "action":"literal", "value":"content", "result":"key" },
{ "action":"store", "struct":"envelope", "key":"key", "value":"_message" },
{ "action":"send", "target":"delegate", "message":"envelope" }
]
A tag uses the identity (address) of the current actor as a label.
"tag":[
{ "action":"literal", "value":"delegate", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"delegate" },
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"envelope" },
{ "action":"literal", "value":"label", "result":"key" },
{ "action":"store", "struct":"envelope", "key":"key", "value":"_self" },
{ "action":"literal", "value":"content", "result":"key" },
{ "action":"store", "struct":"envelope", "key":"key", "value":"_message" },
{ "action":"send", "target":"delegate", "message":"envelope" }
]
Forward exactly one message.
"one-shot":[
{ "action":"literal", "value":"delegate", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"delegate" },
{ "action":"send", "target":"delegate", "message":"_message" },
{ "action":"literal", "value":"Array", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Array" },
{ "action":"new", "type":"Array", "result":"behavior" },
{ "action":"literal", "value":"_behavior", "result":"key" },
{ "action":"store", "struct":"_self", "key":"key", "value":"behavior" }
]
An evaluator for pure-functional Lambda Calculus can be implemented with a very small number of actor behaviors.
A Binding responds to a lookup
message by
returning the bound value
(if name
matches),
or delegating to the next environment
.
"binding":[
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"action" },
{ "action":"literal", "value":"lookup", "result":"value" },
{ "action":"compare", "this":"action", "that":"value", "equal":"equal" },
{ "action":"if", "condition":"equal", "false":"l_end" },
{ "action":"literal", "value":"name", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"name" },
{ "action":"literal", "value":"x", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"x" },
{ "action":"compare", "this":"name", "that":"x", "equal":"equal" },
{ "action":"if", "condition":"equal", "false":"l_delegate" },
{ "action":"literal", "value":"value", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"value" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"customer" },
{ "action":"send", "target":"customer", "message":"value" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_delegate" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"environment" },
{ "action":"send", "target":"environment", "message":"_message" },
{ "action":"label", "name":"l_end" }
]
For comparison, the equivalent Humus code would be:
LET binding(x, value, environment) = \_message.[
CASE _message OF
(customer, #lookup, $x) : [
SEND value TO customer
]
(_, #lookup, _) : [
SEND _message TO environment
]
END
]
An Identifier responds to an eval
message by
looking itself up in the provided environment.
It responds to a bind
message by
extending the provided environment
with a new binding.
"identifier":[
{ "action":"literal", "value":"x", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"x" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"action" },
{ "action":"literal", "value":"eval", "result":"value" },
{ "action":"compare", "this":"action", "that":"value", "equal":"equal" },
{ "action":"if", "condition":"equal", "true":"l_eval" },
{ "action":"literal", "value":"bind", "result":"value" },
{ "action":"compare", "this":"action", "that":"value", "equal":"equal" },
{ "action":"if", "condition":"equal", "true":"l_bind" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_eval" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"customer" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"environment" },
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"message" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"customer" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"literal", "value":"lookup", "result":"value" },
{ "action":"store", "struct":"message", "key":"key", "value":"value" },
{ "action":"literal", "value":"name", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"x" },
{ "action":"send", "target":"environment", "message":"message" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_bind" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"customer" },
{ "action":"literal", "value":"value", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"value" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"environment" },
{ "action":"literal", "value":"binding", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"behavior" },
{ "action":"create", "behavior":"behavior", "result":"extended" },
{ "action":"literal", "value":"x", "result":"key" },
{ "action":"store", "struct":"extended", "key":"key", "value":"x" },
{ "action":"literal", "value":"value", "result":"key" },
{ "action":"store", "struct":"extended", "key":"key", "value":"value" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"extended", "key":"key", "value":"environment" },
{ "action":"send", "target":"customer", "message":"extended" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_end" }
]
For comparison, the equivalent Humus code would be:
LET identifier(x) = \_message.[
CASE _message OF
(customer, #eval, environment) : [
SEND (customer, #lookup, x) TO environment
]
(customer, #bind, value, environment) : [
CREATE extended WITH binding(x, value, environment)
SEND extended TO customer
]
END
]
"application":[
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"action" },
{ "action":"literal", "value":"eval", "result":"value" },
{ "action":"compare", "this":"action", "that":"value", "equal":"equal" },
{ "action":"if", "condition":"equal", "true":"l_eval" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_eval" },
{ "action":"literal", "value":"application_k_oper", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"behavior" },
{ "action":"create", "behavior":"behavior", "result":"k_oper" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"customer" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"k_oper", "key":"key", "value":"customer" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"environment" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"k_oper", "key":"key", "value":"environment" },
{ "action":"literal", "value":"opnd_expr", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"opnd_expr" },
{ "action":"literal", "value":"opnd_expr", "result":"key" },
{ "action":"store", "struct":"k_oper", "key":"key", "value":"opnd_expr" },
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"message" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"k_oper" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"literal", "value":"eval", "result":"value" },
{ "action":"store", "struct":"message", "key":"key", "value":"value" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"environment" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"environment" },
{ "action":"literal", "value":"opnd_expr", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"oper_expr" },
{ "action":"send", "target":"oper_expr", "message":"message" }
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_end" }
],
"application_k_oper":[
{ "action":"literal", "value":"application_k_opnd", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"behavior" },
{ "action":"create", "behavior":"behavior", "result":"k_opnd" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"customer" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"k_opnd", "key":"key", "value":"customer" },
{ "action":"literal", "value":"operator", "result":"key" },
{ "action":"store", "struct":"k_opnd", "key":"key", "value":"_message" },
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"message" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"k_opnd" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"literal", "value":"eval", "result":"value" },
{ "action":"store", "struct":"message", "key":"key", "value":"value" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"environment" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"environment" },
{ "action":"literal", "value":"opnd_expr", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"opnd_expr" },
{ "action":"send", "target":"opnd_expr", "message":"message" }
],
"application_k_opnd":[
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"message" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"customer" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"customer" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"literal", "value":"apply", "result":"value" },
{ "action":"store", "struct":"message", "key":"key", "value":"value" },
{ "action":"literal", "value":"argument", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"_message" },
{ "action":"literal", "value":"operator", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"operator" },
{ "action":"send", "target":"operator", "message":"message" }
]
For comparison, the equivalent Humus code would be:
LET application(oper_expr, opnd_expr) = \_message.[
CASE _message OF
(customer, #eval, environment) : [
SEND (k_oper, #eval, environment) TO oper_expr
CREATE k_oper WITH \operator.[
SEND (k_opnd, #eval, environment) TO opnd_expr
CREATE k_opnd WITH \operand.[
SEND (customer, #apply, operand) TO operator
]
]
]
END
]
"lambda":[
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"action" },
{ "action":"literal", "value":"eval", "result":"value" },
{ "action":"compare", "this":"action", "that":"value", "equal":"equal" },
{ "action":"if", "condition":"equal", "true":"l_eval" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_eval" },
{ "action":"literal", "value":"operator", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"behavior" },
{ "action":"create", "behavior":"behavior", "result":"operator" },
{ "action":"literal", "value":"formal", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"formal" },
{ "action":"literal", "value":"formal", "result":"key" },
{ "action":"store", "struct":"operator", "key":"key", "value":"formal" },
{ "action":"literal", "value":"body", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"body" },
{ "action":"literal", "value":"body", "result":"key" },
{ "action":"store", "struct":"operator", "key":"key", "value":"body" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"environment" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"operator", "key":"key", "value":"environment" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"formal" },
{ "action":"send", "target":"customer", "message":"operator" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_end" }
],
"operator":[
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"action" },
{ "action":"literal", "value":"apply", "result":"value" },
{ "action":"compare", "this":"action", "that":"value", "equal":"equal" },
{ "action":"if", "condition":"equal", "true":"l_apply" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_apply" },
{ "action":"literal", "value":"lambda_k_bind", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"behavior" },
{ "action":"create", "behavior":"behavior", "result":"k_bind" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"customer2" },
{ "action":"literal", "value":"customer2", "result":"key" },
{ "action":"store", "struct":"k_bind", "key":"key", "value":"customer2" },
{ "action":"literal", "value":"body", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"body" },
{ "action":"literal", "value":"body", "result":"key" },
{ "action":"store", "struct":"k_bind", "key":"key", "value":"body" },
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"message" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"k_bind" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"literal", "value":"bind", "result":"value" },
{ "action":"store", "struct":"message", "key":"key", "value":"value" },
{ "action":"literal", "value":"argument", "result":"key" },
{ "action":"load", "struct":"_message", "key":"key", "result":"argument" },
{ "action":"literal", "value":"value", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"argument" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"environment" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"environment" },
{ "action":"literal", "value":"formal", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"formal" },
{ "action":"send", "target":"formal", "message":"message" },
{ "action":"jump", "label":"l_end" },
{ "action":"label", "name":"l_end" }
],
"lambda_k_bind":[
{ "action":"literal", "value":"Object", "result":"key" },
{ "action":"load", "struct":"_sponsor", "key":"key", "result":"Object" },
{ "action":"new", "type":"Object", "result":"message" },
{ "action":"literal", "value":"customer2", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"customer" },
{ "action":"literal", "value":"customer", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"customer" },
{ "action":"literal", "value":"action", "result":"key" },
{ "action":"literal", "value":"eval", "result":"value" },
{ "action":"store", "struct":"message", "key":"key", "value":"value" },
{ "action":"literal", "value":"environment", "result":"key" },
{ "action":"store", "struct":"message", "key":"key", "value":"_message" },
{ "action":"literal", "value":"body", "result":"key" },
{ "action":"load", "struct":"_self", "key":"key", "result":"body" },
{ "action":"send", "target":"body", "message":"message" }
]
For comparison, the equivalent Humus code would be:
LET lambda(formal, body) = \_message.[
CASE _message OF
(customer, #eval, environment) : [
CREATE operator WITH \(customer2, #apply, argument).[
SEND (k_bind, #bind, argument, environment) TO formal
CREATE k_bind WITH \extended.[
SEND (customer2, #eval, extended) TO body
]
]
SEND operator TO customer
]
END
]
Communication between memory domains is accomplished through reading and writing bit-streams. Cap'n Proto is our model for message encoding. [This implies that our abstract machine word-size is 64 bits.]
struct NullableBool {
union { // 16-bit type-tag in bits 0-15
null @0 : Void;
bool @0 : Bool;
}
}
16#0000000100000000 ------------0000 = Null
16#0000000100000000 --------00000001 = False
16#0000000100000000 --------00010001 = True
Three bits are sufficient to encode a wide range of bit-string sizes.
CODE WIDTH
000 0 bits (empty)
001 1 bit
010 2 bits
011 4 bits
100 8 bits (1 byte)
101 16 bits (2 bytes)
110 32 bits (4 bytes)
111 64 bits (8 bytes)