Alternative exception handling proposal

How can this be encoded in dwarf unwind info? The personality routine is specified by the CIE and each FDE can only point to one CIE and currently there is one FDE per function.

So would you have multiple (non overlapping) FDEs per function? Seems like that would break some assumptions somewhere.

I'd image that you just block inlining of a function that needs a different personality function.

Or better yet, stop putting language specific semantics into the personality function and instead encode the semantics the language wants the function to have into the LSDA info and just have one uber-personality function (to rule them all).

-Nick

Hi John,

I'm unhappy about how this bakes _Unwind_Resume into the backend, particularly
since that prevents us from using better alternatives when they're available
(e.g. the ARM EH ABI's _cxa_end_cleanup(), which saves code size by not requiring
the exception pointer, but which we can only use if we're linking in the C++
standard
library).

the code generators can lower "unwind" to a call to _cxa_end_cleanup on that
platform.

No, they can't, it's language-library-specific. Only the frontend knows whether
it's safe to
introduce that dependency.

I guess the front-end can pass an option to the code generators to select the
desired form.

Also, _Unwind_Resume has a slightly different name in ARM sjlj EH; it would be great
if codegen didn't have to hard-code all this again.

If it was all in codegen then front-ends wouldn't have to have this logic.

One idea that comes to mind is that we could make Yet Another call-like
instruction,
a terminator like 'invoke' but with no successors and with the special
replaced-with-a-branch behavior when inlined through an invoke. So the front-end
could call whatever function it pleases, taking responsibility for passing in
the right
information.

I think this is way too complicated.

Then instead of using a call, it can be a special kind of unconditional branch which
the inliner rewrites into a normal branch; that would look exactly like your
"rewind"
instruction except for having a successor.

Another possibility is to have front-ends output the desired library call, and
stick an attribute "rewinds" on the call, which tells the optimizers that an
invoke of this call is equivalent to a branch.

Ciao, Duncan.

Hi Bill,

This is similar to my first proposal.

yup, I still consider your first proposal to have been basically sound.

But it also suffers from a major problem,

which stopped that proposal dead in its tracks. Namely, you have information in

one place which needs to be shared in two different, but possibly disjoint,

places: the type, filters, and personality information. In order to generate the

EH tables, you need to know this information at the throw site and at the place

which makes the decision of which catch handler to invoke. There is no guarantee

in your proposal that the invoke can be associated with the proper eh.selector

call. And because of (C++) cleanups and inlining, it’s the rule not the exception.

I disagree that this information is needed anywhere except the invoke. If it
was needed arbitrarily far downstream then of course my proposal would be dead.
But it isn’t! Got an example where it is?

Example, if you have this:

invoke void @foo()

to label %invcont unwind label %lpad

personality @__gxx_personality_v0

catches %struct.__fundamental_type_info_pseudo* @_ZTIi,

%struct.__pointer_type_info_pseudo* @_ZTIPKc

lpad:

call void @bar(%A* %a) ; a cleanup

br label %ppad

ppad:

%eh_ptr = call i8* llvm.eh.exception()

%eh_sel = call i32 llvm.eh.selector()

; code to clean up.

The call to @bar can insert an arbitrarily complex amount of code, including

invokes, llvm.eh.selector calls, etc. Because there is no relationship between

the invoke of @foo and %eh_sel in ppad, we lose that information at ppad, which

is where we need it.

It would of course be wrong to expect eh.exception to return the original value
in ppad if you inlined an invoke via the call to @bar, and reached %ppad via the
unwind branch of that invoke because a new exception was thrown. This is not a
problem. Here’s how gcc does it. In fact llvm-gcc does exactly the same thing!
In lpad gcc grabs the exception and selector using the equivalent of
eh.exception and eh.selector and stashes the values in local variables. It
then uses those stashed variables everywhere, for example in ppad to do the
comparisons with eh.typeid.for etc. It doesn’t try to get the value via
eh.exception in ppad. Since presumably you know this (since llvm-gcc does it)
maybe you were talking about something else?

The code in DwarfEHPrepare::MoveExceptionValueCalls that moves the call to

llvm.eh.exception into the landing pad, and which you want to do for

llvm.eh.selector as well, will only complicate matters. It would introduce PHI

nodes for llvm.eh.selector values like it currently does for llvm.eh.exception

