Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PATCH] refactor and fix leak in const_av_xsub #13510

Open
p5pRT opened this issue Jan 6, 2014 · 20 comments
Open

[PATCH] refactor and fix leak in const_av_xsub #13510

p5pRT opened this issue Jan 6, 2014 · 20 comments

Comments

@p5pRT
Copy link

p5pRT commented Jan 6, 2014

Migrated from rt.perl.org#120939 (status was 'open')

Searchable as RT120939$

@p5pRT
Copy link
Author

p5pRT commented Jan 6, 2014

From @bulk88

Created by @bulk88

This patch might need more refinement, specifically I think the number
of asserts is excessive, I used them when I developed the patch and was
having "Bizarre copy of" problems, but I think they are useless now. The
function dropped from 0x122 bytes of machine code to 0x10c on my VC 2003
32 bits -O1 build. The TARG usage "bloated" the function a little bit
since sv_2mortal(newSViv()) takes less machine code. It is an acceptable
tradeoff AFAIK.

This ML thread also covers this patch
http​://www.nntp.perl.org/group/perl.perl5.porters/2014/01/msg211182.html
. The G_SCALAR branch is never executed by a make test. FC's code in the
ML thread did go through the G_SCALAR branch, but I don't know enough to
write a test. ../cpan/NEXT/t/stringify.t and op/gv.t execute the G_ARRAY
branch of this xsub.
------------------------------------
  assert(av);
  #ifndef DEBUGGING
  if (!av) {
- XSRETURN(0);
+ goto end;
  }
  #endif
------------------------------------
I didn't consider the properness of the above code it came from
http​://perl5.git.perl.org/perl.git/commit/6f1b3ab07ea077b8191fe508269d778bc64a5f50
as the entire const_av_xsub function.

Perl Info

Flags:
        category=core
        severity=low

Site configuration information for perl 5.19.7:

Configured by Owner at Thu Nov 28 02:32:44 2013.

Summary of my perl5 (revision 5 version 19 subversion 7) configuration:
      Derived from: 8f47723e28b75530b743941cdd8b07f849ec48e2
      Ancestor: 1061065f7a09399eefb50e9a035502621722bcc0
      Platform:
        osname=MSWin32, osvers=5.1, archname=MSWin32-x86-multi-thread
        uname=''
        config_args='undef'
        hint=recommended, useposix=true, d_sigaction=undef
        useithreads=define, usemultiplicity=define
        useperlio=define, d_sfio=undef, uselargefiles=define, usesocks=undef
        use64bitint=undef, use64bitall=undef, uselongdouble=undef
        usemymalloc=n, bincompat5005=undef
      Compiler:
        cc='cl', ccflags ='-nologo -GF -W3 -O1 -MD -Zi -DNDEBUG -G7 -GL
-DWIN32 -D_CONSOLE -DNO_STRICT  -DPERL_TEXTMODE_SCRIPTS
-DPERL_HASH_FUNC_ONE_AT_A_TIME -DPERL_IMPLICIT_CONTEXT
-DPERL_IMPLICIT_SYS -DUSE_PERLIO -D_USE_32BIT_TIME_T',
        optimize='-O1 -MD -Zi -DNDEBUG -G7 -GL',
        cppflags='-DWIN32'
        ccversion='13.10.6030', gccversion='', gccosandvers=''
        intsize=4, longsize=4, ptrsize=4, doublesize=8, byteorder=1234
        d_longlong=undef, longlongsize=8, d_longdbl=define, longdblsize=8
        ivtype='long', ivsize=4, nvtype='double', nvsize=8, Off_t='__int64',
lseeksize=8
        alignbytes=8, prototype=define
      Linker and Libraries:
        ld='link', ldflags ='-nologo -nodefaultlib -debug -opt:ref,icf
-ltcg  -libpath:"c:\perl519\lib\CORE"  -machine:x86'
        libpth="C:\Program Files\Microsoft Visual Studio .NET 2003\VC7\lib"
        libs=oldnames.lib kernel32.lib user32.lib gdi32.lib winspool.lib
comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib
netapi32.lib uuid.lib ws2_32.lib mpr.lib winmm.lib  version.lib
odbc32.lib odbccp32.lib comctl32.lib msvcrt.lib
        perllibs=oldnames.lib kernel32.lib user32.lib gdi32.lib
winspool.lib  comdlg32.lib advapi32.lib shell32.lib ole32.lib
oleaut32.lib  netapi32.lib uuid.lib ws2_32.lib mpr.lib winmm.lib
version.lib odbc32.lib odbccp32.lib comctl32.lib msvcrt.lib
        libc=msvcrt.lib, so=dll, useshrplib=true, libperl=perl519.lib
        gnulibc_version=''
      Dynamic Linking:
        dlsrc=dl_win32.xs, dlext=dll, d_dlsymun=undef, ccdlflags=' '
        cccdlflags=' ', lddlflags='-dll -nologo -nodefaultlib -debug
-opt:ref,icf -ltcg  -libpath:"c:\perl519\lib\CORE"  -machine:x86'

