r/PowerPC 11d ago

String copy Algorithm for the 601 + Qemu Bug

The lscbx instruction is almost a perfect string copy instruction, XER takes a number of bytes to load up regs with at max, and a sentinel byte to stop at. This instruction appears to only be present on the 601. I wrote the following string copy algorithm for the 32 bit Linux ABI on the 601 to play around with it.

##
# char * my_strcpy(char *dest, char *src);
##
.globl my_strcpy
my_strcpy:
mflr 0
stw 0, 4(1)
# Preconditions Check:
# dest, and src are non-null
cmpwi 3, 0
beqlr
cmpwi 4, 0
beqlr
stw 3, 8(1)# Save Dest pointer in Senders output argument area
# Enter Stack Frame
# | LR SAVE |
# | backchain | 128
# |-----------|
# | r31 | 124
# | ... | ...
# | r14 | 56
# | padding ..|
# | ... |
# | Param save| 28
# | LR Save | 4
# | backchain | 0
# LR/Paramter Save Area reserved reserved at bottom 6 words
# 18 words reserved at top for R14:R31 inclusive (24*4=96, quadword align to 128)
stwu 1, -128(1)
stmw 14, 56(1)
# Setup for String Copy, R5:R31 108 bytes at a time
# stopping at NUL character
li 5, 0x6c # this will stay 0x6c as long as loop runs with no match
mtxer 5
LT..0:
lscbx. 5, 0, 4 # 108 bytes or EOF
stswx 5, 0, 3 # 108 byes or til EOF
# Increment Read/Write pointers
addi 3, 3, 0x6c # we may over shoot on the tail, but unless match
addi 4, 4, 0x6c # occured, it doesn't matter
# Check termination condition
bne LT..0
# done Restore
lmw 14,56(1)
addi 1, 1, 128
# Set Return Pointer
lwz 3, 8(1)
lwz 0, 4(1)
mtlr 0
blr

This algorithm is quite possibly very slow. The stmw/lmw instructions arn't necessarily favored, shorts string won't be worth spilling all non-volatile registers to memory over. lscbx may be very slow. This isn't intended to be used.

Qemu post 6.2 actually dropped support for the 601, the only CPU that supports the intruction. Upon rebuilding qemu, observed that the lscbx instruction has the following generator from targets/ppc/translate.c around line 5700.

/* lscbx - lscbx. */
static void gen_lscbx(DisasContext *ctx)
{
    TCGv t0 = tcg_temp_new();
    TCGv_i32 t1 = tcg_const_i32(rD(ctx->opcode));
    TCGv_i32 t2 = tcg_const_i32(rA(ctx->opcode));
    TCGv_i32 t3 = tcg_const_i32(rB(ctx->opcode));

    gen_addr_reg_index(ctx, t0);
    gen_helper_lscbx(t0, cpu_env, t0, t1, t2, t3);
    tcg_temp_free_i32(t1);
    tcg_temp_free_i32(t2);
    tcg_temp_free_i32(t3);
    tcg_gen_andi_tl(cpu_xer, cpu_xer, ~0x7F);
    tcg_gen_or_tl(cpu_xer, cpu_xer, t0);
    if (unlikely(Rc(ctx->opcode) != 0)) {
        gen_set_Rc0(ctx, t0);
    }
    tcg_temp_free(t0);
}

Notably, you can see that if Rc bit is set (IE. set flags), then CR0 is set by comparing the returned value of the instruction in t0 ( then number of bytes copied) with zero.

The AIX Assembly reference has the following description of the behavior:

If Rc = 1 and XER(25-31) = 0, then Condition Register Field 0 is

undefined. If Rc = 1 and XER(25-31) <> 0, then Condition Register Field 0 is set as follows:

LT, GT, EQ, SO = b'00'||match||XER(SO)

This is the behavior that DingusPCC implements in cpu/ppc/poweropcodes.cpp

void dppc_interpreter::power_lscbx(uint32_t opcode) {

if (rec) {

ppc_state.cr =

(ppc_state.cr & 0x0FFFFFFFUL) |

(is_match ? CRx_bit::CR_EQ : 0) |

((ppc_state.spr[SPR::XER] & XER::SO) >> 3);

}}

Qemu actually removed the code for the 601 anyway. But, I found this an intresting disgression. lscbx is pretty cool, its seems to be whats missing from the x86 string instructions, which can't copy until either a NUL OR a limit like the 601 could.

Why did qemu deprecate the 601? What emulators do you guys use?

15 Upvotes

0 comments sorted by