TOP

Brief Announcement: SIAL-RV64IM Converter

2026 May 10

Just wanted to write a quick update that I have implemented the necessary changes to the SIAL-Alpha.b source code to make it work with my homebrew RV64I assembler!

Rough VM Description

To save you the time of going through the documentation, this BCPL distribution works very differently from your typical C-based machine.

There are a number of pseudo-registers, which are mapped to the RV64I instruction set, as follows:

Pseudo-Register Real Register Purpose Notes
A a0 Primary accumulator  
B a1 Secondary accumulator (used for dyadic operators)  
C a2 Auxiliary data (used for memory accessors, multiply/divide, etc.)  
P a3 Parameter block pointer Machine pointer, not BCPL pointer.
G a4 Global Vector pointer Machine pointer, not BCPL pointer.
S n/a Current Stack pointer Does not physically exist at run-time; internal compiler state only.
X n/a Primary floating point accumulator Not supported (yet), as I don't support floating point in my Kestrel-2/EX VM.

NOTE: The entire BCPL virtual machine state can be contained within the argument registers of the RISC-V ISA. However, I'm thinking that I might want to change the binary interface a bit. For instance, the global vector pointer is in register a4, but I think it might better reside in the gp or s0 register instead. Likewise, I might want to relocate the parameter block pointer from a3 to maybe s0 or s1. This leaves sp open for C interoperability in the future.

Stack Frame Layout

The evaluation stack grows from low-addresses upwards to higher addresses. This is opposite most C compiler interfaces, where the stack pointer typically starts at a high address and grows downwards.

A parameter block typically looks like this:

+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
| P'  | PC' | EPC | ar0 | ar1 | ar2 | ... | arN | Lv0 | Lv1 | ... | exp |
+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+
   ^                                                                 ^
   |                                                                 |
   :-------------------------------- S ------------------------------'
   |
   
   P
       
KEY:  P'   (Machine) Pointer to previous parameter block
      PC'  (Machine) Return address from subroutine.
      EPC  (Machine) Pointer to subroutine just called (aka Entry PC).
      arN  Subroutine arguments.  ar0 is left-most argument.
      LvN  Local Variables.  Lv0 is first to be allocated.
      exp  Expression currently being calculated.  Also includes new parameter blocks.

Note that the P pseudo-register always points at the start of the current subroutine's parameter block. The value at P!0 is a link back to the calling procedure's parameter block. The value at P!1 is the return address for the subroutine. The value at P!2 is typically the address of the subroutine currently being executed. This provides rapid linkage to debugging information at run-time, instead of relying on bounds checks and linear scans to determine where the PC is currently located.

Calling a BCPL Procedure

To invoke a BCPL procedure, follow these steps:

callSite001:
            ; we assume the arguments have already been placed in memory
            ; we further assume the global vector pointer is set correctly.
            
            auipc   t0,theBCPLproc-callSite001+2048 ; Point T0 at theBCPLproc
            addi    t0,t0,theBCPLproc-callSite001
            sd      t0,128(a3)                      ; Set entry PC at P!16
            sd      a3,112(a3)                      ; Set P' (P!14) to current P
            addi    a3,a3,112                       ; Point P at new parameter block
            jal     ra,theBCPLproc                  ; call the procedure.
            
            ; since T0 already points to the procedure, you can also
            ; call it with:  jalr ra,0(t0)

CLEVER TRICK: Why add 2048 in the auipc instruction? It's because the addi instruction uses signed arithmetic. If bit 11 of the addi immediate operand is set, the processor will subtract 4096 from the value in t0 because of the sign extension. So, by adding 2048 to the displacement in auipc, we conditionally add 4096 to the displacement only if bit 11 of the displacement happens to be set. However, since the auipc instruction masks off the lower 12 bits, the 2048 bias is never stored. Therefore, if bit 11 of the displacement is not set, then no 4096 correction is added. So, regardless of if bit 11 is set, the end result is the correct address.

Most assemblers hide this behind a la pseudo-instruction. However, my assembler does not support this pseudo-instruction, so I disclose the full logic explicitly here.

OBSERVATION: Returning from the subroutine is super-fast, especially compared to C. So, with careful construction of code, BCPL can potentially beat C at subroutine overheads. The drawback is that there is heavy reliance on stack for holding parameters.

Once inside the procedure, we need to complete the parameter block initialization. This involves storing the return address and the initial argument to the procedure (if any).

theBCPLproc:
            sd      ra,120(a3)                      ; P!15 stores the return address
            sd      a0,136(a3)                      ; A register holds 1st argument
            
            ; ... rest of business logic happens here ...
            
            ld      ra,120(a3)                      ; Restore return address
            ld      a3,112(a3)                      ; and previous parameter block
            jalr    x0,0(ra)                        ; Return like any other subroutine

NOTE: If you have no need for debugging information at run-time, then you can speed up the subroutine call process by simply leaving P!2 undefined.

callSite002:
            ; we assume the arguments have already been placed in memory
            ; we further assume the global vector pointer is set correctly.
            
            sd      a3,112(a3)                      ; Set P' (P!14) to current P
            addi    a3,a3,112                       ; Point P at new parameter block
            jal     ra,theBCPLproc                  ; call the procedure.

The disadvantage of this, of course, is that should you need to enter a debugger, and range-based debugging metadata isn't available, the debugger might not be able to locate which procedure you're currently working with.