Commit Graph

267 Commits

Author SHA1 Message Date
Andrew Trick
9d40198c0f SIL: fix miscompiles of non-Copyable struct/enum with deinits
The SIL optimizer has fundamental bugs that result in dropping non-Copyable
struct & enum the deinitializers.

Fix this by

1. correctly representing the ownership of struct & enum values that are
   initialized from trivial values.

2. checking move-only types before deleting forwarding instructions.

These bugs block other bug fixes. They are exposed by other unrelated SIL
optimizations to SIL. I'm sure its possible to expose the bugs with source-level
tests, but the current order of inlining and deinit devirtualization has been
hiding the bugs and complicates reproduction.
2025-08-15 15:03:50 -07:00
Erik Eckstein
48db89031a SIL: be more tolerant when a user is deleted during use-list iteration.
Check if an operand's instruction has been deleted in `UseList.next()`.
This allows to delete an instruction, which has two uses of a value, during use-list iteration.
2025-03-07 15:59:32 +01:00
Yuta Saito
84bfaf5f7e [SIL] Explicitly use uint64_t for maxBitfieldID
The `maxBitfieldID` was defined as `size_t` and used to assert
bitfield ID values. The `currentBitfieldID` of `SILFunction` is
defined as `uint64_t`, so we should consistently use `uint64_t`
for `maxBitfieldID` as well for 32-bit platforms.
2025-02-13 02:04:54 +00:00
Andrew Trick
e705a6d7c3 Temporarily introduce AnyInteriorPointer operand ownership.
This is necessary to fix a recent OSSA bug that breaks common occurrences on
mark_dependence [nonescaping]. Rather than reverting that change above, we make
forward progress toward implicit borrows scopes, as was the original intention.

In the near future, all InteriorPointer instructions will create an implicit
borrow scope. This means we have the option of not emitting extraneous
begin/end_borrow instructions around intructions like ref_element_addr,
open_existential, and project_box. After that, we can also migrate
GuaranteedForwarding instructions like tuple_extract and struct_extract.
2025-02-05 16:23:02 -08:00
Saleem Abdulrasool
a3b60ed0f3 SIL: avoid truncation warnings on Windows (NFCI)
`enum` is a signed integer type on some platforms. The return value of
`std::numeric_limits<T>::max()` is a `size_t` which is unsigned. Further
manipulation retains the unsigned-ness until the assignment which
results in truncation. Change the type to a `constexpr` constant
definition and remove the `enum` type along with a type conversion to an
explicitly unsigned integral type.
2024-12-09 08:29:23 -08:00
Erik Eckstein
83224b255e SIL: automatically set the reborrow flags for phi-arguments when creating an end_borrow or setting the operand of an end_borrow
This is done for all transitively incoming phi-arguments of an end_borrow operand.
2024-11-12 09:26:58 +01:00
Andrew Trick
3316a58ba7 Allow move_value of trivial values.
Required for SIL level local variable scopes.
2024-07-26 08:27:31 -07:00
Andrew Trick
075d3a42c1 Add SILValue::isFromVarDecl utility. 2024-07-26 08:27:31 -07:00
Nate Chandler
f39b70a86d [NFC] SIL: Extracted remove "any" m-o wrapping. 2024-06-11 16:25:29 -07:00
Nate Chandler
78c18374c3 [NFC] SIL: Extracted has "any" move-only wrapping.
This predicate is used in several places.
2024-06-11 16:25:14 -07:00
Nate Chandler
b5b637c72e [NFC] SIL: Renamed helper.
removing...To -> removing...From
2024-06-11 16:25:11 -07:00
Nate Chandler
121b2faccc [NFC] SIL: Add ValueBase::isBeginApplyToken.
Forwards to the member function of the same name on
MultipleValueInstructionResult.
2024-04-25 17:03:11 -07:00
Erik Eckstein
e14c1d1f62 SIL, Optimizer: update and handle borrowed-from instructions
Compute, update and handle borrowed-from instruction in various utilities and passes.
Also, used borrowed-from to simplify `gatherBorrowIntroducers` and `gatherEnclosingValues`.
Replace those utilities by `Value.getBorrowIntroducers` and `Value.getEnclosingValues`, which return a lazily computed Sequence of borrowed/enclosing values.
2024-04-10 13:38:10 +02:00
Erik Eckstein
7afa419dd4 SIL: improve inline bitfields in SILNode, SILBasicBlock and Operand
* Let the customBits and lastInitializedBitfieldID share a single uint64_t. This increases the number of available bits in SILNode and Operand from 8 to 20. Also, it simplifies the Operand class because no PointerIntPairs are used anymore to store the operand pointer fields.
* Instead make the "deleted" flag a separate bool field in SILNode (instead of encoding it with the sign of lastInitializedBitfieldID). Another simplification
* Enable important invariant checks also in release builds by using `require` instead of `assert`. Not catching such errors in release builds would be a disaster.
* Let the Swift optimization passes use all the available bits and not only a fixed amount of 8 (SILNode) and 16 (SILBasicBlock).
2024-03-21 15:52:39 +01:00
Michael Gottesman
e4f365516d [sil] Also give Placeholder a parent SILFunction. 2024-02-27 17:15:49 -08:00
Ben Barham
ef8825bfe6 Migrate llvm::Optional to std::optional
LLVM has removed llvm::Optional, move over to std::optional. Also
clang-format to fix up all the renamed #includes.
2024-02-21 11:20:06 -08:00
Michael Gottesman
9303c40162 [region-isolation] Teach region isolation that assigning into a transferring parameter is a transfer of the value.
The specific semantics is if we assign into a transferring parameter's field,
then we "merge" src's value into the transferring parameter, so we
conservatively leave the region of the transferring parameter alone. If we
assign over the entire transferring parameter, we perform an assign fresh since
any value that used to be in the transferring parameter cannot reference
anything in its new value since they are all gone.
2024-01-18 13:20:28 -08:00
Michael Gottesman
918f207b34 [sil] Add a variant of NodeSet for Operands called OperandSet.
To do this I used 8 spare bits in the pointers in Operand for the custom flags.
The reason I did this is just like one sometimes wants to iterate and use
sets/worklists with Nodes/Blocks, one often wants to do it with operands
especially in situations where one wants to know a using instruction and the
value on the instruction that was used.
2023-11-26 14:39:56 -08:00
Paolo Tranquilli
e13a85c8f1 NFC: make headers compatible with C++20
In a downstream project we are using the Swift code as a library and
compiling with C++20. This mostly works except for two backward
incompatibilities introduced by C++20:

* [P1008] disallows aggregate initialization when defaulted or deleted
  constructors are specified. This lead to a compilation error for
  aggregate initialization of `swift::Compilation::Result`. The backward
  and forward compatible fix for that is to provide a constructor
  turning aggregate initializations into a normal constructor call.
* [P1185] introduces more candidates for overload resolution of
  comparison operator calls. As explained in [P1630], this leads in some
  cases to ambiguity compilation errors in C++20, which is exactly the
  case with `swift::ValueOwnershipKind`. The fix in this case is to
  remove some redundant operator calls conditionally on the
  `__cpp_impl_three_way_comparison` feature test macro, which [includes
  the papers mentioned above][feature_test].

[P1008]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2018/p1008r1.pdf
[P1185]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1185r2.html
[P1630]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1630r1.html
[feature_test]: https://en.cppreference.com/w/cpp/feature_test
2023-07-31 17:34:05 +02:00
Nate Chandler
8e7d9842ce [SIL] NFC: Added ValueBase::getUsers.
Make iterating user instructions a bit easier.
2023-07-13 08:19:09 -07:00
Evan Wilde
f3ff561c6f [NFC] add llvm namespace to Optional and None
This is phase-1 of switching from llvm::Optional to std::optional in the
next rebranch. llvm::Optional was removed from upstream LLVM, so we need
to migrate off rather soon. On Darwin, std::optional, and llvm::Optional
have the same layout, so we don't need to be as concerned about ABI
beyond the name mangling. `llvm::Optional` is only returned from one
function in
```
getStandardTypeSubst(StringRef TypeName,
                     bool allowConcurrencyManglings);
```
It's the return value, so it should not impact the mangling of the
function, and the layout is the same as `std::optional`, so it should be
mostly okay. This function doesn't appear to have users, and the ABI was
already broken 2 years ago for concurrency and no one seemed to notice
so this should be "okay".