values.

Sure, but that’s not a problem because the catch info is only needed at invokes,
there is no need to go searching for it downstream, and I’m not sure why you
think there is such a need.

In order to generate the EH tables correctly, we need to know what types can be thrown, where to land for a specific throw site, and how to branch to the correct catch handler. When you divorce the point where you branch to the catch handler from the point where it throws, you now have a huge gap that cannot be easily recovered from, and may be impossible to recover from.

This is the code that G++ generates from the example in my proposal:

LEHB2:
call __Z3foov
LEHE2:
. . .

L24:

basic block 10

movq %rax, %r12
L5:

basic block 11

movl %edx, %ebx
leaq -18(%rbp), %rdi
call __ZN1BD1Ev
movslq %ebx,%rdx
jmp L7
. . .

L7:

basic block 15

movl %edx, %ebx
leaq -17(%rbp), %rdi
call __ZN1AD1Ev
movslq %ebx,%rdx
jmp L19

. . .

L19:

basic block 19

cmpq $3, %rdx
je L9

basic block 20

cmpq $2, %rdx
jne L30

basic block 21

jmp L39

. . .

L39: # The catch handler
. . .

GCC_except_table0:
LLSDA4:

. . .

.set L$set$6,LEHB2-LFB4 .long L$set$6 # region 2 start .set L$set$7,LEHE2-LEHB2 .long L$set$7 # length .set L$set$8,L24-LFB4 .long L$set$8 # landing pad .byte 0x7 # uleb128 0x7; action
. . .

.byte 0x1 # Action record table
.byte 0x0
.byte 0x2
.byte 0x7d
.byte 0x3
.byte 0x7d
.byte 0x0
.byte 0x7d
.align 2
.long __ZTIi+4@GOTPCREL
.long __ZTIPKc+4@GOTPCREL
.long 0

If the call to __Z3foov throws, we need to set up the tables to that it knows that it needs to call the __ZN1BD1Ev and __ZN1AD1Ev cleanups. This information requires looking at the invoke instruction – i.e., “where should I land?”. It also needs to know which types it can catch in order to get the “action” variable.

So the information is needed at the invoke site.

The information is also needed at the site that makes the decision of which catch handler to execute (L19 in the above example). For one, it needs to know the action record table entries. And of course it needs to know the types that can catch, the personality function, and information about any filters. In your model, that point in the code is completely, and potentially irreversibly, separated from the invoke instruction.

This is why I abandoned my original idea. There was no good way of modeling a relationship between the invoke and the catch handler decision site in the IR.

invoke void @_Z3foov()

to label %“3” unwind label %lpad personality @__gxx_personality_v0

catches %struct.__fundamental_type_info_pseudo* @_ZTIi,

%struct.__pointer_type_info_pseudo* @_ZTIPKc, i8* null

The use of “i8* null” here is just as bad as it is for the current

llvm.eh.selector call. There’s no way to determine from this list whether the

last value is truly the catchall value or for a catch handler.

There is a catch-all here only because there is a catch-all in the original
code:

} catch (…) {
printf(“catchall\n”);
}

In my proposal you don’t need to know about catch-all, add special catch-alls
etc. If there was a catch-all in the original code then there is one on the
invoke, otherwise there is not. There is no special treatment of catch-all.

You miss the point. As you well know, we need to know the specific type that is used for a catchall. E.g., i8* null in C++ and a global variable in Ada. In your code above, the i8* null is indistinguishable from the other types.

“10”: ; preds = %“5”

%exc_ptr31 = call i8* @llvm.eh.exception()

%filter32 = call i32 @llvm.eh.selector()

invoke void @_ZN1CD1Ev(%struct.A* %memtmp)

to label %“11” unwind label %fail personality @__gxx_personality_v0

catches i32 1 ; ← this is an empty filter, i.e. one that catches everything

Filter? What do you mean by this?

http://llvm.org/docs/ExceptionHandling.html#throw_filters

Why is it in a “catches” clause?

Will everything work?


I am confident that it will work fine, for a very simple reason: this is exactly

what gcc does! Of course it is in disguise, a wolf in sheep’s clothing some

might say :slight_smile: In fact moving closer to gcc like this is probably the best way

to be sure that exception handling works properly, since gcc is what everyone

tests against whether we like it or not (for example libstdc++ exploits some

details of how gcc implements exception handling that are not specified by the

standard, i.e. are implementation defined, and this has caused trouble for LLVM

in the past).

I would suspect that GCC has proper EH table generation mostly because it keeps

tables on the side; whereas we do not and cannot. Our current EH tables are

pretty poor. I would love to be able to generate tables similar to theirs.

I think you are reading more into the gcc tables than actually exists. The
tables hold a set of nested regions. Each region consists of a set of basic
blocks. There are various types of regions, corresponding to handlers, filters,
cleanups etc. Given a basic block, what happens when an exception is thrown?
You wind up through the enclosing regions, from inner-most to outer-most looking
for what to do. If nothing matches then the exception unwinds out of the
function, otherwise the action specified by the region is taken.

This is exactly what my newest EH model proposal is meant to do.

Here is an equivalent way of storing regions, by attaching them to basic blocks:
given a basic block BB, consider all regions that contain BB, and attach their
info to BB in order of inner-most region to outer-most region. That’s what
my “catch info” does - and I think it contains all relevant info from the gcc
regions. If so, we have all the same info gcc has, so if gcc can do something
then so can we. You might object that regions can contain multiple basic
blocks, and by attaching info to basic blocks (currently this means to invokes)
you no longer can tell if two basic blocks are in the same region or not. This
is true to some extent (you can reconstruct maximal regions by comparing catch
info on basic blocks) but I don’t think it matters for anything, in gcc it is
just an optimization to reduce memory usage and duplicated effort.

I hate the way dwarf typeinfos, catches and filters are being baked into the

IR. Maybe metadata (see above) helps with this.

Metadata cannot be counted on to remain.

Is that also true for global metadata used as an argument to an intrinsic?

You will need to read the documentation. But Chris never expects metadata to stick around. In fact, it’s derived from Value, not Use. So how can you have a use of it in an intrinsic that the compiler would know about?

Do you have an idea for how to keep catches etc out of the definition of the
IR? I’m worried that if one day we add support for, say, SEH then we will
have to change how the IR is defined again, and that’s better avoided.

I haven’t given it a lot of thought. I don’t like encoding DWARF-specific concepts into the IR. But my proposal doesn’t do that either. It involves generic concepts that could be applied to all forms of EH.

How will your implementation allow us to remove the Horrible Hack from

DwarfEHPrepare.cpp? Right now we catch and throw at almost every level that the

exception can propagate up. How will your proposal solve this?

The horrible hack is not needed at all, pushing extra catch-alls is not needed
at all - it all goes away. Why were these needed? They were needed to handle
the effects of inlining, in particular that right now when you inline through
an invoke the catch info (contained in the selector) gets attached to the
inlined _Unwind_Resume which is far away from the place you really want it: you
want it on the inlined invoke that the _Unwind_Resume is downstream of. But
notice how inlining works with my scheme (described in my original proposal):
when inlining through an invoke, the catch info for that invoke gets appended
to everything you inline, including the invoke you inline. Thus it occurs in
the right place automatically! It also gets attached to the _Unwind_Resume,
which is also correct. If _Unwind_Resume is replaced with unwind (rewind in
my original proposal, since amended) then you can just replace unwind with a
branch and everything comes out in the wash (it is not obvious that everything
comes out in the wash, but nonetheless it does!).

John all ready mentioned problems with your inlining proposal. Here is the code that brought up the need for what you instantly labeled a “horrible hack”. It must work flawlessly with your new proposal.

http://llvm.org/viewvc/llvm-project?view=rev&revision=99670

int main() {
try {

        throw new std::exception();
    } catch (std::exception *e) {
        throw e;
    }
}


And this code needs to give a sensible backtrace (on Darwin at least). It will crash libunwind because it was doing horrible things:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

@try {
@throw [NSException exceptionWithName:@“TestException” reason:@“Test” userInfo:nil];
}
@catch (NSException *e) {
@throw e;
}

[pool drain];
return 0;
}

-bw

The problem comes about with LTO. It’s an entirely new problem, because LTO (and gcc’s version of it) didn’t exist before it. There is a PR about handling multiple personality functions in a function. It’s just a matter of having the libunwind mechanism handling it.

-bw