Locally applied patches:
        uncommitted-changes
        8f47723e28b75530b743941cdd8b07f849ec48e2


@INC for perl 5.19.7:
        C:/perl519/site/lib
        C:/perl519/lib
        .


Environment for perl 5.19.7:
        HOME (unset)
        LANG (unset)
        LANGUAGE (unset)
        LD_LIBRARY_PATH (unset)
        LOGDIR (unset)
        PATH=C:\perl519\bin;C:\Program Files\Microsoft Visual Studio .NET
2003\Common7\IDE;C:\Program Files\Microsoft Visual Studio .NET
2003\VC7\BIN;C:\Program Files\Microsoft Visual Studio .NET
2003\Common7\Tools;C:\Program Files\Microsoft Visual Studio .NET
2003\Common7\Tools\bin\prerelease;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\system32\wbem;
        PERL_BADLANG (unset)
        SHELL (unset)






@p5pRT
Copy link
Author

p5pRT commented Jan 6, 2014

From @bulk88

0001-refactor-and-fix-leak-in-const_av_xsub.patch
From f7b29e4d38e71e3a6df27fc557c161333883d8e8 Mon Sep 17 00:00:00 2001
From: Daniel Dragan <bulk88@hotmail.com>
Date: Sun, 5 Jan 2014 22:21:04 -0500
Subject: [PATCH] refactor and fix leak in const_av_xsub

-calculating items takes more opcodes and memreads than using the mark
 that is already there, we don't care how many incoming args there are
-replace XSRETURN since SP is already 0 ret args, make the function
 var ax and ST() free, just goto the PUTBACK and return
-read AvFILLp only once, save it to var
-do only 1 EXTEND call, either with 1, or 1+av's last index, branchfree
-both the Copy needs a SP++ed, PUSHs will also SP++, factor it out, then
 then use TOPs in G_SCALAR branch
-use TARG in the G_SCALAR branch, write TARG to Perl stack before any calls
 so TARG doesn't use a non-vol reg
-in the original code in G_SCALAR, newSViv was leaked because it wasn't
 mortaled
-in the G_ARRAY branch, remove the multiple AvFILLp reads

See also "leak in const_av_xsub?"
Message ID: BLU0-SMTP1979C3A6BA69B12AADC7936DFB40@phx.gbl
---
 op.c |   43 +++++++++++++++++++++++++++++++++----------
 1 files changed, 33 insertions(+), 10 deletions(-)

diff --git a/op.c b/op.c
index fc5742f..4135eb2 100644
--- a/op.c
+++ b/op.c
@@ -12333,23 +12333,46 @@ const_av_xsub(pTHX_ CV* cv)
     dVAR;
     dXSARGS;
     AV * const av = MUTABLE_AV(XSANY.any_ptr);
-    SP -= items;
+    U32 gimme_v;
+    assert((SP - items) == MARK);
+    SP = MARK; /* remove all incoming args, more efficient than items */
     assert(av);
 #ifndef DEBUGGING
     if (!av) {
-	XSRETURN(0);
+	goto end;
     }
 #endif
     if (SvRMAGICAL(av))
 	Perl_croak(aTHX_ "Magical list constants are not supported");
