Skip to content

Commit 76d0208

Browse files
authored
Merge pull request #299 from jayrm/optimize-StrConcatByref
Optimize Byref String Concatenation
2 parents 506dba5 + fd18dda commit 76d0208

File tree

14 files changed

+237
-24
lines changed

14 files changed

+237
-24
lines changed

changelog.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ Version 1.08.0
3838
- fbc: internal changes to optimize away unused call results
3939
- github #203: allow casts of addresses on static initializers
4040
- only write debug line information for statements and don't write comments / empty lines / directives for top level source code in assembly debug ouput
41+
- optimize byref 'm += s' string concatenations to fb_StrConcatByref() which will check for same string descriptor at run-time which can't be determined at compile time for byref parameters.
4142

4243
[added]
4344
- extern "rtlib": respects the parent namespace, uses default fb calling convention and C style name mangling
@@ -73,6 +74,7 @@ Version 1.08.0
7374
- __FB_ARG_EXTRACT__( index, args... ) builtin macro (adeyblue)
7475
- __FB_X86__ intrinsic define on x86 and x86_64
7576
- added warning 'FOR counter variable is unable to exceed limit value' on constant end value for loops to help avoid infinite loops, e.g. for i as ubyte = 0 to 255
77+
- internal rtlib function fb_LEFTSELF( string, n ) to reduce the size of a string without reallocating the buffer
7678

7779
[fixed]
7880
- makefile: under MSYS2 (and friends), TARGET_ARCH is now identified from shell's default target architecture instead of shell's host architecture
@@ -121,6 +123,7 @@ Version 1.08.0
121123
- gcc backend: fix GOSUB causing crash/exception on win64 - setjmp/longjmp failed on mingw-w64 64-bit and needed to be passed 2 parameters instead of 1
122124
- fix __FB_EVAL__() incorrectly reading past the end of the expression, and report errors in expressions
123125
- C backend: switch to .text section after writing the exports to the C file in the explicit asm block. gcc can move sections around with optimizations and there is a change between 7.x and 8.x that causes issue with where the directive section is located
126+
- sf.net #917: optimize 'm += s' string concatenations to fix the long compile times in the gcc backend (which makes heavy use of string building).
124127

125128

126129
Version 1.07.0

src/compiler/ast-optimize.bas

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,9 +1464,10 @@ private function hOptStrAssignment _
14641464
byval r as ASTNODE ptr _
14651465
) as ASTNODE ptr
14661466

1467-
dim as integer optimize = any, is_wstr = any
1467+
dim as integer optimize = any, is_byref = any, is_wstr = any
14681468

14691469
optimize = FALSE
1470+
is_byref = FALSE
14701471

14711472
'' is right side a bin operation?
14721473
if( r->class = AST_NODECLASS_BOP ) then
@@ -1486,14 +1487,35 @@ private function hOptStrAssignment _
14861487
end if
14871488
end if
14881489

1490+
case AST_NODECLASS_DEREF
1491+
'' check if we can optimize to fb_StrConcatByref()
1492+
'' looking specifically for a = a + b
1493+
'' where both a & b are STRINGS but we possibly
1494+
'' don't know until run time if a and b could
1495+
'' be the same descriptor.
1496+
if( astIsTreeEqual( l, r->l ) ) then
1497+
sym = astGetSymbol( l->l )
1498+
if( sym <> NULL ) then
1499+
if( astGetDataType( l ) = FB_DATATYPE_STRING ) then
1500+
optimize = TRUE
1501+
is_byref = TRUE
1502+
end if
1503+
end if
1504+
end if
1505+
14891506
case AST_NODECLASS_FIELD, AST_NODECLASS_IIF
14901507
select case as const l->l->class
14911508
case AST_NODECLASS_VAR, AST_NODECLASS_IDX
14921509

14931510
if( astIsTreeEqual( l, r->l ) ) then
14941511
sym = astGetSymbol( l )
14951512
if( sym <> NULL ) then
1496-
optimize = astIsSymbolOnTree( sym, r->r ) = FALSE
1513+
if( astGetDataType( l ) = FB_DATATYPE_STRING ) then
1514+
optimize = TRUE
1515+
is_byref = TRUE
1516+
else
1517+
optimize = astIsSymbolOnTree( sym, r->r ) = FALSE
1518+
end if
14971519
end if
14981520
end if
14991521

