WebKit Bugzilla
Attachment 338939 Details for
Bug 185060
: [negative result] B3 should have generalized path specialization
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
it's a start
blah.patch (text/plain), 22.27 KB, created by
Filip Pizlo
on 2018-04-26 17:18:18 PDT
(
hide
)
Description:
it's a start
Filename:
MIME Type:
Creator:
Filip Pizlo
Created:
2018-04-26 17:18:18 PDT
Size:
22.27 KB
patch
obsolete
>Index: Source/JavaScriptCore/b3/B3BreakCriticalEdges.cpp >=================================================================== >--- Source/JavaScriptCore/b3/B3BreakCriticalEdges.cpp (revision 230970) >+++ Source/JavaScriptCore/b3/B3BreakCriticalEdges.cpp (working copy) >@@ -57,8 +57,8 @@ void breakCriticalEdges(Procedure& proc) > } > } > >- insertionSet.execute(); >- proc.invalidateCFG(); >+ if (insertionSet.execute()) >+ proc.invalidateCFG(); > } > > } } // namespace JSC::B3 >Index: Source/JavaScriptCore/b3/B3SpecializeBranches.cpp >=================================================================== >--- Source/JavaScriptCore/b3/B3SpecializeBranches.cpp (nonexistent) >+++ Source/JavaScriptCore/b3/B3SpecializeBranches.cpp (working copy) >@@ -0,0 +1,313 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE >+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR >+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, >+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, >+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR >+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY >+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#include "config.h" >+#include "B3SpecializeBranches.h" >+ >+#include "B3ValueInBlock.h" >+ >+#if ENABLE(B3_JIT) >+ >+namespace JSC { namespace B3 { >+ >+class SpecializeBranches { >+public: >+ SpecializeBranches(Procedure& proc) >+ : m_proc(proc) >+ { >+ } >+ >+ bool run() >+ { >+ createHoistingTargets(); >+ m_dominators = &proc.dominators(); >+ m_proc.resetValueOwners(); >+ findBranches(); >+ findBlocksDuplicatedForPredicates(); >+ createSlicesByDominantRedundancy(); >+ createSlicesByHoisting(); >+ } >+ >+private: >+ void createHoistingTargets() >+ { >+ // All lower-frequency predecessors of a block should jump to that block via a hoisting >+ // target block. If we don't use these blocks, they'll get collected by reduceStrength. >+ >+ BlockInsertionSet insertionSet(m_proc); >+ >+ for (BasicBlock* block : proc) { >+ unsigned numberOfLowerFrequencyPredecessors = 0; >+ double maxFrequencyOfLowerFrequencyPredecessors = 0; >+ for (BasicBlock* predecessor : block->predecessors()) { >+ if (predecessor->frequency() >= block->frequency()) >+ continue; >+ >+ numberOfLowerFrequencyPredecessors++; >+ maxFrequencyOfLowerFrequencyPredecessors = >+ std::max(maxFrequencyOfLowerFrequencyPredecessors, predecessor->frequency()); >+ } >+ >+ if (numberOfLowerFrequencyPredecessors < 2) >+ continue; >+ >+ BasicBlock* hoistingTarget = insertionSet.insertBefore(block, maxFrequencyOfLowerFrequencyPredecessors); >+ hoistingTarget->appendNew<Value>(proc, Jump, block); >+ hoistingTarget->setSuccessors(FrequentedBlock(block)); >+ for (BasicBlock* predecessor : block->predecessors()) { >+ if (predecessor->frequency() >= block->frequency()) >+ continue; >+ >+ predecessor->replaceSuccessor(block, hoistingTarget); >+ hoistingTarget->addPredecessor(predecessor); >+ block->removePredecessor(predecessor); >+ } >+ block->addPredecessor(hoistingTarget); >+ } >+ >+ if (insertionSet.execute()) >+ m_proc.invalidateCFG(); >+ } >+ >+ void findBranches() >+ { >+ for (BasicBlock* block : m_proc) { >+ for (unsigned index = block->size(); index--;) { >+ Value* value = block->at(index); >+ switch (value->opcode()) { >+ case Branch: >+ case Check: >+ m_predicateToBranches.add(value->child(0), Vector<Value*>()).iterator->value.append(ValueInBlock(block, index)); >+ break; >+ default: >+ break; >+ } >+ } >+ } >+ } >+ >+ void findBlocksDuplicatedForPredicates() >+ { >+ for (BasicBlock* block : m_proc) { >+ if (block->last()->opcode() != Branch) >+ continue; >+ >+ Value* predicate = block->last()->child(0); >+ >+ for (BasicBlock* start : insertionPoint.block()->successors()) { >+ m_dominators->forAllBlocksDominatesBy( >+ start, >+ [&] (BasicBlock* duplicatedBlock) { >+ m_duplicatedForPredicate.add(std::make_pair(duplicatedBlock, predicate)); >+ }); >+ } >+ } >+ } >+ >+ void createSlicesByDominantRedundancy() >+ { >+ for (auto& entry : m_predicateToBranches) { >+ Value* predicate = entry.key; >+ Vector<ValueInBlock>& branches = entry.value; >+ >+ // Each block can only have one "branch", since: >+ // >+ // 1) This redundancy would have already been fixed: >+ // >+ // BB#foo: >+ // Check(@p) >+ // Check(@p) >+ // >+ // 2) This redundancy would have already been fixed: >+ // >+ // BB#foo: >+ // Check(@p) >+ // Branch(@p) >+ // >+ // 3) This redundancy is not valid IR: >+ // >+ // BB#foo: >+ // Branch(@p) >+ // Check(@p) >+ // >+ // 4) This redundancy is not valid IR: >+ // >+ // BB#foo: >+ // Branch(@p) >+ // Branch(@p) >+ for (ValueInBlock branch : branches) >+ m_blocksWithBranches.add(branch.block); >+ >+ for (ValueInBlock insertionPoint : branches) { >+ if (insertionPoint.value()->opcode() != Branch) >+ continue; >+ >+ for (ValueInBlock branchPoint : branches) { >+ if (!m_dominators->dominates(insertionPoint.block, branchPoint.block)) >+ continue; >+ >+ if (insertionPoint.block == branchPoint.block) { >+ if (insertionPoint.index() > branchPoint.index()) >+ continue; >+ >+ RELEASE_ASSERT(insertionPoint.index() == branchPoint.index()); >+ } >+ >+ m_slice = m_slices.add(); >+ m_slice->predicate = predicate; >+ m_slice->insertionPoint = insertionPoint; >+ m_slice->branchPoint = branchPoint; >+ m_slice->insertedBranchCost = insertionPoint.block()->frequency(); >+ >+ if (insertionPoint.value() == branchPoint.value()) { >+ m_slice->spaceCost = 1; >+ m_slice->benefit = insertionPoint.block()->frequency(); >+ } else { >+ m_slice->spaceCost = branchPoint.index(); >+ m_slice->benefit = branchPoint.block()->frequency() + m_slice->insertedBranchCost.block()->frequency(); >+ >+ RELEASE_ASSERT(insertionPoint.block() != branchPoint.block()); >+ >+ BlockWorklist worklist; >+ worklist.pushAll(branchPoint.block()->predecessors()); >+ while (BasicBlock* block = worklist.pop()) { >+ if (block == insertionPoint.block()) >+ continue; >+ >+ m_slice->blocks.append(block); >+ >+ if (!m_duplicatedForPredicate.contains(std::make_pair(block, predicate))) { >+ m_slice->spaceCost += block->size(); >+ if (m_blocksWithBranches.contains(block)) >+ m_slice->benefit += block->frequency(); >+ } >+ >+ worklist.push(block->predecessors()); >+ } >+ } >+ } >+ } >+ >+ for (ValueInBlock branch : branches) >+ m_blocksWithBranches.remove(branch.block); >+ } >+ } >+ >+ void createSlicesByHoisting() >+ { >+ bool changed; >+ do { >+ changed = false; >+ for (m_slice : m_slices) { >+ if (m_slice->insertionPoint.block()->frequency() <= m_slice->predicate->owner->frequency()) >+ continue; >+ >+ BasicBlock* newInsertionPointBlock = m_slice->insertionPoint.block(); >+ while (newInsertionPointBlokc->frequency() >= m_slice->insertionPoint.block()) >+ newInsertionPointBlock = m_dominators->idom(newInsertionPointBlock); >+ >+ if (!m_dominators->dominates(m_slice->predicate->owner, newInsertionPointBlock)) >+ continue; >+ >+ if (newInsertionPointBlock == m_slice->insertionPoint.block()) >+ continue; >+ >+ // Hoisting targets must have jumps at the end of them because we created hoisting >+ // targets that have jumps. >+ RELEASE_ASSERT(newInsertionPointBlock->last()->opcode() == Jump); >+ >+ changed = true; >+ Slice* newSlice = m_slices.add(*m_slice); >+ newSlice->insertionPoint = ValueInBlock( >+ newInsertionPointBlock, >+ newInsertionPointBlock->size() - 1); >+ >+ BlockWorklist worklist; >+ for (BasicBlock* block : m_slice->blocks) >+ worklist.pretendToHaveSeen(block); >+ >+ worklist.pushAll(m_slice->insertionPoint.block()->predecessors()); >+ while (BasicBlock* block : worklist.pop()) { >+ if (block == newInsertionPoint) >+ continue; >+ >+ if (block == m_slice->branchPoint.block()) { >+ // We only want to add the end of this block. Note, if we see this block, >+ // it's because it wasn't in the blocks set. That means that m_slice was a >+ // a dominant redundancy slice rather than a hoisted slice. It also means >+ // that this block doesn't have any unaccounted-for branches. >+ >+ newSlice->spaceCost += block->size() - (m_slice->branchPoint.index() + 1); >+ continue; >+ } >+ >+ // FIXME: Need to have a global blocksWithBranches so that we can count whether >+ // the block adds to the benefit. >+ } >+ } >+ } while (changed); >+ } >+ >+ Procedure& m_proc; >+ Dominators* m_dominators { nullptr }; >+ >+ HashMap<Value*, Vector<ValueInBlock>> m_predicateToBranches; >+ >+ HashSet<std::pair<BasicBlock*, Value*>> m_blocksWithBranches; >+ HashSet<std::pair<BasicBlock*, Value*>> m_duplicatedForPredicate; >+ >+ IndexSet<BasicBlock*> m_blocksWithBranches; >+ >+ Slice* m_slice; >+ >+ struct Slice { >+ double benefitOverCost() const >+ { >+ return (benefit - insertedBranchCost) / spaceCost; >+ } >+ >+ Value* predicate; >+ ValueInBlock insertionPoint; >+ ValueInBlock branchPoint; >+ double benefit { 0 }; >+ double insertedBranchCost { 0 }; >+ double spaceCost { 0 }; >+ Vector<BasicBlock*> blocks; // The list of duplicated blocks not including the branchPoint. >+ }; >+ >+ Bag<Slice> m_slices; >+}; >+ >+bool specializeBranches(Procedure& proc) >+{ >+ PhaseScope phaseScope(proc, "specializeBranches"); >+ SpecializeBranches specializeBranches(proc); >+ return specializeBranches.run(); >+} >+ >+} } // namespace JSC::B3 >+ >+#endif // ENABLE(B3_JIT) >+ >Index: Source/JavaScriptCore/b3/B3SpecializeBranches.h >=================================================================== >--- Source/JavaScriptCore/b3/B3SpecializeBranches.h (nonexistent) >+++ Source/JavaScriptCore/b3/B3SpecializeBranches.h (working copy) >@@ -0,0 +1,41 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE >+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR >+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, >+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, >+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR >+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY >+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#pragma once >+ >+#if ENABLE(B3_JIT) >+ >+namespace JSC { namespace B3 { >+ >+class Procedure; >+ >+// Duplicates code to remove branches. Each piece of code gets duplicated at most once, so there >+// is no exponential blow-up. >+ >+bool specializeBranches(Procedure&); >+ >+} } // namespace JSC::B3 >+ >+#endif // ENABLE(B3_JIT) >Index: Source/JavaScriptCore/b3/B3ValueInBlock.cpp >=================================================================== >--- Source/JavaScriptCore/b3/B3ValueInBlock.cpp (nonexistent) >+++ Source/JavaScriptCore/b3/B3ValueInBlock.cpp (working copy) >@@ -0,0 +1,44 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE >+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR >+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, >+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, >+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR >+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY >+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#include "config.h" >+#include "B3ValueInBlock.h" >+ >+#if ENABLE(B3_JIT) >+ >+namespace JSC { namespace B3 { >+ >+void ValueInBlock::dump(PrintStream& out) const >+{ >+ if (*this) >+ out.print(pointerDump(m_block), ":", m_index, "->", value()); >+ else >+ out.print("null"); >+} >+ >+} } // namespace JSC::B3 >+ >+#endif // ENABLE(B3_JIT) >+ >Index: Source/JavaScriptCore/b3/B3ValueInBlock.h >=================================================================== >--- Source/JavaScriptCore/b3/B3ValueInBlock.h (nonexistent) >+++ Source/JavaScriptCore/b3/B3ValueInBlock.h (working copy) >@@ -0,0 +1,110 @@ >+/* >+ * Copyright (C) 2018 Apple Inc. All rights reserved. >+ * >+ * Redistribution and use in source and binary forms, with or without >+ * modification, are permitted provided that the following conditions >+ * are met: >+ * 1. Redistributions of source code must retain the above copyright >+ * notice, this list of conditions and the following disclaimer. >+ * 2. Redistributions in binary form must reproduce the above copyright >+ * notice, this list of conditions and the following disclaimer in the >+ * documentation and/or other materials provided with the distribution. >+ * >+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY >+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE >+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR >+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR >+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, >+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, >+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR >+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY >+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT >+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE >+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. >+ */ >+ >+#pragma once >+ >+#if ENABLE(B3_JIT) >+ >+#include "B3BasicBlock.h" >+#include "B3Value.h" >+ >+namespace JSC { namespace B3 { >+ >+class ValueInBlock { >+public: >+ ValueInBlock() { } >+ >+ ValueInBlock(BasicBlock* block, unsigned index) >+ : m_block(block) >+ , m_index(index) >+ { >+ } >+ >+ BasicBlock* block() const { return m_block; } >+ unsigned index() const { return m_index; } >+ Value* value() const { return m_block->at(m_index); } >+ >+ bool operator==(const ValueInBlock& other) const >+ { >+ return m_block == other.m_block >+ && m_index == other.m_index; >+ } >+ >+ bool operator!=(const ValueInBlock& other) const >+ { >+ return !(*this == other); >+ } >+ >+ explicit operator bool() const >+ { >+ return *this != ValueInBlock(); >+ } >+ >+ void dump(PrintStream& out) const; >+ >+ unsigned hash() const >+ { >+ return WTF::PtrHash<BasicBlock*>::hash(m_block) + m_index; >+ } >+ >+ ValueInBlock(WTF::HashTableDeletedValueType) >+ { >+ m_index--; >+ } >+ >+ bool isHashTableDeletedValue() const >+ { >+ return *this == ValueInBlock(WTF::HashTableDeletedValue); >+ } >+ >+private: >+ BasicBlock* m_block { nullptr }; >+ unsigned m_index { UINT_MAX }; >+}; >+ >+struct ValueInBlockHash { >+ static unsigned hash(const ValueInBlock& key) { return key.hash(); } >+ static bool equal(const ValueInBlock& a, const ValueInBlock& b) { return a == b; } >+ static constexpr bool safeToCompareToEmptyOrDeleted = true; >+} >+ >+} } // namespace JSC::B3 >+ >+namespace WTF { >+ >+template<typename> struct DefaultHash; >+template<> struct DefaultHash<JSC::B3::ValueInBlock> { >+ typedef JSC::B3::ValueInBlockHash Hash; >+}; >+ >+template<typename> struct HashTraits; >+template<> struct HashTraits<JSC::B3::ValueInBlock> : public SimpleClassHashTraits<JSC::B3::ValueInBlock> { >+ static constexpr bool emptyValueIsZero = false; >+}; >+ >+} // namespace WTF >+ >+#endif // ENABLE(B3_JIT) >+ >Index: Source/WTF/wtf/BitVector.h >=================================================================== >--- Source/WTF/wtf/BitVector.h (revision 230970) >+++ Source/WTF/wtf/BitVector.h (working copy) >@@ -110,6 +110,8 @@ public: > // Like ensureSize(), but supports reducing the size of the bitvector. > WTF_EXPORT_PRIVATE void resize(size_t numBits); > >+ // Clears the set without freeing memory. If you want to clear the set and free memory, then >+ // you want *this = BitVector(). > WTF_EXPORT_PRIVATE void clearAll(); > > bool quickGet(size_t bit) const >Index: Source/WTF/wtf/GraphNodeWorklist.h >=================================================================== >--- Source/WTF/wtf/GraphNodeWorklist.h (revision 230970) >+++ Source/WTF/wtf/GraphNodeWorklist.h (working copy) >@@ -1,5 +1,5 @@ > /* >- * Copyright (C) 2015-2016 Apple Inc. All rights reserved. >+ * Copyright (C) 2015-2018 Apple Inc. All rights reserved. > * > * Redistribution and use in source and binary forms, with or without > * modification, are permitted provided that the following conditions >@@ -64,8 +64,10 @@ public: > > bool saw(Node node) { return m_seen.contains(node); } > >+ bool pretendToHaveSeen(Node node) { return m_seen.add(node); } >+ > const Set& seen() const { return m_seen; } >- >+ > private: > Set m_seen; > Vector<Node, 16> m_stack; >Index: Source/WTF/wtf/IndexSet.h >=================================================================== >--- Source/WTF/wtf/IndexSet.h (revision 230970) >+++ Source/WTF/wtf/IndexSet.h (working copy) >@@ -61,6 +61,12 @@ public: > { > return m_set.clear(IndexKeyType<T>::index(value)); > } >+ >+ // Removes everything from the set without freeing memory. >+ void removeAll() >+ { >+ m_set.clearAll(); >+ } > > bool contains(const T& value) const > {
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 185060
:
338939
|
338952
|
338954
|
338965
|
339038
|
339050
|
339075
|
339087
|
339104
|
339163