Jump to page: 1 2
Thread overview
Why does scope(success) have to use exceptions?
Jan 16, 2013
Andrej Mitrovic
Jan 17, 2013
Era Scarecrow
Jan 17, 2013
Brad Roberts
Jan 17, 2013
Andrej Mitrovic
Jan 17, 2013
Stewart Gordon
Jan 17, 2013
Era Scarecrow
Jan 17, 2013
Maxim Fomin
Jan 17, 2013
deadalnix
Jan 17, 2013
Maxim Fomin
Jan 17, 2013
Johannes Pfau
Jan 17, 2013
Johannes Pfau
Jan 17, 2013
Maxim Fomin
Jan 17, 2013
monarch_dodra
Jan 17, 2013
H. S. Teoh
Jan 17, 2013
Andrej Mitrovic
Jan 17, 2013
Maxim Fomin
January 16, 2013
Sample code:

void callScope(ref int x)
{
    x = 1;
    scope(success) { x = 2; }
    x = 3;
    scope(success) { x = 4; }
}

void callFunc(ref int x)
{
    x = 1;
    x = 3;
    x = 4;
    x = 2;
}

void main()
{
    int x;
    callScope(x);
    assert(x == 2);

    callFunc(x);
    assert(x == 2);
}

I was expecting callScope to be lowered down to the handwritten code in callFunc in assembly, but instead it uses exceptions. Here's some simple ASM for callFunc compiled with -c -release -O (no inline):

_D4test8callFuncFKiZv:; Function begin, communal
        mov     dword [eax], 1                          ; 0000 _ C7.
00, 00000001
        mov     ecx, eax                                ; 0006 _ 89. C1
        mov     dword [ecx], 3                          ; 0008 _ C7.
01, 00000003
        mov     dword [ecx], 4                          ; 000E _ C7.
01, 00000004
        mov     dword [ecx], 2                          ; 0014 _ C7.
01, 00000002
        ret                                             ; 001A _ C3
; _D4test8callFuncFKiZv End of function

And this monster code for callScope:

_D4test9callScopeFKiZv:; Function begin, communal
        push    ebp                                     ; 0000 _ 55
        mov     ebp, esp                                ; 0001 _ 8B. EC
        mov     edx, dword [fs:__except_list]           ; 0003 _ 64:
8B. 15, 00000000(segrel)
        push    -1                                      ; 000A _ 6A, FF
        mov     ecx, 1                                  ; 000C _ B9, 00000001
        push    _D4test9callScopeFKiZv+0A6H             ; 0011 _ 68,
000000A6(segrel)
        push    edx                                     ; 0016 _ 52
        mov     dword [fs:__except_list], esp           ; 0017 _ 64:
89. 25, 00000000(segrel)
        sub     esp, 24                                 ; 001E _ 83. EC, 18
        push    ebx                                     ; 0021 _ 53
        push    esi                                     ; 0022 _ 56
        push    edi                                     ; 0023 _ 57
        mov     dword [ebp-18H], eax                    ; 0024 _ 89. 45, E8
        mov     dword [eax], ecx                        ; 0027 _ 89. 08
        mov     byte [ebp-1CH], 0                       ; 0029 _ C6. 45, E4, 00
        mov     dword [ebp-4H], 0                       ; 002D _ C7.
45, FC, 00000000
        mov     dword [ebp-4H], ecx                     ; 0034 _ 89. 4D, FC
        mov     dword [eax], 3                          ; 0037 _ C7.
00, 00000003
        xor     ecx, ecx                                ; 003D _ 31. C9
        mov     dword [eax], 4                          ; 003F _ C7.
00, 00000004
        mov     dword [ebp-4H], ecx                     ; 0045 _ 89. 4D, FC
        jmp     ?_002                                   ; 0048 _ EB, 0C

; Note: Inaccessible code
        mov     byte [ebp-1CH], 1                       ; 004A _ C6. 45, E4, 01
        push    dword [ebp-20H]                         ; 004E _ FF. 75, E0
        call    __d_throwc                              ; 0051 _ E8,
