Skip to content

Commit 4bf541d

Browse files
committed
M68K: Fix fmove.x 96-bit extended immediate decode length and printing
1 parent 511ba59 commit 4bf541d

3 files changed

Lines changed: 332 additions & 3 deletions

File tree

arch/M68K/M68KDisassembler.c

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,93 @@ static unsigned long long read_imm_64(m68k_info *info)
225225
return value & 0xffffffffffffffff;
226226
}
227227

228+
/* Round `value >> shift` to nearest, ties to even. Requires 1 <= shift <= 64. */
229+
static uint64_t round_right_to_even(uint64_t value, unsigned int shift)
230+
{
231+
if (shift < 64) {
232+
uint64_t quotient = value >> shift;
233+
uint64_t remainder = value & ((1ULL << shift) - 1);
234+
uint64_t halfway = 1ULL << (shift - 1);
235+
if (remainder > halfway ||
236+
(remainder == halfway && (quotient & 1)))
237+
return quotient + 1;
238+
return quotient;
239+
}
240+
/* shift == 64: only values above the halfway point round up, and the
241+
* exactly-halfway value rounds to even, i.e. to 0. */
242+
if (value > 0x8000000000000000ULL)
243+
return 1;
244+
return 0;
245+
}
246+
247+
/* Convert a Motorola 96-bit extended-precision real (16-bit sign+exponent
248+
* word and 64-bit significand; the reserved 16-bit word is not part of the
249+
* encoding passed in here) to IEEE-754 binary64.
250+
* Pure integer arithmetic:Out-of-range values map to
251+
* infinity, subnormals, or signed zero; NaNs become quiet NaNs. */
252+
static double extended_to_double(uint16_t sign_exp, uint64_t significand)
253+
{
254+
const uint64_t sign = ((uint64_t)(sign_exp & 0x8000)) << 48;
255+
const unsigned int E = sign_exp & 0x7fff;
256+
257+
if (E == 0x7fff) {
258+
uint64_t fraction = significand & 0x7fffffffffffffffULL;
259+
if (fraction == 0)
260+
return BitsToDouble(sign | 0x7ff0000000000000ULL);
261+
/* quiet NaN: set the quiet bit, keep the top 52 payload bits */
262+
return BitsToDouble(sign | 0x7ff0000000000000ULL |
263+
((fraction >> 11) | 0x0008000000000000ULL));
264+
}
265+
266+
if (E == 0 || significand == 0)
267+
return BitsToDouble(sign); /* signed zero */
268+
269+
{
270+
unsigned int leading = CountLeadingZeros_64(significand);
271+
uint64_t normalized = significand << leading;
272+
int64_t e = (int64_t)E - 16383 - (int64_t)leading;
273+
274+
if (e > 1023)
275+
return BitsToDouble(sign | 0x7ff0000000000000ULL);
276+
277+
if (e >= -1022) {
278+
uint64_t rounded = round_right_to_even(normalized, 11);
279+
if (rounded == (1ULL << 53)) {
280+
rounded >>= 1;
281+
++e;
282+
if (e > 1023)
283+
return BitsToDouble(
284+
sign | 0x7ff0000000000000ULL);
285+
}
286+
return BitsToDouble(sign |
287+
((uint64_t)(e + 1023) << 52) |
288+
(rounded & 0x000fffffffffffffULL));
289+
}
290+
291+
{
292+
uint64_t shift = (uint64_t)(-e - 1011);
293+
uint64_t fraction = shift > 64 ?
294+
0 :
295+
round_right_to_even(
296+
normalized,
297+
(unsigned int)shift);
298+
/* rounding to 1ULL << 52 naturally encodes the
299+
* smallest normal binary64 value */
300+
return BitsToDouble(sign | fraction);
301+
}
302+
}
303+
}
304+
305+
/* Read a 12-byte Motorola extended-precision immediate: sign+exponent word,
306+
* reserved word (always ignored), 64-bit significand. */
307+
static double read_imm_extended(m68k_info *info)
308+
{
309+
uint16_t sign_exp = (uint16_t)read_imm_16(info);
310+
(void)read_imm_16(info); /* reserved word */
311+
uint64_t significand = read_imm_64(info);
312+
return extended_to_double(sign_exp, significand);
313+
}
314+
228315
/* 100% portable signed int generators */
229316
static int make_int_8(int value)
230317
{
@@ -3000,7 +3087,13 @@ static void d68020_cpgen(m68k_info *info)
30003087
case M68K_FPSRC_EXTENDED:
30013088
ext->op_size.type = M68K_SIZE_TYPE_FPU;
30023089
ext->op_size.fpu_size = M68K_FPU_SIZE_EXTENDED;
3003-
get_ea_mode_op(info, op0, info->ir, 12);
3090+
if (m68k_ea_is_immediate(info->ir)) {
3091+
op0->address_mode = M68K_AM_IMMEDIATE;
3092+
op0->type = M68K_OP_FP_DOUBLE;
3093+
op0->dimm = read_imm_extended(info);
3094+
} else {
3095+
get_ea_mode_op(info, op0, info->ir, 12);
3096+
}
30043097
break;
30053098

30063099
case M68K_FPSRC_PACKED:

arch/M68K/M68KInstPrinter.c

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,13 @@ static void printImmediate(SStream *O, const cs_m68k *inst,
218218
SStream_concat(O, "#<float_point_unsupported>");
219219
return;
220220
#else
221-
if (inst->op_size.fpu_size == M68K_FPU_SIZE_SINGLE)
221+
/* Dispatch on the operand storage type: single- and
222+
* double-precision immediates print their converted value,
223+
* extended-precision `.x` values arrive as binary64 in
224+
* `dimm`, while packed decimal immediates stay unsupported. */
225+
if (op->type == M68K_OP_FP_SINGLE)
222226
SStream_concat(O, "#%f", op->simm);
223-
else if (inst->op_size.fpu_size == M68K_FPU_SIZE_DOUBLE)
227+
else if (op->type == M68K_OP_FP_DOUBLE)
224228
SStream_concat(O, "#%f", op->dimm);
225229
else
226230
SStream_concat(O, "#<unsupported>");

tests/details/m68k.yaml

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3696,6 +3696,238 @@ test_cases:
36963696
reg: fp1
36973697
regs_write: [fp1]
36983698
regs_impl_write: [fp1]
3699+
# Motorola 96-bit extended-precision immediate (fmove.x):
3700+
# canonical +1.0 is 3fff 0000 8000 0000 0000 0000 (reserved word ignored).
3701+
# The fmove.x must consume all 16 bytes so the two nops follow directly.
3702+
- input:
3703+
bytes:
3704+
[0xf2, 0x3c, 0x48, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
3705+
0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x71, 0x4e, 0x71]
3706+
arch: "m68k"
3707+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3708+
address: 0x0
3709+
expected:
3710+
insns:
3711+
- asm_text: "fmove.x #1.000000, fp0"
3712+
size: 16
3713+
details:
3714+
m68k:
3715+
op_size_type: M68K_SIZE_TYPE_FPU
3716+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3717+
operands:
3718+
- type: M68K_OP_FP_DOUBLE
3719+
address_mode: M68K_AM_IMMEDIATE
3720+
dimm: 1.000000
3721+
- type: M68K_OP_REG
3722+
reg: fp0
3723+
regs_write: [fp0]
3724+
regs_impl_write: [fp0]
3725+
- asm_text: "nop"
3726+
- asm_text: "nop"
3727+
# Signed value with nonzero exponent and fraction: -2.5.
3728+
- input:
3729+
bytes:
3730+
[0xf2, 0x3c, 0x48, 0x00, 0xc0, 0x00, 0x00, 0x00, 0xa0, 0x00, 0x00,
3731+
0x00, 0x00, 0x00, 0x00, 0x00]
3732+
arch: "m68k"
3733+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3734+
address: 0x0
3735+
expected:
3736+
insns:
3737+
- asm_text: "fmove.x #-2.500000, fp0"
3738+
size: 16
3739+
details:
3740+
m68k:
3741+
op_size_type: M68K_SIZE_TYPE_FPU
3742+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3743+
operands:
3744+
- type: M68K_OP_FP_DOUBLE
3745+
address_mode: M68K_AM_IMMEDIATE
3746+
dimm: -2.500000
3747+
- type: M68K_OP_REG
3748+
reg: fp0
3749+
regs_write: [fp0]
3750+
regs_impl_write: [fp0]
3751+
# Rounding boundary: significand 0x8000000000000c00 is exactly halfway
3752+
# between two binary64 values; the truncated 53-bit significand is odd,
3753+
# so ties-to-even rounds up to 1 + 2^-51 (1.0000000000000004).
3754+
- input:
3755+
bytes:
3756+
[0xf2, 0x3c, 0x48, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
3757+
0x00, 0x00, 0x00, 0x0c, 0x00]
3758+
arch: "m68k"
3759+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3760+
address: 0x0
3761+
expected:
3762+
insns:
3763+
- asm_text: "fmove.x #1.000000, fp0"
3764+
size: 16
3765+
details:
3766+
m68k:
3767+
op_size_type: M68K_SIZE_TYPE_FPU
3768+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3769+
operands:
3770+
- type: M68K_OP_FP_DOUBLE
3771+
address_mode: M68K_AM_IMMEDIATE
3772+
dimm: 1.0000000000000004
3773+
- type: M68K_OP_REG
3774+
reg: fp0
3775+
regs_write: [fp0]
3776+
regs_impl_write: [fp0]
3777+
# The original request encoding put 0x8000 in the ignored reserved word
3778+
# (3fff 8000 0000 0000 0000 0000): the significand is all zero, so the
3779+
# value must decode to 0.0 while still consuming the full 16 bytes.
3780+
- input:
3781+
bytes:
3782+
[0xf2, 0x3c, 0x48, 0x00, 0x3f, 0xff, 0x80, 0x00, 0x00, 0x00, 0x00,
3783+
0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x71, 0x4e, 0x71]
3784+
arch: "m68k"
3785+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3786+
address: 0x0
3787+
expected:
3788+
insns:
3789+
- asm_text: "fmove.x #0.000000, fp0"
3790+
size: 16
3791+
details:
3792+
m68k:
3793+
op_size_type: M68K_SIZE_TYPE_FPU
3794+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3795+
operands:
3796+
- type: M68K_OP_FP_DOUBLE
3797+
address_mode: M68K_AM_IMMEDIATE
3798+
dimm: 0.000000
3799+
- type: M68K_OP_REG
3800+
reg: fp0
3801+
regs_write: [fp0]
3802+
regs_impl_write: [fp0]
3803+
- asm_text: "nop"
3804+
- asm_text: "nop"
3805+
# Rounding boundary with an even truncated significand: 0x8000000000000400
3806+
# is exactly halfway between binary64 neighbors, but the truncated 53-bit
3807+
# significand (0x10000000000000) is even, so ties-to-even must round DOWN
3808+
# to exactly 1.0. This distinguishes ties-to-even from round-half-up.
3809+
- input:
3810+
bytes:
3811+
[0xf2, 0x3c, 0x48, 0x00, 0x3f, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
3812+
0x00, 0x00, 0x00, 0x04, 0x00]
3813+
arch: "m68k"
3814+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3815+
address: 0x0
3816+
expected:
3817+
insns:
3818+
- asm_text: "fmove.x #1.000000, fp0"
3819+
size: 16
3820+
details:
3821+
m68k:
3822+
op_size_type: M68K_SIZE_TYPE_FPU
3823+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3824+
operands:
3825+
- type: M68K_OP_FP_DOUBLE
3826+
address_mode: M68K_AM_IMMEDIATE
3827+
dimm: 1.000000
3828+
- type: M68K_OP_REG
3829+
reg: fp0
3830+
regs_write: [fp0]
3831+
regs_impl_write: [fp0]
3832+
# Special value: E=0x7fff with a zero 63-bit fraction encodes +infinity.
3833+
- input:
3834+
bytes:
3835+
[0xf2, 0x3c, 0x48, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
3836+
0x00, 0x00, 0x00, 0x00, 0x00]
3837+
arch: "m68k"
3838+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3839+
address: 0x0
3840+
expected:
3841+
insns:
3842+
- asm_text: "fmove.x #inf, fp0"
3843+
size: 16
3844+
details:
3845+
m68k:
3846+
op_size_type: M68K_SIZE_TYPE_FPU
3847+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3848+
operands:
3849+
- type: M68K_OP_FP_DOUBLE
3850+
address_mode: M68K_AM_IMMEDIATE
3851+
dimm: inf
3852+
- type: M68K_OP_REG
3853+
reg: fp0
3854+
regs_write: [fp0]
3855+
regs_impl_write: [fp0]
3856+
# Special value: E=0x7fff with a nonzero fraction becomes a quiet NaN.
3857+
- input:
3858+
bytes:
3859+
[0xf2, 0x3c, 0x48, 0x00, 0x7f, 0xff, 0x00, 0x00, 0x80, 0x00, 0x00,
3860+
0x00, 0x00, 0x00, 0x00, 0x01]
3861+
arch: "m68k"
3862+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3863+
address: 0x0
3864+
expected:
3865+
insns:
3866+
- asm_text: "fmove.x #nan, fp0"
3867+
size: 16
3868+
details:
3869+
m68k:
3870+
op_size_type: M68K_SIZE_TYPE_FPU
3871+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3872+
operands:
3873+
- type: M68K_OP_FP_DOUBLE
3874+
address_mode: M68K_AM_IMMEDIATE
3875+
dimm: nan
3876+
- type: M68K_OP_REG
3877+
reg: fp0
3878+
regs_write: [fp0]
3879+
regs_impl_write: [fp0]
3880+
# Subnormal path: E=0x3bcd with significand 0x8000000000000000 is the
3881+
# smallest binary64 subnormal (2^-1074, bits 0x0000000000000001).
3882+
- input:
3883+
bytes:
3884+
[0xf2, 0x3c, 0x48, 0x00, 0x3b, 0xcd, 0x00, 0x00, 0x80, 0x00, 0x00,
3885+
0x00, 0x00, 0x00, 0x00, 0x00]
3886+
arch: "m68k"
3887+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3888+
address: 0x0
3889+
expected:
3890+
insns:
3891+
- asm_text: "fmove.x #0.000000, fp0"
3892+
size: 16
3893+
details:
3894+
m68k:
3895+
op_size_type: M68K_SIZE_TYPE_FPU
3896+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3897+
operands:
3898+
- type: M68K_OP_FP_DOUBLE
3899+
address_mode: M68K_AM_IMMEDIATE
3900+
dimm: 4.9406564584124654e-324
3901+
- type: M68K_OP_REG
3902+
reg: fp0
3903+
regs_write: [fp0]
3904+
regs_impl_write: [fp0]
3905+
# Extended source with a memory effective address: no inline 12-byte
3906+
# immediate is consumed; the operand stays a memory reference.
3907+
- input:
3908+
bytes: [0xf2, 0x10, 0x48, 0x00]
3909+
arch: "m68k"
3910+
options: [CS_OPT_DETAIL, CS_MODE_M68K_040]
3911+
address: 0x0
3912+
expected:
3913+
insns:
3914+
- asm_text: "fmove.x (a0), fp0"
3915+
size: 4
3916+
details:
3917+
m68k:
3918+
op_size_type: M68K_SIZE_TYPE_FPU
3919+
op_size_fpu: M68K_FPU_SIZE_EXTENDED
3920+
operands:
3921+
- type: M68K_OP_MEM
3922+
mem:
3923+
base_reg: a0
3924+
address_mode: M68K_AM_REGI_ADDR
3925+
- type: M68K_OP_REG
3926+
reg: fp0
3927+
regs_read: [a0]
3928+
regs_write: [fp0]
3929+
regs_impl_read: [a0]
3930+
regs_impl_write: [fp0]
36993931
- input:
37003932
bytes: [0xf2, 0x10, 0x50, 0x00]
37013933
arch: "m68k"

0 commit comments

Comments
 (0)