compiler: optimize runtime.stringToBytes calls
This optimization makes sure the following pattern doesn't do a heap allocation (assuming Write doesn't modify the slice): var w *machine.UART = ... w.Write([]byte("foo")) As long as Write doesn't modify the slice and LLVM can detect this, a call to runtime.stringToBytes with the necessary allocation + copy is avoided.
Этот коммит содержится в:
родитель
4219652535
коммит
ec73bd6a26
1 изменённых файлов: 103 добавлений и 19 удалений
|
@ -37,6 +37,7 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
|
|||
goPasses.Run(c.mod)
|
||||
|
||||
// Run Go-specific optimization passes.
|
||||
c.OptimizeStringToBytes()
|
||||
c.OptimizeAllocs()
|
||||
c.Verify()
|
||||
}
|
||||
|
@ -48,6 +49,56 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) {
|
|||
modPasses.Run(c.mod)
|
||||
}
|
||||
|
||||
// Transform runtime.stringToBytes(...) calls into const []byte slices whenever
|
||||
// possible. This optimizes the following pattern:
|
||||
// w.Write([]byte("foo"))
|
||||
// where Write does not store to the slice.
|
||||
func (c *Compiler) OptimizeStringToBytes() {
|
||||
stringToBytes := c.mod.NamedFunction("runtime.stringToBytes")
|
||||
if stringToBytes.IsNil() {
|
||||
// nothing to optimize
|
||||
return
|
||||
}
|
||||
|
||||
for _, call := range getUses(stringToBytes) {
|
||||
strptr := call.Operand(0)
|
||||
strlen := call.Operand(1)
|
||||
|
||||
// strptr is always constant because strings are always constant.
|
||||
|
||||
convertedAllUses := true
|
||||
for _, use := range getUses(call) {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAExtractValueInst() == nilValue {
|
||||
convertedAllUses = false
|
||||
continue
|
||||
}
|
||||
switch use.Type().TypeKind() {
|
||||
case llvm.IntegerTypeKind:
|
||||
// A length (len or cap). Propagate the length value.
|
||||
use.ReplaceAllUsesWith(strlen)
|
||||
use.EraseFromParentAsInstruction()
|
||||
case llvm.PointerTypeKind:
|
||||
// The string pointer itself.
|
||||
if !c.isReadOnly(use) {
|
||||
convertedAllUses = false
|
||||
continue
|
||||
}
|
||||
use.ReplaceAllUsesWith(strptr)
|
||||
use.EraseFromParentAsInstruction()
|
||||
default:
|
||||
// should not happen
|
||||
panic("unknown return type of runtime.stringToBytes: " + use.Type().String())
|
||||
}
|
||||
}
|
||||
if convertedAllUses {
|
||||
// Call to runtime.stringToBytes can be eliminated: both the input
|
||||
// and the output is constant.
|
||||
call.EraseFromParentAsInstruction()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basic escape analysis: translate runtime.alloc calls into alloca
|
||||
// instructions.
|
||||
func (c *Compiler) OptimizeAllocs() {
|
||||
|
@ -122,25 +173,8 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
|
|||
// Call only escapes when the (pointer) parameter is not marked
|
||||
// "nocapture". This flag means that the parameter does not escape
|
||||
// the give function.
|
||||
fn := use.CalledValue()
|
||||
if fn.IsAFunction() == nilValue {
|
||||
// This is not a function but something else, like a function
|
||||
// pointer.
|
||||
return false
|
||||
}
|
||||
nocaptureKind := llvm.AttributeKindID("nocapture")
|
||||
for i := 0; i < fn.ParamsCount(); i++ {
|
||||
if use.Operand(i) != value {
|
||||
// This is not the parameter we're checking.
|
||||
continue
|
||||
}
|
||||
index := i + 1 // param attributes start at 1
|
||||
nocapture := fn.GetEnumAttributeAtIndex(index, nocaptureKind)
|
||||
nilAttribute := llvm.Attribute{}
|
||||
if nocapture == nilAttribute {
|
||||
// Parameter doesn't have the nocapture flag, so may escape.
|
||||
return true
|
||||
}
|
||||
if !c.hasFlag(use, value, "nocapture") {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
// Unknown instruction, might escape.
|
||||
|
@ -152,6 +186,56 @@ func (c *Compiler) doesEscape(value llvm.Value) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Check whether the given value (which is of pointer type) is never stored to.
|
||||
func (c *Compiler) isReadOnly(value llvm.Value) bool {
|
||||
uses := getUses(value)
|
||||
for _, use := range uses {
|
||||
nilValue := llvm.Value{}
|
||||
if use.IsAGetElementPtrInst() != nilValue {
|
||||
if !c.isReadOnly(use) {
|
||||
return false
|
||||
}
|
||||
} else if use.IsACallInst() != nilValue {
|
||||
if !c.hasFlag(use, value, "readonly") {
|
||||
return false
|
||||
}
|
||||
} else {
|
||||
// Unknown instruction, might not be readonly.
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Check whether all uses of this param as parameter to the call have the given
|
||||
// flag. In most cases, there will only be one use but a function could take the
|
||||
// same parameter twice, in which case both must have the flag.
|
||||
// A flag can be any enum flag, like "readonly".
|
||||
func (c *Compiler) hasFlag(call, param llvm.Value, kind string) bool {
|
||||
fn := call.CalledValue()
|
||||
nilValue := llvm.Value{}
|
||||
if fn.IsAFunction() == nilValue {
|
||||
// This is not a function but something else, like a function pointer.
|
||||
return false
|
||||
}
|
||||
kindID := llvm.AttributeKindID(kind)
|
||||
for i := 0; i < fn.ParamsCount(); i++ {
|
||||
if call.Operand(i) != param {
|
||||
// This is not the parameter we're checking.
|
||||
continue
|
||||
}
|
||||
index := i + 1 // param attributes start at 1
|
||||
attr := fn.GetEnumAttributeAtIndex(index, kindID)
|
||||
nilAttribute := llvm.Attribute{}
|
||||
if attr == nilAttribute {
|
||||
// At least one parameter doesn't have the flag (there may be
|
||||
// multiple).
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Return a list of values (actually, instructions) where this value is used as
|
||||
// an operand.
|
||||
func getUses(value llvm.Value) []llvm.Value {
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче