Oleg-N-Cher / OfrontPlus

Oberon family of languages to C translator for ARM, x64 and x86 architectures
Other
57 stars 11 forks source link

Adapt ASH for 64 bit #11

Closed Oleg-N-Cher closed 8 years ago

Oleg-N-Cher commented 8 years ago

In SYSTEM.h we see:

#define __ASH(x, n) ((n)>=0?__ASHL(x,n):__ASHR(x,-(n)))
#define __ASHL(x, n)    ((long)(x)<<(n))
#define __ASHR(x, n)    ((long)(x)>>(n))

As we know, type "long long" is always 64 bit, while "long" may be 32 bit e.g. under MS Visual C/MinGW. So to adapt ASHL, ASHR for 64 bit, we can use cast to "long long", but isn't it overhead?

Ofront's docu:

NAME    ARGUMENT TYPE   RESULT TYPE FUNCTION
ASH(x, n)   x, n: integer type  LONGINT arithmetic shift (x * 2n)

Result is always LONGINT, but keep in mind the fact that type LONGINT previously cannot be 8 bytes in Ofront - we just now adapt it to this size.

Take look to BlackBox:

Name    Argument type   Result type Function
ASH(x, y)   x: <= INTEGER   INTEGER arithmetic shift (x * 2^y)
    x: LONGINT  LONGINT
    y: integer type

GPCP:

4.9.5 Changes for ASH

Prior to version 1.3.16 the builtin arithmetic shift function ASH only worked correctly for the 32-bit integer type. From the current version onward two significant changes have been made. First, the function now has the following available signatures — PROCEDURE ASH(arg : LONGINT; n : INTEGER) : LONGINT; PROCEDURE ASH(arg : INTEGER; n : INTEGER) : INTEGER; The return value is the shifted version of the first argument, and has the same type. The second change is the behavior when shifts of greater than the data-word width are attempted. Previously, shift amounts were applied modulo-wordwidth, which is the usual semantic for machine instruction sets. The shift value is now range-checked, so that INTEGER (LONGINT) shifts of magnitude greater than 31 (63, respectively) will return a correctly sized zero (if arg is positive OR the shift is leftward) or negative-one (if arg is negative AND the shift is rightward).

As we know, constants have an implicit type in Ofront:

CONST a = 10; (* type of it is SHORTINT *)
CONST a = LONG(10); (* type of it is INTEGER *)
CONST a = LONG(LONG(10)); (* type of it is LONGINT *)

So if we need LONGINT result for ASH, we can call ASH(LONG(argument)) or even ASH(LONG(LONG(argument))). As we see, LONGINT result by default for INTEGER argument is not so good idea as I thought at first.

So I suggest this way:

#define __ASHL(x, n)    (sizeof(x)==sizeof(long long)? (long long)(x)<<(n): (int)(x)<<(n))
#define __ASHR(x, n)    (sizeof(x)==sizeof(long long)? (long long)(x)>>(n): (int)(x)>>(n))

Thus, the advantage of my proposal is no overhead (while cast INTEGER argument to LONGINT and result from LONGINT to INTEGER back), and also compatibility with BlackBox and GPCP. Because the result of multiplication of two INTEGERs can always be LONGINT too, and so on. And it is normal practice in the care of very large numbers where they are excessive.

Oleg-N-Cher commented 8 years ago

The arithmetic shift is implemented with the same logic as in GPCP

ASH is arranged in different ways in different implementations of Oberon. For example, in BlackBox ASH (x, n) - n can be any integer including LONGINT. I see it as excessive. What a long type to do for n? In GPCP n is an INTEGER type, and that is correct - make a person more cautious when the long variable or expression used n, think about SHORTH( ), etc. While the SHORT( ) there may has a side effect (for n like SHORT(100000001H) => 1 ) in runtime gives a small number for the shift ( 1 ), and that's the wrong result. Not 0, as after the shift to ultra-large number, but it is shifted to a low value ( 1 ). I agree, this case looks rare, but it still can happen, and it's a dangerous.

This is done not only to focus attention on the fact that n can be LONGINT, and we have not noticed, but also for efficiency - do not use too long number where it is not really required.

So I find GPCP's implementation of ASH the most perfect.

P.S. As it turned out, shift operations << and >> in С language, as signed (arithmetic shift), as unsigned (logical shitf), have a specific implementation: the result is not defined, if ABS(n) >= the number of bits to be shifted. GPCP returns 0 in this case. It looks more clean and perfect, but less effective for existing processor architectures (like i80x86).

Oleg-N-Cher commented 8 years ago

https://github.com/Oleg-N-Cher/OfrontPlus/commit/8dd600c58ace88e8d902991cd930114f01eb0c3dhttps://github.com/Oleg-N-Cher/OfrontPlus/commit/f31bed70490b91fb24dad9bf1b1dcafd34916043https://github.com/Oleg-N-Cher/OfrontPlus/commit/6a2dc15a9fbee8b9fc4e9563a565dc259c86ae57https://github.com/Oleg-N-Cher/OfrontPlus/commit/4f34598a82533b439179c3fafd36e5348b66e940https://github.com/Oleg-N-Cher/OfrontPlus/commit/fbaa2ea58a17df8064562b94ccda32c62fb5fa83https://github.com/Oleg-N-Cher/OfrontPlus/commit/891fb51b27adab9211dd08db832aa14183fb9756https://github.com/Oleg-N-Cher/OfrontPlus/commit/ae2687ff15ecdf43b9fad64e6bf1a91de5cd7183https://github.com/Oleg-N-Cher/OfrontPlus/commit/6d60c073f6d3a01e41bf42a0ef51d121d38fe8c9https://github.com/Oleg-N-Cher/OfrontPlus/commit/76385c8beae937a185e91377c11bc236120bc5fehttps://github.com/Oleg-N-Cher/OfrontPlus/commit/fb7f9ff916c75fdfaec008ca954559e1cd774ca3https://github.com/Oleg-N-Cher/OfrontPlus/commit/c6cb9f41145804a6571cc4541408111fc6081b11https://github.com/Oleg-N-Cher/OfrontPlus/commit/be85d867025d042637ee07fce8133088b9013667