Here's a test program that illustrates an issue with passing parameters from a wrapper function to a wrapped function. I know this used to work because I deployed a program to production that was functional. Not sure when this behavior changed, but had to modify this module to work like the fn'test_wrapper_fixed() function. That doesn't seem like it should be necessary?
Digging around through the archives, I've narrowed it down to somewhere between 6.5.1731.2 and 1737.0...
The top line difference is due to the bug "fix" in compiler edit 1047 (that stopped OUTPUTONLY parameters from receiving the value of the parameter passed in from the caller). But that's a different issue. I think the problem you're talking about is the second highlight, where a B,6 parameter (=1) is passed to a function receiving it as an X0, which then passes it to another function receiving it as both an X0 and a B6, with the value of the B6 somehow reverting to 0 rather than 1.
For what it's worth, the wrapper function is able to receive the B,6 value correctly, but in the process of passing it through as an X0 to the wrapped function, it loses its value.
Seems like a straightforward ( ) bug. I'll try to track it down...
How would this situation be affected with an ARG_PASSED(@changes) function?
Ideally, the parameter type shouldn't be x0, but has to be for the .NULL default value to be tested in the wrapped function.
Here would be the more desirable rewrite with actual types. This isn't currently support for .NULL, also I think as it stands the _wrapped() function when called from _wrapper() would always see the changes as passed. I'm not sure how solvable this is.
Code
FUNCTION fn'test_wrapper(changes=.NULL as b6:INPUTONLY) as f8
MAP1 func$,s,64,"fn'test_wrapper()"
trace.print func$ + " ", changes
.fn = fn'test_wrapped(changes=changes)
ENDFUNCTION
FUNCTION fn'test_wrapped(changes=.NULL as b6:INPUTONLY) as f8
MAP1 func$,s,64,"fn'test_wrapped()"
trace.print func$ + " ", changes
IF arg_passed(@changes) THEN
trace.print func$ + " changes passed changes=[0x"+Fn'Dec2Hex$(changes)+"] ", changes
ELSE
trace.print func$ + "changes not passed"
ENDIF
ENDFUNCTION
Last edited by Stephen Funkhouser; 19 Dec 2401:01 PM.
4. Param passing refinement: passing an X value to a numeric parameter type now performs a string-to-numeric conversion like you would get when passing an S value to a numeric type, or when assigning an X value to a numeric type. Previously it was doing a raw byte copy from the source to the receiving parameter.
This is another one of those cases where one person's bug fix may be another's bug creation. I don't remember the exact trigger for this, but I suspect it had to do with a historical limitation on the ability to pass DIMX arrays as parameters (i.e. the X0 was allowed by S0 was not). It may also relate to the fact that in the dichotomy between numbers and strings, X variables are grouped with S. They're stored on the string stack; you can use the [start;length] substring operators, the plus operator acts like concatenation, etc.
The problem can be reduced down to the following. You start by passing a B,6 parameter (b'changes) to a function receiving it as an X0. For better or worse, that works in the sense that the X0 variable receives a raw binary copy of the B6 variable passed. Then you pass it (as an X0 variable) to the second function receiving it as an X0, which also works, in the sense that the newly received variable is a raw copy of the passed variables. The problem occurs when you try to receive that X0 variable back into a new B6 (l'changes); that's where it ends up using a string-to-numeric conversion rather than a binary copy.
Code
MAP1 b'changes,b,6
CALL fn'test_wrapper_broke(changes=b'changes)
...
FUNCTION fn'test_wrapper_broke(changes=.NULL as x0:INPUTONLY) as f8
...
.fn = fn'test_wrapped(changes=changes)
ENDFUNCTION
FUNCTION fn'test_wrapped(changes=.NULL as x0:INPUTONLY) as f8
MAP1 l'changes,B,6
IF (changes # .NULL) THEN
XGETARG 1, l'changes
You could make an argument that numeric-to-X and X-to-numeric should be perfectly symmetrical. The counter-argument is that when the target is an X variable, a raw copy seems like the only logical option, considering that the concept of null termination doesn't exist for X variable. But when the target is a formatted numeric type, conversion to that type seems the most in line with the anything-goes rubric of parameter passing. But I guess it all depends on whether X is to be treated purely as raw, or basically like a string that allows any bytes. In any case, I'm reluctant to change it back, only to slowly re-discover the problems that were fixed by the original change.
One seemingly obvious solution (to me) would be to just overlay your l'changes (b,6) on top of the X changes, i.e.
Code
FUNCTION fn'test_wrapped(changes=.NULL as x0:INPUTONLY) as f8
MAP1 l'changes,B,6,@changes
But you can't overlay on top of a dynamic variable. So you'd have to first copy the received changes from the x0 variable to a fixed size one (or just give up on the x0 intermediate variables and make them all something fixed, perhaps x100).
Or, simply adjust the mapping of the final target variable (l'changes) to be a field within a structure, so that the parameter passing operation remains raw to raw, e.g.
Code
FUNCTION fn'test_wrapped(changes=.NULL as x0:INPUTONLY) as f8
MAP1 x'changes
MAP2 l'changes,B,6,@changes
XGETARG, 1, x'changes
I got sidetracked on this and haven't had a chance to go back to the original problem, so maybe that arg_passed() function or something like that might be another solution. (Maybe an optional flag on XGETARG telling it to do a raw copy rather than trying to convert anything to anything.)
If the arg_passed() was usable, an XGETARG flag would still need to be used to force some different behavior. Could we use numexpr()/strexpr() with XGETARG to force our chosen casting? Would it still need an option to use raw copy before the final cast?
I can't really see how numexpr()/strexpr() could work with XGETARG, since other than the first param (argument #), the remaining params are all 'lvalues', i.e. suitable for the left side of an assignment statement, i.e. variables, not expressions.
Given that we already have multiple variations of XGETARG, perhaps the cleanest would be to add a new XGETARGRAW? It would treat both the source and destination parameters as raw bytes, adjusting only for the size. But unlike the regular XGETARG which tries to do its best to produce a result that makes sense, the XGETARGRAW would be more like "you asked for it, you got it". And would probably only make sense if the target variable was either X0 or at least same physical size as the source.
I'm not sure though whether it should zero-out any extra bytes in the target. For example, if the caller passed an F,6 and you received it into an X,10, should it zero out the last 4 bytes or leave them untouched?
It's unclear to me how exactly/where XGETARGRAW would be used.
Do you use it in the wrapped function to set the b,6 directly, or do you have to set an x var and then assign the value to the ,b,6?
Code
MAP1 b'changes,b,6
CALL fn'test_wrapper_broke(changes=b'changes)
...
FUNCTION fn'test_wrapper_broke(changes=.NULL as x0:INPUTONLY) as f8
...
.fn = fn'test_wrapped(changes=changes)
ENDFUNCTION
FUNCTION fn'test_wrapped(changes=.NULL as x0:INPUTONLY) as f8
MAP1 l'changes,B,6
IF (changes # .NULL) THEN
XGETARGRAW 1, l'changes
I think zero'ing the x,10 probably makes sense, but I will have to defer to your judgment on that.
You would use it in the wrapped function, as shown immediately above, to defeat the type conversion that would otherwise occur when translating the X0 changes value passed into the B6 l'changes value received.
As for clearing the excess bytes in the received variable, I think we both agree.