Changeset d3e4d6c for src/Tuples


Ignore:
Timestamp:
Aug 23, 2017, 6:22:07 PM (9 years ago)
Author:
Peter A. Buhr <pabuhr@…>
Branches:
ADT, aaron-thesis, arm-eh, ast-experimental, cleanup-dtors, deferred_resn, demangler, enum, forall-pointer-decay, jacob/cs343-translation, jenkins-sandbox, master, new-ast, new-ast-unique-expr, new-env, no_list, persistent-indexer, pthread-emulation, qualifiedEnum, resolv-new, stuck-waitfor-destruct, with_gc
Children:
87e08e24, cb811ac
Parents:
9f07232 (diff), bd37119 (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.
Message:

Merge branch 'master' of plg2:software/cfa/cfa-cc

Location:
src/Tuples
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • src/Tuples/Explode.cc

    r9f07232 rd3e4d6c  
    1515
    1616#include "Explode.h"
     17#include <list>                  // for list
    1718
    18 #include <list>               // for list
    19 
    20 #include "SynTree/Mutator.h"  // for Mutator
     19#include "SynTree/Mutator.h"     // for Mutator
     20#include "Common/PassVisitor.h"  // for PassVisitor
    2121
    2222namespace Tuples {
    2323        namespace {
    24                 struct AddrExploder : public Mutator {
     24                // remove one level of reference from a reference type -- may be useful elsewhere.
     25                Type * getReferenceBase( Type * t ) {
     26                        if ( ReferenceType * refType = dynamic_cast<ReferenceType *>( t ) ) {
     27                                return refType->get_base();
     28                        } else {
     29                                // for the moment, I want to know immediately if a non-reference type is ever passed in here.
     30                                assertf( false, "getReferenceBase for non-ref: %s", toString( refType ).c_str() );
     31                                return nullptr;
     32                        }
     33                }
     34
     35                struct CastExploder {
     36                        bool castAdded = false;
    2537                        bool foundUniqueExpr = false;
    26                         Expression * applyAddr( Expression * expr, bool first = true ) {
     38                        Expression * applyCast( Expression * expr, bool first = true ) {
    2739                                if ( TupleExpr * tupleExpr = dynamic_cast< TupleExpr * >( expr ) ){
    2840                                        foundUniqueExpr = true;
    2941                                        std::list< Expression * > exprs;
    3042                                        for ( Expression *& expr : tupleExpr->get_exprs() ) {
    31                                                 // move & into tuple exprs
    32                                                 exprs.push_back( applyAddr( expr, false ) );
     43                                                // move cast into tuple exprs
     44                                                exprs.push_back( applyCast( expr, false ) );
    3345                                        }
    34                                         // want the top-level expression to be address-taken, but not nested
     46                                        // want the top-level expression to be cast to reference type, but not nested
    3547                                        // tuple expressions
    3648                                        if ( first ) {
    37                                                 return new AddressExpr( new TupleExpr( exprs ) );
     49                                                castAdded = true;
     50                                                Expression * tupleExpr = new TupleExpr( exprs );
     51                                                return new CastExpr( tupleExpr, new ReferenceType( Type::Qualifiers(), tupleExpr->result->clone() ) );
    3852                                        } else {
    3953                                                return new TupleExpr( exprs );
    4054                                        }
    4155                                }
    42                                 // anything else should be address-taken as normal
    43                                 return new AddressExpr( expr->clone() );
     56                                if ( dynamic_cast<ReferenceType*>( expr->result ) ) {
     57                                        // don't need to cast reference type to another reference type
     58                                        return expr->clone();
     59                                } else {
     60                                        // anything else should be cast to reference as normal
     61                                        castAdded = true;
     62                                        return new CastExpr( expr->clone(), new ReferenceType( Type::Qualifiers(), expr->result->clone() ) );
     63                                }
    4464                        }
    4565
    46                         virtual Expression * mutate( UniqueExpr * uniqueExpr ) {
    47                                 // move & into unique expr so that the unique expr has type T* rather than
     66                        Expression * postmutate( UniqueExpr * uniqueExpr ) {
     67                                // move cast into unique expr so that the unique expr has type T& rather than
    4868                                // type T. In particular, this transformation helps with generating the
    49                                 // correct code for address-taken member tuple expressions, since the result
    50                                 // should now be a tuple of addresses rather than the address of a tuple.
     69                                // correct code for reference-cast member tuple expressions, since the result
     70                                // should now be a tuple of references rather than a reference to a tuple.
    5171                                // Still, this code is a bit awkward, and could use some improvement.
    52                                 if ( dynamic_cast< AddressExpr * > ( uniqueExpr->get_expr() ) ) {
    53                                         // this unique expression has already been mutated or otherwise shouldn't be (can't take the address-of an address-of expression)
    54                                         return uniqueExpr;
     72                                UniqueExpr * newUniqueExpr = new UniqueExpr( applyCast( uniqueExpr->get_expr() ), uniqueExpr->get_id() );
     73                                delete uniqueExpr;
     74                                if ( castAdded ) {
     75                                        // if a cast was added by applyCast, then unique expr now has one more layer of reference
     76                                        // than it had coming into this function. To ensure types still match correctly, need to cast
     77                                        //  to reference base so that outer expressions are still correct.
     78                                        castAdded = false;
     79                                        Type * toType = getReferenceBase( newUniqueExpr->result );
     80                                        return new CastExpr( newUniqueExpr, toType->clone() );
    5581                                }
    56                                 UniqueExpr * newUniqueExpr = new UniqueExpr( applyAddr( uniqueExpr->get_expr() ), uniqueExpr->get_id() );
    57                                 delete uniqueExpr;
    58                                 UntypedExpr * deref = UntypedExpr::createDeref( Mutator::mutate( newUniqueExpr ) );
    59                                 return deref;
     82                                return newUniqueExpr;
    6083                        }
    6184
    62                         virtual Expression * mutate( TupleIndexExpr * tupleExpr ) {
     85
     86                        Expression * postmutate( TupleIndexExpr * tupleExpr ) {
    6387                                // tuple index expr needs to be rebuilt to ensure that the type of the
    6488                                // field is consistent with the type of the tuple expr, since the field
    65                                 // may have changed from type T to T*.
    66                                 Expression * expr = tupleExpr->get_tuple()->acceptMutator( *this );
     89                                // may have changed from type T to T&.
     90                                Expression * expr = tupleExpr->get_tuple();
    6791                                tupleExpr->set_tuple( nullptr );
    6892                                TupleIndexExpr * ret = new TupleIndexExpr( expr, tupleExpr->get_index() );
     
    7397        } // namespace
    7498
    75         Expression * distributeAddr( Expression * expr ) {
    76                 AddrExploder addrExploder;
    77                 expr = expr->acceptMutator( addrExploder );
    78                 if ( ! addrExploder.foundUniqueExpr ) {
    79                         expr = new AddressExpr( expr );
     99        Expression * distributeReference( Expression * expr ) {
     100                PassVisitor<CastExploder> exploder;
     101                expr = expr->acceptMutator( exploder );
     102                if ( ! exploder.pass.foundUniqueExpr ) {
     103                        // if a UniqueExpr was found, then the cast has already been added inside the UniqueExpr as appropriate
     104                        expr = new CastExpr( expr, new ReferenceType( Type::Qualifiers(), expr->result->clone() ) );
    80105                }
    81106                return expr;
  • src/Tuples/Explode.h

    r9f07232 rd3e4d6c  
    2828
    2929namespace Tuples {
    30         /// helper function used by explode to properly distribute
    31         /// '&' across a tuple expression
    32         Expression * distributeAddr( Expression * expr );
     30        Expression * distributeReference( Expression * );
    3331
    3432        /// helper function used by explode
     
    3634        void explodeUnique( Expression * expr, const ResolvExpr::Alternative & alt, const SymTab::Indexer & indexer, OutputIterator out, bool isTupleAssign ) {
    3735                if ( isTupleAssign ) {
    38                         // tuple assignment needs AddressExprs to be recursively exploded to easily get at all of the components
    39                         if ( AddressExpr * addrExpr = dynamic_cast< AddressExpr * >( expr ) ) {
     36                        // tuple assignment needs CastExprs to be recursively exploded to easily get at all of the components
     37                        if ( CastExpr * castExpr = dynamic_cast< CastExpr * >( expr ) ) {
    4038                                ResolvExpr::AltList alts;
    41                                 explodeUnique( addrExpr->get_arg(), alt, indexer, back_inserter( alts ), isTupleAssign );
     39                                explodeUnique( castExpr->get_arg(), alt, indexer, back_inserter( alts ), isTupleAssign );
    4240                                for ( ResolvExpr::Alternative & alt : alts ) {
    43                                         // distribute '&' over all components
    44                                         alt.expr = distributeAddr( alt.expr );
     41                                        // distribute reference cast over all components
     42                                        alt.expr = distributeReference( alt.expr );
    4543                                        *out++ = alt;
    4644                                }
     
    4947                        }
    5048                }
    51                 Type * res = expr->get_result();
     49                Type * res = expr->get_result()->stripReferences();
    5250                if ( TupleType * tupleType = dynamic_cast< TupleType * > ( res ) ) {
    5351                        if ( TupleExpr * tupleExpr = dynamic_cast< TupleExpr * >( expr ) ) {
     
    5755                                }
    5856                        } else {
    59                                 // tuple type, but not tuple expr - recursively index into its components
     57                                // tuple type, but not tuple expr - recursively index into its components.
     58                                // if expr type is reference, convert to value type
    6059                                Expression * arg = expr->clone();
    61                                 if ( Tuples::maybeImpure( arg ) && ! dynamic_cast< UniqueExpr * >( arg ) ) {
     60                                if ( Tuples::maybeImpureIgnoreUnique( arg ) ) {
    6261                                        // expressions which may contain side effects require a single unique instance of the expression.
    6362                                        arg = new UniqueExpr( arg );
     63                                }
     64                                // cast reference to value type to facilitate further explosion
     65                                if ( dynamic_cast<ReferenceType *>( arg->get_result() ) ) {
     66                                        arg = new CastExpr( arg, tupleType->clone() );
    6467                                }
    6568                                for ( unsigned int i = 0; i < tupleType->size(); i++ ) {
  • src/Tuples/TupleAssignment.cc

    r9f07232 rd3e4d6c  
    2121#include <string>                          // for string
    2222
     23#include "CodeGen/OperatorTable.h"
    2324#include "Common/UniqueName.h"             // for UniqueName
    2425#include "Common/utility.h"                // for zipWith
     
    8485                if ( ! expr ) return false;
    8586                assert( expr->has_result() );
    86                 return dynamic_cast< TupleType * >( expr->get_result() );
     87                return dynamic_cast< TupleType * >( expr->get_result()->stripReferences() );
    8788        }
    8889
     
    9697        }
    9798
    98         bool pointsToTuple( Expression *expr ) {
     99        bool refToTuple( Expression *expr ) {
     100                assert( expr->get_result() );
    99101                // also check for function returning tuple of reference types
    100102                if ( CastExpr * castExpr = dynamic_cast< CastExpr * >( expr ) ) {
    101                         return pointsToTuple( castExpr->get_arg() );
    102                 } else if ( AddressExpr *addr = dynamic_cast< AddressExpr * >( expr) ) {
    103                         return isTuple( addr->get_arg() );
     103                        return refToTuple( castExpr->get_arg() );
     104                } else {
     105                        return isTuple( expr );
    104106                }
    105107                return false;
     
    116118        void TupleAssignSpotter::spot( UntypedExpr * expr, const std::list<ResolvExpr::AltList> &possibilities ) {
    117119                if (  NameExpr *op = dynamic_cast< NameExpr * >(expr->get_function()) ) {
    118                         if ( InitTweak::isCtorDtorAssign( op->get_name() ) ) {
     120                        if ( CodeGen::isCtorDtorAssign( op->get_name() ) ) {
    119121                                fname = op->get_name();
    120122                                for ( std::list<ResolvExpr::AltList>::const_iterator ali = possibilities.begin(); ali != possibilities.end(); ++ali ) {
    121123                                        if ( ali->size() == 0 ) continue; // AlternativeFinder will natrually handle this case, if it's legal
    122                                         if ( ali->size() <= 1 && InitTweak::isAssignment( op->get_name() ) ) {
     124                                        if ( ali->size() <= 1 && CodeGen::isAssignment( op->get_name() ) ) {
    123125                                                // what does it mean if an assignment takes 1 argument? maybe someone defined such a function, in which case AlternativeFinder will naturally handle it
    124126                                                continue;
     
    129131                                        const ResolvExpr::Alternative & alt1 = ali->front();
    130132                                        auto begin = std::next(ali->begin(), 1), end = ali->end();
    131                                         if ( pointsToTuple(alt1.expr) ) {
     133                                        if ( refToTuple(alt1.expr) ) {
    132134                                                if ( isMultAssign( begin, end ) ) {
    133135                                                        matcher.reset( new MultipleAssignMatcher( *this, *ali ) );
     
    187189
    188190                ResolvExpr::Alternative lhsAlt = alts.front();
    189                 // peel off the cast that exists on ctor/dtor expressions
    190                 bool isCast = false;
    191                 if ( CastExpr * castExpr = dynamic_cast< CastExpr * >( lhsAlt.expr ) ) {
    192                         lhsAlt.expr = castExpr->get_arg();
    193                         castExpr->set_arg( nullptr );
    194                         delete castExpr;
    195                         isCast = true;
     191                // explode is aware of casts - ensure every LHS expression is sent into explode with a reference cast
     192                if ( ! dynamic_cast< CastExpr * >( lhsAlt.expr ) ) {
     193                        lhsAlt.expr = new CastExpr( lhsAlt.expr, new ReferenceType( Type::Qualifiers(), lhsAlt.expr->get_result()->clone() ) );
    196194                }
    197195
     
    199197                explode( lhsAlt, spotter.currentFinder.get_indexer(), back_inserter(lhs), true );
    200198
    201                 // and finally, re-add the cast to each lhs expr, so that qualified tuple fields can be constructed
    202                 if ( isCast ) {
    203                         for ( ResolvExpr::Alternative & alt : lhs ) {
    204                                 Expression *& expr = alt.expr;
    205                                 Type * castType = expr->get_result()->clone();
    206                                 Type * type = InitTweak::getPointerBase( castType );
    207                                 assert( type );
    208                                 type->get_qualifiers() -= Type::Qualifiers( Type::Const | Type::Volatile | Type::Restrict | Type::Atomic );
    209                                 type->set_lvalue( true ); // xxx - might not need this
    210                                 expr = new CastExpr( expr, castType );
     199                for ( ResolvExpr::Alternative & alt : lhs ) {
     200                        // every LHS value must be a reference - some come in with a cast expression, if it doesn't just cast to reference here.
     201                        if ( ! dynamic_cast< ReferenceType * >( alt.expr->get_result() ) ) {
     202                                alt.expr = new CastExpr( alt.expr, new ReferenceType( Type::Qualifiers(), alt.expr->get_result()->clone() ) );
    211203                        }
    212204                }
     
    228220                assert( left );
    229221                std::list< Expression * > args;
    230                 args.push_back( new AddressExpr( UntypedExpr::createDeref( new VariableExpr( left ) ) ) );
     222                args.push_back( new VariableExpr( left ) );
    231223                // args.push_back( new AddressExpr( new VariableExpr( left ) ) );
    232224                if ( right ) args.push_back( new VariableExpr( right ) );
     
    248240                assert( expr->has_result() && ! expr->get_result()->isVoid() );
    249241                ObjectDecl * ret = new ObjectDecl( namer.newName(), Type::StorageClasses(), LinkageSpec::Cforall, nullptr, expr->get_result()->clone(), new SingleInit( expr->clone() ) );
    250                 ConstructorInit * ctorInit = InitTweak::genCtorInit( ret );
    251                 ret->set_init( ctorInit );
    252                 ResolvExpr::resolveCtorInit( ctorInit, spotter.currentFinder.get_indexer() ); // resolve ctor/dtors for the new object
    253                 EnvRemover rm; // remove environments from subexpressions of StmtExprs
    254                 ctorInit->accept( rm );
     242                // if expression type is a reference, don't need to construct anything, a simple initializer is sufficient.
     243                if ( ! dynamic_cast< ReferenceType * >( expr->get_result() ) ) {
     244                        ConstructorInit * ctorInit = InitTweak::genCtorInit( ret );
     245                        ret->set_init( ctorInit );
     246                        ResolvExpr::resolveCtorInit( ctorInit, spotter.currentFinder.get_indexer() ); // resolve ctor/dtors for the new object
     247                        EnvRemover rm; // remove environments from subexpressions of StmtExprs
     248                        ctorInit->accept( rm );
     249                }
    255250                return ret;
    256251        }
  • src/Tuples/TupleExpansion.cc

    r9f07232 rd3e4d6c  
    3737namespace Tuples {
    3838        namespace {
    39                 class MemberTupleExpander final : public Mutator {
    40                 public:
     39                struct MemberTupleExpander final : public Mutator {
    4140                        typedef Mutator Parent;
    4241                        using Parent::mutate;
     
    4544                };
    4645
    47                 class UniqueExprExpander final : public GenPoly::DeclMutator {
    48                 public:
    49                         typedef GenPoly::DeclMutator Parent;
    50                         using Parent::mutate;
    51 
    52                         virtual Expression * mutate( UniqueExpr * unqExpr ) override;
     46                struct UniqueExprExpander final : public WithDeclsToAdd {
     47                        Expression * postmutate( UniqueExpr * unqExpr );
    5348
    5449                        std::map< int, Expression * > decls; // not vector, because order added may not be increasing order
     
    6156                };
    6257
    63                 class TupleAssignExpander : public Mutator {
    64                 public:
    65                         typedef Mutator Parent;
    66                         using Parent::mutate;
    67 
    68                         virtual Expression * mutate( TupleAssignExpr * tupleExpr );
     58                struct TupleAssignExpander {
     59                        Expression * postmutate( TupleAssignExpr * tupleExpr );
    6960                };
    7061
     
    7970                };
    8071
    81                 class TupleIndexExpander {
    82                 public:
     72                struct TupleIndexExpander {
    8373                        Expression * postmutate( TupleIndexExpr * tupleExpr );
    8474                };
    8575
    86                 class TupleExprExpander final : public Mutator {
    87                 public:
    88                         typedef Mutator Parent;
    89                         using Parent::mutate;
    90 
    91                         virtual Expression * mutate( TupleExpr * tupleExpr ) override;
     76                struct TupleExprExpander final {
     77                        Expression * postmutate( TupleExpr * tupleExpr );
    9278                };
    9379        }
     
    9985
    10086        void expandUniqueExpr( std::list< Declaration * > & translationUnit ) {
    101                 UniqueExprExpander unqExpander;
    102                 unqExpander.mutateDeclarationList( translationUnit );
     87                PassVisitor<UniqueExprExpander> unqExpander;
     88                mutateAll( translationUnit, unqExpander );
    10389        }
    10490
    10591        void expandTuples( std::list< Declaration * > & translationUnit ) {
    106                 TupleAssignExpander assnExpander;
     92                PassVisitor<TupleAssignExpander> assnExpander;
    10793                mutateAll( translationUnit, assnExpander );
    10894
     
    11399                mutateAll( translationUnit, idxExpander );
    114100
    115                 TupleExprExpander exprExpander;
     101                PassVisitor<TupleExprExpander> exprExpander;
    116102                mutateAll( translationUnit, exprExpander );
    117103        }
     
    146132                        // aggregate expressions which might be impure must be wrapped in unique expressions
    147133                        // xxx - if there's a member-tuple expression nested in the aggregate, this currently generates the wrong code if a UniqueExpr is not used, and it's purely an optimization to remove the UniqueExpr
    148                         // if ( Tuples::maybeImpure( memberExpr->get_aggregate() ) ) aggr = new UniqueExpr( aggr );
     134                        // if ( Tuples::maybeImpureIgnoreUnique( memberExpr->get_aggregate() ) ) aggr = new UniqueExpr( aggr );
    149135                        aggr = new UniqueExpr( aggr );
    150136                        for ( Expression *& expr : tupleExpr->get_exprs() ) {
     
    164150        }
    165151
    166         Expression * UniqueExprExpander::mutate( UniqueExpr * unqExpr ) {
    167                 unqExpr = safe_dynamic_cast< UniqueExpr * > ( Parent::mutate( unqExpr ) );
     152        Expression * UniqueExprExpander::postmutate( UniqueExpr * unqExpr ) {
    168153                const int id = unqExpr->get_id();
    169154
     
    175160                        if ( unqExpr->get_object() ) {
    176161                                // an object was generated to represent this unique expression -- it should be added to the list of declarations now
    177                                 addDeclaration( unqExpr->get_object() );
     162                                declsToAddBefore.push_back( unqExpr->get_object() );
    178163                                unqExpr->set_object( nullptr );
    179164                                // steal the expr from the unqExpr
     
    189174                        ObjectDecl * finished = new ObjectDecl( toString( "_unq", id, "_finished_" ), Type::StorageClasses(), LinkageSpec::Cforall, nullptr, new BasicType( Type::Qualifiers(), BasicType::Bool ),
    190175                                                                                                        new SingleInit( new ConstantExpr( Constant::from_int( 0 ) ) ) );
    191                         addDeclaration( finished );
     176                        declsToAddBefore.push_back( finished );
    192177                        // (finished ? _unq_expr_N : (_unq_expr_N = <unqExpr->get_expr()>, finished = 1, _unq_expr_N))
    193178                        // This pattern ensures that each unique expression is evaluated once, regardless of evaluation order of the generated C code.
     
    203188        }
    204189
    205         Expression * TupleAssignExpander::mutate( TupleAssignExpr * assnExpr ) {
    206                 assnExpr = safe_dynamic_cast< TupleAssignExpr * >( Parent::mutate( assnExpr ) );
     190        Expression * TupleAssignExpander::postmutate( TupleAssignExpr * assnExpr ) {
    207191                StmtExpr * ret = assnExpr->get_stmtExpr();
    208192                assnExpr->set_stmtExpr( nullptr );
     
    238222                for ( auto p : group_iterate( tupleType->get_types(), decl->get_parameters() ) ) {
    239223                        Type * t = std::get<0>(p);
    240                         TypeDecl * td = std::get<1>(p);
    241224                        newType->get_parameters().push_back( new TypeExpr( t->clone() ) );
    242                         if ( env ) {
    243                                 // add bindings to the type environment.
    244                                 // xxx - This may not be sufficient, it may be necessary to rename type variables on StructInstType?
    245                                 env->add( td->get_name(), t->clone() );
    246                         }
    247225                }
    248226                delete tupleType;
     
    293271        }
    294272
    295         Expression * TupleExprExpander::mutate( TupleExpr * tupleExpr ) {
    296                 // recursively expand sub-tuple-expressions
    297                 tupleExpr = safe_dynamic_cast<TupleExpr *>(Parent::mutate(tupleExpr));
     273        Expression * TupleExprExpander::postmutate( TupleExpr * tupleExpr ) {
    298274                Type * result = tupleExpr->get_result();
    299275                std::list< Expression * > exprs = tupleExpr->get_exprs();
     
    342318                class ImpurityDetector : public Visitor {
    343319                public:
     320                        ImpurityDetector( bool ignoreUnique ) : ignoreUnique( ignoreUnique ) {}
     321
    344322                        typedef Visitor Parent;
    345323                        virtual void visit( ApplicationExpr * appExpr ) {
     
    355333                                maybeImpure = true;
    356334                        }
    357                         virtual void visit( __attribute__((unused)) UntypedExpr * untypedExpr ) { maybeImpure = true; }
     335                        virtual void visit( UntypedExpr * ) { maybeImpure = true; }
     336                        virtual void visit( UniqueExpr * unq ) {
     337                                if ( ignoreUnique ) {
     338                                        // bottom out at unique expression.
     339                                        // The existence of a unique expression doesn't change the purity of an expression.
     340                                        // That is, even if the wrapped expression is impure, the wrapper protects the rest of the expression.
     341                                        return;
     342                                }
     343                                maybeAccept( unq->expr, *this );
     344                        }
     345
    358346                        bool maybeImpure = false;
     347                        bool ignoreUnique;
    359348                };
    360349        } // namespace
    361350
    362351        bool maybeImpure( Expression * expr ) {
    363                 ImpurityDetector detector;
     352                ImpurityDetector detector( false );
     353                expr->accept( detector );
     354                return detector.maybeImpure;
     355        }
     356
     357        bool maybeImpureIgnoreUnique( Expression * expr ) {
     358                ImpurityDetector detector( true );
    364359                expr->accept( detector );
    365360                return detector.maybeImpure;
  • src/Tuples/Tuples.h

    r9f07232 rd3e4d6c  
    4646        /// returns true if the expression may contain side-effects.
    4747        bool maybeImpure( Expression * expr );
     48
     49        /// returns true if the expression may contain side-effect, ignoring the presence of unique expressions.
     50        bool maybeImpureIgnoreUnique( Expression * expr );
    4851} // namespace Tuples
    4952
Note: See TracChangeset for help on using the changeset viewer.