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.
Этот коммит содержится в:
Ayke van Laethem 2023-09-21 15:39:06 +02:00 коммит произвёл Ron Evans
родитель 081d2e58c6
коммит a896f7f218
3 изменённых файлов: 44 добавлений и 6 удалений

Просмотреть файл

@ -28,32 +28,44 @@ func OptimizeStringToBytes(mod llvm.Module) {
// strptr is always constant because strings are always constant.
convertedAllUses := true
var pointerUses []llvm.Value
canConvertPointer := true
for _, use := range getUses(call) {
if use.IsAExtractValueInst().IsNil() {
// Expected an extractvalue, but this is something else.
convertedAllUses = false
canConvertPointer = false
continue
}
switch use.Type().TypeKind() {
case llvm.IntegerTypeKind:
// 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.EraseFromParentAsInstruction()
case llvm.PointerTypeKind:
// The string pointer itself.
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
}
use.ReplaceAllUsesWith(strptr)
use.EraseFromParentAsInstruction()
// It may be that the pointer value can be propagated, if all of
// the pointer uses are readonly.
pointerUses = append(pointerUses, use)
default:
// should not happen
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
// and the output is constant.
call.EraseFromParentAsInstruction()

16
transform/testdata/stringtobytes.ll предоставленный
Просмотреть файл

@ -30,3 +30,19 @@ entry:
call fastcc void @writeToSlice(ptr %1, i64 %2, i64 %3)
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 предоставленный
Просмотреть файл

@ -22,3 +22,13 @@ entry:
call fastcc void @writeToSlice(ptr %1, i64 6, i64 6)
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
}