HLSL: add intrinsic function implicit promotions

This PR handles implicit promotions for intrinsics when there is no exact match,
such as for example clamp(int, bool, float).  In this case the int and bool will
be promoted to a float, and the clamp(float, float, float) form used.

These promotions can be mixed with shape conversions, e.g, clamp(int, bool2, float2).

Output conversions are handled either via the existing addOutputArgumentConversion
function, which this PR generalizes to handle either aggregates or unaries, or by
intrinsic decomposition.  If there are methods or intrinsics to be decomposed,
then decomposition is responsible for any output conversions, which turns out to
happen automatically in all current cases.  This can be revisited once inout
conversions are in place.

Some cases of actual ambiguity were fixed in several tests, e.g, spv.register.autoassign.*

Some intrinsics with only uint versions were expanded to signed ints natively, where the
underlying AST and SPIR-V supports that.  E.g, countbits.  This avoids extraneous
conversion nodes.

A new function promoteAggregate is added, and used by findFunction.  This is essentially
a generalization of the "promote 1st or 2nd arg" algorithm in promoteBinary.

The actual selection proceeds in three steps, as described in the comments in
hlslParseContext::findFunction:

1. Attempt an exact match.  If found, use it.
2. If not, obtain the operator from step 1, and promote arguments.
3. Re-select the intrinsic overload from the results of step 2.
This commit is contained in:
steve-lunarg 2016-11-02 12:42:34 -06:00
parent 1c573fbcfb
commit ef33ec0925
19 changed files with 5453 additions and 3261 deletions

View file

@ -45,6 +45,7 @@
#include "propagateNoContraction.h"
#include <cfloat>
#include <utility>
namespace glslang {
@ -575,6 +576,27 @@ TIntermTyped* TIntermediate::addConversion(TOperator op, const TType& type, TInt
case EOpDivAssign:
case EOpModAssign:
case EOpAtan:
case EOpClamp:
case EOpCross:
case EOpDistance:
case EOpDot:
case EOpDst:
case EOpFaceForward:
case EOpFma:
case EOpFrexp:
case EOpLdexp:
case EOpMix:
case EOpLit:
case EOpMax:
case EOpMin:
case EOpModf:
case EOpPow:
case EOpReflect:
case EOpRefract:
case EOpSmoothStep:
case EOpStep:
case EOpSequence:
case EOpConstructStruct:
@ -833,6 +855,9 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
if (profile == EEsProfile || version == 110)
return false;
if (from == to)
return true;
// TODO: Move more policies into language-specific handlers.
// Some languages allow more general (or potentially, more specific) conversions under some conditions.
if (source == EShSourceHlsl) {
@ -901,6 +926,8 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
return version >= 400;
case EbtUint:
return true;
case EbtBool:
return (source == EShSourceHlsl);
default:
return false;
}
@ -908,6 +935,8 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
switch (from) {
case EbtInt:
return true;
case EbtBool:
return (source == EShSourceHlsl);
default:
return false;
}
@ -1747,6 +1776,9 @@ bool TIntermediate::promote(TIntermOperator* node)
if (node->getAsBinaryNode())
return promoteBinary(*node->getAsBinaryNode());
if (node->getAsAggregate())
return promoteAggregate(*node->getAsAggregate());
return false;
}
@ -2190,6 +2222,77 @@ bool TIntermediate::promoteBinary(TIntermBinary& node)
return true;
}
//
// See TIntermediate::promote
//
bool TIntermediate::promoteAggregate(TIntermAggregate& node)
{
TOperator op = node.getOp();
TIntermSequence& args = node.getSequence();
const int numArgs = args.size();
// Presently, only hlsl does intrinsic promotions.
if (getSource() != EShSourceHlsl)
return true;
// set of opcodes that can be promoted in this manner.
switch (op) {
case EOpAtan:
case EOpClamp:
case EOpCross:
case EOpDistance:
case EOpDot:
case EOpDst:
case EOpFaceForward:
// case EOpFindMSB: TODO: ??
// case EOpFindLSB: TODO: ??
case EOpFma:
case EOpMod:
case EOpFrexp:
case EOpLdexp:
case EOpMix:
case EOpLit:
case EOpMax:
case EOpMin:
case EOpModf:
// case EOpGenMul: TODO: ??
case EOpPow:
case EOpReflect:
case EOpRefract:
// case EOpSinCos: TODO: ??
case EOpSmoothStep:
case EOpStep:
break;
default:
return true;
}
// TODO: array and struct behavior
// Try converting all nodes to the given node's type
TIntermSequence convertedArgs(numArgs, nullptr);
// Try to convert all types to the nonConvArg type.
for (int nonConvArg = 0; nonConvArg < numArgs; ++nonConvArg) {
// Try converting all args to this arg's type
for (int convArg = 0; convArg < numArgs; ++convArg) {
convertedArgs[convArg] = addConversion(op, args[nonConvArg]->getAsTyped()->getType(),
args[convArg]->getAsTyped());
}
// If we successfully converted all the args, use the result.
if (std::all_of(convertedArgs.begin(), convertedArgs.end(),
[](const TIntermNode* node) { return node != nullptr; })) {
std::swap(args, convertedArgs);
return true;
}
}
return false;
}
void TIntermBinary::updatePrecision()
{
#ifdef AMD_EXTENSIONS