Skip to content

[NFC] SILFunctionArgument and ApplySite docs and comments. #18119

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 2 commits into from
Jul 27, 2018
Merged
Show file tree
Hide file tree
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
3 changes: 3 additions & 0 deletions docs/SIL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ In contrast to LLVM IR, SIL is a generally target-independent format
representation that can be used for code distribution, but it can also express
target-specific concepts as well as LLVM can.

For more information on developing the implementation of SIL and SIL passes, see
SILProgrammersManual.md.

SIL in the Swift Compiler
-------------------------

Expand Down
181 changes: 181 additions & 0 deletions docs/SILProgrammersManual.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# SIL Programmers' Manual

This document provides information for developers working on the
implementation of SIL. The formal specification of the Swift
Intermediate _Language_ is in SIL.rst. This is a guide to the internal
implementation. Source comments normally provide this level of
information as much as possible. However, some features of the
implementation are spread out across the source base. This
documentation is meant to offer a central point for approaching the
source base with links to the code for more detailed comments.

## SILType

TBD: Define the different levels of types. Explain type lowering with
examples.

## SILInstructionResults

TBD: Explain how the various types fit together with pointers to the
source: SILValue, SILInstruction, SingleValueInstruction,
MultipleValueInstructionResult. And why it was done this way.

## `SILFunction` and `apply` arguments.

Throughout the compiler, integer indices are used to identify argument
positions in several different contexts:

- A `SILFunctionType` has a tuple of parameters.

- The SIL function definition has a list of `SILFunctionArgument`.
This is the callee-side argument list. It includes indirect results.

- `apply`, `try_apply`, and `begin_apply` have "applied arguments":
the subset of instruction operands representing the callee's
`SILFunctionArgument` list.

- `partial_apply` has "applied arguments": the subset of instruction
operands representing closure captures. Closure captures in turn map
to a subset of the callee's `SILFunctionArgument` list.

- In the above three contexts, `SILFunctionArgument`, `apply`, and
`partial_apply`, the argument indices also depend on the SIL stage:
Canonical vs. Lowered.

Consider the example:

```
func example<T>(Int, i, t: T) -> (Int, T) {
let foo = { return ($0, t) }
return foo(i)
}
```

The closure `foo` has the indices as numbered below in each
context, ignoring the calling convention and boxing/unboxing of
captures for brevity:

The closure's `SILFunctionType` has two direct formal parameters at
indices (`#0`, `#1`) and one direct formal result of tuple type:

```
SILFunctionType(foo): (#0: Int, #1: T) -> @out (Int, T)
```

Canonical SIL with opaque values matches `SILFunctionType`. The
definition of `foo` has two direct `SILFunctionArgument`s at (`#0`,
`#1`):

```
SILFunctionArguments: (#0: Int, #1: T) -> (Int, T)
```

The Lowered SIL for `foo`s definition has an indirect "result
argument" at index #0. The function parameter indices are now (`#1`,
`#2`):

```
SILFunctionArguments: (#0: *T, #1: Int, #2: T) -> Int
```

Creation of the closure has one applied argument at index `#0`. Note
that the first applied argument is actually the second operand (the
first is the callee), and in lowered SIL, it is actually the third
SILFunctionArgument (after the indirect result and first parameter):

```
%closure = partial_apply @foo(#0: %t)
```

Application of the closure with opaque values has one applied
argument:

```
%resultTuple = apply %closure(#0: i)
```

Lowered application of the closure has two applied arguments:

```
%directResult = apply %closure(#0: %indirectResult: $*T, #1: i)
```