I'm doing the migration incrementally so that folks working on main can
cherry-pick back to the release/5.9 branch. Once 5.9 is done and locked
away, then we can go through and finish the replacement. Since `None`
and `Optional` show up in contexts where they are not `llvm::None` and
`llvm::Optional`, I'm preparing the work now by going through and
removing the namespace unwrapping and making the `llvm` namespace
explicit. This should make it fairly mechanical to go through and
replace llvm::Optional with std::optional, and llvm::None with
std::nullopt. It's also a change that can be brought onto the
release/5.9 with minimal impact. This should be an NFC change.
2023-06-27 09:03:52 -07:00
Michael Gottesman
70ab38d9da [move-only] Add a new instruction: moveonlywrapper_to_copyable_box.
I am going to use this to unwrap ${ @moveOnly T } so that I can pass it to
partial_apply that expect a ${ T }
2023-06-06 14:26:22 -04:00
Meghana Gupta
1dc713e2f7 Add new flags "reborrow" and "escaping" to SILArgument.
"reborrow" flag on the SILArgument avoids transitive walk over the phi operandsi
to determine if it is a reborrow in multiple utilities.
SIL transforms must keep the flag up-to-date by calling SILArgument::setReborrow.
SILVerifier checks to ensure the flag is not invalidated.

Currently "escaping" is not used anywhere.
2023-05-11 12:31:37 -07:00
Andrew Trick
c588c657f5 SILVerifier - option to verify with or without linear lifetime check
Add a separate 'verifyOwnership()' entry point so it's possible
to check OSSA lifetimes at various points.

Move SILGenCleanup into a SILGen pass pipeline.

After SILGen, verify incomplete OSSA.

After SILGenCleanup, verify ownership.
2023-03-01 21:41:46 -08:00
Meghana Gupta
511739b494 Delete OperandOwnership::GuaranteedForwardingPhi
Use OperandOwnership::GuaranteedForwarding instead.
2022-12-13 12:51:31 -08:00
Andrew Trick
fe44dce4e7 Add SILValue::getDefiningInstructionOrTerminator().
This allows code to handle terminator results similar to any other
instruction result. Data flow should generally handle terminator
results like any other instruction that may forward operand ownership
to its results. The fact that it is represented as a block argument is
an implementation detail that gets in the way of conceptual
simplicity.
2022-12-12 12:23:40 -08:00
Nate Chandler
fff993a31b [NFC] Removed SILValue::getOwnershipKind.
It has been deprecated for awhile.
2022-12-09 18:30:10 -08:00
Erik Eckstein
8ca0143279 Remove uses of std::iterator
It's deprecated in C++17
2022-11-14 09:35:40 +01:00
Meghana Gupta
786eb94853 Support @guaranteed forwarding phis 2022-10-19 19:54:27 -07:00
Meghana Gupta
b1f719709b Rename ForwardingBorrow -> GuaranteedForwarding 2022-10-19 19:54:27 -07:00
Utkarsh Saxena
1d61437727 Add matching operator!= for asymmetric operator==
This helps clang to compile target in C++20 mode.

Context: https://github.com/llvm/llvm-project/issues/57711
2022-10-18 16:47:01 +02:00
Andrew Trick
87d7237181 Add debug_value [trace] attribute.
This lets us write optimizer unit tests and selectively debug the
optimizer in general. We'll be able trace analyses and control
optimization selectively for certain values.

Adding a trace flag to debug_value is the easiest way to start using
it experimentally and develop the rest of the infrastructure. If this
takes off, then we can consider a new `trace_value`
instruction. For now, reusing debug_value is the least intrusive way to
start writing liveness unit tests.
2022-09-25 22:44:15 -07:00
Arnold Schwaighofer
2296c72515 Merge pull request #60256 from aschwaighofer/rebranch_adjust_to_DEPRECATED
[rebranch] Adjust to removal of LLVM_ATTRIBUTE_DEPRECATED
2022-07-27 12:22:05 -07:00
Arnold Schwaighofer
3b42240209 Adjust to removal of LLVM_ATTRIBUTE_DEPRECATED 2022-07-27 08:35:32 -07:00
swift-ci
4492599fea Merge remote-tracking branch 'origin/main' into rebranch 2022-07-26 17:13:27 -07:00
Michael Gottesman
1e6187c4f4 [sil] Update all usages of old API SILValue::getOwnershipKind() in favor of new ValueBase::getOwnershipKind().
Andy some time ago already created the new API but didn't go through and update
the old occurences. I did that in this PR and then deprecated the old API. The
tree is clean, so I could just remove it, but I decided to be nicer to
downstream people by deprecating it first.
2022-07-26 11:46:23 -07:00
swift-ci
5c8f1fcb12 Merge remote-tracking branch 'origin/main' into rebranch 2022-07-02 17:03:37 -07:00
Michael Gottesman
8f3fe63fed [no-implicit-copy] Rename SILMoveOnlyType -> SILMoveOnlyWrappedType.
Since I am beginning to prepare for adding real move only types to the language,
I am renaming everything that has to do with copyable types "move only wrapped"
values instead of move only. The hope is this reduces/prevents any confusion in
between the two.
2022-07-01 17:26:13 -07:00
swift-ci
e7f293814c Merge remote-tracking branch 'origin/main' into rebranch 2022-06-28 13:53:46 -07:00
Michael Gottesman
2f9d67de18 [move-only] Add a new pass called MoveOnlyTypeEliminator that runs after move only checking.
This pass lowers moveonly-ness from the IR after we have finished move only
checking. The transform can be configured in two ways: such that it only handles
trivial types and such that it does trivial and non-trivial types. For ease of
use, I created two top level transforms (TrivialMoveOnlyTypeEliminator and
MoveOnlyTypeElimintor) that invoke the two behaviors by configuring the
underlying transform slightly differently.