-    if (GIMME_V != G_ARRAY) {
-	EXTEND(SP, 1);
-	ST(0) = newSViv((IV)AvFILLp(av)+1);
-	XSRETURN(1);
-    }
-    EXTEND(SP, AvFILLp(av)+1);
-    Copy(AvARRAY(av), &ST(0), AvFILLp(av)+1, SV *);
-    XSRETURN(AvFILLp(av)+1);
+    gimme_v = GIMME_V;
+    if(gimme_v != G_VOID) {
+	I32 stack_grow = 1;
+	const I32 avlastindex = AvFILLp(av); /* 0 based */
+	I32 avitems; /* 1 based */
+	stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch free */
+	if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);
+	EXTEND(SP, stack_grow);
+	SP += 1;
+	avitems = avlastindex+1;
+	if(gimme_v != G_ARRAY) {
+	    dXSTARG;
+	    SETs(TARG);
+	    sv_setiv(TARG, (IV)avitems);
+	    assert(ST(0) == TARG);
+	}
+	else {
+	    SV ** const dstsp = SP;
+	    assert(gimme_v == G_ARRAY);
+	    assert(&ST(0) == dstsp);
+	    assert((AvFILLp(av)+1) == avlastindex+1);
+	    SP += avlastindex;
+	    assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
+	    Copy(AvARRAY(av), dstsp, avitems, SV *);
+	}
+    }
+    end:
+    PUTBACK;
+    return;
 }
 
 /*
-- 
1.7.9.msysgit.0

@p5pRT
Copy link
Author

p5pRT commented Jan 6, 2014

From @cpansprout

On Sun Jan 05 19​:35​:47 2014, bulk88 wrote​:

This is a bug report for perl from bulk88@​hotmail.com,
generated with the help of perlbug 1.39 running under perl 5.19.7.

-----------------------------------------------------------------
[Please describe your issue here]

This patch might need more refinement, specifically I think the number
of asserts is excessive, I used them when I developed the patch and
was
having "Bizarre copy of" problems, but I think they are useless now.
The
function dropped from 0x122 bytes of machine code to 0x10c on my VC
2003
32 bits -O1 build. The TARG usage "bloated" the function a little bit
since sv_2mortal(newSViv()) takes less machine code. It is an
acceptable
tradeoff AFAIK.

Thank you for using TARG. As an infrequent XS user, I often forget about it. Though it may make const_av_xsub slightly bigger, it should speed things up overall, since I image dXSTARG is faster than newSV.

Please use SSize_t, not I32, for stack and array offsets. (I know there are still I32s here and there for stack offsets. It’s an incremental task.)

As for the number of assertions, while I agree there may be too many, it doesn’t actually hurt. There is no general rule for how many to add. Usually I add them when the code feels a bit fragile and they make me feel more comfortable, or if they document the code’s assumptions. I leave it to you.

This ML thread also covers this patch
http​://www.nntp.perl.org/group/perl.perl5.porters/2014/01/msg211182.html
. The G_SCALAR branch is never executed by a make test. FC's code in
the
ML thread did go through the G_SCALAR branch, but I don't know enough
to
write a test. ../cpan/NEXT/t/stringify.t and op/gv.t execute the
G_ARRAY
branch of this xsub.
------------------------------------
assert(av);
#ifndef DEBUGGING
if (!av) {
- XSRETURN(0);
+ goto end;
}
#endif
------------------------------------
I didn't consider the properness of the above code it came from
http​://perl5.git.perl.org/perl.git/commit/6f1b3ab07ea077b8191fe508269d778bc64a5f50
as the entire const_av_xsub function.

Pure paranoia. It should never happen (hence the assert), but it is an easy mistake to make. So I chose not to be pedantic about it under non-debugging builds.

--

Father Chrysostomos

@p5pRT
Copy link
Author

p5pRT commented Jan 6, 2014

The RT System itself - Status changed from 'new' to 'open'

@p5pRT
Copy link
Author

p5pRT commented Jan 12, 2014

From @tonycoz

On Sun Jan 05 19​:35​:47 2014, bulk88 wrote​:

This patch might need more refinement, specifically I think the number
of asserts is excessive, I used them when I developed the patch and
was
having "Bizarre copy of" problems, but I think they are useless now.
The
function dropped from 0x122 bytes of machine code to 0x10c on my VC
2003
32 bits -O1 build. The TARG usage "bloated" the function a little bit
since sv_2mortal(newSViv()) takes less machine code. It is an
acceptable
tradeoff AFAIK.

I'm fairly happy with this patch, but I'm not sure that​:

+ I32 stack_grow = 1;
+ const I32 avlastindex = AvFILLp(av); /* 0 based */
+ I32 avitems; /* 1 based */
+ stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch free */

is a speed or maintenance win (it is a space win on x86_64 and presumably x86_32).

On 32/64-bit x86 this avoids a branch by using the setCC instruction, but then we later check the exact same condition again.

Tony

@p5pRT
Copy link
Author

p5pRT commented Jan 17, 2014

From @bulk88

On Sun Jan 12 14​:26​:57 2014, tonyc wrote​:

I'm fairly happy with this patch, but I'm not sure that​:

+ I32 stack_grow = 1;
+ const I32 avlastindex = AvFILLp(av); /* 0 based */
+ I32 avitems; /* 1 based */
+ stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch
free */

is a speed or maintenance win (it is a space win on x86_64 and
presumably x86_32).

On 32/64-bit x86 this avoids a branch by using the setCC instruction,
but then we later check the exact same condition again.

Where specifically is it checked again?

--
bulk88 ~ bulk88 at hotmail.com

@p5pRT
Copy link
Author

p5pRT commented Jan 19, 2014

From @tonycoz

On Fri Jan 17 00​:38​:41 2014, bulk88 wrote​:

On Sun Jan 12 14​:26​:57 2014, tonyc wrote​:

I'm fairly happy with this patch, but I'm not sure that​:

+ I32 stack_grow = 1;
+ const I32 avlastindex = AvFILLp(av); /* 0 based */
+ I32 avitems; /* 1 based */
+ stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch
free */

is a speed or maintenance win (it is a space win on x86_64 and
presumably x86_32).

On 32/64-bit x86 this avoids a branch by using the setCC instruction,
but then we later check the exact same condition again.

Where specifically is it checked again?

+ gimme_v = GIMME_V;
+ if(gimme_v != G_VOID) {
+ I32 stack_grow = 1;
+ const I32 avlastindex = AvFILLp(av); /* 0 based */
+ I32 avitems; /* 1 based */
+ stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch free */
+ if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);
+ EXTEND(SP, stack_grow);
+ SP += 1;
+ avitems = avlastindex+1;

