REXX Language implementation
This chapter covers direct and conditional branches, counted-loop primitives, procedure calls and returns, VM termination, no-op control points, and packed jump-table dispatch.
Branch labels and procedure names are resolved by the assembler. Ordinary conditional branches read integer payloads without changing them; counted forms state their mutations explicitly. Packed jump-table instructions fall through on a miss and reserve signals for malformed runtime table data or strict slice bounds failures.
bcfBranch on a zero counter; otherwise advance one counted-loop step.
| Opcode | Form | Effect |
|---|---|---|
0x015e |
bcf label,rCounter |
Branch if zero; otherwise decrement rCounter. |
0x015f |
bcf label,rCounter,rIndex |
On fall-through, decrement the counter and increment the index. |
The zero test occurs before mutation. If rCounter is zero, control branches
and neither register changes. Otherwise its integer payload is decremented;
the three-operand form also increments rIndex. This is useful as a loop-entry
or exhaustion test.
No overflow/underflow or control-flow signal is raised; integer mutation uses the VM integer representation.
.globals=0
main() .locals=1
load r0,0
bcf empty,r0
ret
empty:
ret
bct, bctnm, brf.
bctDecrement a counter and branch while the new value remains positive.
| Opcode | Form | Effect |
|---|---|---|
0x0159 |
bct label,rCounter |
Decrement, then branch if greater than zero. |
0x015a |
bct label,rCounter,rIndex |
Also increment rIndex before testing the counter. |
The counter always decrements, whether or not the branch is taken. In the three-register form the index always increments. The branch uses the post-decrement counter value.
No integer overflow/underflow signal is raised.
.globals=0
main() .locals=1
load r0,1
bct again,r0
ret
again:
ret
bcf, bctnm, bctp.
bctnmDecrement a counter and branch while the new value is nonnegative.
| Opcode | Form | Effect |
|---|---|---|
0x015b |
bctnm label,rCounter |
Decrement, then branch if at least zero. |
0x015c |
bctnm label,rCounter,rIndex |
Also increment rIndex before the test. |
Both mutations are unconditional; the branch tests the counter afterward. This
differs from bct by including the iteration whose resulting counter is zero.
No integer overflow/underflow signal is raised.
.globals=0
main() .locals=1
load r0,0
bctnm again,r0
ret
again:
ret
bct, bcf, bctp.
bctpIncrement an integer register and branch unconditionally.
| Opcode | Form | Effect |
|---|---|---|
0x015d |
bctp label,rIndex |
Increment rIndex, then jump to label. |
The integer payload is incremented exactly once before control transfers. The instruction is a compact loop-back or progress-step primitive and never falls through.
No integer overflow signal is raised.
.globals=0
main() .locals=1
load r0,0
bctp done,r0
done:
ret
bct, bctnm, br.
beqBranch when two VM integer values are equal.
| Opcode | Form | Effect |
|---|---|---|
0x0028 |
beq label,rLeft,rRight |
Compare two integer register payloads. |
0x0029 |
beq label,rLeft,value |
Compare with an integer literal. |
The operands are not modified. Equality transfers to the procedure-local label; inequality falls through to the next instruction.
The comparison and branch do not signal.
.globals=0
main() .locals=1
load r0,2
beq equal,r0,2
ret
equal:
ret
bne, ieq, brt.
bgeBranch when a VM integer value is greater than or equal to another.
| Opcode | Form | Effect |
|---|---|---|
0x0162 |
bge label,rLeft,rRight |
Compare two integer register payloads. |
0x0163 |
bge label,rLeft,value |
Compare with an integer literal. |
The signed integer comparison does not change either operand. A true relation transfers to the label; false falls through.
The comparison and branch do not signal.
.globals=0
main() .locals=1
load r0,2
bge matched,r0,2
ret
matched:
ret
bgt, ble, igte.
bgtBranch when a VM integer value is strictly greater than another.
| Opcode | Form | Effect |
|---|---|---|
0x0160 |
bgt label,rLeft,rRight |
Compare two integer register payloads. |
0x0161 |
bgt label,rLeft,value |
Compare with an integer literal. |
The signed comparison is side-effect free. A true relation branches; equality or a smaller left value falls through.
The comparison and branch do not signal.
.globals=0
main() .locals=1
load r0,3
bgt matched,r0,2
ret
matched:
ret
bge, blt, igt.
bleBranch when a VM integer value is less than or equal to another.
| Opcode | Form | Effect |
|---|---|---|
0x0166 |
ble label,rLeft,rRight |
Compare two integer register payloads. |
0x0167 |
ble label,rLeft,value |
Compare with an integer literal. |
The signed comparison leaves both operands unchanged. A true relation branches; a greater left operand falls through.
The comparison and branch do not signal.
.globals=0
main() .locals=1
load r0,2
ble matched,r0,2
ret
matched:
ret
blt, bge, ilte.
bltBranch when a VM integer value is strictly less than another.
| Opcode | Form | Effect |
|---|---|---|
0x0164 |
blt label,rLeft,rRight |
Compare two integer register payloads. |
0x0165 |
blt label,rLeft,value |
Compare with an integer literal. |
The signed comparison is side-effect free. A true relation transfers control; equality or a greater left value falls through.
The comparison and branch do not signal.
.globals=0
main() .locals=1
load r0,1
blt matched,r0,2
ret
matched:
ret
ble, bgt, ilt.
bneBranch when two VM integer values are unequal.
| Opcode | Form | Effect |
|---|---|---|
0x002a |
bne label,rLeft,rRight |
Compare two integer register payloads. |
0x002b |
bne label,rLeft,value |
Compare with an integer literal. |
The operands are unchanged. Inequality transfers to the target label; equality falls through.
The comparison and branch do not signal.
.globals=0
main() .locals=1
load r0,1
bne different,r0,2
ret
different:
ret
beq, ine, brf.
brTransfer control unconditionally to a procedure-local label.
| Opcode | Form | Effect |
|---|---|---|
0x0024 |
br label |
Continue execution at label. |
The assembler resolves and backpatches the label. No register, payload, or flag is read or modified, and there is no fall-through path.
A valid assembled branch does not signal. Undefined labels are assembler errors.
.globals=0
main() .locals=0
br done
done:
ret
brt, brf, brtf.
brfBranch when an integer register payload is zero.
| Opcode | Form | Effect |
|---|---|---|
0x0026 |
brf label,rCondition |
Branch for integer false; otherwise fall through. |
Zero is false and every nonzero integer is true. The register is not normalized or otherwise modified.
The test and branch do not signal.
.globals=0
main() .locals=1
load r0,0
brf false,r0
ret
false:
ret
brt, brtf, bcf.
brtBranch when an integer register payload is nonzero.
| Opcode | Form | Effect |
|---|---|---|
0x0025 |
brt label,rCondition |
Branch for integer true; otherwise fall through. |
Every nonzero integer is true. The source is inspected without normalization or mutation.
The test and branch do not signal.
.globals=0
main() .locals=1
load r0,1
brt true,r0
ret
true:
ret
brf, brtf, br.
brtfChoose between two labels from one integer Boolean condition.
| Opcode | Form | Effect |
|---|---|---|
0x0027 |
brtf trueLabel,falseLabel,rCondition |
Branch true for nonzero, false for zero. |
The instruction always transfers control and never falls through. It reads only the integer payload and does not mutate the condition register.
The test and branch do not signal.
.globals=0
main() .locals=1
load r0,1
brtf true,false,r0
true:
ret
false:
ret
brt, brf, br.
brtpandtBranch when any requested readable status-flag bit is set.
| Opcode | Form | Effect |
|---|---|---|
0x020c |
brtpandt label,rValue,mask |
Test (readable_flags & mask) != 0. |
The integer literal becomes a 32-bit mask intersected with
RXFLAG_READABLE_MASK; the reserved sign bit cannot match. This is an any-bit
test. The value and all flags remain unchanged.
The flag test and branch do not signal.
.globals=0
main() .locals=1
setortp r0,512
brtpandt matched,r0,512
ret
matched:
ret
brtpt, getandtp, setortp.
brtptBranch when a value has any legacy public-test status-flag bit set.
| Opcode | Form | Effect |
|---|---|---|
0x020b |
brtpt label,rValue |
Test compiler, library, and user flag bands. |
The test uses RXFLAG_PUBLIC_TEST_MASK: compiler-ABI, library, and user bands.
It deliberately excludes VM-private state, protected language metadata, and
the reserved sign bit so cache/certificate changes cannot alter legacy branch
behavior. It does not alter the value or flags.
The flag test and branch do not signal.
.globals=0
main() .locals=1
setortp r0,512
brtpt matched,r0
ret
matched:
ret
brtpandt, gettp, setortp.
callInvoke a named RXAS or native procedure, optionally collecting a return value and passing a contiguous argument block.
| Opcode | Form | Effect |
|---|---|---|
0x002c |
call procedure() |
Call without a return destination or arguments. |
0x002d |
call rResult,procedure() |
Clear rResult, call, and collect a return value. |
0x002e |
call rResult,procedure(),rArgCount |
Call with a contiguous argument block. |
For the argument form, rArgCount holds the integer count and the actual
arguments occupy consecutive register numbers immediately after it. RXAS
procedures receive live argument storage bindings; native procedures receive
the corresponding values through the plugin ABI. The no-argument result form
clears rResult before dispatch. A return resumes at the next instruction.
Raises FUNCTION_NOT_FOUND for an unresolved runtime procedure and FAILURE
when an RXAS call frame cannot be allocated. Native or callee signals propagate
normally. The opcode does not bounds-check a hand-written argument block.
.globals=0
main() .locals=1
call r0,worker()
ret
worker() .locals=0
ret 7
ret, dcall, linkarg.
call1Call a bytecode procedure with one explicit caller register as its argument.
| Opcode | Form | Effect |
|---|---|---|
0x0191 |
call1 rResult,procedure(),rArg1 |
Call with rArg1 visible as a1. |
The callee receives the explicit register through the ordinary a1 view and
returns through rResult. Caller-side status/copy preparation remains separate
and unchanged. This fixed form is for resolved bytecode targets; imported,
native, dynamic, or unsupported calls retain the general call path.
Raises FUNCTION_NOT_FOUND for an unresolved target, NOT_IMPLEMENTED for a
native target, and FAILURE if the bytecode frame cannot be allocated. Callee
signals propagate normally.
.globals=0
main() .locals=2
load r1,7
call1 r0,identity(),r1
ret
identity() .locals=0
ret a1
call, call2, ret.
call2Call a bytecode procedure with two explicit caller registers as its arguments.
| Opcode | Form | Effect |
|---|---|---|
0x0192 |
call2 rResult,procedure(),rArg1,rArg2 |
Call with arguments visible as a1 and a2. |
The two explicit registers become the callee’s ordinary a1 and a2 views;
the return value is written through rResult. Caller-side status/copy
preparation remains separate. Only resolved bytecode targets use this form.
Raises FUNCTION_NOT_FOUND for an unresolved target, NOT_IMPLEMENTED for a
native target, and FAILURE if the bytecode frame cannot be allocated. Callee
signals propagate normally.
.globals=0
main() .locals=3
load r1,7
load r2,8
call2 r0,second(),r1,r2
ret
second() .locals=0
ret a2
call, call1, call3, ret.
call3Call a bytecode procedure with three explicit caller registers as its arguments.
| Opcode | Form | Effect |
|---|---|---|
0x0193 |
call3 rResult,procedure(),rArg1,rArg2,rArg3 |
Call with arguments visible as a1 through a3. |
The explicit registers map in order to the callee’s ordinary argument views;
the return value is written through rResult. Caller-side status/copy
preparation remains separate. Only resolved bytecode targets use this form.
Raises FUNCTION_NOT_FOUND for an unresolved target, NOT_IMPLEMENTED for a
native target, and FAILURE if the bytecode frame cannot be allocated. Callee
signals propagate normally.
.globals=0
main() .locals=4
load r1,7
load r2,8
load r3,9
call3 r0,third(),r1,r2,r3
ret
third() .locals=0
ret a3
call, call2, call4, ret.
call4Call a bytecode procedure with four explicit caller registers as its arguments.
| Opcode | Form | Effect |
|---|---|---|
0x0194 |
call4 rResult,procedure(),rArg1,rArg2,rArg3,rArg4 |
Call with arguments visible as a1 through a4. |
The explicit registers map in order to the callee’s ordinary argument views;
the return value is written through rResult. Caller-side status/copy
preparation remains separate. Higher arities and non-bytecode targets retain
the general counted call form.
Raises FUNCTION_NOT_FOUND for an unresolved target, NOT_IMPLEMENTED for a
native target, and FAILURE if the bytecode frame cannot be allocated. Callee
signals propagate normally.
.globals=0
main() .locals=5
load r1,7
load r2,8
load r3,9
load r4,10
call4 r0,fourth(),r1,r2,r3,r4
ret
fourth() .locals=0
ret a4
call, call3, ret.
cnopExecute an explicit no-operation instruction, primarily for tests, patch points, or deliberately retained instruction positions.
| Opcode | Form | Effect |
|---|---|---|
0x0057 |
cnop rA,rB,rC,rD,rE,rF,rG,rH,rI |
Consume nine register inputs and advance without changing VM state. |
0x021c |
cnop |
Advance to the next instruction without changing VM state. |
The zero-operand form has no inputs. The nine-operand form records explicit register reads for optimiser liveness but intentionally leaves the register values unchanged. Flags, frames, and control metadata remain unchanged except for normal program-counter advance.
This instruction does not signal.
.globals=0
main() .locals=9
load r0,0
load r1,1
load r2,2
load r3,3
load r4,4
load r5,5
load r6,6
load r7,7
load r8,8
cnop r0,r1,r2,r3,r4,r5,r6,r7,r8
ret
br.
exitTerminate the entire VM invocation immediately with an integer process result.
| Opcode | Form | Effect |
|---|---|---|
0x0035 |
exit |
Terminate with result code 0. |
0x0036 |
exit rCode |
Use the register’s integer payload. |
0x0037 |
exit code |
Use an integer literal. |
Unlike ret, exit does not return to a caller or unwind through RXAS return
sites. The selected integer becomes the interpreter’s result code; later
instructions are not executed.
Normal exit raises no VM signal.
.globals=0
main() .locals=0
exit
ret.
jumpbDispatch through a packed table using a whole logical binary value as the key.
| Opcode | Form | Effect |
|---|---|---|
0x0278 |
jumpb rKey,table |
Branch to the matching binary .jcase. |
rKey supplies its complete binary payload and table is a procedure-local
.jtable whose cases are binary literals. A match branches to the case label;
a miss falls through. The source and table are unchanged.
Malformed packed data or an invalid target raises RXBIN_CORRUPTION.
.globals=0
main() .locals=3
.jtable tokens linear
br after_cases
tok_ab: .jcase tokens 0x6162
load r1,1
ret
after_cases:
load r0,0x6162
jumpb r0,tokens
load r1,0
ret
jumps, jumpbs, jumpi, .jtable, .jcase.
jumpbsDispatch through a fixed-width binary table using a zero-copy source slice.
| Opcode | Form | Effect |
|---|---|---|
0x0279 |
jumpbs rBinary,rOffset,table |
Match the table-width slice at a byte offset. |
rOffset supplies a zero-based byte offset. All table keys must be nonempty
binary literals of one fixed width, which determines the slice length. A match
branches and a miss falls through. Neither register is modified, and no
temporary slice is allocated.
Negative offsets, variable/zero-width tables, or a slice outside the logical
binary length raise OUT_OF_RANGE. Malformed packed data or targets raise
RXBIN_CORRUPTION.
.globals=0
main() .locals=4
.jtable pairs linear
br after_cases
tok_bc: .jcase pairs 0x6263
load r2,1
ret
after_cases:
load r0,0x616263
load r1,1
jumpbs r0,r1,pairs
load r2,0
ret
jumpb, jumps, jumpi, bcopy, bslice.
jumpiDispatch through a packed table using a VM integer key.
| Opcode | Form | Effect |
|---|---|---|
0x027a |
jumpi rKey,table |
Branch to the matching integer .jcase. |
The integer payload is canonicalized as eight little-endian bytes, matching integer-literal cases in the procedure-local table. A match branches and a miss falls through. The key register is unchanged.
Malformed packed data or an invalid target raises RXBIN_CORRUPTION.
.globals=0
main() .locals=3
.jtable choices auto
br after_cases
case_two: .jcase choices 2
load r1,20
ret
after_cases:
load r0,2
jumpi r0,choices
load r1,0
ret
jumps, jumpb, jumpbs.
jumpsDispatch through a packed table using a string’s exact UTF-8 bytes.
| Opcode | Form | Effect |
|---|---|---|
0x0277 |
jumps rKey,table |
Branch to an exact string .jcase. |
The logical string bytes, excluding any NUL terminator, are matched against string-literal cases. A match branches and a miss falls through. The string is unchanged; this form performs exact byte matching without numeric or trailing-blank canonicalization.
Malformed packed data or an invalid target raises RXBIN_CORRUPTION.
.globals=0
main() .locals=3
.jtable keywords linear
br after_cases
keyword_if: .jcase keywords "if"
load r1,1
ret
after_cases:
load r0,"if"
jumps r0,keywords
load r1,0
ret
jumpb, jumpbs, jumpi, jumpr, jumpn, .jtable, .jcase.
jumprDispatch using Rexx blank-padded nonnumeric string equality.
| Opcode | Form | Effect |
|---|---|---|
0x027b |
jumpr rKey,table |
Match after ignoring trailing ASCII spaces. |
The assembler trims trailing ASCII spaces from case keys and the VM ignores them in the runtime string. Leading spaces remain significant. A match branches and a miss falls through without copying or changing the source. A table cannot mix exact, padded, and numeric string modes.
Malformed packed data or a bad target raises RXBIN_CORRUPTION; canonical
duplicate cases are assembler errors.
.globals=0
main() .locals=2
.jtable words auto
br after_cases
word_if: .jcase words "if "
ret
after_cases:
load r0,"if"
jumpr r0,words
ret
jumps, jumpn, .jtable, .jcase, req.
jumpnParse a string once and dispatch equivalent numeric spellings through a packed table.
| Opcode | Form | Effect |
|---|---|---|
0x027c |
jumpn rKey,table |
Match a canonical numeric string key. |
Assembler and VM use the shared string-to-double parser and canonical
little-endian IEEE-754 key bytes; -0 and 0 are one key. A numeric match
branches, while nonnumeric input falls through. The source is unchanged. NaN
input retains loose-comparison first-case alias behavior.
Malformed packed data or targets raise RXBIN_CORRUPTION. Nonnumeric/NaN case
keys, canonical duplicates, and mixed table modes are assembler errors.
.globals=0
main() .locals=2
.jtable numbers auto
br after_cases
case_one: .jcase numbers "1"
ret
after_cases:
load r0,"01.000"
jumpn r0,numbers
ret
jumpr, jumps, .jtable, .jcase, req.
retReturn from the current procedure, optionally supplying a typed result.
| Opcode | Form | Effect |
|---|---|---|
0x0030 |
ret |
Return without a value. |
0x0031 |
ret rValue |
Return a complete register value. |
0x0032 |
ret integer |
Return an integer literal payload. |
0x0033 |
ret float |
Return a float literal payload. |
0x0034 |
ret "text" |
Return a string constant payload. |
The register form full-copies nonlocal/linked storage but may move an ordinary
callee-local value into the caller’s return register before destroying the
frame. Literal forms write only their respective payload. A void return leaves
the caller’s result as prepared by call. Returning from main() terminates
the interpreter; an integer result supplies its process result code.
Return itself does not signal. It also participates in signal-handler action returns when the current frame is an interrupt frame.
.globals=0
main() .locals=1
call r0,worker()
ret
worker() .locals=0
ret "done"
call, exit.