Skip to content

Commit

Permalink
Merge pull request WebAssembly#33 from KarlSchimpf/layer1
Browse files Browse the repository at this point in the history
Initial draft of the layer 1.
  • Loading branch information
eholk authored Feb 9, 2018
2 parents ddf3e5a + d2ff7d8 commit f87ba21
Showing 1 changed file with 390 additions and 0 deletions.
390 changes: 390 additions & 0 deletions proposals/Layer-1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
# Layer 1 exception handling

Layer 1 of exception handling is the MVP (minimal viable proposal) for
implementing exceptions in WebAssembly. As such, it doesn't include higher-level
concepts and structures. These concept and structures are introduced in later
layers, and either:

1. Improves readability by combining concepts in layer 1 into higher-level
constructs, thereby reducing code size.

2. Allow performance improvements in the VM.

3. Introduce additional new functionality not available in layer 1.

## Overview

Exception handling allows code to break control flow when an exception is
thrown. The exeception can be any exception known by the WebAssembly module, or
it may an unknown exception that was thrown by a called imported function.

One of the problems with exception handling is that both WebAssembly and the
host VM probably have different notions of what exceptions are, but both must be
aware of the other.

It is difficult to define exceptions in WebAssembly because (in general)
it doesn't have knowledge of the host VM. Further, adding such knowledge to
WebAssembly would limit the ability for other host VMs to support WebAssembly
exceptions.

One issue is that both sides need to know if an exception was thrown by the
other, because cleanup may need to be performed.

Another problem is that WebAssembly doesn't have direct access to the host VM's
memory. As a result, WebAssembly defers the handling of exceptions to the host
VM.

To access exceptions, WebAssembly provides instructions to check if the
exception is one that WebAssembly understands. If so, the data of the
WebAssembly exceptions's data is extracted and copied onto the stack, allowing
succeeding instructions to process the data.

Lastly, exception lifetimes must be maintained by the host VM, so that it can
collect and reuse the memory used by exceptions. This implies that the host must
know where exceptions are stored, so that it can determine when an exception can
be garbage collected.

This also implies that the host VM must provide a garbage collector for
exceptions. For host VMs that have garbage collection (such as JavaScript),
this is not a problem.

However, not all host VMs may have a garbage collector. For this reason,
WebAssembly exceptions are designed to allow the use of reference counters to
perform the the garbage collection in the host VM.

To do this, WebAssembly exceptions are immutable once created, to avoid cyclic
data structures that can't be garbage collected. It also means that exceptions
can't be stored into linear memory. The rationale for this is twofold:

* For security. Loads
and stores do not guarantee that the data read was of the same type as
stored. This allows spoofing of exception references that may allow a
WebAssembly module to access data it should not know in the host VM.

* The host VM does not know the layout of data in linear memory, so it can't
find places where exception references are stored.

Hence, while an exception reference is a new first class type, this proposal
disallows their usage in linear memory.

A WebAssembly exception is created by the `except` instruction. Once an
exception is created, you can throw it with the `throw` instruction. Thrown
exceptions are handled as follows:

1. They can be caught by a catch block in an enclosing try block of a function
body. The caught exception is pushed onto the stack.

1. Throws not caught within a function body continue up the call stack, popping
call frames, until an enclosing try block is found.

1. If the call stack is exhausted without any enclosing try blocks, the host VM
defines how to handle the uncaught exception.

### Exceptions

An `exception` is an internal construct in WebAssembly that is maintained by the
host. WebAssembly exceptions (as opposed to host exceptions) are defined by a
new `exception section` of a WebAssembly module. The exception section is a list
of exception types, from which exceptions can be created.

Each exception type has a `type signature`. The type signature defines the list
of values associated with the exception.

