Skip to content

Latest commit

 

History

History
597 lines (501 loc) · 11.2 KB

b4-tests.org

File metadata and controls

597 lines (501 loc) · 11.2 KB

b4 vm/debugger test suite

About this file

This is a suite of acceptance tests for the B4 virtual machine. There are B4 implementations in multiple languages, so this file helps to make sure they are all compatible and follow the spec.

Running the tests

This is a slightly messy process at the moment, as test runner is currently in another repository:

https://github.com/tangentstorm/learntris

See the test-b4-* scripts in this repository for examples.

[6/6] debugger interface

establish a command loop and a way to exit

> %q
= %q : quit
: The '%q' command instructs b4 to quit.
:
: b4 should not produce any output unless
: explicitly instructed to do so.

print the data stack

> ?d %q
ds: [] # should be an empty array
= ?d : query the data stack

print the call stack

> ?c %q
cs: [] # should be an empty array
= ?c : query the call stack

push a hex number to the stack

> FF CC ?d %q
ds: [FF CC]
= hex numbers are pushed to data stack
: it should print in hex

push an ascii character onto the stack

> 'a 'A ' ?d %q
ds: [61 41 20]
= ascii syntax
: note lack of a second space after the '
: it should print in hex

push control character addresses to stack

> `@ `A `B `C `X `Y `Z ?d %q
ds: [0 4 8 C 60 64 68]
= control characters
: the 32 ascii control characters act as a dictionary.
: (^@ is written 00 and is a no-op)
: ^@ calls the word whose address is at address 0000
:   (but the actual code emitted is =lb 00 cl= op, as bytecode 0 is a null op)
: ^A calls the word whose address is at address (4 * 1 = 0004)
: ^X calls the word whose address is at address (4 * ord('X')-ord('A') = 5C)
: But if we want handy way to refer to the address, we use `X
> `[ `\ `] `^ `_ ?d %q
ds: [6C 70 74 78 7C]
= non-alphabetic control characters
: there are 5 control characters after ^Z that use punctuation.

[17/17] alu / stack operations

ad op

> 01 02 ad ?d %q
ds: [3]
= ad: add top two items on the stack
: result is pushed back to stack

ml op

> 03 03 ml ?d %q
ds: [9]

sb op

> 0A 05 sb ?d %q
ds: [5]

dv op

> 0A 05 dv ?d %q
ds: [2]

md op

> 0A 05 md ?d
ds: [0]
> zp 0A 03 md ?d %q
ds: [1]

sh op

> 06 01 sh ?d %q
ds: [C]

an op

> 12 35 an ?d %q
ds: [10]

or op

> 12 35 or ?d %q
ds: [37]

xr op

> 12 35 xr ?d %q
ds: [27]

nt op

> 12 nt ?d %q
ds: [-13]

eq op

> AA BB eq CC CC eq ?d %q
ds: [0 -1]

lt op

> AA BB lt DD CC lt EE EE lt ?d %q
ds: [-1 0 0]

du op

> 0A du ?d %q
ds: [A A]

zp op

> 0A ?d zp ?d %q
ds: [A]
ds: []

sw op

> 0A 0B sw ?d %q
ds: [B A]

ov op

> 0A 0B ov ?d %q
ds: [A B A]

cd and dc ops

> 0A dc ?d ?c
ds: []
cs: [A]
> cd ?d ?c %q
ds: [A]
cs: []

[2/2] batch memory access from debugger

inspect ram

> ?100 %q
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. # 16 0 bytes

write to ram

> ?100
.. .. .. .. .. .. .. .. .. .. .. .. .. .. .. ..
> :100 00 AA BB CC
> ?100 %q
.. AA BB CC .. .. .. .. .. .. .. .. .. .. .. ..

[2/4] memory operations

TODO: test that %C clears memory and stacks

wi

wi writes a 32-bit integer.

> %C
> AABBCCDD 0100 wi
> ?100 %q
DD CC BB AA .. .. .. .. .. .. .. .. .. .. .. .. # 16 0 bytes

wb

wb writes a single byte:

> %C
> AABBCCDD 0100 wb
> ?100 %q
DD .. .. .. .. .. .. .. .. .. .. .. .. .. .. .. # 16 0 bytes

test the display of negative numbers

which of these should it be?

ds: [CC00BBAA]
ds: [-33FF4456]

rb op : read a value from address

rb reads a byte

> :100 AA BB 00 77
> ?100
AA BB .. +W .. .. .. .. .. .. .. .. .. .. .. ..
> 0100 ri ?d %q
ds: [7700BBAA]

ri writes a 32-bit integer

> :100 AA BB CC 00
> ?100
AA BB CC .. .. .. .. .. .. .. .. .. .. .. .. ..
> 0100 ri ?d %q
ds: [CCBBAA]

registers

read and write from shell

> 12345678 !X ?d
ds: []
> @X ?d %q
ds: [12345678]

read and write while executing

> :100 lb 12 !X @X c1 +X @X
> %s ?d
ds: [12]
> %s ?d ?X  # !X
ds: []
00000012
> %s ?d ?X  # @X
ds: [12]
00000012
> %s %s ?d ?X  # c1 +X
ds: [12 12]
00000013
> %s ?d ?X # @X
ds: [12 12 13]
00000013
> %q

+x ops

The “+” ops take a value off the stack and add it to a register, leaving the original value of the register. You can use this to treat the register as a cursor through a string or array of values.

> 11223344 !X
> 04 +X 02 +X @X ?d %q
ds: [11223344 11223348 1122334A]

the constant ops

> c0 c1 ?d %q
ds: [0 1]

[2/2] debugger/stepper

query instruction pointer

> ?i %q
ip: 100
= ?i : query instruction pointer
: it should print in hex

%s step command

> ?i %s ?i %q
ip: 100
ip: 101
= %s : step
: step and execute a no-op

[1/3] sequences

lb op

> :100 lb AB
> ?100
lb AB .. .. .. .. .. .. .. .. .. .. .. .. .. ..
> ?d
ds: [] # it should not be on the stack YET
> %s ?d ?i %q
ds: [AB]
ip: 102
= lb: load byte
: lb loads a byte from memory at runtime.
: we never needed it before because our debug shell
: is pushing numbers directly to the stack

li op

[1/8] control flow

.. (zero) is no-op

hp hop

Hop is a small relative jump. It takes a signed 8-bit int as a parameter, and can thus move the instruction pointer forward up to 127 bytes, or backwards up to 128 bytes.

forward

> :100 hp 05
> ?i %s ?i %q
ip: 100
ip: 105

forward max

> :100 hp 7F
> %s ?i %q
ip: 17F

forward wrap

here we set the high bit so it’s the same as negative 1. (but then that puts us at address 00FF, which is too small so we clamp to 0100 and then we have an infinite loop)

> :100 hp 80
> %s ?i %q
ip: 100

backward

> :100 .. .. .. hp -3
> %s %s %s ?i %s ?i %q
ip: 103
ip: 100

backward (and out of bounds)

> :100 hp -5
> %s ?i %q
ip: 100

zero?

This causes an infinite loop.

> :100 hp 00
> %s ?i %q
ip: 100

h0 hop if 0

h0 is the same as hp but conditional.

It pops a value off the data stack, and only hops if the value is 0.

when 0

We push 0 to the stack and then step, so we should jump to address $0123

> :100 h0 23
> 00 %s ?i %q
ip: 123

when 1

Here the hop is not taken, but we still hop over the argument.

> :100 h0 23
> 01 %s ?i %q
ip: 102

jm jump

jm is an unconditional jump to a 4-byte address.

> :100 jm 78 56 34 12
> %s ?i %q
ip: 12345678

cl call

cl is the same as jm but also pushes a return address to the control stack.

Note that the instruction pointer is incremented by 5 first, to skip over the cl op itself, plus its 4-byte argument.

> :100 cl 78 56 34 12
> %s ?i ?c %q
ip: 12345678
cs: [105]

rt return

In general, rt is used to return control from a called function.

The actual mechanic is a jump to an address popped from the control stack.

To simplify this test, we simply push the address we want to the control stack ourselves.

> :100 rt
> 1234 dc ?i ?c %s ?i ?c %q
ip: 100
cs: [1234]
ip: 1234
cs: []

rt dynamic call caveat

Here’s a small catch for the “dynamic call” technique used in the previous test.

It only comes into play when using the “calculator mode”.

Note that in the previous test, we used %s to trigger a step. This reads an instruction from ram[ip] and then causes the instruction pointer to increment.

If we had simply invoked rt directly using the “calculator”, no “step” has occured, and so the address would be off by one.

In general, it probably just doesn’t make sense to use contrtol flow ops from the “calculator” outside of testing.

> 1234 dc ?i ?c rt ?i ?c %q
ip: 100
cs: [1234]
ip: 1233
cs: []

nx next

This is probably the most complicated operation.

It’s intended for loops where you do something a fixed number of times.

An integer counter is stored on the control stack. Every time nx is run, the counter is decremented. A hop is triggered when the result is not zero, so that the loop continues until the countdown reaches 0.

On the step where it does reach zero, the counter is dropped.

In this test, we loop back to the starting address twice, and then proceed.

> :100 nx 00
> 2 dc
> ?c ?i
cs: [2]
ip: 100
> %s ?c ?i
cs: [1]
ip: 100
> %s ?c ?i
cs: []
ip: 102
> %s ?c ?i
cs: []
ip: 103
> %q

io op

‘e for emit

This should emit a single character.

While running in the b4i interpreter, it should buffer the output until the end of line is received.

> 'h 'e io 'i 'e io %q
hi

defining the ctrl registers

> ?E                # by default, most registers are blank
00000000
> ?_                # but "here" pointer is set to $100=256
00000100
> :E lb 'e io rt    # assemble "emit"
> ?E                # now ^E should be assigned
00000100
> ?_ %q             # and ^_ should reflect the 4 bytes we assembled
00000104
> :E lb 'e io rt
> 'o ^E 'k ^E
ok
> %q

host language

virtual hardware

[0/2] system ops (how to test these?)

db triggers the debugger

hl halts the virtual machine