loongarch64 inconsistencies between hardware and qemu due too lenient decoding of floating point comparisons

Host environment

  • Operating system: Linux Mint 22 Cinnamon
  • OS/kernel version: Linux lorenz-IdeaPad-5-15ALC05 6.8.0-38-generic #38-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 7 15:25:01 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
  • Architecture: x86
  • QEMU flavor: qemu-user-static
  • QEMU version: qemu-loongarch64 version 8.2.2 (Debian 1:8.2.2+ds-0ubuntu1.6)
  • QEMU command line:
    qemu-loongarch64-static ./test_inv_vldi

Emulated/Virtualized environment

  • Architecture: loongarch64 (default one)

Description of problem

(Related to #2971 (closed) )

Lenient instruction decoding of floating point comparisons leads to instructions that do not exist on hardware (Loongson 3A5000).

The decoding of floating point compare instruction allows for instructions with illegal fcmp modes.

The following are affected (target/loongarch/insns.decode):

vfcmp_cond_s     0000 11000101 ..... ..... ..... .....    @vvv_fcond
vfcmp_cond_d     0000 11000110 ..... ..... ..... .....    @vvv_fcond
xvfcmp_cond_s    0000 11001001 ..... ..... ..... .....    @vvv_fcond
xvfcmp_cond_d    0000 11001010 ..... ..... ..... .....    @vvv_fcond
fcmp_cond_s     0000 11000001 ..... ..... ..... 00 ...   @cff_fcond
fcmp_cond_d     0000 11000010 ..... ..... ..... 00 ...   @cff_fcond

The get_fcmp_flags function in target/loongarch/tcg/insn_trans/trans_fcmp.c.inc allows decoding all possible 4-bit condition values even though some are invalid on hardware (Loongson 3A5000):

qemu-loongarch64-static
 --- fcond = 0b0 () --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1 () --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10 (LT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11 (LT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b100 (EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b101 (EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b110 (LT | EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b111 (LT | EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1000 (UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1001 (UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1010 (LT | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1011 (LT | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1100 (EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1101 (EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1110 (LT | EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1111 (LT | EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10000 (LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10001 (LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10010 (LT | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10011 (LT | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10100 (EQ | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10101 (EQ | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10110 (LT | EQ | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10111 (LT | EQ | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11000 (UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11001 (UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11010 (LT | UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11011 (LT | UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11100 (EQ | UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11101 (EQ | UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11110 (LT | EQ | UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11111 (LT | EQ | UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True
Loongson 3A5000
 --- fcond = 0b0 () --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1 () --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10 (LT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11 (LT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b100 (EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b101 (EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b110 (LT | EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b111 (LT | EQ) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1000 (UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1001 (UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1010 (LT | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1011 (LT | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1100 (EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1101 (EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1110 (LT | EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b1111 (LT | EQ | UN) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10000 (LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10001 (LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10010 (LT | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b10011 (LT | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b10100 (EQ | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10101 (EQ | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b10110 (LT | EQ | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b10111 (LT | EQ | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b11000 (UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11001 (UN | LT | GT) --- 
vfcmp_cond_s  True
vfcmp_cond_d  True
xvfcmp_cond_s True
xvfcmp_cond_d True
fcmp_cond_s   True
fcmp_cond_d   True

 --- fcond = 0b11010 (LT | UN | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b11011 (LT | UN | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b11100 (EQ | UN | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b11101 (EQ | UN | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b11110 (LT | EQ | UN | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

 --- fcond = 0b11111 (LT | EQ | UN | LT | GT) --- 
vfcmp_cond_s  False
vfcmp_cond_d  False
xvfcmp_cond_s False
xvfcmp_cond_d False
fcmp_cond_s   False
fcmp_cond_d   False

Steps to reproduce

  1. compile the test_instr_valid test program for loongarch64 (see additional information).
  2. run the check_defined.py python script to print out information about defined instructions.
  3. All tested fcmp instructions are defined when run using qemu, but some are invalid (SIGILL) on actual hardware.

Additional information

I will post a patch for this issue to the mailing list soon.

test_instr_valid source code:

#include <signal.h>
#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <sys/mman.h>

#define PAGE_SIZE 0x4000

#define ret 0x4c000020

typedef void (*instr_fun)(void);

static __attribute__((aligned(PAGE_SIZE))) uint32_t code[PAGE_SIZE / sizeof(uint32_t)];

void handler(int signo, siginfo_t *info, void *context) {
    printf("False");
    exit(0);
}

int main(int argc, char** argv) {
    struct sigaction act = { 0 };
    act.sa_flags = SA_SIGINFO | SA_ONSTACK;
    act.sa_sigaction = &handler;
    sigaction(SIGILL, &act, NULL);
    uint32_t instr = (uint32_t)strtoull(argv[1], NULL, 0);
    code[0] = instr;
    code[1] = ret;
    mprotect(code, sizeof(code), PROT_READ | PROT_EXEC);
    instr_fun f = (instr_fun)(void*)code;
    f();
    printf("True");
    exit(0);
}

check_defined.py:

import subprocess

CMD = ["qemu-loongarch64-static", "./test_instr_valid"] # remove "qemu-loongarch64-static" when running without emulation

def is_defined(instr):
    try:
        return eval(subprocess.check_output(CMD + [hex(instr)]).decode())
    except:
        return False

bases = [
    ("vfcmp_cond_s ", 0b00001100010100000000000000000000), # vfcmp_cond_s     0000 11000101 ..... ..... ..... .....    @vvv_fcond
    ("vfcmp_cond_d ", 0b00001100011000000000000000000000), # vfcmp_cond_d     0000 11000110 ..... ..... ..... .....    @vvv_fcond
    ("xvfcmp_cond_s", 0b00001100100100000000000000000000), # xvfcmp_cond_s    0000 11001001 ..... ..... ..... .....    @vvv_fcond
    ("xvfcmp_cond_d", 0b00001100101000000000000000000000), # xvfcmp_cond_d    0000 11001010 ..... ..... ..... .....    @vvv_fcond
]

bases_2 = [
    ("fcmp_cond_s  ", 0b00001100000100000000000000000000), # fcmp_cond_s     0000 11000001 ..... ..... ..... 00 ...   @cff_fcond
    ("fcmp_cond_d  ", 0b00001100001000000000000000000000), # fcmp_cond_d     0000 11000010 ..... ..... ..... 00 ...   @cff_fcond
]

def format_fcond(val):
    x = ["LT", "EQ", "UN", "LT | GT"]
    return " | ".join(x[i] for i in range(4) if (val >> 1) & (1 << i))

fcond_shift = 15

# use r1, r2, r3 for vector instructions
reg_mask = 0b000110001000001

# use c0, r1, r2 for normal instructions
reg_mask_2 = 0b000100000100000

for fcond in range(1 << 5):
    print(f" --- fcond = {bin(fcond)} ({format_fcond(fcond)}) --- ")
    for name, mask in bases:
        print(name, is_defined(mask | reg_mask | (fcond << fcond_shift)))
    for name, mask in bases_2:
        print(name, is_defined(mask | reg_mask_2 | (fcond << fcond_shift)))
    print("")