Robert
>>2. Variable argument lists
>>
>>Ada simply lacks this capability altogether. The familiar "printf" is a fine
>>example of a variable argument list:
>>
>> printf ("Hello %s. You have %d messages.\n", name, num_messages);
>.
>Indeed you can't do this directly in Ada, because it is fundamentally type
>unsafe. As most C programmers know, printf is unable to do any type checking
>and can generate most interesting results if the types get messed up. If you
>have a homogenously typed variable length list, then the aggregate notation
>in Ada gives you what you want. If you absolutely MUST have a type unsafe
>heterogenous list, you could do it in Ada by having an array of Address values
>for the objects, and then dereferncing these addresses via unchecked conversion
>in Ada 83, or the appropriate routines in System in Ada 9X. Yes the code would
>look pretty horrible, but the Ada philosophy is that if you want to do horrible
>type unsafe things, it is quite appropriate for the code to reflect this, rathe
>than making it too easy to sink to such a level.
>.
I actually wrote and used a package that allowed me to create a
sort-of heterogeneous list and pass it to an sprintf-like function.
It looked something like this:
package SPRINTF_PACKAGE is
type ITEM_TYPE is (I_NONE, I_INTEGER, I_FLOAT, I_CHARACTER, I_STRING);
type ITEM (ITYPE : ITEM_TYPE := I_NONE) is record
case ITYPE is
when I_NONE => null;
when I_INTEGER => i : integer;
when I_FLOAT => f : float;
when I_CHARACTER => c : character;
when I_STRING => s : string_pointer;
end case;
end record;
type ITEM_ARRAY is array (natural range <>) of ITEM;
function V (i : integer) return ITEM;
function V (f : float) return ITEM:
function V (c : character) return ITEM;
function V (s : string) return ITEM;
function SPRINTF (format : in string;
items : in ITEM_ARRAY) return string;
end SPRINTF_PACKAGE;
Then I could code stuff like
TEXT_IO.PUT_LINE
(SPRINTF ("Hello %s. You have %-3d messages.",
(V (name), V (num_messages))));
which is almost as convenient as the C version---you just have to add
a few parentheses and some V's. Also, this version of SPRINTF does
type checking for you (by checking the ITYPE of each item), which
neither the C functions nor Robert Dewar's 'ADDRESS suggestion can do.
The drawbacks are that it's a little less efficient than passing
addresses, since the V function has to be called. String items aren't
handled too well (in the version I wrote, the string version of V
simply allocated a copy of the string on the heap; there are better
ways to handle strings). Also, you're limited as to what types you
can pass to SPRINTF. If you declare your own integer type, an object
of that type must be converted to INTEGER before it's passed to
SPRINTF. (9X should alleviate those problems; it should also allow
you to pass objects of ANY type, by writing your own formatting
routine for that type. You could then, for example, use SPRINTF on
complex numbers, which you can't do in C.)
I try not to use this version of SPRINTF any more; now, I prefer code
like:
TEXT_IO.PUT_LINE
("Hello "
& name
& ". You have "
& FORMAT_INTEGER (num_messages, base => 10, width => 3,
justify => left)
& ".");
(When I was working in C, one of the most annoying things was that you
can't write functions that return strings, and there's no string
concatenation operator, so you can't write code like this.)
-- Adam
>>1. POINTERS
>Any reasonable compiler should take the loop:
>..
>.. for I in 0 .. 9999 loop
>.. aa.item1 := 5;
>.. aa.item2 := 6;
>.. aa.item3 := 7;
>.. end loop;
>..
>and do elementary strength reduction and test replacement to obtain
>identical code to what is generated for the C loop above. This is hardly
>something that requires a dynamite compiler (strength reduction in a loop
>of this kind is among the most elementary compiler optimizations)
>The corresponding Ada program is something like:
>..
>.. procedure A1 is
>.. type Atype is record
>.. item1 : integer;
>.. item2 : integer;
>.. item3 : integer;
>.. end record;
>..
>.. i : integer;
>..
>.. aa : array (0 .. 9999) of Atype;
>..
>.. begin
>.. for i in 0 .. 9999 loop
>.. aa (i).item1 := 5;
>.. aa (i).item2 := 6;
>.. aa (i).item3 := 7;
>.. end loop;
>.. end A1;
>..
>compiling for the 386 using GNAT, we get for the loop:
>..
>.. L7:
>.. movl $5,(%edx,%eax)
>.. movl $6,4(%eax,%edx)
>.. movl $7,8(%eax,%edx)
>.. addl $12,%eax
>.. cmpl $119988,%eax
>.. jle L7
>..
I was surprised that someone carried it to this level! GNAT sure beats DEC
Ada 3.0 for the VAX (which I consider to be a very mature "dynamite"
compiler). It yields the following with optimization:
Line 13: CLRL R2
Line 14: MULL3 S^#3,R2,AP
: MOVL S^#5,L^-120012(FP)[AP]
Line 15: MOVL S^#6,L^-120008(FP)[AP]
Line 16: MOVL S^#7,L^-120004(FP)[AP]
Line 17: INCL R2
: CMPL R2,I^#9999
: BLEQ A1.LOOP$13.%LINE 14
While it did recognize that the computed index could be reused, it was unable
to remove the multiply by 3 for each pass through the loop.
>>3. Bitwise operators
>>
>>Bitwise ANDs, ORs, shifts, etc. are trivial in most other languages. Ada
>>lacks anything standard here. I know it allows to to layout records in a
>>bitwise fashion (so does C by the way), but this just isn't the same.
>>Usually, one must rely on an "implementation defined" package or some other
>>non-portable way to do these operations. In any case, these operations
>>are inherant in a C compiler and it can optimize expressions using bitwise
>>operations.
>..
>Finally, where does this "trivial in most other languages come from". Of
>the languages that I do use or have in the past routinely used (Ada,
>Fortran, C, COBOL, Algol-68, SNOBOL4), only C has this capability (well
>I guess I didn't include assembler :-)
"Other languages" I was refering to include:
BASIC (Microsoft variants)
C
Assembly
FORTRAN (I differ with you here, but every FORTRAN compiler I have used
DOES support .and. and .or. in a bitwise fashion; shift is
usually provided as well, but it's not standardized)
>
>Robert
>
Mike
........................................................................
Michael Livorsi
F/A-18E/F Flight Controls Software
McDonnell Douglas Corporation
liv...@fcws11.mdc.com
>>compiling for the 386 using GNAT, we get for the loop:
>>..
>>.. L7:
>>.. movl $5,(%edx,%eax)
>>.. movl $6,4(%eax,%edx)
>>.. movl $7,8(%eax,%edx)
>>.. addl $12,%eax
>>.. cmpl $119988,%eax
>>.. jle L7
>>..
>
>I was surprised that someone carried it to this level! GNAT sure beats DEC
>Ada 3.0 for the VAX (which I consider to be a very mature "dynamite"
>compiler). It yields the following with optimization:
>
>Line 13: CLRL R2
>Line 14: MULL3 S^#3,R2,AP
> : MOVL S^#5,L^-120012(FP)[AP]
>Line 15: MOVL S^#6,L^-120008(FP)[AP]
>Line 16: MOVL S^#7,L^-120004(FP)[AP]
>Line 17: INCL R2
> : CMPL R2,I^#9999
> : BLEQ A1.LOOP$13.%LINE 14
>
>While it did recognize that the computed index could be reused, it was unable
>to remove the multiply by 3 for each pass through the loop.
I tried putting the following example through our DEC Ada for OpenVMS AXP
compiler, was very embarrassed by the result, spent some time this morning
fixing the glitches that were inhibiting optimisation, and now have a compiler
that does the following...
1 procedure Eg is
2 type R is
3 record
4 I,J,K : Integer;
5 end record;
6 X : array(1..10) of R;
7 begin
8 for I in X'range loop
9 X(I).I := 0;
10 X(I).J := 1;
11 X(I).K := 2;
12 end loop;
13 end Eg;
EG::
STQ R31, -16512(SP)
LDA SP, -128(SP)
LDA R1, -4(SP)
CLR I
MOV 2, R0
MOV 1, R17
L$2:
STL R31, 12(R1)
ADDL I, 5, I
STL R17, 16(R1)
ADDQ I, -10, R19
STL R0, 20(R1)
STL R31, 24(R1)
STL R17, 28(R1)
STL R0, 32(R1)
STL R31, 36(R1)
STL R17, 40(R1)
STL R0, 44(R1)
STL R31, 48(R1)
STL R17, 52(R1)
LDA R1, 60(R1)
STL R0, -4(R1)
STL R31, (R1)
STL R17, 4(R1)
STL R0, 8(R1)
BLT R19, L$2
LDA SP, 128(SP)
RET R26
Which differs from the GNAT example in that the loop has been unrolled
five times, and there is some fiddling around with 'I' that I have yet to
understand.
[My loop went from 1..10 instead of 0..9999, it makes no difference
to the example].
The VAX Ada compiler uses a very old code generator, the VCG, that has not had
any significant work put into it over the last ten years. In particular it
doesn't have this kind of strength reduction because the multiplies didn't use
to be much slower than the adds.
/Bevin
DEC Ada team
Regarding the claim that "the FORTRAN's I use all allow bitwise logical
operations", this claim can of course be true (for some particular person
and their experience), but I hope everyone realizes that the Fortran standard
definitely does NOT support this usage.
By this standard, I wouldn't be surprised if nearly all Ada compilers support
bitwise logical operations using the ordinary And/Or/Not etc on Booleans.
Try:
procedure Integer_Or (A,B : Integer) return Integer is
Ooops, some kind of glitch, let's try that again:
with Unchecked_Conversion;
function Integer_Or (A,B : Integer) return Integer is
type B is new Boolean;
Oh heck I'll have to start a new message, my editor is in some peculiar stateZit
with Unchecked_Conversion;
function Integer_And (A,B : Integer) return Integer is
. type MyB is new Boolean;
. for MyB'Size use Integer'Size;
. function ItoB is new Unchecked_Conversion (Integer, Boolean);
. function BtoI is new Unchecked_Conversion (Boolean, Integer);
begin
. return BtoI (ItoB (A) and ItoB (B));
end;
I think that's about as likely to work on an arbitrary Ada compiler as
the suggested approach (use .AND.) is to work on an arbitrary Fortran
compiler.
Furthermore, I would expect a decent Ada compiler to generate essentially
optimal code for this function. Unfortunately I can't try it on GNAT because
derived Boolean types are not implemented.
(Up to now we had thought this a non-critical feature, obviously, in view
of the important usage above, we must raise its priority :-) :-)
Actually this is a specific example of a general problem with C, the pointers
are a little too general, and when you see something like:
*x = y;
you need a lot of analysis, often very difficult to do in the interprocedural
case, to limit the implied damage of the assignment (on the face of it, this
assignment could destroy any variable of the appropriate type anywhere --
indeed if your C programmers expect aliasing via pointer casts to "work
properly", then it may be too restrictive to assume that only the evident
type is affected!
>Mike Livorsi said:
>>2. Variable argument lists
>>
>>Ada simply lacks this capability altogether. The familiar "printf" is a fine
>>example of a variable argument list:
>>
>> printf ("Hello %s. You have %d messages.\n", name, num_messages);
>Indeed you can't do this directly in Ada, because it is fundamentally type unsafe.
---No, it's not someting that's "fundamentally type unsafe". It's perfectly
practical in BASIC, PL/I and COBOL, with appropriate type conversions.
As most C programmers know, printf is unable to do any type checking
---There's no reason why it couldn't.
>>3. Bitwise operators
>>Bitwise ANDs, ORs, shifts, etc. are trivial in most other languages. Ada
>>lacks anything standard here. I know it allows to to layout records in a
>>bitwise fashion (so does C by the way), but this just isn't the same.
>>Usually, one must rely on an "implementation defined" package or some other
>>non-portable way to do these operations. In any case, these operations
>>are inherant in a C compiler and it can optimize expressions using bitwise operations.
>In Ada-83 bitwise operations are certainly available between packed bit strings ...
>. type Bits is array (0 .. 31) of Boolean;
>. pragma Pack (Bits);
>. A,B,C : Bits;
>. A := B or C xor (B and C);
>If you need to do logical operations on bits, then you need to define
>unchecked conversions between bit strings defined in this way and
>integer types. Works fine, and portably
---but not "trivial".
>Finally, where does this "trivial in most other languages come from". Of
>the languages that I do use or have in the past routinely used (Ada,
>Fortran, C, COBOL, Algol-68, SNOBOL4), only C has this capability (well
>I guess I didn't include assembler :-)
---There's PL/I, as well as FORTRAN (90). [Incidentally, in PL/I, it's
unnecessary to define an array of bits--one can use bit strings. One can
also use arrays of bit strings.]
printf is a function and for it to do any type checking on its own it
would have to do that at run time. However, all type information has
been optimised away before we get to run time so there is no way printf
could do any type checking.
You could envisage the case where the C compiler parses the format
string and then uses this information to verify the number of arguments
and their types or possibly some other scheme where printf depends on
the complicity of the compiler, but on its own printf can't do any
serious type checking.
Furthermore the ramification of type safe variable argument lists would
be that the number of a function's parameters and their types would be
dependent on one (or more if we want to generalize) of the parameters
of the function. From a software engineering point of view this doesn't
strike me as a particularly brillant solution to the problem.
-Robb
>printf is a function and for it to do any type checking on its own it
>would have to do that at run time. However, all type information has
>been optimised away before we get to run time so there is no way printf
>could do any type checking.
I seem to recall that the Algol-68R implementation had an elegant
solution to the problem. The second parameter was a dynamically
sized array, each of whose components was a discriminated union
of all printable base types. (Yes, that does need structural type
equivalence to work without generics.) The procedure then checked
the format cues individually against the type tags in the unions.
Given that I/O is a pretty expensive thing to do anyway, the extra
runtime overhead wasn't all that great (one case statement and one
or two tests per parameter), and it preserved type safety.
The ANSI C standard doesn't prohibit runtime type checks, it merely
doesn't mandate them. As a matter of fact, there are implementations
of C that perform the necessary runtime type checking.
|> You could envisage the case where the C compiler parses the format
|> string and then uses this information to verify the number of arguments
|> and their types or possibly some other scheme where printf depends on
|> the complicity of the compiler, but on its own printf can't do any
|> serious type checking.
GNU C will statically type check printf arguments when the format
string is known, which is by far the most common case.
Thomas.
I too was surprised the DEC Ada didn't eliminate the multiplication.
For another data point, I ran the above code through the only two Ada
compilers I have available on this machine: the old TeleSoft
RISCAda/SPARC compiler (RIP), and the Verdix VADSworks compiler for the
68020. Both generated architecturally equivalent code as shown below.
>>The corresponding Ada program is something like:
>>..
>>.. procedure A1 is
>>.. type Atype is record
>>.. item1 : integer;
>>.. item2 : integer;
>>.. item3 : integer;
>>.. end record;
>>..
>>.. i : integer;
>>..
>>.. aa : array (0 .. 9999) of Atype;
>>..
>>.. begin
>>.. for i in 0 .. 9999 loop
>>.. aa (i).item1 := 5;
>>.. aa (i).item2 := 6;
>>.. aa (i).item3 := 7;
>>.. end loop;
>>.. end A1;
>>..
RISCAda/SPARC: (excess commentary deleted)
! 14: for i in 0 .. 9999 loop
LN4: set -0x1d4fc,%l0 !001c a0142304
add %fp,%l0,%l4 !0020 a8078010
set 0x270f,%l1 !0028 a214630f
mov %g0,%l2 !002c a4100000
mov %l4,%l3 !0030 a6100014
L4:
! 15: aa (i).item1 := 5;
! 16: aa (i).item2 := 6;
! 17: aa (i).item3 := 7;
!
! 18: end loop;
!
LN8:
LN9: set 5,%l0 !0034 a0102005
st %l0,[%l3] !0038 e024c000
set 6,%l0 !003c a0102006
st %l0,[%l3+4] !0040 e024e004
set 7,%l0 !0044 a0102007
st %l0,[%l3+8] !0048 e024e008
add %l2,1,%l2 !004c a404a001
cmp %l2,%l1 !0050 80a48011
ble L4 !0054 04bffff8
add %l3,0xc,%l3 !0058 a604e00c
It's a shame the code generator did't choose to assign the numeric literals
5, 6, and 7 to local registers (say %l5, %l6, and %l7) and hoist their
initialization out of the loop. But then again, the Sun C compiler didn't
do that for the equivalent C code in Robert Dewar's post.
VADSworks/68020:
14 for i in 0 .. 9999 loop
001a: moveq.l #00, d0
001c: movea.l a6, a0
001e: adda.l #-01d4c4, a0
0024: adda.l #-0c, a0
15 aa (i).item1 := 5;
002a: adda.l #0c, a0
0030: move.l #05, (a0)
16 aa (i).item2 := 6;
0036: move.l #06, (04,a0)
17 aa (i).item3 := 7;
003e: move.l #07, (08,a0)
18 end loop;
0046: addq.l #01, d0
0048: cmpi.l #0270f, d0
004e: ble.b -026 -> 02a
The conclusion I derive from this experiment is that Robert is correct.
Modern Ada compilers do indeed generate code on par with modern C
compilers -- even without resorting to 'C' pointer trickery as referenced
in the original post on this thread.
--
Tom Quiggle t...@visicom.com quig...@ajpo.sei.cmu.edu
VisiCom Laboratories Inc.
San Diego, CA 92121
Voice: (619) 457-2111 ex 231 Fax: (619) 457-0888
An American's a person who isn't afraid to criticize the President but
is always polite to traffic cops.
-- the fortune program
No you couldn't, it is perfectly permissible to compute this string
at runtime. True enough it is often a literal, but it is not required
to be!
true enough, but the GNU compiler will check the format strings
at compile time if you use the -Wall option, a nice feature.
for example:
#include <stdio.h>
void main(void) {
int i = 1;
printf ("this %s is a bug %d\n", i);
}
produces:
x.c:4: warning: format argument is not a pointer (arg 2)
x.c:4: warning: too few arguments for format
--
Alex Blakemore
al...@cs.umd.edu NeXT mail accepted
Presumably Tom's "(RIP)" refers to TeleSoft (absorbed by Alsys) and not
to the RISCAda/SPARC compiler, which is still a product developed and
sold by Alsys.
(Disclaimer: If I spoke officially for Alsys, I suspect I'd have said
something fairly similar, but I don't.)
--
Keith Thompson (The_Other_Keith) k...@alsys.com (k...@telesoft.com still works)
TeleSoft^H^H^H^H^H^H^H^H Alsys, Inc.
10251 Vista Sorrento Parkway, Suite 300, San Diego, CA, USA, 92121-2718
"Hush my darling, be still my darling, the lion's on the phone" -- TMBG