From d653088cbee1466cad38b00ee676773090934bbe Mon Sep 17 00:00:00 2001 From: Ayke van Laethem Date: Sat, 30 Mar 2019 13:14:04 +0100 Subject: [PATCH] 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. --- compiler/asserts.go | 19 +++++++++++++++++-- compiler/compiler.go | 5 +++++ compiler/optimizer.go | 16 ++++++++++++++++ src/runtime/panic.go | 8 ++++++++ 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/compiler/asserts.go b/compiler/asserts.go index 2b01e696..e8e183d6 100644 --- a/compiler/asserts.go +++ b/compiler/asserts.go @@ -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. diff --git a/compiler/compiler.go b/compiler/compiler.go index b27d23ba..93b3f224 100644 --- a/compiler/compiler.go +++ b/compiler/compiler.go @@ -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", diff --git a/compiler/optimizer.go b/compiler/optimizer.go index 57d2593c..0a6f8f59 100644 --- a/compiler/optimizer.go +++ b/compiler/optimizer.go @@ -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 diff --git a/src/runtime/panic.go b/src/runtime/panic.go index ba8c1018..16bb75d7 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -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")