This repository has been archived by the owner on Dec 14, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 80
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allocation stack lowering based on escape analysis
Now that we can run libgo benchmarks: https://codereview.appspot.com/103550047/ we can measure the effect of optimizations to see if they are worth adding to the compiler. This introduces an ssaopt package to hold SSA-level optimizations, to which we add an allocation stack lowering pass based on a simple intraprocedural escape analysis. In an experiment comparing libgo benchmark performance before and after this change, this pass yielded a geo-mean 5.4% (3.6%-7.2% at a 95% confidence interval) improvement in performance.
- Loading branch information
Showing
3 changed files
with
98 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
// Copyright 2014 The llgo Authors. | ||
// Use of this source code is governed by an MIT-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package ssaopt | ||
|
||
import ( | ||
"go/token" | ||
|
||
"code.google.com/p/go.tools/go/ssa" | ||
) | ||
|
||
func escapes(val ssa.Value, bb *ssa.BasicBlock, pending []ssa.Value) bool { | ||
for _, p := range pending { | ||
if val == p { | ||
return false | ||
} | ||
} | ||
|
||
for _, ref := range *val.Referrers() { | ||
switch ref := ref.(type) { | ||
case *ssa.Phi: | ||
// We must consider the variable to have escaped if it is | ||
// possible for the program to see more than one "version" | ||
// of the variable at once, as this requires the program | ||
// to use heap allocation for the multiple versions. | ||
// | ||
// I (pcc) think that this is only possible (without stores) | ||
// in the case where a phi node that (directly or indirectly) | ||
// refers to the allocation dominates the allocation. | ||
if ref.Block().Dominates(bb) { | ||
return true | ||
} | ||
if escapes(ref, bb, append(pending, val)) { | ||
return true | ||
} | ||
|
||
case *ssa.BinOp, *ssa.ChangeType, *ssa.Convert, *ssa.ChangeInterface, *ssa.MakeInterface, *ssa.Slice, *ssa.FieldAddr, *ssa.IndexAddr, *ssa.TypeAssert, *ssa.Extract: | ||
if escapes(ref.(ssa.Value), bb, append(pending, val)) { | ||
return true | ||
} | ||
|
||
case *ssa.Range, *ssa.DebugRef: | ||
continue | ||
|
||
case *ssa.UnOp: | ||
if ref.Op == token.MUL || ref.Op == token.ARROW { | ||
continue | ||
} | ||
if escapes(ref, bb, append(pending, val)) { | ||
return true | ||
} | ||
|
||
case *ssa.Store: | ||
if val == ref.Val { | ||
return true | ||
} | ||
|
||
case *ssa.Call: | ||
if builtin, ok := ref.Call.Value.(*ssa.Builtin); ok { | ||
switch builtin.Name() { | ||
case "cap", "len", "copy", "ssa:wrapnilchk": | ||
continue | ||
case "append": | ||
if ref.Call.Args[0] == val && escapes(ref, bb, append(pending, val)) { | ||
return true | ||
} | ||
default: | ||
return true | ||
} | ||
} else { | ||
return true | ||
} | ||
|
||
default: | ||
return true | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
func LowerAllocsToStack(f *ssa.Function) { | ||
pending := make([]ssa.Value, 0, 10) | ||
|
||
for _, b := range f.Blocks { | ||
for _, instr := range b.Instrs { | ||
if alloc, ok := instr.(*ssa.Alloc); ok && alloc.Heap && !escapes(alloc, alloc.Block(), pending) { | ||
alloc.Heap = false | ||
f.Locals = append(f.Locals, alloc) | ||
} | ||
} | ||
} | ||
} |