Bobmilla Viral programs with a certain cosmopolitan charm Last updated 12 Sep. 2002 |
Bombilla is a bytecode interpreter that runs on TinyOS. The conciseness of a high-level instruction set allows users to write programs and dynamically install them in a network much more quickly than possible with binary images. Additionally, Bombilla can automatically forward programs through a network; a new forwarding Bombilla program installed on a single mote will eventually run on every mote in the network.
Bombilla simplifies programming by providing a synchronous
interface to TinyOS operations. For example, the send
instruction blocks until the underlying sendDone
event is handled by TinyOS. This makes code much less complex,
especially for novice programmers. Currently, Bombilla programs
are written in a high-level assembly language (e.g. sending a
packet is a single instruction).
As Bombilla is a virtual machine, it also provides a form of execution protection. Unlike TinyOS applications, which has no user/kernel boundary, Bombilla programs cannot crash a mote or render it unresponsive. Bombilla catches errors at run time (e.g. accessing an invalid heap variable) and has a mechanism for alerting a user to them, including simple debugging information on the instruction that caused the error.
Bombilla has an event-based execution model; pieces of code are run in response to events, such as a timer or a packet reception. The presence of blocking operations requires that event handlers be able to run in parallel; the presence of shared variables could make race conditions a significant problem. Bombilla handles this issue by having handlers implicitly lock all shared resources they use; the set of shared resources used is computed with a full program analysis when code is installed. Therefore, a programmer can write several Bombilla event handlers that share variables and be sure there will be no race conditions without having to consider the necessary synchronization.
A Bombilla-enabled mote application exists in the TinyOS
applications directory: Bombilla
. Build and install
this application on a mote. By default, Bombilla runs a simple
program, similar to CntToLedsAndRfm, except that it sends the
packet over the UART instead of the radio. Once the application
installs, you should see the LEDs blink and a packet containing a
counter being sent to your PCs serial port; the packet format is
different from that used by IntToRfm.
Bombilla programs are broken into capsules of 24 instructions. There are two kinds of capsules: event handlers and subroutines. Event handler capsules are executed in response to an event; subroutines are called from other capsules. For example, there is a clock capsule, that runs in response to Bombilla's 1Hz timer.
Let's install a new clock capsule. Run
net.tinyos.vm_asm.CapsuleInjector
; a GUI like this
should appear:
Select the capsule type to be "Clock." Set the mote ID to be
ffff
or the ID you installed on the mote. Bombilla
only installs capsules that have a higher version number than the
currently installed capsule; the default clock capsule is version
0, so set the capsule version to 1.
Next, we need to type in a program. Type this program into the Program Text window:
|
This program is a simpler and slightly different version of the default capsule. Instead of incrementing the counter, it decrements it, and it does not send the counter over the UART. Press the Inject button in the GUI; you should shortly see the LEDs blinking in a decrementing (instead of incrementing) fashion. If all 3 LEDs begin blinking in unison, then Bombilla has encountered a runtime error in your program; make sure that you typed it in correctly. If you need to install a new capsule, be sure to increment the Capsule Version field.
Bombilla is a stack based architecture; instructions push and
pop operands on a stack. This results in more condense code, as no
embedded addresses are required; there are, however, instructions
with embedded operands (such as pushc
, push
constant). Bombilla has a 16 variable heap, however, which is
accessed with the getvar
and setvar
instructions, which have embedded operands.
In addition to capsules, Bombilla has contexts; contexts are the execution contexts (e.g. program counter, operand stack, etc.) that run capsules. Every context is associated with a capsule that it runs when triggered. For example, the clock context runs the clock capsule. There is not a 1-to-1 mapping between capsules and contexts, as subroutine capsules do not have associated contexts.
Let's look at the program you just installed and step through each instruction.
pushc 1: This instruction pushes a 1 onto the operand stack, which is empty when the capsule starts execution. The operand stack now has one variable, 1.
inv: This instruction inverts the value on top of the operand stack, making the 1 a -1. The operand stack now has one variable, -1.
getvar 0: This instruction fetches the counter from the 0th shared variable (they are numbered 0-15). The shared variables are initialized to all be 0 when Bombilla boots; as the default capsule was using this to store its counter, it has some small positive value (depending on how long you let the default capsule run). The operand stack now has two variables; the top of the stack is the counter, and below it is -1.
add: This instruction takes the top two variables on the operand stack and adds them, pushing the result onto the operand stack. In this case, it takes the counter and adds -1 to it, decrementing it. The operand stack now has one variable, the new decremented counter.
copy: This instruction copies the top of the operand stack. The operand stack now has two variables, both of which are separate copies of the decremented counter.
setvar 0: This instruction takes the top of the operand stack and stores it in shared variable 0, updating the stored value of the counter to the new decremented value. The operand stack now has one variable, a single copy of the decremented counter.
pushc 7: This instruction pushes the number 7 onto the operand stack. The operand stack now has two variables; the top of the stack is 7, and below it is the decremented counter.
land: This instruction pops two variables off the
operand stack and computes their logical and. As 7 is
00000111
in binary, the result of this operation is
the lowest three bits of the decremented counter; this is pushed
onto the operand stack. The operand stack now has one variable, a
value between 0 and 7 composed of the three least significant bits
of the new counter value.
putled: This instruction sets the mote's LEDs. It pops
the variable on top of the operand stack and uses it as an
operand. putled
takes a five bit operand. The lowest
three bits specify which LEDs to operate on, and the fourth and
fifth bits specify which LED operation. For us, these two bits are
both zero; this specifies the "set" LED operation. This means
that, for each LED bit, putled
will turn the LED on
if the bit is 1 and off if it is 0. This behavior is similar to
the component IntToLeds
. This results in the bottom
three bit of the new counter value being displayed on the
LEDs. The operand stack is now empty.
halt: Halt execution. One can always omit the trailing
halt
; CapsuleInjector will automatically add
one. Bombilla will stop running the capsule until the next clock
event (one second later).
This clock capsule keeps a stored counter in shared variable 0,
decrements it by one on each clock event, and displays the bottom
three bits of the new counter on the mote LEDs. Except for sending
the data over the UART (discussed in a later section), this
capsule's only distinction from the default one is the presence of
the inv
instruction.
When Bombilla encounters an error in a program (e.g. trying to pop off an empty operand stack), it enters an error state. In this error state, it stops running capsules. Instead, on each clock event, it toggles all of the LEDs (all of them blinking on and off is an indication something has gone wrong) and sends a packet over the UART. The packet is a BombillaErrorMsg and contains information on the cause of the error.
The format of a BombillaErrorMsg is as follows:
typedef struct BombillaErrorMsg {
uint8_t context;
uint8_t reason;
uint8_t capsule;
uint8_t instruction;
} BombillaErrorMsg;
The context
field indicates which context
triggered the error. The reason
field indicates the
cause of the error. The capsule
field indicates which
capsule the context was running when it triggered the error, and
the instruction
field indicates which instruction in
the capsule. A list of reason codes can be found in Bombilla.h:
|
For example, if the error message packet were Currently broken need to fix
|
this would mean that context 64 (0x40, the clock context) failed on capsule 64 (0x40, the clock capsule) on instruction 4 because of an operand stack underflow (reason 3). Here's an example program that would do this; try installing it to see what happens:
|
Twiddling with counters shows that Bombilla works, but is of
limited use. Bombilla can also read sensors with the
sense
instruction. sense
takes one
operand, which indicates which sensor to read. Currently, Bombilla
supports two sensors, the temperature and light sensors. Light is
sensor 1, and temperature is sensor 2.
An issue arises with the fact that sensor readings have types; for example, what does it mean to add a light reading to a temperature reading? Bombilla handles this problem by having a simple type system. Every variable has one of three types: a value, a sensor reading (of a certain kind), or a buffer. We'll talk more about buffers later; right now, we're concerned with values and readings.
The counter maintained in the previous programs was a value, a 16-bit signed integer. Sensor readings are distinct from values in that they have a sensor type (e.g. light) and are immutable; any operation that modifies a reading produces a value. For example, adding two light readings produces a value. A sensor reading variable represents only a raw reading; otherwise, it could have any value and a light reading shifted three bits to the right would be the same as a raw reading.
Let's recreate the TinyOS application SenseToLeds
;
this application reads the light sensor and displays the three
most significant digits of the ten bit reading on the LEDs:
|
Let's step through each instruction and explain what's happening.
pushc 1: Push the value 1 onto the operand stack. The operand stack now has one variable, 1.
sense: Take a reading from a sensor board; the sensor is specified by the variable on top of the stack. Sensor 1 is the light sensor (sensor 2 is the temperature sensor), so this instruction will pop the 1 off the stack and push a light sensor reading. The operand stack now has one variable, a 10 bit light reading.
cast: Pop the sensor reading on top of the stack and push the equal value.
pushc 7: Push the value 7 onto the operand stack. The operand stack now has two variables; the top of the stack is 7 and below it is the 10 bit value of the cast light reading.
shiftr: Bitshift a value to the right; this instruction takes the variable below the top of the operand stack and shifts it right a number of bits specified by the top of the operand stack. In this case, it takes the cast sensor reading and shifts it right seven bits, producing a value; as the sensor value was ten bits, this is the three most significant bits of the reading.
putled: Display these top three bits on the mote LEDs.
Storing and dealing with individual values is useful, but often being able to handle sets of data is needed. Bombilla provides buffers, which can hold up to ten variables. Buffers are typed. An empty buffer has no type, and takes on the type of the first variable put in it. For example, if the first variable is a light reading, then the buffer will become a light reading buffer. Attempting to put a variable of another type (e.g. a value) into the buffer will result in an error.
Bombilla provides two buffers, which can be pushed onto the
operand stack with bpush0
and
bpush1
. Variables can be placed in the buffers with
the add
instruction; this polymorphism of the
add
instruction leads to a more concise instruction
set.
In an add, if the buffer is on top of the stack (with the variable below it), the variable will be prepended. If the variable is on top of the stack (with the buffer below it), the variable will be appended. Appending takes O(1) time, while prepending takes O(n) time. In both cases, the buffer will remain on the stack. For example, the first sequence of instructions will prepend 6 to buffer 0, while the second will append it:
|
Variables can be extracted from buffers with the
bhead
, btail
, bget
and
byank
instructions. Their semantics are:
bhead: Remove the first variable in the buffer and push it onto the operand stack. Triggers an error if the buffer is empty.
btail: Remove the last variable in the buffer and push it onto the operand stack. Triggers an error if the buffer was empty.
bget: Copy the nth variable of the buffer onto the operand stack; do not remove it. Consumes two operands; the first (on top of the stack) is the index in the buffer, the second is the buffer. Triggers an error if the index is out of bounds.
byank: Remove the nth variable of the buffer and push it onto the operand stack. Consumes two operands; the first (on top of the stack) is the index in the buffer, the second is the buffer. Triggers an error if the index is out of bounds.
There are two instructions for querying the status of a buffer:
bsize
and bfull
. Additionally, buffers
can be cleared (set to size 0 with no type) with the
bclear
instruction. Last of all, the variables in a
buffer can be sorted with a single instruction;
bsorta
sorts them into ascending order, while
bsortd
sorts them into descending order.
All buffer instructions keep the buffer variable on the operand
stack. For example bfull
takes one operand, a buffer,
and after it completes, the top of the stack is whether the buffer
was full, beneath which is the buffer.
There are two branch instructions in Bombilla:
jumps
and jumpc
; we'll only discuss the
first, jumps
(which stands for "jump stack"
... jumpc
is "jump condition"). Both instructions
have an embedded 5-bit operand, the absolute instruction to jump
to. jumps
consumes a single value operand from the
operand stack; if the top of the stack is not a value, this
triggers an error. If the value is nonzero, control jumps to the
instruction specified by embedded operand. For example, the first
code snippet below always jumps (infinite loop) to instruction 8,
while the second jumps to instruction 14 if shared variable 3 is
equal to 8 (tested with the eq
instruction).
|
Bringing all of these concepts together, below is a simple data filtering clock capsule. This capsule reads the light sensor and stores it in a buffer. When the buffer is full, it sorts the elements and displays the median value on the LEDs. Comments alongside the instructions briefly explain each step.
|
Blinking the three LEDs demonstrates you're collecting data, but communicating it is better; these are sensor networks, after all. Bombilla has three communication mechanisms. The first is a built in ad-hoc routing layer; sent using this mechanism is automatically routed to a base station (it uses the Narpro component, so one must have a Narpro base station)). The second is sending to the uart. The third is to trigger Bombilla's routing capsules, which can format headers before sending a packet; this allows ad-hoc routing algorithms to be implemented in Bombilla. This final mechanism is rather complex, and is not covered in this tutorial.
uart: The uart
instruction consumes a
single operand, a buffer. uart
sends that buffer to
the mote UART.
send: The send
instruction consumes a
single operand, a buffer. send
passes that buffer to
the Narpro component, which sends the packet using a simple
beaconless ad-hoc routing algorithm.
Buffers have the following layout in a packet:
|
where type is one of
|
For example, the packet
|
contains a buffer of four values: 5, 8, 13, and 21.
Here's a variation of the previous filtering program; instead of displaying the median value on the LEDs, this capsule sends a sorted buffer over the radio.
|
Install this capsule and see. Having your Bombilla mote on the programming board can be a problem; on one hand, it has to be plugged in to install the capsule, but it can't have a sensor board on when plugged in (e.g. when it boots), which means that the board will. not be initialized propely. The way to handle this is to transmit the capsule to the mote with a GenericBase; you can then see the packets the mote sends.
So far, we've been installing capsules on individual motes. This is inherently difficult on large sensor networks, especially multihop networks. Bombilla capsules can therefore virally propagate through a network. One copy of a capsule set "forwarding" will slowly duplicate itself through the network; version numbers are used to determine which capsules to install and which to not.
Install Bombilla on two motes. Using a GenericBase, send a new program to both motes; barring packets being dropped, both should receive and start running the new capsule. Reset the motes and replace the GenericBase with one of the motes, then install the new program over the UART. The second Bombilla mote (not plugged into the UART) will continue to run the old program.
The CapsuleInjector
GUI has a radio button for
setting whether the generated capsule is forwarding or not. Set
your new program to be forwarding, increment the version number,
and install it on the mote plugged into the UART. After a little
while (within a minute or so), the second mote should start
running the new program; the first mote broadcast the new capsule
out over the radio and the second mote installed it.
Capsule forwarding uses a very simple heuristic to try to not saturate a network; it takes a rough measure of network activity and decides whether to forward a capsule based on this measure. In a very, very busy network, Bombilla will try forwarding a capsule every minute or so; in an idle one, it forwards every other second or so. However, it randomly selects which capsule to forward; this means that, with 8 capsules, it will actually forward any given capsule every 16 seconds or so.
Bombilla has several other pieces of functionality, such as the receive and send contexts for message processing. These are, however, outside of the scope of this tutorial.
Instruction | Short description | Opcode(s) | Class | Operands | Results | Long Description | ||||||||||||||||
halt |
Halt execution | 0x00 |
Basic | None | None | Halts execution of the issuing context, which returns to an idle state, releasing all locks | ||||||||||||||||
id |
Push mote ID | 0x01 |
Basic | None | Value | Pushes the mote ID onto the operand stack as a value | ||||||||||||||||
rand |
Push random number | 0x02 |
Basic | None | Value | Pushes a 16-bit random number onto the operand stack as a value | ||||||||||||||||
ctrue |
Set condition to true | 0x03 |
Basic | None | Value | Sets the issuing context's condition variable to true (1) | ||||||||||||||||
cfalse |
Set condition to false | 0x04 |
Basic | None | Value | Sets the issuing context's condition variable to false (0) | ||||||||||||||||
cpush |
Push condition variable | 0x05 |
Basic | None | Value | Pushes the issuing context's condition variable onto the operand stack as a value. | ||||||||||||||||
logp |
not implemented | 0x06 |
Basic | None | Value | Pushes the global log line index onto the operand stack as a value. | ||||||||||||||||
bpush0 |
Push buffer 0 | 0x07 |
Basic | None | Buffer | Pushes buffer zero onto the operand stack. | ||||||||||||||||
bpush1 |
Push buffer 1 | 0x08 |
Basic | None | Buffer | Pushes buffer one onto the operand stack. | ||||||||||||||||
depth |
Push opstack depth | 0x09 |
Basic | None | Value | Pushes the current depth of the issuing context's operand stack as a value; the value is the depth before the push. | ||||||||||||||||
err |
Enter error state | 0x0a |
Basic | None | None | Forces the issuing context to enter an error state with reason BOMB_ERROR_TRIGGERED; halts execution. |
||||||||||||||||
ret |
Return from subroutine call | 0xb |
Basic | None | None | Pops a return address of the context's address stack and returns control to the popped instruction and capsule. | ||||||||||||||||
call0 |
Call subroutine 0 | 0x0c |
Basic | None | None | Pushes a return address onto the context's return address stack, consisting of the current capsule and current pc+1. Then jumps to instruction zero of subroutine 0. | ||||||||||||||||
call1 |
Call subroutine 1 | 0x0d |
Basic | None | None | Pushes a return address onto the context's return address stack, consisting of the current capsule and current pc+1. Then jumps to instruction zero of subroutine 1. | ||||||||||||||||
call2 |
Call subroutine 2 | 0x0e |
Basic | None | None | Pushes a return address onto the context's return address stack, consisting of the current capsule and current pc+1. Then jumps to instruction zero of subroutine 2. | ||||||||||||||||
call3 |
Call subroutine 3 | 0x0f |
Basic | None | None | Pushes a return address onto the context's return address stack, consisting of the current capsule and current pc+1. Then jumps to instruction zero of subroutine 3. | ||||||||||||||||
inv |
Invert a value (multiply by -1) | 0x10 |
Basic | Value | Value | Pops a value off the operand stack, then pushes -1* that value onto the operand stack. | ||||||||||||||||
cpull |
Set condition variable | 0x11 |
Basic | Value | None | Pops a value off the operand stack, then set's the
issuing context's condition variable to this value. Combined
with jumpc , useful for fixed loops. |
||||||||||||||||
not |
Boolean not | 0x12 |
Basic | Value | Value | Pops a value off the operand stack; if equal to zero, pushes 1, else pushes zero. | ||||||||||||||||
lnot |
Logical not | 0x13 |
Basic | Value | Value | Pops a value off the operand stack and pushes its logical not (e.g. 0x2e will result in 0xffd1). | ||||||||||||||||
sense |
Read a sensor | 0x14 |
Basic | Value | Sensor reading | Pops a value off the operand stack; reads the sensor indicated by the reading. Sensor 1 is light, sensor 2 is temperature. | ||||||||||||||||
send |
Send a packet | 0x15 |
Basic | Buffer | None | Pops a buffer off the operand stack and sends it using a built-in ad-hoc routing protocol. Failure to send is silent. | ||||||||||||||||
sendr |
Send a raw packet | 0x16 |
Basic | Buffer | None | Pops a buffer off the operand stack and pushes a copy onto the top of the send context, which is triggered to run. The send context (and only the send context) is given an opportunity to obtain locks in the issuer's relinquish set. If the send context is non-idle, fails silently. | ||||||||||||||||
uart |
Send packet to UART | 0x17 |
Basic | Buffer | None | Pops a buffer off the operand stack and sends it to the UART. Fails silently. | ||||||||||||||||
logw |
not implemented | 0x18 |
Basic | Buffer | None | Pops a buffer off the operand stack and writes it to non-volatile memory (the log), at a location derived from the log index pointer. Log write failures will be retried indefinitely (context blocks). | ||||||||||||||||
bhead |
Push head of buffer (element 0) | 0x19 |
Basic | Buffer | <buffer type>, Buffer, ... | Pops a buffer off the operand stack and copies its head (the first element); pushes the buffer back onto the operand stack, followed by the variable copied from the buffer. This results in the variable being on the top of the stack, followed by the buffer. The variable has the type of the buffer; it could be either a value or a sensor reading. | ||||||||||||||||
btail |
Push tail of buffer (element size -1) | 0x1a |
Basic | Buffer | <buffer type>, Buffer, ... | Pops a buffer off the operand stack and copies its tail (the last element); pushes the buffer back onto the operand stack, followed by the variable copied from the buffer. This results in the variable being on the top of the stack, followed by the buffer. The variable has the type of the buffer; it could be either a value or a sensor reading. | ||||||||||||||||
bclear |
Clear buffer | 0x1b |
Basic | Buffer | Buffer | Pops a buffer off the operand stack and clears it: sets it to be of size zero and untyped. Pushes this cleared buffer back onto the operand stack. | ||||||||||||||||
bsize |
# elements in buffer | 0x1c |
Basic | Buffer | Value, Buffer, ... | Pops a buffer off the operand stack and copies its current size as a value. Pushes the buffer back onto the operand stack, followed by the value. This results in the value being on the top of the stack, followed by the buffer. | ||||||||||||||||
copy |
Copy top of operand stack | 0x1d |
Basic | <x> | <x>, <x>, ... | Pops the top of the operand stack, then pushes two copies of this variable back onto the operand stack. For example, if the value 1 were on top of the stack, the stack would become 1, 1, ...; if buffer 0 were on top of the stack, the stack would become buffer 0, buffer 0, ... | ||||||||||||||||
pop |
Pop operand stack | 0x1e |
Basic | <any> | None | Pops the top of the operand stack. | ||||||||||||||||
bsorta |
Sort buffer, ascending | 0x20 |
Basic | Buffer | Buffer | Pops a buffer off the operand stack, sorts its elements in ascending order, then pushes the buffer back onto the operand stack. | ||||||||||||||||
bsortd |
Sort buffer, descending | 0x21 |
Basic | Buffer | Buffer | Pops a buffer off the operand stack, sorts its elements in descending order, then pushes the buffer back onto the operand stack. | ||||||||||||||||
bfull |
Buffer full? | 0x22 |
Basic | Buffer | Value, Buffer, ... | Pops a buffer off the operand stack; if the buffer is full, pushes the buffer, then the value 1. If it is not, pushes the buffer, then the value 0. | ||||||||||||||||
putled |
Actuate LEDs | 0x23 |
Basic | Value | None | putled takes a single operand, a value. It
uses the lowest five bits of the operand to determine how to
actuate the LEDs. The lowest three bits denote the 3 LEDs;
bit 0 is red, bit 1 is green, and bit 2 is yellow. The next
two bits (3 and 4) specify which operation to apply; 00 is
set, 01 is on, 10 is off, and 11 is toggle. For example, 31
(0x1f) toggles all three LEDs, 19 turns the red and green
off (leaving yellow unchanged), and 0x2 sets the LEDS to be
red off, green on, and yellow off. |
||||||||||||||||
cast |
Cast reading to value | 0x24 |
Basic | Reading | Value | Pops a sensor reading, then pushes a value equal to the reading's magnitude. | ||||||||||||||||
unlock |
Unlock single lock | 0x25 |
Basic | Value | None | Adds the lock specified by the operand to both the relinquish and acquire sets of the context. This will cause the lock the be yielded at the next scheduling point and reacquired before resuming execution. The value operand should be in the range of 0-15 (the lock numbers). | ||||||||||||||||
unlockb |
Unlock set of locks | 0x26 |
Basic | Value | None | Adds the locks specified by the operand to both the relinquish and acquire sets of the context. This will cause the locks the be yielded at the next scheduling point and reacquired before resuming execution. The value operand is considered a bitmask, with the least significant bit corresponding to lock 0. For example an operand of 0x1025 would unlock locks 0, 2, 5, and 12. | ||||||||||||||||
punlock |
Permanently unlock lock single lock | 0x27 |
Basic | Value | None | Adds the lock specified by the operand to the relinquish set of the context. This will cause the lock the be released at the next scheduling point and notreacquired before resuming execution. This can cause a runtime error if the context later attempts to access the shared variable it no longer has locked. The value operand should be in the range of 0-15 (the lock numbers). | ||||||||||||||||
punlockb |
Permanently unlock set of locks | 0x28 |
Basic | Value | None | Adds the locks specified by the operand to the relinquish set of the context. This will cause the locks the be released at the next scheduling point and notreacquired before resuming execution. This can cause a runtime error if the context later attempts to access a shared variable it no longer has locked. The value operand is considered a bitmask, with the least significant bit corresponding to lock 0. For example an operand of 0x1025 would permanently unlock locks 0, 2, 5, and 12. | ||||||||||||||||
logwl |
not implemented | 0x2b |
Basic | Value, Buffer, ... | None | Write the buffer to the area of non-volatile storage specified by a certain log index. | ||||||||||||||||
logr |
not implemented | 0x2c |
Basic | Value, Buffer, ... | None | Read from the area of non-volatile storage specified by a certain log index to the buffer. | ||||||||||||||||
bget |
Get nth element of buffer | 0x2d |
Basic | Value, Buffer, ... | <type of buffer>, Buffer, ... | Make a copy of the nth variable of the buffer (specified by the value), push the buffer back onto the operand stack, then push the variable onto the operand stack. | ||||||||||||||||
byank |
Yank nth element of buffer | 0x2e |
Basic | Value, Buffer, ... | <type of buffer>, Buffer, ... | Remove the nth variable of the buffer (specified by the value), push the buffer back onto the operand stack, then push the variable onto the operand stack. This will result in the buffer size shrinking by 1. This operation is O(1) for the last element at O(n) for the first. | ||||||||||||||||
motectl |
not implemented | 0x2f |
Basic | Value, ???, ... | ??? | General purpose instruction for rare operations that modify underlying mote state; e.g. get/set AM group, mote ID, timer rates, etc. | ||||||||||||||||
swap |
Swap top 2 variables | 0x30 |
Basic | <x>, <y>, ... | <y>, <x>, ... | Pop x then y off the operand stack, then push x, then push y. This results in y being on top of the operand stack, with x below it; they have been swapped. | ||||||||||||||||
land |
Logical and | 0x31 |
Basic | Value, Value | Value | Pop two values off the operand stack, take their logical and, and push the result. | ||||||||||||||||
lor |
Logical or | 0x32 |
Basic | Value, Value | Value | Pop two values off the operand stack, take their logical or, and push the result. | ||||||||||||||||
and |
Boolean and | 0x33 |
Basic | Value, Value | Value | Pop two values off the operand stack, take their boolean and, and push the result: 1 if both were nonzero, 0 otherwise. | ||||||||||||||||
or |
Boolean or | 0x34 |
Basic | Value, Value | Value | Pop two values off the operand stack, take their boolean or, and push the result: 1 if either was non-zero, 0 otherwise. | ||||||||||||||||
shiftr |
Shift right | 0x35 |
Basic | Value1, Value2, ... | Value, ... | Pop two values (Value1, Value2) off the operand stack; shift Value2 right the number of bits specified by Value1. Push the result. | ||||||||||||||||
shiftl |
Shift left | 0x36 |
Basic | Value1, Value2, ... | Value, ... | Pop two values (Value1, Value2) off the operand stack; shift Value2 left the number of bits specified by Value1. Push the result. | ||||||||||||||||
add |
Add (polymorphic) | 0x37 |
Basic | <x>, <y>, ... | <z>, ... | Pop two operands and perform an operation determined by
their types, pushing a single result.
|
||||||||||||||||
mod |
Modulo | 0x38 |
Basic | Value1, Value2, ... | Value, ... | Pop two values (Value1, Value2) off the operand stack; push Value2 module Value1. | ||||||||||||||||
eq |
Equal | 0x39 |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push if they are equal. They are equal if their types are equal and if values, they are the same number, if readings, they are the same sensor and have equal magnitudes, or if buffers, they are the same buffer. If equal, push 1, else push 0. | ||||||||||||||||
neq |
Not equal | 0x3a |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push if they are not equal. They are equal iff their types are equal and if values, they are the same number, if readings, they are the same sensor and have equal magnitudes, or if buffers, they are the same buffer. If not equal, push 1, else push 0. | ||||||||||||||||
lt |
Less than | 0x3b |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push if (x < y). x is less than y iff they are both of the same type. Push 1 if x < y, 0 otherwise. | ||||||||||||||||
gt |
Greater than | 0x3c |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push if (x > y). x is greater than y iff they are both of the same type. Push 1 if x > y, 0 otherwise. | ||||||||||||||||
lte |
Less than or equal to | 0x3d |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push if (x <e; y). x is less than or equal to y iff they are both of the same type. Push 1 if x <e; y, 0 otherwise. | ||||||||||||||||
gte |
Greater than or equal to | 0x3e |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push if (x >e; y). x is greater than or equal to y iff they are both of the same type. Push 1 if x >e; y, 0 otherwise. | ||||||||||||||||
eqtype |
Type equal | 0x3f |
Basic | <x>, <y>, ... | Value, ... | Pop two values (x, y) off the operand stack; push 1 if x and y are the same type, 0 otherwise. If x and y are sensor readings, they are only the same type iff from the same sensor. | ||||||||||||||||
getms |
Get message header short | 0x40-47 |
M-class | None | Value | Push the nth (specified by the embedded 3-bit operand)
short of the message header (0-2) onto the operand stack as
a value. When a receive context begins execution, the
header variables are set to the received packet's; in the
send context, they are zero unless having been modified in
this execution by setms or
setmb . |
||||||||||||||||
getmb |
Get message header byte | 0x48-4f |
M-class | None | Value | Push the nth (specified by the embedded 3-bit operand)
byte of the message header (0-5) onto the operand stack as a
value. When a receive context begins execution, the
header variables are set to the received packet's; in the
send context, they are zero unless having been modified in
this execution by setms or
setmb . |
||||||||||||||||
getms |
Set message header short | 0x50-57 |
M-class | Value | None | Set the nth (specified by the 3-bit embedded operand) short of the message header (0-1) to Value.. | ||||||||||||||||
getmb |
Get message header byte | 0x58-5f |
M-class | Value | None | Set the nth (specified by the 3-bit embedded operand) byte of the message header (0-5) to Value. | ||||||||||||||||
setvar |
Set shared variable | 0x60-6f |
V-class | Value or Reading | None | Set the nth (specified by the 4-bit embedded operand) shared variable to operand. | ||||||||||||||||
getvar |
Get shared variable | 0x70-7f |
V-class | None | Value or Reading | Copy the nth (specified by the 4-bit embedded operand) shared variable and push the copy onto the operand stack. | ||||||||||||||||
jumpc |
Jump condition | 0x80-9f |
J-class | None | None | Jump to the absolute address of this capsule specified by the 5-bit embedded operand if the context's condition variable is > 0. If the condition variable is > 0, decrement it after the jump. | ||||||||||||||||
jumps |
Jump stack | 0xa0-bf |
J-class | Value | None | Jump to the absolute address of this capsule specified by the 5-bit embedded operand if the Value operand != 0. | ||||||||||||||||
pushc |
Push constant | 0xc0-ff |
X-class | None | Value | Push the constant specified by the 6-bit embedded operand onto the operand stack as a value. |
While Bombilla automatically handles context atomicity (with regards to shared variables, but not buffers) through implicit locking, this can fail due to capsule installation. Notably, as capsules are installed immediately upon reception, a context can be in the midst of a computation when it is halted due to new code being installed.
This means that to ensure perfect data consistency, the values of shared variables cannot be transferred from one version of a capsule to the next. For example, before installing a new version of the clock capsule, one could install a clock capsule that clears out the shared variables, restoring them to a consistent state.
We are currently working on a capsule installation mechanism that will preserve execution atomicity; this understandably becomes difficult in the presence of buggy capsules that do not halt.