The main point of this change is to make sure that a shared function always has a body: both, in the optimizer pipeline and in the swiftmodule file.
This is important because the compiler always needs to emit code for a shared function. Shared functions cannot be referenced from outside the module.
In several corner cases we missed to maintain this invariant which resulted in unresolved-symbol linker errors.
As side-effect of this change we can drop the shared_external SIL linkage and the IsSerializable flag, which simplifies the serialization and linkage concept.
PublicCMOSymbols stores symbols which are made public by cross-module-optimizations.
Those symbols are primarily stored in SILModule and eventually used by TBD generation and validation.
The reason why I am doing this is that we are going to be enabling lexical
lifetimes early in the pipeline so that I can use it for the move operator's
diagnostics.
To make it easy for passes to know whether or not they should support lexical
lifetimes, I included a query on SILOptions called
supportsLexicalLifetimes. This will return true if the pass (given the passed in
option) should insert the lexical lifetime flag. This ensures that passes that
run in both pipelines (e.x.: AllocBoxToStack) know whether or not to set the
lexical lifetime flag without having to locally reason about it.
This is just chopping off layers of a larger patch I am upstreaming.
NOTE: This is technically NFC since it leaves the default alone of not inserting
lexical lifetimes at all.
StackList is a very efficient data structure for worklist type things.
This is a port of the C++ utility with the same name.
Compared to Array, it does not require any memory allocations.
It's not needed anymore with delayed instruction deletion.
It was used for two purposes:
1. For analysis, which cache instructions, to avoid dangling instruction pointers
2. For passes, which maintain worklists of instructions, to remove a deleted instructions from the worklist. This is now done by checking SILInstruction::isDeleted().
When an instruction is "deleted" from the SIL, it is put into the SILModule::scheduledForDeletion list.
The instructions in this list are eventually deleted for real in SILModule::flushDeletedInsts(), which is called by the pass manager after each pass run.
In other words: instruction deletion is deferred to the end of a pass.
This avoids dangling instruction pointers within the run of a pass and in analysis caches.
Note that the analysis invalidation mechanism ensures that analysis caches are invalidated before flushDeletedInsts().
In theory we could map opened archetypes per module because opened archetypes _should_ be unique across the module.
But currently in some rare cases SILGen re-uses the same opened archetype in multiple functions.
The fix is to add the SILFunction to the map's key.
That also requires that we update the map whenever instructions are moved from one function to another.
This fixes a compiler crash.
rdar://76916931
Instead, put the archetype->instrution map into SIlModule.
SILOpenedArchetypesTracker tried to maintain and reconstruct the mapping locally, e.g. during a use of SILBuilder.
Having a "global" map in SILModule makes the whole logic _much_ simpler.
I'm wondering why we didn't do this in the first place.
This requires that opened archetypes must be unique in a module - which makes sense. This was the case anyway, except for keypath accessors (which I fixed in the previous commit) and in some sil test files.
... with a fix for a non-assert build crash: I used the wrong ilist type for SlabList. This does not explain the crash, though. What I think happened here is that llvm miscompiled and put the llvm_unreachable from the Slab's deleteNode function unconditionally into the SILModule destructor.
Now by using simple_ilist, there is no need for a deleteNode at all.
A StackList is the best choice for things like worklists, etc., if no random access is needed.
Regardless of how large a Stack gets, there is no memory allocation needed (except maybe for the first few uses in the compiler run).
All operations have (almost) zero cost.
The needed memory is managed by the SILModule. Initially, the memory slabs are allocated with the module's bump pointer allocator. In contrast to bump pointer allocated memory, those slabs can be freed again (at zero cost) and then recycled.
StackList is meant to be a replacement for llvm::SmallVector, which needs to malloc after the small size is exceeded.
This is more a usability than a compile time improvement.
Usually we think hard about how to correctly use an llvm::SmallVector to avoid memory allocations: we chose the small size wisely and in many cases we keep a shared instance of a SmallVector to reuse its allocated capacity.
All this is not necessary by using a StackList: no need to select a small size and to share it across usages.
...and avoid reallocation.
This is immediately necessary for LICM, in addition to its current
uses. I suspect this could be used by many passes that work with
addresses. RLE/DSE should absolutely migrate to it.
The leak happened in this scenario:
1. A function becomes dead and gets deleted (which means: it gets added to the zombie-list)
2. A function with the same name is created again. This can happen with specializations.
In such a case we just removed the zombie function from the zombie-list without deleting it.
But we cannot delete zombie functions, because they might still be referenced by metadata, like debug-info.
Therefore the right fix is to resurrect the zombie function if a new function is created with the same name.
rdar://problem/66931238
Previously we would only link `.sib` partial
modules in SILGen, with other kinds of serialized
ASTs being linked just before the SILOptimizer if
`-sil-merge-partial-modules` was specified.
However linking them always seems to be the desired
behaviour, so adjust SILGen to link SIL for all
serialized AST inputs.
Private and internal classes shouldn't have ABI constraints on their concrete vtable layout, so if methods
don't have overrides in practice, we can elide their vtable entries.
We were not using the primary benefits of an intrusive list, namely the
ability to insert or remove from the middle of the list, so let's switch
to a plain vector. This also avoids linked-list pointer chasing.
Adjust `SILModule::createEmptyModule` to accept a
FileUnit or ModuleDecl, and pass the corresponding
context for SIL generation and parsing. This
change means that SIL parsing will now correctly
use a SourceFile associated context when in
single-file mode.
Destroying the SIL remark streamer after transferring ownership to LLVM
is frought. For one, the streamer holds the remark file's stream open.
Destroying it early doesn't accomodate sil-opt, which transfers control
to LLVM before running passes that emit remarks.
Instead, just take a reference to the context that the streamer gets
parented onto. If the remarks streamer infrastructure could just hold
the file stream open for us, we wouldn't have to do any of this.
Corrects a mistake introduced in #31106
I was under the impression that the LLVMContext for an instance of
llvm::remarks::RemarkStreamer was somehow just scratch-space. It turns
out the ASMPrinters don't gather remarks data from modules, they gather
it from the remark streamer associated with the module's context.
Thus, we cannot have the module's context be distinct from whatever
context the streamer is eventually associated with.
In order to bring these two systems back into harmony, introduce a simple
ownership contract to SILRemarkStreamer. That is, it starts owned by
a SILModule, in which case the SILRemarkStreamer holds onto the
underlying LLVM object as the optimizer emits remarks. When it comes
time to IRGen the module, then and only then do we install the streamer
on the module's context. This tranfers ownership of the underlying LLVM
streamer to LLVM itself, so it acts as a consuming operation. When we
are about to perform IR Generation, the SILModule will be expiring
anyways, so the streamer was already about to be destroyed.
We're just putting it to better use doing its job.
Delete all of the formalism and infrastructure around maintaining our own copy of the global context. The final frontier is the Builtins, which need to lookup intrinsics in a given scratch context and convert them into the appropriate Swift annotations and types. As these utilities have wormed their way through the compiler, I have decided to leave this alone for now.
This allows the usage of the whole remark infrastructure developed in
LLVM, which includes a new binary format, metadata in object files, etc.
This gets rid of the YAMLTraits-based remark serialization and does the
plumbing for hooking to LLVM's main remark streamer.
For more about the idea behind LLVM's main remark streamer, see the
docs/Remarks.rst changes in https://reviews.llvm.org/D73676.
The flags are now:
* -save-optimization-record: enable remarks, defaults to YAML
* -save-optimization-record=<format>: enable remarks, use <format> for
serialization
* -save-optimization-record-passes <regex>: only serialize passes that
match <regex>.
The YAMLTraits in swift had a different `flow` setting for the debug
location, resulting in some test changes.
Add ExecuteSILPipelineRequest which executes a
pipeline plan on a given SIL (and possibly IRGen)
module. This serves as a top-level request for
the SILOptimizer that we'll be able to hang
dependencies off.
SIL differentiability witnesses are a new top-level SIL construct mapping
an "original" SIL function and derivative configuration to derivative SIL
functions.
This patch adds `SILDifferentiabilityWitness` serialization/deserialization.
Resolves TF-1136.