Lint on a non-constant expression - harmful? 
Author Message
 Lint on a non-constant expression - harmful?

As we know, an attempt to modify value of a routine's argument
whose actual argument is a constant is illegal and, depending
on compiler, causes either a run-time error or (worse) {*filter*}
behaviour:

call foo(1)
...
subroutine foo(i)
i=2
end subroutine foo

(is "lint" a common term for this?)

But what's with non-constant expressions in this context?
I'd like to do something like:

call foo(k*i(1:4))
...
subroutine foo(j)
!No explicit interface assumed, no intent nor (:)
integer::    j(*)    
j(1:4)=something
end subroutine foo

My guess is that in theory, this is illegal too. But in
practice? (To quote someone's signature "In theory, theory
and practice are the same; in practice, they aren't.").

Since the argument has to be evaluated at run-time, it has to
be done either on stack or on heap (I know, I know, Standard
doesn't have any knowledge on these terms) and address of
that temporary has to be passed to foo; the temporary is
probably deleted either immediately after call. So, no
harm could be possibly done? Or is there a gotcha?

Jugoslav
________________________
www.geocities.com/jdujic



Fri, 02 Jul 2004 19:46:57 GMT  
 Lint on a non-constant expression - harmful?


Quote:
> My guess is that in theory, this is illegal too.

Right. "If a dummy argument has no intent, ..., the actual argument must be
a variable if the dummy argument is redefined." (fortran 90/95 Explained",
Section 5.9).

      The called procedure will write those 4 words somewhere and that may
kill you sometime.

Regards,

Mike



Fri, 02 Jul 2004 20:19:27 GMT  
 Lint on a non-constant expression - harmful?

Quote:

> As we know, an attempt to modify value of a routine's argument
> whose actual argument is a constant is illegal and, depending
> on compiler, causes either a run-time error or (worse) {*filter*}
> behaviour:

> call foo(1)
> ...
> subroutine foo(i)
> i=2
> end subroutine foo

> (is "lint" a common term for this?)

> But what's with non-constant expressions in this context?
> I'd like to do something like:

> call foo(k*i(1:4))
> ...
> subroutine foo(j)
> !No explicit interface assumed, no intent nor (:)
> integer::    j(*)    
> j(1:4)=something
> end subroutine foo

> My guess is that in theory, this is illegal too. But in
> practice? (To quote someone's signature "In theory, theory
> and practice are the same; in practice, they aren't.").

> Since the argument has to be evaluated at run-time, it has to
> be done either on stack or on heap (I know, I know, Standard
> doesn't have any knowledge on these terms) and address of
> that temporary has to be passed to foo; the temporary is
> probably deleted either immediately after call. So, no
> harm could be possibly done? Or is there a gotcha?

> Jugoslav
> ________________________
> www.geocities.com/jdujic

Supposing that you employed the same argument expression more than once, and the compiler decided to save and re-use the temporary array, you would pass unexpected values on the repeated use.  As we have seen, compilers based on the EPC front end create and destroy these temporary arrays at the call site, at a great cost in performance.

--
Tim Prince



Fri, 02 Jul 2004 22:00:04 GMT  
 Lint on a non-constant expression - harmful?

| Supposing that you employed the same argument expression more than once, and
the compiler decided to save and re-use the temporary array, you would pass
unexpected values on the repeated use.  As we have seen, compilers based on the
EPC front end create and destroy these temporary arrays at the call site, at a
great cost in performance.

A good point. So, this belongs to a "Don't do this at home" group.

Jugoslav
________________________
www.geocities.com/jdujic



Fri, 02 Jul 2004 22:23:59 GMT  
 Lint on a non-constant expression - harmful?

Quote:



> | Supposing that you employed the same argument expression more than once, and
> the compiler decided to save and re-use the temporary array, you would pass
> unexpected values on the repeated use.  As we have seen, compilers based on the
> EPC front end create and destroy these temporary arrays at the call site, at a
> great cost in performance.

> A good point. So, this belongs to a "Don't do this at home" group.

> Jugoslav
> ________________________
> www.geocities.com/jdujic

Is it not illegal to do that? Something like:

   call subr( var1, var1, var1 )

Or does it make a difference if you do:

   call subr( var1+var2, var1+var2, var1+var2 )

Regards,

Arjen



Fri, 02 Jul 2004 23:26:51 GMT  
 Lint on a non-constant expression - harmful?

|
| Is it not illegal to do that? Something like:
|
|    call subr( var1, var1, var1 )
|
| Or does it make a difference if you do:
|
|    call subr( var1+var2, var1+var2, var1+var2 )
|
| Regards,
|
| Arjen

Well, that's another (though similar) topic. If all args
are INTENT(IN), it's of course OK. If not, it depends whether
subr is side-effect free (1). If it isn't, your first example
is surely illegal. The second is illegal too, but you may
get away with it if your compiler is dumb enough to evaluate
the expression three times on three different addresses.

(1) I use term "legality" here as "sure to work on 99.99%
compilers". In a strict sense, nothing is guaranteed by
if you use aliased arguments as intent(out).

For example, I have a collection of frequently used helper
routines which have to be safe in that sense. For example:

!-----------------
subroutine mul(ar,ai,br,bi,cr,ci)
!Subroutine to multiply two complex numbers in pairs-of-real form
! (a+jai)*(b+jbi)=c+jci
implicit none

real, intent(in) ::  ar, ai, br, bi
real, intent(out) :: cr, ci

real::               crr, cii

crr=ar*br - ai*bi
cii=ar*bi + ai*br
cr=crr
ci=cii

end subroutine mul
!-----------------

Written this way, it's side-effect free (in order to allow
calls like mul(ar, ai, br, bi, ar, ai)); but these have to be
handled carefully. Similar code but with only lines:

cr = ar*br - ai*bi
ci = ar*bi + ai*br

and a "right" combination of aliased arguments (as above) misbehaved
beautifully when ported to another compiler (which just used
different order of evaluation). Don't ask me how long we traced
the error :-(.

So, it depends whether samples like yours are legal or not.
Another "Don't do this at home" sample (unless you pay a *very*
special attention).

For example, this is a side-effect-safe matrix transposition:

subroutine rorto(n,a,b,ndima,ndimb)
...
do i = 1,n
   b(i,i) = a(i,i)
   do j = i+1,n
      temp = a(j,i)
      b(j,i) = a(i,j)
      b(i,j) = temp
   end do
end do

so that it's safe to call it like (n,a,a,size(a,1),size(a,1)).

Jugoslav
________________________
www.geocities.com/jdujic



Fri, 02 Jul 2004 23:59:53 GMT  
 Lint on a non-constant expression - harmful?

Quote:




>>| Supposing that you employed the same argument expression more than once, and
>>the compiler decided to save and re-use the temporary array, you would pass
>>unexpected values on the repeated use.  As we have seen, compilers based on the
>>EPC front end create and destroy these temporary arrays at the call site, at a
>>great cost in performance.

>>A good point. So, this belongs to a "Don't do this at home" group.

>>Jugoslav
>>________________________
>>www.geocities.com/jdujic

> Is it not illegal to do that? Something like:

>    call subr( var1, var1, var1 )

> Or does it make a difference if you do:

>    call subr( var1+var2, var1+var2, var1+var2 )

> Regards,

> Arjen

Your last example may seem a bit academic, but look at the way the
induct.f90 benchmark on www.polyhedron.com uses the expression

sqrt(dot_product(r1_rot_vec-r2_rot_vec, r1_rot_vec-r2_rot_vec))

and how certain compilers stumble over it.

--
Tim Prince



Sat, 03 Jul 2004 00:16:22 GMT  
 Lint on a non-constant expression - harmful?

Quote:

> subroutine rorto(n,a,b,ndima,ndimb)
> ...
> do i = 1,n
>    b(i,i) = a(i,i)
>    do j = i+1,n
>       temp = a(j,i)
>       b(j,i) = a(i,j)
>       b(i,j) = temp
>    end do
> end do

> so that it's safe to call it like (n,a,a,size(a,1),size(a,1)).

"Safe" perhaps in practice.  At least in theory, this can fail.
I couldn't tell you whether it does on any current compilers.  An
"obvious" falure mode would be if copy-out was done and the "a"
argument got copied out last (undoing all your careful work).

Seems like a sufficiently "smart" compiler should note that "a" is
never changed and thus shouldn't be copied out....but then a sufficiently
"smarter" compiler might even notice the standard violation and refuse
to compile it at all.

It is unambiguously nonstandard.  Whether it is "safe" in practice
I'll not commit myself to one way or another.  I used to have some
code almost identical to that.  Odds are its still even in use
somewhere.  But I've gotten more conservative about such things in new
code.  Partly, it's that I've run into enough cases where compilers
surprised me by doing things that were standard-conforming, but
unexpected.  (I was really surprised the first time I deduced that an
f90 compiler was actually doing copy-in/out to make a contiguous copy
of an actual argument).  Perhaps its just my age.

--
Richard Maine                       |  Good judgment comes from experience;
email: my last name at host.domain  |  experience comes from bad judgment.
host: altair, domain: dfrc.nasa.gov |        -- Mark Twain



Sat, 03 Jul 2004 01:25:43 GMT  
 Lint on a non-constant expression - harmful?

Quote:




> |
> | Is it not illegal to do that? Something like:
> |
> |    call subr( var1, var1, var1 )
> |
> | Or does it make a difference if you do:
> |
> |    call subr( var1+var2, var1+var2, var1+var2 )
> |
> | Regards,
> |
> | Arjen

> Well, that's another (though similar) topic. If all args
> are INTENT(IN), it's of course OK. If not, it depends whether
> subr is side-effect free (1). If it isn't, your first example
> is surely illegal. The second is illegal too, but you may
> get away with it if your compiler is dumb enough to evaluate
> the expression three times on three different addresses.

> (1) I use term "legality" here as "sure to work on 99.99%
> compilers". In a strict sense, nothing is guaranteed by
> if you use aliased arguments as intent(out).

> For example, I have a collection of frequently used helper
> routines which have to be safe in that sense. For example:

> !-----------------
> subroutine mul(ar,ai,br,bi,cr,ci)
> !Subroutine to multiply two complex numbers in pairs-of-real form
> ! (a+jai)*(b+jbi)=c+jci
> implicit none

> real, intent(in) ::  ar, ai, br, bi
> real, intent(out) :: cr, ci

> real::               crr, cii

> crr=ar*br - ai*bi
> cii=ar*bi + ai*br
> cr=crr
> ci=cii

> end subroutine mul
> !-----------------

> Written this way, it's side-effect free (in order to allow
> calls like mul(ar, ai, br, bi, ar, ai)); but these have to be
> handled carefully. Similar code but with only lines:

> cr = ar*br - ai*bi
> ci = ar*bi + ai*br

> and a "right" combination of aliased arguments (as above) misbehaved
> beautifully when ported to another compiler (which just used
> different order of evaluation). Don't ask me how long we traced
> the error :-(.

> So, it depends whether samples like yours are legal or not.
> Another "Don't do this at home" sample (unless you pay a *very*
> special attention).

> For example, this is a side-effect-safe matrix transposition:

> subroutine rorto(n,a,b,ndima,ndimb)
> ...
> do i = 1,n
>    b(i,i) = a(i,i)
>    do j = i+1,n
>       temp = a(j,i)
>       b(j,i) = a(i,j)
>       b(i,j) = temp
>    end do
> end do

> so that it's safe to call it like (n,a,a,size(a,1),size(a,1)).

Just tested on xlf with -O3. It isn't safe. Not that I expected it to
be; optimizing out temp here is an optimization I'd expect from a good
optimizer (but the optimizer obviously works a bit different, because

   1.000000       2.000000       3.000000    
   4.000000       5.000000       6.000000    
   7.000000       8.000000       9.000000    

gets

   1.000000       4.000000       7.000000    
   2.000000       5.000000       8.000000    
   7.000000       8.000000       9.000000    

where with simply optimizing away tmp it should have read s.th. like

   1.000000       4.000000       7.000000    
   4.000000       5.000000       8.000000    
   7.000000       8.000000       9.000000    

).



Wed, 04 Aug 2004 04:15:55 GMT  
 Lint on a non-constant expression - harmful?


...

Quote:
> For example, I have a collection of frequently used helper
> routines which have to be safe in that sense. For example:

> !-----------------
> subroutine mul(ar,ai,br,bi,cr,ci)
> !Subroutine to multiply two complex numbers in pairs-of-real form
> ! (a+jai)*(b+jbi)=c+jci
> implicit none

> real, intent(in) ::  ar, ai, br, bi
> real, intent(out) :: cr, ci

> real::               crr, cii

> crr=ar*br - ai*bi
> cii=ar*bi + ai*br
> cr=crr
> ci=cii

> end subroutine mul
> !-----------------

> Written this way, it's side-effect free (in order to allow
> calls like mul(ar, ai, br, bi, ar, ai)); but these have to be
> handled carefully. [...]

This may work as you intend on many systems, but it isn't standard
conforming.  The actual arguments in your sample call cause the
AR dummy argument to be aliased with the CR dummy, and the
AI dummy to be aliased with the CI dummy.  If an aliased argument
is defined, redefined, or becomes undefined anywhere in a procedure,
then the data it refers to must be accessed *only* through that
single argument.  In this case, the compiler is perfectly free to
notice that CRR and CII are just local temporaries and to optimize
them out, substituting CR and CI in their place.  It can do that because
such a transformation doesn't change the semantic meaning for any
*legal* call.

The only safe way to call this routine is to explicitly introduce some
syntax that forces copies of the values to be passed for the INTENT(IN)
arguments.  I believe that a call such as CALL MUL((AR), (AI), (BR), (BI), AR,
AI)
will behave as you intend, even if you recode your procedure and explicitly
leave out CRR and CII.  This is because the first four actual arguments are
non-definable expressions instead of variables.  Of course, there may be
implementations which don't make copies of the arguments that are written
as expressions.  I believe that there is sometimes debate over whether that
is a conforming implementation.  To be safe, maybe you should introduce
temporaries yourself:

  tr = ar
  ti = ai
  call mem(tr, ti, br, bi, ar, ai)

Here, the temporaries *can't* be optimized out because doing so would
change the semantic meaning of completely legal code.

--
J. Giles



Wed, 04 Aug 2004 05:26:39 GMT  
 Lint on a non-constant expression - harmful?

|


| ...
| This may work as you intend on many systems, but it isn't standard
| conforming.  The actual arguments in your sample call cause the
| AR dummy argument to be aliased with the CR dummy, and the
| AI dummy to be aliased with the CI dummy.  If an aliased argument
| is defined, redefined, or becomes undefined anywhere in a procedure,
| then the data it refers to must be accessed *only* through that
| single argument.  In this case, the compiler is perfectly free to
| notice that CRR and CII are just local temporaries and to optimize
| them out, substituting CR and CI in their place.  It can do that because
| such a transformation doesn't change the semantic meaning for any
| *legal* call.

Well, we're reviving this old thread, (probably under influence of
"Standard-conforming program") and your and Christopher's remarks
really make me think (read: scared). As I said, I knew that my
samples weren't standard-conforming but I thought I'd safely
get away with a {*filter*}.

| I believe that a call such as CALL MUL((AR), (AI), (BR), (BI), AR,
| AI)
| will behave as you intend, even if you recode your procedure and explicitly
| leave out CRR and CII.

Thanks, that trick would never occur to myself; didn't think
that (AR) is actually an expression, not a variable. However,
being warned by Christopher's results, couldn't a smart compiler
see that ar is intent(in) (mul is a module procedure) and decide
not to make a temporary stack copy of AR before call, but to
reuse the original &AR? Further, mul looks like a good candidate
for inlining. Would it be a standard-conforming compiler?
[am I starting a debate between Richard and you? ;-)]

