Skip to content

Conversation

@t-a-k
Copy link
Contributor

@t-a-k t-a-k commented Nov 11, 2025

I found that some use of SvUOK() in conditional blocks like if (SvIOK(sv)) { ... } or if (SvIV_please_nomg(sv)) { ... } are redundant and can be safely replaced with SvIsUV, as SvUOK(sv) is essentially SvIOK(sv) && SvIsUV(sv) and SvIOK(sv) is already asserted by the outer conditional.
This change will save a few code size and runtime CPU cycles, because many CPUs can do single-bit tests like SvIsUV in fewer instructions than multi-bit tests like SvUOK.


  • This set of changes does not require a perldelta entry.

This would make it easier to receive the result with `bool` type.

Note that (implicit) casting to C99 `_Bool` automatically converts
any nonzero scalar values to 1, so that this change is technically
not necessary in C99 which perl now requires to compile.  But I think
it will keep the code less surprising.
SvUOK(x) inside a block guarded by SvIV_please_nomg(x) can be
replaced by SvIsUV(x) because SvIV_please_nomg implies SvIOK.
This will save a few code size and runtime CPU cycles, because
many CPUs can do single-bit tests like SvIsUV in fewer instructions
than multi-bit tests like SvUOK.
Because SvUOK is essentially (SvIOK && SvIsUV), SvUOK is redundant
in the block where SvIOK is guaranteed to hold true.
(SvPVX_const(sv) == PL_No))

#define SvIsUV(sv) (SvFLAGS(sv) & SVf_IVisUV)
#define SvIsUV(sv) ((SvFLAGS(sv) & SVf_IVisUV) != 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn't seem necessary - surely the truthiness is already being returned?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, this change is technically not necessary in C99, but I think this will help keeping the code less surprising.

SvFLAGS(sv) & SVf_IVisUV takes 0 or 0x80000000 and assigning this result into bool value (which is not guaranteed to be as wide as 0x80000000) is not intuitive I think (although this is not real problem in C99).

@richardleach
Copy link
Contributor

@t-a-k - did you observe a decrease in code size or runtime performance improvement? (I haven't built perl from this PR and compared it to blead yet.)

A quick play with godbolt.org suggests that a perl built with gcc < 15 or clang < 10 might not see any instruction or runtime throughput improvements.

On recent versions of gcc (15.1) & clang (21.1.0), a simple function analogous to SvUOK(sv) compiles to something like:

        mov     eax, edi
        shr     eax, 8
        and     eax, 1

whereas a SvIsUV(sv) case compiles to something like:

        shr     edi, 31
        mov     eax, edi

so there is theoretically a single instruction saving there.

Putting the SvIOK(sv) test in front of the SvUOK or SvIsUV(sv) test changes things though. Both very recent compiler versions output the same three instructions as before for the SvUOK case:

        mov     eax, edi
        shr     eax, 8
        and     eax, 1

and and three different instructions for the isUV case:

        not     edi
        and     edi, -2147483392
        sete    al

i.e. The compilers (both using -O2) combined the two bit tests in both of the simplistic examples.

gcc 14.2 produces the same instructions as before for the simple SvUOK case, but actually does worse for the SvIsUV case:

        mov     edx, edi
        xor     eax, eax
        shr     edx, 31
        and     edi, 256
        cmovne  eax, edx

@t-a-k
Copy link
Contributor Author

t-a-k commented Nov 12, 2025

On recent versions of gcc (15.1) & clang (21.1.0), a simple function analogous to SvUOK(sv) compiles to something like:

        mov     eax, edi
        shr     eax, 8
        and     eax, 1

Is this really SvUOK(sv)? SvUOK(sv) should check bit 31 (SVf_IVisUV) of SvFLAGS(sv) (assumed as edi here), but this only extracts bit 8 (SVf_IOK) of edi so this looks like SvIOK(sv).

In my observation on Debian 13 with gcc 14.2.0,

bool auvok = SvUOK(svl);
...
if (auvok) ...

compiles to something like (sorry for AT&T syntax)

	and	$0x80000100,%eax	// SVf_IVisUV | SVf_IOK
	cmp	$0x80000100,%eax
	je	...

or

	not	%r8d
	and	$0x80000100,%r8d
	je	...

whereas a SvIsUV(sv) case compiles something like

	test	%ecx,%ecx
	js	...

so the conclusion remains the same, there is theoretically a single instruction saving. In addition, SvIsUV(sv) case doesn't need lengthy immediate (0x80000100) so that this is slightly more beneficial on code size. (I think this is also beneficial for other CPU architectures like AArch64 where complex immediate like 0x80000100 cannot be encoded in a single instruction.)

With this change, overall decrease in perl code size is 16 bytes on my build:

% size */perl
   text	   data	    bss	    dec	    hex	filename
3962237	  76448	  28592	4067277	 3e0fcd	blead/perl
3962221	  76448	  28592	4067261	 3e0fbd	use-SvIsUV/perl

More specifically, pp_multiply, pp_divide, pp_pow and do_ncmp functions got ~16 bytes smaller for each, but pp_add and pp_subtract got slightly bigger. This seems because this change will affect compiler optimizations by reducing register usage.

@richardleach
Copy link
Contributor

Is this really SvUOK(sv)?

I do apologise, the brackets around the flags in my SvUOK-esque mockup were incorrect. Correcting them, it looks like for an SvIOK check immediately followed by either an SvUOK or a SvIsUV check:

  • clang produces the same instructions and has done so for a loooong time.
  • gcc prior to 15.1 does produce more instructions for the SvUOK case than it does for the SvIsUV case.

So the pp.c & pp_hot.c changes LGTM.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants