/*
 * HLSL constant value operations for constant folding
 *
 * Copyright 2022 Francisco Casas for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include <math.h>

#include "hlsl.h"

static bool fold_abs(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = fabsf(src->value.u[k].f);
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = fabs(src->value.u[k].d);
                break;

            case HLSL_TYPE_INT:
                /* C's abs(INT_MIN) is undefined, but HLSL evaluates this to INT_MIN */
                if (src->value.u[k].i == INT_MIN)
                    dst->u[k].i = INT_MIN;
                else
                    dst->u[k].i = abs(src->value.u[k].i);
                break;

            case HLSL_TYPE_BOOL:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                /* Should not occur. */
                vkd3d_unreachable();
        }
    }
    return true;
}

static uint32_t float_to_uint(float x)
{
    if (isnan(x) || x <= 0)
        return 0;

    if (x >= 4294967296.0f)
        return UINT32_MAX;

    return x;
}

static int32_t float_to_int(float x)
{
    if (isnan(x))
        return 0;

    if (x <= -2147483648.0f)
        return INT32_MIN;

    if (x >= 2147483648.0f)
        return INT32_MAX;

    return x;
}

static uint32_t double_to_uint(double x)
{
    if (isnan(x) || x <= 0)
        return 0;

    if (x >= 4294967296.0)
        return UINT32_MAX;

    return x;
}

static int32_t double_to_int(double x)
{
    if (isnan(x))
        return 0;

    if (x <= -2147483648.0)
        return INT32_MIN;

    if (x >= 2147483648.0)
        return INT32_MAX;

    return x;
}

static bool fold_bit_not(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
                dst->u[k].u = ~src->value.u[k].u;
                break;

            default:
                vkd3d_unreachable();
        }
    }

    return true;
}

static bool fold_cast(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    unsigned int k;
    uint32_t u = 0;
    double d = 0.0;
    float f = 0.0f;
    int32_t i = 0;

    for (k = 0; k < src->node.data_type->e.numeric.dimx; ++k)
    {
        switch (src->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                u = float_to_uint(src->value.u[k].f);
                i = float_to_int(src->value.u[k].f);
                f = src->value.u[k].f;
                d = src->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                u = double_to_uint(src->value.u[k].d);
                i = double_to_int(src->value.u[k].d);
                f = src->value.u[k].d;
                d = src->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
                u = src->value.u[k].i;
                i = src->value.u[k].i;
                f = src->value.u[k].i;
                d = src->value.u[k].i;
                break;

            case HLSL_TYPE_UINT:
            case HLSL_TYPE_MIN16UINT:
                u = src->value.u[k].u;
                i = src->value.u[k].u;
                f = src->value.u[k].u;
                d = src->value.u[k].u;
                break;

            case HLSL_TYPE_BOOL:
                u = !!src->value.u[k].u;
                i = !!src->value.u[k].u;
                f = !!src->value.u[k].u;
                d = !!src->value.u[k].u;
                break;
        }

        switch (dst_type->e.numeric.type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = d;
                break;

            case HLSL_TYPE_INT:
                dst->u[k].i = i;
                break;

            case HLSL_TYPE_UINT:
            case HLSL_TYPE_MIN16UINT:
                dst->u[k].u = u;
                break;

            case HLSL_TYPE_BOOL:
                dst->u[k].u = u ? ~0u : 0u;
                break;
        }
    }

    if (src->node.data_type->e.numeric.dimx == 1)
    {
        for (k = 1; k < dst_type->e.numeric.dimx; ++k)
            dst->u[k] = dst->u[0];
    }

    return true;
}

