Previous Thread
Next Thread
Print Thread
Wrapper function issue #37721 19 Dec 24 09:26 AM
Joined: Nov 2006
Posts: 2,233
S
Stephen Funkhouser Online Content OP
Member
OP Online Content
Member
S
Joined: Nov 2006
Posts: 2,233
Hi Jack,

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?

Feel free to reach out if you need clarification.

Thanks!

Attached Files
testfuncs.bas.gpg (2.52 KB, 1 downloads)
SHA1: 2ea180a25ffaef1b07b3d4843af08831e2ac01ad

Stephen Funkhouser
Diversified Data Solutions
Re: Wrapper function issue [Re: Stephen Funkhouser] #37722 19 Dec 24 11:20 AM
Joined: Jun 2001
Posts: 11,823
J
Jack McGregor Online Content
Member
Online Content
Member
J
Joined: Jun 2001
Posts: 11,823
Digging around through the archives, I've narrowed it down to somewhere between 6.5.1731.2 and 1737.0...

[Linked Image]

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 ( confused) bug. I'll try to track it down...

Re: Wrapper function issue [Re: Stephen Funkhouser] #37723 19 Dec 24 01:00 PM
Joined: Nov 2006
Posts: 2,233
S
Stephen Funkhouser Online Content OP
Member
OP Online Content
Member
S
Joined: Nov 2006
Posts: 2,233
That's what I thought.

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 24 01:01 PM.

Stephen Funkhouser
Diversified Data Solutions
Re: Wrapper function issue [Re: Stephen Funkhouser] #37724 19 Dec 24 05:22 PM
Joined: Jun 2001
Posts: 11,823
J
Jack McGregor Online Content
Member
Online Content
Member
J
Joined: Jun 2001
Posts: 11,823
I found the culprit -- 6.5.1735.0.4 (July 2023) -
Quote
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.)

Last edited by Jack McGregor; 19 Dec 24 06:16 PM.
Re: Wrapper function issue [Re: Stephen Funkhouser] #37725 20 Dec 24 07:33 AM
Joined: Nov 2006
Posts: 2,233
S
Stephen Funkhouser Online Content OP
Member
OP Online Content
Member
S
Joined: Nov 2006
Posts: 2,233
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?


Stephen Funkhouser
Diversified Data Solutions
Re: Wrapper function issue [Re: Stephen Funkhouser] #37726 20 Dec 24 08:41 AM
Joined: Jun 2001
Posts: 11,823
J
Jack McGregor Online Content
Member
Online Content
Member
J
Joined: Jun 2001
Posts: 11,823
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?

Re: Wrapper function issue [Re: Stephen Funkhouser] #37727 20 Dec 24 11:03 AM
Joined: Nov 2006
Posts: 2,233
S
Stephen Funkhouser Online Content OP
Member
OP Online Content
Member
S
Joined: Nov 2006
Posts: 2,233
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.


Stephen Funkhouser
Diversified Data Solutions
Re: Wrapper function issue [Re: Stephen Funkhouser] #37728 20 Dec 24 11:33 AM
Joined: Jun 2001
Posts: 11,823
J
Jack McGregor Online Content
Member
Online Content
Member
J
Joined: Jun 2001
Posts: 11,823
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.


Moderated by  Jack McGregor, Ty Griffin 

Powered by UBB.threads™ PHP Forum Software 7.7.3