For now, I am running first the trivial-only and then the all of the above
lowering. The trivial only pass will remain at this part of the pipeline
forever, but with time we are going to move the lower everything pass later into
the pipeline once I have audited the optimizer pipeline to just not perform any
work on move only types. That being said, currently we do not have this
guarantee and this patch at least improves the world and lets us codegen no
implicit copy code again.
2022-06-22 15:31:57 -07:00
swift-ci
af2419ca06 Merge remote-tracking branch 'origin/main' into rebranch 2022-05-13 19:54:03 -07:00
Meghana Gupta
dcce7fcf63 AddressLowering: Handle coroutines (#58821) 2022-05-13 19:53:29 -07:00
Ben Barham
68296c9037 [next] Remove LLVM_ATTRIBUTE_DEPRECATED
`LLVM_ATTRIBUTE_DEPRECATED` was removed in llvm/llvm-project
903c30f4d1f3bc0d1aae9ca83af17c0062d02b40. Use `[[deprecated]]` directly.
2022-05-05 16:25:10 -07:00
Josh Soref
d767912be2 Spelling sil (#42471)
* spelling: accessible

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: accessories

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: allocated

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: amortizes

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: are

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: arguments

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: cacheable

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: check

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: clazz

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: compatible

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: compilation

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: completely

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: construct

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: conversion

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: declarations

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: derivation

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: deserialization

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: destroyed

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: determined

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: different

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: doesn't

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: equality

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: equivalent

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: formation

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: forwards

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: global

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: guaranteed

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: have

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: identify

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: inaccessible

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: indeterminate

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: indices

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: inefficient

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: inheritance

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: instantaneous

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: instruction

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: intentionally

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: interior

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: intrinsic

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: introducing

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: irrelevant

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: message

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: multi

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: necessarily

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: object

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: one

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: optimization

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: otherwise

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: overridden

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: parameter

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: pattern

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: pipeline

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: possibility

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: postdominance

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: providing

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: reached

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: recognized

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: refrigerator

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: remaining

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: resilient

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: retrieve

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: scavenge

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: scheduled

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: separately

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: serializable

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: signature

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: simplicity

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: specifically

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: substituted

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: substitution

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: subtypes

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: supplement

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: syntax

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: the

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: there

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: these

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: this

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: though

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: through

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: transitively

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: transpose

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: trivial

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: value

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: verification

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: visibility

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: weird

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

* spelling: whole

Signed-off-by: Josh Soref <jsoref@users.noreply.github.com>

Co-authored-by: Josh Soref <jsoref@users.noreply.github.com>
2022-04-22 15:11:09 -07:00
Meghana Gupta
16c277f8fb Add new api replaceAllTypeDependentUsesWith 2022-04-07 09:19:42 -07:00
Meghana Gupta
8962da9a66 Add iterators for type dependent uses 2022-04-07 09:19:42 -07:00
Andrew Trick
f6ba6332a6 Allow SIL convention overriding in operand ownership verification
This lets the SILBuilder's verification continue to run during
address lowering before the SIL stage has been updated.
2022-02-15 13:28:47 -08:00
Nate Chandler
7d4f6ffd7d [SIL] Added isLexical to ValueBase. 2022-01-27 15:19:30 -08:00
Nate Chandler
e31852a04c [Gardening] Added omitted word. 2021-12-19 08:14:45 -08:00
Michael Gottesman
a670a34f75 [sil] Add SILValue::dump() for use in the debugger.
It just trampolines to calling ValueBase::dump(). The reason I added this is
sometimes one wants to do this in the debugger and confuses if one has a
ValueBase or a SILValue causing lldb to be unhappy. By mirroring this API, one
can do the same operation on either type and just move on with ones life.

I also wrapped this in LLVM_ATTRIBUTE_DEPRECATED with a msg saying only for use
in the debugger so people do not call dump() in the codebase by mistake.
2021-11-02 09:55:17 -07:00