static bool fold_ceil(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = ceilf(src->value.u[k].f);
                break;

            default:
                FIXME("Fold 'ceil' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_clz(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = src->node.data_type->e.numeric.type;
    unsigned int k, v;

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_INT:
                v = src->value.u[k].i < 0 ? ~src->value.u[k].u : src->value.u[k].u;
                break;

            case HLSL_TYPE_UINT:
                v = src->value.u[k].u;
                break;

            default:
                FIXME("Fold 'clz' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }

        dst->u[k].u = v ? vkd3d_log2i(v) ^ 0x1f : ~0u;
    }

    return true;
}

static bool fold_cos(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = cosf(src->value.u[k].f);
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = cos(src->value.u[k].d);
                break;

            default:
                FIXME("Fold 'cos' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_countbits(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_UINT:
                dst->u[k].u = vkd3d_popcount(src->value.u[k].u);
                break;

            default:
                FIXME("Fold 'countbits' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_ctz(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_UINT:
                if (!src->value.u[k].u)
                    dst->u[k].u = ~0u;
                else
                    dst->u[k].u = vkd3d_ctz(src->value.u[k].u);
                break;

            default:
                FIXME("Fold 'ctz' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_exp2(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = exp2f(src->value.u[k].f);
                break;

            default:
                FIXME("Fold 'exp2' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_floor(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = floorf(src->value.u[k].f);
                break;

            default:
                FIXME("Fold 'floor' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_fract(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;
    float i;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = modff(src->value.u[k].f, &i);
                break;

            default:
                FIXME("Fold 'fract' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_log2(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src, const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                if (ctx->profile->major_version >= 4 && src->value.u[k].f < 0.0f)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_NON_FINITE_RESULT,
                            "Indefinite logarithm result.");
                }
                dst->u[k].f = log2f(src->value.u[k].f);
                if (ctx->profile->major_version < 4 && !isfinite(dst->u[k].f))
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NON_FINITE_RESULT,
                            "Infinities and NaNs are not allowed by the shader model.");
                }
                break;

            case HLSL_TYPE_DOUBLE:
                if (src->value.u[k].d < 0.0)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_NON_FINITE_RESULT,
                            "Indefinite logarithm result.");
                }
                dst->u[k].d = log2(src->value.u[k].d);
                break;

            default:
                FIXME("Fold 'log2' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_neg(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = -src->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = -src->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = -src->value.u[k].u;
                break;

            default:
                FIXME("Fold negation for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_not(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_BOOL:
                dst->u[k].u = ~src->value.u[k].u;
                break;

            default:
                FIXME("Fold logic 'not' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_rcp(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src, const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                if (ctx->profile->major_version >= 4 && src->value.u[k].f == 0.0f)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_DIVISION_BY_ZERO,
                            "Floating point division by zero.");
                }
                dst->u[k].f = 1.0f / src->value.u[k].f;
                if (ctx->profile->major_version < 4 && !isfinite(dst->u[k].f))
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_DIVISION_BY_ZERO,
                            "Infinities and NaNs are not allowed by the shader model.");
                }
                break;

            case HLSL_TYPE_DOUBLE:
                if (src->value.u[k].d == 0.0)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_DIVISION_BY_ZERO,
                            "Floating point division by zero.");
                }
                dst->u[k].d = 1.0 / src->value.u[k].d;
                break;

            default:
                FIXME("Fold 'rcp' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_reinterpret(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    unsigned int k;

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        dst->u[k] = src->value.u[k];
    }

    return true;
}

