Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,14 @@ add_executable(test_c_execute
target_link_libraries(test_c_execute PRIVATE dr_cpu)
add_test(NAME c_execute COMMAND test_c_execute)

add_executable(test_fpscr tests/test_fpscr.c)
target_link_libraries(test_fpscr PRIVATE dr_cpu)
add_test(NAME fpscr COMMAND test_fpscr)

add_executable(test_float_semantics tests/test_float_semantics.c)
target_link_libraries(test_float_semantics PRIVATE dr_cpu)
add_test(NAME float_semantics COMMAND test_float_semantics)

add_executable(test_dolir tests/test_dolir.c)
target_link_libraries(test_dolir PRIVATE dr_ir)
add_test(NAME dolir COMMAND test_dolir)
Expand Down
254 changes: 122 additions & 132 deletions src/backend/emitter.c

Large diffs are not rendered by default.

174 changes: 158 additions & 16 deletions src/cpu/cpu.c
Original file line number Diff line number Diff line change
Expand Up @@ -1277,57 +1277,199 @@ void ppc_fcmp(CPUState* cpu, u8 crfd, f64 a, f64 b, bool ordered) {
} else {
compare = 2u;
}
cpu->fpscr |= compare << 12;
// FPCC is the low four bits of FPRF (bits 12-15 here); bit 16 is the C
// class bit, which a compare leaves alone. Replace rather than OR, or a
// less-than followed by an equal reads back as 0xA instead of 0x2.
cpu->fpscr = (cpu->fpscr & ~(0xFu << 12)) | (compare << 12);
u32 shift = 4u * (7u - crfd);
cpu->cr = (cpu->cr & ~(0xFu << shift)) | (compare << shift);
}

enum {
FPSCR_ZX = 0x04000000u,
FPSCR_XX = 0x02000000u,
FPSCR_VXSNAN = 0x01000000u,
FPSCR_VXISI = 0x00800000u,
FPSCR_VXIDI = 0x00400000u,
FPSCR_VXZDZ = 0x00200000u,
FPSCR_VXIMZ = 0x00100000u,
FPSCR_FR = 0x00040000u,
FPSCR_FI = 0x00020000u,
FPSCR_VE = 0x00000080u,
FPSCR_ZE = 0x00000010u,
FPSCR_NI = 0x00000004u,
};

typedef struct {
f64 value;
u32 exception;
} FPBinaryResult;

typedef enum {
FP_ADD,
FP_SUB,
FP_MUL,
FP_DIV,
} FPBinaryOp;

static f64 quiet_nan(f64 value) {
return f64_value(f64_bits(value) | 0x0008000000000000ull);
}

static f32 force_single(CPUState* cpu, f64 value) {
if (cpu->fpscr & FPSCR_NI) {
u64 bits = f64_bits(value);
if ((bits & 0x7FFFFFFFFFFFFFFFull) < 0x3810000000000000ull)
return f32_value((u32)(bits >> 32) & 0x80000000u);
}

f32 result = (f32)value;
if ((cpu->fpscr & FPSCR_NI) && fpclassify(result) == FP_SUBNORMAL)
return copysignf(0.0f, result);
return result;
}

static f64 force_double(CPUState* cpu, f64 value) {
if ((cpu->fpscr & FPSCR_NI) && fpclassify(value) == FP_SUBNORMAL)
return copysign(0.0, value);
return value;
}

static void clear_fi_fr(CPUState* cpu) {
cpu->fpscr &= ~(FPSCR_FI | FPSCR_FR);
}

static void set_fi_fr(CPUState* cpu, f64 exact, f64 rounded) {
clear_fi_fr(cpu);
if (isnan(exact) || exact == rounded)
return;
set_fp_exception(cpu, FPSCR_XX);
cpu->fpscr |= FPSCR_FI;
if (fabs(rounded) > fabs(exact))
cpu->fpscr |= FPSCR_FR;
}

static FPBinaryResult binary_fp(CPUState* cpu, FPBinaryOp op, f64 a, f64 b) {
FPBinaryResult result = {0};
switch (op) {
case FP_ADD: result.value = a + b; break;
case FP_SUB: result.value = a - b; break;
case FP_MUL: result.value = a * b; break;
case FP_DIV: result.value = a / b; break;
}

if (op == FP_DIV && isinf(result.value) && b == 0.0) {
result.exception = FPSCR_ZX;
} else if (isnan(result.value)) {
if (is_snan(a) || is_snan(b))
result.exception = FPSCR_VXSNAN;
clear_fi_fr(cpu);

if (isnan(a))
result.value = quiet_nan(a);
else if (isnan(b))
result.value = quiet_nan(b);
else if (op == FP_MUL) {
result.value = f64_value(0x7FF8000000000000ull);
result.exception = FPSCR_VXIMZ;
} else if (op == FP_DIV) {
result.value = f64_value(0x7FF8000000000000ull);
result.exception = b == 0.0 ? FPSCR_VXZDZ : FPSCR_VXIDI;
} else {
result.value = f64_value(0x7FF8000000000000ull);
result.exception = FPSCR_VXISI;
}
} else if ((op == FP_ADD || op == FP_SUB) && (isinf(a) || isinf(b))) {
clear_fi_fr(cpu);
}

if (result.exception)
set_fp_exception(cpu, result.exception);
return result;
}

