Keyboard Shortcuts

File
u :up to issue
m :publish + mail comments
M :edit review message
j / k :jump to file after / before current file
J / K :jump to next file with a comment after / before current file
Side-by-side diff
i :toggle intra-line diffs
e :expand all comments
c :collapse all comments
s :toggle showing all comments
n / p :next / previous diff chunk or comment
N / P :next / previous comment
<Up> / <Down> :next / previous line
<Enter> :respond to / edit current comment
d :mark current comment as done
Issue
u :up to list of issues
m :publish + mail comments
j / k :jump to patch after / before current patch
o / <Enter> :open current patch in side-by-side view
i :open current patch in unified diff view
Issue List
j / k :jump to issue after / before current issue
o / <Enter> :open current issue
# : close issue
Comment/message editing
<Ctrl> + s or <Ctrl> + Enter :save comment
<Esc> :cancel edit
Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(34)
Issues Repositories Search
Open Issues | Closed Issues | All Issues | Sign in with your Google Account to create issues and add comments

Issue 4865045: Generic topological sorting for graphs in llvm/ADT

Can't Edit
Can't Publish+Mail
Start Review
Created:
14 years, 5 months ago by delesley
Modified:
14 years, 5 months ago
Visibility:
Public.

Patch Set 1 #

Total comments: 41

Patch Set 2 : Generic topological sort, with style changes #

Total comments: 34

Patch Set 3 : Final changes to topological sort #

