Jump to page: 1 2
Thread overview
scope(success) lowered to try-catch ?
Jun 17, 2018
Cauterite
Jun 17, 2018
Nicholas Wilson
Jun 17, 2018
Cauterite
Jun 17, 2018
Timoses
Jun 17, 2018
Mike Franklin
Jun 18, 2018
Neia Neutuladh
Jun 18, 2018
Cauterite
Jun 18, 2018
aliak
June 17, 2018
Hello,
I'm not sure whether I'm missing something obvious here, but is there a reason for scope(success) being lowered to a try-catch statement?
I would have expected only scope(exit) and scope(failure) to actually interact with exception handling, while scope(success) simply places code on the path of normal control flow.

Example (windows x32):

---

// main.d
void main() {
	scope(success) {}
}

> dmd -betterC main.d
Error: Cannot use try-catch statements with -betterC

---

Regardless of whether -betterC is used, you can see in the disassembly that having a scope(success) anywhere in the function causes the SEH prologue to be emitted in the code.

Is there a reason scope(success) needs to set up for exception handling?
Or is this a bug / potential enhancement ?
June 17, 2018
On Sunday, 17 June 2018 at 10:58:29 UTC, Cauterite wrote:
> Hello,
> I'm not sure whether I'm missing something obvious here, but is there a reason for scope(success) being lowered to a try-catch statement?
> I would have expected only scope(exit) and scope(failure) to actually interact with exception handling, while scope(success) simply places code on the path of normal control flow.
>
> Example (windows x32):
>
> ---
>
> // main.d
> void main() {
> 	scope(success) {}
> }
>
>> dmd -betterC main.d
> Error: Cannot use try-catch statements with -betterC
>
> ---
>
> Regardless of whether -betterC is used, you can see in the disassembly that having a scope(success) anywhere in the function causes the SEH prologue to be emitted in the code.
>
> Is there a reason scope(success) needs to set up for exception handling?
> Or is this a bug / potential enhancement ?

I suspect scope(success) is lowered because scope(exit) and scope(failure)
are, and that would result in a simpler (compiler) implementation of it.

does adding nothrow to main fix it? For dcompute I specifically allow scope(exit|success) because there will never be any exceptions _at all_.

If not, please do submit an issue. Also a better error message should be given.
June 17, 2018
On 6/17/18 6:58 AM, Cauterite wrote:
> Hello,
> I'm not sure whether I'm missing something obvious here, but is there a reason for scope(success) being lowered to a try-catch statement?
> I would have expected only scope(exit) and scope(failure) to actually interact with exception handling, while scope(success) simply places code on the path of normal control flow.
> 
> Example (windows x32):
> 
> ---
> 
> // main.d
> void main() {
>      scope(success) {}
> }
> 
>> dmd -betterC main.d
> Error: Cannot use try-catch statements with -betterC
> 
> ---
> 
> Regardless of whether -betterC is used, you can see in the disassembly that having a scope(success) anywhere in the function causes the SEH prologue to be emitted in the code.
> 
> Is there a reason scope(success) needs to set up for exception handling?
> Or is this a bug / potential enhancement ?

I think you are right, adding scope(success) should just add the statements to the end of the scope.

Here's what I think happens: Because scope(anything) needs to put things like this:

try
{
   normal code
   scope(success) code
} catch(Exception e) {
   scope(failure) code
   throw e;
} finally {
   scope(exit) code
}

so any time you use a scope statement, it has to set up this framework so it can have the correct place to put things (there may be scope(failure) or scope(exit) code later). But I think we can fix this.

-Steve
June 17, 2018
On Sunday, 17 June 2018 at 10:58:29 UTC, Cauterite wrote:
> Hello,
> I'm not sure whether I'm missing something obvious here, but is there a reason for scope(success) being lowered to a try-catch statement?
> I would have expected only scope(exit) and scope(failure) to actually interact with exception handling, while scope(success) simply places code on the path of normal control flow.
>
> Example (windows x32):
>
> ---
>
> // main.d
> void main() {
> 	scope(success) {}
> }
>
>> dmd -betterC main.d
> Error: Cannot use try-catch statements with -betterC
>
> ---
>
> Regardless of whether -betterC is used, you can see in the disassembly that having a scope(success) anywhere in the function causes the SEH prologue to be emitted in the code.
>
> Is there a reason scope(success) needs to set up for exception handling?
> Or is this a bug / potential enhancement ?

In Andrei's book 'The D Programming Language' the following is written:

{
    <statement1>
    scope(success) <statement2>
    <statement3>
}
is lowered to
{
    <statement1>
    bool __succeeded = true;
    try {
        <statement3>
    } catch(Exception e) {
        __succeeded = false;
        throw e;
    } finally {
        if (__succeeded) <statement2> // vice-versa for scope(failure): `if (!__succeeded) ...`
    }
}

If it weren't and it would simply be integrated one would have to write

    potentiallyThrowingFunc();
    scope(success) {...};

I suppose? And this seems like breaking how scope works with failure and exit?!
June 17, 2018
On Sunday, 17 June 2018 at 12:10:33 UTC, Nicholas Wilson wrote:
> I suspect scope(success) is lowered because scope(exit) and scope(failure)
> are, and that would result in a simpler (compiler) implementation of it.
>
> does adding nothrow to main fix it? For dcompute I specifically allow scope(exit|success) because there will never be any exceptions _at all_.
>
> If not, please do submit an issue. Also a better error message should be given.

