summaryrefslogtreecommitdiff
path: root/source/slang/slang-lower-to-ir.cpp
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2023-09-03 12:56:31 -0700
committerGitHub <noreply@github.com>2023-09-03 12:56:31 -0700
commit1d4b5b6fd2433a10cc7ab87626cb560f54b0acbb (patch)
tree6196d519190720fd2968ac7d4b373e3c967d5fe6 /source/slang/slang-lower-to-ir.cpp
parent355bb4287861f96082751042f4e58ff3598b4e5e (diff)
Proper lowering of functiosn that returns NonCopyable values. (#3179)
* Proper lowering of functiosn that returns NonCopyable values. * Fix tests. * Fix clang errors. * Fix. * Fix clang error. --------- Co-authored-by: Yong He <yhe@nvidia.com>
Diffstat (limited to 'source/slang/slang-lower-to-ir.cpp')
-rw-r--r--source/slang/slang-lower-to-ir.cpp1140
1 files changed, 669 insertions, 471 deletions
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index bbd494ee9..ed1da3d25 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -563,6 +563,10 @@ struct IRGenContext
// The IR witness value to use for `ThisType`
IRInst* thisTypeWitness = nullptr;
+ // The return destination parameter to write to at return sites.
+ // (For use by functions that returns non-copyable types)
+ LoweredValInfo returnDestination;
+
bool includeDebugInfo = false;
explicit IRGenContext(SharedIRGenContext* inShared, ASTBuilder* inAstBuilder)
@@ -804,6 +808,11 @@ LoweredValInfo lowerRValueExpr(
IRGenContext* context,
Expr* expr);
+void lowerRValueExprWithDestination(
+ IRGenContext* context,
+ LoweredValInfo destination,
+ Expr* expr);
+
IRType* lowerType(
IRGenContext* context,
Type* type);
@@ -1038,8 +1047,6 @@ LoweredValInfo extractField(
}
}
-
-
LoweredValInfo materialize(
IRGenContext* context,
LoweredValInfo lowered)
@@ -1232,6 +1239,12 @@ void assign(
LoweredValInfo const& left,
LoweredValInfo const& right);
+void assignExpr(
+ IRGenContext* context,
+ const LoweredValInfo& inLeft,
+ Expr* rightExpr,
+ SourceLoc assignmentLoc);
+
IRInst* getAddress(
IRGenContext* context,
LoweredValInfo const& inVal,
@@ -2703,6 +2716,9 @@ struct IRLoweringParameterInfo
// Is this the representation of a `this` parameter?
bool isThisParam = false;
+
+ // Is this the destination of address for non-copyable return val?
+ bool isReturnDestination = false;
};
//
// We need a way to be able to create a `IRLoweringParameterInfo` given the declaration
@@ -2772,6 +2788,19 @@ void addThisParameter(
ioParameterLists->params.add(info);
}
+
+void maybeAddReturnDestinationParam(ParameterLists* ioParameterLists, Type* resultType)
+{
+ if (isNonCopyableType(resultType))
+ {
+ IRLoweringParameterInfo info;
+ info.type = resultType;
+ info.decl = nullptr;
+ info.direction = kParameterDirection_Ref;
+ info.isReturnDestination = true;
+ ioParameterLists->params.add(info);
+ }
+}
//
// And here is our function that will do the recursive walk:
void collectParameterLists(
@@ -2842,6 +2871,7 @@ void collectParameterLists(
{
ioParameterLists->params.add(getParameterInfo(context, paramDeclRef));
}
+ maybeAddReturnDestinationParam(ioParameterLists, getResultType(context->astBuilder, callableDeclRef));
}
}
}
@@ -2883,6 +2913,10 @@ struct FuncDeclBaseTypeInfo
IRType* resultType;
ParameterLists parameterLists;
List<IRType*> paramTypes;
+ // If the function returns a non-copyable value, this
+ // flag is set to indicate that the result should be
+ // returned via the last ref parameter.
+ bool returnViaLastRefParam = false;
};
void _lowerFuncDeclBaseTypeInfo(
@@ -2947,24 +2981,34 @@ void _lowerFuncDeclBaseTypeInfo(
}
auto& irResultType = outInfo.resultType;
- irResultType = lowerType(context, getResultType(context->astBuilder, declRef));
-
- if (auto setterDeclRef = declRef.as<SetterDecl>())
+
+ if (parameterLists.params.getCount() && parameterLists.params.getLast().isReturnDestination)
{
- // A `set` accessor always returns `void`
- //
- // TODO: We should handle this by making the result
- // type of a `set` accessor be represented accurately
- // at the AST level (ditto for the `ref` case below).
- //
- irResultType = builder->getVoidType();
+ irResultType = context->irBuilder->getVoidType();
+ outInfo.returnViaLastRefParam = true;
}
-
- if( auto refAccessorDeclRef = declRef.as<RefAccessorDecl>() )
+ else
{
- // A `ref` accessor needs to return a *pointer* to the value
- // being accessed, rather than a simple value.
- irResultType = builder->getPtrType(irResultType);
+ irResultType = lowerType(context, getResultType(context->astBuilder, declRef));
+
+
+ if (auto setterDeclRef = declRef.as<SetterDecl>())
+ {
+ // A `set` accessor always returns `void`
+ //
+ // TODO: We should handle this by making the result
+ // type of a `set` accessor be represented accurately
+ // at the AST level (ditto for the `ref` case below).
+ //
+ irResultType = builder->getVoidType();
+ }
+
+ if (auto refAccessorDeclRef = declRef.as<RefAccessorDecl>())
+ {
+ // A `ref` accessor needs to return a *pointer* to the value
+ // being accessed, rather than a simple value.
+ irResultType = builder->getPtrType(irResultType);
+ }
}
if (!getErrorCodeType(context->astBuilder, declRef)->equals(context->astBuilder->getBottomType()))
@@ -3023,10 +3067,8 @@ static LoweredValInfo _emitCallToAccessor(
return result;
}
-//
-
template<typename Derived>
-struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
+struct ExprLoweringContext
{
static bool isLValueContext() { return Derived::_isLValueContext(); }
@@ -3035,20 +3077,493 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
IRBuilder* getBuilder() { return context->irBuilder; }
ASTBuilder* getASTBuilder() { return context->astBuilder; }
+
+ struct ResolvedCallInfo
+ {
+ DeclRef<Decl> funcDeclRef;
+ Expr* baseExpr = nullptr;
+ };
+
+ // Try to resolve a the function expression for a call
+ // into a reference to a specific declaration, along
+ // with some contextual information about the declaration
+ // we are calling.
+ bool tryResolveDeclRefForCall(
+ Expr* funcExpr,
+ ResolvedCallInfo* outInfo)
+ {
+ // TODO: unwrap any "identity" expressions that might
+ // be wrapping the callee.
+
+ // First look to see if the expression references a
+ // declaration at all.
+ auto declRefExpr = as<DeclRefExpr>(funcExpr);
+ if (!declRefExpr)
+ return false;
+
+ // A little bit of future proofing here: if we ever
+ // allow higher-order functions, then we might be
+ // calling through a variable/field that has a function
+ // type, but is not itself a function.
+ // In such a case we should be careful to not statically
+ // resolve things.
+ //
+ if (auto callableDecl = as<CallableDecl>(declRefExpr->declRef.getDecl()))
+ {
+ // Okay, the declaration is directly callable, so we can continue.
+ }
+ else
+ {
+ // The callee declaration isn't itself a callable (it must have
+ // a function type, though).
+ return false;
+ }
+
+ // Now we can look at the specific kinds of declaration references,
+ // and try to tease them apart.
+ if (auto memberFuncExpr = as<MemberExpr>(funcExpr))
+ {
+ outInfo->funcDeclRef = memberFuncExpr->declRef;
+ outInfo->baseExpr = memberFuncExpr->baseExpression;
+ return true;
+ }
+ else if (auto staticMemberFuncExpr = as<StaticMemberExpr>(funcExpr))
+ {
+ outInfo->funcDeclRef = staticMemberFuncExpr->declRef;
+ return true;
+ }
+ else if (auto varExpr = as<VarExpr>(funcExpr))
+ {
+ outInfo->funcDeclRef = varExpr->declRef;
+ return true;
+ }
+ else
+ {
+ // Seems to be a case of declaration-reference we don't know about.
+ SLANG_UNEXPECTED("unknown declaration reference kind");
+ //return false;
+ }
+ }
+
+ /// Return `expr` with any outer casts to interface types stripped away
+ Expr* maybeIgnoreCastToInterface(Expr* expr)
+ {
+ auto e = expr;
+ while (auto castExpr = as<CastToSuperTypeExpr>(e))
+ {
+ if (auto declRefType = as<DeclRefType>(e->type))
+ {
+ if (declRefType->getDeclRef().as<InterfaceDecl>())
+ {
+ e = castExpr->valueArg;
+ continue;
+ }
+ }
+ else if (auto andType = as<AndType>(e->type))
+ {
+ // TODO: We might eventually need to tell the difference
+ // between conjunctions of interfaces and conjunctions
+ // that might include non-interface types.
+ //
+ // For now we assume that any case to a conjunction
+ // is effectively a cast to an interface type.
+ //
+ e = castExpr->valueArg;
+ continue;
+ }
+ break;
+ }
+ return e;
+ }
+
+
// Lower an expression that should have the same l-value-ness
// as the visitor itself.
LoweredValInfo lowerSubExpr(Expr* expr)
{
IRBuilderSourceLocRAII sourceLocInfo(getBuilder(), expr->loc);
- return this->dispatch(expr);
+ if (isLValueContext())
+ return lowerLValueExpr(context, expr);
+ return lowerRValueExpr(context, expr);
}
- LoweredValInfo lowerSubExpr(Expr* expr, IRGenContext* subContext)
+ /// Create IR instructions for an argument at a call site, based on
+ /// AST-level expressions plus function signature information.
+ ///
+ /// The `funcType` parameter is always required, and specifies the types
+ /// of all the parameters. The `funcDeclRef` parameter is only required
+ /// if there are parameter positions for which the matching argument is
+ /// absent.
+ ///
+ void addDirectCallArgs(
+ InvokeExpr* expr,
+ Index argIndex,
+ IRType* paramType,
+ ParameterDirection paramDirection,
+ DeclRef<ParamDecl> paramDeclRef,
+ List<IRInst*>* ioArgs,
+ List<OutArgumentFixup>* ioFixups)
{
- IRBuilderSourceLocRAII sourceLocInfo(getBuilder(), expr->loc);
- Derived d;
- d.context = subContext;
- return d.dispatch(expr);
+ Count argCount = expr->arguments.getCount();
+ if (argIndex < argCount)
+ {
+ auto argExpr = expr->arguments[argIndex];
+ addCallArgsForParam(context, paramType, paramDirection, argExpr, ioArgs, ioFixups);
+ }
+ else
+ {
+ // We have run out of arguments supplied at the call site,
+ // but there are still parameters remaining. This must mean
+ // that these parameters have default argument expressions
+ // associated with them.
+ //
+ // Currently we simply extract the initial-value expression
+ // from the parameter declaration and then lower it in
+ // the context of the caller.
+ //
+ // Note that the expression could involve subsitutions because
+ // in the general case it could depend on the generic parameters
+ // used the specialize the callee. For now we do not handle that
+ // case, and simply ignore generic arguments.
+ //
+ SubstExpr<Expr> argExpr = getInitExpr(getASTBuilder(), paramDeclRef);
+ SLANG_ASSERT(argExpr);
+
+ IRGenEnv subEnvStorage;
+ IRGenEnv* subEnv = &subEnvStorage;
+ subEnv->outer = context->env;
+
+ IRGenContext subContextStorage = *context;
+ IRGenContext* subContext = &subContextStorage;
+ subContext->env = subEnv;
+
+ _lowerSubstitutionEnv(subContext, argExpr.getSubsts() ? argExpr.getSubsts().declRef : nullptr);
+
+ addCallArgsForParam(subContext, paramType, paramDirection, argExpr.getExpr(), ioArgs, ioFixups);
+
+ // TODO: The approach we are taking here to default arguments
+ // is simplistic, and has consequences for the front-end as
+ // well as binary serialization of modules.
+ //
+ // We could consider some more refined approaches where, e.g.,
+ // functions with default arguments generate multiple IR-level
+ // functions, that compute and provide the default values.
+ //
+ // Alternatively, each parameter with defaults could be generated
+ // into its own callable function that provides the default value,
+ // so that calling modules can call into a pre-generated function.
+ //
+ // Each of these options involves trade-offs, and we need to
+ // make a conscious decision at some point.
+
+ // Assert that such an expression must have been present.
+ }
+ }
+
+ void addDirectCallArgs(
+ InvokeExpr* expr,
+ FuncType* funcType,
+ List<IRInst*>* ioArgs,
+ List<OutArgumentFixup>* ioFixups)
+ {
+ Count argCount = expr->arguments.getCount();
+ SLANG_ASSERT(argCount == funcType->getParamCount());
+
+ for (Index i = 0; i < argCount; ++i)
+ {
+ IRType* paramType = lowerType(context, funcType->getParamType(i));
+ ParameterDirection paramDirection = funcType->getParamDirection(i);
+ addDirectCallArgs(expr, i, paramType, paramDirection, DeclRef<ParamDecl>(), ioArgs, ioFixups);
+ }
+ }
+
+ void addDirectCallArgs(
+ InvokeExpr* expr,
+ DeclRef<CallableDecl> funcDeclRef,
+ List<IRInst*>* ioArgs,
+ List<OutArgumentFixup>* ioFixups)
+ {
+ Count argCounter = 0;
+ for (auto paramDeclRef : getMembersOfType<ParamDecl>(getASTBuilder(), funcDeclRef))
+ {
+ auto paramDecl = paramDeclRef.getDecl();
+ IRType* paramType = lowerType(context, getType(getASTBuilder(), paramDeclRef));
+ auto paramDirection = getParameterDirection(paramDecl);
+
+ Index argIndex = argCounter++;
+ addDirectCallArgs(expr, argIndex, paramType, paramDirection, paramDeclRef, ioArgs, ioFixups);
+ }
+ }
+
+ // Add arguments that appeared directly in an argument list
+ // to the list of argument values for a call.
+ void addDirectCallArgs(
+ InvokeExpr* expr,
+ DeclRef<Decl> funcDeclRef,
+ List<IRInst*>* ioArgs,
+ List<OutArgumentFixup>* ioFixups)
+ {
+ if (auto callableDeclRef = funcDeclRef.as<CallableDecl>())
+ {
+ addDirectCallArgs(expr, callableDeclRef, ioArgs, ioFixups);
+ }
+ else
+ {
+ SLANG_UNEXPECTED("callee was not a callable decl");
+ }
+ }
+
+ void addFuncBaseArgs(
+ LoweredValInfo funcVal,
+ List<IRInst*>* /*ioArgs*/)
+ {
+ switch (funcVal.flavor)
+ {
+ default:
+ return;
+ }
+ }
+
+
+ void _lowerSubstitutionArg(IRGenContext* subContext, GenericAppDeclRef* subst, Decl* paramDecl, Index argIndex)
+ {
+ SLANG_ASSERT(argIndex < subst->getArgs().getCount());
+ auto argVal = lowerVal(subContext, subst->getArgs()[argIndex]);
+ subContext->setValue(paramDecl, argVal);
+ }
+
+ void _lowerSubstitutionEnv(IRGenContext* subContext, DeclRefBase* subst)
+ {
+ if (!subst) return;
+ _lowerSubstitutionEnv(subContext, subst->getBase());
+
+ if (auto genSubst = as<GenericAppDeclRef>(subst))
+ {
+ auto genDecl = genSubst->getGenericDecl();
+
+ Index argCounter = 0;
+ for (auto memberDecl : genDecl->members)
+ {
+ if (auto typeParamDecl = as<GenericTypeParamDecl>(memberDecl))
+ {
+ _lowerSubstitutionArg(subContext, genSubst, typeParamDecl, argCounter++);
+ }
+ else if (auto valParamDecl = as<GenericValueParamDecl>(memberDecl))
+ {
+ _lowerSubstitutionArg(subContext, genSubst, valParamDecl, argCounter++);
+ }
+ }
+ for (auto memberDecl : genDecl->members)
+ {
+ if (auto constraintDecl = as<GenericTypeConstraintDecl>(memberDecl))
+ {
+ _lowerSubstitutionArg(subContext, genSubst, constraintDecl, argCounter++);
+ }
+ }
+ }
+ // TODO: also need to handle this-type substitution here?
+ }
+
+ /// Lower an invoke expr, and attempt to fuse a store of the expr's result into destination.
+ /// If the store is fused, returns LoweredValInfo::None. Otherwise, returns the IR val representing the RValue.
+ LoweredValInfo visitInvokeExprImpl(InvokeExpr* expr, LoweredValInfo destination, const TryClauseEnvironment& tryEnv)
+ {
+ auto type = lowerType(context, expr->type);
+
+ // We are going to look at the syntactic form of
+ // the "function" expression, so that we can avoid
+ // a lot of complexity that would come from lowering
+ // it as a general expression first, and then trying
+ // to apply it. For example, given `obj.f(a,b)` we
+ // will try to detect that we are trying to compute
+ // something like `ObjType::f(obj, a, b)` (in pseudo-code),
+ // rather than trying to construct a meaningful
+ // intermediate value for `obj.f` first.
+ //
+ // Note that this doe not preclude having support
+ // for directly generating code from `obj.f` - it
+ // just may be that such usage is more complicated.
+
+ // Along the way, we may end up collecting additional
+ // arguments that will be part of the call.
+ List<IRInst*> irArgs;
+
+ // We will also collect "fixup" actions that need
+ // to be performed after the call, in order to
+ // copy the final values for `out` parameters
+ // back to their arguments.
+ List<OutArgumentFixup> argFixups;
+
+ auto funcExpr = expr->functionExpr;
+ ResolvedCallInfo resolvedInfo;
+ if (tryResolveDeclRefForCall(funcExpr, &resolvedInfo))
+ {
+ // In this case we know exactly what declaration we
+ // are going to call, and so we can resolve things
+ // appropriately.
+ auto funcDeclRef = resolvedInfo.funcDeclRef;
+ auto baseExpr = resolvedInfo.baseExpr;
+
+ // If the thing being invoked is a subscript operation,
+ // then we need to handle multiple extra details
+ // that don't arise for other kinds of calls.
+ //
+ // TODO: subscript operations probably deserve to
+ // be handled on their own path for this reason...
+ //
+ if (auto subscriptDeclRef = funcDeclRef.template as<SubscriptDecl>())
+ {
+ // A reference to a subscript declaration is a special case,
+ // because it is not possible to call a subscript directly;
+ // we must call one of its accessors.
+ //
+ auto loweredBase = lowerSubExpr(baseExpr);
+ addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
+ auto result = lowerStorageReference(context, type, subscriptDeclRef, loweredBase, irArgs.getCount(), irArgs.getBuffer());
+
+ // TODO: Applying the fixups for arguments to the subscript at this point
+ // won't technically be correct, since the call to the subscript may
+ // not have occured at this point.
+ //
+ // It seems like we need to either:
+ //
+ // * Capture the arguments to the subscript as `LoweredValInfo` instead of `IRInst*`
+ // so that we can deal with everything related to fixups around the actual call
+ // site.
+ //
+ // OR
+ //
+ // * Handle everything to do with "fixups" differently, by treating them as deferred
+ // actions that gert queued up on the context itself and then flushed at certain
+ // well-defined points, so that we don't have to be as careful around them.
+ //
+ // OR
+ //
+ // * Switch to a more "destination-driven" approach to code generation, where we
+ // can determine on entry to the lowering of a sub-expression whether it will be
+ // used for read, write, or read/write, and resolve things like the choice of
+ // accessor at that point instead.
+ //
+ applyOutArgumentFixups(context, argFixups);
+ return result;
+ }
+
+ // First comes the `this` argument if we are calling
+ // a member function:
+ if (baseExpr)
+ {
+ // The base expression might be an "upcast" to a base interface, in
+ // which case we don't want to emit the result of the cast, but instead
+ // the source.
+ //
+ baseExpr = this->maybeIgnoreCastToInterface(baseExpr);
+
+ auto thisType = getThisParamTypeForCallable(context, funcDeclRef);
+ auto irThisType = lowerType(context, thisType);
+ addCallArgsForParam(
+ context,
+ irThisType,
+ getThisParamDirection(funcDeclRef.getDecl(), kParameterDirection_In),
+ baseExpr,
+ &irArgs,
+ &argFixups);
+ }
+
+ // Then we have the "direct" arguments to the call.
+ // These may include `out` and `inout` arguments that
+ // require "fixup" work on the other side.
+ //
+ FuncDeclBaseTypeInfo funcTypeInfo;
+ _lowerFuncDeclBaseTypeInfo(context, funcDeclRef.template as<FunctionDeclBase>(), funcTypeInfo);
+
+ auto funcType = funcTypeInfo.type;
+ addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
+
+ LoweredValInfo result;
+ if (funcTypeInfo.returnViaLastRefParam)
+ {
+ // If the function returns a non-copyable type, then we need to
+ // pass in the destination that receives the result value as an `__ref` parameter.
+ //
+ if (destination.flavor != LoweredValInfo::Flavor::None)
+ {
+ // If we have a known destination, we can use it directly as argument to the call.
+ irArgs.add(destination.val);
+ result = LoweredValInfo();
+ }
+ else
+ {
+ // Otherwise, we need to create a temporary variable to hold the result.
+ //
+ auto tempVar = context->irBuilder->emitVar(tryGetPointedToType(context->irBuilder, funcTypeInfo.paramTypes.getLast()));
+ irArgs.add(tempVar);
+ result = LoweredValInfo::ptr(tempVar);
+ }
+ }
+
+ auto callResult = emitCallToDeclRef(
+ context,
+ type,
+ funcDeclRef,
+ funcType,
+ irArgs,
+ tryEnv);
+ applyOutArgumentFixups(context, argFixups);
+
+ if (funcTypeInfo.returnViaLastRefParam)
+ return result;
+ return callResult;
+ }
+ else if (auto funcType = as<FuncType>(expr->functionExpr->type))
+ {
+ auto funcVal = lowerRValueExpr(context, expr->functionExpr);
+ addDirectCallArgs(expr, funcType, &irArgs, &argFixups);
+
+ auto result = emitCallToVal(context, type, funcVal, irArgs.getCount(), irArgs.getBuffer(), tryEnv);
+
+ applyOutArgumentFixups(context, argFixups);
+ return result;
+ }
+
+
+ // TODO: In this case we should be emitting code for the callee as
+ // an ordinary expression, then emitting the arguments according
+ // to the type information on the callee (e.g., which parameters
+ // are `out` or `inout`, and then finally emitting the `call`
+ // instruction.
+ //
+ // We don't currently have the case of emitting arguments according
+ // to function type info (instead of declaration info), and really
+ // this case can't occur unless we start adding first-class functions
+ // to the source language.
+ //
+ // For now we just bail out with an error.
+ //
+ SLANG_UNEXPECTED("could not resolve target declaration for call");
+ UNREACHABLE_RETURN(LoweredValInfo());
+ }
+
+};
+
+template<typename Derived>
+struct ExprLoweringVisitorBase : public ExprVisitor<Derived, LoweredValInfo>
+{
+ static bool isLValueContext() { return Derived::_isLValueContext(); }
+
+ ExprLoweringContext<Derived> sharedLoweringContext;
+
+ IRGenContext*& context;
+
+ ExprLoweringVisitorBase()
+ : context(sharedLoweringContext.context)
+ {
+ }
+
+ IRBuilder* getBuilder() { return context->irBuilder; }
+ ASTBuilder* getASTBuilder() { return context->astBuilder; }
+ LoweredValInfo lowerSubExpr(Expr* expr)
+ {
+ return sharedLoweringContext.lowerSubExpr(expr);
}
LoweredValInfo visitIncompleteExpr(IncompleteExpr*)
@@ -3380,12 +3895,17 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
return context->thisVal;
}
+ LoweredValInfo visitReturnValExpr(ReturnValExpr*)
+ {
+ return context->returnDestination;
+ }
+
LoweredValInfo visitMemberExpr(MemberExpr* expr)
{
auto loweredType = lowerType(context, expr->type);
auto baseExpr = expr->baseExpression;
- baseExpr = maybeIgnoreCastToInterface(baseExpr);
+ baseExpr = sharedLoweringContext.maybeIgnoreCastToInterface(baseExpr);
auto loweredBase = lowerSubExpr(baseExpr);
auto declRef = expr->declRef;
@@ -3812,281 +4332,6 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
UNREACHABLE_RETURN(LoweredValInfo());
}
- void _lowerSubstitutionArg(IRGenContext* subContext, GenericAppDeclRef* subst, Decl* paramDecl, Index argIndex)
- {
- SLANG_ASSERT(argIndex < subst->getArgs().getCount());
- auto argVal = lowerVal(subContext, subst->getArgs()[argIndex]);
- subContext->setValue(paramDecl, argVal);
- }
-
- void _lowerSubstitutionEnv(IRGenContext* subContext, DeclRefBase* subst)
- {
- if(!subst) return;
- _lowerSubstitutionEnv(subContext, subst->getBase());
-
- if (auto genSubst = as<GenericAppDeclRef>(subst))
- {
- auto genDecl = genSubst->getGenericDecl();
-
- Index argCounter = 0;
- for( auto memberDecl: genDecl->members )
- {
- if(auto typeParamDecl = as<GenericTypeParamDecl>(memberDecl) )
- {
- _lowerSubstitutionArg(subContext, genSubst, typeParamDecl, argCounter++);
- }
- else if( auto valParamDecl = as<GenericValueParamDecl>(memberDecl) )
- {
- _lowerSubstitutionArg(subContext, genSubst, valParamDecl, argCounter++);
- }
- }
- for( auto memberDecl: genDecl->members )
- {
- if(auto constraintDecl = as<GenericTypeConstraintDecl>(memberDecl) )
- {
- _lowerSubstitutionArg(subContext, genSubst, constraintDecl, argCounter++);
- }
- }
- }
- // TODO: also need to handle this-type substitution here?
- }
-
- /// Create IR instructions for an argument at a call site, based on
- /// AST-level expressions plus function signature information.
- ///
- /// The `funcType` parameter is always required, and specifies the types
- /// of all the parameters. The `funcDeclRef` parameter is only required
- /// if there are parameter positions for which the matching argument is
- /// absent.
- ///
- void addDirectCallArgs(
- InvokeExpr* expr,
- Index argIndex,
- IRType* paramType,
- ParameterDirection paramDirection,
- DeclRef<ParamDecl> paramDeclRef,
- List<IRInst*>* ioArgs,
- List<OutArgumentFixup>* ioFixups)
- {
- Count argCount = expr->arguments.getCount();
- if (argIndex < argCount)
- {
- auto argExpr = expr->arguments[argIndex];
- addCallArgsForParam(context, paramType, paramDirection, argExpr, ioArgs, ioFixups);
- }
- else
- {
- // We have run out of arguments supplied at the call site,
- // but there are still parameters remaining. This must mean
- // that these parameters have default argument expressions
- // associated with them.
- //
- // Currently we simply extract the initial-value expression
- // from the parameter declaration and then lower it in
- // the context of the caller.
- //
- // Note that the expression could involve subsitutions because
- // in the general case it could depend on the generic parameters
- // used the specialize the callee. For now we do not handle that
- // case, and simply ignore generic arguments.
- //
- SubstExpr<Expr> argExpr = getInitExpr(getASTBuilder(), paramDeclRef);
- SLANG_ASSERT(argExpr);
-
- IRGenEnv subEnvStorage;
- IRGenEnv* subEnv = &subEnvStorage;
- subEnv->outer = context->env;
-
- IRGenContext subContextStorage = *context;
- IRGenContext* subContext = &subContextStorage;
- subContext->env = subEnv;
-
- _lowerSubstitutionEnv(subContext, argExpr.getSubsts() ? argExpr.getSubsts().declRef : nullptr);
-
- addCallArgsForParam(subContext, paramType, paramDirection, argExpr.getExpr(), ioArgs, ioFixups);
-
- // TODO: The approach we are taking here to default arguments
- // is simplistic, and has consequences for the front-end as
- // well as binary serialization of modules.
- //
- // We could consider some more refined approaches where, e.g.,
- // functions with default arguments generate multiple IR-level
- // functions, that compute and provide the default values.
- //
- // Alternatively, each parameter with defaults could be generated
- // into its own callable function that provides the default value,
- // so that calling modules can call into a pre-generated function.
- //
- // Each of these options involves trade-offs, and we need to
- // make a conscious decision at some point.
-
- // Assert that such an expression must have been present.
- }
- }
-
- void addDirectCallArgs(
- InvokeExpr* expr,
- FuncType* funcType,
- List<IRInst*>* ioArgs,
- List<OutArgumentFixup>* ioFixups)
- {
- Count argCount = expr->arguments.getCount();
- SLANG_ASSERT(argCount == funcType->getParamCount());
-
- for(Index i = 0; i < argCount; ++i)
- {
- IRType* paramType = lowerType(context, funcType->getParamType(i));
- ParameterDirection paramDirection = funcType->getParamDirection(i);
- addDirectCallArgs(expr, i, paramType, paramDirection, DeclRef<ParamDecl>(), ioArgs, ioFixups);
- }
- }
-
-
- void addDirectCallArgs(
- InvokeExpr* expr,
- DeclRef<CallableDecl> funcDeclRef,
- List<IRInst*>* ioArgs,
- List<OutArgumentFixup>* ioFixups)
- {
- Count argCounter = 0;
- for (auto paramDeclRef : getMembersOfType<ParamDecl>(getASTBuilder(), funcDeclRef))
- {
- auto paramDecl = paramDeclRef.getDecl();
- IRType* paramType = lowerType(context, getType(getASTBuilder(), paramDeclRef));
- auto paramDirection = getParameterDirection(paramDecl);
-
- Index argIndex = argCounter++;
- addDirectCallArgs(expr, argIndex, paramType, paramDirection, paramDeclRef, ioArgs, ioFixups);
- }
- }
-
- // Add arguments that appeared directly in an argument list
- // to the list of argument values for a call.
- void addDirectCallArgs(
- InvokeExpr* expr,
- DeclRef<Decl> funcDeclRef,
- List<IRInst*>* ioArgs,
- List<OutArgumentFixup>* ioFixups)
- {
- if (auto callableDeclRef = funcDeclRef.as<CallableDecl>())
- {
- addDirectCallArgs(expr, callableDeclRef, ioArgs, ioFixups);
- }
- else
- {
- SLANG_UNEXPECTED("callee was not a callable decl");
- }
- }
-
- void addFuncBaseArgs(
- LoweredValInfo funcVal,
- List<IRInst*>* /*ioArgs*/)
- {
- switch (funcVal.flavor)
- {
- default:
- return;
- }
- }
-
- struct ResolvedCallInfo
- {
- DeclRef<Decl> funcDeclRef;
- Expr* baseExpr = nullptr;
- };
-
- // Try to resolve a the function expression for a call
- // into a reference to a specific declaration, along
- // with some contextual information about the declaration
- // we are calling.
- bool tryResolveDeclRefForCall(
- Expr* funcExpr,
- ResolvedCallInfo* outInfo)
- {
- // TODO: unwrap any "identity" expressions that might
- // be wrapping the callee.
-
- // First look to see if the expression references a
- // declaration at all.
- auto declRefExpr = as<DeclRefExpr>(funcExpr);
- if(!declRefExpr)
- return false;
-
- // A little bit of future proofing here: if we ever
- // allow higher-order functions, then we might be
- // calling through a variable/field that has a function
- // type, but is not itself a function.
- // In such a case we should be careful to not statically
- // resolve things.
- //
- if(auto callableDecl = as<CallableDecl>(declRefExpr->declRef.getDecl()))
- {
- // Okay, the declaration is directly callable, so we can continue.
- }
- else
- {
- // The callee declaration isn't itself a callable (it must have
- // a function type, though).
- return false;
- }
-
- // Now we can look at the specific kinds of declaration references,
- // and try to tease them apart.
- if (auto memberFuncExpr = as<MemberExpr>(funcExpr))
- {
- outInfo->funcDeclRef = memberFuncExpr->declRef;
- outInfo->baseExpr = memberFuncExpr->baseExpression;
- return true;
- }
- else if (auto staticMemberFuncExpr = as<StaticMemberExpr>(funcExpr))
- {
- outInfo->funcDeclRef = staticMemberFuncExpr->declRef;
- return true;
- }
- else if (auto varExpr = as<VarExpr>(funcExpr))
- {
- outInfo->funcDeclRef = varExpr->declRef;
- return true;
- }
- else
- {
- // Seems to be a case of declaration-reference we don't know about.
- SLANG_UNEXPECTED("unknown declaration reference kind");
- //return false;
- }
- }
-
- /// Return `expr` with any outer casts to interface types stripped away
- Expr* maybeIgnoreCastToInterface(Expr* expr)
- {
- auto e = expr;
- while( auto castExpr = as<CastToSuperTypeExpr>(e) )
- {
- if(auto declRefType = as<DeclRefType>(e->type))
- {
- if(declRefType->getDeclRef().as<InterfaceDecl>())
- {
- e = castExpr->valueArg;
- continue;
- }
- }
- else if( auto andType = as<AndType>(e->type) )
- {
- // TODO: We might eventually need to tell the difference
- // between conjunctions of interfaces and conjunctions
- // that might include non-interface types.
- //
- // For now we assume that any case to a conjunction
- // is effectively a cast to an interface type.
- //
- e = castExpr->valueArg;
- continue;
- }
- break;
- }
- return e;
- }
-
LoweredValInfo visitSelectExpr(SelectExpr* expr)
{
// A vector typed `select` expr will turn into a normal `select` op.
@@ -4126,158 +4371,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
LoweredValInfo visitInvokeExpr(InvokeExpr* expr)
{
- return visitInvokeExprImpl(expr, TryClauseEnvironment());
- }
-
- LoweredValInfo visitInvokeExprImpl(InvokeExpr* expr, const TryClauseEnvironment& tryEnv)
- {
- auto type = lowerType(context, expr->type);
-
- // We are going to look at the syntactic form of
- // the "function" expression, so that we can avoid
- // a lot of complexity that would come from lowering
- // it as a general expression first, and then trying
- // to apply it. For example, given `obj.f(a,b)` we
- // will try to detect that we are trying to compute
- // something like `ObjType::f(obj, a, b)` (in pseudo-code),
- // rather than trying to construct a meaningful
- // intermediate value for `obj.f` first.
- //
- // Note that this doe not preclude having support
- // for directly generating code from `obj.f` - it
- // just may be that such usage is more complicated.
-
- // Along the way, we may end up collecting additional
- // arguments that will be part of the call.
- List<IRInst*> irArgs;
-
- // We will also collect "fixup" actions that need
- // to be performed after the call, in order to
- // copy the final values for `out` parameters
- // back to their arguments.
- List<OutArgumentFixup> argFixups;
-
- auto funcExpr = expr->functionExpr;
- ResolvedCallInfo resolvedInfo;
- if (tryResolveDeclRefForCall(funcExpr, &resolvedInfo))
- {
- // In this case we know exactly what declaration we
- // are going to call, and so we can resolve things
- // appropriately.
- auto funcDeclRef = resolvedInfo.funcDeclRef;
- auto baseExpr = resolvedInfo.baseExpr;
-
- // If the thing being invoked is a subscript operation,
- // then we need to handle multiple extra details
- // that don't arise for other kinds of calls.
- //
- // TODO: subscript operations probably deserve to
- // be handled on their own path for this reason...
- //
- if (auto subscriptDeclRef = funcDeclRef.template as<SubscriptDecl>())
- {
- // A reference to a subscript declaration is a special case,
- // because it is not possible to call a subscript directly;
- // we must call one of its accessors.
- //
- auto loweredBase = lowerSubExpr(baseExpr);
- addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
- auto result = lowerStorageReference(context, type, subscriptDeclRef, loweredBase, irArgs.getCount(), irArgs.getBuffer());
-
- // TODO: Applying the fixups for arguments to the subscript at this point
- // won't technically be correct, since the call to the subscript may
- // not have occured at this point.
- //
- // It seems like we need to either:
- //
- // * Capture the arguments to the subscript as `LoweredValInfo` instead of `IRInst*`
- // so that we can deal with everything related to fixups around the actual call
- // site.
- //
- // OR
- //
- // * Handle everything to do with "fixups" differently, by treating them as deferred
- // actions that gert queued up on the context itself and then flushed at certain
- // well-defined points, so that we don't have to be as careful around them.
- //
- // OR
- //
- // * Switch to a more "destination-driven" approach to code generation, where we
- // can determine on entry to the lowering of a sub-expression whether it will be
- // used for read, write, or read/write, and resolve things like the choice of
- // accessor at that point instead.
- //
- applyOutArgumentFixups(context, argFixups);
- return result;
- }
-
- // First comes the `this` argument if we are calling
- // a member function:
- if (baseExpr)
- {
- // The base expression might be an "upcast" to a base interface, in
- // which case we don't want to emit the result of the cast, but instead
- // the source.
- //
- baseExpr = maybeIgnoreCastToInterface(baseExpr);
-
- auto thisType = getThisParamTypeForCallable(context, funcDeclRef);
- auto irThisType = lowerType(context, thisType);
- addCallArgsForParam(
- context,
- irThisType,
- getThisParamDirection(funcDeclRef.getDecl(), kParameterDirection_In),
- baseExpr,
- &irArgs,
- &argFixups);
- }
-
- // Then we have the "direct" arguments to the call.
- // These may include `out` and `inout` arguments that
- // require "fixup" work on the other side.
- //
- FuncDeclBaseTypeInfo funcTypeInfo;
- _lowerFuncDeclBaseTypeInfo(context, funcDeclRef.template as<FunctionDeclBase>(), funcTypeInfo);
-
- auto funcType = funcTypeInfo.type;
- addDirectCallArgs(expr, funcDeclRef, &irArgs, &argFixups);
- auto result = emitCallToDeclRef(
- context,
- type,
- funcDeclRef,
- funcType,
- irArgs,
- tryEnv);
- applyOutArgumentFixups(context, argFixups);
- return result;
- }
- else if(auto funcType = as<FuncType>(expr->functionExpr->type))
- {
- auto funcVal = lowerRValueExpr(context, expr->functionExpr);
- addDirectCallArgs(expr, funcType, &irArgs, &argFixups);
-
- auto result = emitCallToVal(context, type, funcVal, irArgs.getCount(), irArgs.getBuffer(), tryEnv);
-
- applyOutArgumentFixups(context, argFixups);
- return result;
- }
-
-
- // TODO: In this case we should be emitting code for the callee as
- // an ordinary expression, then emitting the arguments according
- // to the type information on the callee (e.g., which parameters
- // are `out` or `inout`, and then finally emitting the `call`
- // instruction.
- //
- // We don't currently have the case of emitting arguments according
- // to function type info (instead of declaration info), and really
- // this case can't occur unless we start adding first-class functions
- // to the source language.
- //
- // For now we just bail out with an error.
- //
- SLANG_UNEXPECTED("could not resolve target declaration for call");
- UNREACHABLE_RETURN(LoweredValInfo());
+ return sharedLoweringContext.visitInvokeExprImpl(expr, LoweredValInfo(), TryClauseEnvironment());
}
/// Emit code for a `try` invoke.
@@ -4287,7 +4381,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
assert(invokeExpr);
TryClauseEnvironment tryEnv;
tryEnv.clauseType = expr->tryClauseType;
- return visitInvokeExprImpl(invokeExpr, tryEnv);
+ return sharedLoweringContext.visitInvokeExprImpl(invokeExpr, LoweredValInfo(), tryEnv);
}
/// Emit code to cast `value` to a concrete `superType` (e.g., a `struct`).
@@ -4540,8 +4634,7 @@ struct ExprLoweringVisitorBase : ExprVisitor<Derived, LoweredValInfo>
// based on the resulting values.
//
auto leftVal = lowerLValueExpr(context, expr->left);
- auto rightVal = lowerRValueExpr(context, expr->right);
- assign(context, leftVal, rightVal);
+ assignExpr(context, leftVal, expr->right, expr->loc);
// The result value of the assignment expression is
// the value of the left-hand side (and it is expected
@@ -4780,7 +4873,7 @@ struct LValueExprLoweringVisitor : ExprLoweringVisitorBase<LValueExprLoweringVis
}
};
-struct RValueExprLoweringVisitor : ExprLoweringVisitorBase<RValueExprLoweringVisitor>
+struct RValueExprLoweringVisitor : public ExprLoweringVisitorBase<RValueExprLoweringVisitor>
{
static bool _isLValueContext() { return false; }
@@ -4868,6 +4961,55 @@ struct RValueExprLoweringVisitor : ExprLoweringVisitorBase<RValueExprLoweringVis
}
};
+// ExprLoweringVisitor that fuses the destination assignment.
+//
+struct DestinationDrivenRValueExprLoweringVisitor
+ : ExprVisitor<DestinationDrivenRValueExprLoweringVisitor>
+{
+ ExprLoweringContext<DestinationDrivenRValueExprLoweringVisitor> sharedLoweringContext;
+ LoweredValInfo destination;
+
+ IRGenContext*& context;
+ DestinationDrivenRValueExprLoweringVisitor()
+ : context(sharedLoweringContext.context)
+ {}
+
+ static bool _isLValueContext() { return false; }
+
+ // The default case is lower the rvalue expr independently and then assign to destination.
+ void visitExpr(Expr* expr)
+ {
+ auto rValue = lowerRValueExpr(context, expr);
+ assign(context, destination, rValue);
+ }
+
+ void visitInvokeExpr(InvokeExpr* expr)
+ {
+ LoweredValInfo resultRVal;
+ {
+ IRBuilderSourceLocRAII sourceLocInfo(context->irBuilder, expr->loc);
+ resultRVal = sharedLoweringContext.visitInvokeExprImpl(expr, destination, TryClauseEnvironment{});
+ }
+ if (resultRVal.flavor != LoweredValInfo::Flavor::None)
+ {
+ // If we weren't able to fuse the destination write during lowering rvalue,
+ // we should insert the assign operation now.
+ assign(context, destination, resultRVal);
+ }
+ }
+
+ /// Emit code for a `try` invoke.
+ LoweredValInfo visitTryExpr(TryExpr* expr)
+ {
+ auto invokeExpr = as<InvokeExpr>(expr->base);
+ assert(invokeExpr);
+ TryClauseEnvironment tryEnv;
+ tryEnv.clauseType = expr->tryClauseType;
+ return sharedLoweringContext.visitInvokeExprImpl(invokeExpr, destination, tryEnv);
+ }
+
+};
+
LoweredValInfo lowerLValueExpr(
IRGenContext* context,
Expr* expr)
@@ -4892,6 +5034,17 @@ LoweredValInfo lowerRValueExpr(
return info;
}
+void lowerRValueExprWithDestination(
+ IRGenContext* context,
+ LoweredValInfo destination,
+ Expr* expr)
+{
+ DestinationDrivenRValueExprLoweringVisitor visitor;
+ visitor.context = context;
+ visitor.destination = destination;
+ visitor.dispatch(expr);
+}
+
struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
{
IRGenContext* context;
@@ -5464,6 +5617,14 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
//
if( auto expr = stmt->expression )
{
+ if (context->returnDestination.flavor != LoweredValInfo::Flavor::None)
+ {
+ // If this function should return via a __ref parameter, do that and return void.
+ lowerRValueExprWithDestination(context, context->returnDestination, expr);
+ getBuilder()->emitReturn();
+ return;
+ }
+
// If the AST `return` statement had an expression, then we
// need to lower it to the IR at this point, both to
// compute its value and (in case we are returning a
@@ -6125,6 +6286,30 @@ IRInst* getAddress(
return nullptr;
}
+void assignExpr(
+ IRGenContext* context,
+ const LoweredValInfo& inLeft,
+ Expr* rightExpr,
+ SourceLoc assignmentLoc)
+{
+ auto left = tryGetAddress(context, inLeft, TryGetAddressMode::Default);
+ IRBuilderSourceLocRAII locRAII(context->irBuilder, assignmentLoc);
+ switch (left.flavor)
+ {
+ case LoweredValInfo::Flavor::Ptr:
+ {
+ lowerRValueExprWithDestination(context, left, rightExpr);
+ }
+ break;
+ default:
+ {
+ auto right = lowerRValueExpr(context, rightExpr);
+ assign(context, inLeft, right);
+ }
+ break;
+ }
+}
+
void assign(
IRGenContext* context,
LoweredValInfo const& inLeft,
@@ -7228,6 +7413,8 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
subContextStorage.thisType = outerContext->thisType;
subContextStorage.thisTypeWitness = outerContext->thisTypeWitness;
+
+ subContextStorage.returnDestination = LoweredValInfo();
}
IRBuilder* getBuilder() { return &subBuilderStorage; }
@@ -7381,9 +7568,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
if( auto initExpr = decl->initExpr )
{
- auto initVal = lowerRValueExpr(context, initExpr);
-
- assign(context, varVal, initVal);
+ assignExpr(context, varVal, initExpr, decl->loc);
}
context->setGlobalValue(decl, varVal);
@@ -7395,7 +7580,7 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
{
return Slang::getInterfaceRequirementKey(context, requirementDecl);
}
-
+
LoweredValInfo visitAssocTypeDecl(AssocTypeDecl* decl)
{
SLANG_ASSERT(decl->parentDecl != nullptr);
@@ -8700,6 +8885,9 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
paramVal = LoweredValInfo::ptr(irParam);
+ if (paramInfo.isReturnDestination)
+ subContext->returnDestination = paramVal;
+
// TODO: We might want to copy the pointed-to value into
// a temporary at the start of the function, and then copy
// back out at the end, so that we don't have to worry
@@ -8815,14 +9003,19 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
auto constructorDecl = as<ConstructorDecl>(decl);
if (constructorDecl)
{
- auto thisVar = subContext->irBuilder->emitVar(irResultType);
- subContext->thisVal = LoweredValInfo::ptr(thisVar);
-
- // For class-typed objects, we need to allocate it from heap.
- if (isClassType(irResultType))
+ if (subContext->returnDestination.flavor != LoweredValInfo::Flavor::None)
+ subContext->thisVal = subContext->returnDestination;
+ else
{
- auto allocatedObj = subContext->irBuilder->emitAllocObj(irResultType);
- subContext->irBuilder->emitStore(thisVar, allocatedObj);
+ auto thisVar = subContext->irBuilder->emitVar(irResultType);
+ subContext->thisVal = LoweredValInfo::ptr(thisVar);
+
+ // For class-typed objects, we need to allocate it from heap.
+ if (isClassType(irResultType))
+ {
+ auto allocatedObj = subContext->irBuilder->emitAllocObj(irResultType);
+ subContext->irBuilder->emitStore(thisVar, allocatedObj);
+ }
}
}
@@ -8846,8 +9039,13 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
// path in an initializer/constructor attempts
// to do an early `return;`.
//
- subContext->irBuilder->emitReturn(
- getSimpleVal(subContext, subContext->thisVal));
+ if (subContext->returnDestination.flavor != LoweredValInfo::Flavor::None)
+ subContext->irBuilder->emitReturn();
+ else
+ {
+ subContext->irBuilder->emitReturn(
+ getSimpleVal(subContext, subContext->thisVal));
+ }
}
else if (as<IRVoidType>(irResultType))
{