Created: 14 years, 5 months ago
Download [raw] [tar.bz2]
Unified diffs Side-by-side diffs Delta from patch set Stats (+646 lines, -1 line) Patch
M include/llvm/ADT/GraphTraits.h View 1 2 1 chunk +16 lines, -0 lines 0 comments Download
A include/llvm/ADT/TopologicalSort.h View 1 2 1 chunk +207 lines, -0 lines 0 comments Download
A unittests/ADT/TopologicalTest.cpp View 1 2 1 chunk +421 lines, -0 lines 0 comments Download
M unittests/CMakeLists.txt View 1 2 1 chunk +2 lines, -1 line 0 comments Download
Total messages: 6
|
chandlerc
Mostly style comments. This needs a pretty rigorous run through the LLVM coding standards. Also, ...
14 years, 5 months ago (2011年08月15日 22:12:15 UTC) #1
Mostly style comments. This needs a pretty rigorous run through the LLVM coding
standards. Also, looking at other code in LLVM might help.
I've not looked at all at the test code, but I think the primary source code
should get cleaned up first.
Feel free to mail this patch at any point upstream and start getting feedback
there as well.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h
File include/llvm/ADT/TopologicalSort.h (right):
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:26: template<class GraphType, class IGT =
IndexedGraphTraits<GraphType> >
space after template, and I suspect we'll want to use 'typename' here instead of
'class'.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:31: // associate a depth with each node
Comments should be complete sentences.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:32: struct NodeInfo {
I might just forward declare this here, then provide the public interface, then
go back and define them at the bottom in the private section.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:36: NodeInfo() : Node(NULL), Depth(-1) { }
It's really unfortunate to make this class no longer a POD or an Aggregate. It
might be worthwhile to design a setup where Depth can be zero in the "null"
state.
Also, use '0' not 'NULL' for pointers in LLVM and Clang.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:40: { }
place the { on the previous line.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:54: // used within the sort() routine
complete sentences
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:63: private:
This is redundant in this particular location, but I would rotate most of this
to the bottom of the class so that the public API comes first.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:68: // Perform a topological sort
Complete sentence. Even better would likely be to not comment here, and provide
a full doxygen comment below.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:72: explicit TopologicallySortedGraph(const
GraphType& G)
always attach '*' and '&' to the variable name (this applies to several places
in this file)
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:76: {
{ on the previous line.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:102: inline iterator begin() { return
iterator(NodeInfoVect.begin()); }
No need for the inline keyword here or below.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:122: // An iterative algorithm is used to
avoid stack overflow for large graphs.
Doxygen comments please.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:128: std::vector<bool> Visited(NumNodes,
false);
No. no nononono. ;] 
vector<bool> is bad. have you read http://llvm.org/docs/ProgrammersManual.html?
It provides really good guidance on what data structures to use throughout LLVM.
You probably want something from here:
http://llvm.org/docs/ProgrammersManual.html#ds_bit
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:132: std::vector<StackFrame> Stack;
SmallVector would seem very handy here to make small graphs go very fast.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:153: NextNodeInfo->Depth <
static_cast<int>(Stack.size())+1) {
To me, this is a sign that Depth should be a size_t
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:170: else if (Stack.size() > 0) {
else should go after } on the same line.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:171: // finish processing current node
This makes me think that you actually have an inner loop and an outer loop. 
Maybe not. Either way, this loop could use some comments.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:185: std::sort(NodeInfoVect.begin(),
NodeInfoVect.end());
As annoying as it is, you should probably use array_pod_sort
(http://llvm.org/doxygen/STLExtras_8h_source.html#l00271)
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:188: for (typename
std::vector<NodeInfo>::iterator I = NodeInfoVect.begin(),
Why not just use an index?
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:190: Depths[IGT::getIndex((*I).Node)] =
(*I).Depth;
I->Node and I->Depth
Sign in to reply to this message.
delesley
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h File include/llvm/ADT/TopologicalSort.h (right): http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h#newcode26 include/llvm/ADT/TopologicalSort.h:26: template<class GraphType, class IGT = IndexedGraphTraits<GraphType> > On 2011年08月15日 ...
14 years, 5 months ago (2011年08月16日 18:15:21 UTC) #2
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h
File include/llvm/ADT/TopologicalSort.h (right):
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:26: template<class GraphType, class IGT =
IndexedGraphTraits<GraphType> >
On 2011年08月15日 22:12:15, chandlerc wrote:
> space after template, and I suspect we'll want to use 'typename' here instead
of
> 'class'.
Space is fixed. GraphTraits.h uses "class".
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:31: // associate a depth with each node
On 2011年08月15日 22:12:15, chandlerc wrote:
> Comments should be complete sentences.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:32: struct NodeInfo {
On 2011年08月15日 22:12:15, chandlerc wrote:
> I might just forward declare this here, then provide the public interface,
then
> go back and define them at the bottom in the private section.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:36: NodeInfo() : Node(NULL), Depth(-1) { }
Done. Although...
> Also, use '0' not 'NULL' for pointers in LLVM and Clang.
Seriously? If we wanted to write C code, then why use C++? I have serious
moral objections to pretending that integers are pointers.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:40: { }
On 2011年08月15日 22:12:15, chandlerc wrote:
> place the { on the previous line.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:54: // used within the sort() routine
On 2011年08月15日 22:12:15, chandlerc wrote:
> complete sentences
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:63: private:
On 2011年08月15日 22:12:15, chandlerc wrote:
> This is redundant in this particular location, but I would rotate most of this
> to the bottom of the class so that the public API comes first.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:68: // Perform a topological sort
On 2011年08月15日 22:12:15, chandlerc wrote:
> Complete sentence. Even better would likely be to not comment here, and
provide
> a full doxygen comment below.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:72: explicit TopologicallySortedGraph(const
GraphType& G)
On 2011年08月15日 22:12:15, chandlerc wrote:
> always attach '*' and '&' to the variable name (this applies to several places
> in this file)
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:76: {
On 2011年08月15日 22:12:15, chandlerc wrote:
> { on the previous line.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:102: inline iterator begin() { return
iterator(NodeInfoVect.begin()); }
On 2011年08月15日 22:12:15, chandlerc wrote:
> No need for the inline keyword here or below.
Done. Also removed inline on the iterator definition above.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:122: // An iterative algorithm is used to
avoid stack overflow for large graphs.
On 2011年08月15日 22:12:15, chandlerc wrote:
> Doxygen comments please.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:128: std::vector<bool> Visited(NumNodes,
false);
On 2011年08月15日 22:12:15, chandlerc wrote:
> No. no nononono. ;] 
> 
> vector<bool> is bad. have you read
http://llvm.org/docs/ProgrammersManual.html?
> It provides really good guidance on what data structures to use throughout
LLVM.
> 
> You probably want something from here:
> http://llvm.org/docs/ProgrammersManual.html#ds_bit
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:132: std::vector<StackFrame> Stack;
On 2011年08月15日 22:12:15, chandlerc wrote:
> SmallVector would seem very handy here to make small graphs go very fast.
I'm not sure that's a win here, although I am willing to be persuaded. It's
very difficult to predict in advance what the appropriate size for a SmallVector
should be, especially since this is a generic routine. Moreover, with a vector
I can reserve the size in advance, and thus keep memory overhead to a single
allocation in all cases.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:153: NextNodeInfo->Depth <
static_cast<int>(Stack.size())+1) {
On 2011年08月15日 22:12:15, chandlerc wrote:
> To me, this is a sign that Depth should be a size_t
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:170: else if (Stack.size() > 0) {
On 2011年08月15日 22:12:15, chandlerc wrote:
> else should go after } on the same line.
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:171: // finish processing current node
On 2011年08月15日 22:12:15, chandlerc wrote:
> This makes me think that you actually have an inner loop and an outer loop. 
> Maybe not. Either way, this loop could use some comments.
The recursive version has a for loop with a nested recursive call. The
iterative version eliminates the call in favor of a stack, but this has the side
effect of obfuscating the loop. I've added additional comments.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:185: std::sort(NodeInfoVect.begin(),
NodeInfoVect.end());
On 2011年08月15日 22:12:15, chandlerc wrote:
> As annoying as it is, you should probably use array_pod_sort
> (http://llvm.org/doxygen/STLExtras_8h_source.html#l00271)
Done.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:188: for (typename
std::vector<NodeInfo>::iterator I = NodeInfoVect.begin(),
On 2011年08月15日 22:12:15, chandlerc wrote:
> Why not just use an index?
In the case of std::vector, is there a difference? I assumed that iterators
were the preferred mechanism for any container class.
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:190: Depths[IGT::getIndex((*I).Node)] =
(*I).Depth;
On 2011年08月15日 22:12:15, chandlerc wrote:
> I->Node and I->Depth
Done.
Sign in to reply to this message.
Jeffrey Yasskin (google)
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h File include/llvm/ADT/TopologicalSort.h (right): http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h#newcode132 include/llvm/ADT/TopologicalSort.h:132: std::vector<StackFrame> Stack; On 2011年08月16日 18:15:22, delesley wrote: > On ...
14 years, 5 months ago (2011年08月16日 21:23:11 UTC) #3
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h
File include/llvm/ADT/TopologicalSort.h (right):
http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort...
include/llvm/ADT/TopologicalSort.h:132: std::vector<StackFrame> Stack;
On 2011年08月16日 18:15:22, delesley wrote:
> On 2011年08月15日 22:12:15, chandlerc wrote:
> > SmallVector would seem very handy here to make small graphs go very fast.
> 
> I'm not sure that's a win here, although I am willing to be persuaded. It's
> very difficult to predict in advance what the appropriate size for a
SmallVector
> should be, especially since this is a generic routine. Moreover, with a
vector
> I can reserve the size in advance, and thus keep memory overhead to a single
> allocation in all cases. 
SmallVector<> also has reserve(). I don't have an opinion on whether common
graphs are small enough to take advantage of a SmallVector here. 32 frames would
be enough to capture a lot of C++ functions though.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h
File include/llvm/ADT/GraphTraits.h (right):
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits....
include/llvm/ADT/GraphTraits.h:107: // typedef NodeType 
- Type of Node in the graph
Don't put so many spaces between the typedef and its meaning.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits....
include/llvm/ADT/GraphTraits.h:108: // static unsigned int getIndex(NodeType*)
LLVM tends to use just "unsigned" instead of "unsigned int".
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits....
include/llvm/ADT/GraphTraits.h:109: // Return the integer index of a node
Maybe describe the invariant that the result is <getMaxIndex?
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
File include/llvm/ADT/TopologicalSort.h (right):
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:11: // topological order, and provides an
iterator for traversing the graph in
"... in topological order, defined as increasing maximum acyclic path length
from the entry node." Otherwise it's not clear what order will be produced for
cyclic graphs.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:55: NodeType *operator*() const { return
(*I).Node; }
s/(*I)./I->/g
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:56: iterator& operator++() { ++I; return
*this; }
It looks weird to me to have "NodeType *operator" but "iterator& operator".
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:62: NodeType *operator->() const { return
&(operator*()); }
Does this work? operator* returns a pointer by value, so how can you take its
address?
Add a use of this to your test.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:82: unsigned int Depth;
Remove extra spaces; use just 'unsigned'.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:99: private:
No need for the redundant private marker.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:114: /// node, where the depth of a node is
defined as the longest acyclic path to
s/longest/length of the longest/
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:116: /// all predecessors of a node, except
back-edges, will have smaller depth).
It seems more that this definition of depth allows you to define back-edges as
edges from a higher depth to a lower depth, and that back-edges so-defined
generally correspond to what we'd intuitively identify as loop backedges.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:123: void TopologicallySortedGraph<GraphType,
IGT>::sort() {
Here's another sort() implementation that passes your test, and structures its
stack in a way that I think is easier to understand:
template <class GraphType, class IGT>
void TopologicallySortedGraph<GraphType, IGT>::sort() {
 int NumNodes = IGT::getMaxIndex(Graph);
 // record which nodes have been visited on the current path
 BitVector Visited(NumNodes, false);
 NodeType *EntryNode = IGT::getEntryNode(Graph);
 if (EntryNode == NULL) return;
 // Set info for entry node.
 NodeInfo *EntryNodeInfo = &NodeInfoVect[IGT::getIndex(EntryNode)];
 EntryNodeInfo->Node = EntryNode;
 EntryNodeInfo->Depth = 0;
 Visited[IGT::getIndex(EntryNode)] = true;
 // Stack for the depth-first search.
 //
 // Stores [current, end) iterator ranges for the nodes we're currently
 // visiting.
 std::vector<std::pair<ChildIteratorType, ChildIteratorType> > Stack;
 Stack.reserve(NumNodes);
 // Invariants. For all frames in Stack:
 // 1) [frame.first, frame.second) is a valid range.
 // 2) There is some node s.t. IGT::child_end(node)==frame.second, and
 // Visited[node] == true.
 Stack.push_back(make_pair(IGT::child_begin(EntryNode),
 IGT::child_end(EntryNode)));
 while (true) {
 if (Stack.back().first == Stack.back().second) {
 // We're done processing the current node. Restore previous state by
 // popping it off of the stack, and resume processing with the next child
 // of the parent node.
 Stack.pop_back();
 if (Stack.empty())
 break;
 Visited.reset(IGT::getIndex(*Stack.back().first));
 ++Stack.back().first;
 } else {
 NodeType *NextNode = *Stack.back().first;
 int NextNodeID = IGT::getIndex(NextNode);
 NodeInfo *NextNodeInfo = &NodeInfoVect[NextNodeID];
 if (!Visited.test(NextNodeID) &&
 NextNodeInfo->Depth < Stack.size()) {
 assert(NextNodeInfo->Node == NULL ||
 NextNodeInfo->Node == NextNode);
 NextNodeInfo->Node = NextNode;
 NextNodeInfo->Depth = Stack.size();
 // Save current state by pushing it onto the stack,
 // and start processing the child node.
 Visited.set(NextNodeID);
 Stack.push_back(make_pair(IGT::child_begin(NextNode),
IGT::child_end(NextNode)));
 } else {
 // Skip processing of the next block.
 ++Stack.back().first;
 }
 }
 }
 // sort according to depth
 array_pod_sort(NodeInfoVect.begin(), NodeInfoVect.end());
 // store the depths of all blocks
 for (typename std::vector<NodeInfo>::iterator I = NodeInfoVect.begin(),
 E = NodeInfoVect.end(); I != E; ++I) {
 Depths[IGT::getIndex(I->Node)] = I->Depth;
 }
}
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:148: int NextNodeID =
IGT::getIndex(*CurrentIter);
s/int/unsigned/
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:156: Visited.set(NextNodeID);
I'd stay with Visited[NextNodeID] = true;
http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest...
File unittests/ADT/TopologicalTest.cpp (right):
http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest...
unittests/ADT/TopologicalTest.cpp:304: // For debugging use only:
gtest has a way to print arbitrary messages on test failure:
http://code.google.com/p/googletest/wiki/AdvancedGuide#Using_a_Function_That_...
http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest...
unittests/ADT/TopologicalTest.cpp:324: TEST(TopologicalSortTest, SmallGraph) {
While randomized tests can be helpful, you also need tests of particular graph
structures with precise assertions about what order is produced.
http://codereview.appspot.com/4865045/diff/4001/unittests/CMakeLists.txt
File unittests/CMakeLists.txt (right):
http://codereview.appspot.com/4865045/diff/4001/unittests/CMakeLists.txt#newc...
unittests/CMakeLists.txt:76: ADT/TopologicalTest.cpp
Alphabetize.
Sign in to reply to this message.
chandlerc
Ping? Can we at least send this out to llvm-commits so that we can finish ...
14 years, 5 months ago (2011年08月17日 21:32:43 UTC) #4
Ping?
Can we at least send this out to llvm-commits so that we can finish its
review concurrently with the review for the warnings that use it?
On Tue, Aug 16, 2011 at 2:23 PM, <jyasskin@google.com> wrote:
>
> http://codereview.appspot.com/**4865045/diff/1/include/llvm/**
>
ADT/TopologicalSort.h<http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h>
> File include/llvm/ADT/**TopologicalSort.h (right):
>
> http://codereview.appspot.com/**4865045/diff/1/include/llvm/**
>
ADT/TopologicalSort.h#**newcode132<http://codereview.appspot.com/4865045/diff/1/include/llvm/ADT/TopologicalSort.h#newcode132>
> include/llvm/ADT/**TopologicalSort.h:132: std::vector<StackFrame> Stack;
> On 2011年08月16日 18:15:22, delesley wrote:
>
>> On 2011年08月15日 22:12:15, chandlerc wrote:
>> > SmallVector would seem very handy here to make small graphs go very
>>
> fast.
>
> I'm not sure that's a win here, although I am willing to be persuaded.
>>
> It's
>
>> very difficult to predict in advance what the appropriate size for a
>>
> SmallVector
>
>> should be, especially since this is a generic routine. Moreover, with
>>
> a vector
>
>> I can reserve the size in advance, and thus keep memory overhead to a
>>
> single
>
>> allocation in all cases.
>>
>
> SmallVector<> also has reserve(). I don't have an opinion on whether
> common graphs are small enough to take advantage of a SmallVector here.
> 32 frames would be enough to capture a lot of C++ functions though.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/GraphTraits.h<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h>
> File include/llvm/ADT/GraphTraits.h (right):
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/GraphTraits.h#**newcode107<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h#newcode107>
> include/llvm/ADT/GraphTraits.**h:107: // typedef NodeType
> - Type of Node in the graph
> Don't put so many spaces between the typedef and its meaning.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/GraphTraits.h#**newcode108<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h#newcode108>
> include/llvm/ADT/GraphTraits.**h:108: // static unsigned int
> getIndex(NodeType*)
> LLVM tends to use just "unsigned" instead of "unsigned int".
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/GraphTraits.h#**newcode109<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h#newcode109>
> include/llvm/ADT/GraphTraits.**h:109: // Return the integer index of a
> node
> Maybe describe the invariant that the result is <getMaxIndex?
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h>
>
> File include/llvm/ADT/**TopologicalSort.h (right):
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode11<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode11>
> include/llvm/ADT/**TopologicalSort.h:11: // topological order, and
> provides an iterator for traversing the graph in
> "... in topological order, defined as increasing maximum acyclic path
> length from the entry node." Otherwise it's not clear what order will be
> produced for cyclic graphs.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode55<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode55>
> include/llvm/ADT/**TopologicalSort.h:55: NodeType *operator*() const {
> return (*I).Node; }
> s/(*I)./I->/g
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode56<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode56>
> include/llvm/ADT/**TopologicalSort.h:56: iterator& operator++() { ++I;
> return *this; }
> It looks weird to me to have "NodeType *operator" but "iterator&
> operator".
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode62<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode62>
> include/llvm/ADT/**TopologicalSort.h:62: NodeType *operator->() const {
> return &(operator*()); }
> Does this work? operator* returns a pointer by value, so how can you
> take its address?
>
> Add a use of this to your test.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode82<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode82>
> include/llvm/ADT/**TopologicalSort.h:82: unsigned int Depth;
> Remove extra spaces; use just 'unsigned'.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode99<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode99>
> include/llvm/ADT/**TopologicalSort.h:99: private:
> No need for the redundant private marker.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode114<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode114>
> include/llvm/ADT/**TopologicalSort.h:114: /// node, where the depth of a
> node is defined as the longest acyclic path to
> s/longest/length of the longest/
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode116<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode116>
> include/llvm/ADT/**TopologicalSort.h:116: /// all predecessors of a node,
> except back-edges, will have smaller depth).
> It seems more that this definition of depth allows you to define
> back-edges as edges from a higher depth to a lower depth, and that
> back-edges so-defined generally correspond to what we'd intuitively
> identify as loop backedges.
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode123<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode123>
> include/llvm/ADT/**TopologicalSort.h:123: void
> TopologicallySortedGraph<**GraphType, IGT>::sort() {
> Here's another sort() implementation that passes your test, and
> structures its stack in a way that I think is easier to understand:
>
>
>
> template <class GraphType, class IGT>
> void TopologicallySortedGraph<**GraphType, IGT>::sort() {
> int NumNodes = IGT::getMaxIndex(Graph);
>
> // record which nodes have been visited on the current path
> BitVector Visited(NumNodes, false);
>
> NodeType *EntryNode = IGT::getEntryNode(Graph);
> if (EntryNode == NULL) return;
>
> // Set info for entry node.
> NodeInfo *EntryNodeInfo = &NodeInfoVect[IGT::getIndex(**EntryNode)];
> EntryNodeInfo->Node = EntryNode;
> EntryNodeInfo->Depth = 0;
> Visited[IGT::getIndex(**EntryNode)] = true;
>
> // Stack for the depth-first search.
> //
> // Stores [current, end) iterator ranges for the nodes we're currently
> // visiting.
> std::vector<std::pair<**ChildIteratorType, ChildIteratorType> > Stack;
> Stack.reserve(NumNodes);
> // Invariants. For all frames in Stack:
> // 1) [frame.first, frame.second) is a valid range.
> // 2) There is some node s.t. IGT::child_end(node)==frame.**second, and
> // Visited[node] == true.
> Stack.push_back(make_pair(IGT:**:child_begin(EntryNode),
> IGT::child_end(EntryNode)));
> while (true) {
> if (Stack.back().first == Stack.back().second) {
> // We're done processing the current node. Restore previous state
> by
> // popping it off of the stack, and resume processing with the
> next child
> // of the parent node.
> Stack.pop_back();
> if (Stack.empty())
> break;
> Visited.reset(IGT::getIndex(***Stack.back().first));
> ++Stack.back().first;
> } else {
> NodeType *NextNode = *Stack.back().first;
> int NextNodeID = IGT::getIndex(NextNode);
> NodeInfo *NextNodeInfo = &NodeInfoVect[NextNodeID];
>
> if (!Visited.test(NextNodeID) &&
> NextNodeInfo->Depth < Stack.size()) {
> assert(NextNodeInfo->Node == NULL ||
> NextNodeInfo->Node == NextNode);
> NextNodeInfo->Node = NextNode;
> NextNodeInfo->Depth = Stack.size();
> // Save current state by pushing it onto the stack,
> // and start processing the child node.
> Visited.set(NextNodeID);
> Stack.push_back(make_pair(IGT:**:child_begin(NextNode),
> IGT::child_end(NextNode)));
> } else {
> // Skip processing of the next block.
> ++Stack.back().first;
> }
> }
> }
>
> // sort according to depth
> array_pod_sort(NodeInfoVect.**begin(), NodeInfoVect.end());
>
> // store the depths of all blocks
>
> for (typename std::vector<NodeInfo>::**iterator I =
> NodeInfoVect.begin(),
> E = NodeInfoVect.end(); I != E; ++I) {
>
> Depths[IGT::getIndex(I->Node)] = I->Depth;
> }
> }
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode148<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode148>
> include/llvm/ADT/**TopologicalSort.h:148: int NextNodeID =
> IGT::getIndex(*CurrentIter);
> s/int/unsigned/
>
> http://codereview.appspot.com/**4865045/diff/4001/include/**
>
llvm/ADT/TopologicalSort.h#**newcode156<http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode156>
> include/llvm/ADT/**TopologicalSort.h:156: Visited.set(NextNodeID);
> I'd stay with Visited[NextNodeID] = true;
>
> http://codereview.appspot.com/**4865045/diff/4001/unittests/**
>
ADT/TopologicalTest.cpp<http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest.cpp>
> File unittests/ADT/TopologicalTest.**cpp (right):
>
> http://codereview.appspot.com/**4865045/diff/4001/unittests/**
>
ADT/TopologicalTest.cpp#**newcode304<http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest.cpp#newcode304>
> unittests/ADT/TopologicalTest.**cpp:304: // For debugging use only:
> gtest has a way to print arbitrary messages on test failure:
> http://code.google.com/p/**googletest/wiki/AdvancedGuide#**
>
Using_a_Function_That_Returns_**an_AssertionResult<http://code.google.com/p/googletest/wiki/AdvancedGuide#Using_a_Function_That_Returns_an_AssertionResult>
>
> http://codereview.appspot.com/**4865045/diff/4001/unittests/**
>
ADT/TopologicalTest.cpp#**newcode324<http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest.cpp#newcode324>
> unittests/ADT/TopologicalTest.**cpp:324: TEST(TopologicalSortTest,
> SmallGraph) {
> While randomized tests can be helpful, you also need tests of particular
> graph structures with precise assertions about what order is produced.
>
> http://codereview.appspot.com/**4865045/diff/4001/unittests/**
>
CMakeLists.txt<http://codereview.appspot.com/4865045/diff/4001/unittests/CMakeLists.txt>
> File unittests/CMakeLists.txt (right):
>
> http://codereview.appspot.com/**4865045/diff/4001/unittests/**
>
CMakeLists.txt#newcode76<http://codereview.appspot.com/4865045/diff/4001/unittests/CMakeLists.txt#newcode76>
> unittests/CMakeLists.txt:76: ADT/TopologicalTest.cpp
> Alphabetize.
>
>
>
http://codereview.appspot.com/**4865045/<http://codereview.appspot.com/4865045/>
>
Sign in to reply to this message.
delesley
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h File include/llvm/ADT/TopologicalSort.h (right): http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalSort.h#newcode11 include/llvm/ADT/TopologicalSort.h:11: // topological order, and provides an iterator for traversing ...
14 years, 5 months ago (2011年08月17日 21:33:23 UTC) #5
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
File include/llvm/ADT/TopologicalSort.h (right):
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:11: // topological order, and provides an
iterator for traversing the graph in
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> "... in topological order, defined as increasing maximum acyclic path length
> from the entry node." Otherwise it's not clear what order will be produced for
> cyclic graphs.
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:55: NodeType *operator*() const { return
(*I).Node; }
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> s/(*I)./I->/g
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:56: iterator& operator++() { ++I; return
*this; }
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> It looks weird to me to have "NodeType *operator" but "iterator& operator".
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:62: NodeType *operator->() const { return
&(operator*()); }
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> Does this work? operator* returns a pointer by value, so how can you take its
> address?
> 
> Add a use of this to your test.
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:82: unsigned int Depth;
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> Remove extra spaces; use just 'unsigned'.
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:99: private:
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> No need for the redundant private marker.
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:114: /// node, where the depth of a node is
defined as the longest acyclic path to
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> s/longest/length of the longest/
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:116: /// all predecessors of a node, except
back-edges, will have smaller depth).
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> It seems more that this definition of depth allows you to define back-edges as
> edges from a higher depth to a lower depth, and that back-edges so-defined
> generally correspond to what we'd intuitively identify as loop backedges.
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:123: void TopologicallySortedGraph<GraphType,
IGT>::sort() {
As per our semi-scientific test, I am leaving the loop structure unchanged. :-)
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> Here's another sort() implementation that passes your test, and structures its
> stack in a way that I think is easier to understand:
> 
> 
> template <class GraphType, class IGT>
> void TopologicallySortedGraph<GraphType, IGT>::sort() {
> int NumNodes = IGT::getMaxIndex(Graph);
> 
> // record which nodes have been visited on the current path
> BitVector Visited(NumNodes, false);
> 
> NodeType *EntryNode = IGT::getEntryNode(Graph);
> if (EntryNode == NULL) return;
> 
> // Set info for entry node.
> NodeInfo *EntryNodeInfo = &NodeInfoVect[IGT::getIndex(EntryNode)];
> EntryNodeInfo->Node = EntryNode;
> EntryNodeInfo->Depth = 0;
> Visited[IGT::getIndex(EntryNode)] = true;
> 
> // Stack for the depth-first search.
> //
> // Stores [current, end) iterator ranges for the nodes we're currently
> // visiting.
> std::vector<std::pair<ChildIteratorType, ChildIteratorType> > Stack;
> Stack.reserve(NumNodes);
> // Invariants. For all frames in Stack:
> // 1) [frame.first, frame.second) is a valid range.
> // 2) There is some node s.t. IGT::child_end(node)==frame.second, and
> // Visited[node] == true.
> Stack.push_back(make_pair(IGT::child_begin(EntryNode),
> IGT::child_end(EntryNode)));
> while (true) {
> if (Stack.back().first == Stack.back().second) {
> // We're done processing the current node. Restore previous state by
> // popping it off of the stack, and resume processing with the next
child
> // of the parent node.
> Stack.pop_back();
> if (Stack.empty())
> break;
> Visited.reset(IGT::getIndex(*Stack.back().first));
> ++Stack.back().first;
> } else {
> NodeType *NextNode = *Stack.back().first;
> int NextNodeID = IGT::getIndex(NextNode);
> NodeInfo *NextNodeInfo = &NodeInfoVect[NextNodeID];
> 
> if (!Visited.test(NextNodeID) &&
> NextNodeInfo->Depth < Stack.size()) {
> assert(NextNodeInfo->Node == NULL ||
> NextNodeInfo->Node == NextNode);
> NextNodeInfo->Node = NextNode;
> NextNodeInfo->Depth = Stack.size();
> // Save current state by pushing it onto the stack,
> // and start processing the child node.
> Visited.set(NextNodeID);
> Stack.push_back(make_pair(IGT::child_begin(NextNode),
> IGT::child_end(NextNode)));
> } else {
> // Skip processing of the next block.
> ++Stack.back().first;
> }
> }
> }
> 
> // sort according to depth
> array_pod_sort(NodeInfoVect.begin(), NodeInfoVect.end());
> 
> // store the depths of all blocks
> for (typename std::vector<NodeInfo>::iterator I = NodeInfoVect.begin(),
> E = NodeInfoVect.end(); I != E; ++I) {
> Depths[IGT::getIndex(I->Node)] = I->Depth;
> }
> }
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:148: int NextNodeID =
IGT::getIndex(*CurrentIter);
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> s/int/unsigned/
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/TopologicalS...
include/llvm/ADT/TopologicalSort.h:156: Visited.set(NextNodeID);
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> I'd stay with Visited[NextNodeID] = true;
Done.
http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest...
File unittests/ADT/TopologicalTest.cpp (right):
http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest...
unittests/ADT/TopologicalTest.cpp:304: // For debugging use only:
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> gtest has a way to print arbitrary messages on test failure:
>
http://code.google.com/p/googletest/wiki/AdvancedGuide#Using_a_Function_That_...
Done.
http://codereview.appspot.com/4865045/diff/4001/unittests/ADT/TopologicalTest...
unittests/ADT/TopologicalTest.cpp:324: TEST(TopologicalSortTest, SmallGraph) {
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> While randomized tests can be helpful, you also need tests of particular graph
> structures with precise assertions about what order is produced.
Done.
Sign in to reply to this message.
delesley
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h File include/llvm/ADT/GraphTraits.h (right): http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h#newcode107 include/llvm/ADT/GraphTraits.h:107: // typedef NodeType - Type of Node in the ...
14 years, 5 months ago (2011年08月17日 21:36:34 UTC) #6
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits.h
File include/llvm/ADT/GraphTraits.h (right):
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits....
include/llvm/ADT/GraphTraits.h:107: // typedef NodeType 
- Type of Node in the graph
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> Don't put so many spaces between the typedef and its meaning.
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits....
include/llvm/ADT/GraphTraits.h:108: // static unsigned int getIndex(NodeType*)
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> LLVM tends to use just "unsigned" instead of "unsigned int".
Done.
http://codereview.appspot.com/4865045/diff/4001/include/llvm/ADT/GraphTraits....
include/llvm/ADT/GraphTraits.h:109: // Return the integer index of a node
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> Maybe describe the invariant that the result is <getMaxIndex?
Done.
http://codereview.appspot.com/4865045/diff/4001/unittests/CMakeLists.txt
File unittests/CMakeLists.txt (right):
http://codereview.appspot.com/4865045/diff/4001/unittests/CMakeLists.txt#newc...
unittests/CMakeLists.txt:76: ADT/TopologicalTest.cpp
On 2011年08月16日 21:23:11, Jeffrey Yasskin (google) wrote:
> Alphabetize.
Done.
Sign in to reply to this message.
|
Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b

AltStyle によって変換されたページ (->オリジナル) /