From: Paul Kimpel on
On 7/21/2010 7:05 PM, Louis Krupp wrote:
> On 7/21/2010 5:12 PM, Ex-FE wrote:
>> Hi, Since I haven't used Fortran since 1967-68 on an IBM 1620, I
>> decided to read the Fortran77 Manual. Changed Fortran77 program
>> to:
>>
>> Notice compiler now allocates 1 array for r and d as result of
>> equivalance statement and generates appropriate code for single
>> precision r and double precision code for d using the same memory
>> offset (3,0002).
>>
>> PROGRAM Test implicit none real r(2) double precision d(1)
>> equivalence (r, d) r(1) = 4.0 Debug Programdump(all) d(1) = 5.0
>> Debug Programdump(all) r(1) = 3.1 Debug Programdump(all) d(1) =
>> 3.1 Debug Programdump(all) STOP END
>>
>>
>> ***** LOCAL AND GLOBAL VARIABLES FOR TEST ***** REAL ARRAY R
>> REL.ADDR.= (3,0002)
>>
>> From first Program Dump r(1) = 4.0 Note: Tag 0 indicating Single
>> Precision 0024 (03,0002) C 200000 00248B Desc: Present-mom,
>> ASD=012459, Length=2 0(0000) 0 000000 000004 0 000000 000000
>>
>> From second Program Dump d(1) = 5.0 Note: Tag 2 indicating Double
>> Precision 0024 (03,0002) C 200000 00248B Desc: Present-mom,
>> ASD=012459, Length=2 0(0000) 2 000000 000005 2 000000 000000
>>
>> From third Program Dump r(1) = 3.1 Note: Tag 0 indicting single
>> precision normalized 0024 (03,0002) C 200000 00248B Desc:
>> Present-mom, ASD=012459, Length=2 0(0000) 0 263199 99999A 2 000000
>> 000000
>
> So the second word still has tag 2. According to the standard, it
> sounds like r(2) should be undefined, and referencing r(2) at this
> point on this system could cause a more dramatic failure than the
> standard writers ever dreamed of. (The same would happen if nothing
> had been stored to either d or r, since as I recall both words had
> their tag initialized to 2.)
>
> Louis

On 7/21/2010 9:23 PM, Mike Hore wrote:
> On 22/07/10 11:35 AM, Louis Krupp wrote:
>
>> ...
>>> From third Program Dump r(1) = 3.1 Note: Tag 0 indicting single
>>> precision normalized 0024 (03,0002) C 200000 00248B Desc:
>>> Present-mom, ASD=012459, Length=2 0(0000) 0 263199 99999A 2
>>> 000000 000000
>>
>> So the second word still has tag 2. According to the standard, it
>> sounds like r(2) should be undefined, and referencing r(2) at this
>> point on this system could cause a more dramatic failure than the
>> standard writers ever dreamed of.
>
> My guess would be a bounds violation error -- the hardware on seeing
> the tag 2 would try to fetch the next word, which would be outside
> the array. If r had been declared bigger, then on my reading of the
> Level Epsilon manual under aFOP <fetch operand value>, the next word
> would be fetched, and its tag of zero wouldn't give an error since
> the manual just says the tag must be in {0, 2}, and the result is
> simply set to tag 2 regardless. So basically you'd get garbage in the
> lower half of your DP operand.
>
> But I don't have an MCP machine here, so I can't check for sure.
>
> Cheers, Mike.
>
> --------------------------------------------------------------- Mike
> Hore mike_horeREM(a)OVE.invalid.aapt.net.au
> ---------------------------------------------------------------



Like Mike, my guess would have been that attempting to access r(2)
[containing the second half of a DP operand] would have produced a
bounds error, but no, there's a twist to the way that MCP architectures
access memory that I didn't appreciate until now.

I amended Ex-FE's FORTRAN77 program slightly to investigate access to r(2):

PROGRAM Test
implicit none
real r(2), x -- modified to add "x"
double precision d(1)
equivalence (r, d)
r(1) = 4.0
Debug Programdump(all)
d(1) = 5.0
Debug Programdump(arrays)
r(1) = 3.1
Debug Programdump(arrays)
d(1) = 3.1d0 -- modified for DP value
Debug Programdump(arrays)
x = r(2) -- added
Debug Programdump(arrays) -- added
x = r(3) -- added
STOP
END

I also changed the parameter for all but the first call on Programdump
from "all" to "arrays", simply to cut down on the volume of debug output.

To understand what is happening to the contents of the r and d arrays,
we need to look at the code that the compiler is generating. I'll try to
explain this in some detail for those that are not familiar with the MCP
architecture and instruction set. The following was generated on an
LX100 using the MCP 10.1 F77 compiler targeting a Level Delta processor.
I believe the Level Epsilon code would the be same for this case.

00000120 r(1) = 4.0
0004:0001:5 ZERO % B0
0004:0002:0 NAMC (3,002) % 7002
0004:0002:2 INDX % A6
0004:0002:3 LT8 4 % B204
0004:0002:5 STOD % B8

The first executable statement is pretty straightforward. ZERO pushes a
literal zero value onto the stack. The triplet along the left margin is
the code address, e.g., segment 4, word 1, syllable (byte) 5. There are
six bytes to a 48-bit word. I'll refer to the word at the top of stack
as TOS, the word below that as TOS-1, etc.

