source: src/GenPoly/SpecializeNew.cpp @ 790d835

Last change on this file since 790d835 was d85141f, checked in by Andrew Beach <ajbeach@…>, 13 months ago

Small refactoring of some helper functions to avoid repeating a loop.

  • Property mode set to 100644
File size: 16.2 KB
RevLine 
[9e23b446]1//
2// Cforall Version 1.0.0 Copyright (C) 2015 University of Waterloo
3//
4// The contents of this file are covered under the licence agreement in the
5// file "LICENCE" distributed with Cforall.
6//
[5cf1228]7// SpecializeNew.cpp -- Generate thunks to specialize polymorphic functions.
[9e23b446]8//
9// Author           : Andrew Beach
10// Created On       : Tue Jun  7 13:37:00 2022
11// Last Modified By : Andrew Beach
12// Last Modified On : Tue Jun  7 13:37:00 2022
13// Update Count     : 0
14//
15
16#include "Specialize.h"
17
[bccd70a]18#include "AST/Copy.hpp"                  // for deepCopy
[e01eb4a]19#include "AST/Inspect.hpp"               // for isIntrinsicCallExpr
20#include "AST/Pass.hpp"                  // for Pass
[9e23b446]21#include "AST/TypeEnvironment.hpp"       // for OpenVarSet, AssertionSet
22#include "Common/UniqueName.h"           // for UniqueName
23#include "GenPoly/GenPoly.h"             // for getFunctionType
24#include "ResolvExpr/FindOpenVars.h"     // for findOpenVars
25#include "ResolvExpr/TypeEnvironment.h"  // for FirstOpen, FirstClosed
26
27namespace GenPoly {
28
29namespace {
30
31struct SpecializeCore final :
32                public ast::WithConstTypeSubstitution,
33                public ast::WithDeclsToAdd<>,
34                public ast::WithVisitorRef<SpecializeCore> {
35        std::string paramPrefix = "_p";
36
37        ast::ApplicationExpr * handleExplicitParams(
38                const ast::ApplicationExpr * expr );
39        const ast::Expr * createThunkFunction(
40                const CodeLocation & location,
41                const ast::FunctionType * funType,
42                const ast::Expr * actual,
43                const ast::InferredParams * inferParams );
44        const ast::Expr * doSpecialization(
45                const CodeLocation & location,
46                const ast::Type * formalType,
47                const ast::Expr * actual,
48                const ast::InferredParams * inferParams );
49
50        const ast::Expr * postvisit( const ast::ApplicationExpr * expr );
51        const ast::Expr * postvisit( const ast::CastExpr * expr );
52};
53
54const ast::InferredParams * getInferredParams( const ast::Expr * expr ) {
55        const ast::Expr::InferUnion & inferred = expr->inferred;
56        if ( inferred.hasParams() ) {
57                return &inferred.inferParams();
58        } else {
59                return nullptr;
60        }
61}
62
63// Check if both types have the same structure. The leaf (non-tuple) types
64// don't have to match but the tuples must match.
65bool isTupleStructureMatching( const ast::Type * t0, const ast::Type * t1 ) {
66        const ast::TupleType * tt0 = dynamic_cast<const ast::TupleType *>( t0 );
67        const ast::TupleType * tt1 = dynamic_cast<const ast::TupleType *>( t1 );
68        if ( tt0 && tt1 ) {
69                if ( tt0->size() != tt1->size() ) {
70                        return false;
71                }
72                for ( auto types : group_iterate( tt0->types, tt1->types ) ) {
73                        if ( !isTupleStructureMatching(
74                                        std::get<0>( types ), std::get<1>( types ) ) ) {
75                                return false;
76                        }
77                }
78                return true;
79        }
80        return (!tt0 && !tt1);
81}
82
[d85141f]83// The number of elements in a list, if all tuples had been flattened.
84size_t flatTypeListSize( const std::vector<ast::ptr<ast::Type>> & types ) {
85        size_t sum = 0;
86        for ( const ast::ptr<ast::Type> & type : types ) {
87                if ( const ast::TupleType * tuple = type.as<ast::TupleType>() ) {
88                        sum += flatTypeListSize( tuple->types );
89                } else {
90                        sum += 1;
[9e23b446]91                }
92        }
[d85141f]93        return sum;
[9e23b446]94}
95
96// Find the total number of components in a parameter list.
97size_t functionParameterSize( const ast::FunctionType * type ) {
[d85141f]98        return flatTypeListSize( type->params );
[9e23b446]99}
100
101bool needsPolySpecialization(
[8f31be6]102                const ast::Type * /*formalType*/,
[9e23b446]103                const ast::Type * actualType,
104                const ast::TypeSubstitution * subs ) {
105        if ( !subs ) {
106                return false;
107        }
108
109        using namespace ResolvExpr;
110        ast::OpenVarSet openVars, closedVars;
[46da46b]111        ast::AssertionSet need, have; // unused
112        ast::TypeEnvironment env; // unused
113        // findOpenVars( formalType, openVars, closedVars, need, have, FirstClosed );
114        findOpenVars( actualType, openVars, closedVars, need, have, env, FirstOpen );
[9e23b446]115        for ( const ast::OpenVarSet::value_type & openVar : openVars ) {
116                const ast::Type * boundType = subs->lookup( openVar.first );
117                // If the variable is not bound, move onto the next variable.
118                if ( !boundType ) continue;
119
120                // Is the variable cound to another type variable?
121                if ( auto inst = dynamic_cast<const ast::TypeInstType *>( boundType ) ) {
122                        if ( closedVars.find( *inst ) == closedVars.end() ) {
123                                return true;
[8f31be6]124                        } else {
[46da46b]125                                assertf(false, "closed: %s", inst->name.c_str());
126                        }
[9e23b446]127                // Otherwise, the variable is bound to a concrete type.
128                } else {
129                        return true;
130                }
131        }
132        // None of the type variables are bound.
133        return false;
134}
135
136bool needsTupleSpecialization(
137                const ast::Type * formalType, const ast::Type * actualType ) {
138        // Needs tuple specialization if the structure of the formal type and
139        // actual type do not match.
140
141        // This is the case if the formal type has ttype polymorphism, or if the structure  of tuple types
142        // between the function do not match exactly.
143        if ( const ast::FunctionType * ftype = getFunctionType( formalType ) ) {
144                // A pack in the parameter or return type requires specialization.
145                if ( ftype->isTtype() ) {
146                        return true;
147                }
148                // Conversion of 0 to a function type does not require specialization.
149                if ( dynamic_cast<const ast::ZeroType *>( actualType ) ) {
150                        return false;
151                }
152                const ast::FunctionType * atype =
153                        getFunctionType( actualType->stripReferences() );
154                assertf( atype,
155                        "formal type is a function type, but actual type is not: %s",
156                        toString( actualType ).c_str() );
157                // Can't tuple specialize if parameter sizes deeply-differ.
158                if ( functionParameterSize( ftype ) != functionParameterSize( atype ) ) {
159                        return false;
160                }
161                // If tuple parameter size matches but actual parameter sizes differ
162                // then there needs to be specialization.
163                if ( ftype->params.size() != atype->params.size() ) {
164                        return true;
165                }
166                // Total parameter size can be the same, while individual parameters
167                // can have different structure.
168                for ( auto pairs : group_iterate( ftype->params, atype->params ) ) {
169                        if ( !isTupleStructureMatching(
170                                        std::get<0>( pairs ), std::get<1>( pairs ) ) ) {
171                                return true;
172                        }
173                }
174        }
175        return false;
176}
177
178bool needsSpecialization(
179                const ast::Type * formalType, const ast::Type * actualType,
180                const ast::TypeSubstitution * subs ) {
181        return needsPolySpecialization( formalType, actualType, subs )
182                || needsTupleSpecialization( formalType, actualType );
183}
184
185ast::ApplicationExpr * SpecializeCore::handleExplicitParams(
186                const ast::ApplicationExpr * expr ) {
187        assert( expr->func->result );
188        const ast::FunctionType * func = getFunctionType( expr->func->result );
189        assert( func );
190
191        ast::ApplicationExpr * mut = ast::mutate( expr );
192
193        std::vector<ast::ptr<ast::Type>>::const_iterator formal;
194        std::vector<ast::ptr<ast::Expr>>::iterator actual;
195        for ( formal = func->params.begin(), actual = mut->args.begin() ;
196                        formal != func->params.end() && actual != mut->args.end() ;
197                        ++formal, ++actual ) {
198                *actual = doSpecialization( (*actual)->location,
199                        *formal, *actual, getInferredParams( expr ) );
200        }
201        return mut;
202}
203
204// Explode assuming simple cases: either type is pure tuple (but not tuple
205// expr) or type is non-tuple.
206template<typename OutputIterator>
207void explodeSimple( const CodeLocation & location,
208                const ast::Expr * expr, OutputIterator out ) {
209        // Recurse on tuple types using index expressions on each component.
210        if ( auto tuple = expr->result.as<ast::TupleType>() ) {
211                ast::ptr<ast::Expr> cleanup = expr;
212                for ( unsigned int i = 0 ; i < tuple->size() ; ++i ) {
213                        explodeSimple( location,
214                                new ast::TupleIndexExpr( location, expr, i ), out );
215                }
216        // For a non-tuple type, output a clone of the expression.
217        } else {
218                *out++ = expr;
219        }
220}
221
[5cf1228]222// Restructures arguments to match the structure of the formal parameters
223// of the actual function. Returns the next structured argument.
[9e23b446]224template<typename Iterator>
225const ast::Expr * structureArg(
226                const CodeLocation& location, const ast::ptr<ast::Type> & type,
227                Iterator & begin, const Iterator & end ) {
[5cf1228]228        if ( auto tuple = type.as<ast::TupleType>() ) {
[9e23b446]229                std::vector<ast::ptr<ast::Expr>> exprs;
[13d326ec]230                for ( const ast::ptr<ast::Type> & t : *tuple ) {
[9e23b446]231                        exprs.push_back( structureArg( location, t, begin, end ) );
232                }
233                return new ast::TupleExpr( location, std::move( exprs ) );
234        } else {
235                assertf( begin != end, "reached the end of the arguments while structuring" );
236                return *begin++;
237        }
238}
239
[c36814a]240struct TypeInstFixer final : public ast::WithShortCircuiting {
241        std::map<const ast::TypeDecl *, std::pair<int, int>> typeMap;
[9e23b446]242
[c36814a]243        void previsit(const ast::TypeDecl *) { visit_children = false; }
244        const ast::TypeInstType * postvisit(const ast::TypeInstType * typeInst) {
245                if (typeMap.count(typeInst->base)) {
246                        ast::TypeInstType * newInst = mutate(typeInst);
247                        auto const & pair = typeMap[typeInst->base];
248                        newInst->expr_id = pair.first;
249                        newInst->formal_usage = pair.second;
250                        return newInst;
[9e23b446]251                }
[c36814a]252                return typeInst;
253        }
254};
[9e23b446]255
256const ast::Expr * SpecializeCore::createThunkFunction(
257                const CodeLocation & location,
258                const ast::FunctionType * funType,
259                const ast::Expr * actual,
260                const ast::InferredParams * inferParams ) {
261        // One set of unique names per program.
262        static UniqueName thunkNamer("_thunk");
263
264        const ast::FunctionType * newType = ast::deepCopy( funType );
265        if ( typeSubs ) {
266                // Must replace only occurrences of type variables
267                // that occure free in the thunk's type.
268                auto result = typeSubs->applyFree( newType );
269                newType = result.node.release();
270        }
271
272        using DWTVector = std::vector<ast::ptr<ast::DeclWithType>>;
273        using DeclVector = std::vector<ast::ptr<ast::TypeDecl>>;
274
275        UniqueName paramNamer( paramPrefix );
276
277        // Create new thunk with same signature as formal type.
278        ast::Pass<TypeInstFixer> fixer;
279        for (const auto & kv : newType->forall) {
280                if (fixer.core.typeMap.count(kv->base)) {
[5cf1228]281                        std::cerr << location << ' ' << kv->base->name
282                                << ' ' << kv->expr_id << '_' << kv->formal_usage
283                                << ',' << fixer.core.typeMap[kv->base].first
284                                << '_' << fixer.core.typeMap[kv->base].second << std::endl;
[9e23b446]285                        assertf(false, "multiple formals in specialize");
286                }
287                else {
288                        fixer.core.typeMap[kv->base] = std::make_pair(kv->expr_id, kv->formal_usage);
289                }
[5cf1228]290        }
[9e23b446]291
292        ast::CompoundStmt * thunkBody = new ast::CompoundStmt( location );
293        ast::FunctionDecl * thunkFunc = new ast::FunctionDecl(
294                location,
295                thunkNamer.newName(),
296                map_range<DeclVector>( newType->forall, []( const ast::TypeInstType * inst ) {
297                        return ast::deepCopy( inst->base );
298                } ),
299                map_range<DWTVector>( newType->assertions, []( const ast::VariableExpr * expr ) {
300                        return ast::deepCopy( expr->var );
301                } ),
302                map_range<DWTVector>( newType->params, [&location, &paramNamer]( const ast::Type * type ) {
303                        return new ast::ObjectDecl( location, paramNamer.newName(), ast::deepCopy( type ) );
304                } ),
305                map_range<DWTVector>( newType->returns, [&location, &paramNamer]( const ast::Type * type ) {
306                        return new ast::ObjectDecl( location, paramNamer.newName(), ast::deepCopy( type ) );
307                } ),
308                thunkBody,
309                ast::Storage::Classes(),
310                ast::Linkage::C
311                );
312
313        thunkFunc->fixUniqueId();
314
315        // Thunks may be generated and not used, avoid them.
316        thunkFunc->attributes.push_back( new ast::Attribute( "unused" ) );
317
318        // Global thunks must be static to avoid collitions.
319        // Nested thunks must not be unique and hence, not static.
320        thunkFunc->storage.is_static = !isInFunction();
321
322        // Weave thunk parameters into call to actual function,
323        // naming thunk parameters as we go.
324        ast::ApplicationExpr * app = new ast::ApplicationExpr( location, actual );
325
326        const ast::FunctionType * actualType = ast::deepCopy( getFunctionType( actual->result ) );
327        if ( typeSubs ) {
328                // Need to apply the environment to the actual function's type,
329                // since it may itself be polymorphic.
330                auto result = typeSubs->apply( actualType );
331                actualType = result.node.release();
332        }
333
334        ast::ptr<ast::FunctionType> actualTypeManager = actualType;
335
336        std::vector<ast::ptr<ast::Expr>> args;
337        for ( ast::ptr<ast::DeclWithType> & param : thunkFunc->params ) {
338                // Name each thunk parameter and explode it.
339                // These are then threaded back into the actual function call.
340                ast::DeclWithType * mutParam = ast::mutate( param.get() );
341                explodeSimple( location, new ast::VariableExpr( location, mutParam ),
342                        std::back_inserter( args ) );
343        }
344
345        // Walk parameters to the actual function alongside the exploded thunk
346        // parameters and restructure the arguments to match the actual parameters.
347        std::vector<ast::ptr<ast::Expr>>::iterator
348                argBegin = args.begin(), argEnd = args.end();
349        for ( const auto & actualArg : actualType->params ) {
[5cf1228]350                app->args.push_back(
351                        structureArg( location, actualArg.get(), argBegin, argEnd ) );
[9e23b446]352        }
353        assertf( argBegin == argEnd, "Did not structure all arguments." );
354
355        app->accept(fixer); // this should modify in place
356
357        app->env = ast::TypeSubstitution::newFromExpr( app, typeSubs );
358        if ( inferParams ) {
359                app->inferred.inferParams() = *inferParams;
360        }
361
362        // Handle any specializations that may still be present.
363        {
364                std::string oldParamPrefix = paramPrefix;
365                paramPrefix += "p";
366                std::list<ast::ptr<ast::Decl>> oldDecls;
367                oldDecls.splice( oldDecls.end(), declsToAddBefore );
368
369                app->accept( *visitor );
370                // Write recursive specializations into the thunk body.
371                for ( const ast::ptr<ast::Decl> & decl : declsToAddBefore ) {
372                        thunkBody->push_back( new ast::DeclStmt( decl->location, decl ) );
373                }
374
375                declsToAddBefore = std::move( oldDecls );
376                paramPrefix = std::move( oldParamPrefix );
377        }
378
379        // Add return (or valueless expression) to the thunk.
380        ast::Stmt * appStmt;
381        if ( funType->returns.empty() ) {
382                appStmt = new ast::ExprStmt( app->location, app );
383        } else {
384                appStmt = new ast::ReturnStmt( app->location, app );
385        }
386        thunkBody->push_back( appStmt );
387
388        // Add the thunk definition:
389        declsToAddBefore.push_back( thunkFunc );
390
391        // Return address of thunk function as replacement expression.
392        return new ast::AddressExpr( location,
393                new ast::VariableExpr( location, thunkFunc ) );
394}
395
396const ast::Expr * SpecializeCore::doSpecialization(
397                const CodeLocation & location,
398                const ast::Type * formalType,
399                const ast::Expr * actual,
400                const ast::InferredParams * inferParams ) {
401        assertf( actual->result, "attempting to specialize an untyped expression" );
402        if ( needsSpecialization( formalType, actual->result, typeSubs ) ) {
403                if ( const ast::FunctionType * type = getFunctionType( formalType ) ) {
404                        if ( const ast::ApplicationExpr * expr =
405                                        dynamic_cast<const ast::ApplicationExpr *>( actual ) ) {
406                                return createThunkFunction( location, type, expr->func, inferParams );
407                        } else if ( auto expr =
408                                        dynamic_cast<const ast::VariableExpr *>( actual ) ) {
409                                return createThunkFunction( location, type, expr, inferParams );
410                        } else {
411                                // (I don't even know what that comment means.)
412                                // This likely won't work, as anything that could build an ApplicationExpr probably hit one of the previous two branches
413                                return createThunkFunction( location, type, actual, inferParams );
414                        }
415                } else {
416                        return actual;
417                }
418        } else {
419                return actual;
420        }
421}
422
423const ast::Expr * SpecializeCore::postvisit(
424                const ast::ApplicationExpr * expr ) {
[e01eb4a]425        if ( ast::isIntrinsicCallExpr( expr ) ) {
[9e23b446]426                return expr;
427        }
428
429        // Create thunks for the inferred parameters.
430        // This is not needed for intrinsic calls, because they aren't
[5cf1228]431        // actually passed to the function. It needs to handle explicit params
432        // before inferred params so that explicit params do not recieve a
433        // changed set of inferParams (and change them again).
434        // Alternatively, if order starts to matter then copy expr's inferParams
435        // and pass them to handleExplicitParams.
[9e23b446]436        ast::ApplicationExpr * mut = handleExplicitParams( expr );
437        if ( !mut->inferred.hasParams() ) {
438                return mut;
439        }
440        ast::InferredParams & inferParams = mut->inferred.inferParams();
441        for ( ast::InferredParams::value_type & inferParam : inferParams ) {
442                inferParam.second.expr = doSpecialization(
443                        inferParam.second.expr->location,
444                        inferParam.second.formalType,
445                        inferParam.second.expr,
446                        getInferredParams( inferParam.second.expr )
447                );
448        }
449        return mut;
450}
451
452const ast::Expr * SpecializeCore::postvisit( const ast::CastExpr * expr ) {
453        if ( expr->result->isVoid() ) {
454                // No specialization if there is no return value.
455                return expr;
456        }
457        const ast::Expr * specialized = doSpecialization(
458                expr->location, expr->result, expr->arg, getInferredParams( expr ) );
459        if ( specialized != expr->arg ) {
460                // Assume that the specialization incorporates the cast.
461                return specialized;
462        } else {
463                return expr;
464        }
465}
466
467} // namespace
468
469void convertSpecializations( ast::TranslationUnit & translationUnit ) {
470        ast::Pass<SpecializeCore>::run( translationUnit );
471}
472
473} // namespace GenPoly
474
475// Local Variables: //
476// tab-width: 4 //
477// mode: c++ //
478// compile-command: "make install" //
479// End: //
Note: See TracBrowser for help on using the repository browser.