Again, your and Christopher's results make me a bit paranoic.

| The only safe way to call this routine is to explicitly introduce some
| syntax that forces copies of the values to be passed for the INTENT(IN)
| arguments.    This is because the first four actual arguments are
| non-definable expressions instead of variables.  Of course, there may be
| implementations which don't make copies of the arguments that are written
| as expressions.  I believe that there is sometimes debate over whether that
| is a conforming implementation.  To be safe, maybe you should introduce
| temporaries yourself:
|
|   tr = ar
|   ti = ai
|   call mem(tr, ti, br, bi, ar, ai)
|
| Here, the temporaries *can't* be optimized out because doing so would
| change the semantic meaning of completely legal code.

The point of introducing that simple routine was to shorten the
calling code and make it more readable :-).

Regards
Jugoslav



Wed, 04 Aug 2004 18:15:47 GMT  
 Lint on a non-constant expression - harmful?

Quote:

> | I believe that a call such as CALL MUL((AR), (AI), (BR), (BI), AR, AI)
> | will behave as you intend, even if you recode your procedure and explicitly
> | leave out CRR and CII.

> Thanks, that trick would never occur to myself; didn't think
> that (AR) is actually an expression, not a variable. However,
> being warned by Christopher's results, couldn't a smart compiler...

It's a trick I've seen before.  Didn't occur to me before I was shown
by someone else (I'm pretty sure here) either, but I was shown a
fairly long time ago.

I didn't take the time to trace through the details of the code and
logic, but I don't think I have to.  There is a difference between AR
and (AR).  In many contexts that difference doesn't matter and might be
subject to being optimized away.  However a compiler is not allowed to
do this incorrectly.  You don't really have to get down into the
implementation details of inlining or whatever else (unless you are
trying to tune performance, perhaps).  The essense is that if the code
is legal (which this is once you add the parens), the compiler is
required to get it right.  If it fails to, then it's a compiler bug.

Compilers have been known to have bugs - even this bug in particular.
But I think most (all?) of the current supported ones have probably
killed this particular bug by now, even if it existed in the past, because
this is one that gets tested for and reported (and by now compiler vendors
probably have a test for this in their internal testing suites).

Quote:
> [am I starting a debate between Richard and you? ;-)]