NAMC (Name Call) pushes an address in the form of a Stuffed Indirect
Reference Word (SIRW) onto the stack. The SIRW is essentially a fancy
base-plus-offset address, in this case referencing the stack frame
(activation record) at lexicographic level 3, plus an offset of two
words. FORTRAN programs would generally have their global/COMMON data
allocated at lex level 2 and the locals for each subroutine at level 3.
The program mainline is compiled as a subroutine that is called by the
global initialization code.

The address referenced by the address couple (lex level 3, offset 2) is
that of a data descriptor (DD) that points to the r/d array. The memory
area for this array is outside the stack frame, and is allocated
automatically by the system on first reference.

INDX indexes the array by applying the offset [zero for r(1)] at TOS-1
to the address of the array's DD at TOS, popping both operands and
pushing an indexed DD (IDD) back onto the stack. In the process of
indexing the array, the hardware checked that the offset was within the
valid bounds of the array. The IDD is another form of address, but
instead of addressing the array as a whole, addresses a specific element
of the array.

An important feature of the architecture to mention here is that both
indexed and unindexed DDs have an element size associated with them.
This is a three-bit field in the DD word, with valid values
0=single-precision word, 1=double-precision word, 2=hex (4-bit) digit,
and 4=byte. The element size affects how the hardware translates an
index offset to a memory address.

Back to the code -- at this point we have an IDD at TOS pointing to
r(1). The LT8 instruction pushes a literal integer value of 4 onto the
stack. STOD (Store Destructive) stores the integer value at TOS in the
location specified by the address (the IDD in this case) at TOS-1, then
pops both operands from the stack (hence "destructive").

Note that the compiler blithely generated an integer literal and stored
it in a floating-point array. As Glen Hermannsfeldt pointed out on a
related thread, MCP machines treat integers as a special case of
floating point numbers, normalized to the right and with an exponent of
zero. Unlike many floating point formats, the scaling point in the MCP
format is after the low-order bit of the mantissa. The hardware
arithmetic and operators (including the arithmetic compare ops) are
equally blithe about mixed integer and floating-point operands.

As Louis Krupp pointed out, Glen is not correct about 1 and 1.0 having
the same bitwise representation. Integer 1 is represented as
000000000001 (hex), whereas 1.0 would have a normalized representation
of 261000000000 (hex), or 68719476736 x 8^-12 [in other words, 1 x 8^12
x 8^-12]. I discuss the FP format a little later on.


00000140 Debug Programdump(all)
0004:0003:0 NAMC (3,005) % 7005
0004:0003:2 EVAL % AC
0004:0003:3 DLET % B5

This is sort of a weird way to call the Programdump intrinsic, but it
has to do with the way the debugging facilities for F77 work. The NAMC
pushes an address to a tag-7 Program Control Word (PCW), which is a code
address. PCWs are used primarily as entry point addresses for
subroutines and as operands for dynamic branch instructions. EVAL
traverses a reference chain, returning the operand at the end of that
chain. If a PCW is encountered along that chain, however, an accidental
entry (or thunk) is generated by the hardware, and the value returned by
the thunk-ed procedure is used to continue the chain. In this case the
thunk-ed procedure ends up calling Programdump. The F77 debug mechanism
does it this way because debugging is enabled by a run-time option.
Based on this option, the initialization code of the program will either
put a PCW at stack location (3,5), or put a zero word. If debugging is
not enabled, the target of the EVAL will be the zero operand, no PCW is
encountered, no thunk is generated, and no dump takes place. Whatever
value is returned by the EVAL operator is simply popped by the DLET
(Delete TOS) operator, so the whole purpose of the EVAL is to optionally
generate a side-effect -- the dump. This is pretty far off the topic of
FORTRAN EQUIVALENCE, but this thread started on the subject of tagged
data access, so I wanted to point out this behavior as just one more
reason why tags are good.

002A (03,0003) 0 000000 000000
0029 (03,0002) C 800000 001A70 Desc: Present-mom, ASD=00D384, Length=2
0(0000) 0 000000 000004 0 000000 000000

Back to the subject of FORTRAN EQUIVALENCE. Above is a snippet of the
program's stack from the first Programdump. The first token is the hex
offset from the base of the stack (MCP stacks are a contiguous area of
memory and grow towards higher memory addresses). The address couple in
parentheses shows the lex level of the activation record and the offset
from the base of the activation record. Next is the stack word itself,
formatted as one hex digit for the 4-bit tag and two groups of six hex
digits for the 48 data bits. (3,3) is the location for variable x; (3,2)
is the location of the DD for array r/d. Below that DD the contents of
the array are formatted with the word offset in decimal and (hex),
followed by tags and data bits for each word. You can see that r(1) has
a value of 4. The MCP initializes all arrays to zero when they are
allocated, so r(2) has it's initial value of zero. This is just what we
would expect to see.


00000160 d(1) = 5.0
0004:0003:4 ZERO % B0
0004:0003:5 NAMC (3,002) % 7002
0004:0004:1 INDX % A6
0004:0004:2 SEDW % 95A3
0004:0004:4 LT8 5 % B205
0004:0005:0 XTND % CE
0004:0005:1 STOD % B8

