Changeset 162256 in webkit
- Timestamp:
- Jan 18, 2014 9:55:07 AM (10 years ago)
- Location:
- trunk/Source/JavaScriptCore
- Files:
-
- 1 added
- 1 deleted
- 9 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/Source/JavaScriptCore/ChangeLog
r162250 r162256 1 2014-01-18 Mark Lam <mark.lam@apple.com> 2 3 Adding UnlinkedCodeBlock::opDebugBytecodeOffsetForLineAndColumn().. 4 https://bugs.webkit.org/show_bug.cgi?id=127127. 5 6 Reviewed by Geoffrey Garen. 7 8 In order to implement bytecode level breakpoints, we need a mechanism 9 for computing the best fit op_debug bytecode offset for any valid given 10 line and column value in the source. The "best fit" op_debug bytecode 11 in this case is defined below in the comment for 12 UnlinkedCodeBlock::opDebugBytecodeOffsetForLineAndColumn(). 13 14 * GNUmakefile.list.am: 15 * JavaScriptCore.vcxproj/JavaScriptCore.vcxproj: 16 * JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters: 17 * JavaScriptCore.xcodeproj/project.pbxproj: 18 * bytecode/CodeBlock.cpp: 19 (JSC::CodeBlock::opDebugBytecodeOffsetForLineAndColumn): 20 - Convert the line and column to unlinked line and column values and 21 pass them to UnlinkedCodeBlock::opDebugBytecodeOffsetForLineAndColumn() 22 to do the real work. 23 24 * bytecode/CodeBlock.h: 25 * bytecode/LineColumnInfo.h: Added. 26 (JSC::LineColumnInfo::operator <): 27 (JSC::LineColumnInfo::LineColumnPair::LineColumnPair): 28 (JSC::LineColumnInfo::operator ==): 29 (JSC::LineColumnInfo::operator !=): 30 (JSC::LineColumnInfo::operator <=): 31 (JSC::LineColumnInfo::operator >): 32 (JSC::LineColumnInfo::operator >=): 33 * bytecode/LineInfo.h: Removed. 34 35 * bytecode/UnlinkedCodeBlock.cpp: 36 (JSC::UnlinkedCodeBlock::decodeExpressionRangeLineAndColumn): 37 - Factored this out of expressionRangeForBytecodeOffset() so that it can 38 be called from multiple places. 39 (JSC::dumpLineColumnEntry): 40 (JSC::UnlinkedCodeBlock::dumpExpressionRangeInfo): 41 (JSC::UnlinkedCodeBlock::dumpOpDebugLineColumnInfoList): 42 - Some dumpers for debugging use only. 43 (JSC::UnlinkedCodeBlock::expressionRangeForBytecodeOffset): 44 (JSC::UnlinkedCodeBlock::opDebugBytecodeOffsetForLineAndColumn): 45 - Finds the earliest op_debug bytecode whose line and column matches the 46 specified line and column values. If an exact match is not found, then 47 finds the nearest op_debug bytecode that precedes the specified line 48 and column values. If there are more than one op_debug at that preceding 49 line and column value, then the earliest of those op_debug bytecodes will 50 be be selected. The offset of the selected bytecode will be returned. 51 52 We want the earliest one because when we have multiple op_debug bytecodes 53 that map to a given line and column, a debugger user would expect to break 54 on the first one and step through the rest thereafter if needed. 55 56 (JSC::compareLineColumnInfo): 57 (JSC::UnlinkedCodeBlock::opDebugLineColumnInfoList): 58 - Creates the sorted opDebugLineColumnInfoList on demand. This list is 59 stored in the UnlinkedCodeBlock's rareData. 60 * bytecode/UnlinkedCodeBlock.h: 61 1 62 2014-01-18 Zan Dobersek <zdobersek@igalia.com> 2 63 -
trunk/Source/JavaScriptCore/GNUmakefile.list.am
r161816 r162256 153 153 Source/JavaScriptCore/bytecode/LazyOperandValueProfile.cpp \ 154 154 Source/JavaScriptCore/bytecode/LazyOperandValueProfile.h \ 155 Source/JavaScriptCore/bytecode/Line Info.h \155 Source/JavaScriptCore/bytecode/LineColumnInfo.h \ 156 156 Source/JavaScriptCore/bytecode/MethodOfGettingAValueProfile.cpp \ 157 157 Source/JavaScriptCore/bytecode/MethodOfGettingAValueProfile.h \ -
trunk/Source/JavaScriptCore/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj
r161733 r162256 817 817 <ClInclude Include="..\bytecode\JumpTable.h" /> 818 818 <ClInclude Include="..\bytecode\LazyOperandValueProfile.h" /> 819 <ClInclude Include="..\bytecode\Line Info.h" />819 <ClInclude Include="..\bytecode\LineColumnInfo.h" /> 820 820 <ClInclude Include="..\bytecode\LLIntCallLinkInfo.h" /> 821 821 <ClInclude Include="..\bytecode\MethodOfGettingAValueProfile.h" /> -
trunk/Source/JavaScriptCore/JavaScriptCore.vcxproj/JavaScriptCore.vcxproj.filters
r161733 r162256 1503 1503 <Filter>bytecode</Filter> 1504 1504 </ClInclude> 1505 <ClInclude Include="..\bytecode\Line Info.h">1505 <ClInclude Include="..\bytecode\LineColumnInfo.h"> 1506 1506 <Filter>bytecode</Filter> 1507 1507 </ClInclude> -
trunk/Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
r162112 r162256 66 66 0F0B83A914BCF56200885B4F /* HandlerInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F0B83A814BCF55E00885B4F /* HandlerInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; 67 67 0F0B83AB14BCF5BB00885B4F /* ExpressionRangeInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F0B83AA14BCF5B900885B4F /* ExpressionRangeInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; 68 0F0B83AD14BCF60400885B4F /* LineInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F0B83AC14BCF60200885B4F /* LineInfo.h */; settings = {ATTRIBUTES = (Private, ); }; };69 68 0F0B83B014BCF71600885B4F /* CallLinkInfo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F0B83AE14BCF71400885B4F /* CallLinkInfo.cpp */; }; 70 69 0F0B83B114BCF71800885B4F /* CallLinkInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = 0F0B83AF14BCF71400885B4F /* CallLinkInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; … … 1314 1313 FE20CE9D15F04A9500DF3430 /* LLIntCLoop.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FE20CE9B15F04A9500DF3430 /* LLIntCLoop.cpp */; }; 1315 1314 FE20CE9E15F04A9500DF3430 /* LLIntCLoop.h in Headers */ = {isa = PBXBuildFile; fileRef = FE20CE9C15F04A9500DF3430 /* LLIntCLoop.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1315 FE37DD56183C1E880018B2F0 /* LineColumnInfo.h in Headers */ = {isa = PBXBuildFile; fileRef = FE37DD55183C1E880018B2F0 /* LineColumnInfo.h */; settings = {ATTRIBUTES = (Private, ); }; }; 1316 1316 FE4A331F15BD2E07006F54F3 /* VMInspector.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FE4A331D15BD2E07006F54F3 /* VMInspector.cpp */; }; 1317 1317 FE4A332015BD2E07006F54F3 /* VMInspector.h in Headers */ = {isa = PBXBuildFile; fileRef = FE4A331E15BD2E07006F54F3 /* VMInspector.h */; settings = {ATTRIBUTES = (Private, ); }; }; … … 1436 1436 0F0B83A814BCF55E00885B4F /* HandlerInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HandlerInfo.h; sourceTree = "<group>"; }; 1437 1437 0F0B83AA14BCF5B900885B4F /* ExpressionRangeInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ExpressionRangeInfo.h; sourceTree = "<group>"; }; 1438 0F0B83AC14BCF60200885B4F /* LineInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LineInfo.h; sourceTree = "<group>"; };1439 1438 0F0B83AE14BCF71400885B4F /* CallLinkInfo.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CallLinkInfo.cpp; sourceTree = "<group>"; }; 1440 1439 0F0B83AF14BCF71400885B4F /* CallLinkInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CallLinkInfo.h; sourceTree = "<group>"; }; … … 2724 2723 FE20CE9B15F04A9500DF3430 /* LLIntCLoop.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = LLIntCLoop.cpp; path = llint/LLIntCLoop.cpp; sourceTree = "<group>"; }; 2725 2724 FE20CE9C15F04A9500DF3430 /* LLIntCLoop.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = LLIntCLoop.h; path = llint/LLIntCLoop.h; sourceTree = "<group>"; }; 2725 FE37DD55183C1E880018B2F0 /* LineColumnInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LineColumnInfo.h; sourceTree = "<group>"; }; 2726 2726 FE4A331D15BD2E07006F54F3 /* VMInspector.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = VMInspector.cpp; sourceTree = "<group>"; }; 2727 2727 FE4A331E15BD2E07006F54F3 /* VMInspector.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = VMInspector.h; sourceTree = "<group>"; }; … … 4231 4231 isa = PBXGroup; 4232 4232 children = ( 4233 FE37DD55183C1E880018B2F0 /* LineColumnInfo.h */, 4233 4234 0F8335B41639C1E3001443B5 /* ArrayAllocationProfile.cpp */, 4234 4235 0F8335B51639C1E3001443B5 /* ArrayAllocationProfile.h */, … … 4281 4282 0FB5467814F5C468002C2989 /* LazyOperandValueProfile.cpp */, 4282 4283 0FB5467614F59AD1002C2989 /* LazyOperandValueProfile.h */, 4283 0F0B83AC14BCF60200885B4F /* LineInfo.h */,4284 4284 0F0FC45814BD15F100B81154 /* LLIntCallLinkInfo.h */, 4285 4285 0FB5467C14F5CFD3002C2989 /* MethodOfGettingAValueProfile.cpp */, … … 4472 4472 A532439418569709002ED692 /* generate-combined-inspector-json.py in Headers */, 4473 4473 C2FCAE1317A9C24E0034C735 /* BytecodeLivenessAnalysis.h in Headers */, 4474 FE37DD56183C1E880018B2F0 /* LineColumnInfo.h in Headers */, 4474 4475 0F8335B81639C1EA001443B5 /* ArrayAllocationProfile.h in Headers */, 4475 4476 A5840E29187CA5E600843B10 /* inline-and-minify-stylesheets-and-scripts.py in Headers */, … … 4937 4938 BC18C52E0E16FCE100B34460 /* Lexer.lut.h in Headers */, 4938 4939 A513E5CB185F9624007E95AD /* InjectedScriptManager.h in Headers */, 4939 0F0B83AD14BCF60400885B4F /* LineInfo.h in Headers */,4940 4940 86D3B3C310159D7F002865E7 /* LinkBuffer.h in Headers */, 4941 4941 0F431738146BAC69007E3890 /* ListableHandler.h in Headers */, -
trunk/Source/JavaScriptCore/bytecode/CodeBlock.cpp
r161615 r162256 1 1 /* 2 * Copyright (C) 2008, 2009, 2010, 2012, 2013 Apple Inc. All rights reserved.2 * Copyright (C) 2008, 2009, 2010, 2012, 2013, 2014 Apple Inc. All rights reserved. 3 3 * Copyright (C) 2008 Cameron Zwarich <cwzwarich@uwaterloo.ca> 4 4 * … … 2566 2566 } 2567 2567 2568 unsigned CodeBlock::opDebugBytecodeOffsetForLineAndColumn(unsigned& line, unsigned& column) 2569 { 2570 unsigned lineAdjustment = m_ownerExecutable->lineNo(); 2571 ASSERT(line >= lineAdjustment); 2572 unsigned unlinkedLine = line - lineAdjustment; 2573 unsigned columnAdjustment = unlinkedLine ? 1 : firstLineColumnOffset(); 2574 ASSERT(column >= columnAdjustment); 2575 unsigned unlinkedColumn = column - columnAdjustment; 2576 2577 unsigned bytecodeOffset = m_unlinkedCode->opDebugBytecodeOffsetForLineAndColumn(unlinkedLine, unlinkedColumn); 2578 if (bytecodeOffset != static_cast<unsigned>(WTF::notFound)) { 2579 line = unlinkedLine + lineAdjustment; 2580 column = unlinkedColumn + (unlinkedLine ? 1 : firstLineColumnOffset()); 2581 } 2582 return bytecodeOffset; 2583 } 2584 2568 2585 void CodeBlock::shrinkToFit(ShrinkMode shrinkMode) 2569 2586 { -
trunk/Source/JavaScriptCore/bytecode/CodeBlock.h
r162139 r162256 1 1 /* 2 * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013 Apple Inc. All rights reserved.2 * Copyright (C) 2008, 2009, 2010, 2011, 2012, 2013, 2014 Apple Inc. All rights reserved. 3 3 * Copyright (C) 2008 Cameron Zwarich <cwzwarich@uwaterloo.ca> 4 4 * … … 66 66 #include "LLIntCallLinkInfo.h" 67 67 #include "LazyOperandValueProfile.h" 68 #include "LineInfo.h"69 68 #include "ProfilerCompilation.h" 70 69 #include "RegExpObject.h" … … 176 175 void expressionRangeForBytecodeOffset(unsigned bytecodeOffset, int& divot, 177 176 int& startOffset, int& endOffset, unsigned& line, unsigned& column); 177 unsigned opDebugBytecodeOffsetForLineAndColumn(unsigned& line, unsigned& column); 178 178 179 179 #if ENABLE(JIT) -
trunk/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.cpp
r159520 r162256 38 38 #include "Structure.h" 39 39 #include "SymbolTable.h" 40 #include <wtf/DataLog.h> 40 41 41 42 namespace JSC { … … 257 258 } 258 259 260 inline void UnlinkedCodeBlock::getLineAndColumn(ExpressionRangeInfo& info, 261 unsigned& line, unsigned& column) 262 { 263 switch (info.mode) { 264 case ExpressionRangeInfo::FatLineMode: 265 info.decodeFatLineMode(line, column); 266 break; 267 case ExpressionRangeInfo::FatColumnMode: 268 info.decodeFatColumnMode(line, column); 269 break; 270 case ExpressionRangeInfo::FatLineAndColumnMode: { 271 unsigned fatIndex = info.position; 272 ExpressionRangeInfo::FatPosition& fatPos = m_rareData->m_expressionInfoFatPositions[fatIndex]; 273 line = fatPos.line; 274 column = fatPos.column; 275 break; 276 } 277 } // switch 278 } 279 280 #ifndef NDEBUG 281 static void dumpLineColumnEntry(size_t index, const RefCountedArray<UnlinkedInstruction>& instructions, unsigned instructionOffset, unsigned line, unsigned column) 282 { 283 OpcodeID opcode = instructions[instructionOffset].u.opcode; 284 const char* event = ""; 285 if (opcode == op_debug) { 286 switch (instructions[instructionOffset + 1].u.operand) { 287 case WillExecuteProgram: event = " WillExecuteProgram"; break; 288 case DidExecuteProgram: event = " DidExecuteProgram"; break; 289 case DidEnterCallFrame: event = " DidEnterCallFrame"; break; 290 case DidReachBreakpoint: event = " DidReachBreakpoint"; break; 291 case WillLeaveCallFrame: event = " WillLeaveCallFrame"; break; 292 case WillExecuteStatement: event = " WillExecuteStatement"; break; 293 } 294 } 295 dataLogF(" [%zu] pc %u @ line %u col %u : %s%s\n", index, instructionOffset, line, column, opcodeNames[opcode], event); 296 } 297 298 void UnlinkedCodeBlock::dumpExpressionRangeInfo() 299 { 300 Vector<ExpressionRangeInfo>& expressionInfo = m_expressionInfo; 301 302 size_t size = m_expressionInfo.size(); 303 dataLogF("UnlinkedCodeBlock %p expressionRangeInfo[%zu] {\n", this, size); 304 for (size_t i = 0; i < size; i++) { 305 ExpressionRangeInfo& info = expressionInfo[i]; 306 unsigned line; 307 unsigned column; 308 getLineAndColumn(info, line, column); 309 dumpLineColumnEntry(i, instructions(), info.instructionOffset, line, column); 310 } 311 dataLog("}\n"); 312 } 313 314 void UnlinkedCodeBlock::dumpOpDebugLineColumnInfoList() 315 { 316 LineColumnInfoList& infoList = this->opDebugLineColumnInfoList(); 317 size_t size = infoList.size(); 318 319 dataLogF("UnlinkedCodeBlock %p opDebugLineColumnInfoList[%zu] {\n", this, size); 320 for (size_t i = 0; i < size; i++) { 321 LineColumnInfo& info = infoList[i]; 322 dumpLineColumnEntry(i, instructions(), info.instructionOffset, info.line, info.column); 323 } 324 dataLog("}\n"); 325 } 326 #endif 327 259 328 void UnlinkedCodeBlock::expressionRangeForBytecodeOffset(unsigned bytecodeOffset, 260 329 int& divot, int& startOffset, int& endOffset, unsigned& line, unsigned& column) … … 290 359 endOffset = info.endOffset; 291 360 divot = info.divotPoint; 292 293 switch (info.mode) { 294 case ExpressionRangeInfo::FatLineMode: 295 info.decodeFatLineMode(line, column); 296 break; 297 case ExpressionRangeInfo::FatColumnMode: 298 info.decodeFatColumnMode(line, column); 299 break; 300 case ExpressionRangeInfo::FatLineAndColumnMode: { 301 unsigned fatIndex = info.position; 302 ExpressionRangeInfo::FatPosition& fatPos = m_rareData->m_expressionInfoFatPositions[fatIndex]; 303 line = fatPos.line; 304 column = fatPos.column; 305 break; 306 } 307 } // switch 361 getLineAndColumn(info, line, column); 362 } 363 364 unsigned UnlinkedCodeBlock::opDebugBytecodeOffsetForLineAndColumn(unsigned& line, unsigned& column) 365 { 366 if (!m_expressionInfo.size()) 367 return static_cast<unsigned>(WTF::notFound); 368 369 LineColumnInfo::LineColumnPair target(line, column); 370 LineColumnInfoList& list = opDebugLineColumnInfoList(); 371 372 // Find the earliest op_debug bytecode whose line and column matches the 373 // target line and column values. If an exact match is not found, then 374 // find the nearest op_debug bytecode that precedes the target line and 375 // column values. If there are more than one op_debug at that preceding 376 // line and column value, then select the earliest of those. 377 // 378 // We want the earliest one because when we have multiple op_debug bytecodes 379 // that map to a given line and column, a debugger user would expect to break 380 // on the first one and step through the rest thereafter if needed. 381 382 LineColumnInfoList::iterator it = std::lower_bound(list.begin(), list.end(), target); 383 if (it == list.end() || *it > target) 384 --it; 385 ASSERT(it >= list.begin() && it < list.end() && *it <= target); 386 ASSERT(it == list.begin() || *(it - 1) < *it); 387 388 line = it->line; 389 column = it->column; 390 391 unsigned offset = it->instructionOffset; 392 ASSERT(instructions()[offset].u.opcode == op_debug); 393 394 return offset; 395 } 396 397 static bool compareLineColumnInfo(const LineColumnInfo& first, const LineColumnInfo& second) 398 { 399 return first < second; 400 } 401 402 UnlinkedCodeBlock::LineColumnInfoList& UnlinkedCodeBlock::opDebugLineColumnInfoList() 403 { 404 createRareDataIfNecessary(); 405 406 if (m_rareData->m_opDebugLineColumnInfoList) { 407 ASSERT(m_rareData->m_opDebugLineColumnInfoList->size() <= m_expressionInfo.size()); 408 return *m_rareData->m_opDebugLineColumnInfoList; 409 } 410 411 Vector<ExpressionRangeInfo>& expressionInfo = m_expressionInfo; 412 size_t size = m_expressionInfo.size(); 413 414 m_rareData->m_opDebugLineColumnInfoList = std::make_unique<LineColumnInfoList>(); 415 LineColumnInfoList& infoList = *m_rareData->m_opDebugLineColumnInfoList; 416 417 for (size_t src = 0; src < size; src++) { 418 ExpressionRangeInfo& exprInfo = expressionInfo[src]; 419 unsigned instructionOffset = exprInfo.instructionOffset; 420 OpcodeID opcode = instructions()[instructionOffset].u.opcode; 421 if (opcode == op_debug) { 422 LineColumnInfo info; 423 info.instructionOffset = instructionOffset; 424 getLineAndColumn(exprInfo, info.line, info.column); 425 infoList.append(info); 426 } 427 } 428 infoList.shrinkToFit(); 429 430 std::sort(infoList.begin(), infoList.end(), compareLineColumnInfo); 431 432 return *m_rareData->m_opDebugLineColumnInfoList.get(); 308 433 } 309 434 -
trunk/Source/JavaScriptCore/bytecode/UnlinkedCodeBlock.h
r160109 r162256 34 34 #include "JSCell.h" 35 35 #include "JSString.h" 36 #include "Line Info.h"36 #include "LineColumnInfo.h" 37 37 #include "ParserModes.h" 38 38 #include "RegExp.h" … … 43 43 #include <wtf/Compression.h> 44 44 #include <wtf/RefCountedArray.h> 45 #include <wtf/StdLibExtras.h> 45 46 #include <wtf/Vector.h> 46 47 … … 445 446 void expressionRangeForBytecodeOffset(unsigned bytecodeOffset, int& divot, 446 447 int& startOffset, int& endOffset, unsigned& line, unsigned& column); 448 unsigned opDebugBytecodeOffsetForLineAndColumn(unsigned& line, unsigned& column); 447 449 448 450 void recordParse(CodeFeatures features, bool hasCapturedVariables, unsigned firstLine, unsigned lineCount, unsigned endColumn) … … 463 465 unsigned endColumn() const { return m_endColumn; } 464 466 467 void dumpExpressionRangeInfo(); // For debugging purpose only. 468 void dumpOpDebugLineColumnInfoList(); // For debugging purpose only. 469 465 470 protected: 466 471 UnlinkedCodeBlock(VM*, Structure*, CodeType, const ExecutableInfo&); … … 482 487 m_rareData = adoptPtr(new RareData); 483 488 } 489 490 void getLineAndColumn(ExpressionRangeInfo&, unsigned& line, unsigned& column); 491 492 typedef Vector<LineColumnInfo> LineColumnInfoList; 493 LineColumnInfoList& opDebugLineColumnInfoList(); 484 494 485 495 RefCountedArray<UnlinkedInstruction> m_unlinkedInstructions; … … 547 557 548 558 Vector<ExpressionRangeInfo::FatPosition> m_expressionInfoFatPositions; 559 std::unique_ptr<LineColumnInfoList> m_opDebugLineColumnInfoList; 549 560 }; 550 561
Note: See TracChangeset
for help on using the changeset viewer.