Commit Graph

383 Commits

Author SHA1 Message Date
Nate Chandler
ab35362056 [SIL] Added new test_specification instruction.
The new instruction exists only to be used in tests.  The idea is to
specify tests that ought to be run "in the context" of the containing
function.
2022-10-11 17:15:13 -07:00
Erik Eckstein
e790bf225c SIL: no need for BRIDGED_... macros in SILNodes.def
It's not needed to distinguish between bridged and not bridged node classes in SILNodes.def.
NFC.
2022-09-08 12:32:26 +02:00
Hamish Knight
b12015c343 [SIL] Introduce the increment_profiler_counter instruction
This is a dedicated instruction for incrementing a
profiler counter, which lowers to the
`llvm.instrprof.increment` intrinsic. This
replaces the builtin instruction that was
previously used, and ensures that its arguments
are statically known. This ensures that SIL
optimization passes do not invalidate the
instruction, fixing some code coverage cases in
`-O`.

rdar://39146527
2022-09-07 17:55:13 +01:00
Erik Eckstein
bfb284f827 Swift SIL: add a few instructions and Instruction.visitReferencedFunctions
* add `DynamicFunctionRefInst` and `PreviousDynamicFunctionRefInst`
* add a common base class to all function-referencing instructions: `FunctionRefBaseInst`
* add `KeyPathInst`
* add `IndexAddrInst.base` and `IndexAddrInst.index` getters
* add `Instruction.visitReferencedFunctions` to visit all functions which are referenced by an instruction
2022-08-24 17:55:02 +02:00
Michael Gottesman
5baf2af88a [sil] Add a new instruction called explicit_copy_addr.
This is exactly like copy_addr except that it is not viewed from the verifiers
perspective as an "invalid" copy of a move only value. It is intended to be used
in two contexts:

1. When the move checker emits a diagnostic since it could not eliminate a copy,
we still need to produce valid SIL without copy_addr on move only types since we
will hit canonical SIL eventually even if we don't actually codegen the SIL. The
pass can just convert said copy_addr to explicit_copy_addr and everyone is
happy.

2. To implement the explicit copy function for address only types.
2022-08-11 11:40:53 -07:00
Michael Gottesman
9b625588f0 [sil] Make copyable_to_moveonlywrapper forwarding like moveonlywrapper_to_copyable.
Specifically this means that rather than always being owned, we now have owned
and guaranteed versions of copyable_to_moveonlywrapper. Similar to
moveonlywrapper_to_copyable, one chooses which variant one gets by using
specific SILBuilder APIs:
create{Owned,Guaranteed}CopyableToMoveOnlyWrapperValueInst. It is still
forwarding and the rest of the forwarding APIs work as expected except that the
forwarding ownership is fixed (and an assertion will result if one attempts to
do so).

NOTE: It is assumed that trivial operands are always passed to the owned
variant.
2022-07-19 14:39:59 -07:00
Michael Gottesman
e1006c62f9 [move-only] Add copyable_to_moveonlywrapper and moveonlywrapper_to_copyable instructions.
These instructions have the following attributes:

1. copyably_to_moveonlywrapper takes in a 'T' and maps it to a '@moveOnly
T'. This is semantically used when initializing a new moveOnly binding from a
copyable value. It semantically destroys its input @owned value and returns a
brand new independent @owned @moveOnly value. It also is used to convert a
trivial copyable value with type 'Trivial' into an owned non-trivial value of
type '@moveOnly Trivial'. If one thinks of '@moveOnly' as a monad, this is how
one injects a copyable value into the move only space.

2. moveonlywrapper_to_copyable takes in a '@moveOnly T' and produces a new 'T'
value. This is a 'forwarding' instruction where at parse time, we only allow for
one to choose it to be [owned] or [guaranteed].

* moveonlywrapper_to_copyable [owned] is used to signal the end of lifetime of
the '@moveOnly' wrapper. SILGen inserts these when ever a move only value has
its ownership passed to a situation where a copyable value is needed. Since it
is consuming, we know that the no implicit copy checker will ensure that if we
need a copy for it, the program will emit a diagnostic.

* moveonlywrapper_to_copyable [guaranteed] is used to pass a @moveOnly T value
as a copyable guaranteed parameter with type 'T' to a function. In the case of
using no-implicit-copy checking this is always fine since no-implicit-copy is a
local pattern. This would be an error when performing no escape
checking. Importantly, this instruction also is where in the case of an
@moveOnly trivial type, we convert from the non-trivial representation to the
trivial representation.

Some important notes:

1. In a forthcoming commit, I am going to rebase the no implicit copy checker on
top of these instructions. By using '@moveOnly' in the type system, we can
ensure that later in the SIL pipeline, we can have optimizations easily ignore
the code.

2. Be aware of is that due to SILGen only emitting '@moveOnly T' along immediate
accesses to the variable and always converts to a copyable representation when
calling other code, we can simply eliminate from the IR all moveonly-ness from
the IR using a lowering pass (that I am going to upstream). In the evil scheme
we are accomplishing here, we perform lowering of trivial values right after
ownership lowering and before diagnostics to simplify the pipeline.

On another note, I also fixed a few things in SILParsing around getASTType() vs
getRawASTType().
2022-06-09 19:47:31 -07:00
Erik Eckstein
325a0b1f48 swift SIL: add some Instruction, Value and Type APIs
* instructions `RefToBridgeObjectInst`, `BridgeObjectToWordInst`, `StringLiteralInst`, `ProjectBoxInst`, `InitEnumDataAddrInst`, `UncheckedTakeEnumDataAddrInst`, `InjectEnumAddrInst`
* protocols `StoringInstruction` and `EnumInstruction`
* load/store-weak/unowned instructions
* `CopyAddrInst.isTakeOfSrc/isInitializationOfDest`
* `ApplySite.calleeArgIndex/callerArgIndex`
* `SILValue.definingInstruction/definingBlock/function`
*  `Type.isReferenceCounted`
* `FunctionArgument.isExclusiveIndirectParameter`
* support `CondBranchInst` in `incomingPhiValues`
2022-03-30 14:45:58 +02:00
Erik Eckstein
6b7ae416e6 SIL: remove unused instructions thin_function_to_pointer and pointer_to_thin_function 2022-03-25 15:44:59 +01:00
Andrew Trick
2fd4de411e [SIL-opaque] Removed [Unconditional]CheckedCastValue 2022-03-22 17:04:13 -07:00
Michael Gottesman
68d37e142b [no-implicit-copy] Add a new instruction called MarkMustCheckInst and use it in the move checker.
This is an instruction that I am going to use to drive some of the ownership
based dataflow optimizations that I am writing now. The instruction contains a
kind that allows one to know what type of checking is required and allows the
need to add a bunch of independent instructions for independent checkers. Each
checker is responsible for removing all of its own mark instructions. NOTE:
MarkMustCheckInst is only allowed in Raw SIL since once we are in Canonical SIL
we want to ensure that all such checking has already occurred.
2022-01-29 14:49:39 -08:00
eeckstein
f87fc5aea4 Merge pull request #40805 from eeckstein/swift-sil-infrastructure
Add swift SIL/Optimizer infrastructure
2022-01-13 08:35:25 +01:00
Andrew Trick
08b445fb7e Remove APPLYSITE obfuscation.
These macros make ApplySite.h and SILNodes.def unreadable. Getting rid
of them will save me (and I'm sure others) a lot of time whenever I
work with ApplySite.

Best practice dictates that the ApplySite abstraction be modeled in
one place. The macros serve no purpose other than obfuscation.
2022-01-12 10:03:16 -08:00
Erik Eckstein
24b62d4d7c Swift SIL: add a few utilities and instructions
* instructions: function_ref, mark_dependence
* add `BuiltinInst.id`
* add isObjC and canAllocOnStack for alloc_ref and alloc_ref_dynamic
* add `ApplySite::referencedFunction`
* add `Builder.createDeallocStackRef`
* add == and != operators for `Function`
* add `List.first` and `ReverseList.first`
2022-01-12 15:47:15 +01:00
Erik Eckstein
383c52aa35 SIL: rename dealloc_ref [stack] -> dealloc_stack_ref
Introduce a new instruction `dealloc_stack_ref ` and remove the `stack` flag from `dealloc_ref`.

The `dealloc_ref [stack]` was confusing, because all it does is to mark the deallocation of the stack space for a stack promoted object.
2022-01-07 16:20:27 +01:00
Michael Gottesman
d74299e68d [move-function] Add a new instruction called mark_unresolved_move_addr.
This instruction is similar to a copy_addr except that it marks a move of an
address that has to be checked. In order to keep the memory lifetime verifier
happy, the semantics before the checker runs are the mark_unresolved_move_addr is
equivalent to copy_addr [init] (not copy_addr [take][init]).

The use of this instruction is that Mandatory Inlining converts builtin "move"
to a mark_unresolved_move_addr when inlining the function "_move" (the only
place said builtin is invoked).

This is then run through a special checker (that is later in this PR) that
either proves that the mark_unresolved_move_addr can actually be a move in which
case it converts it to copy_addr [take][init] or if it can not be a move, emit
an error and convert the instruction to a copy_addr [init]. After this is done
for all instructions, we loop back through again and emit an error on any
mark_unresolved_move_addr that were not processed earlier allowing for us to
know that we have completeness.

NOTE: The move kills checker for addresses is going to run after Mandatory
Inlining, but before predictable memory opts and friends.
2021-12-06 12:47:29 -08:00
Erik Eckstein
27dc5afb10 libswift: add a few utilities
* add the IntegerLiteralInst + the Builder function to create it
* add Value.nonDebugUses
* add a general utility Sequence.isEmpty
* add PassContext.replaceAllUses
* add a function to erase all `debug_value` uses in PassContext.erase
2021-11-29 09:41:05 +01:00
Andrew Trick
90c0c8b60f Add rebind_memory SIL instruction.
Required for UnsafeRawPointer.withMemoryReboud(to:).

%out_token = rebind_memory %0 : $Builtin.RawPointer to %in_token

%0 must be of $Builtin.RawPointer type

%in_token represents a cached set of bound types from a prior memory state.

%out_token is an opaque $Builtin.Word representing the previously bound
types for this memory region.

This instruction's semantics are identical to ``bind_memory``, except
that the types to which memory will be bound, and the extent of the
memory region is unknown at compile time. Instead, the bound-types are
represented by a token that was produced by a prior memory binding
operation. ``%in_token`` must be the result of bind_memory or
2021-11-14 22:44:46 -08:00
Andrew Trick
17fef2fb1e Give bind_memory a token result.
Required for UnsafeRawPointer.withMemoryRebound(to:)

%token = bind_memory %0 : $Builtin.RawPointer, %1 : $Builtin.Word to $T

%0 must be of $Builtin.RawPointer type

%1 must be of $Builtin.Word type

%token is an opaque $Builtin.Word representing the previously bound types
for this memory region.
2021-11-14 22:04:46 -08:00
Michael Gottesman
f9122a79b7 [moveOnly] Implement a new _copy function that performs an explicit copy value.
The key thing is that the move checker will not consider the explicit copy value
to be a copy_value that can be rewritten, ensuring that any uses of the result
of the explicit copy_value (consuming or other wise) are not checked.

Similar to the _move operator I recently introduced, this is a transparent
function so we can perform one level of specialization and thus at least be
generic over all concrete types.
2021-10-29 15:37:46 -07:00
Erik Eckstein
7849f09e52 SIL: remove the unused alloc_value_buffer, project_value_buffer and dealloc_value_buffer instructions.
Those instructions were use for the materializeForSet implementation, which was replaced by modify-coroutines.
2021-10-07 07:41:54 +02:00
Erik Eckstein
fc772fff50 libswift: add more instructions and other small utilities
* ApplySite.arguments
* BasicBlock != operator
* some Function argument related properties
* Operand.isTypeDependent
* Type.isTrivial
* bridging of raw_ostream::write
2021-09-24 11:36:41 +02:00
Michael Gottesman
5590c7b526 [sil] Add a move_value instruction.
This is a new instruction that can be used by SILGen to perform a semantic move
in between two entities that are considered separate variables at the AST
level. I am going to use it to implement an experimental borrow checker.

This PR contains the following:

1. I define move_value, setup parsing, printing, serializing, deserializing,
   cloning, and filled in all of the visitors as appropriate.
2. I added createMoveValue and emitMoveValueOperation SILBuilder
   APIs. createMoveValue always creates a move and asserts is passed a trivial
   type. emitMoveValueOperation in contrast, will short circuit if passed a
   trivial value and just return the trivial value.
3. I added IRGen tests to show that we can push this through the entire system.

This is all just scaffolding for the instruction to live in SIL land and as of
this PR doesn't actually do anything.
2021-09-12 11:07:42 -07:00
Min-Yih Hsu
343d842394 [SIL][DebugInfo] PATCH 3/3: Deprecate debug_value_addr SIL instruciton
This patch removes all references to DebugValueAddrInst class and
debug_value_addr instruction in textual SIL files.
2021-08-31 12:01:04 -07:00
Erik Eckstein
56cfab2b55 libswift: add more instruction classes
And add `UnaryInstruction` which adds a property `operand` to all unary instructions.
This replaces the existing single-operand properties, which simplifies the code.
2021-08-23 10:21:12 +02:00
Andrew Trick
988ff18f06 Merge branch 'main' into fix-copy-sideeffect 2021-07-13 10:38:47 -07:00
Erik Eckstein
b175436d07 libswift: add instructions, support block predecessors/successors
Add many new instruction classes in libswift, including all terminator instructions.
This allows to support BasicBlock predecessors and successors.
2021-07-01 16:15:44 +02:00
Andrew Trick
851bfeb318 Fix copy_value to have 'None' side-effects.
Copies can be moved as much as you like as long as OSSA is legal.

This fixes some instruction deletion utilities for OSSA and any other
utilities that check side effects. Copies are common.

It also finally allows pure functions to be CSE'd!
2021-06-14 10:44:51 -07:00
Erik Eckstein
8080465e77 libswift: basic SIL and SIL bridging
This is the initial version of a buildable SIL definition in libswift.
It defines an initial set of SIL classes, like Function, BasicBlock, Instruction, Argument, and a few instruction classes.
The interface between C++ and SIL is a bridging layer, implemented in C.
It contains all the required bridging data structures used to access various SIL data structures.
2021-06-09 11:28:57 +02:00
Ikko Ashimine
4dea05ff45 [SIL] Fix typo in SILNodes.def
heirarchy -> hierarchy
2021-05-11 11:19:36 +09:00
Doug Gregor
26551b16e6 [SIL] Fix egregious errors in extract_executor handling.
Make sure it is properly treated as a SingleValueInstruction, and
don't blatantly read freed memory in the LowerHopToExecutor pass.
2021-04-14 23:33:51 -07:00
Erik Eckstein
df2a89f3b3 SIL: introduce a PlaceholderValue and use it in the parser and deserializer.
... instead of a GlobalAddrInst.
This is cleaner and makes the handling of forward-referenced values in the deserializer a bit simpler.
2021-04-14 08:17:58 +02:00
Doug Gregor
e77a27e8ed [Concurrency] Introduce runtime detection of data races.
Through various means, it is possible for a synchronous actor-isolated
function to escape to another concurrency domain and be called from
outside the actor. The problem existed previously, but has become far
easier to trigger now that `@escaping` closures and local functions
can be actor-isolated.

Introduce runtime detection of such data races, where a synchronous
actor-isolated function ends up being called from the wrong executor.
Do this by emitting an executor check in actor-isolated synchronous
functions, where we query the executor in thread-local storage and
ensure that it is what we expect. If it isn't, the runtime complains.
The runtime's complaints can be controlled with the environment
variable `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL`:

  0 - disable checking
  1 - warn when a data race is detected
  2 - error and abort when a data race is detected

At an implementation level, this introduces a new concurrency runtime
entry point `_checkExpectedExecutor` that checks the given executor
(on which the function should always have been called) against the
executor on which is called (which is in thread-local storage). There
is a special carve-out here for `@MainActor` code, where we check
against the OS's notion of "main thread" as well, so that `@MainActor`
code can be called via (e.g.) the Dispatch library's
`DispatchQueue.main.async`.

The new SIL instruction `extract_executor` performs the lowering of an
actor down to its executor, which is implicit in the `hop_to_executor`
instruction. Extend the LowerHopToExecutor pass to perform said
lowering.
2021-04-12 15:19:51 -07:00
Erik Eckstein
09755659a1 SIL: remove the sub-classes of MultipleValueInstructionResult
They are not really needed, because they don't contain any stored properties and for isa-checks we can check the parent instruction.
2021-04-11 19:14:34 +02:00
Erik Eckstein
011358edd6 SIL: let SingleValueInstruction only inherit from a single SILNode.
This removes the ambiguity when casting from a SingleValueInstruction to SILNode, which makes the code simpler. E.g. the "isRepresentativeSILNode" logic is not needed anymore.
Also, it reduces the size of the most used instruction class - SingleValueInstruction - by one pointer.

Conceptually, SILInstruction is still a SILNode. But implementation-wise SILNode is not a base class of SILInstruction anymore.
Only the two sub-classes of SILInstruction - SingleValueInstruction and NonSingleValueInstruction - inherit from SILNode. SingleValueInstruction's SILNode is embedded into a ValueBase and its relative offset in the class is the same as in NonSingleValueInstruction (see SILNodeOffsetChecker).
This makes it possible to cast from a SILInstruction to a SILNode without knowing which SILInstruction sub-class it is.
Casting to SILNode cannot be done implicitly, but only with an LLVM `cast` or with SILInstruction::asSILNode(). But this is a rare case anyway.
2021-01-27 16:40:15 +01:00
Eric Miotto
8e7f9c9cbd Revert "SIL: let SingleValueInstruction only inherit from a single SILNode." 2021-01-26 10:02:24 -08:00
Erik Eckstein
ff1991740a SIL: let SingleValueInstruction only inherit from a single SILNode.
This removes the ambiguity when casting from a SingleValueInstruction to SILNode, which makes the code simpler. E.g. the "isRepresentativeSILNode" logic is not needed anymore.
Also, it reduces the size of the most used instruction class - SingleValueInstruction - by one pointer.

Conceptually, SILInstruction is still a SILNode. But implementation-wise SILNode is not a base class of SILInstruction anymore.
Only the two sub-classes of SILInstruction - SingleValueInstruction and NonSingleValueInstruction - inherit from SILNode. SingleValueInstruction's SILNode is embedded into a ValueBase and its relative offset in the class is the same as in NonSingleValueInstruction (see SILNodeOffsetChecker).
This makes it possible to cast from a SILInstruction to a SILNode without knowing which SILInstruction sub-class it is.
Casting to SILNode cannot be done implicitly, but only with an LLVM `cast` or with SILInstruction::asSILNode(). But this is a rare case anyway.
2021-01-25 09:30:04 +01:00
Andrew Trick
f529072cb1 Remove side-effects semantics from unchecked_ownership_converison.
I can't reason about whether this is correct to do, because SIL both
uses the side-effects semantics to model arbitrary implicit
dependencies and simultaneously considers them to be memory
writes. What's more, the SIL instruction definitions almost *never*
specify the actual constraints that we're attempting to approximate with
the model.

Be that as it may, we can't verify SIL if we "conservatively" consider
instructions that don't write to memory to be writes. So I'll attempt
to fix the undocumented model and find out what breaks.

Fixes rdar://70737095 Unknown instruction: unchecked_ownership_conversion
2020-11-16 11:50:38 -08:00
Michael Gottesman
c3681a6eef [membehavior] Fix base memory behavior/releasing of Load/Store for ownership.
load, store in ossa can have side-effects and stores can release. Specifically:

Memory Behavior
---------------

* Load: unqualified, trivial, take have a read side-effect, but copy retains so
  has side-effects.

* Store: unqualified, trivial, init may write but assign releases so it may have
  side-effects.

Release Behavior
----------------

* Load: No changes.

* Store: May release if store has assign as an ownership qualifier.
2020-11-11 15:32:32 -08:00
Erik Eckstein
f18a9b8ee8 [Concurrency] SIL: add hop_to_executor instruction
This instructions ensures that all instructions, which need to run on the specified executor actually run on that executor.
For details see the description in SIL.rst.
2020-10-28 18:17:43 +01:00
Joe Groff
3364c51b1d SIL: Verify invariants of async_continuation instructions.
- Enforce types of continuations and resume/error BBargs for await
- Can't access the continuation again or exit the function mid-suspend
2020-10-09 14:57:38 -07:00
Joe Groff
a664a33b52 SIL: Add instructions to represent async suspend points.
`get_async_continuation[_addr]` begins a suspend operation by accessing the continuation value that can resume
the task, which can then be used in a callback or event handler before executing `await_async_continuation` to
suspend the task.
2020-10-01 14:21:52 -07:00
Michael Gottesman
5e36ae1c7c [sil] Add a forwarding cast called unchecked_value_cast.
Today unchecked_bitwise_cast returns a value with ObjCUnowned ownership. This is
important to do since the instruction can truncate memory meaning we want to
treat it as a new object that must be copied before use.

This means that in OSSA we do not have a purely ossa forwarding unchecked
layout-compatible assuming cast. This role is filled by unchecked_value_cast.
2020-07-09 21:14:32 -07:00
Erik Eckstein
67605553df SIL: a new instruction 'base_addr_for_offset' for field offset calculations.
The ``base_addr_for_offset`` instruction creates a base address for offset calculations.
The result can be used by address projections, like ``struct_element_addr``, which themselves return the offset of the projected fields.
IR generation simply creates a null pointer for ``base_addr_for_offset``.
2020-07-01 15:10:08 +02:00
Erik Eckstein
f96f9368b4 SIL: define begin_cow_mutation to have side effects.
This fixes a correctness issue.
The begin_cow_mutation instruction has dependencies with instructions which retain the buffer operand.
This prevents optimizations from moving begin_cow_mutation instructions across such retain instructions.
2020-06-08 10:24:29 +02:00
Erik Eckstein
f5a8f600ea SIL: new instructions for copy-on-write support
* a new [immutable] attribute on ref_element_addr and ref_tail_addr
* new instructions: begin_cow_mutation and end_cow_mutation

These new instructions are intended to be used for the stdlib's COW containers, e.g. Array.
They allow more aggressive optimizations, especially for Array.
2020-05-14 08:39:54 +02:00
ematejska
75691d641e [AutoDiff upstream] Add linear function SIL instructions (#30638)
Add `linear_function` and `linear_function_extract` instructions.

`linear_function` creates a `@differentiable(linear)` function-typed value from
an original function operand and a transpose function operand (optional).

`linear_function_extract` extracts either the original or transpose function
value from a `@differentiable(linear)` function.

Resolves TF-1142 and TF-1143.
2020-03-26 09:41:14 -07:00
Dan Zheng
cc7e9fc39e [AutoDiff upstream] [SIL] Add differentiable function instructions.
Add `differentiable_function` and `differentiable_function_extract`
instructions.

`differentiable_function` creates a `@differentiable` function-typed
value from an original function operand and derivative function operands
(optional).

`differentiable_function_extract` extracts either the original or
derivative function value from a `@differentiable` function.

The differentiation transform canonicalizes `differentiable_function`
instructions, filling in derivative function operands if missing.

Resolves TF-1139 and TF-1140.
2020-03-22 23:53:43 -07:00
Andrew Trick
badc5658bb Fix SIL MemBehavior queries with access markers.
This is in prepration for other bug fixes.

Clarify the SIL utilities that return canonical address values for
formal access given the address used by some memory operation:

- stripAccessMarkers
- getAddressAccess
- getAccessedAddress

These are closely related to the code in MemAccessUtils.

Make sure passes use these utilities consistently so that
optimizations aren't defeated by normal variations in SIL patterns.

Create an isLetAddress() utility alongside these basic utilities to
make sure it is used consistently with the address corresponding to
formal access. When this query is used inconsistently, it defeats
optimization. It can also cause correctness bugs because some
optimizations assume that 'let' initialization is only performed on a
unique address value.

Functional changes to Memory Behavior:

- An instruction with side effects now conservatively still has side
  effects even when the queried value is a 'let'. Let values are
  certainly sensitive to side effects, such as the parent object being
  deallocated.

- Return the correct MemBehavior for begin/end_access markers.
2020-03-03 09:24:18 -08:00
Dan Zheng
a49428ca7c [AutoDiff upstream] Add differentiability_witness_function instruction. (#29765)
The `differentiability_witness_function` instruction looks up a
differentiability witness function (JVP, VJP, or transpose) for a referenced
function via SIL differentiability witnesses.

Add round-trip parsing/serialization and IRGen tests.

Notes:
- Differentiability witnesses for linear functions require more support.
  `differentiability_witness_function [transpose]` instructions do not yet
  have IRGen.
- Nothing currently generates `differentiability_witness_function` instructions.
  The differentiation transform does, but it hasn't been upstreamed yet.

Resolves TF-1141.
2020-02-13 16:55:46 -08:00