Changeset 6840e7c for src/InitTweak
- Timestamp:
- Oct 19, 2017, 12:01:04 PM (8 years ago)
- Branches:
- ADT, arm-eh, ast-experimental, cleanup-dtors, enum, forall-pointer-decay, jacob/cs343-translation, jenkins-sandbox, master, new-ast, new-ast-unique-expr, pthread-emulation, qualifiedEnum
- Children:
- 837ce06
- Parents:
- b96ec83 (diff), a15b72c (diff)
Note: this is a merge changeset, the changes displayed below correspond to the merge itself.
Use the(diff)links above to see all the changes relative to each parent. - Location:
- src/InitTweak
- Files:
-
- 5 edited
-
FixInit.cc (modified) (20 diffs)
-
GenInit.cc (modified) (10 diffs)
-
GenInit.h (modified) (2 diffs)
-
InitTweak.cc (modified) (6 diffs)
-
InitTweak.h (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
src/InitTweak/FixInit.cc
rb96ec83 r6840e7c 94 94 /// true if type does not need to be copy constructed to ensure correctness 95 95 bool skipCopyConstruct( Type * type ); 96 void copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr );96 void copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr, Type * formal ); 97 97 void destructRet( ObjectDecl * ret, ImplicitCopyCtorExpr * impCpCtorExpr ); 98 98 … … 259 259 260 260 GenStructMemberCalls::generate( translationUnit ); 261 261 262 // xxx - ctor expansion currently has to be after FixCopyCtors, because there is currently a 262 263 // hack in the way untyped assignments are generated, where the first argument cannot have … … 288 289 for ( std::list< Declaration * >::iterator i = translationUnit.begin(); i != translationUnit.end(); ++i ) { 289 290 try { 290 *i =maybeMutate( *i, fixer );291 maybeMutate( *i, fixer ); 291 292 translationUnit.splice( i, fixer.pass.staticDtorDecls ); 292 293 } catch( SemanticError &e ) { … … 322 323 323 324 Expression * InsertImplicitCalls::postmutate( ApplicationExpr * appExpr ) { 324 assert( appExpr );325 326 325 if ( VariableExpr * function = dynamic_cast< VariableExpr * > ( appExpr->get_function() ) ) { 327 if ( LinkageSpec::isBuiltin( function->get_var()->get_linkage() )) {326 if ( function->var->linkage.is_builtin ) { 328 327 // optimization: don't need to copy construct in order to call intrinsic functions 329 328 return appExpr; … … 331 330 FunctionType * ftype = dynamic_cast< FunctionType * >( GenPoly::getFunctionType( funcDecl->get_type() ) ); 332 331 assertf( ftype, "Function call without function type: %s", toString( funcDecl ).c_str() ); 333 if ( CodeGen::isConstructor( funcDecl->get_name() ) && ftype-> get_parameters().size() == 2 ) {334 Type * t1 = getPointerBase( ftype-> get_parameters().front()->get_type() );335 Type * t2 = ftype-> get_parameters().back()->get_type();332 if ( CodeGen::isConstructor( funcDecl->get_name() ) && ftype->parameters.size() == 2 ) { 333 Type * t1 = getPointerBase( ftype->parameters.front()->get_type() ); 334 Type * t2 = ftype->parameters.back()->get_type(); 336 335 assert( t1 ); 337 336 … … 366 365 ImplicitCtorDtorStmt * stmt = genCtorDtor( fname, var, cpArg ); 367 366 ExprStmt * exprStmt = strict_dynamic_cast< ExprStmt * >( stmt->get_callStmt() ); 368 Expression * untyped = exprStmt->get_expr(); 367 Expression * resolved = exprStmt->expr; 368 exprStmt->expr = nullptr; // take ownership of expr 369 369 370 370 // resolve copy constructor 371 371 // should only be one alternative for copy ctor and dtor expressions, since all arguments are fixed 372 372 // (VariableExpr and already resolved expression) 373 CP_CTOR_PRINT( std::cerr << "ResolvingCtorDtor " << untyped << std::endl; )374 Expression * resolved = ResolvExpr::findVoidExpression( untyped, indexer );373 CP_CTOR_PRINT( std::cerr << "ResolvingCtorDtor " << resolved << std::endl; ) 374 ResolvExpr::findVoidExpression( resolved, indexer ); 375 375 assert( resolved ); 376 376 if ( resolved->get_env() ) { … … 380 380 resolved->set_env( nullptr ); 381 381 } // if 382 383 382 delete stmt; 384 383 return resolved; 385 384 } 386 385 387 void ResolveCopyCtors::copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr ) {386 void ResolveCopyCtors::copyConstructArg( Expression *& arg, ImplicitCopyCtorExpr * impCpCtorExpr, Type * formal ) { 388 387 static UniqueName tempNamer("_tmp_cp"); 389 388 assert( env ); 390 389 CP_CTOR_PRINT( std::cerr << "Type Substitution: " << *env << std::endl; ) 391 assert( arg-> has_result());392 Type * result = arg-> get_result();390 assert( arg->result ); 391 Type * result = arg->result; 393 392 if ( skipCopyConstruct( result ) ) return; // skip certain non-copyable types 394 393 395 // type may involve type variables, so apply type substitution to get temporary variable's actual type 394 // type may involve type variables, so apply type substitution to get temporary variable's actual type. 395 // Use applyFree so that types bound in function pointers are not substituted, e.g. in forall(dtype T) void (*)(T). 396 396 result = result->clone(); 397 env->apply ( result );397 env->applyFree( result ); 398 398 ObjectDecl * tmp = ObjectDecl::newObject( "__tmp", result, nullptr ); 399 399 tmp->get_type()->set_const( false ); … … 406 406 // if the chosen constructor is intrinsic, the copy is unnecessary, so 407 407 // don't create the temporary and don't call the copy constructor 408 VariableExpr * function = dynamic_cast< VariableExpr * >( appExpr->get_function() ); 409 assert( function ); 410 if ( function->get_var()->get_linkage() == LinkageSpec::Intrinsic ) return; 408 VariableExpr * function = strict_dynamic_cast< VariableExpr * >( appExpr->function ); 409 if ( function->var->linkage == LinkageSpec::Intrinsic ) { 410 // arguments that need to be boxed need a temporary regardless of whether the copy constructor is intrinsic, 411 // so that the object isn't changed inside of the polymorphic function 412 if ( ! GenPoly::needsBoxing( formal, result, impCpCtorExpr->callExpr, env ) ) return; 413 } 411 414 } 412 415 … … 416 419 // replace argument to function call with temporary 417 420 arg = new CommaExpr( cpCtor, new VariableExpr( tmp ) ); 418 impCpCtorExpr-> get_tempDecls().push_back( tmp );419 impCpCtorExpr-> get_dtors().push_front( makeCtorDtor( "^?{}", tmp ) );421 impCpCtorExpr->tempDecls.push_back( tmp ); 422 impCpCtorExpr->dtors.push_front( makeCtorDtor( "^?{}", tmp ) ); 420 423 } 421 424 … … 427 430 CP_CTOR_PRINT( std::cerr << "ResolveCopyCtors: " << impCpCtorExpr << std::endl; ) 428 431 429 ApplicationExpr * appExpr = impCpCtorExpr-> get_callExpr();432 ApplicationExpr * appExpr = impCpCtorExpr->callExpr; 430 433 431 434 // take each argument and attempt to copy construct it. 432 for ( Expression * & arg : appExpr->get_args() ) { 433 copyConstructArg( arg, impCpCtorExpr ); 435 FunctionType * ftype = GenPoly::getFunctionType( appExpr->function->result ); 436 assert( ftype ); 437 auto & params = ftype->parameters; 438 auto iter = params.begin(); 439 for ( Expression * & arg : appExpr->args ) { 440 Type * formal = nullptr; 441 if ( iter != params.end() ) { 442 DeclarationWithType * param = *iter++; 443 formal = param->get_type(); 444 } 445 446 copyConstructArg( arg, impCpCtorExpr, formal ); 434 447 } // for 435 448 … … 437 450 // initialized with the return value and is destructed later 438 451 // xxx - handle named return values? 439 Type * result = appExpr-> get_result();452 Type * result = appExpr->result; 440 453 if ( ! result->isVoid() ) { 441 454 static UniqueName retNamer("_tmp_cp_ret"); … … 443 456 env->apply( result ); 444 457 ObjectDecl * ret = ObjectDecl::newObject( retNamer.newName(), result, nullptr ); 445 ret-> get_type()->set_const( false );446 impCpCtorExpr-> get_returnDecls().push_back( ret );458 ret->type->set_const( false ); 459 impCpCtorExpr->returnDecls.push_back( ret ); 447 460 CP_CTOR_PRINT( std::cerr << "makeCtorDtor for a return" << std::endl; ) 448 461 if ( ! dynamic_cast< ReferenceType * >( result ) ) { … … 551 564 Expression * retExpr = new CommaExpr( assign, new VariableExpr( returnDecl ) ); 552 565 // move env from callExpr to retExpr 553 retExpr->set_env( callExpr->get_env() ); 554 callExpr->set_env( nullptr ); 566 std::swap( retExpr->env, callExpr->env ); 555 567 return retExpr; 556 568 } else { … … 754 766 if ( ctorStmt && (ctorCall = isIntrinsicCallExpr( ctorStmt->expr )) && ctorCall->get_args().size() == 2 ) { 755 767 // clean up intrinsic copy constructor calls by making them into SingleInits 756 objDecl->init = new SingleInit( ctorCall->args.back() ); 768 Expression * ctorArg = ctorCall->args.back(); 769 std::swap( ctorArg->env, ctorCall->env ); 770 objDecl->init = new SingleInit( ctorArg ); 771 757 772 ctorCall->args.pop_back(); 758 773 } else { … … 822 837 GuardValue( labelVars ); 823 838 labelVars.clear(); 839 // LabelFinder does not recurse into FunctionDecl, so need to visit 840 // its children manually. 824 841 maybeAccept( funcDecl->type, finder ); 825 842 maybeAccept( funcDecl->statements, finder ); … … 1079 1096 } 1080 1097 1081 DeclarationWithType * MutatingResolver::mutate( ObjectDecl * objectDecl ) {1098 DeclarationWithType * MutatingResolver::mutate( ObjectDecl * objectDecl ) { 1082 1099 // add object to the indexer assumes that there will be no name collisions 1083 1100 // in generated code. If this changes, add mutate methods for entities with … … 1087 1104 } 1088 1105 1089 Expression* MutatingResolver::mutate( UntypedExpr *untypedExpr ) { 1090 return strict_dynamic_cast< ApplicationExpr * >( ResolvExpr::findVoidExpression( untypedExpr, indexer ) ); 1106 Expression * MutatingResolver::mutate( UntypedExpr * untypedExpr ) { 1107 Expression * newExpr = untypedExpr; 1108 ResolvExpr::findVoidExpression( newExpr, indexer ); 1109 return newExpr; 1091 1110 } 1092 1111 … … 1094 1113 static UniqueName tempNamer( "_tmp_ctor_expr" ); 1095 1114 // xxx - is the size check necessary? 1096 assert( ctorExpr-> has_result()&& ctorExpr->get_result()->size() == 1 );1115 assert( ctorExpr->result && ctorExpr->get_result()->size() == 1 ); 1097 1116 1098 1117 // xxx - ideally we would reuse the temporary generated from the copy constructor passes from within firstArg if it exists and not generate a temporary if it's unnecessary. … … 1113 1132 1114 1133 // resolve assignment and dispose of new env 1115 Expression * resolvedAssign = ResolvExpr::findVoidExpression( assign, indexer ); 1116 delete resolvedAssign->env; 1117 resolvedAssign->env = nullptr; 1118 delete assign; 1134 ResolvExpr::findVoidExpression( assign, indexer ); 1135 delete assign->env; 1136 assign->env = nullptr; 1119 1137 1120 1138 // for constructor expr: … … 1125 1143 // T & tmp; 1126 1144 // &tmp = &x, ?{}(tmp), tmp 1127 CommaExpr * commaExpr = new CommaExpr( resolvedAssign, new CommaExpr( callExpr, new VariableExpr( tmp ) ) );1145 CommaExpr * commaExpr = new CommaExpr( assign, new CommaExpr( callExpr, new VariableExpr( tmp ) ) ); 1128 1146 commaExpr->set_env( env ); 1129 1147 return commaExpr; -
src/InitTweak/GenInit.cc
rb96ec83 r6840e7c 85 85 // should not have a ConstructorInit generated. 86 86 87 bool isManaged( ObjectDecl * objDecl ) const ; // determine if object is managed 88 bool isManaged( Type * type ) const; // determine if type is managed 89 void handleDWT( DeclarationWithType * dwt ); // add type to managed if ctor/dtor 90 GenPoly::ScopedSet< std::string > managedTypes; 87 ManagedTypes managedTypes; 91 88 bool inFunction = false; 92 89 }; … … 129 126 // hands off if the function returns a reference - we don't want to allocate a temporary if a variable's address 130 127 // is being returned 131 if ( returnStmt-> get_expr()&& returnVals.size() == 1 && isConstructable( returnVals.front()->get_type() ) ) {128 if ( returnStmt->expr && returnVals.size() == 1 && isConstructable( returnVals.front()->get_type() ) ) { 132 129 // explicitly construct the return value using the return expression and the retVal object 133 assertf( returnVals.front()->get_name() != "", "Function %s has unnamed return value\n", funcName.c_str() ); 134 135 stmtsToAddBefore.push_back( genCtorDtor( "?{}", dynamic_cast< ObjectDecl *>( returnVals.front() ), returnStmt->get_expr() ) ); 130 assertf( returnVals.front()->name != "", "Function %s has unnamed return value\n", funcName.c_str() ); 131 132 ObjectDecl * retVal = strict_dynamic_cast< ObjectDecl * >( returnVals.front() ); 133 if ( VariableExpr * varExpr = dynamic_cast< VariableExpr * >( returnStmt->expr ) ) { 134 // return statement has already been mutated - don't need to do it again 135 if ( varExpr->var == retVal ) return; 136 } 137 stmtsToAddBefore.push_back( genCtorDtor( "?{}", retVal, returnStmt->get_expr() ) ); 136 138 137 139 // return the retVal object 138 returnStmt-> set_expr( new VariableExpr( returnVals.front()) );140 returnStmt->expr = new VariableExpr( returnVals.front() ); 139 141 } // if 140 142 } … … 199 201 } 200 202 201 bool CtorDtor::isManaged( Type * type ) const {203 bool ManagedTypes::isManaged( Type * type ) const { 202 204 // references are never constructed 203 205 if ( dynamic_cast< ReferenceType * >( type ) ) return false; … … 215 217 } 216 218 217 bool CtorDtor::isManaged( ObjectDecl * objDecl ) const {219 bool ManagedTypes::isManaged( ObjectDecl * objDecl ) const { 218 220 Type * type = objDecl->get_type(); 219 221 while ( ArrayType * at = dynamic_cast< ArrayType * >( type ) ) { … … 223 225 } 224 226 225 void CtorDtor::handleDWT( DeclarationWithType * dwt ) {227 void ManagedTypes::handleDWT( DeclarationWithType * dwt ) { 226 228 // if this function is a user-defined constructor or destructor, mark down the type as "managed" 227 229 if ( ! LinkageSpec::isOverridable( dwt->get_linkage() ) && CodeGen::isCtorDtor( dwt->get_name() ) ) { … … 233 235 } 234 236 } 237 238 void ManagedTypes::handleStruct( StructDecl * aggregateDecl ) { 239 // don't construct members, but need to take note if there is a managed member, 240 // because that means that this type is also managed 241 for ( Declaration * member : aggregateDecl->get_members() ) { 242 if ( ObjectDecl * field = dynamic_cast< ObjectDecl * >( member ) ) { 243 if ( isManaged( field ) ) { 244 StructInstType inst( Type::Qualifiers(), aggregateDecl ); 245 managedTypes.insert( SymTab::Mangler::mangle( &inst ) ); 246 break; 247 } 248 } 249 } 250 } 251 252 void ManagedTypes::beginScope() { managedTypes.beginScope(); } 253 void ManagedTypes::endScope() { managedTypes.endScope(); } 235 254 236 255 ImplicitCtorDtorStmt * genCtorDtor( const std::string & fname, ObjectDecl * objDecl, Expression * arg ) { … … 277 296 278 297 void CtorDtor::previsit( ObjectDecl * objDecl ) { 279 handleDWT( objDecl );298 managedTypes.handleDWT( objDecl ); 280 299 // hands off if @=, extern, builtin, etc. 281 300 // even if unmanaged, try to construct global or static if initializer is not constexpr, since this is not legal C 282 if ( tryConstruct( objDecl ) && ( isManaged( objDecl ) || ((! inFunction || objDecl->get_storageClasses().is_static ) && ! isConstExpr( objDecl->get_init() ) ) ) ) {301 if ( tryConstruct( objDecl ) && ( managedTypes.isManaged( objDecl ) || ((! inFunction || objDecl->get_storageClasses().is_static ) && ! isConstExpr( objDecl->get_init() ) ) ) ) { 283 302 // constructed objects cannot be designated 284 303 if ( isDesignated( objDecl->get_init() ) ) throw SemanticError( "Cannot include designations in the initializer for a managed Object. If this is really what you want, then initialize with @=.\n", objDecl ); … … 295 314 inFunction = true; 296 315 297 handleDWT( functionDecl );316 managedTypes.handleDWT( functionDecl ); 298 317 299 318 GuardScope( managedTypes ); … … 301 320 for ( auto & tyDecl : functionDecl->get_functionType()->get_forall() ) { 302 321 for ( DeclarationWithType *& assertion : tyDecl->get_assertions() ) { 303 handleDWT( assertion );322 managedTypes.handleDWT( assertion ); 304 323 } 305 324 } … … 311 330 visit_children = false; // do not try to construct and destruct aggregate members 312 331 313 // don't construct members, but need to take note if there is a managed member, 314 // because that means that this type is also managed 315 for ( Declaration * member : aggregateDecl->get_members() ) { 316 if ( ObjectDecl * field = dynamic_cast< ObjectDecl * >( member ) ) { 317 if ( isManaged( field ) ) { 318 StructInstType inst( Type::Qualifiers(), aggregateDecl ); 319 managedTypes.insert( SymTab::Mangler::mangle( &inst ) ); 320 break; 321 } 322 } 323 } 332 managedTypes.handleStruct( aggregateDecl ); 324 333 } 325 334 -
src/InitTweak/GenInit.h
rb96ec83 r6840e7c 16 16 #pragma once 17 17 18 #include <list> // for list19 #include <string> // for string18 #include <list> // for list 19 #include <string> // for string 20 20 21 #include "SynTree/SynTree.h" // for Visitor Nodes 21 #include "SynTree/SynTree.h" // for Visitor Nodes 22 23 #include "GenPoly/ScopedSet.h" // for ScopedSet 22 24 23 25 namespace InitTweak { … … 33 35 /// creates an appropriate ConstructorInit node which contains a constructor, destructor, and C-initializer 34 36 ConstructorInit * genCtorInit( ObjectDecl * objDecl ); 37 38 class ManagedTypes { 39 public: 40 bool isManaged( ObjectDecl * objDecl ) const ; // determine if object is managed 41 bool isManaged( Type * type ) const; // determine if type is managed 42 43 void handleDWT( DeclarationWithType * dwt ); // add type to managed if ctor/dtor 44 void handleStruct( StructDecl * aggregateDecl ); // add type to managed if child is managed 45 46 void beginScope(); 47 void endScope(); 48 private: 49 GenPoly::ScopedSet< std::string > managedTypes; 50 }; 35 51 } // namespace 36 52 -
src/InitTweak/InitTweak.cc
rb96ec83 r6840e7c 168 168 deleteAll( indices ); 169 169 indices.clear(); 170 } 171 172 bool InitExpander::addReference() { 173 bool added = false; 174 for ( Expression *& expr : cur ) { 175 expr = new AddressExpr( expr ); 176 added = true; 177 } 178 return added; 170 179 } 171 180 … … 270 279 } 271 280 272 Type * getT hisType( FunctionType * ftype ) {273 assertf( ftype, "getT hisType: nullptr ftype" );274 ObjectDecl * thisParam = get ThisParam( ftype );281 Type * getTypeofThis( FunctionType * ftype ) { 282 assertf( ftype, "getTypeofThis: nullptr ftype" ); 283 ObjectDecl * thisParam = getParamThis( ftype ); 275 284 ReferenceType * refType = strict_dynamic_cast< ReferenceType * >( thisParam->type ); 276 285 return refType->base; 277 286 } 278 287 279 ObjectDecl * get ThisParam( FunctionType * ftype ) {280 assertf( ftype, "get ThisParam: nullptr ftype" );288 ObjectDecl * getParamThis( FunctionType * ftype ) { 289 assertf( ftype, "getParamThis: nullptr ftype" ); 281 290 auto & params = ftype->parameters; 282 assertf( ! params.empty(), "get ThisParam: ftype with 0 parameters: %s", toString( ftype ).c_str() );291 assertf( ! params.empty(), "getParamThis: ftype with 0 parameters: %s", toString( ftype ).c_str() ); 283 292 return strict_dynamic_cast< ObjectDecl * >( params.front() ); 284 293 } … … 353 362 assert( expr ); 354 363 if ( VariableExpr * varExpr = dynamic_cast< VariableExpr * >( expr ) ) { 355 return varExpr-> get_var();364 return varExpr->var; 356 365 } else if ( MemberExpr * memberExpr = dynamic_cast< MemberExpr * >( expr ) ) { 357 return memberExpr-> get_member();366 return memberExpr->member; 358 367 } else if ( CastExpr * castExpr = dynamic_cast< CastExpr * >( expr ) ) { 359 return getCalledFunction( castExpr-> get_arg());368 return getCalledFunction( castExpr->arg ); 360 369 } else if ( UntypedExpr * untypedExpr = dynamic_cast< UntypedExpr * >( expr ) ) { 361 370 return handleDerefCalledFunction( untypedExpr ); … … 363 372 return handleDerefCalledFunction( appExpr ); 364 373 } else if ( AddressExpr * addrExpr = dynamic_cast< AddressExpr * >( expr ) ) { 365 return getCalledFunction( addrExpr->get_arg() ); 374 return getCalledFunction( addrExpr->arg ); 375 } else if ( CommaExpr * commaExpr = dynamic_cast< CommaExpr * >( expr ) ) { 376 return getCalledFunction( commaExpr->arg2 ); 366 377 } 367 378 return nullptr; … … 578 589 FunctionDecl * isCopyFunction( Declaration * decl, const std::string & fname ) { 579 590 FunctionDecl * function = dynamic_cast< FunctionDecl * >( decl ); 580 if ( ! function ) return 0;581 if ( function-> get_name() != fname ) return 0;582 FunctionType * ftype = function-> get_functionType();583 if ( ftype-> get_parameters().size() != 2 ) return 0;591 if ( ! function ) return nullptr; 592 if ( function->name != fname ) return nullptr; 593 FunctionType * ftype = function->type; 594 if ( ftype->parameters.size() != 2 ) return nullptr; 584 595 585 596 Type * t1 = getPointerBase( ftype->get_parameters().front()->get_type() ); 586 Type * t2 = ftype-> get_parameters().back()->get_type();597 Type * t2 = ftype->parameters.back()->get_type(); 587 598 assert( t1 ); 588 599 … … 604 615 } 605 616 FunctionDecl * isDefaultConstructor( Declaration * decl ) { 606 if ( isConstructor( decl-> get_name()) ) {617 if ( isConstructor( decl->name ) ) { 607 618 if ( FunctionDecl * func = dynamic_cast< FunctionDecl * >( decl ) ) { 608 if ( func-> get_functionType()->get_parameters().size() == 1 ) {619 if ( func->type->parameters.size() == 1 ) { 609 620 return func; 610 621 } -
src/InitTweak/InitTweak.h
rb96ec83 r6840e7c 31 31 32 32 /// returns the base type of the first parameter to a constructor/destructor/assignment function 33 Type * getT hisType( FunctionType * ftype );33 Type * getTypeofThis( FunctionType * ftype ); 34 34 35 35 /// returns the first parameter of a constructor/destructor/assignment function 36 ObjectDecl * get ThisParam( FunctionType * ftype );36 ObjectDecl * getParamThis( FunctionType * ftype ); 37 37 38 38 /// transform Initializer into an argument list that can be passed to a call expression … … 105 105 void addArrayIndex( Expression * index, Expression * dimension ); 106 106 void clearArrayIndices(); 107 bool addReference(); 107 108 108 109 class ExpanderImpl;
Note:
See TracChangeset
for help on using the changeset viewer.