The next statement stores a value of five in d(1), which overlays r(1)
and r(2). This is similar to the code for r(1)=4.0 except for the
addition of the SEDW and XTND operators. SEDW (Set Element Size to
Double Words) modifies the IDD that was generated by the INDX operator
for d(1), changing its element size field from 0 to 1. This causes
memory accesses through that descriptor to consider the data to be
double precision. Other MCP languages, particularly COBOL, get the same
effect by allocating multiple descriptors for the same area, each with a
different element size.

The XTND operator simply takes the SP integer value 5 at TOS, pushes a
second word of zero onto the stack, and sets the tags of both words to
2, indicating a double-precision value. The STOD operator is sensitive
to the element size of the IDD and the tag of the word at TOS, and thus
stores the DP result into two virtually-contiguous words of memory.

00000180 Debug Programdump(arrays)
0004:0005:2 NAMC (3,006) % 7006
0004:0005:4 EVAL % AC
0004:0005:5 DLET % B5

002A (03,0003) 0 000000 000000
0029 (03,0002) C 800000 001A70 Desc: Present-mom, ASD=00D384, Length=2
0(0000) 2 000000 000005 2 000000 000000

The second Programdump shows the DP value of five in the r/d array at
(3,2). Note again that the scale point is after the low-order bit of the
first word.


00000200 r(1) = 3.1
0004:0006:0 ZERO % B0
0004:0006:1 NAMC (3,002) % 7002
0004:0006:3 INDX % A6
0004:0006:4 LT48 26319999999A % BEFF26319999999A
0004:0008:0 STOD % B8

The next statement overwrites r(1) with the FP value 3.1. Mike Hore
mentioned that the FP format is a bit weird... well, it's at least
different. The FP word format, along with the idea of integers being a
subset of FP values, were carried over intact from the Burroughs B5500:

bit 47 (high order): not used (was the "flag" bit on B5500)
bit 46: mantissa sign (0=positive)
bit 45: exponent sign (0=positive)
bits 44-39: exponent, base 8
bits 38-0: mantissa, with scale point to the right

Therefore, that hex value of 26319999999A is simply 213030377882 x
8^-12, or approximately 3.1.

00000220 Debug Programdump(arrays)
0004:0008:1 NAMC (3,007) % 7007
0004:0008:3 EVAL % AC
0004:0008:4 DLET % B5

002A (03,0003) 0 000000 000000
0029 (03,0002) C 800000 001A70 Desc: Present-mom, ASD=00D384, Length=2
0(0000) 0 263199 99999A 2 000000 000000

The dump clearly shows just r(1) being overwritten, with r(2) [the
second word of d(1)] remaining untouched. Up to this point, the results
are the same as those reported by Ex-FE.


00000240 d(1) = 3.1d0
0004:0008:5 ZERO % B0
0004:0009:0 NAMC (3,002) % 7002
0004:0009:2 INDX % A6
0004:0009:3 SEDW % 95A3
0004:0009:5 LT48 263199999999 % BE263199999999
0004:000B:0 LT48 004CCCCCCCCD % BEFFFFFFFFFF004CCCCCCCCD
0004:000D:0 JOIN % 9542
0004:000D:2 STOD % B8

Now for something a little different. 3.1 is a SP value, so when it is
coerced to DP, the compiler just emits code to extend the SP value with
a second word of zero. By specifying the constant as 3.1d0, the compiler
generates a DP literal value. It does this by pushing two SP words onto
the stack and using the JOIN operator to convert them to a single DP
operand. Note again the use of SEDW to fiddle with the element size of
the IDD for r/d.

00000260 Debug Programdump(arrays)
0004:000D:3 NAMC (3,008) % 7008
0004:000D:5 EVAL % AC
0004:000E:0 DLET % B5

002A (03,0003) 0 000000 000000
0029 (03,0002) C 800000 001A70 Desc: Present-mom, ASD=00D384, Length=2
0(0000) 2 263199 999999 2 004CCC CCCCCD

The corresponding dump shows the two words of r/d as a DP value. In the
MCP architecture's representation of DP values, the first word has
exactly the same format as a SP value (which allows coercion from SP to
DP simply by appending a zero word). The second word of the DP value has
this format:

bits 47-39: high-order extension to the exponent
bits 38-0: low-order extension to the mantissa

Therefore, a DP value has a 15-bit exponent (base 8) and a 78-bit
mantissa, with the scale point midway between the two 39-bit halves.


00000270 x = r(2)
0004:000E:1 ONE % B1
0004:000E:2 NAMC (3,002) % 7002
0004:000E:4 NXLV % AD
0004:000E:5 NAMC (3,003) % 7003
0004:000F:1 STOD % B8

Now for the critical test. This statement fetches the second word of r
(which has a double-precision tag) and stores it in the SP variable x.
The NXLV (Index and Load Value) operator indexes the DD for r and pushes
the word at the resulting memory location. The second NAMC and the STOD
simply store that value in the location for x.

As I mentioned in the beginning, I was in Mike Hore's camp -- I would
have thought that this would produce a bounds violation fault, as the
hardware found r(2) to have a DP tag value, and consequently tried to
fetch the second word, which would have been outside the bounds of the
array. Surprise, surprise, it completed without generating a fault.

