Skip to content

Allow larger systems to have a larger max stack alloc #3967

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builder/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func Build(pkgName, outpath, tmpdir string, config *compileopts.Config) (BuildRe
Scheduler: config.Scheduler(),
AutomaticStackSize: config.AutomaticStackSize(),
DefaultStackSize: config.StackSize(),
MaxStackAlloc: config.MaxStackAlloc(),
NeedsStackObjects: config.NeedsStackObjects(),
Debug: !config.Options.SkipDWARF, // emit DWARF except when -internal-nodwarf is passed
}
Expand Down
9 changes: 9 additions & 0 deletions compileopts/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,15 @@ func (c *Config) StackSize() uint64 {
return c.Target.DefaultStackSize
}

// MaxStackAlloc returns the size of the maximum allocation to put on the stack vs heap.
func (c *Config) MaxStackAlloc() uint64 {
if c.StackSize() > 32*1024 {
return 1024
}

return 256
}

// RP2040BootPatch returns whether the RP2040 boot patch should be applied that
// calculates and patches in the checksum for the 2nd stage bootloader.
func (c *Config) RP2040BootPatch() bool {
Expand Down
5 changes: 3 additions & 2 deletions compiler/compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ type Config struct {
Scheduler string
AutomaticStackSize bool
DefaultStackSize uint64
MaxStackAlloc uint64
NeedsStackObjects bool
Debug bool // Whether to emit debug information in the LLVM module.
}
Expand Down Expand Up @@ -1997,8 +1998,8 @@ func (b *builder) createExpr(expr ssa.Value) (llvm.Value, error) {
case *ssa.Alloc:
typ := b.getLLVMType(expr.Type().Underlying().(*types.Pointer).Elem())
size := b.targetData.TypeAllocSize(typ)
// Move all "large" allocations to the heap. This value is also transform.maxStackAlloc.
if expr.Heap || size > 256 {
// Move all "large" allocations to the heap.
if expr.Heap || size > b.MaxStackAlloc {
// Calculate ^uintptr(0)
maxSize := llvm.ConstNot(llvm.ConstInt(b.uintptrType, 0, false)).ZExtValue()
if size > maxSize {
Expand Down
10 changes: 1 addition & 9 deletions transform/allocs.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,14 @@ import (
"tinygo.org/x/go-llvm"
)

// maxStackAlloc is the maximum size of an object that will be allocated on the
// stack. Bigger objects have increased risk of stack overflows and thus will
// always be heap allocated.
//
// TODO: tune this, this is just a random value.
// This value is also used in the compiler when translating ssa.Alloc nodes.
const maxStackAlloc = 256

// OptimizeAllocs tries to replace heap allocations with stack allocations
// whenever possible. It relies on the LLVM 'nocapture' flag for interprocedural
// escape analysis, and within a function looks whether an allocation can escape
// to the heap.
// If printAllocs is non-nil, it indicates the regexp of functions for which a
// heap allocation explanation should be printed (why the object can't be stack
// allocated).
func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, logger func(token.Position, string)) {
func OptimizeAllocs(mod llvm.Module, printAllocs *regexp.Regexp, maxStackAlloc uint64, logger func(token.Position, string)) {
allocator := mod.NamedFunction("runtime.alloc")
if allocator.IsNil() {
// nothing to optimize
Expand Down
4 changes: 2 additions & 2 deletions transform/allocs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
func TestAllocs(t *testing.T) {
t.Parallel()
testTransform(t, "testdata/allocs", func(mod llvm.Module) {
transform.OptimizeAllocs(mod, nil, nil)
transform.OptimizeAllocs(mod, nil, 256, nil)
})
}

Expand Down Expand Up @@ -47,7 +47,7 @@ func TestAllocs2(t *testing.T) {

// Run heap to stack transform.
var testOutputs []allocsTestOutput
transform.OptimizeAllocs(mod, regexp.MustCompile("."), func(pos token.Position, msg string) {
transform.OptimizeAllocs(mod, regexp.MustCompile("."), 256, func(pos token.Position, msg string) {
testOutputs = append(testOutputs, allocsTestOutput{
filename: filepath.Base(pos.Filename),
line: pos.Line,
Expand Down
5 changes: 3 additions & 2 deletions transform/optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
// Run TinyGo-specific optimization passes.
OptimizeStringToBytes(mod)
OptimizeReflectImplements(mod)
OptimizeAllocs(mod, nil, nil)
maxStackSize := config.MaxStackAlloc()
OptimizeAllocs(mod, nil, maxStackSize, nil)
err = LowerInterfaces(mod, config)
if err != nil {
return []error{err}
Expand All @@ -84,7 +85,7 @@ func Optimize(mod llvm.Module, config *compileopts.Config) []error {
}

// Run TinyGo-specific interprocedural optimizations.
OptimizeAllocs(mod, config.Options.PrintAllocs, func(pos token.Position, msg string) {
OptimizeAllocs(mod, config.Options.PrintAllocs, maxStackSize, func(pos token.Position, msg string) {
fmt.Fprintln(os.Stderr, pos.String()+": "+msg)
})
OptimizeStringToBytes(mod)
Expand Down