summaryrefslogtreecommitdiff
path: root/tests/fixtures
diff options
context:
space:
mode:
authorkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
committerkx <kx@radix-linux.su>2026-10-09 18:00:15 +0300
commit065da3509019b15dd0e678072e6be629e5330bef (patch)
tree69c934c9e786ac048b4fbe8f4c4c5571661521f4 /tests/fixtures
parentd130fe27fa753be02ed76fd9b277aa3c3d928eb9 (diff)
downloadlibmcpu-0.4.0.tar.xz
Version 0.4.00.4.0
Diffstat (limited to 'tests/fixtures')
-rw-r--r--tests/fixtures/argc-argv.s44
-rw-r--r--tests/fixtures/block-copy.s46
-rw-r--r--tests/fixtures/byte-swap.s15
-rw-r--r--tests/fixtures/call.s39
-rw-r--r--tests/fixtures/frame-pointer.s63
-rw-r--r--tests/fixtures/hll/libc-stub.S42
-rw-r--r--tests/fixtures/hll/model-pendulum-life.S133
-rw-r--r--tests/fixtures/hll/program-main.S48
-rw-r--r--tests/fixtures/indirect-control.s39
-rw-r--r--tests/fixtures/life-print.s30
-rw-r--r--tests/fixtures/model-lifecycle.s53
-rw-r--r--tests/fixtures/mpu-ops.s25
-rw-r--r--tests/fixtures/psw-call-abi.s36
-rw-r--r--tests/fixtures/radd-pi.s24
-rw-r--r--tests/fixtures/shared-libs/dynamic-liba.s18
-rw-r--r--tests/fixtures/shared-libs/dynamic-libb.s18
-rw-r--r--tests/fixtures/shared-libs/dynamic-main.s35
-rw-r--r--tests/fixtures/stack.s32
18 files changed, 740 insertions, 0 deletions
diff --git a/tests/fixtures/argc-argv.s b/tests/fixtures/argc-argv.s
new file mode 100644
index 0000000..4c1d2ac
--- /dev/null
+++ b/tests/fixtures/argc-argv.s
@@ -0,0 +1,44 @@
+; Minimal argc/argv and explicit "--" separator example.
+;
+; Assemble/link:
+; ../utils/mcpu-as -o argc-argv.o argc-argv.s
+; ../utils/mcpu-ld -o argc-argv --stack-size=4K argc-argv.o
+;
+; Run:
+; ../utils/mcpu-run \
+; --on-step=print-int-regs:r64@0x00000,r8@0x00020,r8@0x00021 \
+; -- \
+; argc-argv --help -x
+;
+; Guest argv is:
+; argv[0] = "argc-argv"
+; argv[1] = "--help"
+; argv[2] = "-x"
+;
+; The example prints argc, argv[1][2] ('h') and argv[2][1] ('x') as integers.
+; Expected output:
+; 3 104 120
+
+.text
+_start_life:
+loop:
+ ; Persistent McpuStartup at SP:
+ ; SP+8 = argc
+ ; SP+16 = argv (PTR64 to argv[0])
+ LDSP r64@0x00000, 8
+ LDSP r64@0x00008, 16
+
+ ; argv[1]
+ PADDI r64@0x00010, r64@0x00008, 8
+ LOADP r64@0x00018, r64@0x00010
+ PADDI r64@0x00018, r64@0x00018, 2
+ LOADP r8@0x00020, r64@0x00018
+
+ ; argv[2]
+ PADDI r64@0x00010, r64@0x00008, 16
+ LOADP r64@0x00018, r64@0x00010
+ PADDI r64@0x00018, r64@0x00018, 1
+ LOADP r8@0x00021, r64@0x00018
+
+ END
+ JMP loop
diff --git a/tests/fixtures/block-copy.s b/tests/fixtures/block-copy.s
new file mode 100644
index 0000000..81422e2
--- /dev/null
+++ b/tests/fixtures/block-copy.s
@@ -0,0 +1,46 @@
+; Build/run from examples/:
+; ../utils/mcpu-as -o block-copy.o block-copy.s
+; ../utils/mcpu-ld --stack-size=512 -o block-copy block-copy.o
+; ../utils/mcpu-run --int-reg=r64@0x00028 block-copy
+;
+; Expected output: 22
+;
+; LibCPU block-copy example.
+;
+; 1. Copy a 32-byte object from DATA to a local STACK object with BMOVI.
+; 2. Use BMOV with a runtime byte count to perform an overlapping move
+; inside that STACK object. BMOV has memmove semantics.
+; 3. Read the moved value back through LOADP. Expected result: 22.
+
+.data
+.align 8
+source_object:
+ .integer 64, 11
+ .integer 64, 22
+ .integer 64, 33
+ .integer 64, 44
+
+.text
+_start_life:
+ ; Source PTR64 -> DATA object.
+ LEA r64@0x00000, source_object
+
+ ; Reserve 32 bytes for a local object and obtain its STACK PTR64.
+ ADJSP -32
+ LEASP r64@0x00008, 0
+
+ ; Fixed-size structure copy: DATA -> STACK.
+ BMOVI r64@0x00008, r64@0x00000, 32
+
+ ; Overlapping move inside the local object:
+ ; bytes [0..15] -> [8..23]. memmove semantics are required.
+ PADDI r64@0x00010, r64@0x00008, 8
+ LDI r64@0x00018, 16
+ BMOV r64@0x00010, r64@0x00008, r64@0x00018
+
+ ; The third 64-bit slot now contains the old second slot: 22.
+ PADDI r64@0x00020, r64@0x00008, 16
+ LOADP r64@0x00028, r64@0x00020
+
+ ADJSP 32
+ HALT
diff --git a/tests/fixtures/byte-swap.s b/tests/fixtures/byte-swap.s
new file mode 100644
index 0000000..7f666d0
--- /dev/null
+++ b/tests/fixtures/byte-swap.s
@@ -0,0 +1,15 @@
+; Byte-swap example.
+;
+; Build/run from examples/:
+; ../utils/mcpu-as -o byte-swap.o byte-swap.s
+; ../utils/mcpu-ld --stack-size=512 -o byte-swap byte-swap.o
+; ../utils/mcpu-run --int-reg=r64@0x00010 byte-swap
+;
+; Expected output: 42
+;
+.text
+_start_life:
+ LDI r64@0x00000, 42
+ BSWAP r64@0x00008, r64@0x00000
+ BSWAP r64@0x00010, r64@0x00008
+ HALT
diff --git a/tests/fixtures/call.s b/tests/fixtures/call.s
new file mode 100644
index 0000000..bfc9965
--- /dev/null
+++ b/tests/fixtures/call.s
@@ -0,0 +1,39 @@
+; Build/run from examples/:
+; ../utils/mcpu-as -o call.o call.s
+; ../utils/mcpu-ld --stack-size=128 -o call call.o
+; ../utils/mcpu-run --int-reg=r64@0x00010 call
+;
+; Expected output: 33
+;
+; LibCPU CALL ABI example.
+;
+; add2(a,b): this two-r64 argument block is exactly 16 bytes, so the
+; traditional right-to-left PUSH sequence also satisfies the formal ABI.
+; CALL then pushes
+; the 64-bit return PC, therefore on callee entry:
+; SP+0 = return address
+; SP+8 = first argument a
+; SP+16 = second argument b
+;
+; Before CALL, SP is 16-byte aligned. The r64 result is returned in the canonical ABI return view
+; r64@0x7fff8. The caller removes its own arguments with ADJSP.
+
+.text
+_start_life:
+
+ LDI r64@0x00000, 11
+ LDI r64@0x00008, 22
+
+ PUSH r64@0x00008 ; second/rightmost argument b
+ PUSH r64@0x00000 ; first/leftmost argument a
+ CALL add2
+ ADJSP 16 ; caller cleanup
+
+ MOV r64@0x00010, r64@0x7fff8
+ HALT
+
+add2:
+ LDSP r64@0x00100, 8 ; a
+ LDSP r64@0x00108, 16 ; b
+ IADD r64@0x7fff8, r64@0x00100, r64@0x00108
+ RET
diff --git a/tests/fixtures/frame-pointer.s b/tests/fixtures/frame-pointer.s
new file mode 100644
index 0000000..6c6934b
--- /dev/null
+++ b/tests/fixtures/frame-pointer.s
@@ -0,0 +1,63 @@
+; Build/run from examples/:
+; ../utils/mcpu-as -o frame-pointer.o frame-pointer.s
+; ../utils/mcpu-ld --stack-size=512 -o frame-pointer frame-pointer.o
+; ../utils/mcpu-run --int-reg=r64@0x00008 frame-pointer
+;
+; Expected output: 42
+;
+; LibCPU optional frame-pointer and dynamic STACK allocation example.
+;
+; demo(5) builds a fixed frame, creates a 24-byte dynamic object below it,
+; accesses a fixed local through FP while SP is moving, and returns 42 in
+; the canonical r64 return register.
+
+.text
+_start_life:
+ LDI r64@0x00000, 5
+ ADJSP -16 ; 16-byte ABI argument block
+ STSP 0, r64@0x00000
+ CALL demo
+ ADJSP 16
+ MOV r64@0x00008, r64@0x7fff8
+ HALT
+
+demo:
+ PUSHFP
+ SETFP
+ ADJSP -16
+
+ ; First explicit argument. With FP in use the layout is:
+ ; FP+0 saved caller FP
+ ; FP+8 return PC
+ ; FP+16 first explicit argument
+ LDFP r64@0x00100, 16
+
+ ; Fixed local at FP-8.
+ STFP -8, r64@0x00100
+ LEAFP r64@0x00108, -8
+
+ ; Save the current stack top as a dynamic-scope mark.
+ LEASP r64@0x00110, 0
+
+ ; Dynamically reserve 24 bytes: new_SP = old_SP - 24.
+ LDI r64@0x00118, 24
+ PSUB r64@0x00120, r64@0x00110, r64@0x00118
+ SETSP r64@0x00120
+
+ ; Put 37 in the dynamic object at current SP.
+ LDI r64@0x00128, 37
+ LEASP r64@0x00130, 0
+ STOREP r64@0x00130, r64@0x00128
+
+ ; FP-relative access is stable although SP moved dynamically.
+ LDFP r64@0x00138, -8
+ LOADP r64@0x00140, r64@0x00130
+ IADD r64@0x7fff8, r64@0x00138, r64@0x00140
+
+ ; Restore the nested dynamic scope explicitly.
+ SETSP r64@0x00110
+
+ ; Destroy the complete frame below FP, restore caller FP and return.
+ RESTSP
+ POPFP
+ RET
diff --git a/tests/fixtures/hll/libc-stub.S b/tests/fixtures/hll/libc-stub.S
new file mode 100644
index 0000000..d4fd878
--- /dev/null
+++ b/tests/fixtures/hll/libc-stub.S
@@ -0,0 +1,42 @@
+; Test/development MCPU Libc emulator.
+;
+; This source is NEVER installed. The register marker is intentionally a
+; testing convention only; it is not part of the CRT <-> MCPU Libc ABI.
+
+.global __mcpu_libc_init
+.global __mcpu_init_array
+.global __mcpu_fini_array
+.global __mcpu_libc_fini
+
+.text
+__mcpu_libc_init:
+ CLR r64@0x70008
+ ICMP r64@0x70000, r64@0x70008
+ JNZ .L_stub_bad
+ LDI r64@0x70000, 1
+ RET
+
+__mcpu_init_array:
+ LDI r64@0x70008, 1
+ ICMP r64@0x70000, r64@0x70008
+ JNZ .L_stub_bad
+ LDI r64@0x70000, 2
+ RET
+
+__mcpu_fini_array:
+ LDI r64@0x70008, 4
+ ICMP r64@0x70000, r64@0x70008
+ JNZ .L_stub_bad
+ LDI r64@0x70000, 5
+ RET
+
+__mcpu_libc_fini:
+ LDI r64@0x70008, 5
+ ICMP r64@0x70000, r64@0x70008
+ JNZ .L_stub_bad
+ LDI r64@0x70000, 6
+ RET
+
+.L_stub_bad:
+ LDI r64@0x70000, 999
+ RET
diff --git a/tests/fixtures/hll/model-pendulum-life.S b/tests/fixtures/hll/model-pendulum-life.S
new file mode 100644
index 0000000..5ad6ddb
--- /dev/null
+++ b/tests/fixtures/hll/model-pendulum-life.S
@@ -0,0 +1,133 @@
+; Assembler emulator of future mcpu-cc MODEL output.
+;
+; The lifecycle shape is a compact pendulum-style MODEL regression fixture.
+; INIT snapshots CONTROL
+; into DATA, LIFE advances one model step and publishes one TRACE frame, and
+; fini() marks graceful model finalization.
+;
+; This reduced fixture tests the HLL MODEL/CRT protocol, not RK4 numerics.
+
+.global init
+.global life
+.global fini
+
+.control
+.align 8
+control_n:
+ .integer 64, 3
+control_error_step:
+ .integer 64, 0
+
+.data
+.align 8
+model_n:
+ .integer 64, 0
+model_error_step:
+ .integer 64, 0
+model_step:
+ .integer 64, 0
+model_fini_marker:
+ .integer 64, 0
+
+.trace
+.align 8
+trace_step:
+ .integer 64, 0
+
+.text
+; int32 init(int64 argc, PTR64 argv)
+init:
+ LDI r64@0x70200, 2
+ ICMP r64@0x70000, r64@0x70200
+ JNZ .L_init_bad_order
+
+ ; The test invokes the MODEL without extra arguments.
+ LDSP r64@0x70208, 8
+ LDI r64@0x70210, 1
+ ICMP r64@0x70208, r64@0x70210
+ JNZ .L_init_bad_args
+
+ ; Negative CONTROL.n deliberately requests an init failure.
+ LEA r64@0x70218, control_n
+ LOADP r64@0x70220, r64@0x70218
+ CLR r64@0x70228
+ ICMP r64@0x70220, r64@0x70228
+ JS .L_init_fail
+
+ STORE model_n, r64@0x70220
+ LEA r64@0x70218, control_error_step
+ LOADP r64@0x70230, r64@0x70218
+ STORE model_error_step, r64@0x70230
+
+ CLR r64@0x70238
+ STORE model_step, r64@0x70238
+ STORE model_fini_marker, r64@0x70238
+
+ LDI r64@0x70000, 3
+ CLR r32@0x7fffc
+ RET
+
+.L_init_fail:
+ LDI r64@0x70000, 3
+ LDI r32@0x7fffc, 17
+ RET
+
+.L_init_bad_order:
+ LDI r64@0x70000, 910
+ LDI r32@0x7fffc, 18
+ RET
+
+.L_init_bad_args:
+ LDI r64@0x70000, 911
+ LDI r32@0x7fffc, 19
+ RET
+
+; int32 life(void)
+life:
+ LOAD r64@0x70300, model_step
+ LDI r64@0x70308, 1
+ IADD r64@0x70300, r64@0x70300, r64@0x70308
+ STORE model_step, r64@0x70300
+
+ ; Optional positive LIFE error requested by CONTROL.error_step.
+ LOAD r64@0x70310, model_error_step
+ CLR r64@0x70318
+ ICMP r64@0x70310, r64@0x70318
+ JZ .L_no_life_error
+ ICMP r64@0x70300, r64@0x70310
+ JNZ .L_no_life_error
+ LDI r32@0x7fffc, 9
+ RET
+
+.L_no_life_error:
+ ; Pendulum-style sequential stop: step == n + 1 means natural end.
+ LOAD r64@0x70320, model_n
+ IADD r64@0x70328, r64@0x70320, r64@0x70308
+ ICMP r64@0x70300, r64@0x70328
+ JZ .L_life_end
+
+ LEA r64@0x70330, trace_step
+ STOREP r64@0x70330, r64@0x70300
+
+ ; LIFE_CONTINUE == INT32_MIN.
+ LDI r32@0x7fffc, -2147483648
+ RET
+
+.L_life_end:
+ CLR r32@0x7fffc
+ RET
+
+fini:
+ LDI r64@0x70200, 3
+ ICMP r64@0x70000, r64@0x70200
+ JNZ .L_model_fini_bad
+
+ LDI r64@0x70000, 4
+ LDI r64@0x70208, 1
+ STORE model_fini_marker, r64@0x70208
+ MOV r64@0x70010, r64@0x70208
+ RET
+
+.L_model_fini_bad:
+ LDI r64@0x70000, 912
+ RET
diff --git a/tests/fixtures/hll/program-main.S b/tests/fixtures/hll/program-main.S
new file mode 100644
index 0000000..3161343
--- /dev/null
+++ b/tests/fixtures/hll/program-main.S
@@ -0,0 +1,48 @@
+; Assembler emulator of future mcpu-cc PROGRAM output.
+;
+; It intentionally defines only main() and fini(); lifecycle symbols belong
+; to CRT and must be inserted automatically by mcpu-ld.
+
+.global main
+.global fini
+
+.text
+main:
+ ; Startup hooks must already have completed.
+ LDI r64@0x70100, 2
+ ICMP r64@0x70000, r64@0x70100
+ JNZ .L_main_bad
+
+ ; argc must include argv[0] plus the two test arguments.
+ LDSP r64@0x70108, 8
+ LDI r64@0x70110, 3
+ ICMP r64@0x70108, r64@0x70110
+ JNZ .L_main_bad
+
+ ; argv[argc] is the required NULL terminator.
+ LDSP r64@0x70118, 16
+ PADDI r64@0x70120, r64@0x70118, 24
+ LOADP r64@0x70128, r64@0x70120
+ CLR r64@0x70130
+ ICMP r64@0x70128, r64@0x70130
+ JNZ .L_main_bad
+
+ LDI r64@0x70000, 3
+ LDI r32@0x7fffc, 42
+ RET
+
+.L_main_bad:
+ LDI r64@0x70000, 900
+ LDI r32@0x7fffc, 43
+ RET
+
+fini:
+ LDI r64@0x70100, 3
+ ICMP r64@0x70000, r64@0x70100
+ JNZ .L_fini_bad
+ LDI r64@0x70000, 4
+ RET
+
+.L_fini_bad:
+ LDI r64@0x70000, 901
+ RET
diff --git a/tests/fixtures/indirect-control.s b/tests/fixtures/indirect-control.s
new file mode 100644
index 0000000..1e9d0e3
--- /dev/null
+++ b/tests/fixtures/indirect-control.s
@@ -0,0 +1,39 @@
+; Indirect CALLP/JMPP control-flow example.
+;
+; Build/run from examples/:
+; ../utils/mcpu-as -o indirect-control.o indirect-control.s
+; ../utils/mcpu-ld --stack-size=512 -o indirect-control indirect-control.o
+; ../utils/mcpu-run --int-reg=r64@0x00020 indirect-control
+;
+; Expected output: 42
+;
+.data
+
+.align 8
+dispatch:
+ .cptr indirect_function
+ .cptr done
+
+.text
+_start_life:
+
+ ; Load a PROGRAM PTR64 from DATA and call through it.
+ LEA r64@0x00000, dispatch
+ LOADP r64@0x00008, r64@0x00000
+ CALLP r64@0x00008
+
+ ; Select the second entry and jump through it.
+ PADDI r64@0x00000, r64@0x00000, 8
+ LOADP r64@0x00010, r64@0x00000
+ JMPP r64@0x00010
+
+ ; JMPP must skip this path.
+ LDI r64@0x00020, 99
+ HALT
+
+indirect_function:
+ LDI r64@0x00020, 42
+ RET
+
+done:
+ HALT
diff --git a/tests/fixtures/life-print.s b/tests/fixtures/life-print.s
new file mode 100644
index 0000000..a1de41f
--- /dev/null
+++ b/tests/fixtures/life-print.s
@@ -0,0 +1,30 @@
+; Minimal LIFE-stage action example.
+;
+; Assemble/link:
+; ../utils/mcpu-as -o life-print.o life-print.s
+; ../utils/mcpu-ld -o life-print --stack-size=1K life-print.o
+;
+; Run three LIFE cycles and print two registers on one line after each END:
+; ../utils/mcpu-run \
+; --steps=3 \
+; --on-step=print-int-regs:r64@0x00000,r64@0x00008 \
+; -- \
+; life-print
+;
+; Expected output:
+; 1 10
+; 2 20
+; 3 30
+
+.text
+_start_life:
+ LDI r64@0x00000, 1
+ LDI r64@0x00008, 10
+ LDI r64@0x00010, 1
+ LDI r64@0x00018, 10
+
+loop:
+ END
+ IADD r64@0x00000, r64@0x00000, r64@0x00010
+ IADD r64@0x00008, r64@0x00008, r64@0x00018
+ JMP loop
diff --git a/tests/fixtures/model-lifecycle.s b/tests/fixtures/model-lifecycle.s
new file mode 100644
index 0000000..4c190d5
--- /dev/null
+++ b/tests/fixtures/model-lifecycle.s
@@ -0,0 +1,53 @@
+; Build from examples/:
+; ../utils/mcpu-as -o model-lifecycle.o model-lifecycle.s
+; ../utils/mcpu-ld --stack-size=512 -o model-lifecycle model-lifecycle.o
+;
+; Run one INIT-only pass or one LIFE step:
+; ../utils/mcpu-run --init-only --int-reg=r64@0x00000 model-lifecycle
+; ../utils/mcpu-run --steps=1 --int-reg=r64@0x00000 model-lifecycle
+;
+; Expected outputs: 40 and 42 respectively.
+;
+; LibCPU lifecycle/model-segment example.
+; cpu-run --steps=1 --int-reg=r64@0x00000 model-lifecycle -> 42
+; cpu-run --init-only --int-reg=r64@0x00000 model-lifecycle -> 40
+
+.control
+model_step:
+ .real 64, 1.0
+
+.input
+external_delta:
+ .integer 64, 2
+
+.output
+current_state:
+ .integer 64, 0
+
+.trace
+telemetry_state:
+ .integer 64, 0
+
+.init
+_start:
+ LDI r64@0x00000, 40
+ END
+
+.text
+_life:
+loop:
+ LEA r64@0x00008, external_delta
+ LOADP r64@0x00010, r64@0x00008
+ IADD r64@0x00000, r64@0x00000, r64@0x00010
+
+ LEA r64@0x00018, current_state
+ STOREP r64@0x00018, r64@0x00000
+ LEA r64@0x00020, telemetry_state
+ STOREP r64@0x00020, r64@0x00000
+
+ END
+ JMP loop
+
+.fini
+_fini:
+ END
diff --git a/tests/fixtures/mpu-ops.s b/tests/fixtures/mpu-ops.s
new file mode 100644
index 0000000..49d4f97
--- /dev/null
+++ b/tests/fixtures/mpu-ops.s
@@ -0,0 +1,25 @@
+; LibMPU-backed arithmetic/conversion operation example.
+;
+; Build/run from examples/:
+; ../utils/mcpu-as -o mpu-ops.o mpu-ops.s
+; ../utils/mcpu-ld --stack-size=512 -o mpu-ops mpu-ops.o
+; ../utils/mcpu-run --int-reg=r64@0x00020 mpu-ops
+;
+; Expected output: 42
+;
+.text
+_start_life:
+
+ LDI r64@0x00000, 6
+ LDI r64@0x00008, 7
+ IMUL r128@0x00010, r64@0x00000, r64@0x00008
+ ICPY r64@0x00020, r128@0x00010
+
+ RCONST r64@0x00028, ONE
+ CRTOC c128@0x00030, r64@0x00028
+ CISEQ r64@0x00040, c128@0x00030, c128@0x00030
+ IINC r64@0x00048, r64@0x00040
+ IDEC r64@0x00050, r64@0x00048
+ ISUB r64@0x00020, r64@0x00020, r64@0x00050
+ IADD r64@0x00020, r64@0x00020, r64@0x00040
+ HALT
diff --git a/tests/fixtures/psw-call-abi.s b/tests/fixtures/psw-call-abi.s
new file mode 100644
index 0000000..3dccab3
--- /dev/null
+++ b/tests/fixtures/psw-call-abi.s
@@ -0,0 +1,36 @@
+; PSW volatility across the CALL ABI example.
+;
+; Build/run from examples/:
+; ../utils/mcpu-as -o psw-call-abi.o psw-call-abi.s
+; ../utils/mcpu-ld --stack-size=512 -o psw-call-abi psw-call-abi.o
+; ../utils/mcpu-run --int-reg=r64@0x00010 psw-call-abi
+;
+; Expected output: 42
+;
+.text
+_start_life:
+
+ ; Establish ZF=1 before the call: -1 + 1 -> 0 in signed r8.
+ LDI r8@0x00000, -1
+ LDI r8@0x00001, 1
+ IADD r8@0x00002, r8@0x00000, r8@0x00001
+
+ ; CALL itself preserves PSW, but the callee is free to clobber
+ ; PSW[15:0]. This function deliberately clears ZF with 1 + 1.
+ CALL clobber_status
+
+ ; The old ZF is not part of the calling convention. Observe the
+ ; callee-produced ZF=0 and report 42.
+ JNZ status_is_volatile
+ LDI r64@0x00010, 99
+ HALT
+
+clobber_status:
+ LDI r8@0x00003, 1
+ LDI r8@0x00004, 1
+ IADD r8@0x00005, r8@0x00003, r8@0x00004
+ RET
+
+status_is_volatile:
+ LDI r64@0x00010, 42
+ HALT
diff --git a/tests/fixtures/radd-pi.s b/tests/fixtures/radd-pi.s
new file mode 100644
index 0000000..2921d91
--- /dev/null
+++ b/tests/fixtures/radd-pi.s
@@ -0,0 +1,24 @@
+; High-precision real arithmetic example.
+;
+; Build/run from examples/:
+; ../utils/mcpu-as -o radd-pi.o radd-pi.s
+; ../utils/mcpu-ld --stack-size=512 -o radd-pi radd-pi.o
+; ../utils/mcpu-run --digits=60 --real-reg=r4096@0x00400 radd-pi
+;
+; Expected output starts with:
+; 5.14159265358979323846264338327950288419716939937510582097
+;
+.data
+
+.align 16
+result:
+ .real 4096, 0.0
+
+.text
+_start_life:
+
+ LDR r4096@0x00000, 2.0
+ RCONST r4096@0x00200, PI
+ RADD r4096@0x00400, r4096@0x00000, r4096@0x00200
+ STORE result, r4096@0x00400
+ HALT
diff --git a/tests/fixtures/shared-libs/dynamic-liba.s b/tests/fixtures/shared-libs/dynamic-liba.s
new file mode 100644
index 0000000..f35cfe2
--- /dev/null
+++ b/tests/fixtures/shared-libs/dynamic-liba.s
@@ -0,0 +1,18 @@
+; Build/run from examples/shared-libs/:
+; make
+; make run
+;
+; make run prints, in order:
+; 43
+; 43
+; 42
+; 42
+; 41
+;
+; Shared-library implementation of foo: adds 2.
+.global foo
+.text
+foo:
+ LDI r64@0x00008, 2
+ IADD r64@0x00000, r64@0x00000, r64@0x00008
+ RET
diff --git a/tests/fixtures/shared-libs/dynamic-libb.s b/tests/fixtures/shared-libs/dynamic-libb.s
new file mode 100644
index 0000000..348b358
--- /dev/null
+++ b/tests/fixtures/shared-libs/dynamic-libb.s
@@ -0,0 +1,18 @@
+; Build/run from examples/shared-libs/:
+; make
+; make run
+;
+; make run prints, in order:
+; 43
+; 43
+; 42
+; 42
+; 41
+;
+; Shared-library implementation of foo: adds 3.
+.global foo
+.text
+foo:
+ LDI r64@0x00008, 3
+ IADD r64@0x00000, r64@0x00000, r64@0x00008
+ RET
diff --git a/tests/fixtures/shared-libs/dynamic-main.s b/tests/fixtures/shared-libs/dynamic-main.s
new file mode 100644
index 0000000..f296429
--- /dev/null
+++ b/tests/fixtures/shared-libs/dynamic-main.s
@@ -0,0 +1,35 @@
+; Build/run from examples/shared-libs/:
+; make
+; make run
+;
+; make run prints, in order:
+; 43
+; 43
+; 42
+; 42
+; 41
+;
+; ABI-v2 startup-dynamic executable example.
+; main-program itself defines foo(), but cpu-ld --dynamic retains CALL foo as
+; a preemptible runtime relocation. cpu-run --ldpreload can therefore replace
+; it before _start executes.
+;
+; Without preload: foo adds 1 -> result 41.
+; With dynamic-liba.so: foo adds 2 -> result 42.
+; With dynamic-liba.so:dynamic-libb.so: rightmost strong GLOBAL wins, so
+; dynamic-libb.so foo adds 3 -> result 43.
+.global _life
+.global foo
+
+.text
+_start_life:
+ LDI r64@0x00000, 40
+ CALL foo
+ MOV r64@0x00018, r64@0x00000
+ LDI r32@0x00030, 0
+ EXIT r32@0x00030
+
+foo:
+ LDI r64@0x00008, 1
+ IADD r64@0x00000, r64@0x00000, r64@0x00008
+ RET
diff --git a/tests/fixtures/stack.s b/tests/fixtures/stack.s
new file mode 100644
index 0000000..9b45785
--- /dev/null
+++ b/tests/fixtures/stack.s
@@ -0,0 +1,32 @@
+; STACK instruction example.
+;
+; Build/run from examples/:
+; ../utils/mcpu-as -o stack.o stack.s
+; ../utils/mcpu-ld --stack-size=128 -o stack stack.o
+; ../utils/mcpu-run --int-reg=r64@0x00028 stack
+;
+; Expected output: 22
+;
+.text
+_start_life:
+ LDI r64@0x00000, 11
+ LDI r64@0x00008, 22
+
+ ; STACK grows downward. cpu-run initializes SP to stack_size.
+ PUSH r64@0x00000
+ PUSH r64@0x00008
+
+ ; Current stack top contains 22, SP+8 contains 11.
+ LDSP r64@0x00010, 8
+ STSP 0, r64@0x00000
+
+ POP r64@0x00018
+ POP r64@0x00020
+
+ ; Reserve a 16-byte local frame without automatic padding.
+ ADJSP -16
+ STSP 0, r64@0x00008
+ LDSP r64@0x00028, 0
+ ADJSP 16
+
+ HALT