transform: fix bug in StringToBytes optimization pass
Previously, this pass would convert any read-only use of a runtime.stringToBytes call to use the original string buffer instead. This is incorrect: if there are any writes to the resulting buffer, none of the slice buffer pointers can be converted to use the original read-only string buffer. This commit fixes that bug and adds a test to prove the new (correct) behavior.
Этот коммит содержится в:
родитель
081d2e58c6
коммит
a896f7f218
3 изменённых файлов: 44 добавлений и 6 удалений
|
@ -28,32 +28,44 @@ func OptimizeStringToBytes(mod llvm.Module) {
|
||||||
|
|
||||||
// strptr is always constant because strings are always constant.
|
// strptr is always constant because strings are always constant.
|
||||||
|
|
||||||
convertedAllUses := true
|
var pointerUses []llvm.Value
|
||||||
|
canConvertPointer := true
|
||||||
for _, use := range getUses(call) {
|
for _, use := range getUses(call) {
|
||||||
if use.IsAExtractValueInst().IsNil() {
|
if use.IsAExtractValueInst().IsNil() {
|
||||||
// Expected an extractvalue, but this is something else.
|
// Expected an extractvalue, but this is something else.
|
||||||
convertedAllUses = false
|
canConvertPointer = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
switch use.Type().TypeKind() {
|
switch use.Type().TypeKind() {
|
||||||
case llvm.IntegerTypeKind:
|
case llvm.IntegerTypeKind:
|
||||||
// A length (len or cap). Propagate the length value.
|
// A length (len or cap). Propagate the length value.
|
||||||
|
// This can always be done because the byte slice is always the
|
||||||
|
// same length as the original string.
|
||||||
use.ReplaceAllUsesWith(strlen)
|
use.ReplaceAllUsesWith(strlen)
|
||||||
use.EraseFromParentAsInstruction()
|
use.EraseFromParentAsInstruction()
|
||||||
case llvm.PointerTypeKind:
|
case llvm.PointerTypeKind:
|
||||||
// The string pointer itself.
|
// The string pointer itself.
|
||||||
if !isReadOnly(use) {
|
if !isReadOnly(use) {
|
||||||
convertedAllUses = false
|
// There is a store to the byte slice. This means that none
|
||||||
|
// of the pointer uses can't be propagated.
|
||||||
|
canConvertPointer = false
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
use.ReplaceAllUsesWith(strptr)
|
// It may be that the pointer value can be propagated, if all of
|
||||||
use.EraseFromParentAsInstruction()
|
// the pointer uses are readonly.
|
||||||
|
pointerUses = append(pointerUses, use)
|
||||||
default:
|
default:
|
||||||
// should not happen
|
// should not happen
|
||||||
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
|
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if convertedAllUses {
|
if canConvertPointer {
|
||||||
|
// All pointer uses are readonly, so they can be converted.
|
||||||
|
for _, use := range pointerUses {
|
||||||
|
use.ReplaceAllUsesWith(strptr)
|
||||||
|
use.EraseFromParentAsInstruction()
|
||||||
|
}
|
||||||
|
|
||||||
// Call to runtime.stringToBytes can be eliminated: both the input
|
// Call to runtime.stringToBytes can be eliminated: both the input
|
||||||
// and the output is constant.
|
// and the output is constant.
|
||||||
call.EraseFromParentAsInstruction()
|
call.EraseFromParentAsInstruction()
|
||||||
|
|
16
transform/testdata/stringtobytes.ll
предоставленный
16
transform/testdata/stringtobytes.ll
предоставленный
|
@ -30,3 +30,19 @@ entry:
|
||||||
call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3)
|
call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3)
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
; Test that pointer values are never propagated if there is even a single write
|
||||||
|
; to the pointer value (but len/cap values still can be).
|
||||||
|
define void @testReadSome() {
|
||||||
|
entry:
|
||||||
|
%s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
|
||||||
|
%s.ptr = extractvalue { ptr, i64, i64 } %s, 0
|
||||||
|
%s.len = extractvalue { ptr, i64, i64 } %s, 1
|
||||||
|
%s.cap = extractvalue { ptr, i64, i64 } %s, 2
|
||||||
|
call fastcc void @writeToSlice(ptr %s.ptr, i64 %s.len, i64 %s.cap)
|
||||||
|
%s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0
|
||||||
|
%s.len2 = extractvalue { ptr, i64, i64 } %s, 1
|
||||||
|
%s.cap2 = extractvalue { ptr, i64, i64 } %s, 2
|
||||||
|
call fastcc void @printSlice(ptr %s.ptr2, i64 %s.len2, i64 %s.cap2)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
10
transform/testdata/stringtobytes.out.ll
предоставленный
10
transform/testdata/stringtobytes.out.ll
предоставленный
|
@ -22,3 +22,13 @@ entry:
|
||||||
call fastcc void @writeToSlice(ptr %1, i64 6, i64 6)
|
call fastcc void @writeToSlice(ptr %1, i64 6, i64 6)
|
||||||
ret void
|
ret void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
define void @testReadSome() {
|
||||||
|
entry:
|
||||||
|
%s = call fastcc { ptr, i64, i64 } @runtime.stringToBytes(ptr @str, i64 6)
|
||||||
|
%s.ptr = extractvalue { ptr, i64, i64 } %s, 0
|
||||||
|
call fastcc void @writeToSlice(ptr %s.ptr, i64 6, i64 6)
|
||||||
|
%s.ptr2 = extractvalue { ptr, i64, i64 } %s, 0
|
||||||
|
call fastcc void @printSlice(ptr %s.ptr2, i64 6, i64 6)
|
||||||
|
ret void
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче