Add MAYBE modifier to ExtUtils::ParseXS - #24557
Conversation
leonerd
left a comment
There was a problem hiding this comment.
Overall this feels good, but a few small comments about trying to be clearer in the docs / code.
| $if_null .= "\n\t\tSTRLEN_length_of_\$var = 0;" if $self->{length_param}; | ||
|
|
||
| $init_template = <<END; | ||
| if (SvOK(\$arg)) { |
There was a problem hiding this comment.
Nothing has called SvGETMAGIC() at this point, so SvOK() may not be valid (fixing that doesn't seem pretty, unless we accept extra magic calls.)
There was a problem hiding this comment.
That's a good one. I think I've handled it everywhere I can now. There's no SvRV_nomg so I guess it doesn't matter enough in that corner.
There was a problem hiding this comment.
The existing XS should[1] SvGETMAGIC() before SvROK(), SvRV() anyway, but you still need the SvGETMAGIC() before the SvOK().
If I fix T_PTRREF to properly SvGETMAGIC():
[
"test MAYBE PTRREF",
Q(<<'EOF'),
|TYPEMAP: <<EOF
|myclass T_MYPTRREF
|INPUT
|T_MYPTRREF
| SvGETMAGIC($arg);
| if (SvROK($arg)) {
| IV tmp = SvIV((SV*)SvRV($arg));
| $var = INT2PTR($type,tmp);
| }
| else
| Perl_croak_nocontext("%s: %s is not a reference",
| ${$ALIAS?\q[GvNAME(CvGV(cv))]:\qq["$pname"]},
| "$var")
|EOF
|
|void
|foo(MAYBE myclass input)
EOF
[ 0, qr/fail me/, 'I want to see the code' ],
],
We get:
# XS_EUPXS(XS_Foo_foo)
# {
# dVAR; dXSARGS;
# if (items != 1)
# croak_xs_usage(cv, "input");
# {
# myclass input;
#
# SvGETMAGIC(ST(0));
# if (SvOK(ST(0))) {
# SvGETMAGIC(ST(0));
# if (SvROK(ST(0))) {
# IV tmp = SvIV_nomg((SV*)SvRV(ST(0)));
# input = INT2PTR(myclass,tmp);
# }
# else
# Perl_croak_nocontext("%s: %s is not a reference",
# "Foo::foo",
# "input")
# ;
# } else {
# input = NULL;;
# }
# ;
#
# foo(input);
# }
# XSRETURN_EMPTY;
# }'
Which has two problems:
- an extraneous SvGETMAGIC(ST(0));
- removed the magic from an SvIV() that wasn't ST(0)
I think it is safer to be extra magical just have extra magic calls.
[1] though lib/ExtUtils/typemap is painfully inconsistent about it, and thinking about this lead to tonycoz/imager#566
There was a problem hiding this comment.
I hadn't realized SvRV never does SvGETMAGIC, that does change things. Now it's always added.
There was a problem hiding this comment.
If I modify a match for the "test MAYBE with length" test to fail I see:
# XS_EUPXS(XS_Foo_foo)
# {
# dVAR; dXSARGS;
# if (items != 1)
# croak_xs_usage(cv, "input");
# {
# STRLEN STRLEN_length_of_input;
# size_t XSauto_length_of_input;
# char* input;
#
# SvGETMAGIC(ST(0));
# if (SvOK(ST(0))) {
# input = (char *)SvPV(ST(0), STRLEN_length_of_input)
# ;
# } else {
# input = NULL;
# STRLEN_length_of_input = 0;;
# }
# ;
#
# XSauto_length_of_input = STRLEN_length_of_input;
#
# foo(input, XSauto_length_of_input);
# }
# XSRETURN_EMPTY;
# }'
The code looks like you're trying to support this case, but matching against the closing ) means this still uses the magical SvPV() instead of the non-magical variant.
There was a problem hiding this comment.
though lib/ExtUtils/typemap is painfully inconsistent about it
Now #24589
2bbac35 to
67abbe1
Compare
|
|
||
| $self->{no_output} = 1 if $type =~ s/^NO_OUTPUT\s+//; | ||
|
|
||
| $self->{retval_sentinel} = defined($1) ? $1 : 'NULL' if $type =~ s/^\s* MAYBE (?: \( (-? \w+) \) )? \s*//x; |
There was a problem hiding this comment.
One thing that occurred to me this morning, was whether this should accept C++ namespace/class qualified names, ie. allow colons.
My ideal would be to accept an expression like MAYBE(Perl_nan()) or MAYBE(std::numeric_limits<NV>::quiet_NaN()) but I can see why you'd want to avoid that.
There was a problem hiding this comment.
One thing that occurred to me this morning, was whether this should accept C++ namespace/class qualified names, ie. allow colons.
I kind of doubt it does in practice, but we can always make it more permissive in the future.
c5dfeff to
43c8fe7
Compare
|
|
||
| if ($init_template && defined $sentinel) { | ||
| $init_template =~ s/^(?!\A)/\t/g; | ||
| $init_template =~ s/(Sv(?:IV|UV|PV(?:|byte|utf8)?))(_nolen|)(\(\$arg\))/$1_nomg$2$3/; |
There was a problem hiding this comment.
SvNV is missing and does have a _nomg variant.
There was a problem hiding this comment.
Something that occurred to me while I was out led to:
[
"test MAYBE byte",
Q(<<'EOF'),
|TYPEMAP: <<TM
|BYTE * T_BYTEV
|INPUT
|T_BYTEV
| $var = ($type)SvPVbyte_nolen($arg);
|TM
|void
|foobyte(MAYBE BYTE *input)
EOF
[ 0, qr/if \(SvOK\(ST\(0\)\)\)/, 'check for SvOK' ],
[ 0, qr/input = \(BYTE \*\)SvPVbyte_nomg_nolen\(ST\(0\)\)/ ],
[ 0, qr/xinput = NULL;/, 'falls back to setting to null'],
],
xinput to force a fail:
# XS_EUPXS(XS_Foo_foobyte)
# {
# dVAR; dXSARGS;
# if (items != 1)
# croak_xs_usage(cv, "input");
# {
# BYTE * input;
#
# SvGETMAGIC(ST(0));
# if (SvOK(ST(0))) {
# input = (BYTE *)SvPVbyte_nomg_nolen(ST(0))
# ;
# } else {
# input = NULL;;
# }
# ;
#
# foobyte(input);
# }
# XSRETURN_EMPTY;
# }'
# doesn't match '(?^:xinput = NULL;)'
but:
tony@venus:.../git/perl6$ git grep SvPVbyte_nomg_nolen
dist/ExtUtils-ParseXS/t/005-parse-parameters.t: [ 0, qr/input = \(BYTE \*\)SvPVbyte_nomg_nolen\(ST\(0\)\)/ ],
there is no such thing as SvPVbyte_nomg_nolen.
My original thought was that not all the various combinations of SvPV(byte|utf8)?(_nomg)?(_nolen)? have existed over the history of perls that EU::PXS needs to support.
There was a problem hiding this comment.
SvNV is missing and does have a
_nomgvariant.
D'oh!
There was a problem hiding this comment.
there is no such thing as SvPVbyte_nomg_nolen.
That is most annoying. I guess that needs to be SvPVbyte_nomg($arg, PL_na) then?
There was a problem hiding this comment.
SvTRUE() is also missing (encountered while looking for the versions below)
I guess that needs to be
SvPVbyte_nomg($arg, PL_na)then?
That looks right, but...
My original thought was that not all the various combinations of
SvPV(byte|utf8)?(_nomg)?(_nolen)?have existed over the history of perls that EU::PXS needs to support.
I think this is still an issue: the replacements need to be perl version dependent:
- SvPV(byte|utf8)_nomg were added in 5.31.3 (757fc32).
- SvNV_nomg() was added in 5.13.1 (6f1401d)
- SvPV_nomg_nolen() was added in v5.13.6-122-g71eb6d8cfb
- SvTRUE_nomg() was added in v5.13.5-120-g06c841cf64 (if you add SvTRUE())
- Sv(IV|UV)_nomg() were added in perl-5.8.0-3191-g891f9566d7 (which probably isn't an issue)
- SvPV_nomg was added in perl-5.6.0-3776-g8d6d96c1bf
- SvPV_nomg() was added in perl-5.005_02-822-g1fa8b10d8c
(git grep -S foo is my friend)
There was a problem hiding this comment.
Alternatively MAYBE could reject old perls.
There was a problem hiding this comment.
Alternatively MAYBE could reject old perls.
Or just require ppport.h on older perls?
There was a problem hiding this comment.
Or just require
ppport.hon older perls?
That's an option, though SvPVbyte_nomg() isn't defined by ppport.h, I haven't checked any others.
There was a problem hiding this comment.
Oh that's annoying. We may have to fix that. And in the mean time I guess it doesn't work on older perls.
0ad82b0 to
0a2a133
Compare
The code is currently put after short argument typemaps, but before long ones. This was fine when STRLEN_length_of_* was only set by short ones, but in the follow up commit it will be set by a longer one, so it needs to be moved after the long ones.
This adds the MAYBE modifier to arguments. This will map undef to the given sentinel (or NULL if none is given), and otherwise use the usual typemapping machinery.
This adds the MAYBE modifier to return values and output arguments. This will map undef to the given sentinel (or NULL if none is given), and otherwise use the usual typemapping machinery.
| | MAYBE char* foo() | ||
| EOF | ||
| [ 0, qr/if \(RETVAL == NULL\)/, '' ], | ||
| # [ 0, qr/asdf/, ''], |
There was a problem hiding this comment.
Yeah, I'll remove that
| typemap logic for any defined values. If an arguments it given to the C<MAYBE> | ||
| (e.g. C<MAYBE(-1)>) then that value is used as the sentinel, otherwise C<NULL> | ||
| is used. | ||
|
|
There was a problem hiding this comment.
This only describes input parameters ("mapping undef to NULL" etc). It should at least briefly mention output parameters. The prose added further up seems to just be about return values.
iabyn
left a comment
There was a problem hiding this comment.
Apart from the PADTMP performance issue, all my comments are small niggles, and I'm otherwise generally happy with this PR.
| # to change SvPV_nolen() to SvPV() or similar. | ||
| # | ||
| # The final assign should be deferred to come after all | ||
| # declarations. |
There was a problem hiding this comment.
I'd prefer this chunk of code comments to be left where it is. Just above, it's explained what set of code lines should be produced for a length(s), and now these comments explain where some of those lines are generated. Just change the 'The final assign' bit to say that that line is added near the end of this function.
| 'is_synthetic', # Bool: var like 'THIS': we pretend it was in the sig | ||
|
|
||
| # values derived from both the XSUB's signature and/or INPUT line | ||
| 'sentinel', # Str: The C constant expression to map undef to |
There was a problem hiding this comment.
The 'sentinel' field line should be added 2 lines earlier, before the 'values derived from both the XSUB's signature and/or INPUT line' comment, since you can't have a MAYBE in an INPUT line.
| ], | ||
|
|
||
| [ | ||
| "test MAYBE with length", |
There was a problem hiding this comment.
Presumably "MAYBE length(s)" an error. If so, there should be a test for it.
| } | ||
|
|
||
| return ($init_template, $eval_vars, 1); | ||
| return ($init_template, $eval_vars, $sentinel, 1); |
There was a problem hiding this comment.
Since lookup_input_typemap() now returns an extra value, the code comments at the top of the function should be updated to reflect this. I.e. the 'Returns ($expr, $eval_vars, $is_template)' bit plus an extra line a few lines further down describing the new value.
| SvPVutf8_nolen => 'SvPVutf8_nomg(%s, PL_na', | ||
| ); | ||
| if ($init_template && defined $sentinel) { | ||
| $init_template =~ s/^(?!\A)/\t/g; |
There was a problem hiding this comment.
I have no idea what the s/^(?!\A)/\t/g; is intended to do. Perhaps a comment at the end of it explaining it?
| # SV * targ = (PL_op->op_private & OPpENTERSUB_HASTARG) | ||
| # ? PAD_SV(PL_op->op_targ) : sv_newmortal() | ||
|
|
||
| if ( $pxs->{config_optimize} |
There was a problem hiding this comment.
This new section seems very sub-optimal. In the presence of a MAYBE return modifier, TARG is never used, and a so new mortal is created for each return (even if the sentinel value is never encountered).
| [ 0, qr/sv_setsv\(ST\(0\), &PL_sv_undef\)/, ''], | ||
| ], | ||
|
|
||
| [ |
There was a problem hiding this comment.
Really aught to have tests for OUTLIST and default parameters too.
| which allow you to return Perl's true and false values, or to return | ||
| an empty list. | ||
|
|
||
| Since ExtUtils::ParseXS 3.64, you can also use the C<MAYBE> modifier to |
There was a problem hiding this comment.
This new section doesn't actually say what the sentinel is for, or what values it can have. Perhaps something like:
If RETVAL is equal to the specified sentinel value (-1 in this example), then an undef-valued SV is returned instead. The sentinel is any simple C value. The value "NULL" is used if not specified.
| of C<int> is only meaningful for declaring C<RETVAL> and for doing the | ||
| autocall. | ||
|
|
||
| It can also be prefixed with C<MAYBE> or C<MAYBE(sentinel)>. In such an XSUB, |
There was a problem hiding this comment.
This section's title should be updated to include the 'MAYBE' word, so it's more likely to be found when looking at a contents page. e.g.
=head3 An XSUB's return type and the NO_OUTPUT and MAYBE keywords
The previous '=head2 An XSUB Declaration' list of examples also aught have the MAYBE keyword somewhere.
| foo(int a, b, char *c) | ||
| C_ARGS: a, c | ||
|
|
||
| =head3 XSUB parameters with sentinels |
There was a problem hiding this comment.
Again, this really needs 'MAYBE' in the section header, e.g.
=head3 MAYBE: XSUB parameters with sentinels.
This section also really needs a small code example.
|
A few further comments: presumably a return type of 'MAYBE void' should give an error (and be tested for)? The commit message for "ExtUtils::ParseXS: Add MAYBE support to return values and output" says "This will map undef to the given sentinel" which is the wrong way round for return/output values. Should the docs mention that in the presence of length(), the length also gets set to 0 if the sentinel is triggered? |
As I suggested here before, I've added a
MAYBEmodifier to arguments and return values. This will map undef to/from a sentinel value such asNULL, but otherwise use the typemap as usual.