From patchwork Sat Aug 17 23:43:12 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ian Lance Taylor X-Patchwork-Id: 1148807 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (mailfrom) smtp.mailfrom=gcc.gnu.org (client-ip=209.132.180.131; helo=sourceware.org; envelope-from=gcc-patches-return-507185-incoming=patchwork.ozlabs.org@gcc.gnu.org; receiver=) Authentication-Results: ozlabs.org; dmarc=fail (p=none dis=none) header.from=golang.org Authentication-Results: ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=gcc.gnu.org header.i=@gcc.gnu.org header.b="lJ6o3pYH"; dkim=fail reason="signature verification failed" (2048-bit key; unprotected) header.d=golang-org.20150623.gappssmtp.com header.i=@golang-org.20150623.gappssmtp.com header.b="GCgF5cNj"; dkim-atps=neutral Received: from sourceware.org (server1.sourceware.org [209.132.180.131]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 469xbM3BvYz9sBF for ; Sun, 18 Aug 2019 09:43:40 +1000 (AEST) DomainKey-Signature: a=rsa-sha1; c=nofws; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :mime-version:from:date:message-id:subject:to:content-type; q= dns; s=default; b=G8aUre2pSyS4JqR5HbzAvmTn+3mTVg73Rsa/WyVZhrNALp oc8UqO3Ab1pUEkZO+si89F0vJmWi8V5OBF4VvK6FLnEIpqP/GRIblV1d0jJBeEF0 I+vYd5H6sNRUA51QCE+FJpDVHyU8lG00UmWxMevCOS69Cd8Dny1tH26igHq/c= DKIM-Signature: v=1; a=rsa-sha1; c=relaxed; d=gcc.gnu.org; h=list-id :list-unsubscribe:list-archive:list-post:list-help:sender :mime-version:from:date:message-id:subject:to:content-type; s= default; bh=qXP8R7AIUaWwE55hTs2Cb+FZxEY=; b=lJ6o3pYHF6Aad6trTJwN ZdalJqNqltbcddk7y7J1sIji8bxeGBKKRFeSfQtVhe2SNg1Qh3xkQx8HKSrMElkH IP3oSNBeDna2u402IXxUXTR/FdqaQpqpcYOOJ53f0SRxb5an5Xo0HdR9AUVtP9OL t8wEia7QKmMA7i8clsnyB9Q= Received: (qmail 96679 invoked by alias); 17 Aug 2019 23:43:30 -0000 Mailing-List: contact gcc-patches-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Id: List-Unsubscribe: List-Archive: List-Post: List-Help: Sender: gcc-patches-owner@gcc.gnu.org Delivered-To: mailing list gcc-patches@gcc.gnu.org Received: (qmail 96671 invoked by uid 89); 17 Aug 2019 23:43:30 -0000 Authentication-Results: sourceware.org; auth=none X-Spam-SWARE-Status: No, score=-10.7 required=5.0 tests=AWL, BAYES_00, GIT_PATCH_2, GIT_PATCH_3, KAM_ASCII_DIVIDERS, RCVD_IN_DNSWL_NONE, SPF_PASS autolearn=ham version=3.3.1 spammy=Transfer, runtime.def, HTo:D*googlegroups.com, runtimedef X-HELO: mail-lf1-f47.google.com Received: from mail-lf1-f47.google.com (HELO mail-lf1-f47.google.com) (209.85.167.47) by sourceware.org (qpsmtpd/0.93/v0.84-503-g423c35a) with ESMTP; Sat, 17 Aug 2019 23:43:27 +0000 Received: by mail-lf1-f47.google.com with SMTP id c19so6454021lfm.10 for ; Sat, 17 Aug 2019 16:43:27 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=golang-org.20150623.gappssmtp.com; s=20150623; h=mime-version:from:date:message-id:subject:to; bh=vSMedVTJvgww5mzu28/769vOxjGszc62sIb/E31J6ys=; b=GCgF5cNjAT5dNjkw8NB3J513ZjU54bhgQTTYlQ/ZM8mSp7rKgByXb0cId3kxuO0aIi GOT55TkcMdxmJ+PuLOWgN3ujk1dTw14xzyiKOujba7VFC/mNaTxJelSMaD/BpakTFEs/ xGoPOlpVLN1kFrLqJB+2x3blGPOA2Gf6VvF1cSmSGpheGSl3jUbNfQ0FoJyR7hvepnZP gq7HU6wDOSzMY1CkVkbl3NLEgd6y4qR4dd2ebFMxdDgFf+QSybevZTk0F0ZYourghrlp 9wXInxmlL+9qTxGcaCaGfrrRqGIWVkWOehMp7VVWlZOrzu3XNHqMRugSXG3TeK8+Qad0 VrPQ== MIME-Version: 1.0 From: Ian Lance Taylor Date: Sat, 17 Aug 2019 16:43:12 -0700 Message-ID: Subject: Go patch committed: allocate defer records on the stack To: gcc-patches , gofrontend-dev This Go patch Cherry Zhang allocates 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 of this kind (which are very common) faster. This is a port of https://golang.org/cl/171758 from the gc repo. Bootstrapped and ran Go testsuite on x86_64-pc-linux-gnu. Committed to mainline. Ian Index: gcc/go/gofrontend/MERGE =================================================================== --- gcc/go/gofrontend/MERGE (revision 274598) +++ gcc/go/gofrontend/MERGE (working copy) @@ -1,4 +1,4 @@ -838f926c93898767f0337122725a4f52a1335186 +4b47cadf938caadf563f8d0bb3f7111d06f61752 The first line of this file holds the git revision number of the last merge done from the gofrontend repository. Index: gcc/go/gofrontend/runtime.def =================================================================== --- gcc/go/gofrontend/runtime.def (revision 274591) +++ gcc/go/gofrontend/runtime.def (working copy) @@ -287,6 +287,10 @@ DEF_GO_RUNTIME(GO, "__go_go", P2(UINTPTR DEF_GO_RUNTIME(DEFERPROC, "runtime.deferproc", P3(BOOLPTR, UINTPTR, POINTER), R0()) +// Defer a function, with stack-allocated defer structure. +DEF_GO_RUNTIME(DEFERPROCSTACK, "runtime.deferprocStack", + P4(POINTER, BOOLPTR, UINTPTR, POINTER), R0()) + // Convert an empty interface to an empty interface, returning ok. DEF_GO_RUNTIME(IFACEE2E2, "runtime.ifaceE2E2", P1(EFACE), R2(EFACE, BOOL)) Index: gcc/go/gofrontend/statements.cc =================================================================== --- gcc/go/gofrontend/statements.cc (revision 274169) +++ gcc/go/gofrontend/statements.cc (working copy) @@ -2614,7 +2614,11 @@ Thunk_statement::simplify_statement(Gogo if (this->classification() == STATEMENT_GO) s = Statement::make_go_statement(call, location); else if (this->classification() == STATEMENT_DEFER) - s = Statement::make_defer_statement(call, location); + { + s = Statement::make_defer_statement(call, location); + if ((Node::make_node(this)->encoding() & ESCAPE_MASK) == Node::ESCAPE_NONE) + s->defer_statement()->set_on_stack(); + } else go_unreachable(); @@ -3019,13 +3023,45 @@ Defer_statement::do_get_backend(Translat Location loc = this->location(); Expression* ds = context->function()->func_value()->defer_stack(loc); - Expression* call = Runtime::make_call(Runtime::DEFERPROC, loc, 3, - ds, fn, arg); + Expression* call; + if (this->on_stack_) + { + if (context->gogo()->debug_optimization()) + go_debug(loc, "stack allocated defer"); + + Type* defer_type = Defer_statement::defer_struct_type(); + Expression* defer = Expression::make_allocation(defer_type, loc); + defer->allocation_expression()->set_allocate_on_stack(); + defer->allocation_expression()->set_no_zero(); + call = Runtime::make_call(Runtime::DEFERPROCSTACK, loc, 4, + defer, ds, fn, arg); + } + else + call = Runtime::make_call(Runtime::DEFERPROC, loc, 3, + ds, fn, arg); Bexpression* bcall = call->get_backend(context); Bfunction* bfunction = context->function()->func_value()->get_decl(); return context->backend()->expression_statement(bfunction, bcall); } +Type* +Defer_statement::defer_struct_type() +{ + Type* ptr_type = Type::make_pointer_type(Type::make_void_type()); + Type* uintptr_type = Type::lookup_integer_type("uintptr"); + Type* bool_type = Type::make_boolean_type(); + return Type::make_builtin_struct_type(9, + "link", ptr_type, + "frame", ptr_type, + "panicStack", ptr_type, + "_panic", ptr_type, + "pfn", uintptr_type, + "arg", ptr_type, + "retaddr", uintptr_type, + "makefunccanrecover", bool_type, + "heap", bool_type); +} + // Dump the AST representation for defer statement. void Index: gcc/go/gofrontend/statements.h =================================================================== --- gcc/go/gofrontend/statements.h (revision 274169) +++ gcc/go/gofrontend/statements.h (working copy) @@ -24,6 +24,7 @@ class Expression_statement; class Block_statement; class Return_statement; class Thunk_statement; +class Defer_statement; class Goto_statement; class Goto_unnamed_statement; class Label_statement; @@ -403,6 +404,11 @@ class Statement Thunk_statement* thunk_statement(); + // If this is a defer statement, return it. Otherwise return NULL. + Defer_statement* + defer_statement() + { return this->convert(); } + // If this is a goto statement, return it. Otherwise return NULL. Goto_statement* goto_statement() @@ -1419,15 +1425,26 @@ class Defer_statement : public Thunk_sta { public: Defer_statement(Call_expression* call, Location location) - : Thunk_statement(STATEMENT_DEFER, call, location) + : Thunk_statement(STATEMENT_DEFER, call, location), + on_stack_(false) { } + void + set_on_stack() + { this->on_stack_ = true; } + protected: Bstatement* do_get_backend(Translate_context*); void do_dump_statement(Ast_dump_context*) const; + + private: + static Type* + defer_struct_type(); + + bool on_stack_; }; // A goto statement. Index: libgo/go/runtime/mgcmark.go =================================================================== --- libgo/go/runtime/mgcmark.go (revision 274169) +++ libgo/go/runtime/mgcmark.go (working copy) @@ -657,6 +657,11 @@ func scanstack(gp *g, gcw *gcWork) { scanstackblock(uintptr(unsafe.Pointer(&gp.context)), unsafe.Sizeof(gp.context), gcw) } + // Note: in the gc runtime scanstack also scans defer records. + // This is necessary as it uses stack objects (a.k.a. stack tracing). + // We don't (yet) do stack objects, and regular stack/heap scan + // will take care of defer records just fine. + gp.gcscanvalid = true } Index: libgo/go/runtime/panic.go =================================================================== --- libgo/go/runtime/panic.go (revision 274169) +++ libgo/go/runtime/panic.go (working copy) @@ -13,6 +13,7 @@ import ( // themselves, so that the compiler will export them. // //go:linkname deferproc runtime.deferproc +//go:linkname deferprocStack runtime.deferprocStack //go:linkname deferreturn runtime.deferreturn //go:linkname setdeferretaddr runtime.setdeferretaddr //go:linkname checkdefer runtime.checkdefer @@ -124,6 +125,38 @@ func deferproc(frame *bool, pfn uintptr, d.makefunccanrecover = false } +// deferprocStack queues a new deferred function with a defer record on the stack. +// The defer record, d, does not need to be initialized. +// Other arguments are the same as in deferproc. +//go:nosplit +func deferprocStack(d *_defer, frame *bool, pfn uintptr, arg unsafe.Pointer) { + gp := getg() + if gp.m.curg != gp { + // go code on the system stack can't defer + throw("defer on system stack") + } + d.pfn = pfn + d.retaddr = 0 + d.makefunccanrecover = false + d.heap = false + // The lines below implement: + // d.frame = frame + // d.arg = arg + // d._panic = nil + // d.panicStack = gp._panic + // d.link = gp._defer + // But without write barriers. They are writes to the stack so they + // don't need a write barrier, and furthermore are to uninitialized + // memory, so they must not use a write barrier. + *(*uintptr)(unsafe.Pointer(&d.frame)) = uintptr(unsafe.Pointer(frame)) + *(*uintptr)(unsafe.Pointer(&d.arg)) = uintptr(unsafe.Pointer(arg)) + *(*uintptr)(unsafe.Pointer(&d._panic)) = 0 + *(*uintptr)(unsafe.Pointer(&d.panicStack)) = uintptr(unsafe.Pointer(gp._panic)) + *(*uintptr)(unsafe.Pointer(&d.link)) = uintptr(unsafe.Pointer(gp._defer)) + + gp._defer = d +} + // Allocate a Defer, usually using per-P pool. // Each defer must be released with freedefer. func newdefer() *_defer { @@ -155,11 +188,13 @@ func newdefer() *_defer { // Duplicate the tail below so if there's a // crash in checkPut we can tell if d was just // allocated or came from the pool. + d.heap = true d.link = gp._defer gp._defer = d return d } } + d.heap = true d.link = gp._defer gp._defer = d return d @@ -179,6 +214,9 @@ func freedefer(d *_defer) { if d.pfn != 0 { freedeferfn() } + if !d.heap { + return + } pp := getg().m.p.ptr() if len(pp.deferpool) == cap(pp.deferpool) { // Transfer half of local cache to the central cache. Index: libgo/go/runtime/runtime2.go =================================================================== --- libgo/go/runtime/runtime2.go (revision 274169) +++ libgo/go/runtime/runtime2.go (working copy) @@ -746,6 +746,12 @@ func extendRandom(r []byte, n int) { // A _defer holds an entry on the list of deferred calls. // If you add a field here, add code to clear it in freedefer. +// This struct must match the code in Defer_statement::defer_struct_type +// in the compiler. +// Some defers will be allocated on the stack and some on the heap. +// All defers are logically part of the stack, so write barriers to +// initialize them are not required. All defers must be manually scanned, +// and for heap defers, marked. type _defer struct { // The next entry in the stack. link *_defer @@ -781,6 +787,9 @@ type _defer struct { // function function will be somewhere in libffi, so __retaddr // is not useful. makefunccanrecover bool + + // Whether the _defer is heap allocated. + heap bool } // panics Index: libgo/go/runtime/stack_test.go =================================================================== --- libgo/go/runtime/stack_test.go (nonexistent) +++ libgo/go/runtime/stack_test.go (working copy) @@ -0,0 +1,62 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package runtime_test + +import "testing" + +func TestDeferHeapAndStack(t *testing.T) { + P := 4 // processors + N := 10000 // iterations + D := 200 // stack depth + + if testing.Short() { + P /= 2 + N /= 10 + D /= 10 + } + c := make(chan bool) + for p := 0; p < P; p++ { + go func() { + for i := 0; i < N; i++ { + if deferHeapAndStack(D) != 2*D { + panic("bad result") + } + } + c <- true + }() + } + for p := 0; p < P; p++ { + <-c + } +} + +// deferHeapAndStack(n) computes 2*n +func deferHeapAndStack(n int) (r int) { + if n == 0 { + return 0 + } + if n%2 == 0 { + // heap-allocated defers + for i := 0; i < 2; i++ { + defer func() { + r++ + }() + } + } else { + // stack-allocated defers + defer func() { + r++ + }() + defer func() { + r++ + }() + } + r = deferHeapAndStack(n - 1) + escapeMe(new([1024]byte)) // force some GCs + return +} + +// Pass a value to escapeMe to force it to escape. +var escapeMe = func(x interface{}) {}