I hate the way dwarf typeinfos, catches and filters are being baked into the
IR. Maybe metadata (see above) helps with this.

  @llvm.eh.catch.all.value = linkonce constant i8* null, section "llvm.metadata"

is not llvm::MDNode or llvm::MDString. @llvm.eh.catch.all.value is just another llvm::Constant.

Metadata cannot be counted on to remain.

Is that also true for global metadata used as an argument to an intrinsic?

Metadata is used to hold some other data. You can not rely that data being held by metadata remains.

the code generators can lower "unwind" to a call to _cxa_end_cleanup on that
platform.

No, they can't, it's language-library-specific. Only the frontend knows whether
it's safe to
introduce that dependency.

I guess the front-end can pass an option to the code generators to select the
desired form.

Also, _Unwind_Resume has a slightly different name in ARM sjlj EH; it would be great
if codegen didn't have to hard-code all this again.

If it was all in codegen then front-ends wouldn't have to have this logic.

If the front-end has to pass an option to the code generator to get the right unwinding
function, then it still has to have the logic — it just acts on it in an unnecessarily
obscure and constrained way.

Also, we wouldn't be doing anyone any favors by hiding this. It's not like the current
representation is some heavily-virtualized ideal which painlessly hides all
these pesky details. Every front-end already hard-codes a dozen other details
about how unwinding works on the target. The IR itself hard-codes multiple details
of DWARF EH, like the type info table being global to a function, and like there
being one entry point per instruction. Bill's proposal is actually much more
virtualized than yours, as it hides both the selector value and the type info indices.

One idea that comes to mind is that we could make Yet Another call-like
instruction,
a terminator like 'invoke' but with no successors and with the special
replaced-with-a-branch behavior when inlined through an invoke. So the front-end
could call whatever function it pleases, taking responsibility for passing in
the right
information.

I think this is way too complicated.

Then instead of using a call, it can be a special kind of unconditional branch which
the inliner rewrites into a normal branch; that would look exactly like your
"rewind"
instruction except for having a successor.

Another possibility is to have front-ends output the desired library call, and
stick an attribute "rewinds" on the call, which tells the optimizers that an
invoke of this call is equivalent to a branch.

I think I prefer the special branch to the tagged call. It's actually semantically
important that the optimizers not touch the call except during inlining.

John.

Hi Bill, there is clearly a misunderstanding: either I am missing something
essential or you are. To clear this up, I suggest you send me evil examples
and I will show you how my scheme handles them (or doesn't handle them, if I
am indeed failing to see something).

This is the code that G++ generates from the example in my proposal:

...

If the call to __Z3foov throws, we need to set up the tables to that it knows
that it needs to call the __ZN1BD1Ev and __ZN1AD1Ev cleanups. This information
requires looking at the invoke instruction – i.e., "where should I land?". It
also needs to know which types it can catch in order to get the "action" variable.

So the information is needed at the invoke site.

The information is also needed at the site that makes the decision of which
catch handler to execute (L19 in the above example). For one, it needs to know
the action record table entries. And of course it needs to know the types that
can catch, the personality function, and information about any filters. In your
model, that point in the code is completely, and potentially irreversibly,
separated from the invoke instruction.

This is why I abandoned my original idea. There was no good way of modeling a
relationship between the invoke and the catch handler decision site in the IR.

In my proposal I showed the IR generated by my scheme for the C++ code in your
example. Since I don't understand what you are getting at here, perhaps you can
explain by referring to that IR, pointing out the problematic bits.

You miss the point. As you well know, we need to know the specific type that is
used for a catchall. E.g., i8* null in C++ and a global variable in Ada. In your
code above, the i8* null is indistinguishable from the other types.

With my proposal we don't need to know that any more.

Why is it in a "catches" clause?

Because it is an action like a catch. This list corresponds exactly to what
gets put in the dwarf actions table.

I hate the way dwarf typeinfos, catches and filters are being baked into the
IR. Maybe metadata (see above) helps with this.

Metadata cannot be counted on to remain.

Is that also true for global metadata used as an argument to an intrinsic?

You will need to read the documentation. But Chris never expects metadata to
stick around. In fact, it's derived from Value, not Use. So how can you have a
use of it in an intrinsic that the compiler would know about?

