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...
For more complete information about compiler optimizations, see our Optimization Notice.

11 comments

Top
anonymous's picture

Hi Doc
im using intel visual fortran compiler 6.0.0 with visual studio 6.0 to build a mixed mode application with c, c++ and fortran on vista32. my c++ application calls into exported functions of my fortran static library. in my file opeing routine in the fortran static lib, i get an access violation excception in release mode but not debug mode. im passing a constant by reference to the fortra routine like this:

..c++ call..
char * filepath = "E:\Samples";
char * filename ="abc.def";
char * fileType = "bee";
int unit = 12;
OPENED(filename, &unit, fileType, &err);
......
fortran code :
subroutine opened(name,iunt,ityp,ierr)
ierr=0
kpc=4*ixd
1if (iunt.le.5 .or. iunt.gt.77) then
.....
i get an access violation excception at the above line in release mode, when trying to access iunt most probbaly. i think i might have the same problem that u mentioned above but i cant find the /assume:[no]protect_constants (Windows) switch that you suggest. apparently my version of compiler doesnt support this switch. Please give some insight as to how i should resolve it.

thanks a bunch
anna

anonymous's picture

Steve,
Could you say more about where/how the literal values are stored? In a large mixed language dll I am sometimes getting a crash the first time a variable passed as a literal is referenced (it is not being changed). I suspect some sort of memory conflict.

noelandres17's picture

Hi Steve,

This next comment comes from a person who has programmed before in C (in college) and now extensively in Matlab. Recently I began learning Fortran since I needed to recreate some legacy code of my company (written in Fortran 77) to Matlab. In such legacy code, I see lines of code like:

CALL SHIFT (XDUM, YDUM, XCG, YCG, ROT, NTOT, XDUM, YDUM)

Here, the output are supposed to be the last two arguments. But they used the same variable name as the first 2 inputs!

Then I remembered about this article (which I read a couple of weeks ago, since I was trying to understand how the arguments behave in Fortran subroutines). It seems to me that understanding such behavior requires one to be an expert in Fortran, in my opinion. I think that Fortran code would be way simpler if the above line could be written as:

[XDUM, YDUM] = SHIFT( XDUM, YDUM, XCG, YCG, ROT, NTOT )

Thus, clearly marking the inputs from the arguments. I am used to doing this in languages like Matlab. From looking at the original code, I know the author intended to replace the XDUM and YDUM from the inputs with the XDUM and YDUM from the outputs. But it looks like it is not legal Fortran, and that different compilers might give different results (assuming I understood your article). Maybe the compiler they used back in the day allowed it and the one I am using now (Intel 10.1) may not.

Anyways, my two cents are: should not Fortran subroutines follow the construct that Matlab uses. I now know that you can
use INTENT (in, out, inout), but I think thats not the elegant way to solve this. It puts an extra burden in the developer.
I have a similar critic with the "IMPLICIT NONE" thing. The way I see it, you should not have to put it! The compiler should tell you that you have to explicitly define all the variables. But that is another story.

I guess if I want to use the legacy code, I'll have to dig into the code (30 subroutines with about 500 lines each) and put the intent myself. It is just hard to understand Fortran code because the inputs and outputs are not clearly defined (not all the time the authors write good documentation).

Bear in mind that this suggestion comes from someone that understand programming in general (all programming languages share the same line of thought). I think that when people are learning something for the first time, they can tell how to make such thing easier to learn, etc. Once you are an expert, it becomes second nature and you are already wired to think that way. I guess I am wired to think of input and output arguments as Matlab does.

I would love to hear what your opinion on this is, if this has been brought to the Fortran standards committee, etc.

Noel Andres

As presented, the compiler does not have enough information to reject the code at compile time. If an explicit interface to SUB was visible where the argument was declared INTENT(OUT), the compiler will complain. Also, as I noted, you will typically get an access violation at run-time. Generated interfaces help in other ways, but don't contain INTENT information as that would require perfect flow analysis to get 100% right.

The Fortran standard describes a standard-conforming program. A compiler is required to be able to diagnose violations of numbered syntax rules and constraints, but not behavior such as noted here, which is the responsibility of the programmer.

You can help the compiler by always using explicit interfaces (best as module procedures) and INTENT.

anonymous's picture

In your first example you wrote: "I will point out now that the above call is not legal Fortran". So how do I simply get the compiler to reject this "not legal" code at compile time? Did I miss something in your explanation? The compiler options alter code generation but is there a "strict" flag to force the compiler to reject the code?

I do not have an example handy that shows different behavior - such examples tend to be longer and more complicated, and I wanted to keep the examples here short to illustrate my point. Perhaps you'll have to trust me when I say that the behavior is not predictable.

Peter's picture

Steve,
I was getting 18 for both the cases (assuming and not assuming dummy aliasing), but you were saying that the results are unpredictable and this program is simple enough to control the compiler ! do you have any better example, you can take you own time. That way I would be able to understand the importance of your post ! thanks

No - 18 would be correct if dummy aliasing was assumed. 12 MIGHT be produced if it weren't assumed. Here's how it goes:

a=a+2.0 is executed. a is referenced from memory and has the value 4.0 - new value is 6.0 and is stored to memory.
b=b*3.0 is executed. The memory address for b is the same as for a, so it now has the value 6.0. 6*3 is 18.

The thing with aliasing is that the results are unpredictable unless you tell the compiler to assume aliasing might occur. With this simple program, the compiler didn't choose to hold intermediate computations in registers, or re-order the operations, but in a larger program it might, causing results to vary.

Peter's picture

Thanks Steve, I enabled /assume:dummy_aliases in Data, but it gives me 18. Don't we expect 12 ??

Peter, if you are referring to /assume:dummy_aliases, that is Data > Assume Dummy Arguments Share Memory Locations. If you look in the compiler documentation for the description of a given option, it will tell you the corresponding IDE option, if there is one.

Pages

Add a Comment

Have a technical question? Visit our forums. Have site or software product issues? Contact support.