Skip to content

Instantly share code, notes, and snippets.

@swannodette
Forked from mossheim/bytecodes.md
Created June 15, 2020 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save swannodette/0bc35fdb425101450dd174e192c6e807 to your computer and use it in GitHub Desktop.
Save swannodette/0bc35fdb425101450dd174e192c6e807 to your computer and use it in GitHub Desktop.

SuperCollider Byte Code Reference

Brian Heim 2018-06-19

Table of Contents

  • Introduction
  • Definitions
  • Codes
  • Notes

Introduction

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.

Definitions

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.

Codes

Code 00: PushClassX [2]

Push a class to the top of the stack. "Special" classes are handled by a different op code (06).

The second byte: TODO (???)

Code 01: PushInstVarX [2]

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 %%%

Code 02: PushTempVarX [3]

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 %%%

Code 03: PushTempZeroVarX [2]

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 %%%

Code 04: PushLiteralX [2]

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]

Code 05: PushClassVarX [3]

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?]

Code 06: PushSpecialClass [2]

Push a "special" (intrinsic) class onto the stack.

The second instruction byte indicates the index of the intrinsic class within the internal array gSpecialClasses.

Code 07: StoreInstVarX [2]

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

Code 08: StoreTempVarX [3]

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

Code 09: StoreClassVarX [3]

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

Code 0A: SendMsgX [4]

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

Code 0B: SendMsgSuperX [4]

TODO: unclear how this should be used

Code 0C: SendSpecialMsgX [4]

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

Code 0D: SendSpecialUnaryArithMsgX [2]

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

Code 0E: SendSpecialUnaryArithMsgX [2]

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

Code 0F: SpecialOpcode [2]

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?]


Codes 10 to 1F: PushInstVar [1]

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

Code 20: JumpIfTrue [3]

Unused. [TODO: sure? not used in PyrParseNode]

Codes 21 to 27: PushTempVar [2]

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

Code 28: PushConstant [2]

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

Code 29: PushConstant [3]

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.

Code 2A: PushConstant [4]

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.

Code 2B: PushConstant [5]

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.

Codes 2C to 2F: PushInteger [2-5]

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

Codes 30 to 3F: PushTempZeroVar [1]

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.


Codes 40 to 4F: PushLiteral [1]

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

Codes 50 to 5F: PushClassVar [2]

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

Code 60: PushSpecialValue: 'this' [1]

Pushes this to the top of the stack.

Code 61: PushOneAndSubtract [1]

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

Codes 62 to 6A: PushSpecialValue [1]

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

Code 6B: PushOneAndAdd [1]

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

Codes 6C to 6F: PushSpecialValue [1]

Pushes a commonly used value to the top of the stack.

Code Value
6C True
6D False
6E Nil
6F Inf

Codes 70 to 7F: StoreInstVar [1]

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

Codes 80 to 87: StoreTempVar [2]

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

Code 88: PushInstVarAndSendSpecialMsg [3]

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

Code 89: PushAllArgs+SendMsg [2]

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

Code 8A: PushAllButFirstArg+SendMsg [2]

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

Code 8B: PushAllArgs+SendSpecialMsg [2]

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

Code 8C: PushAllButFirstArg+SendSpecialMsg [2]

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

Code 8D: PushAllButFirstTwoArgs+SendMsg [2]

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

Code 8E: PushAllButFirstTwoArgs+SendSpecialMsg [2]

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

Code 8F: Loop: Specialized byte codes

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.

Codes 8F 00 & 8F 01: Integer:-do

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.

Codes 8F 02, 8F 03, 8F 04: Integer:-reverseDo

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.

Codes 8F 05, 8F 06, 8F 10: Integer:-for

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.

Codes 8F 07, 8F 08, 8F 09: Integer:-forBy

TODO document with new type behavior

Codes 8F 0A, 8F 01: ArrayedCollection:-do

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.

Codes 8F 0B, 8F 0C, 8F 04: ArrayedCollection:-reverseDo

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.

Codes 8F 0D, 8F 0E: Dictionary:-keysValuesArrayDo

Implements Dictionary:-keysValuesArrayDo: 8F 0D 8F 0E. The frame of this method holds eight variables:

  1. the receiver
  2. array, array representing underlying storage
  3. the apply function
  4. index accumulator i, updated once per key or value
  5. pair index accumulator j, updated once per pair
  6. key, slot representing the current key
  7. value, slot representing the current value
  8. 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 ith 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.

Codes 8F 13, 8F 14, 8F 15: Float:-reverseDo

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

Code 8F 16: ? [2]

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?

Code 8F 17: ?? [4]

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.

Code 8F 18: ifNotNil [4]

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

Code 8F 19: ifNil [4]

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.

Code 8F 1A: ifNotNilPushNil [4]

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.

Code 8F 1B: ifNilPushNil [4]

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.

Code 8F 1C: switch [2]

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))?

Codes 8F 1D, 8F 1E, 8F 1F: Number:-forSeries

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 secondandlimitare nil,stepis set to1andlastis setINT_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.


Codes 90 to 9F: StoreClassVar [2]

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

Code A0: SendMsg (this-optimization) [2]

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

Codes A1 to AF: SendMsg [2]

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

Code B0: TailCallReturnFromFunction [1]

Sets the global tail call state to 2. TODO what does this mean?

Code B1 to BF: SuperMsg [2]

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

Code C0: SendSpecialMsg (this-optimization) [2]

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

Codes C1 to CF: SendSpecialMsg [2]

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

Code D0: SendSpecialUnaryArithMsg: '.neg' [1]

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

Code D1: SendSpecialUnaryArithMsg: '.not' [1]

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

Code D2: SendSpecialUnaryArithMsg: '.isNil' [1]

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

Code D3: SendSpecialUnaryArithMsg: '.notNil' [1]

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

Codes D4 to DF: SendSpecialBinaryArithMsg [1]

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

Code E0: SendSpecialBinaryArithMsg: '+' [1]

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.?]

Code E1: SendSpecialBinaryArithMsg: '-' [1]

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.?]

Code E2: SendSpecialBinaryArithMsg: '*' [1]

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.?]

Codes E3 to EF: SendSpecialBinaryArithMsg [1]

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

Code F0: Drop [1]

Pop the top of the stack and discard it.

Code F1: Dup [1]

Duplicate the top of the stack.

Code F2: BlockReturn (FunctionReturn) [1]

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.

Code F3: Return [1]

Used when returning from a class method. Return from the current method.

Sets the current

TODO document

Code F4: ReturnSelf [1]

Push this to the top of the stack and return from the current method, as in F3 Return.

Code F5: ReturnTrue [1]

Push True to the top of the stack and return from the current method, as in F3 Return.

Code F6: ReturnFalse [1]

Push False to the top of the stack and return from the current method, as in F3 Return.

Code F7: ReturnNil [1]

Push nil to the top of the stack and return from the current method, as in F3 Return.

Code F8: JumpIfFalse [3]

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.

Code F9: JumpIfFalsePushNil [3]

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.

Code FA: JumpIfFalsePushFalse [3]

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.

Code FB: JumpIfTruePushTrue [3]

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.

Code FC: JumpFwd [3]

Jump forward the number of instructions equal to the second and third instruction bytes interpreted as a 16-bit integer.

Code FD: JumpBak [3]

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.

Code FE: SpecialBinaryOpWithAdverb [2]

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.

Code FF: TailCallReturnFromMethod [1]

Sets the tail call state to 1. TODO what does this mean?

Notes

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment