The standard says:
'The number of iterations of a loop may be determined at the beginning of execution of the DO construct, or may be left indefinite ('DO forever' or DO WHILE). In either case, an EXIT statement (8.1.4.4.4) anywhere in the DO construct may be executed to terminate the loop immediately.'
It also says:
'Except for the incrementation of the DO variable that occurs in step (3), the DO variable shall neither be redefined nor become undefined while the DO construct is active.'
The standard doesn't forbid changing m1, m2, or m3 in the loop as far as I know, but such changes don't affect the number of loop iterations. They can't because the number of iterations is calculated at the beginning, before the loop is executed.
Knowing the number of iterations up front allows optimizing compilers to optimize the loop effectively (techniques as loop-unrolling, swapping loops, merging loops).
The loop
do i=1,m2
<body of loop>
end do
is equivalent to:
i = 1
count = max(m2, 0) ! <-- loop is executed count times
c = count
10 if (c .eq. 0) goto 20
<body of loop>
c = c - 1
i = i + 1
goto 10
20 continue
A more general equivalence is given in the post mecej4 refers to.
As John points out you can do all manner of complicated loops if you use the indefinite 'DO forever' type with EXIT. But don't expect much optimization in such cases.