Within the module, exception types are identified by an index into the
[exception index space](#exception-index-space). This index is referred to as
the `exception tag`. The `tagged exception type` is the corresponding
exception type refered to by the exception tag.

Exception types can be imported and exported by adding the appropriate entries
to the import and export sections of the module. All imported/exported exception
types must be named to reconcile exception tags between modules.

Exception tags are used by:

1. The `except` instruction which creates a WebAssembly instance of the
corresponding tagged exception type, and pushes a reference to it onto the
stack.

2. The `if_except` instruction that queries an exception to see if it is an
instance of the corresponding tagged exception class, and if true it pushes
the corresponding values of the exception onto the stack.

### The exception refernce data type

Data types are extended to have a new `except_ref` type. The representation of
an exception type is left to the host VM, but its size must be fixed. The actual
number of bytes it takes to represent any `except_ref` is left to the host VM.

### Try and catch blocks

A _try block_ defines a list of instructions that may need to process exceptions
and/or clean up state when an exception is thrown. Like other higher-level
constructs, a try block begins with a `try` instruction, and ends with an `end`
instruction. That is, a try block is sequence of instructions having the
following form:

```
try block_type
instruction*
catch
instruction*
end
```

A try block ends with a `catch block` that is defined by the list of
instructions after the `catch` instruction.

Try blocks, like control-flow blocks, have a _block type_. The block type of a
try block defines the values yielded by the evaluation the try block when either
no exception is thrown, or the exception is successfully caught by the catch
block.

In the initial implementation, try blocks may only yield 0 or 1 values.

### Exception creation

A `except` instruction has a single immediate argument, an exception tag. The
corresponding tagged exception type is used to define the data fields of the
created exception. The values for the data fields must be on top of the operand
stack, and must correspond to the exception's type signature. These values are
popped off the stack and an instance of the exception is then created. A
reference to the created exception is then pushed onto the stack.

### Throws

The `throw` throws the exception on top of the stack. The exception is popped
off the top of the stack before throwing.

When an exception is thrown, the host VM searches for nearest enclosing try
block body that execution is in. That try block is called the _catching_ try
block.

If the throw appears within the body of a try block, it is the catching try
block.

If a throw occurs within a function body, and it doesn't appear inside the body
of a try block, the throw continues up the call stack until it is in the body of
an an enclosing try block, or the call stack is flushed. If the call stack is
flushed, the host VM defines how to handle uncaught exceptions. Otherwise, the
found enclosing try block is the catching try block.

A throw inside the body of a catch block is never caught by the corresponding
try block of the catch block, since instructions in the body of the catch block
are not in the body of the try block.

Once a catching try block is found for the throw, the operand stack is popped back
to the size the operand stack had when the try block was entered, and then
the caught exception is pushed onto the stack.

If control is transferred to the body of a catch block, and the last instruction
in the body is executed, control then exits the try block.

If the selected catch block does not throw an exception, it must yield the
value(s) expected by the corresponding catching try block. This includes popping
the caught exception.

Note that a caught exception can be rethrown using the `throw` instruction.

### Exception data extraction

The `if_except block` defines a conditional query of the exception on top of the
stack. The exception is not popped when queried. The if_except block has two
subblocks, the `then` and `else` subblocks, like that of an `if` block. The then
block is a sequence of instructions following the `if_except` instruction. The
else block is optional, and if it appears, it begins with the `else`
instruction. The scope of the if_except block is from the `if_except`
instruction to the corresponding `end` instruction.

That is, the forms of an if_except block is:

```
if_except block_type except_index
Instruction*
end
if_except block_type except_index
Instruction*
else
Instructions*
end
```

The conditional query of an exception succeeds when the exception on the top of
the stack is an instance of the corresponding tagged exception type (defined by
`except_index`).

If the query succeeds, the data values (associated with the type signature of
the exception class) are extracted and pushed onto the stack, and control
transfers to the instructions in the then block.

If the query fails, it either enters the else block, or transfer control to the
end of the if_except block if there is no else block.

### Debugging

Earlier discussion implied that when an exception is thrown, the runtime will
pop the operand stack across function calls until a corresponding, enclosing try
block is found. The implementation may actually not do this. Rather, it may
first search up the call stack to see if there is an enclosing try. If none are
found, it could terminate the thread at the point of the throw. This would
allow better debugging capability, since the corresponding call stack is still
there to query.

## Changes to the text format.

This section describes change in the
[instruction syntax document](https://github.com/WebAssembly/spec/blob/master/document/core/instructions.rst).

### New instructions

The following rules are added to *instructions*:

```
try resulttype instructions* catch instructions* end |
except except_index |
throw |
if_except resulttype except_index then Instructions* end |
if_except resulttype except_index then Instructions* else Instructions* end
```

Like the `block`, `loop`, and `if` instructions, the `try` and `if_except`
instructions are *structured* control flow instructions, and can be
labeled. This allows branch instructions to exit try and `if_except` blocks.

The `except_index` of the `except` and `if_except` instructions defines the
exception type to create/extract form. See [exception index
space](#exception-index-space) for further clarification of exception tags.

## Changes to Modules document.

This section describes change in the
[Modules document](https://github.com/WebAssembly/design/blob/master/Modules.md).

### Exception index space

The _exception index space_ indexes all imported and internally-defined
exceptions, assigning monotonically-increasing indices based on the order
defined in the import and exception sections. Thus, the index space starts at
zero with imported exceptions followed by internally-defined exceptions in
the [exception section](#exception-section).

## Changes to the binary model

This section describes changes in
the
[binary encoding design document](https://github.com/WebAssembly/design/blob/master/BinaryEncoding.md).


### Data Types

#### except_ref

An exception reference pointing to an instance of an exception. The size
is fixed, but unknown in WebAssembly (the host defines the size in bytes).

### Language Types

| Opcode | Type constructor |
|--------|------------------|
| -0x41 | `except_ref` |

#### value_type

A `varint7` indicating a a `value type` is extended to include `except_ref` as
encoded above.

#### Other Types

##### except_type

An exception is described by its exception type signature, which corresponds to
the data fields of the exception.

| Field | Type | Description |
|-------|------|-------------|
| `count` | `varuint32` | The number of types in the signature |
| `type` | `value_type*` | The type of each element in the signature |


##### external_kind

A single-byte unsigned integer indicating the kind of definition being imported
or defined:

* `0` indicating a `Function` [import](Modules.md#imports) or [definition](Modules.md#function-and-code-sections)
* `1` indicating a `Table` [import](Modules.md#imports) or [definition](Modules.md#table-section)
* `2` indicating a `Memory` [import](Modules.md#imports) or [definition](Modules.md#linear-memory-section)
* `3` indicating a `Global` [import](Modules.md#imports) or [definition](Modules.md#global-section)
* `4` indicating an `Exception` [import](#import-section) or [definition](#exception-sectio)

### Module structure

#### High-level structure

A new `exception` section is introduced and is named `exception`. If included,
it must appear between the `Export` and `Start` sections of the module.


##### Exception section

The `exception` section is the named section 'exception'. The exception section
declares exception types using exception type signatures.

| Field | Type | Description |
|-------|------|-------------|
| count | `varuint32` | count of the number of exceptions to follow |
| sig | `except_type*` | The type signature of the data fields for each exception |


##### Import section

The import section is extended to include exception types by extending an
`import_entry` as follows:

If the `kind` is `Exception`:

| Field | Type | Description |
|-------|------|-------------|
| `sig` | `except_type` | the type signature of the exception |

##### Export section

The export section is extended to include exception types by extending an
`export_entry` as follows:

If the `kind` is `Exception`, then the `index` is into the corresponding
exception in the [exception index space](#exception-index-space).


##### Name section

The set of known values for `name_type` of a name section is extended as
follows:

| Name Type | Code | Description |
| --------- | ---- | ----------- |
| [Function](#function-names) | `1` | Assigns names to functions |
| [Local](#local-names) | `2` | Assigns names to locals in functions |
| [Exception](#exception-names) | `3` | Assigns names to exception types |

###### Exception names

The exception names subsection is a `name_map` which assigns names to a subset
of the _exception_ indices from the exception section. (Used for both imports
and module-defined).

### Control flow operators

The control flow operators are extended to define try blocks, catch blocks,
throws, and rethrows as follows:

| Name | Opcode | Immediates | Description |
| ---- | ---- | ---- | ---- |
| `try` | `0x06` | sig : `block_type` | begins a block which can handle thrown exceptions |
| `catch` | `0x07` | | begins the catch block of the try block |
| `throw` | `0x08` | |Throws the exception on top of the stack |
| `except` | `0x09` | tag : varuint32 | Creates an exception defined by the exception tag and pushes reference on stack |
| `if_except` | `0x0a` | sig : `block_type` , tag : `varuint32` | Begin exception data extraction if exception on stack was created using the corresponding exception tag |

The *sig* fields of `block`, `if`, `try` and `if_except` operators are block
signatures which describe their use of the operand stack.

0 comments on commit f87ba21

Please sign in to comment.