模拟jg指令(datalab的isGreater) [英] simulate jg instruction(datalab's isGreater)

查看:215
本文介绍了模拟jg指令(datalab的isGreater)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在做CSAPP的数据实验室,即isGreater函数.
这是说明

isGreater - if x > y  then return 1, else return 0
   Example: isGreater(4,5) = 0, isGreater(5,4) = 1
   Legal ops: ! ~ & ^ | + << >>
   Max ops: 24
   Rating: 3

x和y都是int类型.
所以我考虑模拟jg指令来实现它.这是我的代码

int isGreater(int x, int y)
{
    int yComplement = ~y + 1;
    int minusResult = x + yComplement;  // 0xffffffff
    int SF = (minusResult >> 31) & 0x1; // 1
    int ZF = !minusResult; // 0
    int xSign = (x >> 31) & 0x1; // 0 
    int ySign = (yComplement >> 31) & 0x1; // 1
    int OF = !(xSign ^ ySign) & (xSign ^ SF); // 0
    return !(OF ^ SF) & !ZF;
}

jg指令需要SF == OF和ZF == 0.
但是它不能通过特殊情况,即x = 0x7fffffff(INT_MAX),y = 0x80000000(INT_MIN).
我这样推断:
x + yComplement = 0xffffffff,因此SF = 1,ZF = 0,因为xSign!= ySign,所以OF设置为0.
那么,我的代码出了什么问题,我的OF设置操作有问题吗?

解决方案

您正在检测加法x + yComplement中的溢出,而不是总的减法

-INT_MIN本身溢出2的补码; INT_MIN == -INT_MIN.这是 2的补码异常 1 ./p>

对于任何负数(INT_MIN除外)减去INT_MIN,您都应该获得快速正溢出检测.产生的加法将有符号溢出.例如-10 + INT_MIN溢出.

http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt 具有用于加法和减法的输入/输出符号表.发生溢出的情况是输入符号相反,但结果符号匹配y.

      SUBTRACTION SIGN BITS  (for num1 - num2 = sum)
    num1sign num2sign sumsign
   ---------------------------
        0 0 0
        0 0 1
        0 1 0
 *OVER* 0 1 1 (subtracting a negative is the same as adding a positive)
 *OVER* 1 0 0 (subtracting a positive is the same as adding a negative)
        1 0 1
        1 1 0
        1 1 1

您可以直接将其与原始的xy一起使用,并且仅将yComplement用作获取minusResult的一部分.调整您的逻辑以匹配此真值表.

或者您可以使用int ySign = (~y) >> 31;并保留其余代码不变. (使用tmp按住~y,因此对于此操作和yComplement,您只需执行一次操作).一个人的补码逆(~)不会遭受2的补码异常的影响.


脚注1 :符号/幅度和补码有两种冗余的方式来表示0,而不是没有反值的值.

有趣的事实:如果您使用整数绝对值函数,则应考虑结果unsigned以避免出现此问题. int不能表示INT_MIN的绝对值.


效率提升:

如果使用unsigned int,则在移位后不需要& 1,因为逻辑移位不会符号扩展. (此外,它还可以避免+中的C签名溢出未定义行为:从gcc和铛输出在x86 ASM上Godbolt编译器浏览器.

I am doing CSAPP's datalab, the isGreater function.
Here's the description

isGreater - if x > y  then return 1, else return 0
   Example: isGreater(4,5) = 0, isGreater(5,4) = 1
   Legal ops: ! ~ & ^ | + << >>
   Max ops: 24
   Rating: 3

x and y are both int type.
So i consider to simulate the jg instruction to implement it.Here's my code

int isGreater(int x, int y)
{
    int yComplement = ~y + 1;
    int minusResult = x + yComplement;  // 0xffffffff
    int SF = (minusResult >> 31) & 0x1; // 1
    int ZF = !minusResult; // 0
    int xSign = (x >> 31) & 0x1; // 0 
    int ySign = (yComplement >> 31) & 0x1; // 1
    int OF = !(xSign ^ ySign) & (xSign ^ SF); // 0
    return !(OF ^ SF) & !ZF;
}

The jg instruction need SF == OF and ZF == 0.
But it can't pass a special case, that is, x = 0x7fffffff(INT_MAX), y = 0x80000000(INT_MIN).
I deduce it like this:
x + yComplement = 0xffffffff, so SF = 1, ZF = 0, since xSign != ySign, the OF is set to 0.
So, what's wrong with my code, is my OF setting operation wrong?

解决方案

You're detecting overflow in the addition x + yComplement, rather than in the overall subtraction

-INT_MIN itself overflows in 2's complement; INT_MIN == -INT_MIN. This is the 2's complement anomaly1.

You should be getting fast-positive overflow detection for any negative number (other than INT_MIN) minus INT_MIN. The resulting addition will have signed overflow. e.g. -10 + INT_MIN overflows.

http://teaching.idallen.com/dat2343/10f/notes/040_overflow.txt has a table of input/output signs for add and subtraction. The cases that overflow are where the inputs signs are opposite but the result sign matches y.

      SUBTRACTION SIGN BITS  (for num1 - num2 = sum)
    num1sign num2sign sumsign
   ---------------------------
        0 0 0
        0 0 1
        0 1 0
 *OVER* 0 1 1 (subtracting a negative is the same as adding a positive)
 *OVER* 1 0 0 (subtracting a positive is the same as adding a negative)
        1 0 1
        1 1 0
        1 1 1

You could use this directly with the original x and y, and only use yComplement as part of getting the minusResult. Adjust your logic to match this truth table.

Or you could use int ySign = (~y) >> 31; and leave the rest of your code unmodified. (Use a tmp to hold ~y so you only do the operation once, for this and yComplement). The one's complement inverse (~) does not suffer from the 2's complement anomaly.


Footnote 1: sign/magnitude and one's complement have two redundant ways to represent 0, instead of an value with no inverse.

Fun fact: if you make an integer absolute-value function, you should consider the result unsigned to avoid this problem. int can't represent the absolute value of INT_MIN.


Efficiency improvements:

If you use unsigned int, you don't need & 1 after a shift because logical shifts don't sign-extend. (And as a bonus, it would avoid C signed-overflow undefined behaviour in +: http://blog.llvm.org/2011/05/what-every-c-programmer-should-know.html).

Then (if you used uint32_t, or sizeof(unsigned) * CHAR_BIT instead of 31) you'd have a safe and portable implementation of 2's complement comparison. (signed shift semantics for negative numbers are implementation-defined in C.) I think you're using C as a sort of pseudo-code for bit operations, and aren't interested in actually writing a portable implementation, and that's fine. The way you're doing things will work on normal compilers on normal CPUs.

Or you can use & 0x80000000 to leave the high bits in place (but then you'd have to left shift your ! result).

It's just the lab's restriction, you can't use unsigned or any constant larger than 0xff(255)

Ok, so you don't have access to logical right shift. Still, you need at most one &1. It's ok to work with numbers where all you care about is the low bit, but where the rest hold garbage.

You eventually do & !ZF, which is either &0 or &1. Thus, any high garbage in OF` is wiped away.

You can also delay the >> 31 until after XORing together two numbers.


This is a fun problem that I want to optimize myself:

// untested, 13 operations
int isGreater_optimized(int x, int y)
{
    int not_y = ~y;
    
    int minus_y = not_y + 1;
    int sum = x + minus_y;

    int x_vs_y = x ^ y;       // high bit = 1 if they were opposite signs: OF is possible
    int x_vs_sum = x ^ sum;   // high bit = 1 if they were opposite signs: OF is possible

    int OF = (x_vs_y & x_vs_sum) >> 31;   // high bits hold garbage
    int SF = sum >> 31;

    int non_zero = !!sum;              // 0 or 1
    return (~(OF ^ SF)) & non_zero;      // high garbage is nuked by `& 1`
}

Note the use of ~ instead of ! to invert a value that has high garbage.

It looks like there's still some redundancy in calculating OF separately from SF, but actually the XORing of sum twice doesn't cancel out. x ^ sum is an input for &, and we XOR with sum after that.

We can delay the shifts even later, though, and I found some more optimizations by avoiding an extra inversion. This is 11 operations

// replace 31 with  sizeof(int) * CHAR_BIT  if you want.  #include <limit.h>
// or use int32_t

int isGreater_optimized2(int x, int y)
{
    int not_y = ~y;

    int minus_y = not_y + 1;
    int sum = x + minus_y;
    int SF = sum;             // value in the high bit, rest are garbage

    int x_vs_y = x ^ y;       // high bit = 1 if they were opposite signs: OF is possible
    int x_vs_sum = x ^ sum;   // high bit = 1 if they were opposite signs: OF is possible
    int OF = x_vs_y & x_vs_sum;   // low bits hold garbage

    int less = (OF ^ SF);
    int ZF   = !sum;               // 0 or 1
    int le   = (less >> 31) & ZF;  // clears high garbage
    return  !le;                   // jg == jnle
}

I wondered if any compilers might see through this manual compare and optimize it into cmp edi, esi/ setg al, but no such luck :/ I guess that's not a pattern that they look for, because code that could have been written as x > y tends to be written that way :P

But anyway, here's the x86 asm output from gcc and clang on the Godbolt compiler explorer.

这篇关于模拟jg指令(datalab的isGreater)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