#include "opt_ddb.h"
#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: fp_complete.c,v 1.33 2025/03/16 22:34:36 thorpej Exp $");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <sys/atomic.h>
#include <sys/evcnt.h>
#include <machine/cpu.h>
#include <machine/fpu.h>
#include <machine/reg.h>
#include <machine/alpha.h>
#include <machine/alpha_instruction.h>
#include <lib/libkern/softfloat.h>
__CTASSERT(ALPHA_AESR_INV == (FP_X_INV << 1));
__CTASSERT(ALPHA_AESR_DZE == (FP_X_DZ << 1));
__CTASSERT(ALPHA_AESR_OVF == (FP_X_OFL << 1));
__CTASSERT(ALPHA_AESR_UNF == (FP_X_UFL << 1));
__CTASSERT(ALPHA_AESR_INE == (FP_X_IMP << 1));
__CTASSERT(ALPHA_AESR_IOV == (FP_X_IOV << 1));
__CTASSERT(IEEE_TRAP_ENABLE_INV == (FP_X_INV << 1));
__CTASSERT(IEEE_TRAP_ENABLE_DZE == (FP_X_DZ << 1));
__CTASSERT(IEEE_TRAP_ENABLE_OVF == (FP_X_OFL << 1));
__CTASSERT(IEEE_TRAP_ENABLE_UNF == (FP_X_UFL << 1));
__CTASSERT(IEEE_TRAP_ENABLE_INE == (FP_X_IMP << 1));
__CTASSERT((uint64_t)FP_X_IMP << (61 - 3) == FPCR_INED);
__CTASSERT((uint64_t)FP_X_UFL << (61 - 3) == FPCR_UNFD);
__CTASSERT((uint64_t)FP_X_OFL << (49 - 0) == FPCR_OVFD);
__CTASSERT((uint64_t)FP_X_DZ << (49 - 0) == FPCR_DZED);
__CTASSERT((uint64_t)FP_X_INV << (49 - 0) == FPCR_INVD);
__CTASSERT(FP_C_ALLBITS == MDLWP_FP_C);
#define TSWINSIZE 4
#define CPUREG_CLASS (0xfUL << 0x10)
#define FPUREG_CLASS (0xfUL << 0x14)
#define CHECKFUNCTIONCODE (1UL << 0x18)
#define TRAPSHADOWBOUNDARY (1UL << 0x00 | \
1UL << 0x19 | \
1UL << 0x1a | \
1UL << 0x1b | \
1UL << 0x1d | \
1UL << 0x1e | \
1UL << 0x1f | \
0xffffUL << 0x30 | \
CHECKFUNCTIONCODE)
#define MAKE_FLOATXX(width, expwidth, sign, exp, msb, rest_of_frac) \
(u_int ## width ## _t)(sign) << ((width) - 1) |\
(u_int ## width ## _t)(exp) << ((width) - 1 - (expwidth)) |\
(u_int ## width ## _t)(msb) << ((width) - 1 - (expwidth) - 1) |\
(u_int ## width ## _t)(rest_of_frac)
#define FLOAT32QNAN MAKE_FLOATXX(32, 8, 0, 0xff, 1, 0)
#define FLOAT64QNAN MAKE_FLOATXX(64, 11, 0, 0x7ff, 1, 0)
#define IS_SUBNORMAL(v) ((v)->exp == 0 && (v)->frac != 0)
#define PREFILTER_SUBNORMAL(l,v) if ((l)->l_md.md_flags & IEEE_MAP_DMZ \
&& IS_SUBNORMAL(v)) \
(v)->frac = 0; else
#define POSTFILTER_SUBNORMAL(l,v) if ((l)->l_md.md_flags & IEEE_MAP_UMZ \
&& IS_SUBNORMAL(v)) \
(v)->frac = 0; else
#define CMP_RESULT(flag) ((flag) ? 4UL << 60 : 0L)
#define CRBLIT(sw, hw, m, offs) (((sw) & ~(m)) | ((hw) >> (offs) & (m)))
static struct evcnt fpevent_use;
static struct evcnt fpevent_reuse;
static struct evcnt ts_scans;
static struct evcnt ts_insns;
static struct evcnt ts_insns_max;
static struct evcnt ts_resolved;
static struct evcnt ts_unresolved;
static struct evcnt fp_ill_opc;
static struct evcnt fp_ill_func;
static struct evcnt fp_ill_anyop;
static struct evcnt fp_vax;
struct alpha_shadow {
uint64_t uop;
uint32_t ufunc;
} alpha_shadow;
static float64 float64_unk(float64, float64);
static float64 compare_un(float64, float64);
static float64 compare_eq(float64, float64);
static float64 compare_lt(float64, float64);
static float64 compare_le(float64, float64);
static void cvt_qs_ts_st_gf_qf(uint32_t, struct lwp *);
static void cvt_gd(uint32_t, struct lwp *);
static void cvt_qt_dg_qg(uint32_t, struct lwp *);
static void cvt_tq_gq(uint32_t, struct lwp *);
static float32 (*swfp_s[])(float32, float32) = {
float32_add, float32_sub, float32_mul, float32_div,
};
static float64 (*swfp_t[])(float64, float64) = {
float64_add, float64_sub, float64_mul, float64_div,
compare_un, compare_eq, compare_lt, compare_le,
float64_unk, float64_unk, float64_unk, float64_unk
};
static void (*swfp_cvt[])(uint32_t, struct lwp *) = {
cvt_qs_ts_st_gf_qf, cvt_gd, cvt_qt_dg_qg, cvt_tq_gq
};
static void
this_cannot_happen(int what_cannot_happen, int64_t bits)
{
static int total;
alpha_instruction inst;
static uint64_t reported;
inst.bits = bits;
atomic_inc_ulong(&fp_ill_func.ev_count);
if (bits != -1)
alpha_shadow.uop |= 1UL << inst.generic_format.opcode;
if (1UL << what_cannot_happen & reported)
return;
reported |= 1UL << what_cannot_happen;
if (total >= 1000)
return;
++total;
if (bits)
printf("FP instruction %x\n", (unsigned int)bits);
printf("FP event %d/%lx/%lx\n", what_cannot_happen, reported,
alpha_shadow.uop);
printf("Please report this to port-alpha-maintainer@NetBSD.org\n");
}
static inline void
sts(unsigned int rn, s_float *v, struct lwp *l)
{
alpha_sts(rn, v);
PREFILTER_SUBNORMAL(l, v);
}
static inline void
stt(unsigned int rn, t_float *v, struct lwp *l)
{
alpha_stt(rn, v);
PREFILTER_SUBNORMAL(l, v);
}
static inline void
lds(unsigned int rn, s_float *v, struct lwp *l)
{
POSTFILTER_SUBNORMAL(l, v);
alpha_lds(rn, v);
}
static inline void
ldt(unsigned int rn, t_float *v, struct lwp *l)
{
POSTFILTER_SUBNORMAL(l, v);
alpha_ldt(rn, v);
}
static float64
compare_lt(float64 a, float64 b)
{
return CMP_RESULT(float64_lt_quiet(a, b));
}
static float64
compare_le(float64 a, float64 b)
{
return CMP_RESULT(float64_le_quiet(a, b));
}
static float64
compare_un(float64 a, float64 b)
{
if (float64_is_nan(a) | float64_is_nan(b)) {
if (float64_is_signaling_nan(a) | float64_is_signaling_nan(b))
float_set_invalid();
return CMP_RESULT(1);
}
return CMP_RESULT(0);
}
static float64
compare_eq(float64 a, float64 b)
{
return CMP_RESULT(float64_eq(a, b));
}
static void
cvt_qs_ts_st_gf_qf(uint32_t inst_bits, struct lwp *l)
{
t_float tfb, tfc;
s_float sfb, sfc;
alpha_instruction inst;
inst.bits = inst_bits;
switch(inst.float_format.function) {
case op_cvtst:
case op_cvtst_u:
sts(inst.float_detail.fb, &sfb, l);
tfc.i = float32_to_float64(sfb.i);
ldt(inst.float_detail.fc, &tfc, l);
return;
}
if(inst.float_detail.src == 2) {
stt(inst.float_detail.fb, &tfb, l);
sfc.i = float64_to_float32(tfb.i);
lds(inst.float_detail.fc, &sfc, l);
return;
}
this_cannot_happen(5, inst.generic_format.opcode);
tfc.i = FLOAT64QNAN;
ldt(inst.float_detail.fc, &tfc, l);
return;
}
static void
cvt_gd(uint32_t inst_bits, struct lwp *l)
{
t_float tfb, tfc;
alpha_instruction inst;
inst.bits = inst_bits;
stt(inst.float_detail.fb, &tfb, l);
(void) float64_to_float32(tfb.i);
l->l_md.md_flags &= ~NETBSD_FLAG_TO_FP_C(FP_X_IMP);
tfc.i = float64_add(tfb.i, (float64)0);
ldt(inst.float_detail.fc, &tfc, l);
}
static void
cvt_qt_dg_qg(uint32_t inst_bits, struct lwp *l)
{
t_float tfb, tfc;
alpha_instruction inst;
inst.bits = inst_bits;
switch(inst.float_detail.src) {
case 0:
this_cannot_happen(3, inst.bits);
case 1:
tfc.i = 0;
break;
case 2:
this_cannot_happen(4, inst.bits);
tfc.i = 0;
break;
case 3:
stt(inst.float_detail.fb, &tfb, l);
tfc.i = int64_to_float64(tfb.i);
break;
}
alpha_ldt(inst.float_detail.fc, &tfc);
}
static void
cvt_tq_gq(uint32_t inst_bits, struct lwp *l)
{
t_float tfb, tfc;
alpha_instruction inst;
inst.bits = inst_bits;
stt(inst.float_detail.fb, &tfb, l);
tfc.i = tfb.sign ? float64_to_int64(tfb.i) : float64_to_uint64(tfb.i);
alpha_ldt(inst.float_detail.fc, &tfc);
}
static uint64_t
fp_c_to_fpcr_1(uint64_t fpcr, uint64_t fp_c)
{
uint64_t disables;
fpcr &= FPCR_DYN_RM;
disables = FP_C_TO_NETBSD_FLAG(fp_c) & ~FP_C_TO_NETBSD_MASK(fp_c);
fpcr |= (disables & (FP_X_IMP | FP_X_UFL)) << (61 - 3);
fpcr |= (disables & (FP_X_OFL | FP_X_DZ | FP_X_INV)) << (49 - 0);
fpcr |= fp_c & FP_C_MIRRORED << (FPCR_MIR_START - FP_C_MIR_START);
fpcr |= (fp_c & IEEE_MAP_DMZ) << 36;
if (fp_c & FP_C_MIRRORED)
fpcr |= FPCR_SUM;
if (fp_c & IEEE_MAP_UMZ)
fpcr |= FPCR_UNDZ | FPCR_UNFD;
fpcr |= (~fp_c & IEEE_TRAP_ENABLE_DNO) << 41;
return fpcr;
}
static void
fp_c_to_fpcr(struct lwp *l)
{
alpha_write_fpcr(fp_c_to_fpcr_1(alpha_read_fpcr(), l->l_md.md_flags));
}
void
alpha_write_fp_c(struct lwp *l, uint64_t fp_c)
{
uint64_t md_flags;
fp_c &= MDLWP_FP_C;
md_flags = l->l_md.md_flags;
if ((md_flags & MDLWP_FP_C) == fp_c)
return;
l->l_md.md_flags = (md_flags & ~MDLWP_FP_C) | fp_c;
kpreempt_disable();
if (md_flags & MDLWP_FPACTIVE) {
alpha_pal_wrfen(1);
fp_c_to_fpcr(l);
alpha_pal_wrfen(0);
} else {
struct pcb *pcb = l->l_addr;
pcb->pcb_fp.fpr_cr =
fp_c_to_fpcr_1(pcb->pcb_fp.fpr_cr, l->l_md.md_flags);
}
kpreempt_enable();
}
uint64_t
alpha_read_fp_c(struct lwp *l)
{
return l->l_md.md_flags & MDLWP_FP_C;
}
static float64
float64_unk(float64 a, float64 b)
{
return 0;
}
static void
print_fp_instruction(unsigned long pc, struct lwp *l, uint32_t bits)
{
#if defined(DDB)
char buf[32];
struct alpha_print_instruction_context ctx = {
.insn.bits = bits,
.pc = pc,
.buf = buf,
.bufsize = sizeof(buf),
};
(void) alpha_print_instruction(&ctx);
printf("INSN [%s:%d] @0x%lx -> %s\n",
l->l_proc->p_comm, l->l_proc->p_pid, ctx.pc, ctx.buf);
#else
alpha_instruction insn = {
.bits = bits,
};
printf("INSN [%s:%d] @0x%lx -> opc=0x%x func=0x%x fa=%d fb=%d fc=%d\n",
l->l_proc->p_comm, l->l_proc->p_pid, (unsigned long)pc,
insn.float_format.opcode, insn.float_format.function,
insn.float_format.fa, insn.float_format.fb, insn.float_format.fc);
printf("INSN [%s:%d] @0x%lx -> trp=0x%x rnd=0x%x src=0x%x fn=0x%x\n",
l->l_proc->p_comm, l->l_proc->p_pid, (unsigned long)pc,
insn.float_detail.trp, insn.float_detail.rnd,
insn.float_detail.src, insn.float_detail.opclass);
#endif
}
static void
alpha_fp_interpret(unsigned long pc, struct lwp *l, uint32_t bits)
{
s_float sfa, sfb, sfc;
t_float tfa, tfb, tfc;
alpha_instruction inst;
if (alpha_fp_complete_debug) {
print_fp_instruction(pc, l, bits);
}
inst.bits = bits;
switch(inst.generic_format.opcode) {
default:
atomic_inc_ulong(&fp_ill_opc.ev_count);
this_cannot_happen(2, inst.bits);
return;
case op_any_float:
if (inst.float_format.function == op_cvtql_sv ||
inst.float_format.function == op_cvtql_v) {
alpha_stt(inst.float_detail.fb, &tfb);
sfc.i = (int64_t)tfb.i >= 0L ? INT_MAX : INT_MIN;
alpha_lds(inst.float_detail.fc, &sfc);
float_raise(FP_X_INV);
} else {
atomic_inc_ulong(&fp_ill_anyop.ev_count);
this_cannot_happen(3, inst.bits);
}
break;
case op_vax_float:
atomic_inc_ulong(&fp_vax.ev_count);
case op_ieee_float:
case op_fix_float:
switch(inst.float_detail.src) {
case op_src_sf:
sts(inst.float_detail.fb, &sfb, l);
if (inst.float_detail.opclass == 11)
sfc.i = float32_sqrt(sfb.i);
else if (inst.float_detail.opclass & ~3) {
this_cannot_happen(1, inst.bits);
sfc.i = FLOAT32QNAN;
} else {
sts(inst.float_detail.fa, &sfa, l);
sfc.i = (*swfp_s[inst.float_detail.opclass])(
sfa.i, sfb.i);
}
lds(inst.float_detail.fc, &sfc, l);
break;
case op_src_xd:
case op_src_tg:
if (inst.float_detail.opclass >= 12)
(*swfp_cvt[inst.float_detail.opclass - 12])(
inst.bits, l);
else {
stt(inst.float_detail.fb, &tfb, l);
if (inst.float_detail.opclass == 11)
tfc.i = float64_sqrt(tfb.i);
else {
stt(inst.float_detail.fa, &tfa, l);
tfc.i = (*swfp_t[inst.float_detail
.opclass])(tfa.i, tfb.i);
}
ldt(inst.float_detail.fc, &tfc, l);
}
break;
case op_src_qq:
float_raise(FP_X_IMP);
break;
}
}
}
int
alpha_fp_complete_at(unsigned long trigger_pc, struct lwp *l, uint64_t *ucode)
{
int needsig;
alpha_instruction inst;
uint64_t rm, fpcr, orig_fpcr;
uint64_t orig_flags, new_flags, changed_flags, md_flags;
if (__predict_false(ufetch_32((void *)trigger_pc, &inst.bits))) {
this_cannot_happen(6, -1);
return SIGSEGV;
}
kpreempt_disable();
if ((curlwp->l_md.md_flags & MDLWP_FPACTIVE) == 0) {
fpu_load();
}
alpha_pal_wrfen(1);
orig_fpcr = fpcr = alpha_read_fpcr();
rm = inst.float_detail.rnd;
if (__predict_false(rm != 3 &&
rm != __SHIFTOUT(fpcr, FPCR_DYN_RM))) {
fpcr = (fpcr & ~FPCR_DYN_RM) | __SHIFTIN(rm, FPCR_DYN_RM);
alpha_write_fpcr(fpcr);
}
orig_flags = FP_C_TO_NETBSD_FLAG(l->l_md.md_flags);
alpha_fp_interpret(trigger_pc, l, inst.bits);
md_flags = l->l_md.md_flags;
new_flags = FP_C_TO_NETBSD_FLAG(md_flags);
changed_flags = orig_flags ^ new_flags;
KASSERT((orig_flags | changed_flags) == new_flags);
alpha_write_fpcr(fp_c_to_fpcr_1(orig_fpcr, md_flags));
needsig = changed_flags & FP_C_TO_NETBSD_MASK(md_flags);
alpha_pal_wrfen(0);
kpreempt_enable();
if (__predict_false(needsig)) {
*ucode = needsig;
return SIGFPE;
}
return 0;
}
int
alpha_fp_complete(u_long a0, u_long a1, struct lwp *l, uint64_t *ucode)
{
uint64_t op_class;
alpha_instruction inst;
alpha_instruction *trigger_pc, *usertrap_pc;
alpha_instruction *pc, *win_begin, tsw[TSWINSIZE];
long insn_count = 0;
int sig;
if (alpha_fp_complete_debug) {
printf("%s: [%s:%d] a0[AESR]=0x%lx a1[regmask]=0x%lx "
"FPCR=0x%lx FP_C=0x%lx\n",
__func__, l->l_proc->p_comm, l->l_proc->p_pid,
a0, a1, alpha_read_fpcr(),
l->l_md.md_flags & (MDLWP_FP_C|MDLWP_FPACTIVE));
}
pc = (alpha_instruction *)l->l_md.md_tf->tf_regs[FRAME_PC];
trigger_pc = pc - 1;
*ucode = a0 >> 1;
if (cpu_amask & ALPHA_AMASK_PAT) {
if ((a0 & (ALPHA_AESR_SWC | ALPHA_AESR_INV)) != 0 ||
alpha_fp_sync_complete) {
sig = alpha_fp_complete_at((u_long)trigger_pc, l,
ucode);
goto resolved;
}
}
if ((a0 & (ALPHA_AESR_SWC | ALPHA_AESR_INV)) == 0)
goto unresolved;
trigger_pc = 0;
win_begin = pc;
atomic_inc_ulong(&ts_scans.ev_count);
for (--pc; a1; --pc) {
insn_count++;
if (pc < win_begin) {
win_begin = pc - TSWINSIZE + 1;
if (copyin(win_begin, tsw, sizeof tsw)) {
win_begin = pc;
if (copyin(win_begin, tsw, 4)) {
return SIGSEGV;
}
}
}
assert(win_begin <= pc && !((long)pc & 3));
inst = tsw[pc - win_begin];
op_class = 1UL << inst.generic_format.opcode;
if (op_class & FPUREG_CLASS) {
a1 &= ~(1UL << (inst.operate_generic_format.rc + 32));
trigger_pc = pc;
} else if (op_class & CPUREG_CLASS) {
a1 &= ~(1UL << inst.operate_generic_format.rc);
trigger_pc = pc;
} else if (op_class & TRAPSHADOWBOUNDARY) {
if (op_class & CHECKFUNCTIONCODE) {
if (inst.mem_format.displacement == op_trapb ||
inst.mem_format.displacement == op_excb)
break;
} else
break;
}
}
if (insn_count > atomic_load_relaxed(&ts_insns_max.ev_count)) {
atomic_store_relaxed(&ts_insns_max.ev_count, insn_count);
}
atomic_add_long(&ts_insns.ev_count, insn_count);
if (__predict_true(trigger_pc != 0 && a1 == 0)) {
atomic_inc_ulong(&ts_resolved.ev_count);
sig = alpha_fp_complete_at((u_long)trigger_pc, l, ucode);
goto resolved;
} else {
atomic_inc_ulong(&ts_unresolved.ev_count);
}
unresolved: ;
uint64_t orig_flags = FP_C_TO_NETBSD_FLAG(l->l_md.md_flags);
uint64_t new_flags = orig_flags | *ucode;
uint64_t changed_flags = orig_flags ^ new_flags;
KASSERT((orig_flags | changed_flags) == new_flags);
l->l_md.md_flags |= NETBSD_FLAG_TO_FP_C(new_flags);
kpreempt_disable();
if ((curlwp->l_md.md_flags & MDLWP_FPACTIVE) == 0) {
fpu_load();
}
alpha_pal_wrfen(1);
uint64_t orig_fpcr = alpha_read_fpcr();
alpha_write_fpcr(fp_c_to_fpcr_1(orig_fpcr, l->l_md.md_flags));
uint64_t needsig =
changed_flags & FP_C_TO_NETBSD_MASK(l->l_md.md_flags);
alpha_pal_wrfen(0);
kpreempt_enable();
if (__predict_false(needsig)) {
*ucode = needsig;
return SIGFPE;
}
return 0;
resolved:
if (sig) {
usertrap_pc = trigger_pc + 1;
l->l_md.md_tf->tf_regs[FRAME_PC] = (unsigned long)usertrap_pc;
}
return sig;
}
void
alpha_fp_init(void)
{
evcnt_attach_dynamic_nozero(&fpevent_use, EVCNT_TYPE_MISC, NULL,
"FP", "proc use");
evcnt_attach_dynamic_nozero(&fpevent_reuse, EVCNT_TYPE_MISC, NULL,
"FP", "proc re-use");
evcnt_attach_dynamic_nozero(&ts_scans, EVCNT_TYPE_MISC, NULL,
"FP", "TS scans");
evcnt_attach_dynamic_nozero(&ts_insns, EVCNT_TYPE_MISC, NULL,
"FP", "TS total insns");
evcnt_attach_dynamic_nozero(&ts_insns_max, EVCNT_TYPE_MISC, NULL,
"FP", "TS max single-scan insns");
evcnt_attach_dynamic_nozero(&ts_resolved, EVCNT_TYPE_MISC, NULL,
"FP", "TS resolved");
evcnt_attach_dynamic_nozero(&ts_unresolved, EVCNT_TYPE_MISC, NULL,
"FP", "TS unresolved");
evcnt_attach_dynamic_nozero(&fp_ill_opc, EVCNT_TYPE_MISC, NULL,
"FP", "illegal op code");
evcnt_attach_dynamic_nozero(&fp_ill_func, EVCNT_TYPE_MISC, NULL,
"FP", "illegal function code");
evcnt_attach_dynamic_nozero(&fp_ill_anyop, EVCNT_TYPE_MISC, NULL,
"FP", "illegal any_float function code");
}
void
fpu_state_load(struct lwp *l, u_int flags)
{
struct pcb * const pcb = lwp_getpcb(l);
KASSERT(l == curlwp);
#ifdef MULTIPROCESSOR
if (flags & PCU_REENABLE) {
KASSERT(flags & PCU_VALID);
l->l_md.md_flags |= MDLWP_FPACTIVE;
return;
}
#else
KASSERT((flags & PCU_REENABLE) == 0);
#endif
if ((flags & PCU_VALID) == 0) {
atomic_inc_ulong(&fpevent_use.ev_count);
} else {
atomic_inc_ulong(&fpevent_reuse.ev_count);
}
if (alpha_fp_complete_debug) {
printf("%s: [%s:%d] loading FPCR=0x%lx\n",
__func__, l->l_proc->p_comm, l->l_proc->p_pid,
pcb->pcb_fp.fpr_cr);
}
alpha_pal_wrfen(1);
restorefpstate(&pcb->pcb_fp);
alpha_pal_wrfen(0);
l->l_md.md_flags |= MDLWP_FPACTIVE;
}
void
fpu_state_save(struct lwp *l)
{
struct pcb * const pcb = lwp_getpcb(l);
alpha_pal_wrfen(1);
savefpstate(&pcb->pcb_fp);
alpha_pal_wrfen(0);
if (alpha_fp_complete_debug) {
printf("%s: [%s:%d] saved FPCR=0x%lx\n",
__func__, l->l_proc->p_comm, l->l_proc->p_pid,
pcb->pcb_fp.fpr_cr);
}
}
void
fpu_state_release(struct lwp *l)
{
l->l_md.md_flags &= ~MDLWP_FPACTIVE;
}