00000000(rel)
?_002:  mov     dword [ebp-4H], -1                      ; 0056 _ C7.
45, FC, FFFFFFFF
; Note: Displacement could be made smaller by sign extension
        lea     ecx, [ebp-0CH]                          ; 005D _ 8D.
8D, FFFFFFF4
        push    -1                                      ; 0063 _ 6A, FF
        push    ecx                                     ; 0065 _ 51
        push    FLAT:?_001                              ; 0066 _ 68,
00000000(segrel)
        call    __d_local_unwind2                       ; 006B _ E8,
00000000(rel)
        add     esp, 12                                 ; 0070 _ 83. C4, 0C
        call    ?_003                                   ; 0073 _ E8, 00000002
        jmp     ?_005                                   ; 0078 _ EB, 18

?_003:  mov     dword [ebp-4H], -1                      ; 007A _ C7.
45, FC, FFFFFFFF
        mov     al, byte [ebp-1CH]                      ; 0081 _ 8A. 45, E4
        xor     al, 01H                                 ; 0084 _ 34, 01
        jz      ?_004                                   ; 0086 _ 74, 09
        mov     edx, dword [ebp-18H]                    ; 0088 _ 8B. 55, E8
        mov     dword [edx], 2                          ; 008B _ C7.
02, 00000002
?_004:  ret                                             ; 0091 _ C3

?_005:
; Note: Displacement could be made smaller by sign extension
        mov     ecx, dword [ebp-0CH]                    ; 0092 _ 8B.
8D, FFFFFFF4
        mov     dword [fs:__except_list], ecx           ; 0098 _ 64:
89. 0D, 00000000(segrel)
        pop     edi                                     ; 009F _ 5F
        pop     esi                                     ; 00A0 _ 5E
        pop     ebx                                     ; 00A1 _ 5B
        mov     esp, ebp                                ; 00A2 _ 8B. E5
        pop     ebp                                     ; 00A4 _ 5D
        ret                                             ; 00A5 _ C3
; _D4test9callScopeFKiZv End of function

I'm trying to understand why. If an exception is thrown between one of those assignment statements the stack will unwind and any following assignment statements will not be called, so there shouldn't be a need to check if an exception is thrown. Why doesn't the compiler simply rewrite callScope to look like callFunc?
January 17, 2013
On Wednesday, 16 January 2013 at 23:19:20 UTC, Andrej Mitrovic wrote:
> Sample code:
> I was expecting callScope to be lowered down to the handwritten code in callFunc in assembly, but instead it uses exceptions. Here's some simple ASM for callFunc compiled with -c -release -O (no inline):
>

 <snip>

> I'm trying to understand why. If an exception is thrown between one of those assignment statements the stack will unwind and any following assignment statements will not be called, so there shouldn't be a need to check if an exception is thrown. Why doesn't the compiler simply rewrite callScope to look like callFunc?

 Obviously your code won't throw, however that's now how scope works.

 TDPL. pg 84-88 explains the lowering of scope is equal to hand written try/catch/finally versions; But you don't have to worry about making a mistake in the writing and adding more scopes is easier. Handling SQL and several tasks that all need to succeed or certain steps to be cleaned up afterwards makes perfect sense for scope.

[quote]
 Consider a block containing a scope(exit) statement:

  {
    <statements1>
    scope(exit) statement<2>
    <statements3>
  }

 Let's pick the first scope in the block, so we can assume that <statements1> itself does not contain scope (but <statement2> and <statments3> might). Lowering transforms the code into this:

  {
    <statements1>
    try {
      <statements3>
    } finally {
      <statement2>
    }
  }

 Following the transform, <statements3> and <statement2> are further lowered because they may contain additional scope statements. (The lowering always ends because the fragments are always strictly smaller than the initial sequence.) this means that code containing multiple scope(exit) statements is well defined, even in weird cases like scope(exit) scope(exit) scope(exit) writeln ("?"). In particular let's see what happens in the interesting case of two scope(exit) statements in the same block.

  }
    <statements1>
    scope(exit) <statement2>
    <statements3>
    scope(exit) <statement4>
    <statements5>
  }

 Let's assume that all statements do not containing additional scope(exit) statements. After lowering we obtain

  {
    <statements1>
    try {
      <statements3>
      try {
        <statements5>
      } finally {
        <statement4>
      }
    } finally {
      <statement2>
    }
  }

 The purpose of showing this unwieldly code is to figure out the order of execution of multiple scope(exit) statements in the same block. Following the flow shows that <statement4> gets executed before <statment2>. In general, scope(exit) statements execute in a stack, LIFO manner, the reverse of their order in the execution flow.

