From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 32738 invoked by alias); 7 May 2003 22:18:07 -0000 Mailing-List: contact gcc-help@gcc.gnu.org; run by ezmlm Precedence: bulk List-Archive: List-Post: List-Help: Sender: gcc-owner@gcc.gnu.org Received: (qmail 32711 invoked from network); 7 May 2003 22:18:06 -0000 Received: from unknown (HELO doubledemon.codesourcery.com) (66.60.148.227) by sources.redhat.com with SMTP; 7 May 2003 22:18:06 -0000 Received: from doubledemon.codesourcery.com (doubledemon.codesourcery.com [127.0.0.1]) by doubledemon.codesourcery.com (8.12.8/8.12.8) with ESMTP id h47MI19X005936; Wed, 7 May 2003 15:18:05 -0700 Subject: Re: __attribute__((cleanup(function)) versus try/finally From: Mark Mitchell To: Jason Merrill Cc: gcc@gcc.gnu.org In-Reply-To: References: <1052245742.2583.315.camel@doubledemon.codesourcery.com> <1052249890.31850.338.camel@doubledemon.codesourcery.com> <1052256289.2583.412.camel@doubledemon.codesourcery.com> Content-Type: text/plain Content-Transfer-Encoding: 7bit Date: Wed, 07 May 2003 22:18:00 -0000 Message-Id: <1052345885.5665.64.camel@doubledemon.codesourcery.com> Mime-Version: 1.0 X-SW-Source: 2003-05/txt/msg00716.txt.bz2 > There are no costs unless there are cleanups. Yes -- but I'm talking about the situation where there are cleanups. Concretely, consider the following C function: void f() { FILE *f1, *f2, *f3; f1 = fopen ("/tmp/foo"); pthread_cleanup_push (fclose, f1); g(); f2 = fopen ("/tmp/bar"); pthread_cleanup_push (fclose, f2); g(); f3 = fopen ("/tmp/baz"); pthread_cleanup_push (fclose, f3); g (); pthread_cleanup_pop (1); pthread_cleanup_pop (1); pthread_cleanup_pop (1); } Now, with my scheme this function has no handler code; i.e, the EH run-time library will never transfer control back to this function. Even when using setjmp/longjmp the cost is just one setjmp at the start of the function, so that the stack can be unwound. If pthread_cleanup_push uses try/finally, it's my understanding that there will be multiple calls to setjmp, before each of the calls to g. And in the non-setjmp case, there will be landing pads and unwind information for each of the exception regions, rather than just one for the entire function. > > Without setjmp/longjmp, you save mostly code space. You also improve > > cleanup time (in that you need not transfer control to the local frame > > of the function before running the cleanup), but this is a smaller > > advantage. > > Which may or may not outweigh the advantages of having the cleanups in the > function (code motion into the landing pad, inlining destructors, not > forcing the argument into the stack). For pthread_cleanup_push, this is generally not an issue; the cleanup is a function pointer. Unless that function pointer is to an inline function, you're going to make a call here anyhow; there's no real advantage. > Basically, what I'm saying is that the question of what sorts of cleanups > to allow can be separated from the question of how to implement them. If > we can set aside the implementation issues, it becomes just a question of > language design, and I still think that try/finally is the elegant > solution. (There's nothing wrong with try/finally as a construct in the abstract. I'm all for continuations, too! And real closures! If C had templates, objects, exceptions, and namespaces, I'd love it to pieces! Seriously, one of the few remaining objections to programming in C++ has been "exceptions add too much overhead". Once we add unwind tables to C -- as all of our proposals do, including mine -- I'm not really sure what the point of programming in C is going to be any more. Maybe if you really like the fact that "struct S {};" doesn't introduce a typedef, or that "void f()" isn't a prototype. Maybe we should add -fnothing-but-exceptions to G++, and just use that as the C compiler.) Adding EH to C is a very dramatic change. The spirit of C is "no hidden stuff." Calling exceptions "zero-cost" is a lie; we say that because they require no additional instructions be executed unless an exception is thrown, not because they actually have no cost. The unwind tables are a significant cost, for example. To that end, I've toyed with the following idea: - When building a library written in C, provide two sets of entry points for the globally visible functions: a version that is called from C, and a version called from everything else. The non-C version of the entry point calls setjmp. Then, you can unwind through C code with a single longjmp, no unwind tables, no handlers. (You do this by saying "huh, no unwind info for this frame; must be C; call the top entry on the longjmp stack.) You actually only need this setjmp on those entry points that are going to call things that might throw exceptions; in glibc, for example, that means functions that call cancellation points, or take callbacks (ala qsort). - The cost of this scheme for a pure C program is exactly zero. No unwind info, no setjmps, no nothing. You get correct mixed C++/C semantics with slight cost; on entry to a C library from C++ code you take a hit if you need to call setjmp. This kind of solution is awfully attractive if you're on an embedded system. (I want pthreads on my cell-phone, but I sure don't want to have extra unwind info around. I want to be able to support C++, but if it's not in use, I don't want to have to pay the cost of supporting C++ by my making my C libraries bigger.) -- Mark Mitchell CodeSourcery, LLC mark@codesourcery.com