Skip to content

Commit 2d5ff4c

Browse files
author
ian
committed
compiler, runtime: allocate defer records on the stack
When a defer is executed at most once in a function body, we can allocate the defer record for it on the stack instead of on the heap. This should make defers like this (which are very common) faster. This is a port of CL 171758 from the gc repo. Reviewed-on: https://go-review.googlesource.com/c/gofrontend/+/190410 git-svn-id: svn+ssh://gcc.gnu.org/svn/gcc/trunk@274613 138bc75d-0d04-0410-961f-82ee72b054a4
1 parent f1d127f commit 2d5ff4c

File tree

8 files changed

+176
-5
lines changed

8 files changed

+176
-5
lines changed

gcc/go/gofrontend/MERGE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
838f926c93898767f0337122725a4f52a1335186
1+
4b47cadf938caadf563f8d0bb3f7111d06f61752
22

33
The first line of this file holds the git revision number of the last
44
merge done from the gofrontend repository.

gcc/go/gofrontend/runtime.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,10 @@ DEF_GO_RUNTIME(GO, "__go_go", P2(UINTPTR, POINTER), R1(POINTER))
287287
DEF_GO_RUNTIME(DEFERPROC, "runtime.deferproc", P3(BOOLPTR, UINTPTR, POINTER),
288288
R0())
289289

290+
// Defer a function, with stack-allocated defer structure.
291+
DEF_GO_RUNTIME(DEFERPROCSTACK, "runtime.deferprocStack",
292+
P4(POINTER, BOOLPTR, UINTPTR, POINTER), R0())
293+
290294

291295
// Convert an empty interface to an empty interface, returning ok.
292296
DEF_GO_RUNTIME(IFACEE2E2, "runtime.ifaceE2E2", P1(EFACE), R2(EFACE, BOOL))

gcc/go/gofrontend/statements.cc

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2614,7 +2614,11 @@ Thunk_statement::simplify_statement(Gogo* gogo, Named_object* function,
26142614
if (this->classification() == STATEMENT_GO)
26152615
s = Statement::make_go_statement(call, location);
26162616
else if (this->classification() == STATEMENT_DEFER)
2617-
s = Statement::make_defer_statement(call, location);
2617+
{
2618+
s = Statement::make_defer_statement(call, location);
2619+
if ((Node::make_node(this)->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE)
2620+
s->defer_statement()->set_on_stack();
2621+
}
26182622
else
26192623
go_unreachable();
26202624

@@ -3019,13 +3023,45 @@ Defer_statement::do_get_backend(Translate_context* context)
30193023
Location loc = this->location();
30203024
Expression* ds = context->function()->func_value()->defer_stack(loc);
30213025

3022-
Expression* call = Runtime::make_call(Runtime::DEFERPROC, loc, 3,
3023-
ds, fn, arg);
3026+
Expression* call;
3027+
if (this->on_stack_)
3028+
{
3029+
if (context->gogo()->debug_optimization())
3030+
go_debug(loc, "stack allocated defer");
3031+
3032+
Type* defer_type = Defer_statement::defer_struct_type();
3033+
Expression* defer = Expression::make_allocation(defer_type, loc);
3034+
defer->allocation_expression()->set_allocate_on_stack();
3035+
defer->allocation_expression()->set_no_zero();
3036+
call = Runtime::make_call(Runtime::DEFERPROCSTACK, loc, 4,
3037+
defer, ds, fn, arg);
3038+
}
3039+
else
3040+
call = Runtime::make_call(Runtime::DEFERPROC, loc, 3,
3041+
ds, fn, arg);
30243042
Bexpression* bcall = call->get_backend(context);
30253043
Bfunction* bfunction = context->function()->func_value()->get_decl();
30263044
return context->backend()->expression_statement(bfunction, bcall);
30273045
}
30283046

3047+
Type*
3048+
Defer_statement::defer_struct_type()
3049+
{
3050+
Type* ptr_type = Type::make_pointer_type(Type::make_void_type());
3051+
Type* uintptr_type = Type::lookup_integer_type("uintptr");
3052+
Type* bool_type = Type::make_boolean_type();
3053+
return Type::make_builtin_struct_type(9,
3054+
"link", ptr_type,
3055+
"frame", ptr_type,
3056+
"panicStack", ptr_type,
3057+
"_panic", ptr_type,
3058+
"pfn", uintptr_type,
3059+
"arg", ptr_type,
3060+
"retaddr", uintptr_type,
3061+
"makefunccanrecover", bool_type,
3062+
"heap", bool_type);
3063+
}
3064+
30293065
// Dump the AST representation for defer statement.
30303066

30313067
void

gcc/go/gofrontend/statements.h

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ class Expression_statement;
2424
class Block_statement;
2525
class Return_statement;
2626
class Thunk_statement;
27+
class Defer_statement;
2728
class Goto_statement;
2829
class Goto_unnamed_statement;
2930
class Label_statement;
@@ -403,6 +404,11 @@ class Statement
403404
Thunk_statement*
404405
thunk_statement();
405406

407+
// If this is a defer statement, return it. Otherwise return NULL.
408+
Defer_statement*
409+
defer_statement()
410+
{ return this->convert<Defer_statement, STATEMENT_DEFER>(); }
411+
406412
// If this is a goto statement, return it. Otherwise return NULL.
407413
Goto_statement*
408414
goto_statement()
@@ -1419,15 +1425,26 @@ class Defer_statement : public Thunk_statement
14191425
{
14201426
public:
14211427
Defer_statement(Call_expression* call, Location location)
1422-
: Thunk_statement(STATEMENT_DEFER, call, location)
1428+
: Thunk_statement(STATEMENT_DEFER, call, location),
1429+
on_stack_(false)
14231430
{ }
14241431

1432+
void
1433+
set_on_stack()
1434+
{ this->on_stack_ = true; }
1435+
14251436
protected:
14261437
Bstatement*
14271438
do_get_backend(Translate_context*);
14281439

14291440
void
14301441
do_dump_statement(Ast_dump_context*) const;
1442+
1443+
private:
1444+
static Type*
1445+
defer_struct_type();
1446+
1447+
bool on_stack_;
14311448
};
14321449

14331450
// A goto statement.

libgo/go/runtime/mgcmark.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,11 @@ func scanstack(gp *g, gcw *gcWork) {
657657
scanstackblock(uintptr(unsafe.Pointer(&gp.context)), unsafe.Sizeof(gp.context), gcw)
658658
}
659659

660+
// Note: in the gc runtime scanstack also scans defer records.
661+
// This is necessary as it uses stack objects (a.k.a. stack tracing).
662+
// We don't (yet) do stack objects, and regular stack/heap scan
663+
// will take care of defer records just fine.
664+
660665
gp.gcscanvalid = true
661666
}
662667

libgo/go/runtime/panic.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
// themselves, so that the compiler will export them.
1414
//
1515
//go:linkname deferproc runtime.deferproc
16+
//go:linkname deferprocStack runtime.deferprocStack
1617
//go:linkname deferreturn runtime.deferreturn
1718
//go:linkname setdeferretaddr runtime.setdeferretaddr
1819
//go:linkname checkdefer runtime.checkdefer
@@ -124,6 +125,38 @@ func deferproc(frame *bool, pfn uintptr, arg unsafe.Pointer) {
124125
d.makefunccanrecover = false
125126
}
126127

128+
// deferprocStack queues a new deferred function with a defer record on the stack.
129+
// The defer record, d, does not need to be initialized.
130+
// Other arguments are the same as in deferproc.
131+
//go:nosplit
132+
func deferprocStack(d *_defer, frame *bool, pfn uintptr, arg unsafe.Pointer) {
133+
gp := getg()
134+
if gp.m.curg != gp {
135+
// go code on the system stack can't defer
136+
throw("defer on system stack")
137+
}
138+
d.pfn = pfn
139+
d.retaddr = 0
140+
d.makefunccanrecover = false
141+
d.heap = false
142+
// The lines below implement:
143+
// d.frame = frame
144+
// d.arg = arg
145+
// d._panic = nil
146+
// d.panicStack = gp._panic
147+
// d.link = gp._defer
148+
// But without write barriers. They are writes to the stack so they
149+
// don't need a write barrier, and furthermore are to uninitialized
150+
// memory, so they must not use a write barrier.
151+
*(*uintptr)(unsafe.Pointer(&d.frame)) = uintptr(unsafe.Pointer(frame))
152+
*(*uintptr)(unsafe.Pointer(&d.arg)) = uintptr(unsafe.Pointer(arg))
153+
*(*uintptr)(unsafe.Pointer(&d._panic)) = 0
154+
*(*uintptr)(unsafe.Pointer(&d.panicStack)) = uintptr(unsafe.Pointer(gp._panic))
155+
*(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer))
156+
157+
gp._defer = d
158+
}
159+
127160
// Allocate a Defer, usually using per-P pool.
128161
// Each defer must be released with freedefer.
129162
func newdefer() *_defer {
@@ -155,11 +188,13 @@ func newdefer() *_defer {
155188
// Duplicate the tail below so if there's a
156189
// crash in checkPut we can tell if d was just
157190
// allocated or came from the pool.
191+
d.heap = true
158192
d.link = gp._defer
159193
gp._defer = d
160194
return d
161195
}
162196
}
197+
d.heap = true
163198
d.link = gp._defer
164199
gp._defer = d
165200
return d
@@ -179,6 +214,9 @@ func freedefer(d *_defer) {
179214
if d.pfn != 0 {
180215
freedeferfn()
181216
}
217+
if !d.heap {
218+
return
219+
}
182220
pp := getg().m.p.ptr()
183221
if len(pp.deferpool) == cap(pp.deferpool) {
184222
// Transfer half of local cache to the central cache.

libgo/go/runtime/runtime2.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,12 @@ func extendRandom(r []byte, n int) {
746746

747747
// A _defer holds an entry on the list of deferred calls.
748748
// If you add a field here, add code to clear it in freedefer.
749+
// This struct must match the code in Defer_statement::defer_struct_type
750+
// in the compiler.
751+
// Some defers will be allocated on the stack and some on the heap.
752+
// All defers are logically part of the stack, so write barriers to
753+
// initialize them are not required. All defers must be manually scanned,
754+
// and for heap defers, marked.
749755
type _defer struct {
750756
// The next entry in the stack.
751757
link *_defer
@@ -781,6 +787,9 @@ type _defer struct {
781787
// function function will be somewhere in libffi, so __retaddr
782788
// is not useful.
783789
makefunccanrecover bool
790+
791+
// Whether the _defer is heap allocated.
792+
heap bool
784793
}
785794

786795
// panics

libgo/go/runtime/stack_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright 2019 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package runtime_test
6+
7+
import "testing"
8+
9+
func TestDeferHeapAndStack(t *testing.T) {
10+
P := 4 // processors
11+
N := 10000 // iterations
12+
D := 200 // stack depth
13+
14+
if testing.Short() {
15+
P /= 2
16+
N /= 10
17+
D /= 10
18+
}
19+
c := make(chan bool)
20+
for p := 0; p < P; p++ {
21+
go func() {
22+
for i := 0; i < N; i++ {
23+
if deferHeapAndStack(D) != 2*D {
24+
panic("bad result")
25+
}
26+
}
27+
c <- true
28+
}()
29+
}
30+
for p := 0; p < P; p++ {
31+
<-c
32+
}
33+
}
34+
35+
// deferHeapAndStack(n) computes 2*n
36+
func deferHeapAndStack(n int) (r int) {
37+
if n == 0 {
38+
return 0
39+
}
40+
if n%2 == 0 {
41+
// heap-allocated defers
42+
for i := 0; i < 2; i++ {
43+
defer func() {
44+
r++
45+
}()
46+
}
47+
} else {
48+
// stack-allocated defers
49+
defer func() {
50+
r++
51+
}()
52+
defer func() {
53+
r++
54+
}()
55+
}
56+
r = deferHeapAndStack(n - 1)
57+
escapeMe(new([1024]byte)) // force some GCs
58+
return
59+
}
60+
61+
// Pass a value to escapeMe to force it to escape.
62+
var escapeMe = func(x interface{}) {}

0 commit comments

Comments
 (0)