static bool fold_round(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                /* Somewhat unfortunately, constant folded round() rounds
                 * halfway cases towards positive infinity, as opposed to
                 * nearest even like vsir/TPF round_ne. */
                dst->u[k].f = floorf(src->value.u[k].f + 0.5f);
                break;

            default:
                FIXME("Fold 'round' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_rsq(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src, const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                if (ctx->profile->major_version >= 4)
                {
                    if (src->value.u[k].f < 0.0f)
                        hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_IMAGINARY_NUMERIC_RESULT,
                                "Imaginary square root result.");
                    else if (src->value.u[k].f == 0.0f)
                        hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_IMAGINARY_NUMERIC_RESULT,
                                "Floating point division by zero.");
                }
                dst->u[k].f = 1.0f / sqrtf(src->value.u[k].f);
                if (ctx->profile->major_version < 4 && !isfinite(dst->u[k].f))
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NON_FINITE_RESULT,
                            "Infinities and NaNs are not allowed by the shader model.");
                }
                break;

            default:
                FIXME("Fold 'rsq' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_sat(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = min(max(0.0f, src->value.u[k].f), 1.0f);
                break;

            default:
                FIXME("Fold 'sat' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_sin(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst,
        const struct hlsl_type *dst_type, const struct hlsl_ir_constant *src)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = sinf(src->value.u[k].f);
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = sin(src->value.u[k].d);
                break;

            default:
                FIXME("Fold 'sin' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_sqrt(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src, const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                if (ctx->profile->major_version >= 4 && src->value.u[k].f < 0.0f)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_IMAGINARY_NUMERIC_RESULT,
                            "Imaginary square root result.");
                }
                dst->u[k].f = sqrtf(src->value.u[k].f);
                if (ctx->profile->major_version < 4 && !isfinite(dst->u[k].f))
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_NON_FINITE_RESULT,
                            "Infinities and NaNs are not allowed by the shader model.");
                }
                break;

            case HLSL_TYPE_DOUBLE:
                if (src->value.u[k].d < 0.0)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_IMAGINARY_NUMERIC_RESULT,
                            "Imaginary square root result.");
                }
                dst->u[k].d = sqrt(src->value.u[k].d);
                break;

            default:
                FIXME("Fold 'sqrt' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_add(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = src1->value.u[k].f + src2->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = src1->value.u[k].d + src2->value.u[k].d;
                break;

            /* Handling HLSL_TYPE_INT through the unsigned field to avoid
             * undefined behavior with signed integers in C. */
            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = src1->value.u[k].u + src2->value.u[k].u;
                break;

            default:
                FIXME("Fold addition for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_and(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
                dst->u[k].u = src1->value.u[k].u & src2->value.u[k].u;
                break;

            default:
                FIXME("Fold bit/logic and for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_or(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
                dst->u[k].u = src1->value.u[k].u | src2->value.u[k].u;
                break;

            default:
                FIXME("Fold bit/logic or for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_bit_xor(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = src1->value.u[k].u ^ src2->value.u[k].u;
                break;

            default:
                FIXME("Fold bit xor for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_dot(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.dimx == src2->node.data_type->e.numeric.dimx);

    dst->u[0].f = 0.0f;
    for (k = 0; k < src1->node.data_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[0].f += src1->value.u[k].f * src2->value.u[k].f;
                break;
            default:
                FIXME("Fold 'dot' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_dp2add(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2, const struct hlsl_ir_constant *src3)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src3->node.data_type->e.numeric.type);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.dimx == src2->node.data_type->e.numeric.dimx);
    VKD3D_ASSERT(src3->node.data_type->e.numeric.dimx == 1);

    dst->u[0].f = src3->value.u[0].f;
    for (k = 0; k < src1->node.data_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[0].f += src1->value.u[k].f * src2->value.u[k].f;
                break;
            default:
                FIXME("Fold 'dp2add' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_div(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2,
        const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                if (ctx->profile->major_version >= 4 && src2->value.u[k].f == 0)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_DIVISION_BY_ZERO,
                            "Floating point division by zero.");
                }
                dst->u[k].f = src1->value.u[k].f / src2->value.u[k].f;
                if (ctx->profile->major_version < 4 && !isfinite(dst->u[k].f))
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_DIVISION_BY_ZERO,
                            "Infinities and NaNs are not allowed by the shader model.");
                }
                break;

            case HLSL_TYPE_DOUBLE:
                if (src2->value.u[k].d == 0)
                {
                    hlsl_warning(ctx, loc, VKD3D_SHADER_WARNING_HLSL_DIVISION_BY_ZERO,
                            "Floating point division by zero.");
                }
                dst->u[k].d = src1->value.u[k].d / src2->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
                if (src2->value.u[k].i == 0)
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_DIVISION_BY_ZERO,
                            "Division by zero.");
                    return false;
                }
                if (src1->value.u[k].i == INT_MIN && src2->value.u[k].i == -1)
                    dst->u[k].i = INT_MIN;
                else
                    dst->u[k].i = src1->value.u[k].i / src2->value.u[k].i;
                break;

            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                if (src2->value.u[k].u == 0)
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_DIVISION_BY_ZERO,
                            "Division by zero.");
                    return false;
                }
                dst->u[k].u = src1->value.u[k].u / src2->value.u[k].u;
                break;

            default:
                FIXME("Fold division for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_equal(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == HLSL_TYPE_BOOL);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (src1->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].u = src1->value.u[k].f == src2->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].u = src1->value.u[k].d == src2->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
            case HLSL_TYPE_MIN16UINT:
                dst->u[k].u = src1->value.u[k].u == src2->value.u[k].u;
                break;
        }

        dst->u[k].u *= ~0u;
    }
    return true;
}

static bool fold_gequal(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == HLSL_TYPE_BOOL);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (src1->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].u = src1->value.u[k].f >= src2->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].u = src1->value.u[k].d >= src2->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
                dst->u[k].u = src1->value.u[k].i >= src2->value.u[k].i;
                break;

            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
            case HLSL_TYPE_MIN16UINT:
                dst->u[k].u = src1->value.u[k].u >= src2->value.u[k].u;
                break;
        }

        dst->u[k].u *= ~0u;
    }
    return true;
}