static bool binary_result_enabled(const CPUState* cpu, FPBinaryResult result) {
const u32 invalid = FPSCR_VXSNAN | FPSCR_VXISI | FPSCR_VXIDI |
FPSCR_VXZDZ | FPSCR_VXIMZ;
if ((result.exception & invalid) && (cpu->fpscr & FPSCR_VE))
return false;
if (result.exception == FPSCR_ZX && (cpu->fpscr & FPSCR_ZE))
return false;
return true;
}

static void write_single_result(CPUState* cpu, u8 d, f32 value) {
cpu->fpr[d] = (f64)value;
cpu->ps1[d] = (f64)value;
set_fprf(cpu, classify_f32(value));
}

static void binary_single(CPUState* cpu, u8 d, f64 a, f64 b, FPBinaryOp op) {
FPBinaryResult result = binary_fp(cpu, op, a, b);
if (!binary_result_enabled(cpu, result))
return;
f32 rounded = force_single(cpu, result.value);
if (isfinite(result.value))
set_fi_fr(cpu, result.value, (f64)rounded);
write_single_result(cpu, d, rounded);
}

static void binary_double(CPUState* cpu, u8 d, f64 a, f64 b, FPBinaryOp op) {
FPBinaryResult result = binary_fp(cpu, op, a, b);
if (!binary_result_enabled(cpu, result))
return;
cpu->fpr[d] = force_double(cpu, result.value);
if (op == FP_MUL)
clear_fi_fr(cpu);
set_fprf(cpu, classify_f64(cpu->fpr[d]));
}

void ppc_fadds(CPUState* cpu, u8 d, u8 a, u8 b) {
write_single_result(cpu, d, (f32)(cpu->fpr[a] + cpu->fpr[b]));
binary_single(cpu, d, cpu->fpr[a], cpu->fpr[b], FP_ADD);
}

void ppc_fsubs(CPUState* cpu, u8 d, u8 a, u8 b) {
write_single_result(cpu, d, (f32)(cpu->fpr[a] - cpu->fpr[b]));
binary_single(cpu, d, cpu->fpr[a], cpu->fpr[b], FP_SUB);
}

void ppc_fmuls(CPUState* cpu, u8 d, u8 a, u8 c) {
write_single_result(cpu, d, (f32)(cpu->fpr[a] * force_25_bit(cpu->fpr[c])));
cpu->fpscr &= ~0x00060000u;
binary_single(cpu, d, cpu->fpr[a], force_25_bit(cpu->fpr[c]), FP_MUL);
}

void ppc_fdivs(CPUState* cpu, u8 d, u8 a, u8 b) {
write_single_result(cpu, d, (f32)(cpu->fpr[a] / cpu->fpr[b]));
binary_single(cpu, d, cpu->fpr[a], cpu->fpr[b], FP_DIV);
}

void ppc_fadd(CPUState* cpu, u8 d, u8 a, u8 b) {
cpu->fpr[d] = cpu->fpr[a] + cpu->fpr[b];
set_fprf(cpu, classify_f64(cpu->fpr[d]));
binary_double(cpu, d, cpu->fpr[a], cpu->fpr[b], FP_ADD);
}

void ppc_fsub(CPUState* cpu, u8 d, u8 a, u8 b) {
cpu->fpr[d] = cpu->fpr[a] - cpu->fpr[b];
set_fprf(cpu, classify_f64(cpu->fpr[d]));
binary_double(cpu, d, cpu->fpr[a], cpu->fpr[b], FP_SUB);
}

void ppc_fmul(CPUState* cpu, u8 d, u8 a, u8 c) {
cpu->fpr[d] = cpu->fpr[a] * cpu->fpr[c];
cpu->fpscr &= ~0x00060000u;
set_fprf(cpu, classify_f64(cpu->fpr[d]));
binary_double(cpu, d, cpu->fpr[a], cpu->fpr[c], FP_MUL);
}

void ppc_fdiv(CPUState* cpu, u8 d, u8 a, u8 b) {
cpu->fpr[d] = cpu->fpr[a] / cpu->fpr[b];
set_fprf(cpu, classify_f64(cpu->fpr[d]));
binary_double(cpu, d, cpu->fpr[a], cpu->fpr[b], FP_DIV);
}

void ppc_frsp(CPUState* cpu, u8 d, u8 b) {
write_single_result(cpu, d, (f32)cpu->fpr[b]);
f64 value = cpu->fpr[b];
f32 rounded = force_single(cpu, value);
if (isnan(value)) {
bool snan = is_snan(value);
if (snan)
set_fp_exception(cpu, FPSCR_VXSNAN);
clear_fi_fr(cpu);
if (snan && (cpu->fpscr & FPSCR_VE))
return;
} else {
set_fi_fr(cpu, value, (f64)rounded);
}
write_single_result(cpu, d, rounded);
}