The mapping between `SILFunctionType` and `SILFunctionArgument`, which depends
on the SIL stage, is managed by the
[SILFunctionConventions](https://github.com/apple/swift/blob/master/include/swift/SIL/SILFunctionConventions.h)
abstraction. This API follows naming conventions to communicate the meaning of the integer indices:

- "Parameters" refer to the function signature's tuple of arguments.

- "SILArguments" refer to the set of `SILFunctionArgument` in the callee's entry block, including any indirect results required by the current SIL stage.

These argument indices and their relative offsets should never be
hardcoded. Although this is common practice in LLVM, it should be
avoided in SIL: (a) the structure of SIL instructions, particularly
applies, is much more nuanced than LLVM IR, (b) assumptions that may
be valid at the initial point of use are often copied into parts of
the code where they are no longer valid; and (c) unlike LLVM IR, SIL
is not stable and will continue evolving.

Translation between SILArgument and parameter indices should use:
`SILFunctionConventions::getSILArgIndexOfFirstParam()`.

Translation between SILArgument and result indices should use:
`SILFunctionConventions::getSILArgIndexOfFirstIndirectResult()`.

Convenience methods exist for the most common uses, so there is
typically no need to use the above "IndexOfFirst" methods to translate
one integer index into another. The naming convention of the
convenience method should clearly indicate which form of index it
expects. For example, information about a parameter's type can be retrieved directly from a SILArgument index: `getParamInfoForSILArg(index)`, `getSILArgumentConvention(index)`, and `getSILArgumentType(index)`.

Another abstraction,
[`ApplySite`](https://github.com/search?utf8=✓&q=%22class+ApplySite%22+repo%3Aapple%2Fswift+path%3Ainclude%2Fswift%2FSIL&type=Code&ref=advsearch&l=&l=),
abstracts over the various kinds of `apply` instructions, including
`try_apply`, `begin_apply`, and `partial_apply`.

`ApplySite::getSubstCalleeConv()` is commonly used to query the
callee's `SILFunctionConventions` which provides information about the
function's type and its definition as explained above. Information about the applied arguments can be queried directly from the `ApplySite` API.

For example, `ApplySite::getAppliedArgumentConvention(index)` takes an
applied argument index, while
`SILFunctionArguments::getSILArgumentConvention(index`) takes a
`SILFunctionArgument` index. They both return the same information,
but from a different viewpoint.

A common mistake is to directly map the ApplySite's caller-side
arguments onto callee-side SILFunctionArguments. This happens to work
until the same code is exposed to a `partial_apply`. Instead, use the `ApplySite` API for applied argument indices, or use
`ApplySite::getCalleeArgIndexOfFirstAppliedArg()` to translate the
apply's arguments into function convention arguments.

Consistent use of common idioms for accessing arguments should be
adopted throughout the compiler. Plenty of bugs have resulted from
assumptions about the form of SIL made in one area of the compiler
that have been copied into other parts of the compiler. For example,
knowing that a block of code is guarded by a dynamic condition that
rules out PartialApplies is no excuse to conflate applied arguments
with function arguments. Also, without consistent use of common
idioms, it becomes overly burdensome to evolve these APIs over time.

## SILGen

TBD: Possibly link to a separate document explaining the architecture of SILGen.

Some information from SIL.rst could be moved here.

## IRGen

TBD: Possibly link to a separate document explaining the architecture of IRGen.

## SILAnalysis and the PassManager

TBD: describe the mechanism by which passes invalidate and update the
PassManager and its avaiable analyses.

## High Level SIL Optimizations

HighLevelSILOptimizations.rst discusses how the optimizer imbues
certain special SIL types and SIL functions with higher level
semantics.
116 changes: 67 additions & 49 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1936,6 +1936,7 @@ class ApplyInstBase<Impl, Base, true>
return OperandValueArrayRef(opsWithoutSelf);
}

/// Return the SILArgumentConvention for the given applied argument index.
SILArgumentConvention getArgumentConvention(unsigned index) const {
return getSubstCalleeConv().getSILArgumentConvention(index);
}
Expand Down Expand Up @@ -7620,47 +7621,59 @@ class ApplySite {
FOREACH_IMPL_RETURN(getSubstitutionMap());
}

/// The arguments passed to this instruction.
/// Return the associated specialization information.
const GenericSpecializationInformation *getSpecializationInfo() const {
FOREACH_IMPL_RETURN(getSpecializationInfo());
}

/// Return an operand list corresponding to the applied arguments.
MutableArrayRef<Operand> getArgumentOperands() const {
FOREACH_IMPL_RETURN(getArgumentOperands());
}

/// The arguments passed to this instruction.
/// Return a list of applied argument values.
OperandValueArrayRef getArguments() const {
FOREACH_IMPL_RETURN(getArguments());
}

/// The number of call arguments.
/// Return the number of applied arguments.
unsigned getNumArguments() const {
FOREACH_IMPL_RETURN(getNumArguments());
}

