Doctor Fortran in "I've Come Here For An Argument, Side 2"

My earlier post, "I've Come Here For An Argument", was very popular with my fellow support engineers, as it provided a convenient answer to questions they frequently receive.  (For me too, which in part is why I wrote it!) But some people (cough, Ron, cough) are never satisfied, and I've been asked to write a follow-up on what else can go wrong when you don't understand all of Fortran's argument-passing rules.  So, here we go...

Look, But Don't Touch


Consider the following subroutine:

subroutine sub (i)
integer i
if (i > 2) i = i + 1
return
end

Now, what happens when you call this with:

call sub(3)

???

a) The value 3 changes to the value 4 in the caller
b) Access violation or segmentation fault
c) Nothing, the variable changes value in the subroutine but not the caller
d) World War III starts

The answer, for many older compilers, was (a)!  For current Intel compilers, the correct answer, however, is (b) - a run-time error that is "access violation (on Windows) or "segmentation fault: on Linux and Mac OS.  Why?  The compiler has to put the value 3 in memory somewhere.  By default, it puts it in a section of memory it has asked the operating system to make "read-only". When the value of variable i changes in the subroutine, that is an attempt to write to read-only memory and you get a run-time error.

The Intel compiler has an option, /assume:[no]protect_constants (Windows) or -assume [no]protect_constants (Linux/Mac OS) which can change this behavior to (c). If "noprotect_constants" is specified, then the compiler creates a temporary copy of the value 3 and passes the address of the copy.  The subroutine can change the value all it wants but the changes will be discarded on return.  (Those who have been with the Doctor for a long time may recall that I wrote about this back in the CVF days, almost nine years ago.  You can read that item here.

I will point out now that the above call is not legal Fortran - the literal constant 3 is not "definable" and that means you are not allowed to "redefine or cause to become undefined" the associated dummy argument. However, code like this has appeared in many applications over the years.

Now, what if you did this?

call sub((3))

What is being passed  here is an expression, not a literal, so is that legal?  No!  An expression is not definable either!  However, Intel Fortran treats this differently and will always pass a temporary copy of the value, as if you had said /assume:noprotect_constants.

But what if you wanted to pass any variable to this subroutine but have the original value preserved?  You could write:

call sub((j))

and take advantage of Intel Fortran's extension where it passes a copy, but Fortran 2003 has another way. If you give the dummy argument the VALUE attribute, which requires an explicit interface to be visible, then the effect is similar in some ways to passing an expression.  Actually, what happens is that on entry to the subroutine, a new local variable is created that copies the value of the argument, and all references in the subroutine are to that local variable, which is definable.  On exit, like other local variables, the copy is discarded. VALUE has another purpose as part of C interoperability, but I'll discuss that another time.

By the way, there's a less obvious way that you can pass an argument that is not definable: an array with a vector subscript.  For example, A([1,3,5]).  Here too, you're not allowed to assign into a dummy argument that is associated with such an actual argument.

Alias Smith and Jones

It is often said that Fortran is faster than C because Fortran disallows variable aliasing, where the same storage can be referred to by two or more different names, and in C everything can be aliased.  There is some truth to this on both sides, but it is not absolute - especially when more recent versions of the C standard are considered.

It is true that in most cases, a Fortran compiler can assume that no aliasing occurs, but not always.  Unfortunately, a lot of programmers inadvertently violate the language rules and run int trouble. Here's the basic text that the standard has to say about aliasing:

While an entity is associated with a dummy argument, the following restrictions hold:
(1) Action that affects the allocation status of the entity or a subobject thereof shall be taken
through the dummy argument. Action that affects the value of the entity or any subobject
of it shall be taken only through the dummy argument unless
(a) the dummy argument has the POINTER attribute or
(b) the dummy argument has the TARGET attribute, the dummy argument does not
have INTENT (IN), the dummy argument is a scalar object or an assumed-shape
array, and the actual argument is a target other than an array section with a vector
subscript.

[12.4.1.7 Restrictions on entities associated with dummy arguments]



Let's look at the simplest example:

program alias1
real x
x = 4.0
call sub (x,x)
print *, x
end

subroutine sub (a,b)
real a,b
a = a + 2.0
b = b * 3.0
return
end subroutine sub

What does this program print?

a) 6.0
b) 8.0
c) 12.0
d) 18.0
e) Any of the above

The correct answer is (e).  The program is not legal Fortran and the results are unpredictable.  For example, the compiler could do either the add or the multiply first, or it could copy the values of the arguments into a temporary (say, a register), do the add/multiply, then store the result.

Ok, that one is pretty obvious.  How about this?

program alias2
real x
common /CMN/ x
x = 4.0
call sub(x)
print *, x
end
subroutine sub (y)
common /CMN/ x
real x,y
y = y + 2.0
x = x * 3.0
return
end subroutine sub

The choices, and answer, are the same as for alias1 above.  Here, the aliasing is between a dummy argument and a COMMON variable.  As of Fortran 90, you could extend this case to module variables or host-associated variables in addition to COMMON.  It's ok if all you do is get the value, but once you change the value (or change the definition status), then the requirement is that all such changes must be through the dummy argument only.

This second scenario is much easier to stumble into, especially with large applications.  Recognizing this, the Intel compiler has an option to tell the compiler to assume that such aliasing may exist and to disable optimizations that depend on the absence of aliasing.  That option is /assume:dummy_aliases (Windows) or -assume dummy_aliases (Linux and Mac IOS).  If you have an old and large program that isn't getting correct answers, try enabling this option to see if it helps.  In many cases, it will.

In Conclusion

Wow, this has been one of my longer posts, and there's lots more that could be said on the general topic of arguments, but we'll save that for another time.

If you have a comment on this article, or a suggestion for a future topic, feel free to add a comment here. (If you need technical support, please visit our user forum instead.)   Also, you can now follow me on Twitter, if you're so inclined: @DoctorFortran I'm still feeling my way with this "fluttering" thing, so be kind...
如需更全面地了解编译器优化,请参阅优化注意事项