static bool fold_less(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == HLSL_TYPE_BOOL);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (src1->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].u = src1->value.u[k].f < src2->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].u = src1->value.u[k].d < src2->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
                dst->u[k].u = src1->value.u[k].i < src2->value.u[k].i;
                break;

            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
            case HLSL_TYPE_MIN16UINT:
                dst->u[k].u = src1->value.u[k].u < src2->value.u[k].u;
                break;
        }

        dst->u[k].u *= ~0u;
    }
    return true;
}

static bool fold_lshift(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(src2->node.data_type->e.numeric.type == HLSL_TYPE_INT);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        unsigned int shift = src2->value.u[k].u % 32;

        switch (src1->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = src1->value.u[k].u << shift;
                break;

            default:
                vkd3d_unreachable();
        }
    }

    return true;
}

static bool fold_mad(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2, const struct hlsl_ir_constant *src3)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src3->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = fmaf(src1->value.u[k].f, src2->value.u[k].f, src3->value.u[k].f);
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = fma(src1->value.u[k].d, src2->value.u[k].d, src3->value.u[k].d);
                break;

            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = src1->value.u[k].u * src2->value.u[k].u + src3->value.u[k].u;
                break;

            default:
                FIXME("Fold 'mad' for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }

    return true;
}

static bool fold_max(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = fmaxf(src1->value.u[k].f, src2->value.u[k].f);
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = fmax(src1->value.u[k].d, src2->value.u[k].d);
                break;

            case HLSL_TYPE_INT:
                dst->u[k].i = max(src1->value.u[k].i, src2->value.u[k].i);
                break;

            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = max(src1->value.u[k].u, src2->value.u[k].u);
                break;

            default:
                FIXME("Fold max for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_min(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = fminf(src1->value.u[k].f, src2->value.u[k].f);
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = fmin(src1->value.u[k].d, src2->value.u[k].d);
                break;

            case HLSL_TYPE_INT:
                dst->u[k].i = min(src1->value.u[k].i, src2->value.u[k].i);
                break;

            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = min(src1->value.u[k].u, src2->value.u[k].u);
                break;

            default:
                FIXME("Fold min for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_mod(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2,
        const struct vkd3d_shader_location *loc)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_INT:
                if (src2->value.u[k].i == 0)
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_DIVISION_BY_ZERO, "Division by zero.");
                    return false;
                }
                if (src1->value.u[k].i == INT_MIN && src2->value.u[k].i == -1)
                    dst->u[k].i = 0;
                else
                    dst->u[k].i = src1->value.u[k].i % src2->value.u[k].i;
                break;

            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                if (src2->value.u[k].u == 0)
                {
                    hlsl_error(ctx, loc, VKD3D_SHADER_ERROR_HLSL_DIVISION_BY_ZERO, "Division by zero.");
                    return false;
                }
                dst->u[k].u = src1->value.u[k].u % src2->value.u[k].u;
                break;

            default:
                FIXME("Fold modulus for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_mul(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    enum hlsl_base_type type = dst_type->e.numeric.type;
    unsigned int k;

    VKD3D_ASSERT(type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].f = src1->value.u[k].f * src2->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].d = src1->value.u[k].d * src2->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = src1->value.u[k].u * src2->value.u[k].u;
                break;

            default:
                FIXME("Fold multiplication for type %s.\n", debug_hlsl_type(ctx, dst_type));
                return false;
        }
    }
    return true;
}

static bool fold_nequal(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == HLSL_TYPE_BOOL);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.type == src2->node.data_type->e.numeric.type);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        switch (src1->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_FLOAT:
            case HLSL_TYPE_HALF:
                dst->u[k].u = src1->value.u[k].f != src2->value.u[k].f;
                break;

            case HLSL_TYPE_DOUBLE:
                dst->u[k].u = src1->value.u[k].d != src2->value.u[k].d;
                break;

            case HLSL_TYPE_INT:
            case HLSL_TYPE_UINT:
            case HLSL_TYPE_BOOL:
            case HLSL_TYPE_MIN16UINT:
                dst->u[k].u = src1->value.u[k].u != src2->value.u[k].u;
                break;
        }

        dst->u[k].u *= ~0u;
    }
    return true;
}

static bool fold_ternary(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2, const struct hlsl_ir_constant *src3)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == src2->node.data_type->e.numeric.type);
    VKD3D_ASSERT(dst_type->e.numeric.type == src3->node.data_type->e.numeric.type);
    VKD3D_ASSERT(src1->node.data_type->e.numeric.type == HLSL_TYPE_BOOL);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
        dst->u[k] = src1->value.u[k].u ? src2->value.u[k] : src3->value.u[k];

    return true;
}

