Brian Heim 2018-06-19
- Introduction
- Definitions
- Codes
- Notes
TODO
I am using the op descriptions provided by dumpByteCodes
. In most cases, they align with the
comment annotations in PyrInterpreter3.cpp
.
Unless otherwise noted the descriptions given here are based directly on the source code.
In some cases, small code snippets are provided which demonstrate a use of the code under discussion.
Lines between %%% are annotations about the location of relevant code in PyrParseNode.cpp
.
TODO
Frame: TODO
Instruction pointer: Instruction pointer (IP).
Intrinsic class: TODO
Intrinsic classes include:
- Object
- Literal types: Symbol, Nil, Boolean, True, False, Magnitude, Char, Number, Complex, SimpleNumber, Integer, Float, String
- Introspective types: Method, FunctionDef, Function, Frame, Process, Main, Class
- Collection types: Collection, SequenceableCollection, ArrayedCollection, Array, literal array types, List, LinkedList, Bag, Set, IdentitySet, Dictionary, IdentityDictionary, SortedList
- Environments: Ref, Environment, Event
- Stream and sound types: Stream, Synth, Wavetable, Env, Routine
- Color
- Rect
Special Selectors:
A set of commonly used messages treated as special [TODO: optimized?] cases.
Defined in Opcodes.h
.
Push a class to the top of the stack. "Special" classes are handled by a different op code (06
).
The second byte: TODO (???)
Push an instance variable of the parent class to the top of the stack. The second byte determines
the index of the variable to push. This operation is the counterpart to 07
StoreInstVarX.
Only used in class method code.
ServerOptions.findMethod('device').dumpByteCodes
%%% compilePushVar: 247 %%%
Push a temporary variable to the top of the stack.
The second instruction byte indicates how many frames up the stack the variable lives. For example,
a value of 1
here means the variable belongs to the function block just outside the current one.
The third instruction byte indicates the index of the variable within that frame.
This instruction is used when 3x
PushTempVar cannot be used; because the variable belongs to an
enclosing frame.
%%% compilePushVar: 269, 272 %%%
Push a temporary variable from the current frame to the top of the stack.
The second instruction byte indicates the index of the variable within that frame.
This instruction is used when 3x
PushTempVar cannot be used because the variable index is above
16.
%%% compilePushVar: 267 %%%
Push a stored literal from the current frame to the top of the stack.
The second instruction byte indicates the index of the literal within that frame.
This instruction is used when the literal is of type FunctionDef. [TODO]
Push a class variable of a class to the top of the stack. The second byte determines the class to use and the third byte determines the index of the variable to push.
Only used in class method code. [TODO: more info? when is this used?]
Push a "special" (intrinsic) class onto the stack.
The second instruction byte indicates the index of the intrinsic class within the internal array
gSpecialClasses
.
Store the top of the stack to an instance variable of the parent class. The second byte determines
the index of the variable. The stack is not altered. This operation is the counterpart to 01
PushInstVarX.
Only used in class method code.
ServerOptions.findMethod('device_').dumpByteCodes
Store the top of the stack in a variable, without popping the stack.
The second instruction byte indicates the distance between the current frame and the target variable's frame.
The third instruction byte indicates the index of the variable within that frame.
TODO: usage?
{ var a; { a = 5; }.def.dumpByteCodes }.value
Store the top of the stack in a class variable, without popping the stack.
The second and third instruction bytes indicate the index of the variable within the VM's
classvars
field. TODO: document
Only used in class method code.
Meta_Server.findMethod('initClass').dumpByteCodes
Send a message.
The second instruction byte indicates the number of normal arguments (TODO: document normal vs key args).
The third instruction byte indicates the number of keyword arguments.
The fourth instruction byte indicates ??? TODO (I think it's the selector index when the selector index is known at compile-time)
The stack will be modified so that all the keyword and normal arguments are consumed; the top of the stack will then be the result of the call.
{ meow(x, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14) }.def.dumpByteCodes
{ meow(x, a: 5) }.def.dumpByteCodes
TODO: unclear how this should be used
Send a "special message" to an object.
The second and third instruction bytes respectively indicate the numbers of normal and keyword arguments.
The fourth instruction byte indicates the index of the selector name within the internal array of special messages.
TODO Used when C1
cannot be used, for example because keyword arguments are used in the call.
{ 5.postln(x: a) }.def.dumpByteCodes
Send a special unary arithmetic message to the object on the top of the stack. If the object is a primitive type, the message is executed in an optimized manner. Otherwise, it becomes a normal message send.
The second instruction byte indicates the value of the unary arithmetic message opcode, as defined
in Opcodes.h
.
{ 5.0.reciprocal }.def.dumpByteCodes
Send a special binary arithmetic message to the two objects on the top of the stack. If the receiver is a primitive type, the message is executed in an optimized manner. Otherwise, it becomes a normal message send.
The second instruction byte indicates the value of the binary arithmetic message opcode, as defined
in Opcodes.h
.
{ 5.0.pow(5) }.def.dumpByteCodes
Pushes a special "this"-type object to the top of the stack.
The second instruciton byte indicates the object to push. One of thisProcess
, thisThread
,
thisMethod
, thisFunctionDef
, thisFunction
.
In the case of thisFunction
, a new (anonymous) closure is allocated. [TODO - how does this work?]
Push an instance variable from the current class to the top of the stack. The index of the variable
is given by the second nibble of the instruction. For example, 10
pushes the first-declared
variable.
If the index is greater than 15, 01
PushInstVarX is used.
Server.findMethod('maxNumClients').dumpByteCodes
Unused. [TODO: sure? not used in PyrParseNode]
Push a variable from an enclosing frame onto the stack.
The second nibble of the first instruction byte gives the distance in frames between the current frame and the desired target.
The second instruction byte determines the index of the variable within that frame.
For instance, 25 02
indicates the third variable of the fifth frame outside this frame.
{ var a, b, c; { { { { { c.floop }.def.dumpByteCodes }.value }.value }.value }.value }.value
Push a constant from the current frame onto the stack. The second instruction byte indicates the index of the constant.
This message is used instead of 4X
PushLiteral when the index is >= 16.
{ a = [\a, \b, \c, \d, \e, \f, \g, \h, \i, \j, \k, \l, \m, \n, \o, \p, \q] }.def.dumpByteCodes
Push a constant from the current frame onto the stack. The second and third instruction bytes interpreted as a 16-bit integer indicate the index of the constant.
This message is used instead of 28
PushConstant when the index is >= 256.
Push a constant from the current frame onto the stack. The second, third and fourth instruction bytes interpreted as a 24-bit integer indicate the index of the constant.
This message is used instead of 29
PushConstant when the index is >= 2^16.
Push a constant from the current frame onto the stack. The second to fifth instruction bytes interpreted as a 32-bit integer indicate the index of the constant.
This message is used instead of 2A
PushConstant when the index is >= 2^24.
Push a constant integer onto the stack. The value of the integer is the remaining instruction bytes interpreted as a signed integer:
Code | Bytes | Bit-depth of integer |
---|---|---|
2C |
2 | 8 |
2D |
3 | 16 |
2E |
4 | 24 |
2F |
5 | 32 |
{ 5 + -3 }.def.dumpByteCodes
{ 259 + -300 }.def.dumpByteCodes
{ 59834 + -32988 }.def.dumpByteCodes
{ 52934879 + -32348798 }.def.dumpByteCodes
Push a variable from the current frame to the top of the stack. The index of the variable is given
by the second nibble of the instruction. For example, 30
pushes the first-declared variable.
The "Zero" in the name indicates that this variable lives in the top (zeroeth) frame.
Push a constant from the current frame to the top of the stack. The index of the constant is given
by the second nibble of the instruction. For example, 40
pushes the first-declared constant.
{ 5.5 + 8.5 }.def.dumpByteCodes
Push a class variable from the current frame to the top of the stack. The index of the variable is given by the second nibble of the first instruction byte and the second instruction byte interpreted as a 12-bit integer. The indexed array is the global collection of class variables.
Server.findMethod('calculateViewBounds').dumpByteCodes
Pushes this
to the top of the stack.
If the top of the stack is an integer, this instruction decrements it by one. Otherwise, the integer
value one is pushed to the top of the stack and prSubNum
is called.
{ |x| x - 1; }.def.dumpByteCodes
Pushes a commonly used numerical value to the top of the stack.
Code | Value |
---|---|
62 | -1 |
63 | 0 |
64 | 1 |
65 | 2 |
66 | 0.5 |
67 | -1.0 |
68 | 0.0 |
69 | 1.0 |
6A | 2.0 |
If the top of the stack is an integer, this instruction increments it by one. Otherwise, the integer
value one is pushed to the top of the stack and prAddNum
is called.
{ |x| x + 1; }.def.dumpByteCodes
Pushes a commonly used value to the top of the stack.
Code | Value |
---|---|
6C | True |
6D | False |
6E | Nil |
6F | Inf |
Pops the top of the stack and stores it in an instance variable of the current receiver. The second nibble of the byte indicates the index of the variable.
TODO: has different behavior depending on whether owner of instance variable is immutable (?)
Only used within class code.
Server.findMethod('addr_').dumpByteCodes
Pops the top of the stack and stores it in a local instance variable.
The second nibble of the first byte indicates the distance in frames between the current frame and the resident frame of the store variable.
The second instruction byte indicates the index of the variable within its frame.
TODO discuss this vs StoreTempVarX
{ var a, b, c, d, e, f, g, h, i; d = 5; d }.def.dumpByteCodes
{ var a, b, c, d, e, f, g, h, i; { d = 5; d }.def.dumpByteCodes }.value
Pushes an instance variable of the current receiver to the top of the stack and calls a special method on it.
The second instruction byte indicates the index of the instance variable in the class.
The third instruction byte indicates the index of the method selector.
Only used within class code.
RingBuffer.findMethod('add').dumpByteCodes
Push all arguments to the top of the stack in order given, and call a method.
The second instruction byte indicates the index of the selector name in the current frame.
{ |a, b, c| a.meow(b, c) }.def.dumpByteCodes
Push all arguments except the first to the top of the stack in order given, and call a method.
The second instruction byte indicates the index of the selector name in the current frame.
TODO when exactly used? Used in class code to avoid pushing this
. (?)
TODO double check usage - is this
really an implicit param?
Meta_File.findMethod('open').dumpByteCodes
Push all arguments to the top of the stack in order given, and call a special method.
The second instruction byte indicates the index of the selector name in the list of special
selectors (gSpecialSelectors
).
{ |a, b, c| a.new(b, c) }.def.dumpByteCodes
{ |a, b, c| a.add(b, c) }.def.dumpByteCodes
Push all arguments except the first to the top of the stack in order given, and call a special method.
The second instruction byte indicates the index of the selector name in the list of special
selectors (gSpecialSelectors
).
TODO when exactly used? Used in class code to avoid pushing this
. (?)
Meta_File.findMethod('open').dumpByteCodes
Push all arguments except the first two (TODO - really?), and call a method.
The second instruction byte indicates the index of the selector name in the current frame.
{ |a, b, c| this.join(a, b, c) }.def.dumpByteCodes
Push all arguments except the first two (TODO - really?), and call a special method.
The second instruction byte indicates the index of the selector name in the list of special
selectors (gSpecialSelectors
).
TODO when exactly used? Used in class code to avoid pushing this
. (?)
{ |a, b, c| this.add(a, b, c) }.def.dumpByteCodes
This code has unique behavior depending on the value of its second byte. Often, two or three
specializations of 8F
are used together.
These codes are injected directly by the compiler, and therefore cannot be produced outside those methods.
Implements Integer:-do
: 8F 00 8F 01
. The frame of this method holds the receiver (loop limit),
the function argument, and an accumulator variable initially set to 0.
At 8F 00
, the accumulator is tested against the receiver and, if greater than or equal to it,
control exits the method. Otherwise, the function and two copies of the accumulator are pushed to
the stack, and value
is invoked on all three.
At 8F 01
, the top of the stack is popped, discarding the result of value
. The IP is set back to
8F 00
.
Implements Integer:-reverseDo
: 8F 02 8F 03 8F 04
. The frame of this method holds the receiver
(loop limit), the function argument, and two accumulators (i
, j
). i
is used as the reverse
accumulator, while j
is the forward accumulator.
At 8F 02
, i
is initialized to the receiver minus 1.
At 8F 03
, i
is tested against 0 and, if less than it, control exits the method. Otherwise, the
function, i
, and j
are pushed to the stack and value
is invoked on all three.
At 8F 04
, the top of the stack is popped, discarding the result of value
. i
is decremented,
j
is incremented, and the IP is set back to 8F 03
.
Note the out-of-order numbering.
Implements Integer:-for
: 8F 05 8F 06 8F 10
. The frame of this method holds the receiver (loop
start), the loop limit, the function argument, the accumulator, the loop count accumulator, and
stepval
.
At 8F 05
, the type of the loop limit is inspected. If it is an int, nothing happens; if it is a
float, it is coerced to an int value using a direct cast. Otherwise, an error is thrown.
Next, stepval
is set to 1 or -1 depending on the direction of iteration: if the loop limit is <=
the loop start, stepval := 1
, otherwise -1.
Finally, the accumulator is set to the value of the receiver.
At 8F 06
, the accumulator is checked: if the accumulator is beyond the loop limit, given the
direction of iteration, the method is exited. Otherwise, the function argument, the accumulator, and
loop count accumulator are pushed to the stack and value
is invoked on all three.
At 8F 10
, the top of the stack is popped, discarding the result of value
. i
is incremented by
stepval
, j
is incremented, and the IP is set back to 8F 06
.
TODO document with new type behavior
Note the duplication of 8F 01
.
Implements ArrayedCollection:-do
: 8F 0A 8F 01
. The frame of this method holds the receiver, the
apply function, and the loop accumulator (initially 0).
At 8F 0A
, the accumulator is inspected. If it is less than the size of the array, then the apply
function, indexed element of the receiver array, and accumulator are pushed to the stack and value
is invoked on all three.
Otherwise, the receiver is pushed to the stack and control exits the method.
At 8F 01
, as above, the result of value
is dropped, the accumulator is incremented, and the IP
is set to 8F 0A
.
Note the duplication of 8F 04
.
Implements ArrayedCollection:-reverseDo
: 8F 0B 8F 0C 8F 04
. The frame of this method holds the
receiver, the apply function, the reverse accumulator i
, and the loop count accumulator j
.
Initially, j = 0
.
At 8F 0B
, i
is initialized to size - 1
.
At 8F 0C
, i
is compared to 0. If it is greater than or equal to 0, the apply function, element
of the receiver indexed by i
, and j
are pushed to the stack, and value
is invoked on all
three. Otherwise, the receiver is pushed to the stack and control exits the method.
At 8F 04
, the top of the stack is popped, i
is decremented, j
is incremented, and the IP is
set back to 8F 0B
.
Implements Dictionary:-keysValuesArrayDo
: 8F 0D 8F 0E
. The frame of this method holds eight
variables:
- the receiver
array
, array representing underlying storage- the apply function
- index accumulator
i
, updated once per key or value - pair index accumulator
j
, updated once per pair key
, slot representing the current keyvalue
, slot representing the current value- array size
At 8F 0D
, the array argument is checked and, if it is nil, the primitive fails.
TODO checking first arg should only be done once! And, it needs to be checked for being an object.
Next, i
is compared to the size of the object. If it is greater than or equal to the size, the
receiver is pushed to the stack and control exits the method.
Otherwise, key
is initialized to the i
th element of array
, then advanced to the next
non-nil value. If at any time the end of the array is reached, the receiver is pushed to the stack
and control exits the method. Otherwise, the apply function and two copies of i
are pushed to the
stack, and value
is invoked on all three.
At 8F 12
, the stack is popped and the result of value
is discarded, i
is incremented by 1.0
,
and the IP is set back to 8F 11
.
Implements Float:-reverseDo
: 8F 13 8F 14 8F 15
. The frame of this method holds the receiver, the
apply function, reverse accumulator i
, and loop accumulator j
.
At 8F 13
, i
is initialized to receiver - 1
.
At 8F 14
, if i + 0.5 < 0.0
, then the receiver is pushed to the stack and control exits the
method. Otherwise, the apply function, i
, and j
are pushed to the stack and value
is invoked
on all three.
At 8F 15
, the stack is popped and the result of value
is discarded, i
is decremented by 1.0
,
j
is decremented by 1.0
, and the IP is reset to 8F 14
.
TODO this is a bug - j should be incremented
This opcode replaces a call to ?
. The top of the stack is popped. If the new top is nil, it is
replaced with the previously popped value. Otherwise it remains the top of the stack.
TODO - even when the second argument is a single variable or literal, this is slower than ??
because the value has to be pushed to the stack. Maybe in such a simple case we could replace it
with the corresponding ??
call?
This opcode replaces a call to ??
when the second argument is an inlined function. If the top of
the stack is not nil, control jumps forward a number of bytes equal to the third and fourth
instruction bytes interpreted as a 16-bit integer. Otherwise, the top of the stack is popped and
discarded.
This opcode replaces the construction if(x.isNil) { ... } { ... }
. If the top of the stack is
nil
, then control flows to the next instruction. Otherwise, control jumps forward a number of
bytes equal to the third and fourth instruction bytes interpreted as a 16-bit integer.
The top of the stack is popped.
TODO - this and ifNil are named backwards in the source code
This opcode replaces the construction if(x.notNil) { ... } { ... }
. If the top of the stack is not
nil
, then control flows to the next instruction. Otherwise, control jumps forward a number of
bytes equal to the third and fourth instruction bytes interpreted as a 16-bit integer.
The top of the stack is popped.
This opcode replaces the construction if(x.isNil) { ... }
. If the top of the stack is nil
, then
control flows to the next instruction and the top of the stack is popped. Otherwise, control jumps
forward a number of bytes equal to the third and fourth instruction bytes interpreted as a 16-bit
integer, and the top of the stack is replaced with nil
.
This opcode replaces the construction if(x.notNil) { ... }
. If the top of the stack is not nil
,
then control flows to the next instruction and the top of the stack is popped. Otherwise, control
jumps forward a number of bytes equal to the third and fourth instruction bytes interpreted as a
16-bit integer, and the top of the stack is replaced with nil
.
This bytecode is used to implement an inlined switch statement. It assumes that top of the stack is a compile-time generated array that contains objects and instruction pointer offsets. The structure of this array is described below. This bytecode also assumes that the second topmost item in the stack is the object to switch against.
The hashtable is searched for a match with arrayAtIdentityHashInPairs
. Add 1 to the result to get
the instruction pointer offset; pop both the switched-against object and hashtable array from the
stack, and add the pointer offset to the instruction pointer.
{ 5.switch {9} {5} {3} {5} {8} {10} }.def.dumpByteCodes
TODO document array design
TODO could improve on array design! location in array indicates pointer jump? (maybe?) doesn't need to be power of two size. (?) and why (IsNil(test))?
Implements Number:-forSeries
: 8F 13 8F 14 8F 15
. The frame of this method holds the receiver
,
second
value (which is used to calculate the step
value), last
value (which is used to
calculate the limit
value), apply function, accumulator i
, and loop counter j
. Note that in
the description below second
and last
are used to name the method inputs, while step
and
limit
are used to name the values used during iteration.
At 8F 1D
, the types and values of receiver
, second
, and last
are used to calculate the
initial values of i
, step
, and limit
respectively. If any of receiver
, second
, or last
is a float, the resulting values of i
, step
, and limit
will be floats; otherwise they will be
ints.
In the int case, if second
is nil then step
is set to limit > receiver ? 1 : -1
; in other
words, step
is set to count toward limit
by 1
. If limit
is nil then last
is set to `second
receiver ? INT_MAX : INT_MIN
. If both
secondand
limitare nil,
stepis set to
1and
lastis set
INT_MAX`.
Similarly, in the float case, if second
is nil then step
is set to limit > receiver ? 1.0 : -1.0
; in other words, step
is set to count toward limit
by 1.0
. If limit
is nil then last
is set to second > receiver ? DBL_MAX : DBL_MIN
. If both are nil, step
is set to 1.0
and
last
is set DBL_MAX
.
i
is always set to the value of receiver
. Unless one of the cases above dictates otherwise,
last
is set to limit
and step
is set to second - receiver
.
If the receiver
is not an int or float, or the second
and/or limit
value are not one of int,
float, or nil, then the receiver
is pushed to the stack and primitiveFailed
is invoked on it.
For instance, if the values of receiver
, second
, and last
are 5
(int), nil, and -1.5
(float) respectively, then the values of i
, step
, and last
will be 5.0
(float), -1.0
(float), and -1.5
(float) respectively. For initial values 5
(int), 3
(int), and nil, the
values of i
, step
, and last
will be 5
(int), -2
(int), and -2147483648
(INT_MIN
)
respectively.
At 8F 1E
, if i
is equal to or exceeds last
in the direction of iteration, then the receiver
is pushed to the stack and control exits the method. Otherwise, the apply function, i
, and j
are
pushed to the stack and value
is invoked on all three.
At 8F 1F
, the stack is popped and the result of value
is discarded, i
is incremented by
step
, j
is incremented by 1
, and the IP is reset to 8F 1E
.
TODO documentation is wrong - 2nd val in frame is 'second', not step. 5th val in frame is 'step', not i. 6th val in frame is accum, not j (not descriptive in this context).
TODO bug here - if jump goes past limit
, will cycle around endlessly:
3.forSeries(1000000, 2147480647, _.postln)
TODO test for float case - probably ok cuz won't wrap, will hit inf and then compare greater (but what if one of the args is inf??)
TODO see line 2025 - confusing? same line 1988. don't match.
Pop and store the top of the stack in a class variable of the current class.
The index of the class variable is found by interpreting the second nibble of the first byte and the entire second byte as a 12-bit integer.
Only used in class code.
Meta_String.findMethod('initClass').dumpByteCodes
Push this
to the top of the stack and call a method taking one argument.
The second instruction byte determines the index of the selector within the block.
{ this.hark }.def.dumpByteCodes
Call a method taking a number of arguments.
The second nibble of the first instruction byte determines the number of arguments passed to the method.
The second instruction byte determines the index of the selector within the block.
{ a.hark(b); c.herp(d, e) }.def.dumpByteCodes
Sets the global tail call state to 2. TODO what does this mean?
Call a method on super
taking a number of arguments.
The second nibble of the first instruction bytes determines the number of arguments passed to the method.
The second instruction byte determines the index of the selector within the block.
Mostly used in class code, as in an interpreted context it refers to Interpreter
.
{ super.class }.def.dumpByteCodes
{ 3.fop; super.class(5, 5, 5) }.def.dumpByteCodes
Push this
to the top of the stack and call a special method taking one argument.
The second instruction byte determines the index of the special method within gSpecialSelectors
.
{ this.init }.def.dumpByteCodes
Call a special method taking a number of arguments.
The second nibble of the first instruction byte determines the number of arguments passed to the method.
The second instruction byte determines the index of the special method within gSpecialSelectors
.
{ 5.init(8, 9, 10) }.def.dumpByteCodes
Negate the top of the stack. If the top of the stack is a float or an integer, it is negated in
place; otherwise the message .neg
is sent.
TODO - signal as well?
{ 3.neg }.def.dumpByteCodes
Perform a logical negation of the top of the stack. If the top of the stack is True or False, it is
negated in place; otherwise the message .not
is sent.
{ 3.not }.def.dumpByteCodes
Check whether the top of the stack is nil
. If it is, replace it with True, else replace it with
False.
{ 3.isNil }.def.dumpByteCodes
Check whether the top of the stack is nil
. If it is, replace it with False, else replace it with
True.
{ 3.notNil }.def.dumpByteCodes
Perform a unary operation in-place on the top of the stack.
The operations are as follows:
Code | Operation |
---|---|
D4 | Bitwise NOT .bitNot |
D5 | Absolute value (.abs ) |
D6 | .asFloat |
D7 | .asInt (N.B.: not asInteger ) |
D8 | .ceil |
D9 | .floor |
DA | .frac |
DB | .sign |
DC | .squared |
DD | .cubed |
DE | .sqrt |
DF | .exp |
Pop the top two stack elements, "add" them, and store the result on the top of the stack. The top of the stack is considered the right-hand operand.
If the elements are both integers, addition is performed directly in place.
If the top element is an integer but the second is not, prAddInt
is used to convert the operands
to a common type. [TODO - more info s.w.?] If the second element (i.e., the left-hand operand) is
not an integer, prAddNum
is used. [TODO - more info s.w.?]
Pop the top two stack elements, "subtract" them, and store the result on the top of the stack. The top of the stack is considered the right-hand operand.
If the elements are both integers, addition is performed directly in place.
If the top element is an integer but the second is not, prSubInt
is used to convert the operands
to a common type. [TODO - more info s.w.?] If the second element (i.e., the left-hand operand) is
not an integer, prSubNum
is used. [TODO - more info s.w.?]
Pop the top two stack elements, "subtract" them, and store the result on the top of the stack. The top of the stack is considered the right-hand operand.
If the elements are both integers, addition is performed directly in place.
If the top element is an integer but the second is not, prMulInt
is used to convert the operands
to a common type. [TODO - more info s.w.?] If the second element (i.e., the left-hand operand) is
not an integer, prMulNum
is used. [TODO - more info s.w.?]
Pop the top two stack elements, perform a binary operation on them, and store the result on the top of the stack. The top of the stack is considered the right-hand operand.
The operations are as follows:
Code | Operation |
---|---|
E3 | Integer division (.div ) |
E4 | Floating-point division (/ ) |
E5 | Modulus (% ) |
E6 | == |
E7 | != |
E8 | < |
E9 | > |
EA | <= |
EB | >= |
EC | .min |
ED | .max |
EE | Bitwise AND .bitAnd |
EF | Bitwise OR .bitOr |
Pop the top of the stack and discard it.
Duplicate the top of the stack.
Used when returning from a closure. Return from the current block.
Sets the current VM frame to the calling frame and updates the VM's IP, current block, and method accordingly.
Used when returning from a class method. Return from the current method.
Sets the current
TODO document
Push this
to the top of the stack and return from the current method, as in F3
Return.
Push True to the top of the stack and return from the current method, as in F3
Return.
Push False to the top of the stack and return from the current method, as in F3
Return.
Push nil to the top of the stack and return from the current method, as in F3
Return.
If the top of the stack is True
, pop and discard it.
If the top of the stack is False
, pop it and jump ahead. The distance by which to jump is the
value of the second and third instruction bytes interpreted as a 16-bit integer.
If the top of the stack is neither True
nor False
, call mustBeBoolean
on it without popping
the value.
If the top of the stack is True
, pop and discard it.
If the top of the stack is False
, pop it, push nil
, and jump ahead. The distance by which to
jump is the value of the second and third instruction bytes interpreted as a 16-bit integer.
If the top of the stack is neither True
nor False
, call mustBeBoolean
on it without popping
the value.
If the top of the stack is True
, pop and discard it.
If the top of the stack is False
, jump ahead without popping the value (i.e., leave False
on the
top of the stack). The distance by which to jump is the value of the second and third instruction
bytes interpreted as a 16-bit integer.
If the top of the stack is neither True
nor False
, call mustBeBoolean
on it without popping
the value.
If the top of the stack is True
, jump ahead and set the top of the stack to True
. The distance
by which to jump is the value of the second and third instruction bytes interpreted as a 16-bit
integer.
If the top of the stack is False
, pop and discard it.
If the top of the stack is neither True
nor False
, call mustBeBoolean
on it without popping
the value.
Jump forward the number of instructions equal to the second and third instruction bytes interpreted as a 16-bit integer.
Jump backward the number of instructions equal to the second and third instruction bytes interpreted as a 16-bit integer. Also pop and discard the top of the stack.
From the source code:
also drops the stack. This saves an opcode in the while loop which is the only place this opcode is used.
Pop the top three objects on the stack and perform a binary operation on the last two using the top of the stack as an adverb. The adverb is ignored if the types of the two operands allow the operation to be performed primitively (i.e., Signal, float, and integer for most math operations). Place the result on the top of the stack.
The second instruction byte indicates the index of the binary operation as given in Opcodes.h
.
Sets the tail call state to 1. TODO what does this mean?
TODO in DumpBytes provide better desc for 'ControlOpcode'
TODO this produces bad bytecodes: 5.switch {9} {8} {10} {9} {9} {3}
TODO for switch, don't need to do linear probing - could construct a true jump table by allowing a certain ratio of nil slots; then just hash, compare once, and jump.