static void write_paired_result(CPUState* cpu, u8 d, f64 ps0, f64 ps1) {
Expand Down
47 changes: 46 additions & 1 deletion tests/test_c_execute.c
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#include <stdio.h>
#include <math.h>
#include <string.h>

#include "../src/cpu/cpu.h"

void func_80004020(CPUState* ctx);
void func_80004040(CPUState* ctx);
void func_80004060(CPUState* ctx);
void func_80004068(CPUState* ctx);
void func_80004070(CPUState* ctx);

static u64 bits_of(f64 value) {
u64 bits;
memcpy(&bits, &value, sizeof(bits));
return bits;
}

int main(void) {
CPUState cpu;
Expand Down Expand Up @@ -47,6 +58,40 @@ int main(void) {
cpu.pc, cpu.gpr[3], cpu.gpr[4], cpu.gpr[5], calls);
}

cpu.msr = 0x00002000u;
cpu.lr = 0x81234564u;
cpu.fpscr = 0;
cpu.fpr[1] = NAN;
cpu.fpr[2] = 1.0;
cpu.pc = 0x80004060u;
func_80004060(&cpu);
int compare_ok = (cpu.fpscr & 0x00080000u) != 0 &&
((cpu.fpscr >> 12) & 0xFu) == 1u &&
((cpu.cr >> 20) & 0xFu) == 1u;

cpu.fpscr = 0xA0000000u;
cpu.cr = 0;
cpu.fpr[1] = 1.25;
cpu.fpr[2] = 2.5;
cpu.pc = 0x80004068u;
func_80004068(&cpu);
int record_ok = cpu.fpr[3] == 3.75 && cpu.ps1[3] == 3.75 &&
((cpu.cr >> 24) & 0xFu) == 0xAu;

cpu.fpr[1] = 0x1.0000000000001p+0;
cpu.fpr[2] = -0x1.0000000000001p+0;
u64 merge_a = bits_of(cpu.fpr[1]);
u64 merge_b = bits_of(cpu.fpr[2]);
cpu.pc = 0x80004070u;
func_80004070(&cpu);
int merge_ok = bits_of(cpu.fpr[5]) == merge_a &&
bits_of(cpu.ps1[5]) == merge_b;

if (!compare_ok || !record_ok || !merge_ok) {
fprintf(stderr, "float compare=%d record=%d merge=%d fpscr=%08X cr=%08X\n",
compare_ok, record_ok, merge_ok, cpu.fpscr, cpu.cr);
}

cpu_free(&cpu);
return !(integer_ok && memory_ok);
return !(integer_ok && memory_ok && compare_ok && record_ok && merge_ok);
}
27 changes: 26 additions & 1 deletion tests/test_codegen_emit.c
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,39 @@ int main(int argc, char** argv) {
if (!emit_function(out, memory_loop, 6, BASE + 0x1040))
return 1;

PPCInst paired_compare[2];
paired_compare[0] = ppc_decode((4u << 26) | (2u << 23) | (1u << 16) |
(2u << 11) | (32u << 1), BASE + 0x1060);
paired_compare[1] = ppc_decode(0x4E800020u, BASE + 0x1064);
if (!emit_function(out, paired_compare, 2, BASE + 0x1060))
return 1;

PPCInst record_float[2];
record_float[0] = ppc_decode((59u << 26) | (3u << 21) | (1u << 16) |
(2u << 11) | (21u << 1) | 1u,
BASE + 0x1068);
record_float[1] = ppc_decode(0x4E800020u, BASE + 0x106C);
if (!emit_function(out, record_float, 2, BASE + 0x1068))
return 1;

PPCInst paired_merge[2];
paired_merge[0] = ppc_decode((4u << 26) | (5u << 21) | (1u << 16) |
(2u << 11) | (528u << 1), BASE + 0x1070);
paired_merge[1] = ppc_decode(0x4E800020u, BASE + 0x1074);
if (!emit_function(out, paired_merge, 2, BASE + 0x1070))
return 1;

FunctionList funcs = {0};
if (!function_list_add(&funcs, BASE, BASE + (u32)count * 4u) ||
!function_list_add(&funcs, BASE + 0x1000, BASE + 0x100C) ||
!function_list_add(&funcs, BASE + 0x100C, BASE + 0x1018) ||
!function_list_add(&funcs, BASE + 0x1018, BASE + 0x101C) ||
!function_list_add(&funcs, BASE + 0x1020, BASE + 0x1030) ||
!function_list_add(&funcs, BASE + 0x1030, BASE + 0x1040) ||
!function_list_add(&funcs, BASE + 0x1040, BASE + 0x1058)) {
!function_list_add(&funcs, BASE + 0x1040, BASE + 0x1058) ||
!function_list_add(&funcs, BASE + 0x1060, BASE + 0x1068) ||
!function_list_add(&funcs, BASE + 0x1068, BASE + 0x1070) ||
!function_list_add(&funcs, BASE + 0x1070, BASE + 0x1078)) {
function_list_free(&funcs);
free(insts);
if (out != stdout) fclose(out);
Expand Down
Loading
Loading