nothrow unfortunately doesn't help; i guess I'll file an issue.
thanks for your input
June 17, 2018
On 6/17/18 8:24 AM, Timoses wrote:
> On Sunday, 17 June 2018 at 10:58:29 UTC, Cauterite wrote:
>> Hello,
>> I'm not sure whether I'm missing something obvious here, but is there a reason for scope(success) being lowered to a try-catch statement?
>> I would have expected only scope(exit) and scope(failure) to actually interact with exception handling, while scope(success) simply places code on the path of normal control flow.
>>
>> Example (windows x32):
>>
>> ---
>>
>> // main.d
>> void main() {
>>     scope(success) {}
>> }
>>
>>> dmd -betterC main.d
>> Error: Cannot use try-catch statements with -betterC
>>
>> ---
>>
>> Regardless of whether -betterC is used, you can see in the disassembly that having a scope(success) anywhere in the function causes the SEH prologue to be emitted in the code.
>>
>> Is there a reason scope(success) needs to set up for exception handling?
>> Or is this a bug / potential enhancement ?
> 
> In Andrei's book 'The D Programming Language' the following is written:
> 
> {
>      <statement1>
>      scope(success) <statement2>
>      <statement3>
> }
> is lowered to
> {
>      <statement1>
>      bool __succeeded = true;
>      try {
>          <statement3>
>      } catch(Exception e) {
>          __succeeded = false;
>          throw e;
>      } finally {
>          if (__succeeded) <statement2> // vice-versa for scope(failure): `if (!__succeeded) ...`
>      }
> }
> 
> If it weren't and it would simply be integrated one would have to write
> 
>      potentiallyThrowingFunc();
>      scope(success) {...};
> 
> I suppose? And this seems like breaking how scope works with failure and exit?
I think the request isn't to integrate the code immediately, but to do:

{
<statement 1>
<statement 3>

<statement 2>
}

and be just fine. I think the reason the scope(failure) is done that way is likely for proper exception chaining in case one of the scope statements throws.

-Steve
June 17, 2018
On Sunday, 17 June 2018 at 10:58:29 UTC, Cauterite wrote:

> ---
>
> // main.d
> void main() {
> 	scope(success) {}
> }
>
>> dmd -betterC main.d
> Error: Cannot use try-catch statements with -betterC
>
> ---

You can see what the compiler is doing at https://run.dlang.io/is/5BZOQV and clicking on the "AST" button.  It produces the following:

import object;
void main()
{
	bool __os2 = false;
	try
	{
	}
	catch(Throwable __o3)
	{
		__os2 = true;
		throw __o3;
	}
	if (!__os2)
	{
	}
	return 0;
}

The compiler could probably lower that to something more intelligent so it could be used in -betterC.  I rule it a bug.

Mike


June 18, 2018
On Sunday, 17 June 2018 at 10:58:29 UTC, Cauterite wrote:
> Is there a reason scope(success) needs to set up for exception handling?
> Or is this a bug / potential enhancement ?

If you had no exception handling in place, you'd need to duplicate code in the output. For instance:

void foo()
{
  scope(success) writeln("success!");
  if (a) return;
  if (b) return;
  throw new Exception;
}

This would have to be lowered to:

void foo()
{
  if (a) { writeln("success!"); return; }
  if (b) { writeln("success!"); return; }
  throw new Exception;
  writeln("success!");  // maybe omitted with flow analysis
}

Now imagine there were 20 places you return from the function early. Now imagine this is in a loop body, where you can leave it via goto, break, continue, return, or end-of-block. And wrapped in several if statements.

You generate smaller code with the exception handling system. The compiler only has to pay attention to scope guards in the code that handles it directly, instead of at every flow control statement. Add to that the fact that -betterC is pretty recent and scope guards are more than ten years old, and you get this hole in the compiler.
June 18, 2018
On Monday, 18 June 2018 at 03:58:47 UTC, Neia Neutuladh wrote:
> ...

yeah, at an AST level it makes sense why it was implemented like this.
it's unfortunate that there's no straightforward way to express 'finally(success) {'.
June 18, 2018
On 6/17/18 11:58 PM, Neia Neutuladh wrote:
> On Sunday, 17 June 2018 at 10:58:29 UTC, Cauterite wrote:
>> Is there a reason scope(success) needs to set up for exception handling?
>> Or is this a bug / potential enhancement ?
> 
> If you had no exception handling in place, you'd need to duplicate code in the output. For instance:
> 
> void foo()
> {
>    scope(success) writeln("success!");
>    if (a) return;
>    if (b) return;
>    throw new Exception;
> }
> 
> This would have to be lowered to:
> 
> void foo()
> {
>    if (a) { writeln("success!"); return; }
>    if (b) { writeln("success!"); return; }
>    throw new Exception;
>    writeln("success!");  // maybe omitted with flow analysis
> }

Yep, it's a good point. But also not the only way to do this. If you are returning void, just a goto would work:

void foo()
{
   if(a) { goto L1; }
   if(b) { goto L1; }
   throw new Exception;
L1:
   writeln("success1");
}

If you are returning a value, then you can establish a local variable with the return value, and use a goto that way. It's already doing this with "did it succeed", so adding another local variable is pretty trivial.

Bottom line is, the compiler understands flow control and can insert structures like this without a huge impact.

I also think it's possible for the compiler to detect that a try/catch clause is trivially omitted if we do it in the right way.

> 
> Now imagine there were 20 places you return from the function early. Now imagine this is in a loop body, where you can leave it via goto, break, continue, return, or end-of-block. And wrapped in several if statements.

These are all pretty easy to deal with. After all, they are essentially glorified gotos.

> 
> You generate smaller code with the exception handling system. The compiler only has to pay attention to scope guards in the code that handles it directly, instead of at every flow control statement. Add to that the fact that -betterC is pretty recent and scope guards are more than ten years old, and you get this hole in the compiler.

I think the last point here is exactly why it was done this way -- the original design of the compiler was to expect there were always exception handling, so why not use it?

-Steve
« First   ‹ Prev
1 2