00000272 Debug Programdump(arrays)
0004:000F:2 NAMC (3,009) % 7009
0004:000F:4 EVAL % AC
0004:000F:5 DLET % B5

002A (03,0003) 0 004CCC CCCCCD Op: Dec: 329853488333
0029 (03,0002) C 800000 001A70 Desc: Present-mom, ASD=00D384, Length=2
0(0000) 2 263199 999999 2 004CCC CCCCCD

The corresponding dump shows that x now contains a copy of the value at
r(2), but with the tag 2 replaced by a tag 0. How? Why?

For the answer, it turns out that one need only RTFM, although in the
case of the Architecture Reference Manual, that is not normally a
trivial task. Mike was on the right track with the "aFOP" common action
(under Read Evaluation Operators in Section 5, Operator Set), but you
have to look at the entire text. Quote:

For most clients, aFOP generates an error interrupt if the
referenced word does not have a tag in {0,2}. The exceptions are
value-call and LOAD, for which a non-operand may be a chained
reference or a target, respectively. If the reference is an address
couple or SIRW, or the reference is an IndexedSingleDD or
IndexedCharDD and the client is LOAD, the non-operand value is
returned to the client operator.

[Here's the important part -- note that "referenced word" is singular]

If the reference is an IRW and the referenced word has a tag of 0
or the reference is an IndexedSingleDD or IndexedCharDD and the
referenced word has a tag in {0,2}, the referenced word is fetched
and the tag set to zero (if necessary) to form an operand value.
The extension is not modified.

If the reference is an IRW and the fetched word has a tag of 2, its
successor word (at the next higher memory address) is fetched. That
word must have a tag in {0, 2}; the referenced word and its
successor are joined as the first and second words of a double
precision operand (with tag 2) to form an operand value.

[This part explains why access to array d fetches two words]

If the reference is an IndexedDoubleDD, the referenced word and its
successor are fetched; each must have a tag in {0,2}. The two words
are joined as the first and second words of a double precision
operand (with tag 2) to form an operand value. A split across a
page boundary can occur if an IndexedDoubleDD is so constructed
that its index refers to the last word of a page.

End quote. In order words, if the memory reference is in the form of an
SIRW (which addresses a word in a stack), the hardware is sensitive to
the tag of the first word that is fetched. If that tag is 2, the
hardware automatically fetches the second word, which must have a tag in
{0,2}. If, however, the memory reference is in the form of an IDD, it is
the element size field of the IDD that determines whether one or two
words are fetched. The tags on the words in memory are ignored, as long
as they are in {0,2}, but the tags of the words pushed onto the stack
will be forced to 2. The element size in the descriptor at (3,2) is 0,
making it an "IndexedSingleDD", so only one word is fetched, and its tag
is forced to zero in the process of pushing it onto the stack.


00000274 x = r(3)
0004:0010:0 LT8 2 % B202
0004:0010:2 NAMC (3,002) % 7002
0004:0010:4 NXLV % AD
0004:0010:5 NAMC (3,003) % 7003
0004:0011:1 STOD % B8

Just to prove the bounds checking was in fact working, I added the
statement above to index r outside of its declared bounds. Sure enough,
the program died on the NXLV operator with an Invalid Index fault.

I found this exercise quite interesting. The thing that was new to me
was the difference between the way that memory is accessed through an
SIRW compared to an IDD. I went back to the B6700 reference manual to
see if it had always been this way, but cannot find any discussion in
that manual about how NXLV works that is specific enough to tell.

For those who would wish to join in the delights of reading the
Architecture Support Reference Manual, here you can find the Level
Epsilon edition:

http://public.support.unisys.com/c71/docs/libra780-1.0/pdf/68787530-005.pdf

and here its precursor, the Level Delta edition:

http://public.support.unisys.com/aseries/docs/ClearPath-MCP-10.1/PDF/68882141-001.pdf

--
Paul
From: glen herrmannsfeldt on
In comp.lang.fortran Paul Kimpel <paul.kimpel(a)digm.com> wrote:
(snip)

> Note that the compiler blithely generated an integer literal and stored
> it in a floating-point array. As Glen Hermannsfeldt pointed out on a
> related thread, MCP machines treat integers as a special case of
> floating point numbers, normalized to the right and with an exponent of
> zero. Unlike many floating point formats, the scaling point in the MCP
> format is after the low-order bit of the mantissa. The hardware
> arithmetic and operators (including the arithmetic compare ops) are
> equally blithe about mixed integer and floating-point operands.

> As Louis Krupp pointed out, Glen is not correct about 1 and 1.0 having
> the same bitwise representation. Integer 1 is represented as
> 000000000001 (hex), whereas 1.0 would have a normalized representation
> of 261000000000 (hex), or 68719476736 x 8^-12 [in other words, 1 x 8^12
> x 8^-12]. I discuss the FP format a little later on.

(snip)

Yes, so the question, then, is when is the normalized result given.

I believe that there are some machines that give an unnormalized
result with zero exponent in the cases where no bits are lost,
otherwise the normalized result.

From Blauuw and Brooks "Computer Architecture Concepts and Evolution"
description of the B5500, and also some of the differences in
later machines:

(for floating point addition and subtraction)
"When the exponents of the two single-precision operands
are equal, then this exponent becomes the preferred exponent
for an unnormalized result; otherwise the result is normalized."

(for floating point multiplication)
"Multiply attempts, like Add, to obtain an integer result from
integer operands. The condition applied in multiplication is
that the exponents of both operands be 0. Division does not
attempt to preserve integer values; it always normalizes.
Divide Integer and Remainder, however, attemtps to give an
integer quotient and remainder, respectively."

In compiling constants, the compiler could generate either
the normalized or unnormalized form for 1.0.

I believe that description also applies to the B6700, though
I am not positive about that.

Given that, the results of EQUIVALENCE between INTEGER and REAL
could be very interesting, as often the same value will be
valid both ways.

-- glen
From: Paul Kimpel on
On 8/1/2010 7:23 PM, glen herrmannsfeldt wrote:
> In comp.lang.fortran Paul Kimpel<paul.kimpel(a)digm.com> wrote:
> (snip)
>
>> Note that the compiler blithely generated an integer literal and stored
>> it in a floating-point array. As Glen Hermannsfeldt pointed out on a
>> related thread, MCP machines treat integers as a special case of
>> floating point numbers, normalized to the right and with an exponent of
>> zero. Unlike many floating point formats, the scaling point in the MCP
>> format is after the low-order bit of the mantissa. The hardware
>> arithmetic and operators (including the arithmetic compare ops) are
>> equally blithe about mixed integer and floating-point operands.
>
>> As Louis Krupp pointed out, Glen is not correct about 1 and 1.0 having
>> the same bitwise representation. Integer 1 is represented as
>> 000000000001 (hex), whereas 1.0 would have a normalized representation
>> of 261000000000 (hex), or 68719476736 x 8^-12 [in other words, 1 x 8^12
>> x 8^-12]. I discuss the FP format a little later on.
>
> (snip)
>
> Yes, so the question, then, is when is the normalized result given.
>
> I believe that there are some machines that give an unnormalized
> result with zero exponent in the cases where no bits are lost,
> otherwise the normalized result.
>
> From Blauuw and Brooks "Computer Architecture Concepts and Evolution"
> description of the B5500, and also some of the differences in
> later machines:
>
> (for floating point addition and subtraction)
> "When the exponents of the two single-precision operands
> are equal, then this exponent becomes the preferred exponent
> for an unnormalized result; otherwise the result is normalized."
>
> (for floating point multiplication)
> "Multiply attempts, like Add, to obtain an integer result from
> integer operands. The condition applied in multiplication is
> that the exponents of both operands be 0. Division does not
> attempt to preserve integer values; it always normalizes.
> Divide Integer and Remainder, however, attemtps to give an
> integer quotient and remainder, respectively."
>
> In compiling constants, the compiler could generate either
> the normalized or unnormalized form for 1.0.
>
> I believe that description also applies to the B6700, though
> I am not positive about that.
>
> Given that, the results of EQUIVALENCE between INTEGER and REAL
> could be very interesting, as often the same value will be
> valid both ways.
>
> -- glen

Interesting question, especially since with the MCP architecture there
are two forms of normalized result -- normalized as integer and
normalized as floating point.

To investigate this, I wrote the following FORTRAN77 program and
compiled and ran it on an LX100 under MCP 10.1. This is an
implementation of the MCP architecture emulated on Intel under Windows,
but I believe the FP operations try to be faithful to the way that the
"hard" processors work:

Program Norm
implicit none
real i1, i2, i3, i4
real r1, r2
real add1, sub1, mul1, div1
real add2, sub2, mul2, div2
real add3, sub3, mul3, div3
real add4, sub4, mul4, div4

C Case 1 -- Integer OP Integer
i1 = 4
i2 = 25
add1 = i1 + i2
sub1 = i1 - i2
mul1 = i1 * i2
div1 = i1 / i2

C Case 2 -- Integer OP Real
r1 = 25.25
add2 = i1 + r1
sub2 = i1 - r1
mul2 = i1 * r1
div2 = i1 / r1

C Case 3 -- Real OP Real
r2 = 3.75
add3 = r1 + r2
sub3 = r1 - r2
mul3 = r1 * r2
div3 = r1 / r2

C Case 4 -- Integer OP Integer producing FP result
i3 = 400000000000
i4 = 500000000000
add4 = i3 + i4
sub4 = -i3 - i4
mul4 = i3 * i4
div4 = i3 / i4

Debug Programdump
stop
end

Here is the code that is generated by the compiler. Note that there are
only one set of arithmetic operators, ADD, SUBT, MULT, DIVD, for
integer, single-precision FP and double-precision FP computations. DIVD
always produces a FP result. There are also IDIV and RDIV ops for
integer and remainder divide, and MULX which always produces a
double-precision multiplication result. VALC (Value Call) fetches the
operand at the location identified by the address couple and pushes a
copy onto the stack. CHSN (Change Sign) simply complements bit 46, the
mantissa sign bit, in the TOS word.

00000340 i1 = 4
0004:0001:5 LT8 4 % B204
0004:0002:1 NAMC (3,002) % 7002
0004:0002:3 STOD % B8
00000360 i2 = 25
0004:0002:4 LT8 25 (19) % B219
0004:0003:0 NAMC (3,003) % 7003
0004:0003:2 STOD % B8
00000400 add1 = i1 + i2
0004:0003:3 VALC (3,002) % 3002
0004:0003:5 VALC (3,003) % 3003
0004:0004:1 ADD % 80
0004:0004:2 NAMC (3,008) % 7008
0004:0004:4 STOD % B8
00000420 sub1 = i1 - i2
0004:0004:5 VALC (3,002) % 3002
0004:0005:1 VALC (3,003) % 3003
0004:0005:3 SUBT % 81
0004:0005:4 NAMC (3,009) % 7009
0004:0006:0 STOD % B8
00000440 mul1 = i1 * i2
0004:0006:1 VALC (3,002) % 3002
0004:0006:3 VALC (3,003) % 3003
0004:0006:5 MULT % 82
0004:0007:0 NAMC (3,00A) % 700A
0004:0007:2 STOD % B8
00000460 div1 = i1 / i2
0004:0007:3 VALC (3,002) % 3002
0004:0007:5 VALC (3,003) % 3003
0004:0008:1 DIVD % 83
0004:0008:2 NAMC (3,00B) % 700B
0004:0008:4 STOD % B8
00000480
00000500C Case 2 -- Integer OP Real
00000540 r1 = 25.25
0004:0008:5 LT48 25B280000000 % BE25B280000000
0004:000A:0 NAMC (3,006) % 7006
0004:000A:2 STOD % B8
00000580 add2 = i1 + r1
0004:000A:3 VALC (3,002) % 3002
0004:000A:5 VALC (3,006) % 3006
0004:000B:1 ADD % 80
0004:000B:2 NAMC (3,00C) % 700C
0004:000B:4 STOD % B8
00000600 sub2 = i1 - r1
0004:000B:5 VALC (3,002) % 3002
0004:000C:1 VALC (3,006) % 3006
0004:000C:3 SUBT % 81
0004:000C:4 NAMC (3,00D) % 700D
0004:000D:0 STOD % B8
00000620 mul2 = i1 * r1
0004:000D:1 VALC (3,002) % 3002
0004:000D:3 VALC (3,006) % 3006
0004:000D:5 MULT % 82
0004:000E:0 NAMC (3,00E) % 700E
0004:000E:2 STOD % B8
00000640 div2 = i1 / r1
0004:000E:3 VALC (3,002) % 3002
0004:000E:5 VALC (3,006) % 3006
0004:000F:1 DIVD % 83
0004:000F:2 NAMC (3,00F) % 700F
0004:000F:4 STOD % B8
00000660
00000680C Case 3 -- Real OP Real
00000720 r2 = 3.75
0004:000F:5 LT48 263C00000000 % BE263C00000000
0004:0011:0 NAMC (3,007) % 7007
0004:0011:2 STOD % B8
00000760 add3 = r1 + r2
0004:0011:3 VALC (3,006) % 3006
0004:0011:5 VALC (3,007) % 3007
0004:0012:1 ADD % 80
0004:0012:2 NAMC (3,010) % 7010
0004:0012:4 STOD % B8
00000780 sub3 = r1 - r2
0004:0012:5 VALC (3,006) % 3006
0004:0013:1 VALC (3,007) % 3007
0004:0013:3 SUBT % 81
0004:0013:4 NAMC (3,011) % 7011
0004:0014:0 STOD % B8
00000800 mul3 = r1 * r2
0004:0014:1 VALC (3,006) % 3006
0004:0014:3 VALC (3,007) % 3007
0004:0014:5 MULT % 82
0004:0015:0 NAMC (3,012) % 7012
0004:0015:2 STOD % B8
00000820 div3 = r1 / r2
0004:0015:3 VALC (3,006) % 3006
0004:0015:5 VALC (3,007) % 3007
0004:0016:1 DIVD % 83
0004:0016:2 NAMC (3,013) % 7013
0004:0016:4 STOD % B8
00000840
00000860C Case 4 -- Integer OP Integer producing FP result
00000900 i3 = 400000000000
0004:0016:5 LT48 005D21DBA000 % BE005D21DBA000
0004:0018:0 NAMC (3,004) % 7004
0004:0018:2 STOD % B8
00000920 i4 = 500000000000
0004:0018:3 LT48 00746A528800 % BEFFFF00746A528800
0004:001A:0 NAMC (3,005) % 7005
0004:001A:2 STOD % B8
00000960 add4 = i3 + i4
0004:001A:3 VALC (3,004) % 3004
0004:001A:5 VALC (3,005) % 3005
0004:001B:1 ADD % 80
0004:001B:2 NAMC (3,014) % 7014
0004:001B:4 STOD % B8
00000980 sub4 = -i3 - i4
0004:001B:5 VALC (3,004) % 3004
0004:001C:1 CHSN % 8E
0004:001C:2 VALC (3,005) % 3005
0004:001C:4 SUBT % 81
0004:001C:5 NAMC (3,015) % 7015
0004:001D:1 STOD % B8
00001000 mul4 = i3 * i4
0004:001D:2 VALC (3,004) % 3004
0004:001D:4 VALC (3,005) % 3005
0004:001E:0 MULT % 82
0004:001E:1 NAMC (3,016) % 7016
0004:001E:3 STOD % B8
00001020 div4 = i3 / i4
0004:001E:4 VALC (3,004) % 3004
0004:001F:0 VALC (3,005) % 3005
0004:001F:2 DIVD % 83
0004:001F:3 NAMC (3,017) % 7017
0004:001F:5 STOD % B8
00190000
00190100 Debug Programdump
0004:0020:0 NAMC (3,019) % 7019
0004:0020:2 EVAL % AC
0004:0020:3 DLET % B5
00190300 stop
0004:0020:4 EXIT % A3

Here is the section of the resulting Programdump that shows the values
of the variables. I have annotated this with the variable names to the
right of the hex word format.

003E (03,0017) 0 26E666 666666 DIV4 Op: Dec: .79999999999927240424
003D (03,0016) 0 06D4B4 0B1F85 MUL4 Op: Dec: 1.9999999999990583+23
003C (03,0015) 0 409A31 85C500 SUB4 Op: Dec: -9.0E+11
003B (03,0014) 0 009A31 85C500 ADD3 Op: Dec: 9.0E+11
003A (03,0013) 0 266BBB BBBBBC DIV3 Op: Dec: 6.7333333333372138441
0039 (03,0012) 0 2517AC 000000 MUL3 Op: Dec: 94.6875
0038 (03,0011) 0 25AB00 000000 SUB3 Op: Dec: 21.5
0037 (03,0010) 0 25BA00 000000 ADD3 Op: Dec: 29.0
0036 (03,000F) 0 269446 F86563 DIV2 Op: Dec: .15841584158442856278
0035 (03,000E) 0 251940 000000 MUL2 Op: Dec: 101.0
0034 (03,000D) 0 65AA80 000000 SUB2 Op: Dec: -21.25
0033 (03,000C) 0 25BA80 000000 ADD2 Op: Dec: 29.25
0032 (03,000B) 0 26947A E147AE DIV1 Op: Dec: .15999999999985448085
0031 (03,000A) 0 000000 000064 MUL1 Op: Dec: 100
0030 (03,0009) 0 400000 000015 SUB1 Op: Dec: -21
002F (03,0008) 0 000000 00001D ADD1 Op: Dec: 29
002E (03,0007) 0 263C00 000000 R2 Op: Dec: 3.75
002D (03,0006) 0 25B280 000000 R1 Op: Dec: 25.25
002C (03,0005) 0 00746A 528800 I4 Op: Dec: 500000000000
002B (03,0004) 0 005D21 DBA000 I3 Op: Dec: 400000000000
002A (03,0003) 0 000000 000019 I2 Op: Dec: 25
0029 (03,0002) 0 000000 000004 I1 Op: Dec: 4

Note that the results for Case 1 (where both operands were normalized as
integers) are all normalized as integers, except for the divide result,
as DIVD always produces a FP result. The results for Cases 2 and 3
(where at least one of the operands was normalized as floating point)
all generate results normalized as floating point.

For Case 4, the operands are normalized as integers, but they are large
enough in magnitude that the results for ADD, SUBT, and MULT will exceed
the size of the 39-bit integer mantissa. The operators automatically
generated normalized floating point results in this case.

The thing I could not figure out how to do in FORTRAN77 was generate
un-normalized FP operands, so I switched to Extended Algol and wrote
this program:

begin
integer
i1;
real
r1, r2, r3,
add1, sub1, mul1, div1,
add2, sub2, mul2, div2,
add3, sub3, mul3, div3;

% Case 1 -- Integer OP Unnormalized FP
i1:= 25;
r1:= 4"230000100000"; % should represent 4.0
add1:= i1 + r1;
sub1:= i1 - r1;
mul1:= i1 * r1;
div1:= i1 / r1;

% Case 2 -- Unnormalized FP OP Unnormalized FP
r2:= 4"208000000070"; % should represent 14.0
add2:= r1 + r2;
sub2:= r1 - r2;
mul2:= r1 * r2;
div2:= r1 / r2;

% Case 3 -- Unnorm FP OP Unnorm FP, same exp
r3:= 4"208000000030"; % should represent 6.0
add3:= r2 + r3;
sub3:= r2 - r3;
mul3:= r2 * r3;
div3:= r2 / r3;

programdump;
end.

The code generated for the arithmetic expressions looks just like that
for FORTRAN77 above. Here is what the variables in the stack look like
at the time of the Programdump call:

0027 (02,0011) 0 262555 555555 DIV3 Dec: 2.3333333333284826949
0026 (02,0010) 0 251500 000000 MUL3 Dec: 84.0
0025 (02,000F) 0 259000 000000 SUB3 Dec: 8.0
0024 (02,000E) 0 25A800 000000 ADD3 Dec: 20.0
0023 (02,000D) 0 26A492 492492 DIV2 Dec: .28571428571376600303
0022 (02,000C) 0 25F000 000000 MUL2 Dec: 56.0
0021 (02,000B) 0 659400 000000 SUB2 Dec: -10.0
0020 (02,000A) 0 25A400 000000 ADD2 Dec: 18.0
001F (02,0009) 0 266400 000000 DIV1 Dec: 6.25
001E (02,0008) 0 251900 000000 MUL1 Dec: 100.0
001D (02,0007) 0 25AA00 000000 SUB1 Dec: 21.0
001C (02,0006) 0 25BA00 000000 ADD1 Dec: 29.0
001B (02,0005) 0 208000 000030 R3 Dec: 6.0
001A (02,0004) 0 208000 000070 R2 Dec: 14.0
0019 (02,0003) 0 230000 100000 R1 Dec: 4.0
0018 (02,0002) 0 000000 000019 I1 Dec: 25

Note that all of the results are properly normalized floating-point
values, even through r1 and r2 are un-normalized operands. This is what
I would have expected to happen, but it's nice to see that it's actually
the case.

Case 3 above tests Blauuw & Brooks statement that for addition and
subtractions, operands with the same exponents can produce an
un-normalized result. That may have been true for the B5500, but as can
be seen, even though R2 and R3 are un-normalized and have the same
exponent value, results based on those operands are properly normalized
by the current architecture.

What I conclude from this exercise (for single-precision values, at
least), is that for addition, subtraction, and multiplication, if the
two operands are both normalized as integers, and the result can be
expressed as an integer, it will be. Otherwise, the result will be a
normalized floating-point value.

The issue with equivalencing INTEGER and REAL in FORTRAN on this
architecture is not with fetching data and doing computations, but with
storing the results. The computations will work regardless of how the
source operands are typed or represented bitwise. Before storing into a
location typed as integer, however, the compilers emit an integerization
operator (NTGR or NTIA) to normalize the result to integer format. If
the magnitude of the result exceeds 2^39-1, an integer overflow fault is
generated.

Note that even if both operands are typed as integers and represented
bitwise as integers, it's still possible with large enough values to
generate a floating point result that exceeds the capacity of the
normalized integer format. That situation will not be detected until the
integerization occurs just before the store, however. That also means
that the intermediate values of some computations on purely integer
values could exceed the integer capacity, but if the final result is
within the integer capacity, you will still get an integer result,
albeit possibly with some loss of precision.

--
Paul
From: Louis Krupp on
On 8/2/2010 10:11 AM, Paul Kimpel wrote:
<snip>
> Case 3 above tests Blauuw & Brooks statement that for addition and
> subtractions, operands with the same exponents can produce an
> un-normalized result. That may have been true for the B5500, but as can
> be seen, even though R2 and R3 are un-normalized and have the same
> exponent value, results based on those operands are properly normalized
> by the current architecture.
>
> What I conclude from this exercise (for single-precision values, at
> least), is that for addition, subtraction, and multiplication, if the
> two operands are both normalized as integers, and the result can be
> expressed as an integer, it will be. Otherwise, the result will be a
> normalized floating-point value.

Interesting. If someone had asked me, I would have said that the system
normalizes floating point values "when it feels like it," but it sounds
like there's a method to the madness.

FWIW, the B6500 Processor Functional Flow Charts (to the extent to which
that document reflects anything that was actually implemented)
reinforces Blauuw and Brooks and shows the ADD operator testing to see
if the exponent fields of the operands were equal and, if they were,
just adding the mantissas, checking for overflow and calling it good.

Thanks very much for investigating all of this.

Louis
From: ralf.schaa on
On Jul 15, 6:16 am, nos...(a)see.signature (Richard Maine) wrote:
> Jan Gerrit Kootstra <jan.ger...(a)kootstra.org.uk> wrote:
>
> > This not a side effect.
>
> > This is what is called an operation on a global variable, for it is
> > defined outside the function.
>
> You appear to have a very narrow definition of "side effect". I can only
> guess that you must be restricting your definition to changes in the
> values of arguments. Most definitions include changes in any variables
> that are known outside of the function. That includes changes to global
> variables; indeed, that is often cited as one of the classic examples of
> a side effect.
>
> The Fortran standard doesn't actually define "side effect" as a term.
> That's more of an informal description that we tend to use as shorthand
> in discussing it. However, if one wants to use such a term in describing
> the limitations on Fortran functions, one needs to use a definition that
> goes along with those restrictions.
>
> > It is a totally legitimate result.
>
> See my quote to the contrary from the standard (not the quote about
> arguments, but the quote about "affect or be affected by"). Note that
> although the quote does use the term "effect", it does not use "side
> effect". As long as there is an effect, it is prohibitted, regardless of
> whether you want to call it "side" or not.
>
> > Rewriting your code with the statement in the main body would lead to this:
>
> [elided]
>
> That rewrite assumes an interpretation that the standard does not
> specify. The standard goes out of its way to be sure not to specify
> that. If, for example, the two invocations of the function were done in
> parallel, you would not necessarily get that result. Allowance of such
> things as parallel invocation is part of why the standard is specified
> the way it is.
>
> --
> Richard Maine                    | Good judgment comes from experience;
> email: last name at domain . net | experience comes from bad judgment.
> domain: summertriangle           |  -- Mark Twain

I am confused after reading the thread - if there is an illegal
statement, is it that global variable n is modified in the function?
Or is it rather in the statement f(3) + f(3) ?
If the first case is illegal than how can one define or change global
variables?

uhhh
-R.