Hi Arthur,
I'm testing the proc in a service pgm that defines 3 parms, all of which 
are 10 bytes, alpha and "const". The last 2 have "options(*nopass)".  I'm 
testing for the presence/absence of the last 2 parms using %addr(parm) 
equal/not equal to *null.
Short Answer:
* You use %PARMS to check if an options(*NOPASS) parameter was passed.
* You use %ADDR(PARM)<>*NULL  to check if an options(*OMIT) parameter 
was passed.
Long Answer:  (err..  "Long, drawn out, you'll need aspirin before it's 
over answer")
Under the covers, the system is passing a list of pointers.  The list 
looks something like this (it's not actually RPG, but I figured you 
could relate better to RPG):
      D ParmList        ds                  qualified
      D   opdesc                        *
      D   workarea                    16A
      D   parm1                         *
      D   parm2                         *
      D   parm3                         *
The structure is different on every call -- the number of fields 
containing the parameters is different...   So you can think of each 
call as generating a different data structure in memory.  The first 2 
fields are always the same (though opdesc is set to *NULL if there's no 
descriptor) but the parms field is different every time.
The above example shows three parameters passed by reference.  If they 
were passed by value, instead, the list would contain the actual data, 
like this:
      D ParmList        ds                  qualified
      D   opdesc                        *
      D   workarea                    16A
      D   parm1                        5A
      D   parm2                       10i 0
      D   parm3                         *
in this case, the first two parameters are passed by value, and the 3rd 
parameter is passed by reference (or is a pointer passed by value -- 
under the covers there's no difference between a parameter passed by 
value and a pointer passed by reference).
So this is what the caller writes into memory... it creates a data 
structure (dynamically/on-the-fly) and writes memory like the above. 
The the procedure that was called overlays that same spot in memory with 
it's own data structure (one generated from your PI...).   If the data 
structures don't match, you'll have problems -- that's why it's so 
important to make the PR and the PI match! (PR=prototype -- that 
generates the caller's structure -- PI = proc interface -- that 
generates the called procedure's structure)
The first pointer (opdesc) points to a list of operational descriptors, 
if an operational descriptor was passed.  That's another data structure 
that looks like this:
     D DescList        ds                  qualified
     D   parm_count                  10i 0
     D   reserved                    28A
     D   parm1_type                   3i 0
     D   parm1_dtype                  3i 0
     D   parm1_info1                  3i 0
     D   parm1_info2                  3i 0
     D   parm1_len                   10i 0
     D   parm2_type                   3i 0
     D   parm2_dtype                  3i 0
     D   parm2_info1                  3i 0
     D   parm2_info2                  3i 0
     D   parm2_len                   10i 0
Again, the layout of this varies depending on how many parameters are 
passed.  And in some languages (e.g. ILE C), you can designate whether a 
descriptor is passed on a parameter-by-parameter basis...   so this list 
can change on the fly.
RPG *always* passes at least a "minimal" operational descriptor.  Which 
means that RPG *always* passes at least this much:
     D DescList        ds                  qualified
     D   parm_count                  10i 0
If you code OPDESC on the prototype, then RPG will pass all of the other 
fields in the operational descriptor as well -- but without OPDESC it 
still passes that one field with the parameter count.
In any case -- this is where the value returned by %PARMS comes from... 
 it comes from this parm_count field in the data structure.  The count 
of the number of parameters passed.
So that's how parameters work.  The key to understand is that these data 
structures are not really RPG data structures, and aren't predefined 
like an RPG data structure would be.  They're generated into memory 
on-the-fly.  The RPG sample is just to help you understand it.
Back to your example, you have something like this:
     D proc1           pr
     D   parm1                       10a   const options(*nopass)
     D   parm2                       10a   const options(*nopass)
     D   parm3                       10a   const options(*nopass)
When you call the procedure with 3 parameters, this is what the 
structure under the covers looks like:
      D ParmList        ds                  qualified
      D   opdesc                        *
      D   workarea                    16A
      D   parm1                         *
      D   parm2                         *
      D   parm3                         *
OpDesc will point to a descriptor structure like this:
     D DescList        ds                  qualified
     D   parm_count                  10i 0
None of the other fields are there, because you didn't code OPDESC.  The 
parm_count field will contain the number 3, because you passed 3 parameters.
Since the called routine (Proc1) is expecting the same 3 parameters, 
it'll generate the exact same structure in memory, and when you check 
%ADDR(PARM1), it'll give you the value that's in the "parm1" pointer of 
the structure above, etc.
When you only pass 1 parameter (even though there are 3 on the 
prototype, you don't use all three... you only pass one of them, as in 
your example) the caller generates the following:
      D ParmList        ds                  qualified
      D   opdesc                        *
      D   workarea                    16A
      D   parm1                         *
It passes the same DescList structure as before, except now the 
parm_count field is set to 1, because you passed only one parameter.
However, the procedure that was called (proc1) doesn't generate this 
shortened data structure.  It's not the one filling in the parameters... 
 it only knows what you have on your PI.  So it generates all 3 
parameters, like this:
      D ParmList        ds                  qualified
      D   opdesc                        *
      D   workarea                    16A
      D   parm1                         *
      D   parm2                         *
      D   parm3                         *
Since this is overlaying the same spot in memory that the caller 
provided, parm1 will give you correct results (since in this example, it 
was passed).  However, parm2 and parm3 won't -- they don't really exist, 
since they're overlaying memory that's beyond the caller's data 
structure!  Ouch.  If you try to access them, you'll get whatever 
happens to be in the memory AFTER the parameter list.
When you do %ADDR(PARM2), you're checking exactly that... you're 
retrieving the pointer for PARM2 from the above structure.  Hopefully 
you understand that this'll be whatever happens to be after the 
structure in memory.  Hopefully you'll see why that's a problem!
In some cases, you'll get "lucky" (or "unlucky" depending on your 
perspective) and the memory that follows the structure will happen to be 
set to the default value of x'00' for all bytes.  This will make the 
pointer look like *NULL, so your %ADDR() check will work.   However, in 
other circumstances, as you have already discovered, you will get 
something else that happens to be in that spot in memmory.
In your example, a prior call to the procedure happend to use that spot 
in memory as the address of a 2nd parameter.  On that subsequent call, 
the spot in memory was still there and still set to that pointer, so you 
managed to get the address of a variable that wasn't passed.
That's why RPG always passes the parm_count -- so you can detect the 
number of parameters passed.  (Though, I wish they had made this more 
dummy-proof!!  But that has to do with the design of the ILE 
environment, and not really the design of the RPG compiler.)
By contrast, options(*OMIT) always passes all of the parameters in the 
parameter list.  (unless, of course, you ALSO have options(*nopass) 
defined in addition to *OMIT).   Instead of leaving the parameter out of 
the generated structure, it still passes it, but puts *NULL in the 
pointer value.   That way, %ADDR()=*NULL will tell you if it was passed.
But, the flaw in that, is that *OMIT can only be used on fields passed 
by reference -- since the other ones don't pass pointers and therefore 
can't set the pointers to *NULL
Anyway.. this is taking way too long to explain.  Hope you found it 
useful or interesting.  If not, just take the "short answer" and make 
sure you check %PARMS (which is the same as the parm_count in my DS 
examples).
As an Amazon Associate we earn from qualifying purchases.