Skip to content

[sil] Add some small docs for copy_value, destroy_value, end_borrow, load_borrow #5453

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 25, 2016
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2166,6 +2166,48 @@ requires ``release`` or other cleanup, that value must be loaded before being
stored over and cleaned up. It is undefined behavior to store to an address
that points to deallocated storage.

load_borrow
```````````

::

sil-instruction ::= 'load_borrow' sil-value

%1 = load_borrow %0 : $*T
// $T must be a loadable type

Loads the value ``%1`` from the memory location ``%0``. The ``load_borrow``
instruction creates a borrowed scope in which a read-only borrow value ``%1``
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should define what's a read-only borrow value is. I assume it's the same as for end_borrow (except %1 instead of %0):

  1. If %1 is an address, %1 can not be written to.
  2. If %1 is a non-trivial value, %1 can not be destroyed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, we can't load an address so 1. is not an option.

Somehow I'm missing information what a load_borrow actually "does". For example:
For non-trivial types it loads the value without retaining the value.
For trivial types... well, can we use it for trivial types?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think if I elaborate specifically on what this instruction does for the various categories of instructions (as you requested below), your question will be answered

can be used to read the value stored in ``%0``. The end of scope is deliminated
by an ``end_borrow`` instruction. All ``load_borrow`` instructions must be
paired with exactly one ``end_borrow`` instruction along any path through the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the "exactly one" really necessary? I have the feeling that it restricts the optimizer too much. Can't we have multiple end_borrow for a single load_borrow, like we do for alloc_stack-dealloc_stack?
Here is an example of a borrow-scope in a loop:

loop_head:
%1 = load_borrow %0
cond_br %c, loop_exit, loop_body
loop_body:
// some code
end_borrow %1 from %0
br loop_head
loop_exit:
end_borrow %1 from %0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notice how I said exactly one along any path through the program. That is the same condition as alloc-stack/dealloc-stack.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I missed that

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries = ).

program. Until ``end_borrow``, it is illegal to invalidate or store to ``%0``.

end_borrow
``````````

::

sil-instruction ::= 'end_borrow' sil-value 'from' sil-value : sil-type, sil-type

end_borrow %1 from %0 : $T, $T
end_borrow %1 from %0 : $T, $*T
end_borrow %1 from %0 : $*T, $T
end_borrow %1 from %0 : $*T, $*T
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If both operands are addresses, do we require them to be the same address?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No.

Copy link
Contributor Author

@gottesmm gottesmm Oct 26, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea is one could do something like this:

%1 = alloc_stack $T
borrow_addr %1 from %0 : $*T
...
end_borrow %1 from %0 : $*T, $*T

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep in mind, that I think we would only do this if we wanted to ensure that all borrowed addresses did not alias. Beyond that I can not see a good reason to have a "borrow" copy like operation that moves the borrowed value to a new memory location (but perhaps I am missing something).

I wrote this instruction purposely as generally as possible so that we could restrict it over time as we get a better idea of the exact constraints that we want here.

// We allow for end_borrow to be specified in between values and addresses
// all of the same type T.

Ends the scope for which the SILValue ``%1`` is borrowed from the SILValue
``%0``. Must be paired with at most 1 borrowing instruction (like
``load_borrow``) along any path through the program. In the region in between
the borrow instruction and the ``end_borrow``, the original SILValue can not be
modified. This means that:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What requirements do we have for the end_borrow operands?
I assume that %0 and %1 must be the same "value" as %0 and %1 of load_borrow. Does it need to be the same SILValue? Or can it be a derived value? Example 1:

%1 = load_borrow %0 : $*T
%2 = upcast %1 to $Base // OK?
end_borrow %2 from %0

Example 2:

%1 = load_borrow %0 : $*T
%2 = struct_extract %1, #only_field_with_ref_semantics
%3 = struct_element_addr %0, #only_field_with_ref_semantics
end_borrow %2 from %3

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine we will have some form of forwarding. Since borrows are in the type system, it should all just work.


1. If ``%0`` is an address, ``%0`` can not be written to.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently we only have load_borrow to begin a borrowing scope, right?
In this case %0 is always the address type of %1. Or am I missing something?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No you are not missing anything. I was implementing a very general instruction that could be used for any of the borrowing cases that are interesting to us.

2. If ``%0`` is a non-trivial value, ``%0`` can not be destroyed.

We require that ``%1`` and ``%0`` have the same type ignoring SILValueCategory.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't use the term SILValueCategory, it's an implementation class. Maybe you can use "formal type" instead?
Again, isn't %0 always the address type of the object type %1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also have a borrow of an object from an object for instance. Or potentially an address type from an address type. Or even potentially an address from a value (for instance in the case of a re-abstraction thunk).


assign
``````
::
Expand Down Expand Up @@ -3191,6 +3233,32 @@ For aggregate types, especially enums, it is typically both easier
and more efficient to reason about aggregate copies than it is to
reason about copies of the subobjects.

copy_value
``````````

::

sil-instruction ::= 'copy_value' sil-operand

%1 = copy_value %0 : $A

Performs a copy of a loadable value as if by the value's type lowering and
returns the copy. The returned copy semantically is a value that is completely
independent of the operand. In terms of specific types:

1. For trivial types, this is equivalent to just propagating through the trivial
value.
2. For reference types, this is equivalent to performing a ``strong_retain``
operation and returning the reference.
3. For ``@unowned`` types, this is equivalent to performing an
``unowned_retain`` and returning the operand.
4. For aggregate types, this is equivalent to recursively performing a
``copy_value`` on its components, forming a new aggregate from the copied
components, and then returning the new aggregate.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This list is a very good description. It would help if you could write something similar for load_borrow and end_borrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I think doing such a thing will provide the clarity that you want above.


In ownership qualified functions, a ``copy_value`` produces a +1 value that must
be consumed at most once along any path through the program.

release_value
`````````````

Expand All @@ -3214,6 +3282,29 @@ For aggregate types, especially enums, it is typically both easier
and more efficient to reason about aggregate destroys than it is to
reason about destroys of the subobjects.

destroy_value
`````````````

::

sil-instruction ::= 'destroy_value' sil-operand

destroy_value %0 : $A

Destroys a loadable value, by releasing any retainable pointers within it.

This is defined to be equivalent to storing the operand into a stack
allocation and using 'destroy_addr' to destroy the object there.

For trivial types, this is a no-op. For reference types, this is
equivalent to a ``strong_release``. For ``@unowned`` types, this is
equivalent to an ``unowned_release``. In each of these cases, those
are the preferred forms.

For aggregate types, especially enums, it is typically both easier
and more efficient to reason about aggregate destroys than it is to
reason about destroys of the subobjects.

autorelease_value
`````````````````

Expand Down