It's checked again here​:
+ if(gimme_v != G_ARRAY) {
+ dXSTARG;

Tony

@p5pRT
Copy link
Author

p5pRT commented Jan 22, 2014

From @bulk88

On Sun Jan 12 14​:26​:57 2014, tonyc wrote​:

On Sun Jan 05 19​:35​:47 2014, bulk88 wrote​:

This patch might need more refinement, specifically I think the
number
of asserts is excessive, I used them when I developed the patch and
was
having "Bizarre copy of" problems, but I think they are useless now.
The
function dropped from 0x122 bytes of machine code to 0x10c on my VC
2003
32 bits -O1 build. The TARG usage "bloated" the function a little bit
since sv_2mortal(newSViv()) takes less machine code. It is an
acceptable
tradeoff AFAIK.

I'm fairly happy with this patch, but I'm not sure that​:

+ I32 stack_grow = 1;
+ const I32 avlastindex = AvFILLp(av); /* 0 based */
+ I32 avitems; /* 1 based */
+ stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch
free */

is a speed or maintenance win (it is a space win on x86_64 and
presumably x86_32).

On 32/64-bit x86 this avoids a branch by using the setCC instruction,
but then we later check the exact same condition again.

I tried saving the result of gimme_v == G_ARRAY into a C auto and then testing truthfulness of the C auto later, the byte difference was 2-5 more asm bytes and an opcode or 2 more. bool was a byte or 2 more than using I32 for the saved result, since there was a movsx op if I used a bool. At the branch I saw no difference in the asm opcode between testing for literal 0 (saved gimme_v == G_ARRAY) and testing for literal 3 (G_ARRAY). It is a 3 byte cmp op with last byte being a literal byte in both cases. Therefore saving the result of gimme_v == G_ARRAY into a C auto is useless. I slightly cleaned up the asserts in the new patch.

--
bulk88 ~ bulk88 at hotmail.com

@p5pRT
Copy link
Author

p5pRT commented Jan 22, 2014

From @bulk88

0001-refactor-and-fix-leak-in-const_av_xsub.patch
From 6348869c9b4cf36c158f9b5d9e44a3be63468d2f Mon Sep 17 00:00:00 2001
From: Daniel Dragan <bulk88@hotmail.com>
Date: Wed, 22 Jan 2014 04:22:38 -0500
Subject: [PATCH] refactor and fix leak in const_av_xsub

-calculating items takes more opcodes and memreads than using the mark
 that is already there, we don't care how many incoming args there are
-replace XSRETURN since SP is already 0 ret args, make the function
 var ax and ST() free, just goto the PUTBACK and return
-read AvFILLp only once, save it to var
-do only 1 EXTEND call, either with 1, or 1+av's last index, branchfree
-both the Copy needs a SP++ed, PUSHs will also SP++, factor it out, then
 then use TOPs in G_SCALAR branch
-use TARG in the G_SCALAR branch, write TARG to Perl stack before any calls
 so TARG doesn't use a non-vol reg
-in the original code in G_SCALAR, newSViv was leaked because it wasn't
 mortaled
-in the G_ARRAY branch, remove the multiple AvFILLp reads

See also "leak in const_av_xsub?"
Message ID: BLU0-SMTP1979C3A6BA69B12AADC7936DFB40@phx.gbl
---
 op.c |   41 +++++++++++++++++++++++++++++++----------
 1 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/op.c b/op.c
index a31e1af..c09c011 100644
--- a/op.c
+++ b/op.c
@@ -12349,23 +12349,44 @@ const_av_xsub(pTHX_ CV* cv)
     dVAR;
     dXSARGS;
     AV * const av = MUTABLE_AV(XSANY.any_ptr);
-    SP -= items;
+    U32 gimme_v;
+    assert((SP - items) == MARK);
+    SP = MARK; /* remove all incoming args, more efficient than items */
     assert(av);
 #ifndef DEBUGGING
     if (!av) {
-	XSRETURN(0);
+	goto end;
     }
 #endif
     if (SvRMAGICAL(av))
 	Perl_croak(aTHX_ "Magical list constants are not supported");
-    if (GIMME_V != G_ARRAY) {
-	EXTEND(SP, 1);
-	ST(0) = newSViv((IV)AvFILLp(av)+1);
-	XSRETURN(1);
-    }
-    EXTEND(SP, AvFILLp(av)+1);
-    Copy(AvARRAY(av), &ST(0), AvFILLp(av)+1, SV *);
-    XSRETURN(AvFILLp(av)+1);
+    gimme_v = GIMME_V;
+    if(gimme_v != G_VOID) {
+	I32 stack_grow = 1;
+	const I32 avlastindex = AvFILLp(av); /* 0 based */
+	I32 avitems; /* 1 based */
+	stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch free */
+	if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);
+	EXTEND(SP, stack_grow);
+	SP += 1;
+	avitems = avlastindex+1;
+	if(gimme_v != G_ARRAY) {
+	    dXSTARG;
+	    SETs(TARG);
+	    assert(ST(0) == TARG);
+	    sv_setiv(TARG, (IV)avitems);
+	}
+	else {
+	    SV ** const dstsp = SP;
+	    assert(gimme_v == G_ARRAY && &ST(0) == dstsp);
+	    SP += avlastindex;
+	    assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
+	    Copy(AvARRAY(av), dstsp, avitems, SV *);
+	}
+    }
+    end:
+    PUTBACK;
+    return;
 }
 
 /*
-- 
1.7.9.msysgit.0

@p5pRT
Copy link
Author

p5pRT commented Feb 12, 2014

From @tonycoz

On Wed Jan 22 01​:31​:11 2014, bulk88 wrote​:

I tried saving the result of gimme_v == G_ARRAY into a C auto and then
testing truthfulness of the C auto later, the byte difference was 2-5
more asm bytes and an opcode or 2 more. bool was a byte or 2 more than
using I32 for the saved result, since there was a movsx op if I used a
bool. At the branch I saw no difference in the asm opcode between
testing for literal 0 (saved gimme_v == G_ARRAY) and testing for
literal 3 (G_ARRAY). It is a 3 byte cmp op with last byte being a
literal byte in both cases. Therefore saving the result of gimme_v ==
G_ARRAY into a C auto is useless. I slightly cleaned up the asserts in
the new patch.

Smaller code is nice, but it's not what we generally optimize for. I think (and I welcome dissenting views) that something like the following would be better​:

  static void
  const_av_xsub(pTHX_ CV* cv)
  {
  dVAR;
  dXSARGS;
  AV * const av = MUTABLE_AV(XSANY.any_ptr);
  SP = MARK;
  U32 gimme_v;
  assert(av);
  #ifndef DEBUGGING
  if (!av) {
  goto end;
  }
  #endif
  if (SvRMAGICAL(av))
  Perl_croak(aTHX_ "Magical list constants are not supported");
 
  gimme_v = GIMME_V;
  if (LIKELY(gimme_v != G_VOID)) {
  const SSize_t avitems = AvFILLp(av)+1;
  if (UNLIKELY(gimme_v != G_ARRAY)) {
  dXSTARG;
  EXTEND(SP, 1);
  SP += 1;
  SETs(TARG);
  assert(ST(0) == TARG);
  sv_setiv(TARG, (IV)avitems);
  }
  else {
  SV ** const dstsp = SP;
  assert(gimme_v == G_ARRAY && &ST(0) == dstsp);
  EXTEND(SP, avitems)
  SP += avitems;
  assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
  Copy(AvARRAY(av), dstsp, avitems, SV *);
  }
  }
  end​:
  PUTBACK;
  return;
  }

Differences from yours​:

- use SSize_t for avitems, xav_fill is now SSize_t

- use UNLIKELY so the scalar case is put with the other unlikely code (and optimize for branch predition)

- avoid doing the same gimme test twice, which could confuse a reader

Tony

@p5pRT
Copy link
Author

p5pRT commented Mar 10, 2014

From @bulk88

On Tue Feb 11 22​:16​:17 2014, tonyc wrote​:

On Wed Jan 22 01​:31​:11 2014, bulk88 wrote​:

I tried saving the result of gimme_v == G_ARRAY into a C auto and
then
testing truthfulness of the C auto later, the byte difference was 2-5
more asm bytes and an opcode or 2 more. bool was a byte or 2 more
than
using I32 for the saved result, since there was a movsx op if I used
a
bool. At the branch I saw no difference in the asm opcode between
testing for literal 0 (saved gimme_v == G_ARRAY) and testing for
literal 3 (G_ARRAY). It is a 3 byte cmp op with last byte being a
literal byte in both cases. Therefore saving the result of gimme_v ==
G_ARRAY into a C auto is useless. I slightly cleaned up the asserts
in
the new patch.

Smaller code is nice, but it's not what we generally optimize for. I
think (and I welcome dissenting views) that something like the
following would be better​:

static void
const_av_xsub(pTHX_ CV* cv)
{
dVAR;
dXSARGS;
AV * const av = MUTABLE_AV(XSANY.any_ptr);
SP = MARK;
U32 gimme_v;
assert(av);
#ifndef DEBUGGING
if (!av) {
goto end;
}
#endif
if (SvRMAGICAL(av))
Perl_croak(aTHX_ "Magical list constants are not supported");

gimme_v = GIMME_V;
if (LIKELY(gimme_v != G_VOID)) {
const SSize_t avitems = AvFILLp(av)+1;
if (UNLIKELY(gimme_v != G_ARRAY)) {
dXSTARG;
EXTEND(SP, 1);
SP += 1;
SETs(TARG);
assert(ST(0) == TARG);
sv_setiv(TARG, (IV)avitems);
}
else {
SV ** const dstsp = SP;
assert(gimme_v == G_ARRAY && &ST(0) == dstsp);
EXTEND(SP, avitems)
SP += avitems;
assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
Copy(AvARRAY(av), dstsp, avitems, SV *);
}
}
end​:
PUTBACK;
return;
}

Differences from yours​:

- use SSize_t for avitems, xav_fill is now SSize_t

fine

- use UNLIKELY so the scalar case is put with the other unlikely code
(and optimize for branch predition)

fine

- avoid doing the same gimme test twice, which could confuse a reader

Now there are 2 EXTENDs which are quite fat (for C) to do, scaling, 4 pushes, comparison, reading interp globals. My code has just 1 EXTEND call, which I like.

Since you fixed the leak a while ago, the last patch of mine fails to apply, I updated it to continue to discussion and see how your changed appear ontop, so it isn't the final one. I also made a patch with your changed dropped ontop of my work to see how it looks. I'll incorporating some of your changes later.

--
bulk88 ~ bulk88 at hotmail.com

@p5pRT
Copy link
Author

p5pRT commented Mar 10, 2014

From @bulk88

0001-refactor-and-fix-leak-in-const_av_xsub.patch
From 33ad0766ba32471307a83e78caeffa3087498692 Mon Sep 17 00:00:00 2001
From: Daniel Dragan <bulk88@hotmail.com>
Date: Wed, 22 Jan 2014 04:22:38 -0500
Subject: [PATCH 1/2] refactor and fix leak in const_av_xsub

-calculating items takes more opcodes and memreads than using the mark
 that is already there, we don't care how many incoming args there are
-replace XSRETURN since SP is already 0 ret args, make the function
 var ax and ST() free, just goto the PUTBACK and return
-read AvFILLp only once, save it to var
-do only 1 EXTEND call, either with 1, or 1+av's last index, branchfree
-both the Copy needs a SP++ed, PUSHs will also SP++, factor it out, then
 then use TOPs in G_SCALAR branch
-use TARG in the G_SCALAR branch, write TARG to Perl stack before any calls
 so TARG doesn't use a non-vol reg
-in the original code in G_SCALAR, newSViv was leaked because it wasn't
 mortaled
-in the G_ARRAY branch, remove the multiple AvFILLp reads

See also "leak in const_av_xsub?"
Message ID: BLU0-SMTP1979C3A6BA69B12AADC7936DFB40@phx.gbl
---
 op.c |   41 +++++++++++++++++++++++++++++++----------
 1 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/op.c b/op.c
index a6488b0..c7b846c 100644
--- a/op.c
+++ b/op.c
@@ -12531,23 +12531,44 @@ const_av_xsub(pTHX_ CV* cv)
     dVAR;
     dXSARGS;
     AV * const av = MUTABLE_AV(XSANY.any_ptr);
-    SP -= items;
+    U32 gimme_v;
+    assert((SP - items) == MARK);
+    SP = MARK; /* remove all incoming args, more efficient than items */
     assert(av);
 #ifndef DEBUGGING
     if (!av) {
-	XSRETURN(0);
+	goto end;
     }
 #endif
     if (SvRMAGICAL(av))
 	Perl_croak(aTHX_ "Magical list constants are not supported");