@@ -1513,13 +1535,13 @@ private function hOptStrAssignment _
15131535
if( hIsMultStrConcat( l, r ) ) then
15141536
function = hOptStrMultConcat( l, l, r, is_wstr )
15151537
else
1516-
'' = f() -- concatassign
1538+
'' = f() -- concatassign | concatbyref
15171539
'' / \ / \
15181540
''a + => a expr
15191541
'' / \
15201542
'' a expr
15211543
if( is_wstr = FALSE ) then
1522-
function = rtlStrConcatAssign( l, astUpdStrConcat( r ) )
1544+
function = rtlStrConcatAssign( l, astUpdStrConcat( r ), is_byref )
15231545
else
15241546
function = rtlWstrConcatAssign( l, astUpdStrConcat( r ) )
15251547
end if

src/compiler/ir-hlc.bas

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3772,7 +3772,14 @@ private sub _emitVarIniScopeEnd( )
37723772
'' Trim separator at the end, to make the output look a bit more clean
37733773
'' (this isn't needed though, since the extra comma is allowed in C)
37743774
if( right( ctx.varini, 2 ) = ", " ) then
3775-
ctx.varini = left( ctx.varini, len( ctx.varini ) - 2 )
3775+
'' fbc doesn't optimize this very well and on long strings it becomes expensive
3776+
'' it's built in to the run-time, so revert to normal expression
3777+
'' if we don't have fb_leftself defined yet.
3778+
#ifndef fb_leftself
3779+
ctx.varini = left( ctx.varini, len( ctx.varini ) - 2 )
3780+
#else
3781+
fb_leftself( ctx.varini, len( ctx.varini ) - 2 )
3782+
#endif ''
37763783
end if
37773784

37783785
ctx.varini += " }"

src/compiler/rtl-string.bas

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,23 @@
143143
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ) _
144144
} _
145145
), _
146+
/' function fb_StrConcatByref(
147+
byref str1 as const any, byval str1_size as const integer, _
148+
byref str2 as const any, byval str2_size as const integer, _
149+
byval fillrem as const long = 1 ) as string '/ _
150+
( _
151+
@FB_RTL_STRCONCATBYREF, NULL, _
152+
FB_DATATYPE_STRING, FB_FUNCMODE_FBCALL, _
153+
NULL, FB_RTL_OPT_NONE, _
154+
5, _
155+
{ _
156+
( FB_DATATYPE_VOID, FB_PARAMMODE_BYREF, FALSE ), _
157+
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ), _
158+
( typeSetIsConst( FB_DATATYPE_VOID ), FB_PARAMMODE_BYREF, FALSE ), _
159+
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ), _
160+
( typeSetIsConst( FB_DATATYPE_LONG ), FB_PARAMMODE_BYVAL, TRUE, 1 ) _
161+
} _
162+
), _
146163
/' function fb_WstrConcat( byval str1 as const wstring ptr, byval str2 as const wstring ptr ) as wstring '/ _
147164
( _
148165
@FB_RTL_WSTRCONCAT, NULL, _
@@ -1840,7 +1857,7 @@
18401857
{ _
18411858
( typeSetIsConst( FB_DATATYPE_LONG ), FB_PARAMMODE_BYVAL, FALSE ) _
18421859
} _
1843-
), _
1860+
), _
18441861
/' function fb_MKLONGINT( byval number as const longint ) as string '/ _
18451862
( _
18461863
@FB_RTL_MKLONGINT, NULL, _
@@ -1850,7 +1867,7 @@
18501867
{ _
18511868
( typeSetIsConst( FB_DATATYPE_LONGINT ), FB_PARAMMODE_BYVAL, FALSE ) _
18521869
} _
1853-
), _
1870+
), _
18541871
/' function left overload( byref str as const string, byval chars as const integer ) as string '/ _
18551872
( _
18561873
@"left", @"fb_LEFT", _
@@ -1861,7 +1878,7 @@
18611878
( typeSetIsConst( FB_DATATYPE_STRING ), FB_PARAMMODE_BYREF, FALSE ), _
18621879
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ) _
18631880
} _
1864-
), _
1881+
), _
18651882
/' function left overload( byref str as const wstring, byval chars as const integer ) as wstring '/ _
18661883
( _
18671884
@"left", @"fb_WstrLeft", _
@@ -1873,6 +1890,17 @@
18731890
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ) _
18741891
} _
18751892
), _
1893+
/' sub fb_leftself overload( byref str as string, byval chars as const integer )'/ _
1894+
( _
1895+
@"fb_LeftSelf", @"fb_LEFTSELF", _
1896+
FB_DATATYPE_VOID, FB_FUNCMODE_FBCALL, _
1897+
NULL, FB_RTL_OPT_OVER or FB_RTL_OPT_NOQB, _
1898+
2, _
1899+
{ _
1900+
( typeSetIsConst( FB_DATATYPE_STRING ), FB_PARAMMODE_BYREF, FALSE ), _
1901+
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ) _
1902+
} _
1903+
), _
18761904
/' function right overload( byref str as const string, byval chars as const integer ) as string '/ _
18771905
( _
18781906
@"right", @"fb_RIGHT", _
@@ -1914,7 +1942,7 @@
19141942
{ _
19151943
( typeSetIsConst( FB_DATATYPE_INTEGER ), FB_PARAMMODE_BYVAL, FALSE ) _
19161944
} _
1917-
), _
1945+
), _
19181946
/' function fb_StrLcase2( byref src as const string, byval mode as const long = 0 ) as string '/ _
19191947
( _
19201948
@FB_RTL_STRLCASE2, NULL, _
@@ -1925,7 +1953,7 @@
19251953
( typeSetIsConst( FB_DATATYPE_STRING ), FB_PARAMMODE_BYREF, FALSE ), _
19261954
( typeSetIsConst( FB_DATATYPE_LONG ), FB_PARAMMODE_BYVAL, TRUE, 0 ) _
19271955
} _
1928-
), _
1956+
), _
19291957
/' function fb_WstrLcase2( byref str as const wstring, byval mode as const long = 0 ) as wstring '/ _
19301958
( _
19311959
@FB_RTL_WSTRLCASE2, NULL, _
@@ -1936,7 +1964,7 @@
19361964
( typeSetIsConst( FB_DATATYPE_WCHAR ), FB_PARAMMODE_BYREF, FALSE ), _
19371965
( typeSetIsConst( FB_DATATYPE_LONG ), FB_PARAMMODE_BYVAL, TRUE, 0 ) _
19381966
} _
1939-
), _
1967+
), _
19401968
/' function fb_StrUcase2( byref src as const string, byval mode as const long = 0 ) as string '/ _
19411969
( _
19421970
@FB_RTL_STRUCASE2, NULL, _
@@ -2316,7 +2344,8 @@ end function
23162344
function rtlStrConcatAssign _
23172345
( _
23182346
byval dst as ASTNODE ptr, _
2319-
byval src as ASTNODE ptr _
2347+
byval src as ASTNODE ptr, _
2348+
byval isConcatByref as integer = FALSE _
23202349
) as ASTNODE ptr
23212350

23222351
dim as ASTNODE ptr proc = any
@@ -2325,7 +2354,11 @@ function rtlStrConcatAssign _
23252354

23262355
function = NULL
23272356

2328-
proc = astNewCALL( PROCLOOKUP( STRCONCATASSIGN ) )
2357+
if( isConcatByref ) then
2358+
proc = astNewCALL( PROCLOOKUP( STRCONCATBYREF ) )
2359+
else
2360+
proc = astNewCALL( PROCLOOKUP( STRCONCATASSIGN ) )
2361+
end if
23292362

23302363
ddtype = astGetDataType( dst )
23312364

src/compiler/rtl.bi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define FB_RTL_HSTRDELTEMP "fb_hStrDelTemp"
1010
#define FB_RTL_STRASSIGN "fb_StrAssign"
1111
#define FB_RTL_STRCONCAT "fb_StrConcat"
12+
#define FB_RTL_STRCONCATBYREF "fb_StrConcatByref"
1213
#define FB_RTL_STRCOMPARE "fb_StrCompare"
1314
#define FB_RTL_STRCONCATASSIGN "fb_StrConcatAssign"
1415
#define FB_RTL_STRALLOCTMPRES "fb_StrAllocTempResult"
@@ -419,6 +420,7 @@ enum FB_RTL_IDX
419420
FB_RTL_IDX_HSTRDELTEMP
420421
FB_RTL_IDX_STRASSIGN
421422
FB_RTL_IDX_STRCONCAT
423+
FB_RTL_IDX_STRCONCATBYREF
422424
FB_RTL_IDX_STRCOMPARE
423425
FB_RTL_IDX_STRCONCATASSIGN
424426
FB_RTL_IDX_STRALLOCTMPRES
@@ -960,7 +962,8 @@ declare function rtlWstrAssign _
960962
declare function rtlStrConcatAssign _
961963
( _
962964
byval dst as ASTNODE ptr, _
963-
byval src as ASTNODE ptr _
965+
byval src as ASTNODE ptr, _
966+
byval isConcatByref as integer = FALSE _
964967
) as ASTNODE ptr
965968

966969
declare function rtlWstrConcatAssign _

src/rtlib/fb_string.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ FBCALL void *fb_StrAssignEx ( void *dst, ssize_t dst_size, void *src
123123
FBCALL void fb_StrDelete ( FBSTRING *str );
124124
FBCALL FBSTRING *fb_StrConcat ( FBSTRING *dst, void *str1, ssize_t str1_size, void *str2, ssize_t str2_size );
125125
FBCALL void *fb_StrConcatAssign ( void *dst, ssize_t dst_size, void *src, ssize_t src_size, int fillrem );
126+
FBCALL void *fb_StrConcatByref ( void *dst, ssize_t dst_size, void *src, ssize_t src_size, int fillrem );
126127
FBCALL int fb_StrCompare ( void *str1, ssize_t str1_size, void *str2, ssize_t str2_size );
127128
FBCALL FBSTRING *fb_StrAllocTempResult ( FBSTRING *src );
128129
FBCALL FBSTRING *fb_StrAllocTempDescF( char *str, ssize_t str_size );
@@ -244,6 +245,7 @@ FBCALL FBSTRING *fb_MKI ( ssize_t num );
244245
FBCALL FBSTRING *fb_MKL ( int num );
245246
FBCALL FBSTRING *fb_MKLONGINT ( long long num );
246247
FBCALL FBSTRING *fb_LEFT ( FBSTRING *str, ssize_t chars );
248+
FBCALL void fb_LEFTSELF ( FBSTRING *str, ssize_t chars );
247249
FBCALL FBSTRING *fb_RIGHT ( FBSTRING *str, ssize_t chars );
248250
FBCALL FBSTRING *fb_SPACE ( ssize_t chars );
249251
FBCALL FBSTRING *fb_LTRIM ( FBSTRING *str );

src/rtlib/str_concatbyref.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
run-time optimization for:
3+
4+
dim shared m as string
5+
sub proc( byref s as string, byref t as string )
6+
'' these are optimized to fb_StrConcatByref()
7+
m += s
8+
s += t
9+
end sub
10+
11+
dim as string a, b
12+
proc( m, a )
13+
proc( a, a )
14+
proc( a, b )
15+
16+
it can't be determined at compile time within proc() if the strings passed
17+
to proc() are the same strings. Might be able to use fb_StrConcatAssign()
18+
but only if it is known that the strings are different. Otherwise the
19+
contents of the string could be destroyed before the are copied. If the
20+
strings are the same, then extend the buffer and copy the first part of the
21+
string to the second half.
22+
23+
We should only get here if the left hand side variable was an FBSTRING type
24+
and we should expect a string descriptor.
25+
*/
26+
27+
#include "fb.h"
28+
29+
FBCALL void *fb_StrConcatByref
30+
(
31+
void *dst,
32+
ssize_t dst_size,
33+
void *src,
34+
ssize_t src_size,
35+
int fillrem
36+
)
37+
{
38+
char *dst_ptr;
39+
const char *src_ptr;
40+
ssize_t dst_len, src_len;
41+
42+
/* dst should always be var-len string */
43+
DBG_ASSERT( dst_size == -1 );
44+
45+
/* dst */
46+
FB_STRSETUP_FIX( dst, dst_size, dst_ptr, dst_len );
47+
48+
/* src */
49+
FB_STRSETUP_FIX( src, src_size, src_ptr, src_len );
50+
51+
/* Are dst & src same same data? */
52+
if( dst == src || dst_ptr == src_ptr )
53+
{
54+
FBSTRING *str = dst;
55+
56+
FB_STRLOCK();
57+
58+
if( fb_hStrRealloc( str, dst_len + src_len, FB_TRUE ) )
59+
{
60+
/* recalculate dst */
61+
FB_STRSETUP_FIX( str, dst_size, dst_ptr, dst_len );
62+
63+
/* copy start of dst to second half */
64+
FB_MEMCPYX( dst_ptr + dst_len, dst, dst_len );
65+
66+
fb_hStrSetLength( str, dst_len + dst_len );
67+
68+
str->data[dst_len + dst_len] = '\0';
69+
}
70+
71+
/* delete temps? */
72+
if( dst_size == -1 )
73+
fb_hStrDelTemp_NoLock( (FBSTRING *)dst );
74+
if( src_size == -1 )
75+
fb_hStrDelTemp_NoLock( (FBSTRING *)src );
76+
77+
FB_STRUNLOCK();
78+
79+
return dst;
80+
}
81+
82+
return fb_StrConcatAssign( dst, dst_size, src, src_size, fillrem );
83+
}

0 commit comments

Comments
 (0)