static bool fold_rshift(struct hlsl_ctx *ctx, struct hlsl_constant_value *dst, const struct hlsl_type *dst_type,
        const struct hlsl_ir_constant *src1, const struct hlsl_ir_constant *src2)
{
    unsigned int k;

    VKD3D_ASSERT(dst_type->e.numeric.type == src1->node.data_type->e.numeric.type);
    VKD3D_ASSERT(src2->node.data_type->e.numeric.type == HLSL_TYPE_INT);

    for (k = 0; k < dst_type->e.numeric.dimx; ++k)
    {
        unsigned int shift = src2->value.u[k].u % 32;

        switch (src1->node.data_type->e.numeric.type)
        {
            case HLSL_TYPE_INT:
                dst->u[k].i = src1->value.u[k].i >> shift;
                break;

            case HLSL_TYPE_MIN16UINT:
            case HLSL_TYPE_UINT:
                dst->u[k].u = src1->value.u[k].u >> shift;
                break;

            default:
                vkd3d_unreachable();
        }
    }

    return true;
}

struct hlsl_ir_node *hlsl_fold_constant_exprs(struct hlsl_ctx *ctx,
        struct hlsl_ir_node *instr, struct hlsl_block *block)
{
    struct hlsl_ir_constant *arg1, *arg2 = NULL, *arg3 = NULL;
    struct hlsl_constant_value res = {0};
    struct hlsl_ir_expr *expr;
    unsigned int i;
    bool success;

    if (instr->type != HLSL_IR_EXPR)
        return false;
    expr = hlsl_ir_expr(instr);
    if (!expr->operands[0].node)
        return false;

    if (instr->data_type->class > HLSL_CLASS_VECTOR)
        return false;

    for (i = 0; i < ARRAY_SIZE(expr->operands); ++i)
    {
        if (expr->operands[i].node)
        {
            if (expr->operands[i].node->type != HLSL_IR_CONSTANT)
                return false;
            VKD3D_ASSERT(expr->operands[i].node->data_type->class <= HLSL_CLASS_VECTOR);
        }
    }
    arg1 = hlsl_ir_constant(expr->operands[0].node);
    if (expr->operands[1].node)
        arg2 = hlsl_ir_constant(expr->operands[1].node);
    if (expr->operands[2].node)
        arg3 = hlsl_ir_constant(expr->operands[2].node);