-    if (GIMME_V != G_ARRAY) {
-	EXTEND(SP, 1);
-	ST(0) = sv_2mortal(newSViv((IV)AvFILLp(av)+1));
-	XSRETURN(1);
-    }
-    EXTEND(SP, AvFILLp(av)+1);
-    Copy(AvARRAY(av), &ST(0), AvFILLp(av)+1, SV *);
-    XSRETURN(AvFILLp(av)+1);
+    gimme_v = GIMME_V;
+    if(gimme_v != G_VOID) {
+	I32 stack_grow = 1;
+	const I32 avlastindex = AvFILLp(av); /* 0 based */
+	I32 avitems; /* 1 based */
+	stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch free */
+	if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);
+	EXTEND(SP, stack_grow);
+	SP += 1;
+	avitems = avlastindex+1;
+	if(gimme_v != G_ARRAY) {
+	    dXSTARG;
+	    SETs(TARG);
+	    assert(ST(0) == TARG);
+	    sv_setiv(TARG, (IV)avitems);
+	}
+	else {
+	    SV ** const dstsp = SP;
+	    assert(gimme_v == G_ARRAY && &ST(0) == dstsp);
+	    SP += avlastindex;
+	    assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
+	    Copy(AvARRAY(av), dstsp, avitems, SV *);
+	}
+    }
+    end:
+    PUTBACK;
+    return;
 }
 
 /*
-- 
1.7.9.msysgit.0

@p5pRT
Copy link
Author

p5pRT commented Mar 10, 2014

From @bulk88

0002-B88-2-TC.patch
From f98823ff1ddc21557404a8eef22de0cf80d67f26 Mon Sep 17 00:00:00 2001
From: bulk88 <bulk88@hotmail.com>
Date: Mon, 10 Mar 2014 01:19:44 -0400
Subject: [PATCH 2/2] B88 2 TC

---
 op.c |   21 ++++++++-------------
 1 files changed, 8 insertions(+), 13 deletions(-)

diff --git a/op.c b/op.c
index c7b846c..3ac573f 100644
--- a/op.c
+++ b/op.c
@@ -12531,9 +12531,8 @@ const_av_xsub(pTHX_ CV* cv)
     dVAR;
     dXSARGS;
     AV * const av = MUTABLE_AV(XSANY.any_ptr);
+    SP = MARK;
     U32 gimme_v;
-    assert((SP - items) == MARK);
-    SP = MARK; /* remove all incoming args, more efficient than items */
     assert(av);
 #ifndef DEBUGGING
     if (!av) {
@@ -12543,17 +12542,12 @@ const_av_xsub(pTHX_ CV* cv)
     if (SvRMAGICAL(av))
 	Perl_croak(aTHX_ "Magical list constants are not supported");
     gimme_v = GIMME_V;
-    if(gimme_v != G_VOID) {
-	I32 stack_grow = 1;
-	const I32 avlastindex = AvFILLp(av); /* 0 based */
-	I32 avitems; /* 1 based */
-	stack_grow += -(gimme_v == G_ARRAY) & avlastindex; /* branch free */
-	if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);
-	EXTEND(SP, stack_grow);
-	SP += 1;
-	avitems = avlastindex+1;
-	if(gimme_v != G_ARRAY) {
+    if (LIKELY(gimme_v != G_VOID)) {
+	const SSize_t avitems = AvFILLp(av)+1;
+	if (UNLIKELY(gimme_v != G_ARRAY)) {
 	    dXSTARG;
+	    EXTEND(SP, 1);
+	    SP += 1;
 	    SETs(TARG);
 	    assert(ST(0) == TARG);
 	    sv_setiv(TARG, (IV)avitems);
@@ -12561,7 +12555,8 @@ const_av_xsub(pTHX_ CV* cv)
 	else {
 	    SV ** const dstsp = SP;
 	    assert(gimme_v == G_ARRAY && &ST(0) == dstsp);
-	    SP += avlastindex;
+	    EXTEND(SP, avitems)
+	    SP += avitems;
 	    assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
 	    Copy(AvARRAY(av), dstsp, avitems, SV *);
 	}
-- 
1.7.9.msysgit.0

@p5pRT
Copy link
Author

p5pRT commented Mar 19, 2014

From @bulk88

New patch attached with changes from Tonyc.

--
bulk88 ~ bulk88 at hotmail.com

@p5pRT
Copy link
Author

p5pRT commented Mar 19, 2014

From @bulk88

0001-refactor-const_av_xsub.patch
From 0341825153c48747531787daea8c27dfa3cd4a37 Mon Sep 17 00:00:00 2001
From: bulk88 <bulk88@hotmail.com>
Date: Wed, 19 Mar 2014 17:11:31 -0400
Subject: [PATCH] refactor const_av_xsub

-calculating items takes more opcodes and memreads than using the mark
 that is already there, we don't care how many incoming args there are
-replace XSRETURN since SP is already 0 ret args, make the function
 var ax and ST() free, just goto the PUTBACK and return
-read AvFILLp only once, save it to var
-do only 1 EXTEND call, either with 1, or 1+av's last index, branchfree
-both the Copy needs a SP++ed, PUSHs will also SP++, factor it out, then
 then use TOPs in G_SCALAR branch
-use TARG in the G_SCALAR branch
-in the G_ARRAY branch, remove the multiple AvFILLp reads

See also "leak in const_av_xsub?"
Message ID: BLU0-SMTP1979C3A6BA69B12AADC7936DFB40@phx.gbl and [#120939]
---
 op.c |   41 +++++++++++++++++++++++++++++++----------
 1 files changed, 31 insertions(+), 10 deletions(-)

diff --git a/op.c b/op.c
index a6488b0..4b689a3 100644
--- a/op.c
+++ b/op.c
@@ -12531,23 +12531,44 @@ const_av_xsub(pTHX_ CV* cv)
     dVAR;
     dXSARGS;
     AV * const av = MUTABLE_AV(XSANY.any_ptr);
-    SP -= items;
+    U32 gimme_v;
+    assert((SP - items) == MARK);
+    SP = MARK; /* remove all incoming args, more efficient than items */
     assert(av);
 #ifndef DEBUGGING
     if (!av) {
-	XSRETURN(0);
+	goto end;
     }
 #endif
     if (SvRMAGICAL(av))
 	Perl_croak(aTHX_ "Magical list constants are not supported");
-    if (GIMME_V != G_ARRAY) {
-	EXTEND(SP, 1);
-	ST(0) = sv_2mortal(newSViv((IV)AvFILLp(av)+1));
-	XSRETURN(1);
-    }
-    EXTEND(SP, AvFILLp(av)+1);
-    Copy(AvARRAY(av), &ST(0), AvFILLp(av)+1, SV *);
-    XSRETURN(AvFILLp(av)+1);
+    gimme_v = GIMME_V;
+    if (LIKELY(gimme_v != G_VOID)) {
+	const SSize_t avlastindex = AvFILLp(av); /* 0 based */
+	SSize_t avitems; /* 1 based */
+	const SSize_t stack_grow = 1 + (-(gimme_v == G_ARRAY) & avlastindex); /* branch free */
+	if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);
+	EXTEND(SP, stack_grow);
+	SP += 1;
+	avitems = avlastindex+1;
+	if (UNLIKELY(gimme_v != G_ARRAY)) {
+	    dXSTARG;
+	    SETs(TARG);
+	    assert(gimme_v == G_SCALAR && ST(0) == TARG);
+	    sv_setiv(TARG, (IV)avitems);
+	    SvSETMAGIC(TARG);
+	}
+	else {
+	    SV ** const dstsp = SP;
+	    assert(gimme_v == G_ARRAY && &ST(0) == dstsp);
+	    SP += avlastindex;
+	    assert(SP == (PL_stack_base + ax + ((AvFILLp(av)+1) - 1)));
+	    Copy(AvARRAY(av), dstsp, avitems, SV *);
+	}
+    }
+    end:
+    PUTBACK;
+    return;
 }
 
 /*
-- 
1.7.9.msysgit.0

@p5pRT
Copy link
Author

p5pRT commented Mar 23, 2014

From @tonycoz

On Sun Mar 09 22​:28​:10 2014, bulk88 wrote​:

On Tue Feb 11 22​:16​:17 2014, tonyc wrote​:

- avoid doing the same gimme test twice, which could confuse a reader

Now there are 2 EXTENDs which are quite fat (for C) to do, scaling, 4
pushes, comparison, reading interp globals. My code has just 1 EXTEND
call, which I like.

The perl implementation strikes the speed vs size balance heavily on the speed side - using macros and inline functions which produce faster but significantly larger code.

So I think adding code like​:

const SSize_t stack_grow = 1 + (-(gimme_v == G_ARRAY) & avlastindex);

to save a few bytes from the extra EXTEND() expansion (while slighly slowing it down) doesn't match how perl's implementation has balanced speed vs size.

Since you fixed the leak a while ago,

Yes, I wanted to make sure the fix at least made it into 5.20.

Tony

@p5pRT
Copy link
Author

p5pRT commented Apr 4, 2014

From @bulk88

On Sun Mar 23 16​:38​:17 2014, tonyc wrote​:

On Sun Mar 09 22​:28​:10 2014, bulk88 wrote​:

On Tue Feb 11 22​:16​:17 2014, tonyc wrote​:

- avoid doing the same gimme test twice, which could confuse a
reader

Now there are 2 EXTENDs which are quite fat (for C) to do, scaling, 4
pushes, comparison, reading interp globals. My code has just 1 EXTEND
call, which I like.

The perl implementation strikes the speed vs size balance heavily on
the speed side - using macros and inline functions which produce
faster but significantly larger code.

Which slows you down when you have a 1 KB in the machine code function to execute. Or 25 calls to hv_common_key() when someone put it inside SvTRUE.

So I think adding code like​:

const SSize_t stack_grow = 1 + (-(gimme_v == G_ARRAY) & avlastindex);

to save a few bytes from the extra EXTEND() expansion (while slighly
slowing it down) doesn't match how perl's implementation has balanced
speed vs size.

That is trading size for speed. I can write that as a traditional if() {} if you want me to. It will be less machine code, but then its a branch/conditional jump (slower).

--
bulk88 ~ bulk88 at hotmail.com

@p5pRT
Copy link
Author

p5pRT commented Jul 3, 2014

From @tonycoz

On Thu Apr 03 23​:57​:57 2014, bulk88 wrote​:

On Sun Mar 23 16​:38​:17 2014, tonyc wrote​:

On Sun Mar 09 22​:28​:10 2014, bulk88 wrote​:

On Tue Feb 11 22​:16​:17 2014, tonyc wrote​:

- avoid doing the same gimme test twice, which could confuse a
reader

Now there are 2 EXTENDs which are quite fat (for C) to do, scaling,
4
pushes, comparison, reading interp globals. My code has just 1
EXTEND
call, which I like.

The perl implementation strikes the speed vs size balance heavily on
the speed side - using macros and inline functions which produce
faster but significantly larger code.

Which slows you down when you have a 1 KB in the machine code function
to execute. Or 25 calls to hv_common_key() when someone put it inside
SvTRUE.

In this specific case, on a compiler we support UNLIKELY() with, the unlikely code will be moved out of the flow of execution and will have little to no effect on speed.

So I think adding code like​:

const SSize_t stack_grow = 1 + (-(gimme_v == G_ARRAY) & avlastindex);

to save a few bytes from the extra EXTEND() expansion (while slighly
slowing it down) doesn't match how perl's implementation has balanced
speed vs size.

That is trading size for speed. I can write that as a traditional if()
{} if you want me to. It will be less machine code, but then its a
branch/conditional jump (slower).

That won't help enough for me to accept this patch.

BTW​:

  if(gimme_v == G_ARRAY) assert(stack_grow == AvFILLp(av)+1);

should probably be​:

  assert(gimme_v != G_ARRAY || stack_grow == AvFILLp(av)+1);

The first causes a compiler warning on non-DEBUGGING builds about the empty if() body.

The code also introduces an unused variable warning for items on non-DEBUGGING builds.

Tony

@toddr
Copy link
Member

toddr commented Feb 13, 2020

@tonycoz @bulk88 The goal of this patch seems to be to "Fix a leak". If the leak still exists, is there a simpler patch to address just that issue?

Maybe we could move this to a pull request to discuss further?

@tonycoz
Copy link
Contributor

tonycoz commented Mar 18, 2020

The leak itself was fixed in d06ddc7.

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

No branches or pull requests

4 participants