Skip to content

Commit 9bd0d3a

Browse files
committed
Remove jump instructions from findPtr, rFindPtr
This results in an approx 3% deoptimisation according to benchmarks, however allows all library functions to be marked as pure and removes the last of the compiler warnings from the code. Marked all library functions as pure
1 parent ee6af05 commit 9bd0d3a

File tree

2 files changed

+63
-76
lines changed

2 files changed

+63
-76
lines changed

src/strings.sol

+63-75
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ library strings {
4242
uint _ptr;
4343
}
4444

45-
function memcpy(uint dest, uint src, uint len) private {
45+
function memcpy(uint dest, uint src, uint len) private pure {
4646
// Copy word-length chunks while possible
4747
for(; len >= 32; len -= 32) {
4848
assembly {
@@ -66,7 +66,7 @@ library strings {
6666
* @param self The string to make a slice from.
6767
* @return A newly allocated slice containing the entire string.
6868
*/
69-
function toSlice(string self) internal returns (slice) {
69+
function toSlice(string self) internal pure returns (slice) {
7070
uint ptr;
7171
assembly {
7272
ptr := add(self, 0x20)
@@ -79,7 +79,7 @@ library strings {
7979
* @param self The value to find the length of.
8080
* @return The length of the string, from 0 to 32.
8181
*/
82-
function len(bytes32 self) internal returns (uint) {
82+
function len(bytes32 self) internal pure returns (uint) {
8383
uint ret;
8484
if (self == 0)
8585
return 0;
@@ -112,7 +112,7 @@ library strings {
112112
* @return A new slice containing the value of the input argument up to the
113113
* first null.
114114
*/
115-
function toSliceB32(bytes32 self) internal returns (slice ret) {
115+
function toSliceB32(bytes32 self) internal pure returns (slice ret) {
116116
// Allocate space for `self` in memory, copy it there, and point ret at it
117117
assembly {
118118
let ptr := mload(0x40)
@@ -128,7 +128,7 @@ library strings {
128128
* @param self The slice to copy.
129129
* @return A new slice containing the same data as `self`.
130130
*/
131-
function copy(slice self) internal returns (slice) {
131+
function copy(slice self) internal pure returns (slice) {
132132
return slice(self._len, self._ptr);
133133
}
134134

@@ -137,7 +137,7 @@ library strings {
137137
* @param self The slice to copy.
138138
* @return A newly allocated string containing the slice's text.
139139
*/
140-
function toString(slice self) internal returns (string) {
140+
function toString(slice self) internal pure returns (string) {
141141
string memory ret = new string(self._len);
142142
uint retptr;
143143
assembly { retptr := add(ret, 32) }
@@ -146,10 +146,6 @@ library strings {
146146
return ret;
147147
}
148148

149-
function len(string self) internal returns (uint l) {
150-
return len(toSlice(self));
151-
}
152-
153149
/*
154150
* @dev Returns the length in runes of the slice. Note that this operation
155151
* takes time proportional to the length of the slice; avoid using it
@@ -158,7 +154,7 @@ library strings {
158154
* @param self The slice to operate on.
159155
* @return The length of the slice in runes.
160156
*/
161-
function len(slice self) internal returns (uint l) {
157+
function len(slice self) internal pure returns (uint l) {
162158
// Starting at ptr-31 means the LSB will be the byte we care about
163159
uint ptr = self._ptr - 31;
164160
uint end = ptr + self._len;
@@ -186,22 +182,10 @@ library strings {
186182
* @param self The slice to operate on.
187183
* @return True if the slice is empty, False otherwise.
188184
*/
189-
function empty(slice self) internal returns (bool) {
185+
function empty(slice self) internal pure returns (bool) {
190186
return self._len == 0;
191187
}
192188

193-
function compare(string self, string other) internal returns (int) {
194-
return compare(toSlice(self), toSlice(other));
195-
}
196-
197-
function compare(string self, slice other) internal returns (int) {
198-
return compare(toSlice(self), other);
199-
}
200-
201-
function compare(slice self, string other) internal returns (int) {
202-
return compare(self, toSlice(other));
203-
}
204-
205189
/*
206190
* @dev Returns a positive number if `other` comes lexicographically after
207191
* `self`, a negative number if it comes before, or zero if the
@@ -211,7 +195,7 @@ library strings {
211195
* @param other The second slice to compare.
212196
* @return The result of the comparison.
213197
*/
214-
function compare(slice self, slice other) internal returns (int) {
198+
function compare(slice self, slice other) internal pure returns (int) {
215199
uint shortest = self._len;
216200
if (other._len < self._len)
217201
shortest = other._len;
@@ -244,7 +228,7 @@ library strings {
244228
* @param self The second slice to compare.
245229
* @return True if the slices are equal, false otherwise.
246230
*/
247-
function equals(slice self, slice other) internal returns (bool) {
231+
function equals(slice self, slice other) internal pure returns (bool) {
248232
return compare(self, other) == 0;
249233
}
250234

@@ -255,7 +239,7 @@ library strings {
255239
* @param rune The slice that will contain the first rune.
256240
* @return `rune`.
257241
*/
258-
function nextRune(slice self, slice rune) internal returns (slice) {
242+
function nextRune(slice self, slice rune) internal pure returns (slice) {
259243
rune._ptr = self._ptr;
260244

261245
if (self._len == 0) {
@@ -297,7 +281,7 @@ library strings {
297281
* @param self The slice to operate on.
298282
* @return A slice containing only the first rune from `self`.
299283
*/
300-
function nextRune(slice self) internal returns (slice ret) {
284+
function nextRune(slice self) internal pure returns (slice ret) {
301285
nextRune(self, ret);
302286
}
303287

@@ -306,7 +290,7 @@ library strings {
306290
* @param self The slice to operate on.
307291
* @return The number of the first codepoint in the slice.
308292
*/
309-
function ord(slice self) internal returns (uint ret) {
293+
function ord(slice self) internal pure returns (uint ret) {
310294
if (self._len == 0) {
311295
return 0;
312296
}
@@ -355,7 +339,7 @@ library strings {
355339
* @param self The slice to hash.
356340
* @return The hash of the slice.
357341
*/
358-
function keccak(slice self) internal returns (bytes32 ret) {
342+
function keccak(slice self) internal pure returns (bytes32 ret) {
359343
assembly {
360344
ret := keccak256(mload(add(self, 32)), mload(self))
361345
}
@@ -367,7 +351,7 @@ library strings {
367351
* @param needle The slice to search for.
368352
* @return True if the slice starts with the provided text, false otherwise.
369353
*/
370-
function startsWith(slice self, slice needle) internal returns (bool) {
354+
function startsWith(slice self, slice needle) internal pure returns (bool) {
371355
if (self._len < needle._len) {
372356
return false;
373357
}
@@ -393,7 +377,7 @@ library strings {
393377
* @param needle The slice to search for.
394378
* @return `self`
395379
*/
396-
function beyond(slice self, slice needle) internal returns (slice) {
380+
function beyond(slice self, slice needle) internal pure returns (slice) {
397381
if (self._len < needle._len) {
398382
return self;
399383
}
@@ -422,7 +406,7 @@ library strings {
422406
* @param needle The slice to search for.
423407
* @return True if the slice starts with the provided text, false otherwise.
424408
*/
425-
function endsWith(slice self, slice needle) internal returns (bool) {
409+
function endsWith(slice self, slice needle) internal pure returns (bool) {
426410
if (self._len < needle._len) {
427411
return false;
428412
}
@@ -450,7 +434,7 @@ library strings {
450434
* @param needle The slice to search for.
451435
* @return `self`
452436
*/
453-
function until(slice self, slice needle) internal returns (slice) {
437+
function until(slice self, slice needle) internal pure returns (slice) {
454438
if (self._len < needle._len) {
455439
return self;
456440
}
@@ -472,33 +456,37 @@ library strings {
472456
return self;
473457
}
474458

459+
event log_bytemask(bytes32 mask);
460+
475461
// Returns the memory address of the first byte of the first occurrence of
476462
// `needle` in `self`, or the first byte after `self` if not found.
477-
function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private returns (uint) {
478-
uint ptr;
463+
function findPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
464+
uint ptr = selfptr;
479465
uint idx;
480466

481467
if (needlelen <= selflen) {
482468
if (needlelen <= 32) {
483-
// Optimized assembly for 68 gas per byte on short strings
484-
assembly {
485-
let mask := not(sub(exp(2, mul(8, sub(32, needlelen))), 1))
486-
let needledata := and(mload(needleptr), mask)
487-
let end := add(selfptr, sub(selflen, needlelen))
488-
ptr := selfptr
489-
loop:
490-
jumpi(exit, eq(and(mload(ptr), mask), needledata))
491-
ptr := add(ptr, 1)
492-
jumpi(loop, lt(sub(ptr, 1), end))
493-
ptr := add(selfptr, selflen)
494-
exit:
469+
bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1));
470+
471+
bytes32 needledata;
472+
assembly { needledata := and(mload(needleptr), mask) }
473+
474+
uint end = selfptr + selflen - needlelen;
475+
bytes32 ptrdata;
476+
assembly { ptrdata := and(mload(ptr), mask) }
477+
478+
while (ptrdata != needledata) {
479+
if (ptr >= end)
480+
return selfptr + selflen;
481+
ptr++;
482+
assembly { ptrdata := and(mload(ptr), mask) }
495483
}
496484
return ptr;
497485
} else {
498486
// For long needles, use hashing
499487
bytes32 hash;
500488
assembly { hash := sha3(needleptr, needlelen) }
501-
ptr = selfptr;
489+
502490
for (idx = 0; idx <= selflen - needlelen; idx++) {
503491
bytes32 testHash;
504492
assembly { testHash := sha3(ptr, needlelen) }
@@ -513,27 +501,27 @@ library strings {
513501

514502
// Returns the memory address of the first byte after the last occurrence of
515503
// `needle` in `self`, or the address of `self` if not found.
516-
function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private returns (uint) {
504+
function rfindPtr(uint selflen, uint selfptr, uint needlelen, uint needleptr) private pure returns (uint) {
517505
uint ptr;
518506

519507
if (needlelen <= selflen) {
520508
if (needlelen <= 32) {
521-
// Optimized assembly for 69 gas per byte on short strings
522-
assembly {
523-
let mask := not(sub(exp(2, mul(8, sub(32, needlelen))), 1))
524-
let needledata := and(mload(needleptr), mask)
525-
ptr := add(selfptr, sub(selflen, needlelen))
526-
loop:
527-
jumpi(ret, eq(and(mload(ptr), mask), needledata))
528-
ptr := sub(ptr, 1)
529-
jumpi(loop, gt(add(ptr, 1), selfptr))
530-
ptr := selfptr
531-
jump(exit)
532-
ret:
533-
ptr := add(ptr, needlelen)
534-
exit:
509+
bytes32 mask = bytes32(~(2 ** (8 * (32 - needlelen)) - 1));
510+
511+
bytes32 needledata;
512+
assembly { needledata := and(mload(needleptr), mask) }
513+
514+
ptr = selfptr + selflen - needlelen;
515+
bytes32 ptrdata;
516+
assembly { ptrdata := and(mload(ptr), mask) }
517+
518+
while (ptrdata != needledata) {
519+
if (ptr <= selfptr)
520+
return selfptr;
521+
ptr--;
522+
assembly { ptrdata := and(mload(ptr), mask) }
535523
}
536-
return ptr;
524+
return ptr + needlelen;
537525
} else {
538526
// For long needles, use hashing
539527
bytes32 hash;
@@ -559,7 +547,7 @@ library strings {
559547
* @param needle The text to search for.
560548
* @return `self`.
561549
*/
562-
function find(slice self, slice needle) internal returns (slice) {
550+
function find(slice self, slice needle) internal pure returns (slice) {
563551
uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr);
564552
self._len -= ptr - self._ptr;
565553
self._ptr = ptr;
@@ -574,7 +562,7 @@ library strings {
574562
* @param needle The text to search for.
575563
* @return `self`.
576564
*/
577-
function rfind(slice self, slice needle) internal returns (slice) {
565+
function rfind(slice self, slice needle) internal pure returns (slice) {
578566
uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr);
579567
self._len = ptr - self._ptr;
580568
return self;
@@ -590,7 +578,7 @@ library strings {
590578
* @param token An output parameter to which the first token is written.
591579
* @return `token`.
592580
*/
593-
function split(slice self, slice needle, slice token) internal returns (slice) {
581+
function split(slice self, slice needle, slice token) internal pure returns (slice) {
594582
uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr);
595583
token._ptr = self._ptr;
596584
token._len = ptr - self._ptr;
@@ -613,7 +601,7 @@ library strings {
613601
* @param needle The text to search for in `self`.
614602
* @return The part of `self` up to the first occurrence of `delim`.
615603
*/
616-
function split(slice self, slice needle) internal returns (slice token) {
604+
function split(slice self, slice needle) internal pure returns (slice token) {
617605
split(self, needle, token);
618606
}
619607

@@ -627,7 +615,7 @@ library strings {
627615
* @param token An output parameter to which the first token is written.
628616
* @return `token`.
629617
*/
630-
function rsplit(slice self, slice needle, slice token) internal returns (slice) {
618+
function rsplit(slice self, slice needle, slice token) internal pure returns (slice) {
631619
uint ptr = rfindPtr(self._len, self._ptr, needle._len, needle._ptr);
632620
token._ptr = ptr;
633621
token._len = self._len - (ptr - self._ptr);
@@ -649,7 +637,7 @@ library strings {
649637
* @param needle The text to search for in `self`.
650638
* @return The part of `self` after the last occurrence of `delim`.
651639
*/
652-
function rsplit(slice self, slice needle) internal returns (slice token) {
640+
function rsplit(slice self, slice needle) internal pure returns (slice token) {
653641
rsplit(self, needle, token);
654642
}
655643

@@ -659,7 +647,7 @@ library strings {
659647
* @param needle The text to search for in `self`.
660648
* @return The number of occurrences of `needle` found in `self`.
661649
*/
662-
function count(slice self, slice needle) internal returns (uint cnt) {
650+
function count(slice self, slice needle) internal pure returns (uint cnt) {
663651
uint ptr = findPtr(self._len, self._ptr, needle._len, needle._ptr) + needle._len;
664652
while (ptr <= self._ptr + self._len) {
665653
cnt++;
@@ -673,7 +661,7 @@ library strings {
673661
* @param needle The text to search for in `self`.
674662
* @return True if `needle` is found in `self`, false otherwise.
675663
*/
676-
function contains(slice self, slice needle) internal returns (bool) {
664+
function contains(slice self, slice needle) internal pure returns (bool) {
677665
return rfindPtr(self._len, self._ptr, needle._len, needle._ptr) != self._ptr;
678666
}
679667

@@ -684,7 +672,7 @@ library strings {
684672
* @param other The second slice to concatenate.
685673
* @return The concatenation of the two strings.
686674
*/
687-
function concat(slice self, slice other) internal returns (string) {
675+
function concat(slice self, slice other) internal pure returns (string) {
688676
string memory ret = new string(self._len + other._len);
689677
uint retptr;
690678
assembly { retptr := add(ret, 32) }
@@ -701,7 +689,7 @@ library strings {
701689
* @return A newly allocated string containing all the slices in `parts`,
702690
* joined with `self`.
703691
*/
704-
function join(slice self, slice[] parts) internal returns (string) {
692+
function join(slice self, slice[] parts) internal pure returns (string) {
705693
if (parts.length == 0)
706694
return "";
707695

src/strings.t.sol

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import './strings.sol';
66
contract StringsTest is DSTest {
77
using strings for *;
88

9-
event log_named_string(bytes32 key, string value);
109

1110
function abs(int x) private pure returns (int) {
1211
if(x < 0)

0 commit comments

Comments
 (0)