I doubt you are starting such a debate on this one - I suspect we agree.

--
Richard Maine
email: my last name at domain
domain: qnet dot com



Thu, 05 Aug 2004 01:33:15 GMT  
 Lint on a non-constant expression - harmful?


...

Quote:
> > [am I starting a debate between Richard and you? ;-)]

> I doubt you are starting such a debate on this one - I suspect we agree.

I'm pretty sure we're on the same side on this one.  That is, adding
the parentheses makes the code conforming, an implementation is
required to get it right, and some implementations might not get it
right anyway.

--
J. Giles



Thu, 05 Aug 2004 02:32:56 GMT  
 
 [ 14 post ] 

 Relevant Pages 

1. PGF90-S-0087-Non-constant expression where constant expression required

2. PGF90-S-0087-Non-constant expression where constant expression required

3. Non-constant expression where constant expression required.

4. optimizing evaluation of constant expressions

5. constant expressions, parameters, `define

6. constant expressions, parameters, `define

7. '87 LRM - constant expression on port

8. Salford compiler: Assigning a value to a constant expression is invalid

9. constants (literal, parameter, expression) in F77

10. complex constants and expression syntax extension a la Mathematica

11. constant expressions & compilers

12. Constant expressions and mathematical functions

 

 
Powered by phpBB® Forum Software