    switch (expr->op)
    {
        case HLSL_OP1_ABS:
            success = fold_abs(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_BIT_NOT:
            success = fold_bit_not(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_CAST:
            success = fold_cast(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_CEIL:
            success = fold_ceil(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_CLZ:
            success = fold_clz(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_COS:
            success = fold_cos(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_COUNTBITS:
            success = fold_countbits(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_CTZ:
            success = fold_ctz(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_EXP2:
            success = fold_exp2(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_FLOOR:
            success = fold_floor(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_FRACT:
            success = fold_fract(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_LOG2:
            success = fold_log2(ctx, &res, instr->data_type, arg1, &instr->loc);
            break;

        case HLSL_OP1_NEG:
            success = fold_neg(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_LOGIC_NOT:
            success = fold_not(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_RCP:
            success = fold_rcp(ctx, &res, instr->data_type, arg1, &instr->loc);
            break;

        case HLSL_OP1_REINTERPRET:
            success = fold_reinterpret(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_ROUND:
            success = fold_round(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_RSQ:
            success = fold_rsq(ctx, &res, instr->data_type, arg1, &instr->loc);
            break;

        case HLSL_OP1_SAT:
            success = fold_sat(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_SIN:
            success = fold_sin(ctx, &res, instr->data_type, arg1);
            break;

        case HLSL_OP1_SQRT:
            success = fold_sqrt(ctx, &res, instr->data_type, arg1, &instr->loc);
            break;

        case HLSL_OP2_ADD:
            success = fold_add(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_BIT_AND:
        case HLSL_OP2_LOGIC_AND:
            success = fold_and(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_BIT_OR:
        case HLSL_OP2_LOGIC_OR:
            success = fold_or(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_BIT_XOR:
            success = fold_bit_xor(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_DOT:
            success = fold_dot(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_DIV:
            success = fold_div(ctx, &res, instr->data_type, arg1, arg2, &instr->loc);
            break;

        case HLSL_OP2_EQUAL:
            success = fold_equal(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_GEQUAL:
            success = fold_gequal(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_LESS:
            success = fold_less(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_LSHIFT:
            success = fold_lshift(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_MAX:
            success = fold_max(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_MIN:
            success = fold_min(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_MOD:
            success = fold_mod(ctx, &res, instr->data_type, arg1, arg2, &instr->loc);
            break;

        case HLSL_OP2_MUL:
            success = fold_mul(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_NEQUAL:
            success = fold_nequal(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP2_RSHIFT:
            success = fold_rshift(ctx, &res, instr->data_type, arg1, arg2);
            break;

        case HLSL_OP3_DP2ADD:
            success = fold_dp2add(ctx, &res, instr->data_type, arg1, arg2, arg3);
            break;

        case HLSL_OP3_MAD:
            success = fold_mad(ctx, &res, instr->data_type, arg1, arg2, arg3);
            break;

        case HLSL_OP3_TERNARY:
            success = fold_ternary(ctx, &res, instr->data_type, arg1, arg2, arg3);
            break;

        default:
            FIXME("Fold \"%s\" expression.\n", debug_hlsl_expr_op(expr->op));
            success = false;
            break;
    }

    if (success)
        return hlsl_block_add_constant(ctx, block, instr->data_type, &res, &instr->loc);

    return NULL;
}

struct hlsl_ir_node *hlsl_fold_constant_identities(struct hlsl_ctx *ctx,
        struct hlsl_ir_node *instr, struct hlsl_block *block)
{
    static const struct hlsl_constant_value zero;
    struct hlsl_ir_constant *const_arg = NULL;
    struct hlsl_ir_node *mut_arg = NULL;
    struct hlsl_ir_expr *expr;
    unsigned int i;

    if (instr->type != HLSL_IR_EXPR)
        return NULL;
    expr = hlsl_ir_expr(instr);

    if (instr->data_type->class > HLSL_CLASS_VECTOR)
        return NULL;

    /* Verify that the expression has two operands. */
    for (i = 0; i < ARRAY_SIZE(expr->operands); ++i)
    {
        if (!!expr->operands[i].node != (i < 2))
            return NULL;
    }

    if (expr->operands[0].node->type == HLSL_IR_CONSTANT)
    {
        const_arg = hlsl_ir_constant(expr->operands[0].node);
        mut_arg = expr->operands[1].node;
    }
    else if (expr->operands[1].node->type == HLSL_IR_CONSTANT)
    {
        mut_arg = expr->operands[0].node;
        const_arg = hlsl_ir_constant(expr->operands[1].node);
    }
    else
    {
        return NULL;
    }

    switch (expr->op)
    {
        case HLSL_OP2_ADD:
            if (hlsl_constant_is_zero(const_arg))
                return mut_arg;
            break;

        case HLSL_OP2_MUL:
            if (hlsl_constant_is_one(const_arg))
                return mut_arg;
            break;

        case HLSL_OP2_LOGIC_AND:
            if (hlsl_constant_is_zero(const_arg))
                return &const_arg->node;
            else if (hlsl_constant_is_one(const_arg))
                return mut_arg;
            break;

        case HLSL_OP2_LOGIC_OR:
            if (hlsl_constant_is_zero(const_arg))
                return mut_arg;
            else if (hlsl_constant_is_one(const_arg))
                return &const_arg->node;
            break;

        case HLSL_OP2_LESS:
            /* x < 0 -> false, if x is unsigned. */
            if (!hlsl_type_is_unsigned_integer(expr->operands[0].node->data_type)
                    || expr->operands[1].node->type != HLSL_IR_CONSTANT
                    || !hlsl_constant_is_zero(hlsl_ir_constant(expr->operands[1].node)))
                break;
            return hlsl_block_add_constant(ctx, block, instr->data_type, &zero, &instr->loc);

        default:
            break;
    }

    return NULL;
}

static bool is_op_associative(enum hlsl_ir_expr_op op, enum hlsl_base_type type)
{
    switch (op)
    {
        case HLSL_OP2_ADD:
        case HLSL_OP2_MUL:
            return hlsl_base_type_is_integer(type);

        case HLSL_OP2_BIT_AND:
        case HLSL_OP2_BIT_OR:
        case HLSL_OP2_BIT_XOR:
        case HLSL_OP2_LOGIC_AND:
        case HLSL_OP2_LOGIC_OR:
        case HLSL_OP2_MAX:
        case HLSL_OP2_MIN:
            return true;

        default:
            return false;
    }
}

static bool is_op_commutative(enum hlsl_ir_expr_op op)
{
    switch (op)
    {
        case HLSL_OP2_ADD:
        case HLSL_OP2_BIT_AND:
        case HLSL_OP2_BIT_OR:
        case HLSL_OP2_BIT_XOR:
        case HLSL_OP2_DOT:
        case HLSL_OP2_LOGIC_AND:
        case HLSL_OP2_LOGIC_OR:
        case HLSL_OP2_MAX:
        case HLSL_OP2_MIN:
        case HLSL_OP2_MUL:
            return true;

        default:
            return false;
    }
}

/* Returns true iff x OPL (y OPR z) = (x OPL y) OPR (x OPL z). */
static bool is_op_left_distributive(enum hlsl_ir_expr_op opl, enum hlsl_ir_expr_op opr, enum hlsl_base_type type)
{
    switch (opl)
    {
        case HLSL_OP2_BIT_AND:
            return opr == HLSL_OP2_BIT_OR || opr == HLSL_OP2_BIT_XOR;

        case HLSL_OP2_BIT_OR:
            return opr == HLSL_OP2_BIT_AND;

        case HLSL_OP2_DOT:
        case HLSL_OP2_MUL:
            return opr == HLSL_OP2_ADD && hlsl_base_type_is_integer(type);

        case HLSL_OP2_MAX:
            return opr == HLSL_OP2_MIN;

        case HLSL_OP2_MIN:
            return opr == HLSL_OP2_MAX;

        default:
            return false;
    }
}

/* Attempt to collect together the expression (x OPL a) OPR (x OPL b) -> x OPL (a OPR b). */
static struct hlsl_ir_node *collect_exprs(struct hlsl_ctx *ctx, struct hlsl_block *block, struct hlsl_ir_node *instr,
        enum hlsl_ir_expr_op opr, struct hlsl_ir_node *node1, struct hlsl_ir_node *node2)
{
    enum hlsl_base_type type = instr->data_type->e.numeric.type;
    struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {0};
    struct hlsl_ir_expr *e1, *e2;
    enum hlsl_ir_expr_op opl;

    if (!node1 || !node2 || node1->type != HLSL_IR_EXPR || node2->type != HLSL_IR_EXPR)
        return NULL;
    e1 = hlsl_ir_expr(node1);
    e2 = hlsl_ir_expr(node2);
    opl = e1->op;

    if (e2->op != opl || !is_op_left_distributive(opl, opr, type))
        return NULL;
    if (e1->operands[0].node != e2->operands[0].node)
        return NULL;
    if (e1->operands[1].node->type != HLSL_IR_CONSTANT || e2->operands[1].node->type != HLSL_IR_CONSTANT)
        return NULL;

    operands[0] = e1->operands[0].node;
    operands[1] = hlsl_block_add_binary_expr(ctx, block, opr, e1->operands[1].node, e2->operands[1].node);
    return hlsl_block_add_expr(ctx, block, opl, operands, instr->data_type, &instr->loc);
}

struct hlsl_ir_node *hlsl_fold_binary_exprs(struct hlsl_ctx *ctx, struct hlsl_ir_node *instr, struct hlsl_block *block)
{
    struct hlsl_ir_node *arg1, *arg2, *tmp;
    struct hlsl_ir_expr *expr;
    enum hlsl_base_type type;
    enum hlsl_ir_expr_op op;
    bool progress = false;

    if (instr->type != HLSL_IR_EXPR)
        return NULL;
    expr = hlsl_ir_expr(instr);

    if (instr->data_type->class > HLSL_CLASS_VECTOR)
        return NULL;

    if (expr->operands[2].node)
        return NULL;

    arg1 = expr->operands[0].node;
    arg2 = expr->operands[1].node;
    type = instr->data_type->e.numeric.type;
    op = expr->op;

    if (!arg1 || !arg2)
        return NULL;

    /* (x OPL a) OPR (x OPL b) -> x OPL (a OPR b) */
    if ((tmp = collect_exprs(ctx, block, instr, op, arg1, arg2)))
        return tmp;

    if (is_op_commutative(op) && arg1->type == HLSL_IR_CONSTANT && arg2->type != HLSL_IR_CONSTANT)
    {
        /* a OP x -> x OP a */
        tmp = arg1;
        arg1 = arg2;
        arg2 = tmp;
        progress = true;
    }

    if (is_op_associative(op, type))
    {
        struct hlsl_ir_expr *e1 = arg1->type == HLSL_IR_EXPR ? hlsl_ir_expr(arg1) : NULL;
        struct hlsl_ir_expr *e2 = arg2->type == HLSL_IR_EXPR ? hlsl_ir_expr(arg2) : NULL;

        if (e1 && e1->op == op && e1->operands[0].node->type != HLSL_IR_CONSTANT
                && e1->operands[1].node->type == HLSL_IR_CONSTANT)
        {
            if (arg2->type == HLSL_IR_CONSTANT)
            {
                /* (x OP a) OP b -> x OP (a OP b) */
                arg1 = e1->operands[0].node;
                arg2 = hlsl_block_add_binary_expr(ctx, block, op, e1->operands[1].node, arg2);
                progress = true;
            }
            else if (is_op_commutative(op))
            {
                /* (x OP a) OP y -> (x OP y) OP a */
                arg1 = hlsl_block_add_binary_expr(ctx, block, op, e1->operands[0].node, arg2);
                arg2 = e1->operands[1].node;
                progress = true;
            }
        }

        if (!progress && arg1->type != HLSL_IR_CONSTANT && e2 && e2->op == op
                && e2->operands[0].node->type != HLSL_IR_CONSTANT && e2->operands[1].node->type == HLSL_IR_CONSTANT)
        {
            /* x OP (y OP a) -> (x OP y) OP a */
            arg1 = hlsl_block_add_binary_expr(ctx, block, op, arg1, e2->operands[0].node);
            arg2 = e2->operands[1].node;
            progress = true;
        }

        if (!progress && e1 && e1->op == op
                && (tmp = collect_exprs(ctx, block, instr, op, e1->operands[1].node, arg2)))
        {
            /* (y OPR (x OPL a)) OPR (x OPL b) -> y OPR (x OPL (a OPR b)) */
            arg1 = e1->operands[0].node;
            arg2 = tmp;
            progress = true;
        }

        if (!progress && is_op_commutative(op) && e1 && e1->op == op
                && (tmp = collect_exprs(ctx, block, instr, op, e1->operands[0].node, arg2)))
        {
            /* ((x OPL a) OPR y) OPR (x OPL b) -> (x OPL (a OPR b)) OPR y */
            arg1 = tmp;
            arg2 = e1->operands[1].node;
            progress = true;
        }

        if (!progress && e2 && e2->op == op
                && (tmp = collect_exprs(ctx, block, instr, op, arg1, e2->operands[0].node)))
        {
            /* (x OPL a) OPR ((x OPL b) OPR y) -> (x OPL (a OPR b)) OPR y */
            arg1 = tmp;
            arg2 = e2->operands[1].node;
            progress = true;
        }

        if (!progress && is_op_commutative(op) && e2 && e2->op == op
                && (tmp = collect_exprs(ctx, block, instr, op, arg1, e2->operands[1].node)))
        {
            /* (x OPL a) OPR (y OPR (x OPL b)) -> (x OPL (a OPR b)) OPR y */
            arg1 = tmp;
            arg2 = e2->operands[0].node;
            progress = true;
        }
    }

    if (progress)
    {
        struct hlsl_ir_node *operands[HLSL_MAX_OPERANDS] = {arg1, arg2};

        return hlsl_block_add_expr(ctx, block, op, operands, instr->data_type, &instr->loc);
    }

    return NULL;
}

struct hlsl_ir_node *hlsl_fold_constant_swizzles(struct hlsl_ctx *ctx,
        struct hlsl_ir_node *instr, struct hlsl_block *block)
{
    struct hlsl_constant_value value;
    struct hlsl_ir_swizzle *swizzle;
    struct hlsl_ir_constant *src;
    unsigned int i;

    if (instr->type != HLSL_IR_SWIZZLE)
        return NULL;
    swizzle = hlsl_ir_swizzle(instr);
    if (swizzle->val.node->type != HLSL_IR_CONSTANT)
        return NULL;
    src = hlsl_ir_constant(swizzle->val.node);

    for (i = 0; i < swizzle->node.data_type->e.numeric.dimx; ++i)
        value.u[i] = src->value.u[hlsl_swizzle_get_component(swizzle->u.vector, i)];

    return hlsl_block_add_constant(ctx, block, instr->data_type, &value, &instr->loc);
}
