Skip to content

Commit 1963ffe

Browse files
committed
transform: avoid byte slice string length allocations
Rewrite len(string(b)) to use the original slice length. This is always valid even when the copied string must remain for other uses.
1 parent 40ca920 commit 1963ffe

5 files changed

Lines changed: 35 additions & 40 deletions

File tree

transform/rtcalls.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,27 +92,38 @@ func OptimizeStringFromBytes(mod llvm.Module) {
9292
}
9393
}
9494

95-
// Rewrite each supported use independently, and remove the conversion only
96-
// when no unconverted uses remain.
95+
// Rewrite each supported use independently. Length extracts are delayed so
96+
// comparison operands can still be matched as pointer/length pairs.
9797
for _, call := range getUses(stringFromBytes) {
98+
var lengthExtracts []llvm.Value
9899
for _, extract := range getUses(call) {
99100
if extract.IsAExtractValueInst().IsNil() {
100101
continue
101102
}
102103
indices := extract.Indices()
103-
if len(indices) != 1 || indices[0] != 0 {
104+
if len(indices) != 1 {
104105
continue
105106
}
106-
for _, use := range getUses(extract) {
107-
if _, ok := safeCalls[use]; !ok {
108-
continue
107+
switch indices[0] {
108+
case 0:
109+
for _, use := range getUses(extract) {
110+
if _, ok := safeCalls[use]; !ok {
111+
continue
112+
}
113+
if !isSafeStringFromBytesUse(call, stringFromBytes, use, safeCalls) {
114+
continue
115+
}
116+
replaceStringFromBytesCompareUse(use, extract, call, stringFromBytes)
109117
}
110-
if !isSafeStringFromBytesUse(call, stringFromBytes, use, safeCalls) {
111-
continue
112-
}
113-
replaceStringFromBytesCompareUse(use, extract, call, stringFromBytes)
118+
case 1:
119+
lengthExtracts = append(lengthExtracts, extract)
114120
}
115121
}
122+
123+
for _, extract := range lengthExtracts {
124+
extract.ReplaceAllUsesWith(call.Operand(1))
125+
extract.EraseFromParentAsInstruction()
126+
}
116127
removeDeadStringFromBytes(call)
117128
}
118129
}

transform/rtcalls_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,6 @@ func TestOptimizeStringFromBytesStringLess(t *testing.T) {
4040
func TestOptimizeStringFromBytesLen(t *testing.T) {
4141
t.Parallel()
4242
testTransform(t, "testdata/stringfrombytes-len", func(mod llvm.Module) {
43-
// TODO: optimize the []byte-to-string conversion away.
43+
transform.OptimizeStringFromBytes(mod)
4444
})
4545
}

transform/testdata/stringfrombytes-len.out.ll

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,13 @@ declare void @useString(ptr, i32)
1212
define i32 @main.stringFromBytesLen(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) {
1313
entry:
1414
%stackalloc = alloca i8, align 1
15-
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
16-
%1 = extractvalue %runtime._string %0, 0
17-
call void @runtime.trackPointer(ptr %1, ptr %stackalloc, ptr undef)
18-
%2 = extractvalue %runtime._string %0, 1
19-
%len = extractvalue %runtime._string %0, 1
20-
ret i32 %len
15+
ret i32 %a.len
2116
}
2217

2318
define i32 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %context) {
2419
entry:
2520
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
2621
%1 = extractvalue %runtime._string %0, 0
27-
%2 = extractvalue %runtime._string %0, 1
28-
call void @useString(ptr %1, i32 %2)
29-
%len = extractvalue %runtime._string %0, 1
30-
ret i32 %len
22+
call void @useString(ptr %1, i32 %a.len)
23+
ret i32 %a.len
3124
}

transform/testdata/stringfrombytes-stringequal.out.ll

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -31,31 +31,25 @@ define i1 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b
3131
entry:
3232
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
3333
%1 = extractvalue %runtime._string %0, 0
34-
%2 = extractvalue %runtime._string %0, 1
35-
call void @useString(ptr %1, i32 %2)
36-
%3 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %b.data, i32 %b.len, ptr undef)
37-
ret i1 %3
34+
call void @useString(ptr %1, i32 %a.len)
35+
%2 = call i1 @runtime.stringEqual(ptr %1, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef)
36+
ret i1 %2
3837
}
3938

4039
define i32 @main.equalAndLen(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) {
4140
entry:
42-
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
43-
%1 = extractvalue %runtime._string %0, 0
44-
%2 = extractvalue %runtime._string %0, 1
4541
%equal = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef)
46-
%len = extractvalue %runtime._string %0, 1
4742
%equal.ext = zext i1 %equal to i32
48-
%result = add i32 %len, %equal.ext
43+
%result = add i32 %a.len, %equal.ext
4944
ret i32 %result
5045
}
5146

5247
define i1 @main.equalBeforeOtherUse(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %s.data, i32 %s.len, ptr %context) {
5348
entry:
5449
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
5550
%1 = extractvalue %runtime._string %0, 0
56-
%2 = extractvalue %runtime._string %0, 1
5751
%equal = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef)
58-
call void @useString(ptr %1, i32 %2)
52+
call void @useString(ptr %1, i32 %a.len)
5953
ret i1 %equal
6054
}
6155

@@ -71,10 +65,9 @@ define i1 @main.keepComparisonAfterMutation(ptr %a.data, i32 %a.len, i32 %a.cap,
7165
entry:
7266
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
7367
%1 = extractvalue %runtime._string %0, 0
74-
%2 = extractvalue %runtime._string %0, 1
7568
%equal1 = call i1 @runtime.stringEqual(ptr %a.data, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef)
7669
store i8 1, ptr %a.data, align 1
77-
%equal2 = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef)
70+
%equal2 = call i1 @runtime.stringEqual(ptr %1, i32 %a.len, ptr %s.data, i32 %s.len, ptr undef)
7871
%result = and i1 %equal1, %equal2
7972
ret i1 %result
8073
}
@@ -84,9 +77,8 @@ entry:
8477
%a.data = alloca [4 x i8], align 1
8578
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 4, i32 4, ptr undef)
8679
%1 = extractvalue %runtime._string %0, 0
87-
%2 = extractvalue %runtime._string %0, 1
8880
call void @llvm.lifetime.end.p0(ptr %a.data)
89-
%equal = call i1 @runtime.stringEqual(ptr %1, i32 %2, ptr %s.data, i32 %s.len, ptr undef)
81+
%equal = call i1 @runtime.stringEqual(ptr %1, i32 4, ptr %s.data, i32 %s.len, ptr undef)
9082
ret i1 %equal
9183
}
9284

transform/testdata/stringfrombytes-stringless.out.ll

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,7 @@ define i1 @main.keepStringConversion(ptr %a.data, i32 %a.len, i32 %a.cap, ptr %b
3434
entry:
3535
%0 = call %runtime._string @runtime.stringFromBytes(ptr %a.data, i32 %a.len, i32 %a.cap, ptr undef)
3636
%1 = extractvalue %runtime._string %0, 0
37-
%2 = extractvalue %runtime._string %0, 1
38-
call void @useString(ptr %1, i32 %2)
39-
%3 = call i1 @runtime.stringLess(ptr %1, i32 %2, ptr %b.data, i32 %b.len, ptr undef)
40-
ret i1 %3
37+
call void @useString(ptr %1, i32 %a.len)
38+
%2 = call i1 @runtime.stringLess(ptr %1, i32 %a.len, ptr %b.data, i32 %b.len, ptr undef)
39+
ret i1 %2
4140
}

0 commit comments

Comments
 (0)