[/quote]

 Only asserts or exceptions would/can manage to decide if the block was successful or not; And the scope(s) can then manage cleanup (or final code) regardless where it was put without you having to do it yourself. If there's never any chance of it failing in the function then scope may not be what you want.

 However if compiler knows and can verify the code is unable to fail (thus exceptions are not needed) perhaps an enhancement request that could remove the unneeded try/catches...
January 17, 2013
On Thu, 17 Jan 2013, Era Scarecrow wrote:

> On Wednesday, 16 January 2013 at 23:19:20 UTC, Andrej Mitrovic wrote:
> > Sample code:
> > I was expecting callScope to be lowered down to the handwritten code in
> > callFunc in assembly, but instead it uses exceptions. Here's some simple ASM
> > for callFunc compiled with -c -release -O (no inline):
> > 
> 
>  Obviously your code won't throw, however that's now how scope works.
>
>  <snip>
> 
>  However if compiler knows and can verify the code is unable to fail (thus
> exceptions are not needed) perhaps an enhancement request that could remove
> the unneeded try/catches...

It's a QOI issue.  I know that parts of DMD are nothrow aware and will collapse away unnecessary layers, but obviously not all are.  The missing piece here is likely examining assignments to validate that they can't throw.  Probably not hard to make a meaningful improvement here.

Later,
Brad
January 17, 2013
On 1/17/13, Era Scarecrow <rtcvb32@yahoo.com> wrote:
> Obviously your code won't throw, however that's now how scope works. Only asserts or exceptions would/can manage to decide if the block was successful or not.

That's true for scope exit and scope failure, which need a try/catch/finally. But if an exception is thrown the stack will unwind, therefore the next statements won't be run, which is interesting for scope(success).

Let's look at it this way:

void foo()
{
    int x;
    scope(success)
    {
        x = 1;
    }
    // < code which might throw >
    x = 2;
}

try/catch version:

void foo()
{
    int x;
    try
    {
        x = 2;
        // < code which might throw >
        x = 1;
    }
    catch (Throwable e)
    {
        throw e;
    }
}

But there's no need for a try/catch, you can rewrite this to:

void foo()
{
    int x;
    x = 2;
    // < code which might throw >
    x = 1;  // if there's no stack unwind, this gets executed, hence
scope(success)
}
January 17, 2013
On 17/01/2013 00:06, Era Scarecrow wrote:
<snip>
>   Consider a block containing a scope(exit) statement:
<snip>
>   However if compiler knows and can verify the code is unable to fail
> (thus exceptions are not needed) perhaps an enhancement request that
> could remove the unneeded try/catches...

The OP was talking about scope(success), not scope(exit). scope(success), by definition, won't be executed if the code fails.  If it does it by changing it to a try/finally, it has to generate extra code to test whether the code succeeded or not.  It's far simpler to just write out the scope(success) code at the end of the scope in question.  As such, the compiler is _pessimising_ the code.

OK, so it's a bit more complicated if the scope can be exited via a return, break, continue or (dare I mention?) goto statement, but even then it ought to be quite easy to make the compiler generate more efficient code.

This should be fixed, as it is discouraging use of scope(success).

Stewart.
January 17, 2013
On Thursday, 17 January 2013 at 00:41:17 UTC, Stewart Gordon wrote:
> The OP was talking about scope(success), not scope(exit). scope(success), by definition, won't be executed if the code fails.  If it does it by changing it to a try/finally, it has to generate extra code to test whether the code succeeded or not.  It's far simpler to just write out the scope(success) code at the end of the scope in question.  As such, the compiler is _pessimising_ the code.

 I am aware of that. And although the examples were scope(exit) doesn't change how it's rewritten, only if finally is used. Depending on how much unrolling there is it might not be worth trying to reduce the try/catches, 3 scope levels as it is can be very hard to write correctly.