unsigned getOperandIndexOfFirstArgument() {
FOREACH_IMPL_RETURN(getArgumentOperandNumber());
/// Return the apply operand for the given applied argument index.
Operand &getArgumentRef(unsigned i) const { return getArgumentOperands()[i]; }

/// Return the ith applied argument.
SILValue getArgument(unsigned i) const { return getArguments()[i]; }

/// Set the ith applied argument.
void setArgument(unsigned i, SILValue V) const {
getArgumentOperands()[i].set(V);
}

/// Return the associated specialization information.
const GenericSpecializationInformation *getSpecializationInfo() const {
FOREACH_IMPL_RETURN(getSpecializationInfo());
/// Return the operand index of the first applied argument.
unsigned getOperandIndexOfFirstArgument() const {
FOREACH_IMPL_RETURN(getArgumentOperandNumber());
}
#undef FOREACH_IMPL_RETURN

/// The arguments passed to this instruction, without self.
OperandValueArrayRef getArgumentsWithoutSelf() const {
switch (Inst->getKind()) {
case SILInstructionKind::ApplyInst:
return cast<ApplyInst>(Inst)->getArgumentsWithoutSelf();
case SILInstructionKind::BeginApplyInst:
return cast<BeginApplyInst>(Inst)->getArgumentsWithoutSelf();
case SILInstructionKind::TryApplyInst:
return cast<TryApplyInst>(Inst)->getArgumentsWithoutSelf();
default:
llvm_unreachable("not implemented for this instruction!");
}
/// Returns true if \p oper is an argument operand and not the callee
/// operand.
bool isArgumentOperand(const Operand &oper) const {
return oper.getOperandNumber() >= getOperandIndexOfFirstArgument();
}

/// Return the applied argument index for the given operand.
unsigned getArgumentIndex(const Operand &oper) const {
assert(oper.getUser() == Inst);
assert(isArgumentOperand(oper));

return oper.getOperandNumber() - getOperandIndexOfFirstArgument();
}

// Get the callee argument index corresponding to the caller's first applied
// argument. Returns 0 for full applies. May return > 0 for partial applies.
/// Return the callee's function argument index corresponding to the first
/// applied argument: 0 for full applies; >= 0 for partial applies.
unsigned getCalleeArgIndexOfFirstAppliedArg() const {
switch (Inst->getKind()) {
case SILInstructionKind::ApplyInst:
Expand All @@ -7683,35 +7696,30 @@ class ApplySite {
}
}

/// Returns true if \p oper is an argument operand and not the callee
/// operand.
bool isArgumentOperand(const Operand &oper) {
return oper.getOperandNumber() >= getOperandIndexOfFirstArgument();
/// Return the callee's function argument index corresponding to the given
/// apply operand. Each function argument index identifies a
/// SILFunctionArgument in the callee and can be used as a
/// SILFunctionConvention argument index.
///
/// Note: Passing an applied argument index into SILFunctionConvention, as
/// opposed to a function argument index, is incorrect.
unsigned getCalleeArgIndex(const Operand &oper) const {
return getCalleeArgIndexOfFirstAppliedArg() + getArgumentIndex(oper);
}

// Translate the index of the argument to the full apply or partial_apply into
// to the corresponding index into the arguments of the called function.
unsigned getCalleeArgIndex(const Operand &oper) {
assert(oper.getUser() == Inst);
assert(isArgumentOperand(oper));

unsigned appliedArgIdx =
oper.getOperandNumber() - getOperandIndexOfFirstArgument();

return getCalleeArgIndexOfFirstAppliedArg() + appliedArgIdx;
/// Return the SILArgumentConvention for the given applied argument operand.
SILArgumentConvention getArgumentConvention(Operand &oper) const {
unsigned calleeArgIdx =
getCalleeArgIndexOfFirstAppliedArg() + getArgumentIndex(oper);
return getSubstCalleeConv().getSILArgumentConvention(calleeArgIdx);
}

Operand &getArgumentRef(unsigned i) const { return getArgumentOperands()[i]; }

/// Return the ith argument passed to this instruction.
SILValue getArgument(unsigned i) const { return getArguments()[i]; }

/// Set the ith argument of this instruction.
void setArgument(unsigned i, SILValue V) const {
getArgumentOperands()[i].set(V);
// FIXME: This is incorrect. It will be removed in the next commit.
SILArgumentConvention getArgumentConvention(unsigned index) const {
return getSubstCalleeConv().getSILArgumentConvention(index);
}

/// Return the self argument passed to this instruction.
/// Return true if 'self' is an applied argument.
bool hasSelfArgument() const {
switch (Inst->getKind()) {
case SILInstructionKind::ApplyInst:
Expand All @@ -7725,7 +7733,7 @@ class ApplySite {
}
}

/// Return the self argument passed to this instruction.
/// Return the applied 'self' argument value.
SILValue getSelfArgument() const {
switch (Inst->getKind()) {
case SILInstructionKind::ApplyInst:
Expand All @@ -7739,7 +7747,7 @@ class ApplySite {
}
}

/// Return the self operand passed to this instruction.
/// Return the 'self' apply operand.
Operand &getSelfArgumentOperand() {
switch (Inst->getKind()) {
case SILInstructionKind::ApplyInst:
Expand All @@ -7753,8 +7761,18 @@ class ApplySite {
}
}

SILArgumentConvention getArgumentConvention(unsigned index) const {
return getSubstCalleeConv().getSILArgumentConvention(index);
/// Return a list of applied arguments without self.
OperandValueArrayRef getArgumentsWithoutSelf() const {
switch (Inst->getKind()) {
case SILInstructionKind::ApplyInst:
return cast<ApplyInst>(Inst)->getArgumentsWithoutSelf();
case SILInstructionKind::BeginApplyInst:
return cast<BeginApplyInst>(Inst)->getArgumentsWithoutSelf();
case SILInstructionKind::TryApplyInst:
return cast<TryApplyInst>(Inst)->getArgumentsWithoutSelf();
default:
llvm_unreachable("not implemented for this instruction!");
}
}

static ApplySite getFromOpaqueValue(void *p) {
Expand Down