From Newsgroup: comp.arch
EricP <
ThatWouldBeTelling@thevillage.com> writes:
anton@mips.complang.tuwien.ac.at (Anton Ertl) writes:
https://www.complang.tuwien.ac.at/anton/tmp/carry.pdf
(unpublished).
Looking at example 3 I think the overflow check can be simplified.
Scheme_Object *ADD_tagged(
Scheme_Object *tagged_a,
Scheme_Object *tagged_b)
{
intptr_t a = ((intptr_t)tagged_a)>>1;
intptr_t b = ((intptr_t)tagged_b)>>1;
intptr_t r;
Scheme_Object *o;
r = (uintptr_t)a + (uintptr_t)b;
o = (Scheme_Object *) ((((uintptr_t)r)<<1)|1);
r = ((intptr_t )o) >> 1;
if (b == (uintptr_t)r - (uintptr_t)a)
return o;
else
return ADD_slow (a , b) ;
}
This code in the upper part of Figure 3 is not from me, but from the
Racket 8.6 source code, and footnote 5 explains the following:
|The shown code is derived from the original ADD function by putting
|the untagging at the callee rather than the caller side, expanding the |macros, and simplifying the resulting code. The C and assembler code
|for the lower part can also be found at
|
https://godbolt.org/z/TroqhsrrM
So one can imagine why the Racket 8.6 implementors have not made the optimization that you suggest, nor the optimization that I suggest.
Also, for Racket 8.6 this was only part of the legacy BC (bytecode) implementation, with their native-code compiler being where the action
was, but I guess that this missed optimization existed before the
native-code compiler was added (and who knows how this case is handled
there; I did not find it).
tagged_a and tagged_b are converted from 63-bit signed integers
to 64-bit signed by the arithmetic right shift,
which also gets rid of the tag lsb.
The add gives a 64-bit signed result, and you want to check if
it can be sign-contracted back to 63 bits,
which is a test if bits r[63] == r[62], which is an XOR.
intptr_t a = ((intptr_t)tagged_a)>>1;
intptr_t b = ((intptr_t)tagged_b)>>1;
intptr_t r, tmp;
r = a + b;
tmp = r << 1;
if ((r ^ tmp) >= 0) // check if bit r[63] == r[62]
return tmp | 1;
else
Add_slow (a, b);
Yes, I thought about this optimization, too, but also came up with:
intptr_t a1=(intptr_t) tagged_a;
intptr_t b1=(intptr_t) tagged_b;
intptr_t r ;
if (!__builtin_add_overflow(a1,(b1-1),&r))
return (Scheme_Object *)r;
intptr_t a = ((intptr_t)tagged_a)>>1;
intptr_t b = ((intptr_t)tagged_b)>>1;
return ADD_slow (a , b );
which you see in the lower part of Figure 3.
On AMD64 this compiles to:
leaq -1(%rsi), %rax
addq %rdi, %rax
jo .L8
ret
.L8:
sarq %rsi
sarq %rdi
jmp ADD_slow
I.e., just 4 instructions in the common case (reducible to 3 by
choosing 0 as the tag for small integers).
It's interesting to think about what it would take to enable an
optimizer to optimize the former, or the original Racket code, into
the assembly code above, or what would come out of your code. I guess
that if the BC run-time of Racket were part of the next SPEC CPU
suite, and a Scheme program with lots of small additions in the
reference input, we would see:-).
- anton
--
'Anyone trying for "industrial quality" ISA should avoid undefined behavior.'
Mitch Alsup, <
c17fcd89-f024-40e7-a594-88a85ac10d20o@googlegroups.com>
--- Synchronet 3.22a-Linux NewsLink 1.2