> OK, so it's a bit more complicated if the scope can be exited via a return, break, continue or (dare I mention?) goto statement, but even then it ought to be quite easy to make the compiler generate more efficient code.

 Not everything that seems obvious to us is easy to write for a computer to recognize; Who knows perhaps the compiler having one 'success' code may end up more efficiently managed by making it a delegate/lambda instead and calling it at each point rather than inlining the code to handle all code paths.

> This should be fixed, as it is discouraging use of scope(success).

 I'll agree, if something can't throw in a given scope then it should be able to remove the try/catches (as much as possible), and if scope(failure) can never be called it should be removed (although a warning might be nice as notification, a hint of a bug perhaps?)
January 17, 2013
On Wednesday, 16 January 2013 at 23:19:20 UTC, Andrej Mitrovic wrote:
> Sample code:
>
> void callScope(ref int x)
> {
>     x = 1;
>     scope(success) { x = 2; }
>     x = 3;
>     scope(success) { x = 4; }
> }
>
> void callFunc(ref int x)
> {
>     x = 1;
>     x = 3;
>     x = 4;
>     x = 2;
> }
>
> void main()
> {
>     int x;
>     callScope(x);
>     assert(x == 2);
>
>     callFunc(x);
>     assert(x == 2);
> }
>
> I was expecting callScope to be lowered down to the handwritten code
> in callFunc in assembly, but instead it uses exceptions. Here's some
> simple ASM for callFunc compiled with -c -release -O (no inline):
>
> I'm trying to understand why. If an exception is thrown between one of
> those assignment statements the stack will unwind and any following
> assignment statements will not be called, so there shouldn't be a need
> to check if an exception is thrown. Why doesn't the compiler simply
> rewrite callScope to look like callFunc?

On linux, segfaults can be translated into exceptions using this module (https://github.com/D-Programming-Language/druntime/blob/master/src/etc/linux/memoryerror.d) however I do not know how to use it - I get linker errors.

On windows null pointer errors are translated into Object.Error (Access Violation) - I do not remember exactly.

In any case, your void callScope(ref int x) can be blown up by:
int* ptr; callScope(*ptr); so, exceptions may come when they are not expected.
January 17, 2013
On Thursday, 17 January 2013 at 12:15:07 UTC, Maxim Fomin wrote:
> On linux, segfaults can be translated into exceptions using this module (https://github.com/D-Programming-Language/druntime/blob/master/src/etc/linux/memoryerror.d) however I do not know how to use it - I get linker errors.
>

Can you provide a sample code and the error message ?
January 17, 2013
On Thursday, 17 January 2013 at 13:05:00 UTC, deadalnix wrote:
> On Thursday, 17 January 2013 at 12:15:07 UTC, Maxim Fomin wrote:
>> On linux, segfaults can be translated into exceptions using this module (https://github.com/D-Programming-Language/druntime/blob/master/src/etc/linux/memoryerror.d) however I do not know how to use it - I get linker errors.
>>
>
> Can you provide a sample code and the error message ?

import etc.linux.memoryerror;
import core.stdc.stdio : printf;


class A { int x; }

void main()
{
	A a;
	try
	{
		a.x = 0;
	}
	catch(NullPointerError er)
	{
		printf("catched\n");
	}
}

main.o:(.data+0xd0): undefined reference to `_D3etc5linux11memoryerror16NullPointerError7__ClassZ'
collect2: error: ld returned 1 exit status
--- errorlevel 1

InvalidPointerError does not work either.

Perhaps this is a wrong usage, but I do not see any spec at dlang.org or inside source file.
January 17, 2013
Am Thu, 17 Jan 2013 14:45:25 +0100
schrieb "Maxim Fomin" <maxim@maxim-fomin.ru>:
> main.o:(.data+0xd0): undefined reference to `_D3etc5linux11memoryerror16NullPointerError7__ClassZ' collect2: error: ld returned 1 exit status
> --- errorlevel 1
> 
> InvalidPointerError does not work either.
> 
> Perhaps this is a wrong usage, but I do not see any spec at dlang.org or inside source file.

Looks like etc.linux.memoryerror.d was not compiled into libdruntime/libphobos.a.
« First   ‹ Prev
1 2