compiler: fix escapes due to nil checks

Some tests get bigger, most get smaller. However, all tested driver
examples get smaller in size showing that this is a good change in the
real world.
Этот коммит содержится в:
Ayke van Laethem 2019-03-30 13:14:04 +01:00 коммит произвёл Ron Evans
родитель cd8471acae
коммит d653088cbe
4 изменённых файлов: 46 добавлений и 2 удалений

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

@ -115,8 +115,23 @@ func (c *Compiler) emitNilCheck(frame *Frame, ptr llvm.Value, blockPrefix string
frame.blockExits[frame.currentBlock] = nextBlock // adjust outgoing block for phi nodes
// Compare against nil.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
var isnil llvm.Value
if ptr.Type().PointerAddressSpace() == 0 {
// Do the nil check using the isnil builtin, which marks the parameter
// as nocapture.
// The reason it has to go through a builtin, is that a regular icmp
// instruction may capture the pointer in LLVM semantics, see
// https://reviews.llvm.org/D60047 for details. Pointer capturing
// unfortunately breaks escape analysis, so we use this trick to let the
// functionattr pass know that this pointer doesn't really escape.
ptr = c.builder.CreateBitCast(ptr, c.i8ptrType, "")
isnil = c.createRuntimeCall("isnil", []llvm.Value{ptr}, "")
} else {
// Do the nil check using a regular icmp. This can happen with function
// pointers on AVR, which don't benefit from escape analysis anyway.
nilptr := llvm.ConstPointerNull(ptr.Type())
isnil = c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
}
c.builder.CreateCondBr(isnil, faultBlock, nextBlock)
// Fail: this is a nil pointer, exit with a panic.

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

@ -379,6 +379,11 @@ func (c *Compiler) Compile(mainPath string) error {
c.mod.NamedFunction("runtime.alloc").AddAttributeAtIndex(0, attr)
}
// See emitNilCheck in asserts.go.
attrKind := llvm.AttributeKindID("nocapture")
attr := c.ctx.CreateEnumAttribute(attrKind, 0)
c.mod.NamedFunction("runtime.isnil").AddAttributeAtIndex(1, attr)
// see: https://reviews.llvm.org/D18355
if c.Debug {
c.mod.AddNamedMetadataOperand("llvm.module.flags",

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

@ -53,6 +53,22 @@ func (c *Compiler) Optimize(optLevel, sizeLevel int, inlinerThreshold uint) erro
c.OptimizeAllocs()
c.OptimizeStringToBytes()
// Lower runtime.isnil calls to regular nil comparisons.
isnil := c.mod.NamedFunction("runtime.isnil")
if !isnil.IsNil() {
for _, use := range getUses(isnil) {
c.builder.SetInsertPointBefore(use)
ptr := use.Operand(0)
if !ptr.IsABitCastInst().IsNil() {
ptr = ptr.Operand(0)
}
nilptr := llvm.ConstPointerNull(ptr.Type())
icmp := c.builder.CreateICmp(llvm.IntEQ, ptr, nilptr, "")
use.ReplaceAllUsesWith(icmp)
use.EraseFromParentAsInstruction()
}
}
err := c.LowerGoroutines()
if err != nil {
return err

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

@ -27,6 +27,14 @@ func _recover() interface{} {
return nil
}
// See emitNilCheck in compiler/asserts.go.
// This function is a dummy function that has its first and only parameter
// marked 'nocapture' to work around a limitation in LLVM: a regular pointer
// comparison captures the pointer.
func isnil(ptr *uint8) bool {
return ptr == nil
}
// Panic when trying to dereference a nil pointer.
func nilpanic() {
runtimePanic("nil pointer dereference")