I don't know, but if you check the metadata testcases and docs you will see that
it can be used as an argument for an intrinsic through some magic. Another
possibility is to replace all the catch info with a single global constant
(which would be in the llvm.metadata section) that contains the info. So an
invoke would look like this (I left bitcasts off the global):

@catch32 = private constant { i8*, i1, { i8*, i8* }} = { @__gxx_personality_v0, false, { @_ZTIi, @_ZTIPKc } } ; would be in metadata section, I forget the syntax

...

     invoke void @_Z3foov()
           to label %invcont unwind label on @catch32

Or something like that. Anyway, personalities, type infos etc would no
longer be hard-wired into the IR.

Do you have an idea for how to keep catches etc out of the definition of the
IR? I'm worried that if one day we add support for, say, SEH then we will
have to change how the IR is defined again, and that's better avoided.

I haven't given it a lot of thought. I don't like encoding DWARF-specific
concepts into the IR. But my proposal doesn't do that either. It involves
generic concepts that could be applied to all forms of EH.

I'm not sure they are generic. Catches, cleanups, filters, typeinfos,
personality functions - how generic are these? I don't know enough about
exception handling implementations on other platforms to say.

John all ready mentioned problems with your inlining proposal.

He seems to have changed his mind once I added in a small but important detail
in my amendment (namely, that if an exception doesn't match the catches on an
invoke, and the cleanup flag is not set, then it is unspecified as to whether
it unwinds straight through the invoke, or branches to the landing pad).

Here is the code

that brought up the need for what you instantly labeled a "horrible hack".

Sorry for the nasty name. But you've got to admit that it's not beautiful, and
I have to admit that it solved a real problem.

It

must work flawlessly with your new proposal.

http://llvm.org/viewvc/llvm-project?view=rev&revision=99670
<http://llvm.org/viewvc/llvm-project?view=rev&revision=99670>

int main() {
try {

         throw new std::exception();
     } catch (std::exception *e) {
         throw e;
     }
}

Here's the IR with my scheme ("auto-generated" from the LLVM IR produced by
dragonegg at -O1 using the recipe described in my proposal). A point of
interest is that the cleanup flag is set on the invoke in <L3>, so when it
throws excecution branches to <L1> (a cleanup) before unwinding continues
via the block labelled "rewind". [Or the program just terminates at that
point if there is no handler further up the call stack, since there would
then only be cleanups all the way up the stack].

define i32 @main() noreturn {
entry:
   %D.2324_1 = invoke i8* @_Znwm(i64 8)
           to label %"<bb 3>" unwind label %"<L2>" personality @__gxx_personality_v0 catches %struct.__pointer_type_info_pseudo* @_ZTIPSt9exception

"<bb 3>": ; preds = %entry
   %0 = bitcast i8* %D.2324_1 to i32 (...)***
   store i32 (...)** getelementptr inbounds ([5 x i32 (...)*]* @_ZTVSt9exception, i64 0, i64 2), i32 (...)*** %0, align 8
   %D.2306_4 = bitcast i8* %D.2324_1 to %struct.exception*
   %D.2305_5 = tail call i8* @__cxa_allocate_exception(i64 8) nounwind
   %D.2318_6 = bitcast i8* %D.2305_5 to %struct.exception**
   store %struct.exception* %D.2306_4, %struct.exception** %D.2318_6, align 8
   invoke void @__cxa_throw(i8* %D.2305_5, i8* bitcast (%struct.__pointer_type_info_pseudo* @_ZTIPSt9exception to i8*), void (i8*)* null) noreturn
           to label %1 unwind label %"<L2>" personality @__gxx_personality_v0 catches %struct.__pointer_type_info_pseudo* @_ZTIPSt9exception

; <label>:1 ; preds = %"<bb 3>"
   unreachable

"<L1>": ; preds = %"<L3>"
   %exc_ptr2 = tail call i8* @llvm.eh.exception()
   %filter3 = tail call i32 @llvm.eh.selector()
   tail call void @__cxa_end_catch() nounwind
   br label %rewind

"<L2>": ; preds = %"<bb 3>", %entry
   %exc_ptr = tail call i8* @llvm.eh.exception()
   %filter = tail call i32 @llvm.eh.selector()
   %typeid = tail call i32 @llvm.eh.typeid.for(i8* bitcast (%struct.__pointer_type_info_pseudo* @_ZTIPSt9exception to i8*))
   %2 = icmp eq i32 %filter, %typeid
   br i1 %2, label %"<L3>", label %rewind

"<L3>": ; preds = %"<L2>"
   %D.2320_8 = tail call i8* @__cxa_begin_catch(i8* %exc_ptr) nounwind
   %e.0_9 = bitcast i8* %D.2320_8 to %struct.exception*
   %D.2312_11 = tail call i8* @__cxa_allocate_exception(i64 8) nounwind
   %D.2321_12 = bitcast i8* %D.2312_11 to %struct.exception**
   store %struct.exception* %e.0_9, %struct.exception** %D.2321_12, align 8
   invoke void @__cxa_throw(i8* %D.2312_11, i8* bitcast (%struct.__pointer_type_info_pseudo* @_ZTIPSt9exception to i8*), void (i8*)* null) noreturn
           to label %3 unwind label %"<L1>" personality @__gxx_personality_v0 cleanup

; <label>:3 ; preds = %"<L3>"
   unreachable

rewind: ; preds = %"<L2>", %"<L1>"
   %rewind_tmp.0 = phi i8* [ %exc_ptr2, %"<L1>" ], [ %exc_ptr, %"<L2>" ]
   %rewind_sel.0 = phi i32 [ %filter3, %"<L1>" ], [ %filter, %"<L2>" ]
   call void @eh.set.exception(i8 *%rewind_tmp.0)
   call void @eh.set.selector(i32 %rewind_sel.0)
   unwind
}

It looks good to me, do you see anything wrong with it?

And this code needs to give a sensible backtrace (on Darwin at least). It will crash libunwind because it was doing horrible things:

#import <Foundation/Foundation.h>

int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

@try {
@throw [NSException exceptionWithName:@"TestException" reason:@"Test" userInfo:nil];
}
@catch (NSException *e) {
@throw e;
}

[pool drain];
return 0;
}

I was unable to compile this (I don't have Foundation). However as my scheme
generates identical unwind tables to gcc (this is the whole point of it) I am
sure it will be fine!

Ciao, Duncan.

Do you have an idea for how to keep catches etc out of the definition of the
IR? I'm worried that if one day we add support for, say, SEH then we will
have to change how the IR is defined again, and that's better avoided.

I haven't given it a lot of thought. I don't like encoding DWARF-specific
concepts into the IR. But my proposal doesn't do that either. It involves
generic concepts that could be applied to all forms of EH.

I'm not sure they are generic. Catches, cleanups, filters, typeinfos,
personality functions - how generic are these? I don't know enough about
exception handling implementations on other platforms to say.

Well, even within DWARF EH, there are some things we'll never be able to
support without de-virtualizing LSDA layout; we've just been lucky so far that
all the personality functions we care about supporting have very similar LSDA
structure.

In SEH-based C++ exceptions, try block handlers (i.e. catches) and unwind
actions (i.e. cleanups) are actually funclets. A function with EH actions registers
itself with the runtime on entry. As the function executes, it maintains an int
variable called the exception state. A function wide-table has three subtables:
- a list of RTTI pointers for the exception filter
- a table, indexed by exception state, indicating the enclosing state and giving
   an optional funclet pointer for how to unwind out of that state
- a list of try blocks, indicating the range of states they cover and giving a list
   of catch handlers, which are basically pairs of RTTI pointers and corresponding
   handler funclets that return the address to resume to.
I think we're not legally allowed to implement SEH, but if we were, there's a
surprisingly faithful lowering from Bill's dispatch instruction:
  - every landing pad is a state
  - the enclosing state is the state for the resume landing pad
  - if there's non-trivial code between the landing pad and the dispatch, it
    gets split out as the unwind funclet for that state
  - frontends would decorate branches out of the catch handler with some
    intrinsic call, and then we'd split out each catch handler as a funclet.

John all ready mentioned problems with your inlining proposal.

He seems to have changed his mind once I added in a small but important detail
in my amendment (namely, that if an exception doesn't match the catches on an
invoke, and the cleanup flag is not set, then it is unspecified as to whether
it unwinds straight through the invoke, or branches to the landing pad).

For the record, I still prefer Bill's proposal; I just think your revised proposal
is now viable.

John.